-
Enhancement
-
Resolution: Won't Fix
-
P4
-
None
-
1.1.6, 1.1.7
-
x86, sparc
-
solaris_2.5.1, windows_nt
When using Toolkit.prepareImage() and an ImageObserver to wait
until an image has been loaded, we've encountered glitches with
the support for animated images (GIF's, in this case)
The ImageObserver gets repeatedly called with various infoflags,
For a non-animated images, you generally get WIDTH and HEIGHT,
then a lot of SOMEBITS, then a single ALLBITS call.
For animated images, you get WIDTH and HEIGHT, then some SOMEBITS
calls, then a repeating series of FRAMEBITS calls as each
frame of the image is delivered. You never receive an ALLBITS
call.
We wish to wait until an image has been loaded, so we call
prepareImage(), and pass in an ImageObserver. The ImageObserver
returns "false" as soon as it receives a FRAMEBITS or ALLBITS
invocation. When "false" is returned, sun.awt.image.ImageRepresentation
calls its removeWatcher() function, which calls checkConsumption().
This last function's responsibility is to discard the data for any
partially loaded image if no one is watching the image. The code
reads:
if (watchers == null && numWaiters == 0
&& (availInfo & ImageObserver.ALLBITS) == 0)
dispose();
Dispose contains the following (correct) line of code:
availinfo &= ~(ImageObserver.SOMEBITS
| ImageObserver.FRAMEBITS
| ImageObserver.ALLBITS);
For non-animated images, there's no issue here; once the
image has been loaded, ALLBITS is set in availinfo, so
checkConsumption() won't call dispose(). For an animated gif,
FRAMEBITS is set, but ALLBITS isn't, so dispose() is called.
Where this really bites us is that, in a separate thread, we're
wait()ing for the ImageObserver to notifyAll() us that FRAMEBITS
has been received. The wait() is wrapped in a while() loop
that calls Toolkit.checkImage() to check if we were notified because
of an error or because the image was loaded. However, because
of the dispose() call, availinfo no longer contains FRAMEBITS,
since the data has been disposed, so our code thinks the image
hasn't been loaded, and then loops back in to wait until the
image does become loaded correctly. This behavior causes hangs.
The hangs are a bit unpredictable, given the multi-threaded nature
of the bug, but it should be possible to write a test case that
always reproduces this bug.
The bug can be fixed, I believe, by changing the code in checkConsumption()
to read:
if (watchers == null && numWaiters == 0
&& (availInfo & (ImageObserver.ALLBITS |
ImageObserver.FRAMEBITS)) == 0)
The following code will reproduce the problem. To run it, place a
.gif file in the same directory as this compiled class, and pass
the name of the file as an argument, e.g. "java ImageLoader stop.gif"
For an animated .gif, you'll see the lines "Waiting!", "Notifying!",
"Waiting!". For a non-animated .gif, the results are "Waiting!",
"Notifying!", and "Done!", and the program completes.
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
public class ImageLoader implements ImageObserver
{
static public void main(String[] args)
{
try
{
URL imageURL = ImageLoader.class.getResource(args[0]);
Object urlContent = imageURL.getContent();
ImageProducer producer = (ImageProducer) urlContent;
loadImage(Toolkit.getDefaultToolkit().createImage(producer));
}
catch (IOException e) {}
System.out.println("Done!");
System.exit(0);
}
static public void loadImage(Image image)
{
ImageLoader loader = new ImageLoader();
Toolkit.getDefaultToolkit().prepareImage(image, -1, -1, loader);
loader.waitFor(image);
}
public synchronized void waitFor(Image image)
{
Toolkit tk = Toolkit.getDefaultToolkit();
int status;
while (((status = tk.checkImage(image, -1, -1, this)) &
(ImageObserver.ALLBITS | ImageObserver.FRAMEBITS |
ImageObserver.ERROR | ImageObserver.ABORT)) == 0)
{
try
{
System.out.println("Waiting!");
wait();
// This sleep() isn't strictly necessary, but it makes the bug
// easily reproducible
Thread.sleep(500);
}
catch (InterruptedException e) { return; }
}
}
public synchronized boolean imageUpdate(Image img, int infoflags,
int x, int y, int width, int height)
{
if ((infoflags & (ImageObserver.ALLBITS | ImageObserver.FRAMEBITS |
ImageObserver.ERROR | ImageObserver.ABORT)) != 0)
{
System.out.println("Notifying!");
notifyAll();
return false;
}
return true;
}
}