-
Bug
-
Resolution: Unresolved
-
P4
-
8, 11, 17, 23, 24, 25
-
generic
-
generic
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 ----------
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 ----------
- links to
-
Review(master) openjdk/jdk/23920