JPEGImageReaderTest.java
/*
* Copyright (c) 2011, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.lang.StringUtil;
import org.hamcrest.core.IsInstanceOf;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.io.*;
import java.time.Duration;
import java.util.List;
import java.util.*;
import org.junit.jupiter.api.Test;
import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
import static java.time.Duration.ofMillis;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.Mockito.*;
/**
* JPEGImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: JPEGImageReaderTest.java,v 1.0 24.01.11 22.04 haraldk Exp$
*/
public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader> {
@Override
protected ImageReaderSpi createProvider() {
return new JPEGImageReaderSpi(lookupDelegateProvider());
}
private static ImageReaderSpi lookupDelegateProvider() {
return lookupProviderByName(IIORegistry.getDefaultInstance(), "com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi", ImageReaderSpi.class);
}
@Override
protected List<TestData> getTestData() {
// While a lot of these files don't conform to any spec (Exif/JFIF), we will read these.
return Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmm-exception-adobe-rgb.jpg"), new Dimension(626, 76)),
new TestData(getClassLoaderResource("/jpeg/cmm-exception-srgb.jpg"), new Dimension(1800, 1200)),
new TestData(getClassLoaderResource("/jpeg/corrupted-icc-srgb.jpg"), new Dimension(1024, 685)),
new TestData(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg"), new Dimension(225, 156)), // Adobe, unknown transform, component ids R, G & B
new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)),
new TestData(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg"), new Dimension(640, 480)),
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)),
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
new TestData(getClassLoaderResource("/jpeg/app-marker-missing-null-term.jpg"), new Dimension(200, 150)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
new TestData(getClassLoaderResource("/jpeg/jfif-grayscale-thumbnail.jpg"), new Dimension(2547, 1537)), // Non-compliant JFIF with 8 bit grayscale thumbnail
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
new TestData(getClassLoaderResource("/jpeg-lossless/16_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 16 bit
new TestData(getClassLoaderResource("/jpeg-lossless/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/f-18.jpg"), new Dimension(320, 240)), // Lossless RGB, 3 DHTs
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512)), // Lossless gray, 16 bit
new TestData(getClassLoaderResource("/jpeg/dnl-marker.jpg"), new Dimension(194, 132)) // Define Number of Lines marker
);
// More test data in specific tests below
}
protected List<TestData> getBrokenTestData() {
// These files are considered too broken to be read (ie. most other software does not read them either).
return Arrays.asList(
new TestData(getClassLoaderResource("/broken-jpeg/broken-bogus-segment-length.jpg"), new Dimension(467, 612)), // Semi-readable, parts missing
new TestData(getClassLoaderResource("/broken-jpeg/broken-adobe-marker-bad-length.jpg"), new Dimension(1800, 1200)), // Unreadable, segment lengths are wrong
new TestData(getClassLoaderResource("/broken-jpeg/broken-invalid-adobe-ycc-gray.jpg"), new Dimension(11, 440)), // Image readable, broken metadata (fixable?)
new TestData(getClassLoaderResource("/broken-jpeg/broken-no-sof-ascii-transfer-mode.jpg"), new Dimension(-1, -1)), // Unreadable, can't find SOFn marker
new TestData(getClassLoaderResource("/broken-jpeg/broken-sos-before-sof.jpg"), new Dimension(-1, -1)), // Unreadable, can't find SOFn marker
new TestData(getClassLoaderResource("/broken-jpeg/broken-adobe-segment-length-beyond-eof.jpg"), new Dimension(-1, -1)), // Unreadable, no EOI
new TestData(getClassLoaderResource("/broken-jpeg/513f29d0-02a8-11e7-9756-6035edb96e79.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/51432b02-02a8-11e7-9203-b42b1c43c0c3.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/5145e95a-02a8-11e7-8372-4787a7307ab8.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/514b20dc-02a8-11e7-92c6-d4fed7b4ebb1.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/514c48ea-02a8-11e7-8789-bb75321f404f.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/514e4122-02a8-11e7-8c03-0830d60cd585.jpg"), new Dimension(-1, -1)),
new TestData(getClassLoaderResource("/broken-jpeg/513f29d0-02a8-11e7-9756-6035edb96e79.jpg"), new Dimension(-1, -1))
);
// More test data in specific tests below
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("JPEG", "jpeg", "JPG", "jpg",
"jpeg-lossless", "JPEG-LOSSLESS");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("jpeg", "jpg");
}
@Override
protected List<String> getMIMETypes() {
return Collections.singletonList("image/jpeg");
}
// TODO: Test that subsampling is actually reading something
// Special cases found in the wild below
@Test
public void testICCProfileCMYKClassOutputColors() throws IOException {
// Make sure ICC profile with class output isn't converted to too bright values
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"))) {
reader.setInput(stream);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(800, 800, 64, 8));
param.setSourceSubsampling(8, 8, 2, 2);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
byte[] expectedData = {34, 37, 34, 47, 47, 44, 22, 26, 28, 23, 26, 28, 20, 23, 26, 20, 22, 25, 22, 25, 27, 18, 21, 24};
assertEquals(expectedData.length, data.length);
assertJPEGPixelsEqual(expectedData, data, 0);
}
finally {
reader.dispose();
}
}
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, @SuppressWarnings("SameParameterValue") int actualOffset) {
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i + actualOffset], 5, String.format("Difference in pixel %d", i));
}
}
@Test
public void testICCDuplicateSequence() throws IOException {
// Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg"))) {
reader.setInput(stream);
assertEquals(345, reader.getWidth(0));
assertEquals(540, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(345, image.getWidth());
assertEquals(540, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testICCDuplicateSequenceZeroBased() throws IOException {
// File contains multiple ICC chunks, with all counts and sequence numbers == 0
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg"))) {
reader.setInput(stream);
assertEquals(3874, reader.getWidth(0));
assertEquals(5480, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(3874, image.getWidth());
assertEquals(16, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testTruncatedICCProfile() throws IOException {
// File contains single 'ICC_PROFILE' chunk, with a truncated (32 000 bytes) "Europe ISO Coated FOGRA27" ICC profile (by Adobe).
// Profile should have been about 550 000 bytes, split into multiple chunks. Written by GIMP 2.6.11
// See: https://bugzilla.redhat.com/show_bug.cgi?id=695246
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg"))) {
reader.setInput(stream);
assertEquals(1993, reader.getWidth(0));
assertEquals(1038, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1993, image.getWidth());
assertEquals(8, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testCCOIllegalArgument() throws IOException {
// File contains CMYK ICC profile ("Coated FOGRA27 (ISO 12647-2:2004)"), but image data is 3 channel YCC/RGB
// JFIF 1.1 with unknown origin.
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cco-illegalargument-rgb-coated-fogra27.jpg"))) {
reader.setInput(stream);
assertEquals(281, reader.getWidth(0));
assertEquals(449, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(281, image.getWidth());
assertEquals(449, image.getHeight());
// TODO: Need to test colors!
}
finally {
reader.dispose();
}
}
@Test
public void testNoImageTypesRGBWithCMYKProfile() throws IOException {
// File contains CMYK ICC profile ("U.S. Web Coated (SWOP) v2") AND Adobe App14 specifying YCCK conversion (!),
// but image data is plain 3 channel YCC/RGB.
// EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"...
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"))) {
reader.setInput(stream);
assertEquals(1743, reader.getWidth(0));
assertEquals(2551, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 1743, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1743, image.getWidth());
assertEquals(16, image.getHeight());
// TODO: Need to test colors!
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
}
finally {
reader.dispose();
}
}
@Test
public void testCMYKWithRGBProfile() throws IOException {
// File contains JFIF (!), RGB ICC profile AND Adobe App14 specifying unknown conversion,
// but image data is 4 channel CMYK (from SOF0 channel Ids 'C', 'M', 'Y', 'K').
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-cmyk-invalid-icc-profile-srgb.jpg"))) {
reader.setInput(stream);
assertEquals(493, reader.getWidth(0));
assertEquals(500, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 493, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(493, image.getWidth());
assertEquals(16, image.getHeight());
// TODO: Need to test colors!
assertFalse(reader.hasThumbnails(0)); // Should not blow up!
}
finally {
reader.dispose();
}
}
@Test
public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException {
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/warning-embedded-color-profile-invalid-ignored-cmyk.jpg"))) {
reader.setInput(stream);
assertEquals(183, reader.getWidth(0));
assertEquals(283, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(183, image.getWidth());
assertEquals(283, image.getHeight());
// TODO: Need to test colors!
}
finally {
reader.dispose();
}
}
@Test
public void testEOFSOSSegment() throws IOException {
// Regression...
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/eof-sos-segment-bug.jpg"))) {
reader.setInput(stream);
assertEquals(266, reader.getWidth(0));
assertEquals(400, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(266, image.getWidth());
assertEquals(400, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testInvalidICCSingleChunkBadSequence() throws IOException {
// Regression
// Single segment ICC profile, with chunk index/count == 0
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-single-chunk-bad-sequence-number.jpg"))) {
reader.setInput(stream);
assertEquals(1772, reader.getWidth(0));
assertEquals(2126, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1772, image.getWidth());
assertEquals(8, image.getHeight());
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
}
finally {
reader.dispose();
}
}
@Test
public void testYCbCrNotSubsampledNonstandardComponentIds() throws IOException {
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if nonstandard component ids
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg"))) {
reader.setInput(stream);
assertEquals(600, reader.getWidth(0));
assertEquals(600, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(8, 8));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(8, image.getWidth());
assertEquals(8, image.getHeight());
// QnD test: Make sure all pixels are white (if treated as RGB, they will be pink-ish)
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals(0xffffff, image.getRGB(x, y) & 0xffffff);
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testCorbisRGB() throws IOException {
// Special case, throws exception below without special treatment
// java.awt.color.CMMException: General CMM error517
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-corbis-rgb.jpg"))) {
reader.setInput(stream);
assertEquals(512, reader.getWidth(0));
assertEquals(384, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(512, image.getWidth());
assertEquals(384, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testStandardMetadataColorSpaceTypeRGBForYCbCr() throws IOException {
List<TestData> ycbcr = Arrays.asList(
// This reports RGB in standard metadata, while the data is really YCbCr.
// Exif files are always YCbCr AFAIK.
new TestData(getClassLoaderResource("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg"), new Dimension(2437, 1662)),
// Not Exif, but same issue: SOF comp ids are JFIF standard 1-3 and
// *should* be interpreted as YCbCr but isn't.
// Possible fix for this, is to insert a fake JFIF segment, as this image
// conforms to the JFIF spec (but it won't work for the Exif samples)
new TestData(getClassLoaderResource("/jpeg/no-jfif-ycbcr.jpg"), new Dimension(310, 206))
);
JPEGImageReader reader = createReader();
try {
for (TestData broken : ycbcr) {
reader.setInput(broken.getInputStream());
IIOMetadata metadata = reader.getImageMetadata(0);
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList colorSpaceTypes = root.getElementsByTagName("ColorSpaceType");
assertEquals(1, colorSpaceTypes.getLength());
IIOMetadataNode csType = (IIOMetadataNode) colorSpaceTypes.item(0);
assertEquals("YCbCr", csType.getAttribute("name"));
}
}
finally {
reader.dispose();
}
}
@Test
public void testGetExifOrientationFromMetadata() throws IOException {
JPEGImageReader reader = createReader();
// TODO: Find better sample data. Should have an uppercase F ;-)
// Test all 9 mutations + missing Exif
List<String> expectedOrientations = Arrays.asList("Normal", "Normal", "FlipH", "Rotate180", "FlipV", "FlipVRotate90", "Rotate270", "FlipHRotate90", "Rotate90");
try {
for (int i = 0; i < 9; i++) {
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource(String.format("/exif/Landscape_%d.jpg", i)))) {
reader.setInput(stream);
IIOMetadata metadata = reader.getImageMetadata(0);
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList orientationNodes = root.getElementsByTagName("ImageOrientation");
assertEquals(1, orientationNodes.getLength());
IIOMetadataNode orientationNode = (IIOMetadataNode) orientationNodes.item(0);
String orientationValue = orientationNode.getAttribute("value");
assertEquals(expectedOrientations.get(i), orientationValue);
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenReadRasterAfterGetMetadataException() throws IOException {
// See issue #107, from PDFBox team
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
try {
reader.getImageMetadata(0);
}
catch (IOException ignore) {
// Expected IOException here, due to broken file
}
try {
reader.readRaster(0, null);
}
catch (IOException expected) {
// Should not throw anything other than IOException here
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testSPIRecognizesBrokenJPEG() throws IOException {
// TODO: There's a bug in com.sun.imageio.plugins.png.PNGImageReaderSpi.canDecode
// causing files < 8 bytes to not be recognized as anything...
for (TestData data : getBrokenTestData()) {
assertTrue(provider.canDecodeInput(data.getInputStream()), data.toString());
}
}
// TODO: Consider wrapping the delegate in JPEGImageReader with methods that don't throw
// runtime exceptions, and instead throw IIOException?
@Test
public void testBrokenGetRawImageType() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
try {
reader.getRawImageType(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetRawImageTypeIgnoreMetadata() throws IOException {
JPEGImageReader reader = createReader();
assertTimeoutPreemptively(ofMillis(200), () -> {
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream(), true, true);
try {
reader.getRawImageType(0);
} catch (IIOException expected) {
assertNotNull(expected.getMessage());
} catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
} finally {
reader.dispose();
}
});
}
@Test
public void testBrokenGetImageTypes() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
try {
reader.getImageTypes(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetImageTypesIgnoreMetadata() throws IOException {
JPEGImageReader reader = createReader();
assertTimeoutPreemptively(ofMillis(200), () -> {
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream(), true, true);
try {
reader.getImageTypes(0);
} catch (IIOException expected) {
assertNotNull(expected.getMessage());
} catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
} finally {
reader.dispose();
}
});
}
@Test
public void testBrokenRead() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
try {
reader.read(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetDimensions() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
Dimension exptectedSize = broken.getDimension(0);
try {
assertEquals(exptectedSize.width, reader.getWidth(0));
assertEquals(exptectedSize.height, reader.getHeight(0));
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetImageMetadata() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(broken.getInputStream());
try {
reader.getImageMetadata(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
catch (IOException expected) {
if (!(expected instanceof EOFException)) {
assertNotNull(expected.getMessage());
}
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testImageMetadata1ChannelGrayWithBogusAdobeYCC() throws IOException {
JPEGImageReader reader = createReader();
try {
// Any sample should do here
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-adobe-ycc-gray-with-metadata.jpg")));
IIOMetadata metadata = reader.getImageMetadata(0);
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
IIOMetadataNode chroma = getSingleElementByName(root, "Chroma");
IIOMetadataNode numChannels = getSingleElementByName(chroma, "NumChannels");
assertEquals("1", numChannels.getAttribute("value"));
IIOMetadataNode colorSpaceType = getSingleElementByName(chroma, "ColorSpaceType");
assertEquals("GRAY", colorSpaceType.getAttribute("name"));
}
finally {
reader.dispose();
}
}
private IIOMetadataNode getSingleElementByName(final IIOMetadataNode root, final String name) {
NodeList elements = root.getElementsByTagName(name);
assertEquals(1, elements.getLength());
return (IIOMetadataNode) elements.item(0);
}
@Test
public void testGetImageMetadataOutOfBounds() throws IOException {
JPEGImageReader reader = createReader();
try {
// Any sample should do here
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/gray-sample.jpg")));
assertThrows(IndexOutOfBoundsException.class, () -> reader.getImageMetadata(-1));
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenBogusSegmentLengthReadWithDestination() throws IOException {
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/broken-jpeg/broken-bogus-segment-length.jpg")));
assertEquals(467, reader.getWidth(0));
assertEquals(612, reader.getHeight(0));
ImageTypeSpecifier type = reader.getImageTypes(0).next();
BufferedImage image = type.createBufferedImage(reader.getWidth(0), reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setDestination(image);
assertThrows(IIOException.class, () -> {
reader.read(0, param);
});
// Even if we get an exception here, the image should contain 10-15% of the image
assertRGBEquals(0xffffffff, image.getRGB(0, 0)); // white area
assertRGBEquals(0xff0000ff, image.getRGB(67, 22)); // blue area
assertRGBEquals(0xffff00ff, image.getRGB(83, 22)); // purple area
}
finally {
reader.dispose();
}
}
@Test
public void testHasThumbnailNoIFD1() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/srgb-exif-no-ifd1.jpg")));
assertEquals(150, reader.getWidth(0));
assertEquals(207, reader.getHeight(0));
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(150, image.getWidth());
assertEquals(207, image.getHeight());
assertFalse(reader.hasThumbnails(0)); // Should just not blow up, even if the EXIF IFD1 is missing
}
@Test
public void testJFIFRawRGBThumbnail() throws IOException {
// JFIF with raw RGB thumbnail (+ EXIF thumbnail)
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-jfif-and-exif-thumbnail-sharpshot-iphone.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(2, reader.getNumThumbnails(0));
// RAW JFIF
assertEquals(131, reader.getThumbnailWidth(0, 0));
assertEquals(122, reader.getThumbnailHeight(0, 0));
BufferedImage rawJFIFThumb = reader.readThumbnail(0, 0);
assertNotNull(rawJFIFThumb);
assertEquals(131, rawJFIFThumb.getWidth());
assertEquals(122, rawJFIFThumb.getHeight());
// Exif (old thumbnail, from original image, should probably been removed by the software...)
assertEquals(160, reader.getThumbnailWidth(0, 1));
assertEquals(120, reader.getThumbnailHeight(0, 1));
BufferedImage exifThumb = reader.readThumbnail(0, 1);
assertNotNull(exifThumb);
assertEquals(160, exifThumb.getWidth());
assertEquals(120, exifThumb.getHeight());
}
// TODO: Test JFXX indexed thumbnail
// TODO: Test JFXX RGB thumbnail
@Test
public void testJFXXJPEGThumbnail() throws IOException {
// JFIF with JFXX JPEG encoded thumbnail
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(1, reader.getNumThumbnails(0));
assertEquals(80, reader.getThumbnailWidth(0, 0));
assertEquals(60, reader.getThumbnailHeight(0, 0));
BufferedImage thumbnail = reader.readThumbnail(0, 0);
assertNotNull(thumbnail);
assertEquals(80, thumbnail.getWidth());
assertEquals(60, thumbnail.getHeight());
}
@Test
public void testEXIFJPEGThumbnail() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(1, reader.getNumThumbnails(0));
assertEquals(114, reader.getThumbnailWidth(0, 0));
assertEquals(160, reader.getThumbnailHeight(0, 0));
BufferedImage thumbnail = reader.readThumbnail(0, 0);
assertNotNull(thumbnail);
assertEquals(114, thumbnail.getWidth());
assertEquals(160, thumbnail.getHeight());
}
@Test
public void testEXIFRawThumbnail() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-rgb-thumbnail-sony-d700.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(1, reader.getNumThumbnails(0));
assertEquals(80, reader.getThumbnailWidth(0, 0));
assertEquals(60, reader.getThumbnailHeight(0, 0));
BufferedImage thumbnail = reader.readThumbnail(0, 0);
assertNotNull(thumbnail);
assertEquals(80, thumbnail.getWidth());
assertEquals(60, thumbnail.getHeight());
}
@Test
public void testBadEXIFRawThumbnail() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-rgb-thumbnail-bad-exif-kodak-dc210.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(1, reader.getNumThumbnails(0));
assertEquals(96, reader.getThumbnailWidth(0, 0));
assertEquals(72, reader.getThumbnailHeight(0, 0));
BufferedImage thumbnail = reader.readThumbnail(0, 0);
assertNotNull(thumbnail);
assertEquals(96, thumbnail.getWidth());
assertEquals(72, thumbnail.getHeight());
}
@Test
public void testInvertedColors() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg")));
assertEquals(2437, reader.getWidth(0));
assertEquals(1662, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, reader.getWidth(0), 8));
BufferedImage strip = reader.read(0, param);
assertNotNull(strip);
assertEquals(2437, strip.getWidth());
assertEquals(8, strip.getHeight());
int[] expectedRGB = new int[] {
0xffe9d0bc, 0xfff3decd, 0xfff5e6d3, 0xfff8ecdc, 0xfff8f0e5, 0xffe3ceb9, 0xff6d3923, 0xff5a2d18,
0xff00170b, 0xff131311, 0xff52402c, 0xff624a30, 0xff6a4f34, 0xfffbf8f1, 0xfff4efeb, 0xffefeae6,
0xffebe6e2, 0xffe3e0d9, 0xffe1d6d0, 0xff10100e
};
// Validate strip colors
for (int i = 0; i < strip.getWidth() / 128; i++) {
int actualRGB = strip.getRGB(i * 128, 4);
assertRGBEquals(expectedRGB[i], actualRGB);
}
}
@Test
public void testThumbnailInvertedColors() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg")));
assertTrue(reader.hasThumbnails(0));
assertEquals(1, reader.getNumThumbnails(0));
assertEquals(160, reader.getThumbnailWidth(0, 0));
assertEquals(109, reader.getThumbnailHeight(0, 0));
BufferedImage thumbnail = reader.readThumbnail(0, 0);
assertNotNull(thumbnail);
assertEquals(160, thumbnail.getWidth());
assertEquals(109, thumbnail.getHeight());
int[] expectedRGB = new int[] {
0xffefd5c4, 0xffead3b1, 0xff55392d, 0xff55403b, 0xff6d635a, 0xff7b726b, 0xff68341f, 0xff5c2f1c,
0xff250f12, 0xff6d7c77, 0xff414247, 0xff6a4f3a, 0xff6a4e39, 0xff564438, 0xfffcf7f1, 0xffefece7,
0xfff0ebe7, 0xff464040, 0xffe3deda, 0xffd4cfc9,
};
// Validate strip colors
for (int i = 0; i < thumbnail.getWidth() / 8; i++) {
int actualRGB = thumbnail.getRGB(i * 8, 4);
assertRGBEquals(expectedRGB[i], actualRGB);
}
}
private List<TestData> getCMYKData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
);
}
@Test
public void testGetImageTypesCMYK() throws IOException {
// Make sure CMYK images will report their embedded color profile among image types
JPEGImageReader reader = createReader();
List<TestData> cmykData = getCMYKData();
for (TestData data : cmykData) {
reader.setInput(data.getInputStream());
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
assertTrue(types.hasNext(), data + " has no image types");
boolean hasRGBType = false;
boolean hasCMYKType = false;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
int csType = type.getColorModel().getColorSpace().getType();
if (csType == ColorSpace.TYPE_RGB) {
hasRGBType = true;
}
else if (csType == ColorSpace.TYPE_CMYK) {
assertTrue(hasRGBType, "CMYK types should be delivered after RGB types (violates \"contract\" of more \"natural\" type first) for " + data);
hasCMYKType = true;
break;
}
}
assertTrue(hasRGBType, "No RGB types for " + data);
assertTrue(hasCMYKType, "No CMYK types for " + data);
}
reader.dispose();
}
@Test
public void testGetRawImageTypeCMYK() throws IOException {
// Make sure images that are encoded as CMYK (not YCCK) actually return non-null for getRawImageType
JPEGImageReader reader = createReader();
List<TestData> cmykData = Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
);
for (TestData data : cmykData) {
reader.setInput(data.getInputStream());
ImageTypeSpecifier rawType = reader.getRawImageType(0);
assertNotNull(rawType, "No raw type for " + data);
}
}
@Test
public void testReadCMYKAsCMYK() throws IOException {
// Make sure CMYK images can be read and still contain their original (embedded) color profile
JPEGImageReader reader = createReader();
List<TestData> cmykData = getCMYKData();
for (TestData data : cmykData) {
reader.setInput(data.getInputStream());
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
assertTrue(types.hasNext(), data + " has no image types");
ImageTypeSpecifier cmykType = null;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
int csType = type.getColorModel().getColorSpace().getType();
if (csType == ColorSpace.TYPE_CMYK) {
cmykType = type;
break;
}
}
assertNotNull(cmykType, "No CMYK types for " + data);
ImageReadParam param = reader.getDefaultReadParam();
param.setDestinationType(cmykType);
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); // We don't really need to read it all
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(ColorSpace.TYPE_CMYK, image.getColorModel().getColorSpace().getType());
}
reader.dispose();
}
@Test
public void testReadCMYKAsCMYKSameRGBasRGB() throws IOException {
// Make sure CMYK images can be read and still contain their original (embedded) color profile
JPEGImageReader reader = createReader();
// NOTE: Data without ICC profile won't work in this test, as we might end up
// using the non-ICC color conversion case and fail miserably on the CI server.
List<TestData> cmykData = Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"), new Dimension(100, 100))
);
for (TestData data : cmykData) {
reader.setInput(data.getInputStream());
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
assertTrue(types.hasNext(), data + " has no image types");
ImageTypeSpecifier cmykType = null;
ImageTypeSpecifier rgbType = null;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
int csType = type.getColorModel().getColorSpace().getType();
if (rgbType == null && csType == ColorSpace.TYPE_RGB) {
rgbType = type;
}
else if (cmykType == null && csType == ColorSpace.TYPE_CMYK) {
cmykType = type;
}
if (rgbType != null && cmykType != null) {
break;
}
}
assertNotNull(rgbType, "No RGB types for " + data);
assertNotNull(cmykType, "No CMYK types for " + data);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); // We don't really need to read it all
param.setDestinationType(cmykType);
BufferedImage imageCMYK = reader.read(0, param);
param.setDestinationType(rgbType);
BufferedImage imageRGB = reader.read(0, param);
assertNotNull(imageCMYK);
assertEquals(ColorSpace.TYPE_CMYK, imageCMYK.getColorModel().getColorSpace().getType());
assertNotNull(imageRGB);
assertEquals(ColorSpace.TYPE_RGB, imageRGB.getColorModel().getColorSpace().getType());
for (int y = 0; y < imageCMYK.getHeight(); y++) {
for (int x = 0; x < imageCMYK.getWidth(); x++) {
int cmykAsRGB = imageCMYK.getRGB(x, y);
int rgb = imageRGB.getRGB(x, y);
if (rgb != cmykAsRGB) {
assertRGBEquals(String.format("Diff at [%d, %d]", x, y), rgb, cmykAsRGB, 2);
}
}
}
}
reader.dispose();
}
@Test
public void testReadNoJFIFYCbCr() throws IOException {
// Basically the same issue as http://stackoverflow.com/questions/9340569/jpeg-image-with-wrong-colors
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-jfif-ycbcr.jpg")));
assertEquals(310, reader.getWidth(0));
assertEquals(206, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 310, 8));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(310, image.getWidth());
assertEquals(8, image.getHeight());
int[] expectedRGB = new int[] {
0xff3c1b14, 0xff35140b, 0xff4b2920, 0xff3b160e, 0xff49231a, 0xff874e3d, 0xff563d27, 0xff926c61,
0xff350005, 0xff84432d, 0xff754f46, 0xff2c2223, 0xff422016, 0xff220f0b, 0xff251812, 0xff1c1209,
0xff483429, 0xff1b140c, 0xff231c16, 0xff2f261f, 0xff2e2923, 0xff170c08, 0xff383025, 0xff443b34,
0xff574a39, 0xff3b322b, 0xffeee1d0, 0xffebdecd, 0xffe9dccb, 0xffe8dbca, 0xffe7dcca,
};
// Validate strip colors
for (int i = 0; i < image.getWidth() / 10; i++) {
int actualRGB = image.getRGB(i * 10, 7);
assertRGBEquals(expectedRGB[i], actualRGB);
}
}
@Test
public void testAdobeUnknownRGBComponentIds() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg")));
assertEquals(225, reader.getWidth(0));
assertEquals(156, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 225, 8));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(225, image.getWidth());
assertEquals(8, image.getHeight());
// Validate strip colors
for (int i = 0; i < image.getWidth() / 10; i++) {
int actualRGB = image.getRGB(i * 10, 7);
assertRGBEquals(0xffffffff, actualRGB); // Will be pink/purple if decoded as YCbCr and not RGB
}
}
@Test
public void testRGBANoGrayImageTypes() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg")));
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
while (imageTypes.hasNext()) {
ImageTypeSpecifier specifier = imageTypes.next();
assertNotEquals(ColorSpace.TYPE_GRAY, specifier.getColorModel().getColorSpace().getType(), "RGB JPEGs can't be decoded as Gray as it has no luminance (Y) component");
}
reader.dispose();
}
@Test
public void testRGBAsGray() throws IOException {
final JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg")));
assertEquals(225, reader.getWidth(0));
assertEquals(156, reader.getHeight(0));
final ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 225, 8));
param.setDestinationType(ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE));
// Should ideally throw IIOException due to destination type mismatch, but throws IllegalArgumentException...
assertThrows(Exception.class, () -> reader.read(0, param));
}
finally {
reader.dispose();
}
}
@Test
public void testYCbCrAsGray() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setDestinationType(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(BufferedImage.TYPE_BYTE_GRAY, image.getType());
}
/**
* Slightly fuzzy RGB equals method. Tolerance +/-5 steps.
*/
private void assertRGBEquals(int expectedRGB, int actualRGB) {
assertRGBEquals("RGB values differ", expectedRGB, actualRGB, 5);
}
// Regression: Test subsampling offset within of bounds
// NOTE: These tests assumes the reader will read at least 1024 scanlines (if available) each iteration,
// this might change in the future. If so, the tests will no longer test what tey are supposed to....
@Test
public void testReadSubsamplingBounds1028() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1028.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
}
@Test
public void testReadSubsamplingNotSkippingLines1028() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1028.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
public void testReadSubsamplingBounds1027() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1027.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 2, 2);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
public void testReadSubsamplingBounds1026() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1026.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
public void testReadSubsamplingBounds1025() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1025.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
}
@Test
public void testReadSubsamplingNotSkippingLines1025() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1025.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
public void testReadSubsamplingBounds1024() throws IOException {
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/read-error1024.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(3, 3, 1, 1);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
public void testXDensityOutOfRangeIssue() throws IOException {
// Image has JFIF with x/y density 0
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/xdensity-out-of-range-zero.jpg")));
IIOMetadata imageMetadata = reader.getImageMetadata(0);
assertNotNull(imageMetadata);
// Assume that the aspect ratio is 1 if both x/y density is 0.
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false);
NodeList dimensions = tree.getElementsByTagName("Dimension");
assertEquals(1, dimensions.getLength());
assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName());
assertEquals("1.0", ((Element) dimensions.item(0).getFirstChild()).getAttribute("value"));
}
// TODO: Test RGBA/YCbCrA handling
@Test
public void testReadMetadata() throws IOException {
// Just test that we can read the metadata without exceptions
JPEGImageReader reader = createReader();
for (TestData testData : getTestData()) {
reader.setInput(testData.getInputStream());
for (int i = 0; i < reader.getNumImages(true); i++) {
try {
IIOMetadata metadata = reader.getImageMetadata(i);
assertNotNull(metadata, String.format("Image metadata null for %s image %s", testData, i));
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
assertThat(tree, new IsInstanceOf(IIOMetadataNode.class));
IIOMetadataNode iioTree = (IIOMetadataNode) tree;
assertEquals(1, iioTree.getElementsByTagName("JPEGvariety").getLength());
Node jpegVariety = iioTree.getElementsByTagName("JPEGvariety").item(0);
assertNotNull(jpegVariety);
Node app0JFIF = jpegVariety.getFirstChild();
if (app0JFIF != null) {
assertEquals("app0JFIF", app0JFIF.getLocalName());
}
NodeList markerSequences = iioTree.getElementsByTagName("markerSequence");
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
assertNotNull(markerSequence);
assertThat(markerSequence.getChildNodes().getLength(), greaterThan(0));
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
for (int j = 0; j < unknowns.getLength(); j++) {
IIOMetadataNode unknown = (IIOMetadataNode) unknowns.item(j);
assertNotNull(unknown.getUserObject()); // All unknowns must have user object (data array)
}
}
catch (IOException e) {
e.printStackTrace();
fail(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()));
}
}
}
}
@Test
public void testReadInconsistentMetadata() throws IOException {
// A collection of JPEG files that makes the JPEGImageReader throw exception "Inconsistent metadata read from stream"...
List<String> resources = Arrays.asList(
"/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg", // Ok
"/jpeg/gray-sample.jpg", // Ok
"/jpeg/cmyk-sample.jpg",
"/jpeg/cmyk-sample-multiple-chunk-icc.jpg",
"/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg",
"/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"
);
for (String resource : resources) {
// Just test that we can read the metadata without exceptions
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
reader.setInput(stream);
IIOMetadata metadata = reader.getImageMetadata(0);
assertNotNull( metadata, String.format("%s: null metadata", resource));
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false);
}
catch (IIOException e) {
throw new AssertionError(String.format("Reading metadata failed for %ss: %s", resource, e.getMessage()), e);
}
}
}
@Test
public void testReadMetadataEqualReference() throws IOException {
// Compares the metadata for JFIF-conformant files with metadata from com.sun...JPEGImageReader
JPEGImageReader reader = createReader();
ImageReader referenceReader = createReferenceReader();
for (TestData testData : getTestData()) {
reader.setInput(testData.getInputStream());
assert referenceReader != null;
referenceReader.setInput(testData.getInputStream());
for (int i = 0; i < reader.getNumImages(true); i++) {
try {
IIOMetadata reference = referenceReader.getImageMetadata(i);
try {
IIOMetadata metadata = reader.getImageMetadata(i);
String[] formatNames = reference.getMetadataFormatNames();
for (String formatName : formatNames) {
Node referenceTree = reference.getAsTree(formatName);
Node actualTree = metadata.getAsTree(formatName);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(referenceTree, false);
// System.out.println("--------");
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(actualTree, false);
assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree);
}
}
catch (IIOException e) {
throw new AssertionError(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()), e);
}
}
catch (IIOException warn) {
// The reference reader will fail on certain images, we'll just ignore that
System.err.printf("WARNING: Reading reference metadata failed for %s image %s: %s%n", testData, i, warn.getMessage());
}
}
}
}
private ImageReader createReferenceReader() {
try {
@SuppressWarnings("unchecked")
Class<ImageReaderSpi> spiClass = (Class<ImageReaderSpi>) Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi");
ImageReaderSpi provider = spiClass.newInstance();
ImageReader reader = provider.createReaderInstance();
assumeTrue(reader != null, "Reader should not be null");
return reader;
}
catch (Throwable t) {
assumeTrue(false, "An exception occurred: " + t.getMessage());
}
return null;
}
private void assertTreesEquals(String message, Node expectedTree, Node actualTree) {
if (expectedTree == actualTree) {
return;
}
if (expectedTree == null) {
fail("Expected tree is null, actual tree is non-null");
}
assertEquals(expectedTree.getNodeName(), actualTree.getNodeName(), String.format("%s: Node names differ", message));
NamedNodeMap expectedAttributes = expectedTree.getAttributes();
NamedNodeMap actualAttributes = actualTree.getAttributes();
assertEquals(expectedAttributes.getLength(), actualAttributes.getLength(), String.format("%s: Number of attributes for <%s> differ", message, expectedTree.getNodeName()));
for (int i = 0; i < expectedAttributes.getLength(); i++) {
Node item = expectedAttributes.item(i);
String nodeValue = item.getNodeValue();
// NOTE: com.sun...JPEGMetadata javax_imageio_1.0 format bug: Uses "normal" instead of "Normal" ImageOrientation
if ("ImageOrientation".equals(expectedTree.getNodeName()) && "value".equals(item.getNodeName())) {
nodeValue = StringUtil.capitalize(nodeValue);
}
assertEquals(nodeValue, actualAttributes.getNamedItem(item.getNodeName()).getNodeValue());
}
// Test for equal user objects.
// - array equals or reflective equality... Most user objects does not have a decent equals method.. :-P
if (expectedTree instanceof IIOMetadataNode) {
assertTrue(actualTree instanceof IIOMetadataNode, String.format("%s: %s not an IIOMetadataNode", message, expectedTree.getNodeName()));
Object expectedUserObject = ((IIOMetadataNode) expectedTree).getUserObject();
if (expectedUserObject != null) {
Object actualUserObject = ((IIOMetadataNode) actualTree).getUserObject();
assertNotNull(actualUserObject, String.format("%s: User object missing for <%s>", message, expectedTree.getNodeName()));
assertEqualUserObjects(String.format("%s: User objects for <%s MarkerTag\"%s\"> differ", message, expectedTree.getNodeName(), ((IIOMetadataNode) expectedTree).getAttribute("MarkerTag")), expectedUserObject, actualUserObject);
}
}
if ("markerSequence".equals(expectedTree.getNodeName()) && "JFIFthumbJPEG".equals(expectedTree.getParentNode().getNodeName())) {
// TODO: We haven't implemented this yet
return;
}
// Sort nodes to make sure that sequence of equally named tags does not matter
List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes());
List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes());
assertEquals(expectedChildren.size(), actualChildren.size(), String.format("%s: Number of child nodes for %s differ", message, expectedTree.getNodeName()));
for (int i = 0; i < expectedChildren.size(); i++) {
assertTreesEquals(message + "<" + expectedTree.getNodeName() + ">", expectedChildren.get(i), actualChildren.get(i));
}
}
private void assertEqualUserObjects(String message, Object expectedUserObject, Object actualUserObject) {
if (expectedUserObject.equals(actualUserObject)) {
return;
}
if (expectedUserObject instanceof ICC_Profile) {
if (actualUserObject instanceof ICC_Profile) {
assertArrayEquals(((ICC_Profile) expectedUserObject).getData(), ((ICC_Profile) actualUserObject).getData(), message);
return;
}
}
else if (expectedUserObject instanceof byte[]) {
if (actualUserObject instanceof byte[]) {
assertArrayEquals((byte[]) expectedUserObject, (byte[]) actualUserObject, message);
return;
}
}
else if (expectedUserObject instanceof JPEGHuffmanTable) {
if (actualUserObject instanceof JPEGHuffmanTable) {
assertArrayEquals(((JPEGHuffmanTable) expectedUserObject).getLengths(), ((JPEGHuffmanTable) actualUserObject).getLengths(), message);
assertArrayEquals(((JPEGHuffmanTable) expectedUserObject).getValues(), ((JPEGHuffmanTable) actualUserObject).getValues(), message);
return;
}
}
else if (expectedUserObject instanceof JPEGQTable) {
if (actualUserObject instanceof JPEGQTable) {
assertArrayEquals(((JPEGQTable) expectedUserObject).getTable(), ((JPEGQTable) actualUserObject).getTable(), message);
return;
}
}
fail(expectedUserObject.getClass().getName());
}
private List<IIOMetadataNode> sortNodes(final NodeList nodes) {
ArrayList<IIOMetadataNode> sortedNodes = new ArrayList<>(new AbstractList<IIOMetadataNode>() {
@Override
public IIOMetadataNode get(int index) {
return (IIOMetadataNode) nodes.item(index);
}
@Override
public int size() {
return nodes.getLength();
}
});
Collections.sort(
sortedNodes,
new Comparator<IIOMetadataNode>() {
public int compare(IIOMetadataNode left, IIOMetadataNode right) {
int res = left.getNodeName().compareTo(right.getNodeName());
if (res != 0) {
return res;
}
// Compare attribute values
NamedNodeMap leftAttributes = left.getAttributes(); // TODO: We should sort left's attributes as well, for stable sorting + handle diffs in attributes
NamedNodeMap rightAttributes = right.getAttributes();
for (int i = 0; i < leftAttributes.getLength(); i++) {
Node leftAttribute = leftAttributes.item(i);
Node rightAttribute = rightAttributes.getNamedItem(leftAttribute.getNodeName());
if (rightAttribute == null) {
return 1;
}
res = leftAttribute.getNodeValue().compareTo(rightAttribute.getNodeValue());
if (res != 0) {
return res;
}
}
if (left.getUserObject() instanceof byte[] && right.getUserObject() instanceof byte[]) {
byte[] leftBytes = (byte[]) left.getUserObject();
byte[] rightBytes = (byte[]) right.getUserObject();
if (leftBytes.length < rightBytes.length) {
return 1;
}
if (leftBytes.length > rightBytes.length) {
return -1;
}
if (leftBytes.length > 0) {
for (int i = 0; i < leftBytes.length; i++) {
if (leftBytes[i] < rightBytes[i]) {
return -1;
}
if (leftBytes[i] > rightBytes[i]) {
return 1;
}
}
}
}
return 0;
}
}
);
return sortedNodes;
}
@Test
public void testGetNumImagesBogusDataPrepended() throws IOException {
// The JPEGImageReader (incorrectly) interprets this image to be a "tables only" image.
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/broken-jpeg/broken-bogus-data-prepended-real-jfif-start-at-4801.jpg")));
assertEquals(-1, reader.getNumImages(false)); // Ok
assertEquals(0, reader.getNumImages(true)); // Should throw IIOException or return 0
}
finally {
reader.dispose();
}
}
@Test
public void testNegativeSOSComponentCount() throws IOException {
// The data in the stream looks like this:
// FF DA 00 08 01 01 01 06 3F 02 0E 70 9A A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 A2 64 05 5D ...
// ..but the JPEGBuffer class contains:
// FF DA 00 08 A2 A2 A2 A2 A2 64 05 5D 02 87 FC 5B 5C E1 0E BD ...
// *****************??
// 15 bytes missing in action! Why?
// There's a bug in com.sun.imageio.plugins.jpeg.AdobeMarkerSegment when parsing non-standard length
// APP14/Adobe segments (i.e. lengths other than 14) that causes the
// com.sun.imageio.plugins.jpeg.JPEGBuffer#loadBuf() method to overwrite parts of the input data
// (the difference between the real length and 14, at the end of the stream). This can cause all
// sorts of weird problems later, and is a pain to track down (it is probably the real cause for
// many of the other issues we've found in the set).
// See also: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6355567
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-exif-xmp-adobe-progressive-negative-component-count.jpg")));
IIOMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
assertThat(tree, new IsInstanceOf(IIOMetadataNode.class));
}
catch (IIOException knownIssue) {
// This shouldn't fail, but the bug is most likely in the JPEGBuffer class
assertNotNull(knownIssue.getCause());
assertThat(knownIssue.getCause(), new IsInstanceOf(NegativeArraySizeException.class));
}
finally {
reader.dispose();
}
}
@Test
public void testInconsistentSOSBandCountExceedsSOFBandCount() throws IOException {
// Last SOS segment contains (FF DA) 00 08 01 03 03 01 3F 10 (... 18 more ... F0 7D FB FB 6D)
// (14th) (SOS) len 8 | | | | | approx high: 1, approx low: 0
// | | | | end spectral selection:
// | | | start spectral selection: 1
// | | dc: 0, ac: 3
// | selector: 3
// 1 component
// Metadata reads completely different values...
// FF DA 00 08 01 F0 7D FB FB 6D
// \_ there's 24 bytes MIA (skipped) here, between the length and the actual data read...
// Seems to be a bug in the AdobeMarkerSegment, it reads 12 bytes always,
// then subtracting length from bufferAvail, but *does not update bufPtr to skip the remaining*.
// This causes trouble for subsequent JPEGBuffer.loadBuf() calls, because it will overwrite the same
// number of bytes *at the end* of the buffer.
// This image has a 38 (36) byte App14/Adobe segment.
// The length 36 - 12 = 24 (the size of the missing bytes!)
// TODO: Report bug!
ImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/progressive-adobe-sof-bands-dont-match-sos-band-count.jpg")));
IIOMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
assertThat(tree, new IsInstanceOf(IIOMetadataNode.class));
}
finally {
reader.dispose();
}
}
@Test
public void testInvalidDHTIssue() throws IOException {
// Image has empty (!) DHT that is okay on read, but not when you set back from tree...
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-progressive-invalid-dht.jpg")));
IIOMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
assertThat(tree, new IsInstanceOf(IIOMetadataNode.class));
}
finally {
reader.dispose();
}
}
@Test
public void testComponentIdOutOfRange() throws IOException {
// Image has SOF and SOS component ids that are negative, setFromTree chokes on this...
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-component-id-out-of-range.jpg")));
IIOMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
assertNotNull(tree);
assertThat(tree, new IsInstanceOf(IIOMetadataNode.class));
}
finally {
reader.dispose();
}
}
@Test
public void testGetRawImageTypeAdobeAPP14CMYKAnd3channelData() throws IOException {
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
ImageTypeSpecifier rawType = reader.getRawImageType(0);
assertNotNull(rawType); // As of Java 9, use RGB for YCC and CMYK for YCCK
}
finally {
reader.dispose();
}
}
@Test
public void testReadAdobeAPP14CMYKAnd3channelData() throws IOException {
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
IIOReadWarningListener listener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(listener);
assertEquals(310, reader.getWidth(0));
assertEquals(384, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
verify(listener, times(1)).warningOccurred(eq(reader), matches("(?i).*Adobe App14.*(?-i)CMYK.*SOF.*"));
assertNotNull(image);
assertEquals(310, image.getWidth());
assertEquals(384, image.getHeight());
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
}
finally {
reader.dispose();
}
}
@Test
public void testReadDuplicateComponentIds() throws IOException {
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/duplicate-component-ids.jpg")));
IIOReadWarningListener listener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(listener);
assertEquals(367, reader.getWidth(0));
assertEquals(242, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
verify(listener, times(1)).warningOccurred(eq(reader), and(matches("(?i).*duplicate component ID.*(?-i)SOF.*"), contains("1")));
verify(listener, times(1)).warningOccurred(eq(reader), and(matches("(?i).*duplicate component ID.*(?-i)SOS.*"), contains("1")));
assertNotNull(image);
assertEquals(367, image.getWidth());
assertEquals(242, image.getHeight());
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
}
finally {
reader.dispose();
}
}
@Test
public void testReadSequenceInverse() throws IOException {
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-with-preview-as-second-image.jpg"))) {
reader.setInput(stream);
BufferedImage image = reader.read(1, null);
assertNotNull(image);
assertEquals(640, image.getWidth());
assertEquals(480, image.getHeight());
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
image = reader.read(0, null);
assertNotNull(image);
assertEquals(3968, image.getWidth());
assertEquals(2976, image.getHeight());
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
assertEquals(2, reader.getNumImages(true));
}
finally {
reader.dispose();
}
}
@Test
public void testStreamOffset() throws IOException {
// Tests a known issue:
// If the JPEGImageReader reads an embedded JPEG stream, we can't assume SOI starts at pos 0,
// instead, we'll just assume SOI at the current stream position.
JPEGImageReader reader = createReader();
try {
// Prepend the data with random padding
InputStream input = new SequenceInputStream(new ByteArrayInputStream(new byte[42]),
getClass().getResourceAsStream("/jpeg/gray-sample.jpg"));
ImageInputStream stream = ImageIO.createImageInputStream(input);
// Skip padding
stream.seek(42);
reader.setInput(stream);
assertEquals(386, reader.getWidth(0));
assertEquals(396, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
assertNotNull(image);
assertEquals(386, image.getWidth());
assertEquals(396, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testReadEmptyICCProfile() throws IOException {
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-empty-icc-profile.jpeg"))) {
reader.setInput(stream);
assertEquals(612, reader.getWidth(0));
assertEquals(816, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
assertNotNull(image);
assertEquals(612, image.getWidth());
assertEquals(816, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testInfiniteLoopCorrupt() throws IOException {
ImageReader reader = createReader();
assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
try (ImageInputStream iis = ImageIO.createImageInputStream(getClassLoaderResource("/broken-jpeg/110115680-6d6dce80-7d84-11eb-99df-4cb21df3b09f.jpeg"))) {
reader.setInput(iis);
try {
reader.read(0, null);
} catch (IIOException expected) {
assertThat(expected.getMessage(), allOf(containsString("SOF"), containsString("stream")));
}
}
});
}
@Test
public void testInfiniteLoopCorruptRaster() throws IOException {
ImageReader reader = createReader();
assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
try (ImageInputStream iis = ImageIO.createImageInputStream(getClassLoaderResource("/broken-jpeg/110115680-6d6dce80-7d84-11eb-99df-4cb21df3b09f.jpeg"))) {
reader.setInput(iis);
try {
reader.readRaster(0, null);
}
catch (IIOException expected) {
assertThat(expected.getMessage(), allOf(containsString("SOF"), containsString("stream")));
}
}
});
}
}