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 new file mode 100644 index 00000000..fd64e3a4 --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * JPEG + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @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 APP0 = 0xFFE0; + int APP1 = 0xFFE1; + int APP2 = 0xFFE2; + int APP14 = 0xFFEE; + + int SOF0 = 0xFFC0; + int SOF1 = 0xFFC1; + int SOF2 = 0xFFC2; + int SOF3 = 0xFFC3; + int SOF5 = 0xFFC5; + int SOF6 = 0xFFC6; + int SOF7 = 0xFFC7; + int SOF9 = 0xFFC9; + int SOF10 = 0xFFCA; + int SOF11 = 0xFFCB; + int SOF13 = 0xFFCD; + int SOF14 = 0xFFCE; + int SOF15 = 0xFFCF; +} 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 new file mode 100644 index 00000000..5e674057 --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -0,0 +1,186 @@ +/* + * 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 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; + +/** + * JPEGSegmentUtil + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @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(); + + private JPEGSegmentUtil() {} + + // TODO: Allow for multiple images (multiple SOI markers), using specified index? + public static List readSegments(final ImageInputStream stream, final int appMarker, final String segmentName) throws IOException { + return readSegments(stream, Collections.singletonMap(appMarker, Collections.singletonList(segmentName))); + } + + public static List readSegments(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { + readSOI(stream); + + List segments = Collections.emptyList(); + + Segment segment; + try { + 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.add(segment); + } + } + } + catch (EOFException ignore) { + // Just end here, in case of malformed stream + } + + return segments; + } + + private static boolean isRequested(Segment segment, Map> segmentIdentifiers) { + return segmentIdentifiers == ALL_SEGMENTS || + (segmentIdentifiers.containsKey(segment.marker) && + (segment.identifier() == null && segmentIdentifiers.get(segment.marker) == null || containsSafe(segment, segmentIdentifiers))); + } + + private static boolean containsSafe(Segment segment, Map> segmentIdentifiers) { + List identifiers = segmentIdentifiers.get(segment.marker); + return identifiers != null && identifiers.contains(segment.identifier()); + } + + private static boolean isImageDone(final Segment 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; + } + + 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")); + } + + static void readSOI(final ImageInputStream stream) throws IOException { + if (stream.readUnsignedShort() != JPEG.SOI) { + throw new IIOException("Not a JPEG stream"); + } + } + + static Segment readSegment(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { + int marker = stream.readUnsignedShort(); + int length = stream.readUnsignedShort(); // Length including length field itself + + byte[] data; + + if (segmentIdentifiers == ALL_SEGMENTS || segmentIdentifiers.containsKey(marker)) { + data = new byte[length - 2]; + stream.readFully(data); + } + else { + data = null; + stream.skipBytes(length - 2); + } + + return new Segment(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; + } + + @Override + public String toString() { + return String.format("Segment[%04x/%s size: %d]", marker, identifier(), segmentLength()); + } + } +}