TMI-140: JPEG with corrupted ICC profile (new kind) can now be read.

This commit is contained in:
Harald Kuhr 2015-05-28 23:01:51 +02:00
parent c97ebae303
commit bbaa3e1186
4 changed files with 47 additions and 8 deletions

View File

@ -161,6 +161,11 @@ public final class ColorSpaces {
if (cs == null) { if (cs == null) {
cs = new ICC_ColorSpace(profile); cs = new ICC_ColorSpace(profile);
// Validate the color space, to avoid caching bad color spaces
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {1f, 0f, 0f});
cache.put(key, cs); cache.put(key, cs);
} }
@ -195,7 +200,7 @@ public final class ColorSpaces {
* @return {@code true} if known to be offending, {@code false} otherwise * @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null} * @throws IllegalArgumentException if {@code profile} is {@code null}
*/ */
public static boolean isOffendingColorProfile(final ICC_Profile profile) { static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile"); Validate.notNull(profile, "profile");
// NOTE: // NOTE:
@ -213,6 +218,26 @@ public final class ColorSpaces {
return data[ICC_Profile.icHdrRenderingIntent] != 0; return data[ICC_Profile.icHdrRenderingIntent] != 0;
} }
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p />
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_Profile validateProfile(final ICC_Profile profile) {
createColorSpace(profile); // Creating a color space will fail if the profile is bad
return profile;
}
/** /**
* Returns the color space specified by the given color space constant. * Returns the color space specified by the given color space constant.
* <p /> * <p />

View File

@ -409,6 +409,7 @@ public class JPEGImageReader extends ImageReaderBase {
if (DEBUG) { if (DEBUG) {
System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace())); System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace()));
} }
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null); convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
} }
// Else, pass through with no conversion // Else, pass through with no conversion
@ -858,7 +859,7 @@ public class JPEGImageReader extends ImageReaderBase {
return null; return null;
} }
return readICCProfileSafe(stream); return readICCProfileSafe(stream, allowBadIndexes);
} }
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...
@ -905,15 +906,17 @@ public class JPEGImageReader extends ImageReaderBase {
streams[badICC ? i : chunkNumber - 1] = stream; streams[badICC ? i : chunkNumber - 1] = stream;
} }
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams)))); return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
} }
return null; return null;
} }
private ICC_Profile readICCProfileSafe(final InputStream stream) throws IOException { private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) throws IOException {
try { try {
return ICC_Profile.getInstance(stream); ICC_Profile profile = ICC_Profile.getInstance(stream);
return allowBadProfile ? profile : ColorSpaces.validateProfile(profile);
} }
catch (RuntimeException e) { catch (RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform. // NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
@ -1306,6 +1309,8 @@ public class JPEGImageReader extends ImageReaderBase {
int subX = 1; int subX = 1;
int subY = 1; int subY = 1;
int xOff = 0;
int yOff = 0;
Rectangle roi = null; Rectangle roi = null;
boolean metadata = false; boolean metadata = false;
boolean thumbnails = false; boolean thumbnails = false;
@ -1318,9 +1323,17 @@ public class JPEGImageReader extends ImageReaderBase {
String[] sub = args[++argIdx].split(","); String[] sub = args[++argIdx].split(",");
try { try {
if (sub.length >= 4) {
subX = Integer.parseInt(sub[0]);
subY = Integer.parseInt(sub[1]);
xOff = Integer.parseInt(sub[2]);
yOff = Integer.parseInt(sub[3]);
}
else {
subX = Integer.parseInt(sub[0]); subX = Integer.parseInt(sub[0]);
subY = sub.length > 1 ? Integer.parseInt(sub[1]) : subX; subY = sub.length > 1 ? Integer.parseInt(sub[1]) : subX;
} }
}
catch (NumberFormatException e) { catch (NumberFormatException e) {
System.err.println("Bad sub sampling (x,y): '" + args[argIdx] + "'"); System.err.println("Bad sub sampling (x,y): '" + args[argIdx] + "'");
} }
@ -1423,7 +1436,7 @@ public class JPEGImageReader extends ImageReaderBase {
BufferedImage image; BufferedImage image;
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
if (subX > 1 || subY > 1 || roi != null) { if (subX > 1 || subY > 1 || roi != null) {
param.setSourceSubsampling(subX, subY, 0, 0); param.setSourceSubsampling(subX, subY, xOff, yOff);
param.setSourceRegion(roi); param.setSourceRegion(roi);
image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY); image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);

View File

@ -86,6 +86,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
return Arrays.asList( return Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmm-exception-adobe-rgb.jpg"), new Dimension(626, 76)), new TestData(getClassLoaderResource("/jpeg/cmm-exception-adobe-rgb.jpg"), new Dimension(626, 76)),
new TestData(getClassLoaderResource("/jpeg/cmm-exception-srgb.jpg"), new Dimension(1800, 1200)), new TestData(getClassLoaderResource("/jpeg/cmm-exception-srgb.jpg"), new Dimension(1800, 1200)),
new TestData(getClassLoaderResource("/jpeg/corrupted-icc-srgb.jpg"), new Dimension(1024, 685)),
new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)), new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)), new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)), new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)),

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB