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

ImageIO.write for JPEG can write corrupt JPEG for certain thumbnail dimensions

XMLWordPrintable

      A DESCRIPTION OF THE PROBLEM :
      When calling ImageIO.write(..) for a JPEG with a thumbnail: you may write a corrupt JPEG file depending on the dimensions.

      In the attached test case: dimensions of 100x218 pass, but 100x219 fail.

      (I'm guessing the problem is that 100*219*3 = 65700, which exceeds the ~65536 size limit for a JPEG segment.)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached test code

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Ideally the console should indicate that both tests pass. This may be tricky, though, because writing a valid JPEG file here may require automatically switching from the RGB-encoded thumbnail to a JPEG-encoded thumbnail.

      Or an alternative expected behavior might be:
      The act of calling `ImageIO.write(..)` should fail with an Exception. (Ideally without writing to the OutputStream at all.) It is important to let the caller know that their image data is NOT safe/saved.
      ACTUAL -
      An exception appears for the larger thumbnail:
      ```
      javax.imageio.IIOException: Unsupported JPEG process: SOF type 0xc8
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:739)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:354)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:504)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.getImageMetadata(JPEGImageReader.java:1136)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.getNumThumbnails(JPEGImageReader.java:1652)
      at WriteJPEGThumbnailTest.run(WriteJPEGThumbnailTest.java:79)
      at WriteJPEGThumbnailTest.main(WriteJPEGThumbnailTest.java:54)
      ```

      ---------- BEGIN SOURCE ----------
      import javax.imageio.IIOImage;
      import javax.imageio.ImageIO;
      import javax.imageio.ImageReader;
      import javax.imageio.ImageWriter;
      import javax.imageio.stream.ImageInputStream;
      import javax.imageio.stream.ImageOutputStream;
      import java.awt.*;
      import java.awt.geom.AffineTransform;
      import java.awt.image.BufferedImage;
      import java.io.*;
      import java.util.Arrays;
      import java.util.Iterator;

      public class WriteJPEGThumbnailTest {

          private static void assertEquals(int expected, int observed) {
              if (expected != observed) {
                  throw new Error("expected " + expected + ", but observed " + observed);
              }
          }
          public static void main(String[] args) throws Exception {
              boolean b1 = new WriteJPEGThumbnailTest(100, 218).run();
              boolean b2 = new WriteJPEGThumbnailTest(100, 219).run();
              if (!(b1 && b2))
                  System.err.println("Test failed");
          }

          final int thumbWidth, thumbHeight;

          public WriteJPEGThumbnailTest(int thumbWidth, int thumbHeight) {
              this.thumbWidth = thumbWidth;
              this.thumbHeight = thumbHeight;
          }

          public boolean run() throws Exception {
              System.out.println("Testing thumbnail " + thumbWidth + "x" + thumbHeight + "...");
              try {
                  byte[] jpegData;
                  BufferedImage thumbnail;
                  try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream()) {
                      thumbnail = writeImage(byteOut);
                      jpegData = byteOut.toByteArray();
                  }

                  ImageReader reader = getJPEGImageReader();
                  ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(jpegData));
                  reader.setInput(stream);
                  assertEquals(1, reader.getNumThumbnails(0));
                  assertEquals(thumbnail.getWidth(), reader.getThumbnailWidth(0, 0));
                  assertEquals(thumbnail.getHeight(), reader.getThumbnailHeight(0, 0));

                  BufferedImage readThumbnail = reader.readThumbnail(0, 0);
                  for (int y = 0; y < readThumbnail.getHeight(); y++) {
                      for (int x = 0; x < readThumbnail.getWidth(); x++) {
                          assertEquals(thumbnail.getRGB(x, y), readThumbnail.getRGB(x, y));
                      }
                  }
                  System.out.println("\tTest passed");
              } catch(Exception e) {
                  e.printStackTrace();
                  return false;
              }
              return true;
          }

          private BufferedImage writeImage(OutputStream out) throws IOException {
              BufferedImage thumbnail = createImage(thumbWidth, thumbHeight);
              BufferedImage bi = createImage(thumbnail.getWidth() * 10, thumbnail.getHeight() * 10);

              ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();

              try (ImageOutputStream outputStream = ImageIO.createImageOutputStream(out)) {
                  writer.setOutput(outputStream);

                  // Write the main image
                  IIOImage img = new javax.imageio.IIOImage(bi, Arrays.asList(thumbnail), null);
                  writer.write(null, img, null);

                  writer.dispose();
              }
              return thumbnail;
          }

          private static BufferedImage createImage(int width, int height) {
              BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
              Graphics2D g = bi.createGraphics();
              double sx = ((double)width) / 1000.0;
              double sy = ((double)height) / 1000.0;
              g.transform(AffineTransform.getScaleInstance(sx, sy));
              g.setColor(Color.red);
              g.fillRect(0,0,100,100);
              g.setColor(Color.green);
              g.fillRect(900,0,900,100);
              g.setColor(Color.orange);
              g.fillRect(0,900,100,100);
              g.setColor(Color.magenta);
              g.fillRect(900,900,100,100);
              g.dispose();
              return bi;
          }

          private static ImageReader getJPEGImageReader() {
              Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpeg");
              ImageReader reader;
              while(readers.hasNext()) {
                  reader = readers.next();
                  if(reader.canReadRaster()) {
                      return reader;
                  }
              }
              return null;
          }
      }
      ---------- END SOURCE ----------

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: