Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4154196

Toolkit.prepareImage() hangs when using ImageObserver to load animated GIF

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Won't Fix
    • Icon: P4 P4
    • None
    • 1.1.6, 1.1.7
    • client-libs
    • 2d
    • 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;
        }
      }

            tdv Dmitri Trembovetski (Inactive)
            mmuellersunw Marianne Mueller (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: