diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java index fd64e3a4..bdc8726d 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java @@ -36,9 +36,9 @@ package com.twelvemonkeys.imageio.metadata.jpeg; * @version $Id: JPEG.java,v 1.0 11.02.11 15.51 haraldk Exp$ */ public interface JPEG { - int SOS = 0xFFDA; int SOI = 0xFFD8; int EOI = 0xFFD9; + int SOS = 0xFFDA; int APP0 = 0xFFE0; int APP1 = 0xFFE1; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegment.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegment.java new file mode 100644 index 00000000..57711e61 --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegment.java @@ -0,0 +1,105 @@ +/* + * 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 "TwelveMonkeys" 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 OWNER 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.metadata.jpeg; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Arrays; + +/** + * Represents a JPEG segment. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JPEGSegment.java,v 1.0 02.03.11 10.44 haraldk Exp$ + */ +public final class JPEGSegment implements Serializable { + final int marker; + final byte[] data; + private transient String id; + + JPEGSegment(int marker, byte[] data) { + this.marker = marker; + this.data = data; + } + + int segmentLength() { + // This is the length field as read from the stream + return data != null ? data.length + 2 : 0; + } + + public int marker() { + return marker; + } + + public String identifier() { + if (id == null) { + if (marker >= 0xFFE0 && marker <= 0xFFEF) { + // Only for APPn markers + id = JPEGSegmentUtil.asNullTerminatedAsciiString(data, 0); + } + } + + return id; + } + + public InputStream data() { + return data != null ? new ByteArrayInputStream(data, offset(), length()) : null; + } + + public int length() { + return data != null ? data.length - offset() : 0; + } + + private int offset() { + String identifier = identifier(); + return identifier == null ? 0 : identifier.length() + 1; + } + + @Override + public String toString() { + String identifier = identifier(); + if (identifier != null) { + return String.format("JPEGSegment[%04x/%s size: %d]", marker, identifier, segmentLength()); + } + return String.format("JPEGSegment[%04x size: %d]", marker, segmentLength()); + } + + @Override + public int hashCode() { + String identifier = identifier(); + return marker() << 16 | (identifier != null ? identifier.hashCode() : 0) & 0xFFFF; + } + + @Override + public boolean equals(Object other) { + return other instanceof JPEGSegment && ((JPEGSegment) other).marker == marker && Arrays.equals(((JPEGSegment) other).data, data); + } +} diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 9d1ba02f..cab3268c 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -32,10 +32,7 @@ import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.io.*; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; /** * JPEGSegmentUtil @@ -45,29 +42,67 @@ import java.util.Map; * @version $Id: JPEGSegmentUtil.java,v 1.0 24.01.11 17.37 haraldk Exp$ */ public final class JPEGSegmentUtil { - public static final Map> ALL_SEGMENTS = Collections.emptyMap(); + public static final List ALL_IDS = Collections.unmodifiableList(new AllIdsList()); + public static final Map> ALL_SEGMENTS = Collections.unmodifiableMap(new AllSegmentsMap()); + public static final Map> APP_SEGMENTS = Collections.unmodifiableMap(createAppSegmentsMap()); private JPEGSegmentUtil() {} - // TODO: Allow for multiple images (multiple SOI markers), using specified index, or document that stream must be placed before SOI of wanted image - public static List readSegments(final ImageInputStream stream, final int imageIndex, final int appMarker, final String segmentName) throws IOException { - return readSegments(stream, Collections.singletonMap(appMarker, Collections.singletonList(segmentName))); + private static Map> createAppSegmentsMap() { + Map> identifiers = new HashMap>(); + + for (int i = 0xFFE0; i <= 0xFFEF; i++) { + identifiers.put(i, JPEGSegmentUtil.ALL_IDS); + } + + return identifiers; } - public static List readSegments(final ImageInputStream stream, final Map> segmentIdentifiers) throws IOException { + /** + * Reads the requested JPEG segments from the stream. + * The stream position must be directly before the SOI marker, and only segments for the current image is read. + * + * @param stream the stream to read from. + * @param marker the segment marker to read + * @param identifier the identifier to read, or {@code null} to match any segment + * @return a list of segments with the given app marker and optional identifier. If no segments are found, an + * empty list is returned. + * @throws IIOException if a JPEG format exception occurs during reading + * @throws IOException if an I/O exception occurs during reading + */ + public static List readSegments(final ImageInputStream stream, final int marker, final String identifier) throws IOException { + return readSegments(stream, Collections.singletonMap(marker, identifier != null ? Collections.singletonList(identifier) : ALL_IDS)); + } + + /** + * Reads the requested JPEG segments from the stream. + * The stream position must be directly before the SOI marker, and only segments for the current image is read. + * + * @param stream the stream to read from. + * @param segmentIdentifiers the segment identifiers + * @return a list of segments with the given app markers and optional identifiers. If no segments are found, an + * empty list is returned. + * @throws IIOException if a JPEG format exception occurs during reading + * @throws IOException if an I/O exception occurs during reading + * + * @see #ALL_SEGMENTS + * @see #APP_SEGMENTS + * @see #ALL_IDS + */ + public static List readSegments(final ImageInputStream stream, final Map> segmentIdentifiers) throws IOException { readSOI(stream); - List segments = Collections.emptyList(); + List segments = Collections.emptyList(); - Segment segment; + JPEGSegment segment; try { - while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) { -// while (!isImageDone(segment = readSegment(stream, ALL_SEGMENTS))) { -// System.err.println("segment: " + segment); +// while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) { + while (!isImageDone(segment = readSegment(stream, ALL_SEGMENTS))) { + System.err.println("segment: " + segment); if (isRequested(segment, segmentIdentifiers)) { if (segments == Collections.EMPTY_LIST) { - segments = new ArrayList(); + segments = new ArrayList(); } segments.add(segment); @@ -78,21 +113,23 @@ public final class JPEGSegmentUtil { // Just end here, in case of malformed stream } + // TODO: Should probably skip until EOI, so that multiple invocations succeeds for multiple image streams. + return segments; } - private static boolean isRequested(Segment segment, Map> segmentIdentifiers) { + private static boolean isRequested(JPEGSegment segment, Map> segmentIdentifiers) { return segmentIdentifiers == ALL_SEGMENTS || - (segmentIdentifiers.containsKey(segment.marker) && - (segment.identifier() == null && segmentIdentifiers.get(segment.marker) == null || containsSafe(segment, segmentIdentifiers))); + (segmentIdentifiers.containsKey(segment.marker) && (segmentIdentifiers.get(segment.marker) == ALL_IDS || + (segment.identifier() == null && segmentIdentifiers.get(segment.marker) == null || containsSafe(segment, segmentIdentifiers)))); } - private static boolean containsSafe(Segment segment, Map> segmentIdentifiers) { + private static boolean containsSafe(JPEGSegment segment, Map> segmentIdentifiers) { List identifiers = segmentIdentifiers.get(segment.marker); return identifiers != null && identifiers.contains(segment.identifier()); } - private static boolean isImageDone(final Segment segment) { + private static boolean isImageDone(final JPEGSegment segment) { // We're done with this image if we encounter a SOS, EOI (or a new SOI, but that should never happen) return segment.marker == JPEG.SOS || segment.marker == JPEG.EOI || segment.marker == JPEG.SOI; } @@ -117,7 +154,7 @@ public final class JPEGSegmentUtil { } } - static Segment readSegment(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { + static JPEGSegment readSegment(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { int marker = stream.readUnsignedShort(); int length = stream.readUnsignedShort(); // Length including length field itself @@ -132,55 +169,20 @@ public final class JPEGSegmentUtil { stream.skipBytes(length - 2); } - return new Segment(marker, data); + return new JPEGSegment(marker, data); } - public static final class Segment { - private final int marker; - private final byte[] data; - private String id; - - Segment(int marker, byte[] data) { - this.marker = marker; - this.data = data; - } - - int segmentLength() { - // This is the length field as read from the stream - return data.length + 2; - } - - public int marker() { - return marker; - } - - public String identifier() { - if (id == null) { - if (marker >= 0xFFE0 && marker <= 0xFFEF) { - // Only for APPn markers - id = asNullTerminatedAsciiString(data, 0); - } - } - - return id; - } - - public InputStream data() { - return new ByteArrayInputStream(data, offset(), length()); - } - - public int length() { - return data.length - offset(); - } - - private int offset() { - String identifier = identifier(); - return identifier == null ? 0 : identifier.length() + 1; - } - + private static class AllIdsList extends ArrayList { @Override public String toString() { - return String.format("Segment[%04x/%s size: %d]", marker, identifier(), segmentLength()); + return "[All ids]"; + } + } + + private static class AllSegmentsMap extends HashMap> { + @Override + public String toString() { + return "{All segments}"; } } } diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentTest.java new file mode 100644 index 00000000..26c16bce --- /dev/null +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentTest.java @@ -0,0 +1,80 @@ +/* + * 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 "TwelveMonkeys" 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 OWNER 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.metadata.jpeg; + +import com.twelvemonkeys.lang.ObjectAbstractTestCase; +import org.junit.Test; + +import java.nio.charset.Charset; + +/** + * JPEGSegmentTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JPEGSegmentTest.java,v 1.0 02.03.11 10.46 haraldk Exp$ + */ +public class JPEGSegmentTest extends ObjectAbstractTestCase { + @Test + public void testCreate() { + byte[] bytes = new byte[14]; + System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4); + + JPEGSegment segment = new JPEGSegment(0xFFE0, bytes); + + assertEquals(0xFFE0, segment.marker()); + assertEquals("JFIF", segment.identifier()); + assertEquals(16, segment.segmentLength()); + assertEquals(bytes.length - 5, segment.length()); + } + + @Test + public void testToStringAppSegment() { + byte[] bytes = new byte[14]; + System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4); + JPEGSegment segment = new JPEGSegment(0xFFE0, bytes); + + assertEquals("JPEGSegment[ffe0/JFIF size: 16]", segment.toString()); + } + + @Test + public void testToStringNonAppSegment() { + byte[] bytes = new byte[40]; + JPEGSegment segment = new JPEGSegment(0xFFC4, bytes); + + assertEquals("JPEGSegment[ffc4 size: 42]", segment.toString()); + } + + @Override + protected Object makeObject() { + byte[] bytes = new byte[14]; + System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4); + return new JPEGSegment(0xFFE0, bytes); + } +} diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java new file mode 100644 index 00000000..fa847c2c --- /dev/null +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java @@ -0,0 +1,199 @@ +/* + * 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 "TwelveMonkeys" 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 OWNER 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.metadata.jpeg; + +import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; +import org.junit.Test; + +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.spi.IIORegistry; +import javax.imageio.stream.ImageInputStream; +import java.awt.color.ICC_Profile; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * JPEGSegmentUtilTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JPEGSegmentUtilTest.java,v 1.0 01.03.11 16.22 haraldk Exp$ + */ +public class JPEGSegmentUtilTest { + static { + IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); + } + + // TODO: Test the JPEGSegment class?! + + protected ImageInputStream getData(final String name) throws IOException { + return ImageIO.createImageInputStream(getClass().getResource(name)); + } + + @Test + public void testReadAPP0JFIF() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEG.APP0, "JFIF"); + + assertEquals(1, segments.size()); + JPEGSegment segment = segments.get(0); + assertEquals(JPEG.APP0, segment.marker()); + assertEquals("JFIF", segment.identifier()); + assertEquals(16, segment.segmentLength()); + } + + @Test + public void testReadAPP1Exif() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEG.APP1, "Exif"); + + assertEquals(1, segments.size()); + JPEGSegment segment = segments.get(0); + assertEquals(JPEG.APP1, segment.marker()); + assertEquals("Exif", segment.identifier()); + } + + @Test + public void testReadAPP1XMP() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEG.APP1, "http://ns.adobe.com/xap/1.0/"); + + assertEquals(1, segments.size()); + JPEGSegment segment = segments.get(0); + assertEquals(JPEG.APP1, segment.marker()); + assertEquals("http://ns.adobe.com/xap/1.0/", segment.identifier()); + } + + @Test + public void testReadAPP13Photoshop() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), 0xFFED, "Photoshop 3.0"); + + assertEquals(1, segments.size()); + JPEGSegment segment = segments.get(0); + assertEquals(0xFFED, segment.marker()); + assertEquals("Photoshop 3.0", segment.identifier()); + } + + @Test + public void testReadAPP14Adobe() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEG.APP14, "Adobe"); + + assertEquals(1, segments.size()); + JPEGSegment segment = segments.get(0); + assertEquals(JPEG.APP14, segment.marker()); + assertEquals("Adobe", segment.identifier()); + assertEquals(14, segment.segmentLength()); + } + + @Test + public void testReadAPP2ICC_PROFILE() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEG.APP2, "ICC_PROFILE"); + + assertEquals(18, segments.size()); + + for (JPEGSegment segment : segments) { + assertEquals(JPEG.APP2, segment.marker()); + assertEquals("ICC_PROFILE", segment.identifier()); + } + + // Test that we can actually read the chunked ICC profile + DataInputStream stream = new DataInputStream(segments.get(0).data()); + int chunkNumber = stream.readUnsignedByte(); + int chunkCount = stream.readUnsignedByte(); + + InputStream[] streams = new InputStream[chunkCount]; + streams[chunkNumber - 1] = stream; + + for (int i = 1; i < chunkCount; i++) { + stream = new DataInputStream(segments.get(i).data()); + + chunkNumber = stream.readUnsignedByte(); + if (stream.readUnsignedByte() != chunkCount) { + throw new IIOException(String.format("Bad number of 'ICC_PROFILE' chunks.")); + } + + streams[chunkNumber - 1] = stream; + } + + ICC_Profile profile = ICC_Profile.getInstance(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams)))); + assertNotNull("Profile could not be read, probably bad data", profile); + } + + @Test + public void testReadAll() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEGSegmentUtil.ALL_SEGMENTS); + assertEquals(6, segments.size()); + + assertEquals(segments.toString(), JPEG.SOF0, segments.get(3).marker()); + assertEquals(segments.toString(), null, segments.get(3).identifier()); + } + + @Test + public void testReadAllAlt() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEGSegmentUtil.ALL_SEGMENTS); + assertEquals(26, segments.size()); + + assertEquals(segments.toString(), JPEG.SOF0, segments.get(23).marker()); + assertEquals(segments.toString(), null, segments.get(23).identifier()); + } + + @Test + public void testReadAppMarkers() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEGSegmentUtil.APP_SEGMENTS); + assertEquals(2, segments.size()); + + assertEquals(JPEG.APP0, segments.get(0).marker()); + assertEquals("JFIF", segments.get(0).identifier()); + assertEquals(JPEG.APP14, segments.get(1).marker()); + assertEquals("Adobe", segments.get(1).identifier()); + } + + @Test + public void testReadAppMarkersAlt() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEGSegmentUtil.APP_SEGMENTS); + assertEquals(22, segments.size()); + + assertEquals(JPEG.APP1, segments.get(0).marker()); + assertEquals("Exif", segments.get(0).identifier()); + assertEquals(0xFFED, segments.get(1).marker()); + assertEquals("Photoshop 3.0", segments.get(1).identifier()); + assertEquals(JPEG.APP1, segments.get(2).marker()); + assertEquals("http://ns.adobe.com/xap/1.0/", segments.get(2).identifier()); + assertEquals(JPEG.APP2, segments.get(3).marker()); + assertEquals("ICC_PROFILE", segments.get(3).identifier()); + // ... + assertEquals(JPEG.APP14, segments.get(21).marker()); + assertEquals("Adobe", segments.get(21).identifier()); + } +} diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/9788245605525.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/9788245605525.jpg new file mode 100644 index 00000000..295234be Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/9788245605525.jpg differ diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/ts_open_300dpi.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/ts_open_300dpi.jpg new file mode 100644 index 00000000..6e95a9d6 Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/ts_open_300dpi.jpg differ