I’m using javax.imageio.ImageIO
to write JPEGs. By default: if I try to write an image with a thumbnail that thumbnail is clipped to 255×255.
Is it possible using only ImageIO classes to embed a thumbnail that is 300x300px?
I (think I) understand that this is because ImageIO’s implementation uses an old-fashioned APP0 marker (using JFIF, instead of the more modern EXIF), and the width and height MUST be expressed as bytes.
However:
I’m looking through Sun’s code, and I think (?) it should be possible to embed larger thumbnails if I can trigger the code that will use the JFIFThumbJPEG
implementation. The default thumbnail is written as a raw table of RGB bytes, but the JFIFThumbJPEG
will (I think) embed a separate mini JPEG in the file. Which should be capable of having a width & height that are not constrained to 1 byte.
Here is a unit test that demonstrates the failure:
import junit.framework.TestCase;
import org.junit.Test;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
/**
* This demonstrates that ImageIO fails to write a JPEG thumbnail that is larger than 255x255.
* <p>
* However, when I did through the implementation/specification, it looks like ImageIO *is*
* capable of supporting different types of thumbnails. I think if I can figure out
* how to write IIOMetaData to use a JFIFThumbJPEG then I may (?) be able to write thumbnails
* of arbitrary size.
* </p>
*/
public class TestThumbnails extends TestCase {
@Test
public void testImageIOThumbnailSizes() throws IOException {
BufferedImage largeImage = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
// a value smaller than 255 will work:
BufferedImage goodThumbnail = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
// a value larger than 255 will be cropped to 255
BufferedImage badThumbnail = new BufferedImage(300, 300, BufferedImage.TYPE_INT_RGB);
testThumbnail(largeImage, goodThumbnail);
System.out.println("first thumbnail passed");
testThumbnail(largeImage, badThumbnail);
System.out.println("second thumbnail passed");
}
private void testThumbnail(BufferedImage largeImage, BufferedImage thumbnail) throws IOException {
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("jpg");
ImageWriter w = iter.next();
byte[] jpegData;
try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream()) {
IIOImage iioImage = new IIOImage(largeImage, Arrays.asList(thumbnail), null);
ImageOutputStream stream = ImageIO.createImageOutputStream(byteOut);
w.setOutput(stream);
w.write(iioImage);
jpegData = byteOut.toByteArray();
}
testReadingThumbnail(jpegData, thumbnail.getWidth(), thumbnail.getHeight());
}
private void testReadingThumbnail(byte[] jpegData, int expectedThumbnailWidth, int expectedThumbnailHeight) throws IOException {
Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix("jpg");
ImageReader reader = iter.next();
try (ByteArrayInputStream byteIn = new ByteArrayInputStream(jpegData)) {
reader.setInput(ImageIO.createImageInputStream(byteIn));
// Thumbnails larger than 255x255 are trimmed. I want to read back
// a thumbnail that uses the dimensions I passed in:
assertEquals(expectedThumbnailWidth, reader.getThumbnailWidth(0, 0));
assertEquals(expectedThumbnailHeight, reader.getThumbnailHeight(0, 0));
}
}
}
1