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

ToolkitImage renders some gifs wrong

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      Observed using a local build of OpenJDK 25 on Mac 15.3.1

      A DESCRIPTION OF THE PROBLEM :
      Sometimes Toolkit.createImage() creates Images that do not render correctly. I think (?) I see two separate issues:

      A. Sometimes -- usually towards the bottom of frames -- the scanline appears messed up. For example: a vertical line may appear OK at y=400, but offset at y=401.
      B. Sometimes the background color appears wrong.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached program with the this folder of images:


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      All tests should pass
      ACTUAL -
      Several tests fail

      ---------- BEGIN SOURCE ----------
      package com.hippogif;

      import javax.imageio.ImageIO;
      import javax.tools.Tool;
      import java.awt.*;
      import java.awt.image.BufferedImage;
      import java.awt.image.ColorModel;
      import java.awt.image.ImageConsumer;
      import java.awt.image.IndexColorModel;
      import java.io.File;
      import java.io.FilenameFilter;
      import java.io.IOException;
      import java.net.MalformedURLException;
      import java.util.HashMap;
      import java.util.Hashtable;
      import java.util.Map;
      import java.util.concurrent.Semaphore;
      import java.util.concurrent.atomic.AtomicReference;

      public class GifImageTest {

          /**
           * This scans a folder named 'gif-files'. This folder contains a set of gifs, and some png images that
           * represent the *expected* frames. For example the file "abc[3].png" refers to the 4th frame of "abc.gif".
           * <p>
           * The initial set of files provided for this ticket include a few passing examples just to prove that
           * the renderer in this test is working correctly.
           * <p>
           * You can also observe these discrepancies visually if you call <code>new JLabel(new ImageIcon(toolkitImage))</code>,
           * however that will be animating so it's hard to do a frame-by-frame comparison.
           * <p>
           * This test may takes about 20s to complete because the ToolkitImage only reveals new frames after calling
           * Thread.sleep(..)
           */
          public static void main(String[] args) throws IOException {
              File dir = new File("gif-files");

              Map<String, File> gifsByFilename = new HashMap<>();
              for (File gifFile : dir.listFiles((dir1, name) -> name.endsWith(".gif"))) {
                  String name = gifFile.getName();
                  name = name.substring(0, name.length() - ".gif".length());
                  gifsByFilename.put(name, gifFile);
              }

              boolean allTestsPassed = true;
              for (File pngFile : dir.listFiles((dir1, name) -> name.endsWith(".png"))) {
                  String name = pngFile.getName();
                  int i = name.indexOf('[');
                  String gifName = name.substring(0,i);
                  int frameIndex = Integer.parseInt(name.substring(i + 1, name.length() - "].png".length()));

                  System.out.println("Testing " + pngFile.getPath());

                  BufferedImage expectedFrame = ImageIO.read(pngFile);

                  File gifFile = gifsByFilename.get(gifName);
                  BufferedImage actualFrame = getFrame(gifFile, frameIndex);

                  // this diff image is not officially part of this test, but it's helpful to visually
                  // compare the two images in debugger mode:
                  BufferedImage diff = new BufferedImage(actualFrame.getWidth(), actualFrame.getHeight(), BufferedImage.TYPE_INT_ARGB);
                  Graphics2D g = diff.createGraphics();
                  g.drawImage(expectedFrame, 0, 0, null);
                  g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
                  g.drawImage(actualFrame, 0, 0, null);
                  g.dispose();

                  boolean passed = testEquals(expectedFrame, actualFrame);
                  if (!passed) {
                      allTestsPassed = false;
                      System.out.println("\tfailed");
                  } else {
                      System.out.println("\tpassed");
                  }
              }

              if (!allTestsPassed)
                  throw new Error("One or more tests failed.");
          }

          private static boolean testEquals(BufferedImage expectedImage, BufferedImage actualImage) {
              if (expectedImage.getWidth() != actualImage.getWidth())
                  return false;
              if (expectedImage.getHeight() != actualImage.getHeight())
                  return false;
              int tolerance = 50;

              for (int y = 0; y < expectedImage.getHeight(); y++) {
                  for (int x = 0; x < expectedImage.getWidth(); x++) {
                      int argb1 = expectedImage.getRGB(x, y);
                      int argb2 = actualImage.getRGB(x, y);

                      int a1 = (argb1 >> 24) & 0xff;
                      int r1 = (argb1 >> 16) & 0xff;
                      int g1 = (argb1 >> 8) & 0xff;
                      int b1 = (argb1 >> 0) & 0xff;

                      int a2 = (argb2 >> 24) & 0xff;
                      int r2 = (argb2 >> 16) & 0xff;
                      int g2 = (argb2 >> 8) & 0xff;
                      int b2 = (argb2 >> 0) & 0xff;

                      // transparency should be 0% or 100%
                      if (a1 != a2)
                          return false;

                      if (a1 == 255) {
                          if (Math.abs(r1 - r2) > tolerance)
                              return false;
                          if (Math.abs(g1 - g2) > tolerance)
                              return false;
                          if (Math.abs(b1 - b2) > tolerance)
                              return false;
                      }
                  }
              }
              return true;
          }

          /**
           * @param gifFile
           * @param frameIndex
           * @return
           * @throws IOException
           */
          private static BufferedImage getFrame(File gifFile, int frameIndex) throws IOException {
              Image image = Toolkit.getDefaultToolkit().createImage(gifFile.toURI().toURL());
              AtomicReference<BufferedImage> returnValue = new AtomicReference<>();

              Semaphore semaphore = new Semaphore(1);
              semaphore.acquireUninterruptibly();
              image.getSource().startProduction(new ImageConsumer() {
                  BufferedImage bi;
                  int frameCtr = 0;

                  @Override
                  public void setDimensions(int width, int height) {
                      bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                      returnValue.set(bi);
                  }

                  @Override
                  public void setProperties(Hashtable<?, ?> props) {}

                  @Override
                  public void setColorModel(ColorModel model) {}

                  @Override
                  public void setHints(int hintflags) {}

                  @Override
                  public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {
                      try {
                          final int yMax = y + h;
                          final int xMax = x + w;

                          IndexColorModel icm = (IndexColorModel) model;
                          int[] colorModelRGBs = new int[icm.getMapSize()];
                          icm.getRGBs(colorModelRGBs);
                          int[] argbRow = new int[bi.getWidth()];

                          for (int y_ = y; y_ < yMax; y_++) {
                              int i = y_ * scansize + off;
                              for (int x_ = x; x_ < xMax; x_++, i++) {
                                  int pixel = pixels[i] & 0xff;
                                  argbRow[x_ - x] = colorModelRGBs[pixel];
                              }
                              bi.getRaster().setDataElements(x, y_, w, 1, argbRow);
                          }
                      } catch (RuntimeException e) {
                          // we don't expect this to happen, but if something goes wrong nobody else
                          // will print our stacktrace for us:
                          e.printStackTrace();
                          throw e;
                      }
                  }

                  @Override
                  public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {}

                  @Override
                  public void imageComplete(int status) {
                      try {
                          if (frameCtr++ == frameIndex) {
                              semaphore.release();
                              // if we don't detach this consumer the producer will loop forever
                              image.getSource().removeConsumer(this);
                              image.flush();
                          }
                      } catch(Exception e) {
                          e.printStackTrace();
                          throw new RuntimeException(e);
                      }
                  }
              });

              // wait for producer thread to finish:
              semaphore.acquireUninterruptibly();

              return returnValue.get();
          }
      }

      ---------- END SOURCE ----------

            jdv Jayathirth D V
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated: