mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 11:05:29 -04:00
TMI-16: Refactorings. Moved segment classes to upper level. Extracted thumbnail reading to separate classes.
This commit is contained in:
parent
ae87726974
commit
241c1882f4
@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,6 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
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.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
@ -71,7 +69,7 @@ import java.util.List;
|
|||||||
public class JPEGImageReader extends ImageReaderBase {
|
public class JPEGImageReader extends ImageReaderBase {
|
||||||
// TODO: Fix the (stream) metadata inconsistency issues.
|
// 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.. :-/
|
// - 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"));
|
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 */
|
/** Cached JPEG app segments */
|
||||||
private List<JPEGSegment> segments;
|
private List<JPEGSegment> segments;
|
||||||
|
|
||||||
private List<BufferedImage> thumbnails;
|
private List<ThumbnailReader> thumbnails;
|
||||||
|
|
||||||
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||||
super(provider);
|
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 {
|
private List<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
|
||||||
initHeader();
|
initHeader();
|
||||||
|
|
||||||
@ -704,7 +585,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AdobeDCT getAdobeDCT() throws IOException {
|
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");
|
List<JPEGSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
||||||
|
|
||||||
if (!adobe.isEmpty()) {
|
if (!adobe.isEmpty()) {
|
||||||
@ -722,7 +603,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JFIF getJFIF() throws IOException{
|
private JFIFSegment getJFIF() throws IOException{
|
||||||
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
||||||
|
|
||||||
if (!jfif.isEmpty()) {
|
if (!jfif.isEmpty()) {
|
||||||
@ -731,7 +612,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
||||||
return new JFIF(
|
return new JFIFSegment(
|
||||||
stream.readUnsignedByte(),
|
stream.readUnsignedByte(),
|
||||||
stream.readUnsignedByte(),
|
stream.readUnsignedByte(),
|
||||||
stream.readUnsignedByte(),
|
stream.readUnsignedByte(),
|
||||||
@ -746,7 +627,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JFXX getJFXX() throws IOException {
|
private JFXXSegment getJFXX() throws IOException {
|
||||||
List<JPEGSegment> jfxx = getAppSegments(JPEG.APP0, "JFXX");
|
List<JPEGSegment> jfxx = getAppSegments(JPEG.APP0, "JFXX");
|
||||||
|
|
||||||
if (!jfxx.isEmpty()) {
|
if (!jfxx.isEmpty()) {
|
||||||
@ -754,7 +635,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
DataInputStream stream = new DataInputStream(segment.data());
|
DataInputStream stream = new DataInputStream(segment.data());
|
||||||
|
|
||||||
return new JFXX(
|
return new JFXXSegment(
|
||||||
stream.readUnsignedByte(),
|
stream.readUnsignedByte(),
|
||||||
readFully(stream, segment.length() - 1)
|
readFully(stream, segment.length() - 1)
|
||||||
);
|
);
|
||||||
@ -763,8 +644,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Util method?
|
||||||
private byte[] readFully(DataInput stream, int len) throws IOException {
|
static byte[] readFully(DataInput stream, int len) throws IOException {
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -858,83 +739,61 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readerSupportsThumbnails() {
|
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 {
|
private void readThumbnailMetadata(int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
if (thumbnails == null) {
|
if (thumbnails == null) {
|
||||||
thumbnails = new ArrayList<BufferedImage>();
|
thumbnails = new ArrayList<ThumbnailReader>();
|
||||||
|
|
||||||
JFIF jfif = getJFIF();
|
JFIFSegment jfif = getJFIF();
|
||||||
if (jfif != null && jfif.thumbnail != null) {
|
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) {
|
if (jfxx != null && jfxx.thumbnail != null) {
|
||||||
switch (jfxx.extensionCode) {
|
switch (jfxx.extensionCode) {
|
||||||
case JFXX.JPEG:
|
case JFXXSegment.JPEG:
|
||||||
thumbnails.add(ImageIO.read(new ByteArrayInputStream(jfxx.thumbnail)));
|
case JFXXSegment.INDEXED:
|
||||||
break;
|
case JFXXSegment.RGB:
|
||||||
case JFXX.INDEXED:
|
thumbnails.add(new JFXXThumbnailReader(this, imageIndex, thumbnails.size(), jfxx));
|
||||||
// 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));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
processWarningOccurred("Unknown JFXX extension code: " + jfxx.extensionCode);
|
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()
|
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||||
CompoundDirectory exifMetadata = getEXIFMetadata();
|
|
||||||
// System.err.println("exifMetadata: " + exifMetadata);
|
if (!exifSegments.isEmpty()) {
|
||||||
// if (exifMetadata != null && exifMetadata.directoryCount() >= 2) {
|
JPEGSegment exif = exifSegments.get(0);
|
||||||
// Directory ifd1 = exifMetadata.getDirectory(1);
|
InputStream data = exif.data();
|
||||||
// if (ifd1.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT) != null) {
|
//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
|
@Override
|
||||||
public int getNumThumbnails(final int imageIndex) throws IOException {
|
public int getNumThumbnails(final int imageIndex) throws IOException {
|
||||||
readThumbnailMetadata(imageIndex);
|
readThumbnailMetadata(imageIndex);
|
||||||
@ -965,14 +824,27 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex) throws IOException {
|
public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex) throws IOException {
|
||||||
checkThumbnailBounds(imageIndex, thumbnailIndex);
|
checkThumbnailBounds(imageIndex, thumbnailIndex);
|
||||||
|
|
||||||
processThumbnailStarted(imageIndex, thumbnailIndex);
|
return thumbnails.get(thumbnailIndex).read();
|
||||||
// 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 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) {
|
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 y = yCbCr[offset ] & 0xff;
|
||||||
int cr = yCbCr[offset + 2] & 0xff;
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
int cb = yCbCr[offset + 1] & 0xff;
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
@ -1192,7 +1064,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
private final int lines; // height
|
private final int lines; // height
|
||||||
private final int samplesPerLine; // width
|
private final int samplesPerLine; // width
|
||||||
private final int componentsInFrame;
|
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) {
|
public SOF(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) {
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
@ -1233,10 +1105,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class SOFComponent {
|
private static class SOFComponent {
|
||||||
private final int id;
|
final int id;
|
||||||
private final int hSub;
|
final int hSub;
|
||||||
private final int vSub;
|
final int vSub;
|
||||||
private final int qtSel;
|
final int qtSel;
|
||||||
|
|
||||||
public SOFComponent(int id, int hSub, int vSub, int qtSel) {
|
public SOFComponent(int id, int hSub, int vSub, int qtSel) {
|
||||||
this.id = id;
|
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) {
|
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
||||||
ImageReaderBase.showIt(pImage, pTitle);
|
ImageReaderBase.showIt(pImage, pTitle);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user