diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index 116fd3f8..1b73b948 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -29,11 +29,13 @@ package com.twelvemonkeys.imageio.plugins.jpeg; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.lang.Validate; import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStreamImpl; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -90,7 +92,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { int marker = stream.readUnsignedShort(); // TODO: Refactor to make various segments optional, we probably only want the "Adobe" APP14 segment, 'Exif' APP1 and very few others - if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && marker != JPEG.APP1 && marker != JPEG.APP14) { + if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && !(marker == JPEG.APP1 && isAppSegmentWithId("Exif", stream)) && marker != JPEG.APP14) { int length = stream.readUnsignedShort(); // Length including length field itself stream.seek(realPosition + 2 + length); // Skip marker (2) + length } @@ -138,6 +140,38 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { return segment; } + private static boolean isAppSegmentWithId(String segmentId, ImageInputStream stream) throws IOException { + Validate.notNull(segmentId, "segmentId"); + + stream.mark(); + + try { + int length = stream.readUnsignedShort(); // Length including length field itself + + byte[] data = new byte[Math.max(20, length - 2)]; + stream.readFully(data); + + return segmentId.equals(asNullTerminatedAsciiString(data, 0)); + } + finally { + stream.reset(); + } + } + + static String asNullTerminatedAsciiString(final byte[] data, final int offset) { + for (int i = 0; i < data.length - offset; i++) { + if (data[i] == 0 || i > 255) { + return asAsciiString(data, offset, offset + i); + } + } + + return null; + } + + static String asAsciiString(final byte[] data, final int offset, final int length) { + return new String(data, offset, length, Charset.forName("ascii")); + } + private void streamInit() throws IOException { stream.seek(0); diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java index d120aed8..5749593c 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java @@ -102,7 +102,7 @@ public class JPEGSegmentImageInputStreamTest { assertThat(length, new LessOrEqual(10203l)); // In no case should length increase - assertEquals(9495l, length); // May change, if more chunks are passed to reader... + assertEquals(9625l, length); // May change, if more chunks are passed to reader... } @Test @@ -110,14 +110,17 @@ public class JPEGSegmentImageInputStreamTest { ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"))); List appSegments = JPEGSegmentUtil.readSegments(stream, JPEGSegmentUtil.APP_SEGMENTS); - assertEquals(2, appSegments.size()); + assertEquals(3, appSegments.size()); assertEquals(JPEG.APP0, appSegments.get(0).marker()); assertEquals("JFIF", appSegments.get(0).identifier()); - assertEquals(JPEG.APP14, appSegments.get(1).marker()); - assertEquals("Adobe", appSegments.get(1).identifier()); + assertEquals(JPEG.APP1, appSegments.get(1).marker()); + assertEquals("Exif", appSegments.get(1).identifier()); - // And thus, no Exif, no ICC_PROFILE or other segments + assertEquals(JPEG.APP14, appSegments.get(2).marker()); + assertEquals("Adobe", appSegments.get(2).identifier()); + + // And thus, no XMP, no ICC_PROFILE or other segments } }