mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -04:00
TMI-21: Implemented getRawImageType and getImageTypes for CMYK/YCCK.
TMI-16: Refactorings, cleaner color space determination + tests for thumbnail readers.
This commit is contained in:
parent
aaef2e4fad
commit
a4dfb7a009
@ -29,12 +29,12 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AdobeDCT
|
* AdobeDCT
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: AdobeDCT.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
* @version $Id: AdobeDCT.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
class AdobeDCT {
|
class AdobeDCT {
|
||||||
public static final int Unknown = 0;
|
public static final int Unknown = 0;
|
||||||
public static final int YCC = 1;
|
public static final int YCC = 1;
|
||||||
|
@ -57,8 +57,8 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
|||||||
|
|
||||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||||
|
|
||||||
public EXIFThumbnailReader(JPEGImageReader parent, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
|
public EXIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
|
||||||
super(parent, imageIndex, thumbnailIndex);
|
super(progressListener, imageIndex, thumbnailIndex);
|
||||||
this.ifd = ifd;
|
this.ifd = ifd;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
|||||||
|
|
||||||
// For certain EXIF files (encoded with TIFF.TAG_YCBCR_POSITIONING = 2?), we need
|
// 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).
|
// EXIF information to read the thumbnail correctly (otherwise the colors are messed up).
|
||||||
|
// Probably related to: http://bugs.sun.com/view_bug.do?bug_id=4881314
|
||||||
|
|
||||||
// HACK: Splice empty EXIF information into the thumbnail stream
|
// HACK: Splice empty EXIF information into the thumbnail stream
|
||||||
byte[] fakeEmptyExif = {
|
byte[] fakeEmptyExif = {
|
||||||
|
@ -28,13 +28,17 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JFIFSegment
|
* JFIFSegment
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
|
* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
class JFIFSegment {
|
class JFIFSegment {
|
||||||
final int majorVersion;
|
final int majorVersion;
|
||||||
final int minorVersion;
|
final int minorVersion;
|
||||||
@ -45,7 +49,7 @@ class JFIFSegment {
|
|||||||
final int yThumbnail;
|
final int yThumbnail;
|
||||||
final byte[] thumbnail;
|
final byte[] thumbnail;
|
||||||
|
|
||||||
public JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
|
private JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
|
||||||
this.majorVersion = majorVersion;
|
this.majorVersion = majorVersion;
|
||||||
this.minorVersion = minorVersion;
|
this.minorVersion = minorVersion;
|
||||||
this.units = units;
|
this.units = units;
|
||||||
@ -81,4 +85,21 @@ class JFIFSegment {
|
|||||||
|
|
||||||
return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
|
return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JFIFSegment read(final InputStream data) throws IOException {
|
||||||
|
DataInputStream stream = new DataInputStream(data);
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
return new JFIFSegment(
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
stream.readUnsignedShort(),
|
||||||
|
stream.readUnsignedShort(),
|
||||||
|
x = stream.readUnsignedByte(),
|
||||||
|
y = stream.readUnsignedByte(),
|
||||||
|
JPEGImageReader.readFully(stream, x * y * 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,8 @@ import java.io.IOException;
|
|||||||
final class JFIFThumbnailReader extends ThumbnailReader {
|
final class JFIFThumbnailReader extends ThumbnailReader {
|
||||||
private final JFIFSegment segment;
|
private final JFIFSegment segment;
|
||||||
|
|
||||||
public JFIFThumbnailReader(JPEGImageReader parent, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
|
public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
|
||||||
super(parent, imageIndex, thumbnailIndex);
|
super(progressListener, imageIndex, thumbnailIndex);
|
||||||
this.segment = segment;
|
this.segment = segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,6 @@ final class JFIFThumbnailReader extends ThumbnailReader {
|
|||||||
public BufferedImage read() {
|
public BufferedImage read() {
|
||||||
processThumbnailStarted();
|
processThumbnailStarted();
|
||||||
BufferedImage thumbnail = readRawThumbnail(segment.thumbnail, segment.thumbnail.length, 0, segment.xThumbnail, segment.yThumbnail);
|
BufferedImage thumbnail = readRawThumbnail(segment.thumbnail, segment.thumbnail.length, 0, segment.xThumbnail, segment.yThumbnail);
|
||||||
|
|
||||||
processThumbnailProgress(100f);
|
processThumbnailProgress(100f);
|
||||||
processThumbnailComplete();
|
processThumbnailComplete();
|
||||||
|
|
||||||
|
@ -28,13 +28,17 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JFXXSegment
|
* JFXXSegment
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
|
* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
class JFXXSegment {
|
class JFXXSegment {
|
||||||
public static final int JPEG = 0x10;
|
public static final int JPEG = 0x10;
|
||||||
public static final int INDEXED = 0x11;
|
public static final int INDEXED = 0x11;
|
||||||
@ -43,7 +47,7 @@ class JFXXSegment {
|
|||||||
final int extensionCode;
|
final int extensionCode;
|
||||||
final byte[] thumbnail;
|
final byte[] thumbnail;
|
||||||
|
|
||||||
public JFXXSegment(int extensionCode, byte[] thumbnail) {
|
private JFXXSegment(int extensionCode, byte[] thumbnail) {
|
||||||
this.extensionCode = extensionCode;
|
this.extensionCode = extensionCode;
|
||||||
this.thumbnail = thumbnail;
|
this.thumbnail = thumbnail;
|
||||||
}
|
}
|
||||||
@ -65,4 +69,13 @@ class JFXXSegment {
|
|||||||
return String.valueOf(extensionCode);
|
return String.valueOf(extensionCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JFXXSegment read(InputStream data, int length) throws IOException {
|
||||||
|
DataInputStream stream = new DataInputStream(data);
|
||||||
|
|
||||||
|
return new JFXXSegment(
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
JPEGImageReader.readFully(stream, length - 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
|||||||
|
|
||||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||||
|
|
||||||
protected JFXXThumbnailReader(final JPEGImageReader parent, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
|
protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
|
||||||
super(parent, imageIndex, thumbnailIndex);
|
super(progressListener, imageIndex, thumbnailIndex);
|
||||||
this.segment = segment;
|
this.segment = segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPEGColorSpace
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: JPEGColorSpace.java,v 1.0 26.04.12 15:05 haraldk Exp$
|
||||||
|
*/
|
||||||
|
enum JPEGColorSpace {
|
||||||
|
Gray,
|
||||||
|
GrayA,
|
||||||
|
RGB,
|
||||||
|
RGBA,
|
||||||
|
YCbCr,
|
||||||
|
YCbCrA,
|
||||||
|
PhotoYCC,
|
||||||
|
PhotoYCCA,
|
||||||
|
CMYK,
|
||||||
|
YCCK
|
||||||
|
}
|
@ -58,8 +58,8 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
||||||
* with support for CMYK JPEGs and other non-standard color spaces,
|
* with support for CMYK/YCCK JPEGs, non-standard color spaces,broken ICC profiles
|
||||||
* like embedded ICC color spaces with rendering intent other than 'perceptual'.
|
* and more.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
||||||
@ -69,7 +69,6 @@ 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(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"));
|
||||||
|
|
||||||
@ -171,33 +170,78 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
// TODO: Read header, and make sure we return valid types for the images we can now read
|
|
||||||
|
|
||||||
Iterator<ImageTypeSpecifier> types = delegate.getImageTypes(imageIndex);
|
Iterator<ImageTypeSpecifier> types = delegate.getImageTypes(imageIndex);
|
||||||
|
JPEGColorSpace csType = getSourceCSType(getAdobeDCT(), getSOF());
|
||||||
|
|
||||||
ICC_Profile profile = getEmbeddedICCProfile();
|
if (types == null || !types.hasNext() || csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||||
AdobeDCT adobeDCT = getAdobeDCT();
|
ArrayList<ImageTypeSpecifier> typeList = new ArrayList<ImageTypeSpecifier>();
|
||||||
|
// Add the standard types, we can always convert to these
|
||||||
|
typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||||
|
typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||||
|
typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||||
|
|
||||||
// TODO: FixMe
|
// We also read and return CMYK if the source image is CMYK/YCCK + original color profile if present
|
||||||
if (types == null || !types.hasNext() || adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK || profile != null && profile.getColorSpaceType() == ColorSpace.TYPE_CMYK) {
|
ICC_Profile profile = getEmbeddedICCProfile();
|
||||||
return Arrays.asList(
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)
|
|
||||||
|
|
||||||
// TODO: We can/should also read and return it as CMYK if the source image is CMYK..
|
if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||||
// + original color profile should be an option
|
if (profile != null) {
|
||||||
|
typeList.add(ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
).iterator();
|
typeList.add(ImageTypeSpecifier.createInterleaved(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
|
}
|
||||||
|
else if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.RGB) {
|
||||||
|
if (profile != null) {
|
||||||
|
typeList.add(ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {0, 1, 2}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (csType == JPEGColorSpace.YCbCrA || csType == JPEGColorSpace.RGBA) {
|
||||||
|
// Prepend ARGB types
|
||||||
|
typeList.addAll(0, Arrays.asList(
|
||||||
|
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB),
|
||||||
|
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
|
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE),
|
||||||
|
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (profile != null) {
|
||||||
|
typeList.add(ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {0, 1, 2, 3}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeList.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
public
|
||||||
// TODO: Implement something better, so we don't return null for CMYK images + fixes the "Inconsistent metadata" issue
|
ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
return delegate.getRawImageType(imageIndex);
|
// If delegate can determine the spec, we'll just go with that
|
||||||
|
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||||
|
|
||||||
|
if (rawType != null) {
|
||||||
|
return rawType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, consult the image metadata
|
||||||
|
JPEGColorSpace csType = getSourceCSType(getAdobeDCT(), getSOF());
|
||||||
|
|
||||||
|
switch (csType) {
|
||||||
|
case CMYK:
|
||||||
|
// Create based on embedded profile if exists, or create from "Generic CMYK"
|
||||||
|
ICC_Profile profile = getEmbeddedICCProfile();
|
||||||
|
|
||||||
|
if (profile != null) {
|
||||||
|
return ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageTypeSpecifier.createInterleaved(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||||
|
default:
|
||||||
|
// For other types, we probably can't give a proper type, return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -226,13 +270,16 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
ICC_Profile profile = getEmbeddedICCProfile();
|
ICC_Profile profile = getEmbeddedICCProfile();
|
||||||
AdobeDCT adobeDCT = getAdobeDCT();
|
AdobeDCT adobeDCT = getAdobeDCT();
|
||||||
|
|
||||||
|
// TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more...
|
||||||
|
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||||
|
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||||
if (delegate.canReadRaster() && (
|
if (delegate.canReadRaster() && (
|
||||||
unsupported ||
|
unsupported ||
|
||||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK ||
|
adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK ||
|
||||||
profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Reading using raster and extra conversion");
|
System.out.println("Reading using raster and extra conversion");
|
||||||
System.out.println("ICC color profile = " + profile);
|
System.out.println("ICC color profile: " + profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, ensureDisplayProfile(profile));
|
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, ensureDisplayProfile(profile));
|
||||||
@ -249,144 +296,63 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int origWidth = getWidth(imageIndex);
|
int origWidth = getWidth(imageIndex);
|
||||||
int origHeight = getHeight(imageIndex);
|
int origHeight = getHeight(imageIndex);
|
||||||
|
|
||||||
ColorSpace srcCs = null;
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
From http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
|
|
||||||
|
|
||||||
"When reading, the contents of the stream are interpreted by the usual JPEG conventions, as follows:
|
|
||||||
|
|
||||||
• If a JFIF APP0 marker segment is present, the colorspace is known to be either grayscale or YCbCr. If an APP2
|
|
||||||
marker segment containing an embedded ICC profile is also present, then the YCbCr is converted to RGB according
|
|
||||||
to the formulas given in the JFIF spec, and the ICC profile is assumed to refer to the resulting RGB space.
|
|
||||||
|
|
||||||
• If an Adobe APP14 marker segment is present, the colorspace is determined by consulting the transform flag.
|
|
||||||
The transform flag takes one of three values:
|
|
||||||
o 2 - The image is encoded as YCCK (implicitly converted from CMYK on encoding).
|
|
||||||
o 1 - The image is encoded as YCbCr (implicitly converted from RGB on encoding).
|
|
||||||
o 0 - Unknown. 3-channel images are assumed to be RGB, 4-channel images are assumed to be CMYK.
|
|
||||||
|
|
||||||
• If neither marker segment is present, the following procedure is followed: Single-channel images are assumed
|
|
||||||
to be grayscale, and 2-channel images are assumed to be grayscale with an alpha channel. For 3- and 4-channel
|
|
||||||
images, the component ids are consulted. If these values are 1-3 for a 3-channel image, then the image is
|
|
||||||
assumed to be YCbCr. If these values are 1-4 for a 4-channel image, then the image is assumed to be YCbCrA. If
|
|
||||||
these values are > 4, they are checked against the ASCII codes for 'R', 'G', 'B', 'A', 'C', 'c'.
|
|
||||||
These can encode the following colorspaces:
|
|
||||||
|
|
||||||
RGB
|
|
||||||
RGBA
|
|
||||||
YCC (as 'Y','C','c'), assumed to be PhotoYCC
|
|
||||||
YCCA (as 'Y','C','c','A'), assumed to be PhotoYCCA
|
|
||||||
|
|
||||||
Otherwise, 3-channel subsampled images are assumed to be YCbCr, 3-channel non-subsampled images are assumed to
|
|
||||||
be RGB, 4-channel subsampled images are assumed to be YCCK, and 4-channel, non-subsampled images are assumed to
|
|
||||||
be CMYK.
|
|
||||||
|
|
||||||
• All other images are declared uninterpretable and an exception is thrown if an attempt is made to read one as
|
|
||||||
a BufferedImage. Such an image may be read only as a Raster. If an image is interpretable but there is no Java
|
|
||||||
ColorSpace available corresponding to the encoded colorspace (e.g. YCbCr), then ImageReader.getRawImageType
|
|
||||||
will return null."
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
// TODO: Fix this algorithm to behave like above, except the presence of JFIF APP0 might mean YCbCr, gray *or CMYK*.
|
|
||||||
// AdobeApp14 with transform either 1 or 2 can be trusted to be YCC/YCCK respectively, transform 0 means 1 component gray, 3 comp rgb, 4 comp cmyk
|
|
||||||
|
|
||||||
SOF startOfFrame = getSOF();
|
|
||||||
AdobeDCT adobeDCT = getAdobeDCT();
|
AdobeDCT adobeDCT = getAdobeDCT();
|
||||||
|
SOF startOfFrame = getSOF();
|
||||||
|
JPEGColorSpace csType = getSourceCSType(adobeDCT, startOfFrame);
|
||||||
|
|
||||||
Iterator<ImageTypeSpecifier> imageTypes = delegate.getImageTypes(imageIndex);
|
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||||
int transform = adobeDCT != null ? adobeDCT.getTransform() : AdobeDCT.Unknown;
|
|
||||||
|
|
||||||
// TODO: The !types.hasNext test is broken for JDK7, as it does return types...
|
|
||||||
// CMYK Support, assuming the delegate reader can't decode, and any 4 component image is CMYK
|
|
||||||
if ((!imageTypes.hasNext() || transform == AdobeDCT.YCCK || profile != null && profile.getColorSpaceType() == ColorSpace.TYPE_CMYK) && startOfFrame.componentsInFrame == 4) {
|
|
||||||
// NOTE: Reading the metadata here chokes on some images. Instead, parse the Adobe App14 segment and read transform directly
|
|
||||||
|
|
||||||
// TODO: If cmyk and no ICC profile, just use FastCMYKToRGB, without attempting loading Generic CMYK profile first?
|
|
||||||
// TODO: Don't get generic CMYK if we already have a CMYK profile...
|
|
||||||
srcCs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
|
||||||
imageTypes = Arrays.asList(
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)
|
|
||||||
// TODO: Only alpha if source has alpha... (ColorConvertOp chokes otherwise)
|
|
||||||
// ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB),
|
|
||||||
// ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE),
|
|
||||||
// ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR),
|
|
||||||
// ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)
|
|
||||||
// TODO: Move to getImageTypes + add native color space if profile != null
|
|
||||||
).iterator();
|
|
||||||
}
|
|
||||||
else if (!imageTypes.hasNext() && profile != null) {
|
|
||||||
// TODO: Merge with above?
|
|
||||||
srcCs = null;
|
|
||||||
imageTypes = Arrays.asList(
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB),
|
|
||||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)
|
|
||||||
).iterator();
|
|
||||||
}
|
|
||||||
// ...else blow up as there's no possible types to decode into...
|
|
||||||
|
|
||||||
BufferedImage image = getDestination(param, imageTypes, origWidth, origHeight);
|
BufferedImage image = getDestination(param, imageTypes, origWidth, origHeight);
|
||||||
|
|
||||||
// System.err.println("JPEGImageReader.readImageAsRasterAndReplaceColorProfile: " + image);
|
|
||||||
|
|
||||||
WritableRaster destination = image.getRaster();
|
WritableRaster destination = image.getRaster();
|
||||||
|
|
||||||
// TODO: checkReadParamBandSettings(param, );
|
// TODO: checkReadParamBandSettings(param, );
|
||||||
|
|
||||||
RasterOp convert = null;
|
RasterOp convert = null;
|
||||||
ICC_ColorSpace replacement = profile != null ? ColorSpaces.createColorSpace(profile) : null;
|
ICC_ColorSpace intendedCS = profile != null ? ColorSpaces.createColorSpace(profile) : null;
|
||||||
|
|
||||||
if (profile != null && profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && image.getColorModel().getColorSpace().getType() == ColorSpace.CS_GRAY) {
|
if (profile != null && (csType == JPEGColorSpace.Gray || csType == JPEGColorSpace.GrayA)) {
|
||||||
// com.sun. reader does not do ColorConvertOp for CS_GRAY, even if embedded ICC profile,
|
// com.sun. reader does not do ColorConvertOp for CS_GRAY, even if embedded ICC profile,
|
||||||
// probably because IJG native part does it already...? If applied, color looks wrong (too dark)...
|
// probably because IJG native part does it already...? If applied, color looks wrong (too dark)...
|
||||||
convert = new ColorConvertOp(srcCs, image.getColorModel().getColorSpace(), null);
|
// convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
else if (replacement != null) {
|
else if (intendedCS != null) {
|
||||||
// Handle inconsistencies
|
// Handle inconsistencies
|
||||||
if (startOfFrame.componentsInFrame != replacement.getNumComponents()) {
|
if (startOfFrame.componentsInFrame != intendedCS.getNumComponents()) {
|
||||||
if (startOfFrame.componentsInFrame < 4 && transform == AdobeDCT.YCCK) {
|
if (startOfFrame.componentsInFrame < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
||||||
processWarningOccurred(String.format(
|
processWarningOccurred(String.format(
|
||||||
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
||||||
"Ignoring Adobe App14 marker, assuming YCC/RGB data.",
|
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
||||||
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
||||||
));
|
));
|
||||||
transform = AdobeDCT.YCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ICC profile number of components and startOfFrame does not match, ignore ICC profile
|
csType = JPEGColorSpace.YCbCr;
|
||||||
processWarningOccurred(String.format(
|
}
|
||||||
"Embedded ICC color profile is incompatible with image data. " +
|
else {
|
||||||
"Profile indicates %d components, but SOF%d has %d color components. " +
|
// If ICC profile number of components and startOfFrame does not match, ignore ICC profile
|
||||||
"Ignoring ICC profile, assuming YCC/RGB data.",
|
processWarningOccurred(String.format(
|
||||||
replacement.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
"Embedded ICC color profile is incompatible with image data. " +
|
||||||
));
|
"Profile indicates %d components, but SOF%d has %d color components. " +
|
||||||
srcCs = null;
|
"Ignoring ICC profile, assuming source color space %s.",
|
||||||
|
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame, csType
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
||||||
else if (replacement != image.getColorModel().getColorSpace()) {
|
else if (intendedCS != image.getColorModel().getColorSpace()) {
|
||||||
// TODO: Use profiles instead of CS, if ICC profiles? Avoid creating expensive CS.
|
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||||
convert = new ColorConvertOp(replacement, image.getColorModel().getColorSpace(), null);
|
|
||||||
}
|
}
|
||||||
// Else, pass through with no conversion
|
// Else, pass through with no conversion
|
||||||
}
|
}
|
||||||
else if (srcCs != null) {
|
else if (csType == JPEGColorSpace.YCCK || csType == JPEGColorSpace.CMYK) {
|
||||||
if (!(srcCs instanceof ICC_ColorSpace) && image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_RGB && srcCs.getType() == ColorSpace.TYPE_CMYK) {
|
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||||
convert = new FastCMYKToRGB();
|
|
||||||
|
if (cmykCS instanceof ICC_ColorSpace) {
|
||||||
|
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO: Use profiles instead of CS, if ICC profiles? Avoid creating expensive CS.
|
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
||||||
convert = new ColorConvertOp(srcCs, image.getColorModel().getColorSpace(), null);
|
convert = new FastCMYKToRGB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else if (!image.getColorModel().getColorSpace().isCS_sRGB()) {
|
|
||||||
// TODO: Need to handle case where src and dest differ still
|
|
||||||
// convert = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), image.getColorModel().getColorSpace(), null);
|
|
||||||
// }
|
|
||||||
else if (profile != null) {
|
else if (profile != null) {
|
||||||
processWarningOccurred("Embedded ICC color profile is incompatible with Java 2D, color profile will be ignored.");
|
processWarningOccurred("Embedded ICC color profile is incompatible with Java 2D, color profile will be ignored.");
|
||||||
}
|
}
|
||||||
@ -409,10 +375,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
processImageStarted(imageIndex);
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
// Unfortunately looping is slower than reading all at once, but
|
// Unfortunately looping is slower than reading all at once, but
|
||||||
// that requires 2 x + memory, so a few steps is an ok compromise I guess
|
// that requires 2 x memory or more, so a few steps is an ok compromise I guess
|
||||||
try {
|
try {
|
||||||
int srcCsType = srcCs != null ? srcCs.getType() : image.getColorModel().getColorSpace().getType();
|
|
||||||
|
|
||||||
final int step = Math.max(1024, srcRegion.height / 10); // * param.getSourceYSubsampling(); // TODO: Using a multiple of 8 is probably a good idea for JPEG
|
final int step = Math.max(1024, srcRegion.height / 10); // * param.getSourceYSubsampling(); // TODO: Using a multiple of 8 is probably a good idea for JPEG
|
||||||
final int srcMaxY = srcRegion.y + srcRegion.height;
|
final int srcMaxY = srcRegion.y + srcRegion.height;
|
||||||
int destY = dstRegion.y;
|
int destY = dstRegion.y;
|
||||||
@ -428,13 +392,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
Raster raster = delegate.readRaster(imageIndex, param); // non-converted
|
Raster raster = delegate.readRaster(imageIndex, param); // non-converted
|
||||||
|
|
||||||
// Apply source color conversion from implicit color space
|
// Apply source color conversion from implicit color space
|
||||||
if ((transform == AdobeDCT.YCC || transform == AdobeDCT.Unknown) && srcCsType == ColorSpace.TYPE_RGB) {
|
if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.YCbCrA) {
|
||||||
YCbCrConverter.convertYCbCr2RGB(raster);
|
YCbCrConverter.convertYCbCr2RGB(raster);
|
||||||
}
|
}
|
||||||
else if (transform == AdobeDCT.YCCK && srcCsType == ColorSpace.TYPE_CMYK) {
|
else if (csType == JPEGColorSpace.YCCK) {
|
||||||
YCbCrConverter.convertYCCK2CMYK(raster);
|
YCbCrConverter.convertYCCK2CMYK(raster);
|
||||||
}
|
}
|
||||||
else if (transform == AdobeDCT.Unknown && srcCsType == ColorSpace.TYPE_CMYK) {
|
else if (csType == JPEGColorSpace.CMYK) {
|
||||||
invertCMYK(raster);
|
invertCMYK(raster);
|
||||||
}
|
}
|
||||||
// ...else assume the raster is already converted
|
// ...else assume the raster is already converted
|
||||||
@ -472,6 +436,123 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JPEGColorSpace getSourceCSType(AdobeDCT adobeDCT, final SOF startOfFrame) throws IIOException {
|
||||||
|
/*
|
||||||
|
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
|
||||||
|
|
||||||
|
When reading, the contents of the stream are interpreted by the usual JPEG conventions, as follows:
|
||||||
|
|
||||||
|
• If a JFIF APP0 marker segment is present, the colorspace is known to be either grayscale, YCbCr or CMYK.
|
||||||
|
If an APP2 marker segment containing an embedded ICC profile is also present, then YCbCr is converted to RGB according
|
||||||
|
to the formulas given in the JFIF spec, and the ICC profile is assumed to refer to the resulting RGB space.
|
||||||
|
CMYK data is read as is, and the ICC profile is assumed to refer to the resulting CMYK space.
|
||||||
|
|
||||||
|
• If an Adobe APP14 marker segment is present, the colorspace is determined by consulting the transform flag.
|
||||||
|
The transform flag takes one of three values:
|
||||||
|
o 2 - The image is encoded as YCCK (implicitly converted from CMYK on encoding).
|
||||||
|
o 1 - The image is encoded as YCbCr (implicitly converted from RGB on encoding).
|
||||||
|
o 0 - Unknown. 3-channel images are assumed to be RGB, 4-channel images are assumed to be CMYK.
|
||||||
|
|
||||||
|
• If neither marker segment is present, the following procedure is followed: Single-channel images are assumed
|
||||||
|
to be grayscale, and 2-channel images are assumed to be grayscale with an alpha channel. For 3- and 4-channel
|
||||||
|
images, the component ids are consulted. If these values are 1-3 for a 3-channel image, then the image is
|
||||||
|
assumed to be YCbCr. If these values are 1-4 for a 4-channel image, then the image is assumed to be YCbCrA. If
|
||||||
|
these values are > 4, they are checked against the ASCII codes for 'R', 'G', 'B', 'A', 'C', 'c', 'M', 'Y', 'K'.
|
||||||
|
These can encode the following colorspaces:
|
||||||
|
|
||||||
|
RGB
|
||||||
|
RGBA
|
||||||
|
YCC (as 'Y','C','c'), assumed to be PhotoYCC
|
||||||
|
YCCA (as 'Y','C','c','A'), assumed to be PhotoYCCA
|
||||||
|
CMYK (as 'C', 'M', 'Y', 'K').
|
||||||
|
|
||||||
|
Otherwise, 3-channel subsampled images are assumed to be YCbCr, 3-channel non-subsampled images are assumed to
|
||||||
|
be RGB, 4-channel subsampled images are assumed to be YCCK, and 4-channel, non-subsampled images are assumed to
|
||||||
|
be CMYK.
|
||||||
|
|
||||||
|
• All other images are declared uninterpretable and an exception is thrown if an attempt is made to read one as
|
||||||
|
a BufferedImage. Such an image may be read only as a Raster. If an image is interpretable but there is no Java
|
||||||
|
ColorSpace available corresponding to the encoded colorspace (e.g. YCbCr/YCCK), then ImageReader.getRawImageType
|
||||||
|
will return null.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (adobeDCT != null) {
|
||||||
|
switch (adobeDCT.getTransform()) {
|
||||||
|
case AdobeDCT.YCC:
|
||||||
|
return JPEGColorSpace.YCbCr;
|
||||||
|
case AdobeDCT.YCCK:
|
||||||
|
return JPEGColorSpace.YCCK;
|
||||||
|
case AdobeDCT.Unknown:
|
||||||
|
if (startOfFrame.components.length == 1) {
|
||||||
|
return JPEGColorSpace.Gray;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components.length == 3) {
|
||||||
|
return JPEGColorSpace.RGB;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components.length == 4) {
|
||||||
|
return JPEGColorSpace.CMYK;
|
||||||
|
}
|
||||||
|
// Else fall through
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (startOfFrame.components.length) {
|
||||||
|
case 1:
|
||||||
|
return JPEGColorSpace.Gray;
|
||||||
|
case 2:
|
||||||
|
return JPEGColorSpace.GrayA;
|
||||||
|
case 3:
|
||||||
|
if (startOfFrame.components[0].id == 1 && startOfFrame.components[1].id == 2 && startOfFrame.components[2].id == 3) {
|
||||||
|
return JPEGColorSpace.YCbCr;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'R' && startOfFrame.components[1].id == 'G' && startOfFrame.components[2].id == 'B') {
|
||||||
|
return JPEGColorSpace.RGB;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'Y' && startOfFrame.components[1].id == 'C' && startOfFrame.components[2].id == 'c') {
|
||||||
|
return JPEGColorSpace.PhotoYCC;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if subsampled, YCbCr else RGB
|
||||||
|
for (SOFComponent component : startOfFrame.components) {
|
||||||
|
if (component.hSub != 1 || component.vSub != 1) {
|
||||||
|
return JPEGColorSpace.YCbCr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JPEGColorSpace.RGB;
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
if (startOfFrame.components[0].id == 1 && startOfFrame.components[1].id == 2 && startOfFrame.components[2].id == 3 && startOfFrame.components[3].id == 4) {
|
||||||
|
return JPEGColorSpace.YCbCrA;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'R' && startOfFrame.components[1].id == 'G' && startOfFrame.components[2].id == 'B' && startOfFrame.components[3].id == 'A') {
|
||||||
|
return JPEGColorSpace.RGBA;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'Y' && startOfFrame.components[1].id == 'C' && startOfFrame.components[2].id == 'c' && startOfFrame.components[3].id == 'A') {
|
||||||
|
return JPEGColorSpace.PhotoYCCA;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'C' && startOfFrame.components[1].id == 'M' && startOfFrame.components[2].id == 'Y' && startOfFrame.components[3].id == 'K') {
|
||||||
|
return JPEGColorSpace.CMYK;
|
||||||
|
}
|
||||||
|
else if (startOfFrame.components[0].id == 'Y' && startOfFrame.components[1].id == 'C' && startOfFrame.components[2].id == 'c' && startOfFrame.components[3].id == 'K') {
|
||||||
|
return JPEGColorSpace.YCCK;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if subsampled, YCCK else CMYK
|
||||||
|
for (SOFComponent component : startOfFrame.components) {
|
||||||
|
if (component.hSub != 1 || component.vSub != 1) {
|
||||||
|
return JPEGColorSpace.YCCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JPEGColorSpace.CMYK;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IIOException("Cannot determine source color space");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
||||||
// NOTE: This is probably not the right way to do it... :-P
|
// NOTE: This is probably not the right way to do it... :-P
|
||||||
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
||||||
@ -608,20 +689,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (!jfif.isEmpty()) {
|
if (!jfif.isEmpty()) {
|
||||||
JPEGSegment segment = jfif.get(0);
|
JPEGSegment segment = jfif.get(0);
|
||||||
DataInputStream stream = new DataInputStream(segment.data());
|
return JFIFSegment.read(segment.data());
|
||||||
|
|
||||||
int x, y;
|
|
||||||
|
|
||||||
return new JFIFSegment(
|
|
||||||
stream.readUnsignedByte(),
|
|
||||||
stream.readUnsignedByte(),
|
|
||||||
stream.readUnsignedByte(),
|
|
||||||
stream.readUnsignedShort(),
|
|
||||||
stream.readUnsignedShort(),
|
|
||||||
x = stream.readUnsignedByte(),
|
|
||||||
y = stream.readUnsignedByte(),
|
|
||||||
readFully(stream, x * y * 3)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -632,13 +700,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (!jfxx.isEmpty()) {
|
if (!jfxx.isEmpty()) {
|
||||||
JPEGSegment segment = jfxx.get(0);
|
JPEGSegment segment = jfxx.get(0);
|
||||||
|
return JFXXSegment.read(segment.data(), segment.length());
|
||||||
DataInputStream stream = new DataInputStream(segment.data());
|
|
||||||
|
|
||||||
return new JFXXSegment(
|
|
||||||
stream.readUnsignedByte(),
|
|
||||||
readFully(stream, segment.length() - 1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -669,10 +731,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (chunkNumber != 1 && chunkCount != 1) {
|
if (chunkNumber != 1 && chunkCount != 1) {
|
||||||
throw new IIOException(String.format("Bad number of 'ICC_PROFILE' chunks."));
|
throw new IIOException(String.format("Bad number of 'ICC_PROFILE' chunks: %d of %d.", chunkNumber, chunkCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ICC_Profile.getInstance(stream);
|
return readICCProfileSafe(stream);
|
||||||
}
|
}
|
||||||
else if (!segments.isEmpty()) {
|
else if (!segments.isEmpty()) {
|
||||||
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
|
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
|
||||||
@ -680,17 +742,18 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkNumber = stream.readUnsignedByte();
|
int chunkNumber = stream.readUnsignedByte();
|
||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
|
||||||
// Others use count == 1, and all numbers == 1.
|
|
||||||
// Handle these by issuing warning
|
|
||||||
boolean badICC = false;
|
|
||||||
if (chunkNumber < 1) {
|
if (chunkNumber < 1) {
|
||||||
badICC = true;
|
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0. Ignore these profiles
|
||||||
processWarningOccurred("Unexpected ICC profile chunk index: " + chunkNumber + ". Ignoring indexes, assuming chunks are in sequence.");
|
processWarningOccurred("Invalid 'ICC_PROFILE' chunk index: " + chunkNumber + ". Ignoring ICC profile.");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean badICC = false;
|
||||||
if (chunkCount != segments.size()) {
|
if (chunkCount != segments.size()) {
|
||||||
|
// Others use count == 1, and all numbers == 1.
|
||||||
|
// Handle these by issuing warning
|
||||||
badICC = true;
|
badICC = true;
|
||||||
processWarningOccurred("Unexpected ICC profile chunk count: " + chunkCount + ". Ignoring count, assuming " + segments.size() + " chunks in sequence.");
|
processWarningOccurred("Unexpected 'ICC_PROFILE' chunk count: " + chunkCount + ". Ignoring count, assuming " + segments.size() + " chunks in sequence.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = badICC ? segments.size() : chunkCount;
|
int count = badICC ? segments.size() : chunkCount;
|
||||||
@ -703,18 +766,30 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
chunkNumber = stream.readUnsignedByte();
|
chunkNumber = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (!badICC && stream.readUnsignedByte() != chunkCount) {
|
if (!badICC && stream.readUnsignedByte() != chunkCount) {
|
||||||
throw new IIOException(String.format("Bad number of 'ICC_PROFILE' chunks."));
|
throw new IIOException(String.format("Bad number of 'ICC_PROFILE' chunks: %d of %d.", chunkNumber, chunkCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
streams[badICC ? i : chunkNumber - 1] = stream;
|
streams[badICC ? i : chunkNumber - 1] = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ICC_Profile.getInstance(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))));
|
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ICC_Profile readICCProfileSafe(final InputStream stream) throws IOException {
|
||||||
|
try {
|
||||||
|
return ICC_Profile.getInstance(stream);
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
||||||
|
// Usual reason: Broken tools store truncated ICC profiles in a single ICC_PROFILE chunk...
|
||||||
|
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk(s): %s. Ignoring ICC profile.", e.getMessage()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canReadRaster() {
|
public boolean canReadRaster() {
|
||||||
return delegate.canReadRaster();
|
return delegate.canReadRaster();
|
||||||
@ -739,7 +814,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readerSupportsThumbnails() {
|
public boolean readerSupportsThumbnails() {
|
||||||
return true; // We support EXIF, JFIF and JFXX style thumbnails, if present
|
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readThumbnailMetadata(int imageIndex) throws IOException {
|
private void readThumbnailMetadata(int imageIndex) throws IOException {
|
||||||
@ -747,47 +822,54 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (thumbnails == null) {
|
if (thumbnails == null) {
|
||||||
thumbnails = new ArrayList<ThumbnailReader>();
|
thumbnails = new ArrayList<ThumbnailReader>();
|
||||||
|
ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate();
|
||||||
|
|
||||||
|
// Read JFIF thumbnails if present
|
||||||
JFIFSegment jfif = getJFIF();
|
JFIFSegment jfif = getJFIF();
|
||||||
if (jfif != null && jfif.thumbnail != null) {
|
if (jfif != null && jfif.thumbnail != null) {
|
||||||
thumbnails.add(new JFIFThumbnailReader(this, imageIndex, thumbnails.size(), jfif));
|
thumbnails.add(new JFIFThumbnailReader(thumbnailProgressDelegator, imageIndex, thumbnails.size(), jfif));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read JFXX thumbnails if present
|
||||||
JFXXSegment jfxx = getJFXX();
|
JFXXSegment jfxx = getJFXX();
|
||||||
if (jfxx != null && jfxx.thumbnail != null) {
|
if (jfxx != null && jfxx.thumbnail != null) {
|
||||||
switch (jfxx.extensionCode) {
|
switch (jfxx.extensionCode) {
|
||||||
case JFXXSegment.JPEG:
|
case JFXXSegment.JPEG:
|
||||||
case JFXXSegment.INDEXED:
|
case JFXXSegment.INDEXED:
|
||||||
case JFXXSegment.RGB:
|
case JFXXSegment.RGB:
|
||||||
thumbnails.add(new JFXXThumbnailReader(this, imageIndex, thumbnails.size(), jfxx));
|
thumbnails.add(new JFXXThumbnailReader(thumbnailProgressDelegator, imageIndex, thumbnails.size(), jfxx));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
processWarningOccurred("Unknown JFXX extension code: " + jfxx.extensionCode);
|
processWarningOccurred("Unknown JFXX extension code: " + jfxx.extensionCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read Exif thumbnails if present
|
||||||
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||||
|
|
||||||
if (!exifSegments.isEmpty()) {
|
if (!exifSegments.isEmpty()) {
|
||||||
JPEGSegment exif = exifSegments.get(0);
|
JPEGSegment exif = exifSegments.get(0);
|
||||||
InputStream data = exif.data();
|
InputStream data = exif.data();
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
data.read(); // Pad
|
|
||||||
|
|
||||||
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
if (data.read() == -1) {
|
||||||
CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
|
// Pad
|
||||||
|
processWarningOccurred("Exif chunk has no data.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
||||||
|
CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
|
||||||
|
|
||||||
if (exifMetadata.directoryCount() == 2) {
|
if (exifMetadata.directoryCount() == 2) {
|
||||||
Directory ifd1 = exifMetadata.getDirectory(1);
|
Directory ifd1 = exifMetadata.getDirectory(1);
|
||||||
|
|
||||||
Entry compression = ifd1.getEntryById(TIFF.TAG_COMPRESSION);
|
Entry compression = ifd1.getEntryById(TIFF.TAG_COMPRESSION);
|
||||||
|
|
||||||
// 1 = no compression, 6 = JPEG compression (default)
|
// 1 = no compression, 6 = JPEG compression (default)
|
||||||
if (compression == null || compression.getValue().equals(1) || compression.getValue().equals(6)) {
|
if (compression == null || compression.getValue().equals(1) || compression.getValue().equals(6)) {
|
||||||
thumbnails.add(new EXIFThumbnailReader(this, 0, thumbnails.size(), ifd1, stream));
|
thumbnails.add(new EXIFThumbnailReader(thumbnailProgressDelegator, 0, thumbnails.size(), ifd1, stream));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
processWarningOccurred("EXIF IFD with unknown compression: " + compression.getValue());
|
processWarningOccurred("EXIF IFD with unknown compression (expected 1 or 6): " + compression.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,26 +909,6 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return thumbnails.get(thumbnailIndex).read();
|
return thumbnails.get(thumbnailIndex).read();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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) {
|
||||||
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
@ -1058,6 +1120,20 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ThumbnailProgressDelegate implements ThumbnailReadProgressListener {
|
||||||
|
public void processThumbnailStarted(int imageIndex, int thumbnailIndex) {
|
||||||
|
JPEGImageReader.this.processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processThumbnailProgress(float percentageDone) {
|
||||||
|
JPEGImageReader.this.processThumbnailProgress(percentageDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processThumbnailComplete() {
|
||||||
|
JPEGImageReader.this.processThumbnailComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class SOF {
|
private static class SOF {
|
||||||
private final int marker;
|
private final int marker;
|
||||||
private final int samplePrecision;
|
private final int samplePrecision;
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ThumbnailReadProgressListener
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: ThumbnailReadProgressListener.java,v 1.0 07.05.12 10:15 haraldk Exp$
|
||||||
|
*/
|
||||||
|
interface ThumbnailReadProgressListener {
|
||||||
|
void processThumbnailStarted(int imageIndex, int thumbnailIndex);
|
||||||
|
|
||||||
|
void processThumbnailProgress(float percentageDone);
|
||||||
|
|
||||||
|
void processThumbnailComplete();
|
||||||
|
}
|
@ -44,25 +44,25 @@ import java.io.InputStream;
|
|||||||
*/
|
*/
|
||||||
abstract class ThumbnailReader {
|
abstract class ThumbnailReader {
|
||||||
|
|
||||||
private final JPEGImageReader parent;
|
private final ThumbnailReadProgressListener progressListener;
|
||||||
protected final int imageIndex;
|
protected final int imageIndex;
|
||||||
protected final int thumbnailIndex;
|
protected final int thumbnailIndex;
|
||||||
|
|
||||||
protected ThumbnailReader(final JPEGImageReader parent, final int imageIndex, final int thumbnailIndex) {
|
protected ThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex) {
|
||||||
this.parent = parent;
|
this.progressListener = progressListener;
|
||||||
this.imageIndex = imageIndex;
|
this.imageIndex = imageIndex;
|
||||||
this.thumbnailIndex = thumbnailIndex;
|
this.thumbnailIndex = thumbnailIndex;
|
||||||
}
|
}
|
||||||
protected final void processThumbnailStarted() {
|
protected final void processThumbnailStarted() {
|
||||||
parent.processThumbnailStarted(imageIndex, thumbnailIndex);
|
progressListener.processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void processThumbnailProgress(float percentageDone) {
|
protected final void processThumbnailProgress(float percentageDone) {
|
||||||
parent.processThumbnailProgress(percentageDone);
|
progressListener.processThumbnailProgress(percentageDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void processThumbnailComplete() {
|
protected final void processThumbnailComplete() {
|
||||||
parent.processThumbnailComplete();
|
progressListener.processThumbnailComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
static protected BufferedImage readJPEGThumbnail(InputStream stream) throws IOException {
|
static protected BufferedImage readJPEGThumbnail(InputStream stream) throws IOException {
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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.stream.URLImageInputStreamSpi;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.spi.IIORegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractThumbnailReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: AbstractThumbnailReaderTest.java,v 1.0 04.05.12 15:55 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public abstract class AbstractThumbnailReaderTest {
|
||||||
|
static {
|
||||||
|
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ThumbnailReader createReader(
|
||||||
|
ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, ImageInputStream stream
|
||||||
|
) throws IOException;
|
||||||
|
|
||||||
|
protected final ImageInputStream createStream(final String name) throws IOException {
|
||||||
|
URL resource = getClass().getResource(name);
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(resource);
|
||||||
|
assertNotNull("Could not create stream for resource " + resource, stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.CompoundDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIFThumbnailReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: EXIFThumbnailReaderTest.java,v 1.0 04.05.12 15:55 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class EXIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EXIFThumbnailReader createReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final ImageInputStream stream) throws IOException {
|
||||||
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(stream, JPEG.APP1, "Exif");
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
assertNotNull(segments);
|
||||||
|
assertFalse(segments.isEmpty());
|
||||||
|
|
||||||
|
EXIFReader reader = new EXIFReader();
|
||||||
|
InputStream data = segments.get(0).data();
|
||||||
|
if (data.read() < 0) {
|
||||||
|
throw new AssertionError("EOF!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInputStream exifStream = ImageIO.createImageInputStream(data);
|
||||||
|
CompoundDirectory ifds = (CompoundDirectory) reader.read(exifStream);
|
||||||
|
|
||||||
|
assertEquals(2, ifds.directoryCount());
|
||||||
|
|
||||||
|
return new EXIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, ifds.getDirectory(1), exifStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadJPEG() throws IOException {
|
||||||
|
ThumbnailReader reader = createReader(mock(ThumbnailReadProgressListener.class), 0, 0, createStream("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"));
|
||||||
|
|
||||||
|
assertEquals(114, reader.getWidth());
|
||||||
|
assertEquals(160, reader.getHeight());
|
||||||
|
|
||||||
|
BufferedImage thumbnail = reader.read();
|
||||||
|
assertNotNull(thumbnail);
|
||||||
|
assertEquals(114, thumbnail.getWidth());
|
||||||
|
assertEquals(160, thumbnail.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadRaw() throws IOException {
|
||||||
|
ThumbnailReader reader = createReader(mock(ThumbnailReadProgressListener.class), 0, 0, createStream("/jpeg/exif-rgb-thumbnail-sony-d700.jpg"));
|
||||||
|
|
||||||
|
assertEquals(80, reader.getWidth());
|
||||||
|
assertEquals(60, reader.getHeight());
|
||||||
|
|
||||||
|
BufferedImage thumbnail = reader.read();
|
||||||
|
assertNotNull(thumbnail);
|
||||||
|
assertEquals(80, thumbnail.getWidth());
|
||||||
|
assertEquals(60, thumbnail.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProgressListenerJPEG() throws IOException {
|
||||||
|
ThumbnailReadProgressListener listener = mock(ThumbnailReadProgressListener.class);
|
||||||
|
|
||||||
|
createReader(listener, 42, 43, createStream("/jpeg/cmyk-sample-multiple-chunk-icc.jpg")).read();
|
||||||
|
|
||||||
|
InOrder order = inOrder(listener);
|
||||||
|
order.verify(listener).processThumbnailStarted(42, 43);
|
||||||
|
order.verify(listener, atLeastOnce()).processThumbnailProgress(100f);
|
||||||
|
order.verify(listener).processThumbnailComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProgressListenerRaw() throws IOException {
|
||||||
|
ThumbnailReadProgressListener listener = mock(ThumbnailReadProgressListener.class);
|
||||||
|
|
||||||
|
createReader(listener, 0, 99, createStream("/jpeg/exif-rgb-thumbnail-sony-d700.jpg")).read();
|
||||||
|
|
||||||
|
InOrder order = inOrder(listener);
|
||||||
|
order.verify(listener).processThumbnailStarted(0, 99);
|
||||||
|
order.verify(listener, atLeastOnce()).processThumbnailProgress(100f);
|
||||||
|
order.verify(listener).processThumbnailComplete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JFIFThumbnailReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: JFIFThumbnailReaderTest.java,v 1.0 04.05.12 15:56 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class JFIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||||
|
@Override
|
||||||
|
protected JFIFThumbnailReader createReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, ImageInputStream stream) throws IOException {
|
||||||
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(stream, JPEG.APP0, "JFIF");
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
assertNotNull(segments);
|
||||||
|
assertFalse(segments.isEmpty());
|
||||||
|
|
||||||
|
return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIFSegment.read(segments.get(0).data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadRaw() throws IOException {
|
||||||
|
ThumbnailReader reader = createReader(mock(ThumbnailReadProgressListener.class), 0, 0, createStream("/jpeg/jfif-jfif-and-exif-thumbnail-sharpshot-iphone.jpg"));
|
||||||
|
|
||||||
|
assertEquals(131, reader.getWidth());
|
||||||
|
assertEquals(122, reader.getHeight());
|
||||||
|
|
||||||
|
BufferedImage thumbnail = reader.read();
|
||||||
|
assertNotNull(thumbnail);
|
||||||
|
assertEquals(131, thumbnail.getWidth());
|
||||||
|
assertEquals(122, thumbnail.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProgressListenerRaw() throws IOException {
|
||||||
|
ThumbnailReadProgressListener listener = mock(ThumbnailReadProgressListener.class);
|
||||||
|
|
||||||
|
createReader(listener, 0, 99, createStream("/jpeg/jfif-jfif-and-exif-thumbnail-sharpshot-iphone.jpg")).read();
|
||||||
|
|
||||||
|
InOrder order = inOrder(listener);
|
||||||
|
order.verify(listener).processThumbnailStarted(0, 99);
|
||||||
|
order.verify(listener, atLeastOnce()).processThumbnailProgress(100f);
|
||||||
|
order.verify(listener).processThumbnailComplete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JFXXThumbnailReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: JFXXThumbnailReaderTest.java,v 1.0 04.05.12 15:56 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class JFXXThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||||
|
@Override
|
||||||
|
protected JFXXThumbnailReader createReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, ImageInputStream stream) throws IOException {
|
||||||
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(stream, JPEG.APP0, "JFXX");
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
assertNotNull(segments);
|
||||||
|
assertFalse(segments.isEmpty());
|
||||||
|
|
||||||
|
JPEGSegment jfxx = segments.get(0);
|
||||||
|
return new JFXXThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFXXSegment.read(jfxx.data(), jfxx.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadJPEG() throws IOException {
|
||||||
|
ThumbnailReader reader = createReader(mock(ThumbnailReadProgressListener.class), 0, 0, createStream("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg"));
|
||||||
|
|
||||||
|
assertEquals(80, reader.getWidth());
|
||||||
|
assertEquals(60, reader.getHeight());
|
||||||
|
|
||||||
|
BufferedImage thumbnail = reader.read();
|
||||||
|
assertNotNull(thumbnail);
|
||||||
|
assertEquals(80, thumbnail.getWidth());
|
||||||
|
assertEquals(60, thumbnail.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test JFXX indexed thumbnail
|
||||||
|
// TODO: Test JFXX RGB thumbnail
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProgressListenerRaw() throws IOException {
|
||||||
|
ThumbnailReadProgressListener listener = mock(ThumbnailReadProgressListener.class);
|
||||||
|
|
||||||
|
createReader(listener, 0, 99, createStream("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg")).read();
|
||||||
|
|
||||||
|
InOrder order = inOrder(listener);
|
||||||
|
order.verify(listener).processThumbnailStarted(0, 99);
|
||||||
|
order.verify(listener, atLeastOnce()).processThumbnailProgress(100f);
|
||||||
|
order.verify(listener).processThumbnailComplete();
|
||||||
|
}
|
||||||
|
}
|
@ -33,17 +33,19 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReadParam;
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.spi.IIORegistry;
|
import javax.imageio.spi.IIORegistry;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JPEGImageReaderTest
|
* JPEGImageReaderTest
|
||||||
@ -138,6 +140,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
assertEquals(expectedData.length, data.length);
|
assertEquals(expectedData.length, data.length);
|
||||||
|
|
||||||
assertJPEGPixelsEqual(expectedData, data, 0);
|
assertJPEGPixelsEqual(expectedData, data, 0);
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, int actualOffset) {
|
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, int actualOffset) {
|
||||||
@ -160,6 +164,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(345, image.getWidth());
|
assertEquals(345, image.getWidth());
|
||||||
assertEquals(540, image.getHeight());
|
assertEquals(540, image.getHeight());
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -175,10 +181,33 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
|
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
|
||||||
BufferedImage image = reader.read(0, param);
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(3874, image.getWidth());
|
assertEquals(3874, image.getWidth());
|
||||||
assertEquals(16, image.getHeight());
|
assertEquals(16, image.getHeight());
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTruncatedICCProfile() throws IOException {
|
||||||
|
// File contains single 'ICC_PROFILE' chunk, with a truncated (32 000 bytes) "Europe ISO Coated FOGRA27" ICC profile (by Adobe).
|
||||||
|
// Profile should have been about 550 000 bytes, split into multiple chunks. Written by GIMP 2.6.11
|
||||||
|
// See: https://bugzilla.redhat.com/show_bug.cgi?id=695246
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg")));
|
||||||
|
|
||||||
|
assertEquals(1993, reader.getWidth(0));
|
||||||
|
assertEquals(1038, reader.getHeight(0));
|
||||||
|
|
||||||
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(1993, image.getWidth());
|
||||||
|
assertEquals(8, image.getHeight());
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -198,6 +227,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
assertEquals(449, image.getHeight());
|
assertEquals(449, image.getHeight());
|
||||||
|
|
||||||
// TODO: Need to test colors!
|
// TODO: Need to test colors!
|
||||||
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -414,4 +444,112 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<TestData> getCMYKData() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(100, 100)),
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(100, 100)),
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"), new Dimension(100, 100)),
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetImageTypesCMYK() throws IOException {
|
||||||
|
// Make sure CMYK images will report their embedded color profile among image types
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
List<TestData> cmykData = getCMYKData();
|
||||||
|
|
||||||
|
for (TestData data : cmykData) {
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
|
||||||
|
|
||||||
|
assertTrue(data + " has no image types", types.hasNext());
|
||||||
|
|
||||||
|
boolean hasRGBType = false;
|
||||||
|
boolean hasCMYKType = false;
|
||||||
|
|
||||||
|
while (types.hasNext()) {
|
||||||
|
ImageTypeSpecifier type = types.next();
|
||||||
|
|
||||||
|
int csType = type.getColorModel().getColorSpace().getType();
|
||||||
|
if (csType == ColorSpace.TYPE_RGB) {
|
||||||
|
hasRGBType = true;
|
||||||
|
}
|
||||||
|
else if (csType == ColorSpace.TYPE_CMYK) {
|
||||||
|
assertTrue("CMYK types should be delivered after RGB types (violates \"contract\" of more \"natural\" type first) for " + data, hasRGBType);
|
||||||
|
|
||||||
|
hasCMYKType = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No RGB types for " + data, hasRGBType);
|
||||||
|
assertTrue("No CMYK types for " + data, hasCMYKType);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRawImageTypeCMYK() throws IOException {
|
||||||
|
// Make sure images that are encoded as CMYK (not YCCK) actually return non-null for getRawImageType
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
List<TestData> cmykData = Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(100, 100)),
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
for (TestData data : cmykData) {
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
|
||||||
|
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||||
|
assertNotNull("No raw type for " + data, rawType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadCMYKAsCMYK() throws IOException {
|
||||||
|
// Make sure CMYK images can be read and still contain their original (embedded) color profile
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
List<TestData> cmykData = getCMYKData();
|
||||||
|
|
||||||
|
for (TestData data : cmykData) {
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
|
||||||
|
|
||||||
|
assertTrue(data + " has no image types", types.hasNext());
|
||||||
|
|
||||||
|
ImageTypeSpecifier cmykType = null;
|
||||||
|
|
||||||
|
while (types.hasNext()) {
|
||||||
|
ImageTypeSpecifier type = types.next();
|
||||||
|
|
||||||
|
int csType = type.getColorModel().getColorSpace().getType();
|
||||||
|
if (csType == ColorSpace.TYPE_CMYK) {
|
||||||
|
cmykType = type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotNull("No CMYK types for " + data, cmykType);
|
||||||
|
|
||||||
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setDestinationType(cmykType);
|
||||||
|
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); // We don't really need to read it all
|
||||||
|
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(ColorSpace.TYPE_CMYK, image.getColorModel().getColorSpace().getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test RGBA/YCbCrA handling
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 615 KiB |
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Loading…
x
Reference in New Issue
Block a user