mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
#573: Always return RAWImageType for JPEG.
+ Bonus: Fix luma to gray conversion
This commit is contained in:
parent
b67975eef7
commit
419ffc9373
@ -97,10 +97,6 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
|
||||
protected abstract List<String> getMIMETypes();
|
||||
|
||||
protected boolean allowsNullRawImageType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static void failBecause(String message, Throwable exception) {
|
||||
throw new AssertionError(message, exception);
|
||||
}
|
||||
@ -221,6 +217,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
image = reader.read(i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
failBecause(String.format("Image %s index %s could not be read: %s", data.getInput(), i, e), e);
|
||||
}
|
||||
|
||||
@ -1359,9 +1356,6 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||
if (rawType == null && allowsNullRawImageType()) {
|
||||
continue;
|
||||
}
|
||||
assertNotNull(rawType);
|
||||
|
||||
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
|
||||
@ -1383,6 +1377,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
|
||||
assertTrue("ImageTypeSpecifier from getRawImageType should be in the iterator from getImageTypes", rawFound);
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
||||
* @return {@code dest}, or a new {@link WritableRaster} if {@code dest} is {@code null}.
|
||||
* @throws IllegalArgumentException if {@code src} and {@code dest} refer to the same object
|
||||
*/
|
||||
@Override
|
||||
public WritableRaster filter(Raster src, WritableRaster dest) {
|
||||
Validate.notNull(src, "src may not be null");
|
||||
// TODO: Why not allow same raster, if converting to 4 byte ABGR?
|
||||
@ -142,10 +143,12 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
||||
rgb[2] = (byte) (255 - (((cmyk[2] & 0xFF) * (255 - k) / 255) + k));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds2D(Raster src) {
|
||||
return src.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableRaster createCompatibleDestRaster(final Raster src) {
|
||||
// WHAT?? This code no longer work for JRE 7u45+... JRE bug?!
|
||||
// Raster child = src.createChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0, 1, 2});
|
||||
@ -157,6 +160,7 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
||||
return raster.createWritableChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0, 1, 2});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
if (dstPt == null) {
|
||||
dstPt = new Point2D.Double(srcPt.getX(), srcPt.getY());
|
||||
@ -168,6 +172,7 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
||||
return dstPt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
@ -194,73 +194,38 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
initHeader(imageIndex);
|
||||
ImageTypeSpecifier rawImageType = getRawImageType(imageIndex);
|
||||
ColorModel rawColorModel = rawImageType.getColorModel();
|
||||
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF());
|
||||
|
||||
Iterator<ImageTypeSpecifier> types;
|
||||
try {
|
||||
types = delegate.getImageTypes(0);
|
||||
}
|
||||
catch (IndexOutOfBoundsException | NegativeArraySizeException ignore) {
|
||||
types = null;
|
||||
}
|
||||
Set<ImageTypeSpecifier> types = new LinkedHashSet<>();
|
||||
|
||||
JPEGColorSpace csType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF());
|
||||
|
||||
if (types == null || !types.hasNext() || csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||
ArrayList<ImageTypeSpecifier> typeList = new ArrayList<>();
|
||||
// Add the standard types, we can always convert to these
|
||||
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||
|
||||
// We also read and return CMYK if the source image is CMYK/YCCK + original color profile if present
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
|
||||
if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||
if (profile != null && profile.getNumComponents() == 4) {
|
||||
typeList.add(ImageTypeSpecifiers.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
||||
}
|
||||
|
||||
typeList.add(ImageTypeSpecifiers.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 && profile.getNumComponents() == 3) {
|
||||
typeList.add(ImageTypeSpecifiers.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(
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB),
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR),
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE),
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)
|
||||
));
|
||||
|
||||
if (profile != null && profile.getNumComponents() == 3) {
|
||||
typeList.add(ImageTypeSpecifiers.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {0, 1, 2, 3}, DataBuffer.TYPE_BYTE, true, false));
|
||||
}
|
||||
if (rawColorModel.getColorSpace().getType() != ColorSpace.TYPE_GRAY) {
|
||||
// Add the standard types, we can always convert to these, except for gray
|
||||
if (rawColorModel.hasAlpha()) {
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE));
|
||||
}
|
||||
|
||||
return typeList.iterator();
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||
}
|
||||
else if (csType == JPEGColorSpace.RGB) {
|
||||
// Bug in com.sun...JPEGImageReader: returns gray as acceptable type, but refuses to convert
|
||||
ArrayList<ImageTypeSpecifier> typeList = new ArrayList<>();
|
||||
|
||||
// Filter out the gray type
|
||||
while (types.hasNext()) {
|
||||
ImageTypeSpecifier type = types.next();
|
||||
if (type.getBufferedImageType() != BufferedImage.TYPE_BYTE_GRAY) {
|
||||
typeList.add(type);
|
||||
}
|
||||
types.add(rawImageType);
|
||||
|
||||
// If the source type has a luminance (Y) component, we can also convert to gray
|
||||
if (sourceCSType != JPEGColorSpace.RGB && sourceCSType != JPEGColorSpace.RGBA && sourceCSType != JPEGColorSpace.CMYK) {
|
||||
if (rawColorModel.hasAlpha()) {
|
||||
types.add(ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE, false));
|
||||
}
|
||||
|
||||
return typeList.iterator();
|
||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
|
||||
}
|
||||
|
||||
return types;
|
||||
return types.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -268,34 +233,55 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
checkBounds(imageIndex);
|
||||
initHeader(imageIndex);
|
||||
|
||||
// If delegate can determine the spec, we'll just go with that
|
||||
try {
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(0);
|
||||
|
||||
if (rawType != null) {
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
catch (IIOException | NullPointerException | ArrayIndexOutOfBoundsException | NegativeArraySizeException ignore) {
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// Otherwise, consult the image metadata
|
||||
// Consult the image metadata
|
||||
JPEGColorSpace csType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF());
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
|
||||
ColorSpace cs;
|
||||
boolean hasAlpha = false;
|
||||
|
||||
switch (csType) {
|
||||
case CMYK:
|
||||
// Create based on embedded profile if exists, or create from "Generic CMYK"
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
case GrayA:
|
||||
hasAlpha = true;
|
||||
case Gray:
|
||||
// Create based on embedded profile if exists, otherwise create from Gray
|
||||
cs = profile != null && profile.getNumComponents() == 1
|
||||
? ColorSpaces.createColorSpace(profile)
|
||||
: ColorSpaces.getColorSpace(ColorSpace.CS_GRAY);
|
||||
return ImageTypeSpecifiers.createInterleaved(cs, hasAlpha ? new int[] {1, 0} : new int[] {0}, DataBuffer.TYPE_BYTE, hasAlpha, false);
|
||||
|
||||
if (profile != null && profile.getNumComponents() == 4) {
|
||||
return ImageTypeSpecifiers.createInterleaved(ColorSpaces.createColorSpace(profile), new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||
case YCbCrA:
|
||||
case RGBA:
|
||||
case PhotoYCCA:
|
||||
hasAlpha = true;
|
||||
case YCbCr:
|
||||
case RGB:
|
||||
case PhotoYCC:
|
||||
// Create based on PhotoYCC profile...
|
||||
if (csType == JPEGColorSpace.PhotoYCC || csType == JPEGColorSpace.PhotoYCCA) {
|
||||
cs = ColorSpaces.getColorSpace(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else {
|
||||
// ...or create based on embedded profile if exists, otherwise create from sRGB
|
||||
cs = profile != null && profile.getNumComponents() == 3
|
||||
? ColorSpaces.createColorSpace(profile)
|
||||
: ColorSpaces.getColorSpace(ColorSpace.CS_sRGB);
|
||||
}
|
||||
|
||||
return ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||
return ImageTypeSpecifiers.createInterleaved(cs, hasAlpha ? new int[] {3, 2, 1, 0} : new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, hasAlpha, false);
|
||||
|
||||
case YCCK:
|
||||
case CMYK:
|
||||
// Create based on embedded profile if exists, otherwise create from "Generic CMYK"
|
||||
cs = profile != null && profile.getNumComponents() == 4
|
||||
? ColorSpaces.createColorSpace(profile)
|
||||
: ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
|
||||
return ImageTypeSpecifiers.createInterleaved(cs, 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;
|
||||
// For other types, we probably can't give a proper type
|
||||
throw new IIOException("Could not determine JPEG source color space");
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,7 +308,8 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
adobeDCT = null;
|
||||
}
|
||||
|
||||
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
|
||||
JFIF jfif = getJFIF();
|
||||
JPEGColorSpace sourceCSType = getSourceCSType(jfif, adobeDCT, sof);
|
||||
|
||||
if (sof.marker == JPEG.SOF3) {
|
||||
// Read image as lossless
|
||||
@ -347,20 +334,15 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
// 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.
|
||||
else if (delegate.canReadRaster() && (
|
||||
bogusAdobeDCT ||
|
||||
sourceCSType == JPEGColorSpace.CMYK ||
|
||||
sourceCSType == JPEGColorSpace.YCCK ||
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
||||
(long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE ||
|
||||
!delegate.getImageTypes(0).hasNext() ||
|
||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
||||
else if (bogusAdobeDCT
|
||||
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|
||||
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|
||||
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Reading using raster and extra conversion");
|
||||
System.out.println("ICC color profile: " + profile);
|
||||
}
|
||||
|
||||
// TODO: Possible to optimize slightly, to avoid readAsRaster for non-CMYK and other good types?
|
||||
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, profile);
|
||||
}
|
||||
|
||||
@ -371,6 +353,56 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
return delegate.read(0, param);
|
||||
}
|
||||
|
||||
private boolean delegateCSTypeMismatch(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame, final JPEGColorSpace sourceCSType) throws IOException {
|
||||
switch (sourceCSType) {
|
||||
case GrayA:
|
||||
case RGBA:
|
||||
case YCbCrA:
|
||||
case PhotoYCC:
|
||||
case PhotoYCCA:
|
||||
case CMYK:
|
||||
case YCCK:
|
||||
// These are no longer supported by the delegate, we'll handle ourselves
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
ImageTypeSpecifier rawImageType = delegate.getRawImageType(0);
|
||||
|
||||
switch (sourceCSType) {
|
||||
case Gray:
|
||||
return rawImageType == null || rawImageType.getColorModel().getColorSpace().getType() != ColorSpace.TYPE_GRAY;
|
||||
case YCbCr:
|
||||
// NOTE: For backwards compatibility, null is allowed for YCbCr
|
||||
if (rawImageType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If We have a JFIF, but with non-standard component Ids, the standard reader mistakes it for RGB
|
||||
if (jfif != null && (startOfFrame.components[0].id != 1 || startOfFrame.components[1].id != 2 || startOfFrame.components[2].id != 3)) {
|
||||
return true;
|
||||
}
|
||||
// Else, if we have no Adobe marker and no subsampling, the standard reader mistakes it for RGB
|
||||
else if (adobeDCT == null
|
||||
&& (startOfFrame.components[0].id != 1 || startOfFrame.components[1].id != 2 || startOfFrame.components[2].id != 3)
|
||||
&& (startOfFrame.components[0].hSub == 1 || startOfFrame.components[0].vSub == 1
|
||||
|| startOfFrame.components[1].hSub == 1 || startOfFrame.components[1].vSub == 1
|
||||
|| startOfFrame.components[2].hSub == 1 || startOfFrame.components[2].vSub == 1)) {
|
||||
return true;
|
||||
}
|
||||
case RGB:
|
||||
return rawImageType == null || rawImageType.getColorModel().getColorSpace().getType() != ColorSpace.TYPE_RGB;
|
||||
default:
|
||||
// Probably needs special handling, but we don't know what to do...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IIOException | NullPointerException | ArrayIndexOutOfBoundsException | NegativeArraySizeException ignore) {
|
||||
// An exception here is a clear indicator we need to handle conversion
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
|
||||
int origWidth = getWidth(imageIndex);
|
||||
int origHeight = getHeight(imageIndex);
|
||||
@ -388,7 +420,10 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
RasterOp convert = null;
|
||||
ICC_ColorSpace intendedCS = profile != null ? ColorSpaces.createColorSpace(profile) : null;
|
||||
|
||||
if (profile != null && (csType == JPEGColorSpace.Gray || csType == JPEGColorSpace.GrayA)) {
|
||||
if (destination.getNumBands() <= 2 && (csType != JPEGColorSpace.Gray && csType != JPEGColorSpace.GrayA)) {
|
||||
convert = new LumaToGray();
|
||||
}
|
||||
else if (profile != null && (csType == JPEGColorSpace.Gray || csType == JPEGColorSpace.GrayA)) {
|
||||
// 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)...
|
||||
// convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||
@ -397,8 +432,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
// Handle inconsistencies
|
||||
if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) {
|
||||
// If ICC profile number of components and startOfFrame does not match, ignore ICC profile
|
||||
processWarningOccurred(String.format(
|
||||
"Embedded ICC color profile is incompatible with image data. " +
|
||||
processWarningOccurred(String.format("Embedded ICC color profile is incompatible with image data. " +
|
||||
"Profile indicates %d components, but SOF%d has %d color components. " +
|
||||
"Ignoring ICC profile, assuming source color space %s.",
|
||||
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame(), csType
|
||||
@ -422,10 +456,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
|
||||
if (cmykCS instanceof ICC_ColorSpace) {
|
||||
processWarningOccurred(
|
||||
"No embedded ICC color profile, defaulting to \"generic\" CMYK ICC profile. " +
|
||||
"Colors may look incorrect."
|
||||
);
|
||||
processWarningOccurred("No embedded ICC color profile, defaulting to \"generic\" CMYK ICC profile. Colors may look incorrect.");
|
||||
|
||||
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
||||
if (cmykCS != image.getColorModel().getColorSpace()) {
|
||||
@ -434,17 +465,11 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
||||
processWarningOccurred(
|
||||
"No embedded ICC color profile, will convert using inaccurate CMYK to RGB conversion. " +
|
||||
"Colors may look incorrect."
|
||||
);
|
||||
processWarningOccurred("No embedded ICC color profile, will convert using inaccurate CMYK to RGB conversion. Colors may look incorrect.");
|
||||
|
||||
convert = new FastCMYKToRGB();
|
||||
}
|
||||
}
|
||||
else if (profile != null) {
|
||||
processWarningOccurred("Embedded ICC color profile is incompatible with Java 2D, color profile will be ignored.");
|
||||
}
|
||||
|
||||
// We'll need a read param
|
||||
if (param == null) {
|
||||
@ -523,10 +548,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
switch (adobeDCT.transform) {
|
||||
case AdobeDCT.Unknown:
|
||||
return JPEGColorSpace.RGB;
|
||||
case AdobeDCT.YCC:
|
||||
return JPEGColorSpace.YCbCr;
|
||||
default:
|
||||
// TODO: Warning!
|
||||
case AdobeDCT.YCC:
|
||||
return JPEGColorSpace.YCbCr; // assume it's YCbCr
|
||||
}
|
||||
}
|
||||
@ -556,10 +580,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
switch (adobeDCT.transform) {
|
||||
case AdobeDCT.Unknown:
|
||||
return JPEGColorSpace.CMYK;
|
||||
case AdobeDCT.YCCK:
|
||||
return JPEGColorSpace.YCCK;
|
||||
default:
|
||||
// TODO: Warning!
|
||||
case AdobeDCT.YCCK:
|
||||
return JPEGColorSpace.YCCK; // assume it's YCCK
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.RasterOp;
|
||||
import java.awt.image.WritableRaster;
|
||||
|
||||
/**
|
||||
* LumaToGray.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: LumaToGray.java,v 1.0 10/04/2021 haraldk Exp$
|
||||
*/
|
||||
final class LumaToGray implements RasterOp {
|
||||
|
||||
@Override
|
||||
public WritableRaster filter(final Raster src, WritableRaster dest) {
|
||||
Validate.notNull(src, "src may not be null");
|
||||
Validate.isTrue(src != dest, "src and dest raster may not be same");
|
||||
Validate.isTrue(src.getNumDataElements() >= 3, src.getNumDataElements(), "Luma raster must have at least 3 data elements: %s");
|
||||
|
||||
if (dest == null) {
|
||||
dest = createCompatibleDestRaster(src);
|
||||
}
|
||||
|
||||
// If src and dest have alpha component, keep it, otherwise extract luma only
|
||||
int[] bandList = src.getNumBands() > 3 && dest.getNumBands() > 1 ? new int[] {0, 3} : new int[] {0};
|
||||
dest.setRect(0, 0, src.createChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, bandList));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds2D(final Raster src) {
|
||||
return src.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableRaster createCompatibleDestRaster(final Raster src) {
|
||||
WritableRaster raster = src.createCompatibleWritableRaster();
|
||||
return raster.createWritableChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getPoint2D(final Point2D srcPt, Point2D dstPt) {
|
||||
if (dstPt == null) {
|
||||
dstPt = new Point2D.Double(srcPt.getX(), srcPt.getY());
|
||||
}
|
||||
else {
|
||||
dstPt.setLocation(srcPt);
|
||||
}
|
||||
|
||||
return dstPt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ public class FastCMYKToRGBTest {
|
||||
assertNotNull(pixel);
|
||||
assertEquals(3, pixel.length);
|
||||
byte[] expected = {(byte) 255, (byte) 255, (byte) 255};
|
||||
assertTrue(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), Arrays.equals(expected, pixel));
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -95,7 +95,7 @@ public class FastCMYKToRGBTest {
|
||||
assertNotNull(pixel);
|
||||
assertEquals(3, pixel.length);
|
||||
byte[] expected = {(byte) 0, (byte) 0, (byte) 0};
|
||||
assertTrue(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), Arrays.equals(expected, pixel));
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ public class FastCMYKToRGBTest {
|
||||
assertNotNull(pixel);
|
||||
assertEquals(3, pixel.length);
|
||||
byte[] expected = {(byte) (255 - i), (byte) i, (byte) (127 - i)};
|
||||
assertTrue(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), Arrays.equals(expected, pixel));
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ public class FastCMYKToRGBTest {
|
||||
assertNotNull(pixel);
|
||||
assertEquals(3, pixel.length);
|
||||
byte[] expected = {(byte) (255 - i), (byte) i, (byte) (127 - i)};
|
||||
assertTrue(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), Arrays.equals(expected, pixel));
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ public class FastCMYKToRGBTest {
|
||||
assertNotNull(pixel);
|
||||
assertEquals(4, pixel.length);
|
||||
byte[] expected = {(byte) (255 - i), (byte) i, (byte) (127 - i), (byte) 0xff};
|
||||
assertTrue(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), Arrays.equals(expected, pixel));
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
@ -55,6 +56,7 @@ import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
@ -140,11 +142,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
// More test data in specific tests below
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean allowsNullRawImageType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("JPEG", "jpeg", "JPG", "jpg",
|
||||
@ -422,8 +419,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException {
|
||||
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes
|
||||
public void testYCbCrNotSubsampledNonstandardComponentIds() throws IOException {
|
||||
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if nonstandard component ids
|
||||
JPEGImageReader reader = createReader();
|
||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg"))) {
|
||||
reader.setInput(stream);
|
||||
@ -1235,6 +1232,56 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRGBANoGrayImageTypes() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg")));
|
||||
|
||||
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
|
||||
|
||||
while (imageTypes.hasNext()) {
|
||||
ImageTypeSpecifier specifier = imageTypes.next();
|
||||
assertNotEquals("RGB JPEGs can't be decoded as Gray as it has no luminance (Y) component", ColorSpace.TYPE_GRAY, specifier.getColorModel().getColorSpace().getType());
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@Test(expected = Exception.class)
|
||||
public void testRGBAsGray() throws IOException {
|
||||
final JPEGImageReader reader = createReader();
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/adobe-unknown-rgb-ids.jpg")));
|
||||
|
||||
assertEquals(225, reader.getWidth(0));
|
||||
assertEquals(156, reader.getHeight(0));
|
||||
|
||||
final ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRegion(new Rectangle(0, 0, 225, 8));
|
||||
param.setDestinationType(ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE));
|
||||
|
||||
// Should ideally throw IIOException due to destination type mismatch, but throws IllegalArgumentException...
|
||||
reader.read(0, param);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYCbCrAsGray() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg")));
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
|
||||
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
assertNotNull(image);
|
||||
assertEquals(BufferedImage.TYPE_BYTE_GRAY, image.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly fuzzy RGB equals method. Tolerance +/-5 steps.
|
||||
*/
|
||||
@ -1818,7 +1865,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||
|
||||
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||
assertNull(rawType); // But no exception, please...
|
||||
assertNotNull(rawType); // As of Java 9, use RGB for YCC and CMYK for YCCK
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* LumaToGrayTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: LumaToGrayTest.java,v 1.0 10/04/2021 haraldk Exp$
|
||||
*/
|
||||
public class LumaToGrayTest {
|
||||
@Test
|
||||
public void testConvertByteYcc() {
|
||||
LumaToGray convert = new LumaToGray();
|
||||
|
||||
WritableRaster input = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 1, 1, 3, null);
|
||||
WritableRaster result = null;
|
||||
|
||||
byte[] pixel = null;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
input.setDataElements(0, 0, new byte[] {(byte) i, (byte) (255 - i), (byte) (127 + i)});
|
||||
result = convert.filter(input, result);
|
||||
pixel = (byte[]) result.getDataElements(0, 0, pixel);
|
||||
|
||||
assertNotNull(pixel);
|
||||
assertEquals(1, pixel.length);
|
||||
byte[] expected = {(byte) i};
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertByteYccK() {
|
||||
LumaToGray convert = new LumaToGray();
|
||||
|
||||
WritableRaster input = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 1, 1, 4, null);
|
||||
WritableRaster result = null;
|
||||
|
||||
byte[] pixel = null;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
input.setDataElements(0, 0, new byte[] {(byte) i, (byte) (255 - i), (byte) (127 + i), (byte) 255});
|
||||
result = convert.filter(input, result);
|
||||
pixel = (byte[]) result.getDataElements(0, 0, pixel);
|
||||
|
||||
assertNotNull(pixel);
|
||||
assertEquals(1, pixel.length);
|
||||
byte[] expected = {(byte) i};
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertByteYccA() {
|
||||
LumaToGray convert = new LumaToGray();
|
||||
|
||||
WritableRaster input = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 1, 1, 4, null);
|
||||
WritableRaster result = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 1, 1, 2, null);
|
||||
|
||||
byte[] pixel = null;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
input.setDataElements(0, 0, new byte[] {(byte) i, (byte) 255, (byte) (127 + i), (byte) (255 - i)});
|
||||
result = convert.filter(input, result);
|
||||
pixel = (byte[]) result.getDataElements(0, 0, pixel);
|
||||
|
||||
assertNotNull(pixel);
|
||||
assertEquals(2, pixel.length);
|
||||
byte[] expected = {(byte) i, (byte) (255 - i)};
|
||||
assertArrayEquals(String.format("Was: %s, expected: %s", Arrays.toString(pixel), Arrays.toString(expected)), expected, pixel);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user