TMI-16: Refactorings. Moved segment classes to upper level. Extracted thumbnail reading to separate classes.

This commit is contained in:
Harald Kuhr 2012-04-24 20:11:04 +02:00
parent ae87726974
commit 241c1882f4
8 changed files with 843 additions and 320 deletions

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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()
);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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<BufferedImage> 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<BufferedImage>(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);
}
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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;
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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<BufferedImage> 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<BufferedImage>(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);
}
}

View File

@ -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<JPEGSegment> segments;
private List<BufferedImage> thumbnails;
private List<ThumbnailReader> 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<JPEGSegment> 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<JPEGSegment> 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<JPEGSegment> 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<JPEGSegment> 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<JPEGSegment> 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,81 +739,59 @@ 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<BufferedImage>();
thumbnails = new ArrayList<ThumbnailReader>();
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<JPEGSegment> 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
@ -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);
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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;
}