diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java
new file mode 100644
index 00000000..9c2be75a
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+/**
+* AdobeDCT
+*
+* @author Harald Kuhr
+* @author last modified by $Author: haraldk$
+* @version $Id: AdobeDCT.java,v 1.0 23.04.12 16:55 haraldk Exp$
+*/
+class AdobeDCT {
+ public static final int Unknown = 0;
+ public static final int YCC = 1;
+ public static final int YCCK = 2;
+
+ final int version;
+ final int flags0;
+ final int flags1;
+ final int transform;
+
+ public AdobeDCT(int version, int flags0, int flags1, int transform) {
+ this.version = version; // 100 or 101
+ this.flags0 = flags0;
+ this.flags1 = flags1;
+ this.transform = transform;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public int getFlags0() {
+ return flags0;
+ }
+
+ public int getFlags1() {
+ return flags1;
+ }
+
+ public int getTransform() {
+ return transform;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "AdobeDCT[ver: %d.%02d, flags: %s %s, transform: %d]",
+ getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform()
+ );
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java
new file mode 100644
index 00000000..2061af3e
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.lang.ref.SoftReference;
+import java.util.Arrays;
+
+/**
+ * EXIFThumbnail
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: EXIFThumbnail.java,v 1.0 18.04.12 12:19 haraldk Exp$
+ */
+final class EXIFThumbnailReader extends ThumbnailReader {
+ private final Directory ifd;
+ private final ImageInputStream stream;
+ private final int compression;
+
+ private transient SoftReference cachedThumbnail;
+
+ public EXIFThumbnailReader(JPEGImageReader parent, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
+ super(parent, imageIndex, thumbnailIndex);
+ this.ifd = ifd;
+ this.stream = stream;
+
+ Entry compression = ifd.getEntryById(TIFF.TAG_COMPRESSION);
+
+ this.compression = compression != null ? ((Number) compression.getValue()).intValue() : 6;
+ }
+
+ @Override
+ public BufferedImage read() throws IOException {
+ if (compression == 1) { // 1 = no compression
+ processThumbnailStarted();
+ BufferedImage thumbnail = readUncompressed();
+ processThumbnailProgress(100f);
+ processThumbnailComplete();
+
+ return thumbnail;
+ }
+ else if (compression == 6) { // 6 = JPEG compression
+ processThumbnailStarted();
+ BufferedImage thumbnail = readJPEGCached(true);
+ processThumbnailProgress(100f);
+ processThumbnailComplete();
+
+ return thumbnail;
+ }
+ else {
+ throw new IIOException("Unsupported EXIF thumbnail compression: " + compression);
+ }
+ }
+
+ private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException {
+ BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null;
+
+ if (thumbnail == null) {
+ thumbnail = readJPEG();
+ }
+
+ cachedThumbnail = pixelsExposed ? null : new SoftReference(thumbnail);
+
+ return thumbnail;
+ }
+
+ private BufferedImage readJPEG() throws IOException {
+ // IFD1 should contain JPEG offset for JPEG thumbnail
+ Entry jpegOffset = ifd.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
+
+ if (jpegOffset != null) {
+ stream.seek(((Number) jpegOffset.getValue()).longValue());
+ InputStream input = IIOUtil.createStreamAdapter(stream);
+
+ // For certain EXIF files (encoded with TIFF.TAG_YCBCR_POSITIONING = 2?), we need
+ // EXIF information to read the thumbnail correctly (otherwise the colors are messed up).
+
+ // HACK: Splice empty EXIF information into the thumbnail stream
+ byte[] fakeEmptyExif = {
+ // SOI (from original data)
+ (byte) input.read(), (byte) input.read(),
+ // APP1 + len (016) + 'Exif' + 0-term + pad
+ (byte) 0xFF, (byte) 0xE1, 0, 16, 'E', 'x', 'i', 'f', 0, 0,
+ // Big-endian BOM (MM), TIFF magic (042), offset (0000)
+ 'M', 'M', 0, 42, 0, 0, 0, 0,
+ };
+
+ input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
+ try {
+ return readJPEGThumbnail(input);
+ }
+ finally {
+ input.close();
+ }
+ }
+
+ return null;
+ }
+
+ private BufferedImage readUncompressed() throws IOException {
+ // Read ImageWidth, ImageLength (height) and BitsPerSample (=8 8 8, always)
+ // PhotometricInterpretation (2=RGB, 6=YCbCr), SamplesPerPixel (=3, always),
+ Entry width = ifd.getEntryById(TIFF.TAG_IMAGE_WIDTH);
+ Entry height = ifd.getEntryById(TIFF.TAG_IMAGE_HEIGHT);
+
+ if (width == null || height == null) {
+ throw new IIOException("Missing dimensions for RAW EXIF thumbnail");
+ }
+
+ Entry bitsPerSample = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
+ Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXELS);
+ Entry photometricInterpretation = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
+
+ // Required
+ int w = ((Number) width.getValue()).intValue();
+ int h = ((Number) height.getValue()).intValue();
+
+ if (bitsPerSample != null) {
+ int[] bpp = (int[]) bitsPerSample.getValue();
+ if (!Arrays.equals(bpp, new int[] {8, 8, 8})) {
+ throw new IIOException("Unknown bits per sample for RAW EXIF thumbnail: " + bitsPerSample.getValueAsString());
+ }
+ }
+
+ if (samplesPerPixel != null && (Integer) samplesPerPixel.getValue() != 3) {
+ throw new IIOException("Unknown samples per pixel for RAW EXIF thumbnail: " + samplesPerPixel.getValueAsString());
+ }
+
+ int interpretation = photometricInterpretation != null ? ((Number) photometricInterpretation.getValue()).intValue() : 2;
+
+ // IFD1 should contain strip offsets for uncompressed images
+ Entry offset = ifd.getEntryById(TIFF.TAG_STRIP_OFFSETS);
+ if (offset != null) {
+ stream.seek(((Number) offset.getValue()).longValue());
+
+ // Read raw image data, either RGB or YCbCr
+ int thumbSize = w * h * 3;
+ byte[] thumbData = JPEGImageReader.readFully(stream, thumbSize);
+
+ switch (interpretation) {
+ case 2:
+ // RGB
+ break;
+ case 6:
+ // YCbCr
+ for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
+ JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
+ }
+ break;
+ default:
+ throw new IIOException("Unknown photometric interpretation for RAW EXIF thumbnail: " + interpretation);
+ }
+
+ return ThumbnailReader.readRawThumbnail(thumbData, thumbData.length, 0, w, h);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getWidth() throws IOException {
+ if (compression == 1) { // 1 = no compression
+ Entry width = ifd.getEntryById(TIFF.TAG_IMAGE_WIDTH);
+
+ if (width == null) {
+ throw new IIOException("Missing dimensions for RAW EXIF thumbnail");
+ }
+
+ return ((Number) width.getValue()).intValue();
+ }
+ else if (compression == 6) { // 6 = JPEG compression
+ return readJPEGCached(false).getWidth();
+ }
+ else {
+ throw new IIOException("Unsupported EXIF thumbnail compression: " + compression);
+ }
+ }
+
+ @Override
+ public int getHeight() throws IOException {
+ if (compression == 1) { // 1 = no compression
+ Entry height = ifd.getEntryById(TIFF.TAG_IMAGE_HEIGHT);
+
+ if (height == null) {
+ throw new IIOException("Missing dimensions for RAW EXIF thumbnail");
+ }
+
+ return ((Number) height.getValue()).intValue();
+ }
+ else if (compression == 6) { // 6 = JPEG compression
+ return readJPEGCached(false).getHeight();
+ }
+ else {
+ throw new IIOException("Unsupported EXIF thumbnail compression: " + compression);
+ }
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java
new file mode 100644
index 00000000..71497a3f
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+/**
+* JFIFSegment
+*
+* @author Harald Kuhr
+* @author last modified by $Author: haraldk$
+* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
+*/
+class JFIFSegment {
+ final int majorVersion;
+ final int minorVersion;
+ final int units;
+ final int xDensity;
+ final int yDensity;
+ final int xThumbnail;
+ final int yThumbnail;
+ final byte[] thumbnail;
+
+ public JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ this.units = units;
+ this.xDensity = xDensity;
+ this.yDensity = yDensity;
+ this.xThumbnail = xThumbnail;
+ this.yThumbnail = yThumbnail;
+ this.thumbnail = thumbnail;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("JFIF v%d.%02d %dx%d %s (%s)", majorVersion, minorVersion, xDensity, yDensity, unitsAsString(), thumbnailToString());
+ }
+
+ private String unitsAsString() {
+ switch (units) {
+ case 0:
+ return "(aspect only)";
+ case 1:
+ return "dpi";
+ case 2:
+ return "dpcm";
+ default:
+ return "(unknown unit)";
+ }
+ }
+
+ private String thumbnailToString() {
+ if (xThumbnail == 0 || yThumbnail == 0) {
+ return "no thumbnail";
+ }
+
+ return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java
new file mode 100644
index 00000000..a696dc27
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * JFIFThumbnailReader
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: JFIFThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$
+ */
+final class JFIFThumbnailReader extends ThumbnailReader {
+ private final JFIFSegment segment;
+
+ public JFIFThumbnailReader(JPEGImageReader parent, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
+ super(parent, imageIndex, thumbnailIndex);
+ this.segment = segment;
+ }
+
+ @Override
+ public BufferedImage read() {
+ processThumbnailStarted();
+ BufferedImage thumbnail = readRawThumbnail(segment.thumbnail, segment.thumbnail.length, 0, segment.xThumbnail, segment.yThumbnail);
+
+ processThumbnailProgress(100f);
+ processThumbnailComplete();
+
+ return thumbnail;
+ }
+
+ @Override
+ public int getWidth() throws IOException {
+ return segment.xThumbnail;
+ }
+
+ @Override
+ public int getHeight() throws IOException {
+ return segment.yThumbnail;
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java
new file mode 100644
index 00000000..edb03f66
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+/**
+* JFXXSegment
+*
+* @author Harald Kuhr
+* @author last modified by $Author: haraldk$
+* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
+*/
+class JFXXSegment {
+ public static final int JPEG = 0x10;
+ public static final int INDEXED = 0x11;
+ public static final int RGB = 0x13;
+
+ final int extensionCode;
+ final byte[] thumbnail;
+
+ public JFXXSegment(int extensionCode, byte[] thumbnail) {
+ this.extensionCode = extensionCode;
+ this.thumbnail = thumbnail;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("JFXX extension (%s thumb size: %d)", extensionAsString(), thumbnail.length);
+ }
+
+ private String extensionAsString() {
+ switch (extensionCode) {
+ case JPEG:
+ return "JPEG";
+ case INDEXED:
+ return "Indexed";
+ case RGB:
+ return "RGB";
+ default:
+ return String.valueOf(extensionCode);
+ }
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java
new file mode 100644
index 00000000..c70a8058
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
+
+import javax.imageio.IIOException;
+import java.awt.image.*;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+
+/**
+ * JFXXThumbnailReader
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: JFXXThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$
+ */
+final class JFXXThumbnailReader extends ThumbnailReader {
+
+ private final JFXXSegment segment;
+
+ private transient SoftReference cachedThumbnail;
+
+ protected JFXXThumbnailReader(final JPEGImageReader parent, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
+ super(parent, imageIndex, thumbnailIndex);
+ this.segment = segment;
+ }
+
+ @Override
+ public BufferedImage read() throws IOException {
+ processThumbnailStarted();
+
+ BufferedImage thumbnail;
+ switch (segment.extensionCode) {
+ case JFXXSegment.JPEG:
+ thumbnail = readJPEGCached(true);
+ break;
+ case JFXXSegment.INDEXED:
+ thumbnail = readIndexed();
+ break;
+ case JFXXSegment.RGB:
+ thumbnail = readRGB();
+ break;
+ default:
+ throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
+ }
+
+ processThumbnailProgress(100f);
+ processThumbnailComplete();
+
+ return thumbnail;
+ }
+
+ private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException {
+ BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null;
+
+ if (thumbnail == null) {
+ thumbnail = readJPEGThumbnail(new ByteArrayInputStream(segment.thumbnail));
+ }
+
+ cachedThumbnail = pixelsExposed ? null : new SoftReference(thumbnail);
+
+ return thumbnail;
+ }
+
+ @Override
+ public int getWidth() throws IOException {
+ switch (segment.extensionCode) {
+ case JFXXSegment.RGB:
+ case JFXXSegment.INDEXED:
+ return segment.thumbnail[0] & 0xff;
+ case JFXXSegment.JPEG:
+ return readJPEGCached(false).getWidth();
+ default:
+ throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
+ }
+ }
+
+ @Override
+ public int getHeight() throws IOException {
+ switch (segment.extensionCode) {
+ case JFXXSegment.RGB:
+ case JFXXSegment.INDEXED:
+ return segment.thumbnail[1] & 0xff;
+ case JFXXSegment.JPEG:
+ return readJPEGCached(false).getHeight();
+ default:
+ throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
+ }
+ }
+
+ private BufferedImage readIndexed() {
+ // 1 byte: xThumb
+ // 1 byte: yThumb
+ // 768 bytes: palette
+ // x * y bytes: 8 bit indexed pixels
+ int w = segment.thumbnail[0] & 0xff;
+ int h = segment.thumbnail[1] & 0xff;
+
+ int[] rgbs = new int[256];
+ for (int i = 0; i < rgbs.length; i++) {
+ rgbs[i] = (segment.thumbnail[3 * i + 2] & 0xff) << 16
+ | (segment.thumbnail[3 * i + 3] & 0xff) << 8
+ | (segment.thumbnail[3 * i + 4] & 0xff);
+ }
+
+ IndexColorModel icm = new InverseColorMapIndexColorModel(8, rgbs.length, rgbs, 0, false, -1, DataBuffer.TYPE_BYTE);
+ DataBufferByte buffer = new DataBufferByte(segment.thumbnail, segment.thumbnail.length - 770, 770);
+ WritableRaster raster = Raster.createPackedRaster(buffer, w, h, 8, null);
+
+ return new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), null);
+ }
+
+ private BufferedImage readRGB() {
+ // 1 byte: xThumb
+ // 1 byte: yThumb
+ // 3 * x * y bytes: 24 bit RGB pixels
+ int w = segment.thumbnail[0] & 0xff;
+ int h = segment.thumbnail[1] & 0xff;
+
+ return ThumbnailReader.readRawThumbnail(segment.thumbnail, segment.thumbnail.length - 2, 2, w, h);
+ }
+}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
index e407bf9c..21e587fd 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -40,7 +39,6 @@ import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
-import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.lang.Validate;
@@ -71,7 +69,7 @@ import java.util.List;
public class JPEGImageReader extends ImageReaderBase {
// TODO: Fix the (stream) metadata inconsistency issues.
// - Sun JPEGMetadata class does not (and can not be made to) support CMYK data.. We need to create all new metadata classes.. :-/
- // TODO: Split thumbnail reading into separate class
+ // TODO: Split thumbnail reading into separate class(es)
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.jpeg.debug"));
@@ -120,7 +118,7 @@ public class JPEGImageReader extends ImageReaderBase {
/** Cached JPEG app segments */
private List segments;
- private List thumbnails;
+ private List thumbnails;
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
super(provider);
@@ -532,123 +530,6 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
- private CompoundDirectory getEXIFMetadata() throws IOException {
- List exifSegments = getAppSegments(JPEG.APP1, "Exif");
-
- if (!exifSegments.isEmpty()) {
- JPEGSegment exif = exifSegments.get(0);
- InputStream data = exif.data();
- //noinspection ResultOfMethodCallIgnored
- data.read(); // Pad
-
- ImageInputStream stream = ImageIO.createImageInputStream(data);
-
- CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
-
- extractEXIFThumbnails(stream, exifMetadata);
-
- return exifMetadata;
- }
-
- return null;
- }
-
- private void extractEXIFThumbnails(ImageInputStream stream, CompoundDirectory exifMetadata) throws IOException {
- if (exifMetadata.directoryCount() == 2) {
- Directory ifd1 = exifMetadata.getDirectory(1);
- Entry compression = ifd1.getEntryById(TIFF.TAG_COMPRESSION);
-
- if (compression != null && compression.getValue().equals(1)) { // 1 = no compression
- // Read ImageWidth, ImageLength (height) and BitsPerSample (=8 8 8, always)
- // PhotometricInterpretation (2=RGB, 6=YCbCr), SamplesPerPixel (=3, always),
- Entry width = ifd1.getEntryById(TIFF.TAG_IMAGE_WIDTH);
- Entry height = ifd1.getEntryById(TIFF.TAG_IMAGE_HEIGHT);
-
- if (width == null || height == null) {
- throw new IIOException("Missing dimensions for RAW EXIF thumbnail");
- }
-
- Entry bitsPerSample = ifd1.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
- Entry samplesPerPixel = ifd1.getEntryById(TIFF.TAG_SAMPLES_PER_PIXELS);
- Entry photometricInterpretation = ifd1.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
-
- // Required
- int w = ((Number) width.getValue()).intValue();
- int h = ((Number) height.getValue()).intValue();
-
- if (bitsPerSample != null) {
- int[] bpp = (int[]) bitsPerSample.getValue();
- if (!Arrays.equals(bpp, new int[] {8, 8, 8})) {
- throw new IIOException("Unknown bits per sample for RAW EXIF thumbnail: " + bitsPerSample.getValueAsString());
- }
- }
-
- if (samplesPerPixel != null && (Integer) samplesPerPixel.getValue() != 3) {
- throw new IIOException("Unknown samples per pixel for RAW EXIF thumbnail: " + samplesPerPixel.getValueAsString());
- }
-
- int interpretation = photometricInterpretation != null ? ((Number) photometricInterpretation.getValue()).intValue() : 2;
-
- // IFD1 should contain strip offsets for uncompressed images
- Entry offset = ifd1.getEntryById(TIFF.TAG_STRIP_OFFSETS);
- if (offset != null) {
- stream.seek(((Number) offset.getValue()).longValue());
-
- // Read raw image data, either RGB or YCbCr
- int thumbSize = w * h * 3;
- byte[] thumbData = readFully(stream, thumbSize);
-
- switch (interpretation) {
- case 2:
- // RGB
- break;
- case 6:
- // YCbCr
- for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
- YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
- }
- break;
- default:
- throw new IIOException("Unknown photometric interpretation for RAW EXIF thumbnail: " + interpretation);
- }
-
- thumbnails.add(readRawThumbnail(thumbData, thumbData.length, 0, w, h));
- }
- }
- else if (compression == null || compression.getValue().equals(6)) { // 6 = JPEG compression
- // IFD1 should contain JPEG offset for JPEG thumbnail
- Entry jpegOffset = ifd1.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
-
- if (jpegOffset != null) {
- stream.seek(((Number) jpegOffset.getValue()).longValue());
- InputStream input = IIOUtil.createStreamAdapter(stream);
-
- // For certain EXIF files (encoded with TIFF.TAG_YCBCR_POSITIONING = 2?), we need
- // EXIF information to read the thumbnail correctly (otherwise the colors are messed up).
-
- // HACK: Splice empty EXIF information into the thumbnail stream
- byte[] fakeEmptyExif = {
- // SOI (from original data)
- (byte) input.read(), (byte) input.read(),
- // APP1 + len (016) + 'Exif' + 0-term + pad
- (byte) 0xFF, (byte) 0xE1, 0, 16, 'E', 'x', 'i', 'f', 0, 0,
- // Big-endian BOM (MM), TIFF magic (042), offset (0000)
- 'M', 'M', 0, 42, 0, 0, 0, 0,
- };
- input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
-
- BufferedImage exifThumb = ImageIO.read(input);
-
- if (exifThumb != null) {
- thumbnails.add(exifThumb);
- }
-
- input.close();
- }
- }
- }
- }
-
private List getAppSegments(final int marker, final String identifier) throws IOException {
initHeader();
@@ -704,7 +585,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
private AdobeDCT getAdobeDCT() throws IOException {
- // TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe app14 markers
+ // TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
List adobe = getAppSegments(JPEG.APP14, "Adobe");
if (!adobe.isEmpty()) {
@@ -722,7 +603,7 @@ public class JPEGImageReader extends ImageReaderBase {
return null;
}
- private JFIF getJFIF() throws IOException{
+ private JFIFSegment getJFIF() throws IOException{
List jfif = getAppSegments(JPEG.APP0, "JFIF");
if (!jfif.isEmpty()) {
@@ -731,7 +612,7 @@ public class JPEGImageReader extends ImageReaderBase {
int x, y;
- return new JFIF(
+ return new JFIFSegment(
stream.readUnsignedByte(),
stream.readUnsignedByte(),
stream.readUnsignedByte(),
@@ -746,7 +627,7 @@ public class JPEGImageReader extends ImageReaderBase {
return null;
}
- private JFXX getJFXX() throws IOException {
+ private JFXXSegment getJFXX() throws IOException {
List jfxx = getAppSegments(JPEG.APP0, "JFXX");
if (!jfxx.isEmpty()) {
@@ -754,7 +635,7 @@ public class JPEGImageReader extends ImageReaderBase {
DataInputStream stream = new DataInputStream(segment.data());
- return new JFXX(
+ return new JFXXSegment(
stream.readUnsignedByte(),
readFully(stream, segment.length() - 1)
);
@@ -763,8 +644,8 @@ public class JPEGImageReader extends ImageReaderBase {
return null;
}
-
- private byte[] readFully(DataInput stream, int len) throws IOException {
+ // TODO: Util method?
+ static byte[] readFully(DataInput stream, int len) throws IOException {
if (len == 0) {
return null;
}
@@ -858,83 +739,61 @@ public class JPEGImageReader extends ImageReaderBase {
@Override
public boolean readerSupportsThumbnails() {
- return true; // We support EXIF thumbnails, even if no JFIF thumbnail is present
+ return true; // We support EXIF, JFIF and JFXX style thumbnails, if present
}
private void readThumbnailMetadata(int imageIndex) throws IOException {
checkBounds(imageIndex);
if (thumbnails == null) {
- thumbnails = new ArrayList();
+ thumbnails = new ArrayList();
- JFIF jfif = getJFIF();
+ JFIFSegment jfif = getJFIF();
if (jfif != null && jfif.thumbnail != null) {
- thumbnails.add(readRawThumbnail(jfif.thumbnail, jfif.thumbnail.length, 0, jfif.xThumbnail, jfif.yThumbnail));
+ thumbnails.add(new JFIFThumbnailReader(this, imageIndex, thumbnails.size(), jfif));
}
- JFXX jfxx = getJFXX();
+ JFXXSegment jfxx = getJFXX();
if (jfxx != null && jfxx.thumbnail != null) {
switch (jfxx.extensionCode) {
- case JFXX.JPEG:
- thumbnails.add(ImageIO.read(new ByteArrayInputStream(jfxx.thumbnail)));
- break;
- case JFXX.INDEXED:
- // 1 byte: xThumb
- // 1 byte: yThumb
- // 768 bytes: palette
- // x * y bytes: 8 bit indexed pixels
- int w = jfxx.thumbnail[0] & 0xff;
- int h = jfxx.thumbnail[1] & 0xff;
-
- int[] rgbs = new int[256];
- for (int i = 0; i < rgbs.length; i++) {
- int rgb = (jfxx.thumbnail[3 * i] & 0xff) << 16
- | (jfxx.thumbnail[3 * i] & 0xff) << 8
- | (jfxx.thumbnail[3 * i] & 0xff);
-
- rgbs[i] = rgb;
- }
-
- IndexColorModel icm = new InverseColorMapIndexColorModel(8, rgbs.length, rgbs, 0, false, -1, DataBuffer.TYPE_BYTE);
- DataBufferByte buffer = new DataBufferByte(jfxx.thumbnail, jfxx.thumbnail.length - 770, 770);
- WritableRaster raster = Raster.createPackedRaster(buffer, w, h, 8, null);
-
- thumbnails.add(new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), null));
- break;
- case JFXX.RGB:
- // 1 byte: xThumb
- // 1 byte: yThumb
- // 3 * x * y bytes: 24 bit RGB pixels
- w = jfxx.thumbnail[0] & 0xff;
- h = jfxx.thumbnail[1] & 0xff;
-
- thumbnails.add(readRawThumbnail(jfxx.thumbnail, jfxx.thumbnail.length - 2, 2, w, h));
+ case JFXXSegment.JPEG:
+ case JFXXSegment.INDEXED:
+ case JFXXSegment.RGB:
+ thumbnails.add(new JFXXThumbnailReader(this, imageIndex, thumbnails.size(), jfxx));
break;
default:
processWarningOccurred("Unknown JFXX extension code: " + jfxx.extensionCode);
}
}
- // TODO: Ideally we want to decode image data in getThumbnail, less ideally here, but at least not in getEXIFMetadata()
- CompoundDirectory exifMetadata = getEXIFMetadata();
-// System.err.println("exifMetadata: " + exifMetadata);
-// if (exifMetadata != null && exifMetadata.directoryCount() >= 2) {
-// Directory ifd1 = exifMetadata.getDirectory(1);
-// if (ifd1.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT) != null) {
-// }
-// }
+ List exifSegments = getAppSegments(JPEG.APP1, "Exif");
+
+ if (!exifSegments.isEmpty()) {
+ JPEGSegment exif = exifSegments.get(0);
+ InputStream data = exif.data();
+ //noinspection ResultOfMethodCallIgnored
+ data.read(); // Pad
+
+ ImageInputStream stream = ImageIO.createImageInputStream(data);
+ CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
+
+ if (exifMetadata.directoryCount() == 2) {
+ Directory ifd1 = exifMetadata.getDirectory(1);
+
+ Entry compression = ifd1.getEntryById(TIFF.TAG_COMPRESSION);
+
+ // 1 = no compression, 6 = JPEG compression (default)
+ if (compression == null || compression.getValue().equals(1) || compression.getValue().equals(6)) {
+ thumbnails.add(new EXIFThumbnailReader(this, 0, thumbnails.size(), ifd1, stream));
+ }
+ else {
+ processWarningOccurred("EXIF IFD with unknown compression: " + compression.getValue());
+ }
+ }
+ }
}
}
- // TODO: Candidate for util method
- private BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) {
- DataBufferByte buffer = new DataBufferByte(thumbnail, size, offset);
- WritableRaster raster = Raster.createInterleavedRaster(buffer, w, h, w * 3, 3, new int[] {0, 1, 2}, null);
- ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
-
- return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
- }
-
@Override
public int getNumThumbnails(final int imageIndex) throws IOException {
readThumbnailMetadata(imageIndex);
@@ -965,14 +824,27 @@ public class JPEGImageReader extends ImageReaderBase {
public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex) throws IOException {
checkThumbnailBounds(imageIndex, thumbnailIndex);
- processThumbnailStarted(imageIndex, thumbnailIndex);
- // For now: Clone. TODO: Do the actual decoding/reading here.
- BufferedImage cached = thumbnails.get(thumbnailIndex);
- BufferedImage thumbnail = new BufferedImage(cached.getColorModel(), cached.copyData(null), cached.getColorModel().isAlphaPremultiplied(), null);
- processThumbnailProgress(100f);
- processThumbnailComplete();
+ return thumbnails.get(thumbnailIndex).read();
+ }
- return thumbnail;
+ @Override
+ protected void processWarningOccurred(String warning) {
+ super.processWarningOccurred(warning);
+ }
+
+ @Override
+ protected void processThumbnailStarted(int imageIndex, int thumbnailIndex) {
+ super.processThumbnailStarted(imageIndex, thumbnailIndex);
+ }
+
+ @Override
+ protected void processThumbnailProgress(float percentageDone) {
+ super.processThumbnailProgress(percentageDone);
+ }
+
+ @Override
+ protected void processThumbnailComplete() {
+ super.processThumbnailComplete();
}
private static void invertCMYK(final Raster raster) {
@@ -1037,7 +909,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
- private static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
+ static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
int y = yCbCr[offset ] & 0xff;
int cr = yCbCr[offset + 2] & 0xff;
int cb = yCbCr[offset + 1] & 0xff;
@@ -1192,7 +1064,7 @@ public class JPEGImageReader extends ImageReaderBase {
private final int lines; // height
private final int samplesPerLine; // width
private final int componentsInFrame;
- private final SOFComponent[] components;
+ final SOFComponent[] components;
public SOF(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) {
this.marker = marker;
@@ -1233,10 +1105,10 @@ public class JPEGImageReader extends ImageReaderBase {
}
private static class SOFComponent {
- private final int id;
- private final int hSub;
- private final int vSub;
- private final int qtSel;
+ final int id;
+ final int hSub;
+ final int vSub;
+ final int qtSel;
public SOFComponent(int id, int hSub, int vSub, int qtSel) {
this.id = id;
@@ -1253,129 +1125,6 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
- private static class JFIF {
- private final int majorVersion;
- private final int minorVersion;
- private final int units;
- private final int xDensity;
- private final int yDensity;
- private final int xThumbnail;
- private final int yThumbnail;
- private final byte[] thumbnail;
-
- public JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
- this.majorVersion = majorVersion;
- this.minorVersion = minorVersion;
- this.units = units;
- this.xDensity = xDensity;
- this.yDensity = yDensity;
- this.xThumbnail = xThumbnail;
- this.yThumbnail = yThumbnail;
- this.thumbnail = thumbnail;
- }
-
- @Override
- public String toString() {
- return String.format("JFIF v%d.%02d %dx%d %s (%s)", majorVersion, minorVersion, xDensity, yDensity, unitsAsString(), thumbnailToString());
- }
-
- private String unitsAsString() {
- switch (units) {
- case 0:
- return "(aspect only)";
- case 1:
- return "dpi";
- case 2:
- return "dpcm";
- default:
- return "(unknown unit)";
- }
- }
-
- private String thumbnailToString() {
- if (xThumbnail == 0 || yThumbnail == 0) {
- return "no thumbnail";
- }
-
- return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
- }
- }
-
- private static class JFXX {
- public static final int JPEG = 0x10;
- public static final int INDEXED = 0x11;
- public static final int RGB = 0x13;
-
- private final int extensionCode;
- private final byte[] thumbnail;
-
- public JFXX(int extensionCode, byte[] thumbnail) {
- this.extensionCode = extensionCode;
- this.thumbnail = thumbnail;
- }
-
- @Override
- public String toString() {
- return String.format("JFXX extension (%s thumb size: %d)", extensionAsString(), thumbnail.length);
- }
-
- private String extensionAsString() {
- switch (extensionCode) {
- case JPEG:
- return "JPEG";
- case INDEXED:
- return "Indexed";
- case RGB:
- return "RGB";
- default:
- return String.valueOf(extensionCode);
- }
- }
- }
-
-
- private static class AdobeDCT {
- public static final int Unknown = 0;
- public static final int YCC = 1;
- public static final int YCCK = 2;
-
- private final int version;
- private final int flags0;
- private final int flags1;
- private final int transform;
-
- public AdobeDCT(int version, int flags0, int flags1, int transform) {
- this.version = version; // 100 or 101
- this.flags0 = flags0;
- this.flags1 = flags1;
- this.transform = transform;
- }
-
- public int getVersion() {
- return version;
- }
-
- public int getFlags0() {
- return flags0;
- }
-
- public int getFlags1() {
- return flags1;
- }
-
- public int getTransform() {
- return transform;
- }
-
- @Override
- public String toString() {
- return String.format(
- "AdobeDCT[ver: %d.%02d, flags: %s %s, transform: %d]",
- getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform()
- );
- }
- }
-
protected static void showIt(final BufferedImage pImage, final String pTitle) {
ImageReaderBase.showIt(pImage, pTitle);
}
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java
new file mode 100644
index 00000000..215f9e2f
--- /dev/null
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012, 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.plugins.jpeg;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * ThumbnailReader
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ThumbnailReader.java,v 1.0 18.04.12 12:22 haraldk Exp$
+ */
+abstract class ThumbnailReader {
+
+ private final JPEGImageReader parent;
+ protected final int imageIndex;
+ protected final int thumbnailIndex;
+
+ protected ThumbnailReader(final JPEGImageReader parent, final int imageIndex, final int thumbnailIndex) {
+ this.parent = parent;
+ this.imageIndex = imageIndex;
+ this.thumbnailIndex = thumbnailIndex;
+ }
+ protected final void processThumbnailStarted() {
+ parent.processThumbnailStarted(imageIndex, thumbnailIndex);
+ }
+
+ protected final void processThumbnailProgress(float percentageDone) {
+ parent.processThumbnailProgress(percentageDone);
+ }
+
+ protected final void processThumbnailComplete() {
+ parent.processThumbnailComplete();
+ }
+
+ protected final void processWarningOccurred(String warning) {
+ parent.processWarningOccurred(warning);
+ }
+
+ static protected BufferedImage readJPEGThumbnail(InputStream stream) throws IOException {
+ return ImageIO.read(stream);
+ }
+
+ static protected BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) {
+ DataBufferByte buffer = new DataBufferByte(thumbnail, size, offset);
+ WritableRaster raster = Raster.createInterleavedRaster(buffer, w, h, w * 3, 3, new int[] {0, 1, 2}, null);
+ ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
+
+ return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
+ }
+
+ public abstract BufferedImage read() throws IOException;
+
+ public abstract int getWidth() throws IOException;
+
+ public abstract int getHeight() throws IOException;
+}