Compare commits

...

11 Commits

Author SHA1 Message Date
Harald Kuhr 7de8231471 [maven-release-plugin] prepare release twelvemonkeys-3.8.0 2021-12-12 13:17:16 +01:00
Harald Kuhr 0de9f79029 [maven-release-plugin] rollback the release of twelvemonkeys-3.8.0 2021-12-12 13:13:54 +01:00
Harald Kuhr eeb56acdde [maven-release-plugin] prepare for next development iteration 2021-12-11 23:26:21 +01:00
Harald Kuhr a6862cfec8 Fixed JavaDoc. 2021-12-11 23:23:05 +01:00
Harald Kuhr f8284700b4 #631 Attempt to fix tests for JDK 15 & 16 2021-12-11 22:38:42 +01:00
Harald Kuhr 38caeb22e0 #631 Introduced ColorProfiles. Profile activation through SPI to force early activation. 2021-12-11 18:58:25 +01:00
Harald Kuhr b2c5915db8 #631 New way of forcing profile activation + guarding all invocations of ICC_Profile.getInstance() 2021-12-11 18:13:07 +01:00
Harald Kuhr 3911191b04 #645 AAIOBE in CCITTFaxDecoderStream now wrapped in IOException 2021-12-11 17:48:57 +01:00
Snyk bot bc328419ac fix: upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4 (#642)
Snyk has created this PR to upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/3eb50c00-e586-4f4c-a39c-90c80f89cc60?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-10 16:13:29 +01:00
Harald Kuhr da4efe98bf Avoid fetching external resources in XMPReader. 2021-12-10 13:41:05 +01:00
Harald Kuhr 6653f4a85d Minor clean-up. 2021-12-10 13:33:28 +01:00
57 changed files with 1295 additions and 492 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -29,6 +29,9 @@
package com.twelvemonkeys.xml;
import java.io.OutputStream;
import java.io.Writer;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementationList;
import org.w3c.dom.Document;
@@ -38,9 +41,6 @@ import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import java.io.OutputStream;
import java.io.Writer;
/**
* {@code DOMImplementationLS} backed implementation.
*
@@ -88,17 +88,6 @@ public final class DOMSerializer {
output.setCharacterStream(pStream);
}
/*
// TODO: Is it useful?
public void setNewLine(final String pNewLine) {
serializer.setNewLine(pNewLine);
}
public String getNewLine() {
return serializer.getNewLine();
}
*/
/**
* Specifies wether the serializer should use indentation and optimize for
* readability.
@@ -169,13 +158,7 @@ public final class DOMSerializer {
try {
return DOMImplementationRegistry.newInstance();
}
catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
catch (InstantiationException e) {
throw new IllegalStateException(e);
}
catch (IllegalAccessException e) {
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
@@ -30,16 +30,23 @@
package com.twelvemonkeys.xml;
import com.twelvemonkeys.lang.StringUtil;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Date;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import com.twelvemonkeys.lang.StringUtil;
/**
* XMLSerializer
@@ -290,7 +297,7 @@ public class XMLSerializer {
}
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
pBuilder.append(pString.substring(pStart, pEnd));
pBuilder.append(pString, pStart, pEnd);
pBuilder.append(pEntity);
return pEnd + 1;
}
@@ -527,8 +534,7 @@ public class XMLSerializer {
builder = factory.newDocumentBuilder();
}
catch (ParserConfigurationException e) {
//noinspection ThrowableInstanceNeverThrown BOGUS
throw (IOException) new IOException(e.getMessage()).initCause(e);
throw new IOException(e);
}
DOMImplementation dom = builder.getDOMImplementation();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -0,0 +1,563 @@
/*
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
/**
* A helper class for working with ICC color profiles.
* <p>
* Standard ICC color profiles are read from system-specific locations
* for known operating systems.
* </p>
* <p>
* Color profiles may be configured by placing a property-file
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
* on the classpath, specifying the full path to the profiles.
* ICC color profiles are probably already present on your system, or
* can be downloaded from
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
* * </p>
* <p>
* Example property file:
* </p>
* <pre>
* # icc_profiles.properties
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
* GENERIC_CMYK=/path/to/Generic CMYK.icc
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/
public final class ColorProfiles {
/**
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
*/
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
static final int ICC_PROFILE_HEADER_SIZE = 128;
static {
// In case we didn't activate through SPI already
ProfileDeferralActivator.activateProfiles();
}
private ColorProfiles() {
}
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
return getProfileHeaderWithProfileId(profile.getData());
}
static byte[] getProfileHeaderWithProfileId(byte[] data) {
// ICC profile header is the first 128 bytes
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(header, data);
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
return header;
}
private static byte[] computeMD5(byte[] header, byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
return digest.digest();
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
/**
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
*/
public static boolean isCS_sRGB(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
}
/**
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_GRAY
*/
public static boolean isCS_GRAY(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* Tests whether an ICC color profile is 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>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* 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>
* </p>
*
* @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) {
// Fix profile before validation
profileCleaner.fixProfile(profile);
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
return profile;
}
/**
* Reads an ICC Profile from the given input stream, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
* @see #readProfile(InputStream)
*/
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
Validate.notNull(input, "input");
return ICC_Profile.getInstance(input);
}
/**
* Reads an ICC Profile from the given input stream, with extra validation.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
*/
public static ICC_Profile readProfile(final InputStream input) throws IOException {
Validate.notNull(input, "input");
DataInputStream dataInput = new DataInputStream(input);
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
try {
dataInput.readFully(header);
int size = validateHeaderAndGetSize(header);
byte[] data = Arrays.copyOf(header, size);
dataInput.readFully(data, header.length, size - header.length);
return createProfile(data);
}
catch (EOFException e) {
throw new IllegalArgumentException("Truncated ICC Profile data", e);
}
}
/**
* Creates an ICC Profile from the given byte array, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
* except that extraneous bytes at the end of the array is ignored.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
* @see #createProfile(byte[])
*/
public static ICC_Profile createProfileRaw(final byte[] input) {
int size = validateHeaderAndGetSize(input);
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
// does not discard extra bytes at the end. We'll chop them off here for convenience
return ICC_Profile.getInstance(limit(input, size));
}
/**
* Reads an ICC Profile from the given byte array, with extra validation.
* Extraneous bytes at the end of the array are ignored.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
*/
public static ICC_Profile createProfile(final byte[] input) {
int size = validateAndGetSize(input);
// Look up in cache before returning, these are already validated
byte[] profileHeader = getProfileHeaderWithProfileId(input);
ICC_Profile internal = getInternalProfile(profileHeader);
if (internal != null) {
return internal;
}
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
if (cached != null) {
return cached.getProfile();
}
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
// We'll validate & cache by creating a color space and returning its profile...
// TODO: Rewrite with separate cache for profiles...
return ColorSpaces.createColorSpace(profile).getProfile();
}
private static byte[] limit(byte[] input, int size) {
return input.length == size ? input : Arrays.copyOf(input, size);
}
private static int validateAndGetSize(byte[] input) {
int size = validateHeaderAndGetSize(input);
if (size < 0 || size > input.length) {
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
}
return size;
}
private static int validateHeaderAndGetSize(byte[] input) {
Validate.notNull(input, "input");
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
}
int size = intBigEndian(input, ICC_Profile.icHdrSize);
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
}
return size;
}
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
int profileCSType = getCsType(profileHeader);
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
}
return null;
}
private static int intBigEndian(byte[] data, int index) {
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
}
private static int getCsType(byte[] profileHeader) {
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
switch (csSig) {
case ICC_Profile.icSigXYZData:
return ColorSpace.TYPE_XYZ;
case ICC_Profile.icSigLabData:
return ColorSpace.TYPE_Lab;
case ICC_Profile.icSigLuvData:
return ColorSpace.TYPE_Luv;
case ICC_Profile.icSigYCbCrData:
return ColorSpace.TYPE_YCbCr;
case ICC_Profile.icSigYxyData:
return ColorSpace.TYPE_Yxy;
case ICC_Profile.icSigRgbData:
return ColorSpace.TYPE_RGB;
case ICC_Profile.icSigGrayData:
return ColorSpace.TYPE_GRAY;
case ICC_Profile.icSigHsvData:
return ColorSpace.TYPE_HSV;
case ICC_Profile.icSigHlsData:
return ColorSpace.TYPE_HLS;
case ICC_Profile.icSigCmykData:
return ColorSpace.TYPE_CMYK;
// Note: There is no TYPE_* 10...
case ICC_Profile.icSigCmyData:
return ColorSpace.TYPE_CMY;
case ICC_Profile.icSigSpace2CLR:
return ColorSpace.TYPE_2CLR;
case ICC_Profile.icSigSpace3CLR:
return ColorSpace.TYPE_3CLR;
case ICC_Profile.icSigSpace4CLR:
return ColorSpace.TYPE_4CLR;
case ICC_Profile.icSigSpace5CLR:
return ColorSpace.TYPE_5CLR;
case ICC_Profile.icSigSpace6CLR:
return ColorSpace.TYPE_6CLR;
case ICC_Profile.icSigSpace7CLR:
return ColorSpace.TYPE_7CLR;
case ICC_Profile.icSigSpace8CLR:
return ColorSpace.TYPE_8CLR;
case ICC_Profile.icSigSpace9CLR:
return ColorSpace.TYPE_9CLR;
case ICC_Profile.icSigSpaceACLR:
return ColorSpace.TYPE_ACLR;
case ICC_Profile.icSigSpaceBCLR:
return ColorSpace.TYPE_BCLR;
case ICC_Profile.icSigSpaceCCLR:
return ColorSpace.TYPE_CCLR;
case ICC_Profile.icSigSpaceDCLR:
return ColorSpace.TYPE_DCLR;
case ICC_Profile.icSigSpaceECLR:
return ColorSpace.TYPE_ECLR;
case ICC_Profile.icSigSpaceFCLR:
return ColorSpace.TYPE_FCLR;
default:
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
}
}
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
static void fixProfile(ICC_Profile profile) {
profileCleaner.fixProfile(profile);
}
static boolean validationAltersProfileHeader() {
return profileCleaner.validationAltersProfileHeader();
}
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
static class sRGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
static class CIEXYZ {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
static class PYCC {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
static class GRAY {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
static class LINEAR_RGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -30,23 +30,17 @@
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.LRUHashMap;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
/**
* A helper class for working with ICC color profiles and color spaces.
@@ -83,9 +77,6 @@ public final class ColorSpaces {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
@@ -96,24 +87,17 @@ public final class ColorSpaces {
@SuppressWarnings("WeakerAccess")
public static final int CS_GENERIC_CMYK = 5001;
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
// Weak references to hold the color spaces while cached
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
// Cache for the latest used color spaces
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
static {
try {
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
}
catch (Throwable disasters) {
System.err.println("ICC Color Profile not properly activated due to the exception below.");
System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
disasters.printStackTrace();
}
// In case we didn't activate through SPI already
ProfileDeferralActivator.activateProfiles();
}
private ColorSpaces() {}
@@ -134,7 +118,7 @@ public final class ColorSpaces {
Validate.notNull(profile, "profile");
// Fix profile before lookup/create
profileCleaner.fixProfile(profile);
fixProfile(profile);
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
@@ -146,53 +130,20 @@ public final class ColorSpaces {
return getCachedOrCreateCS(profile, profileHeader);
}
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
byte[] data = profile.getData();
// Clear out preferred CMM, platform & creator, as these does not affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(data);
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
// ICC profile header is the first 128 bytes
return Arrays.copyOf(data, 128);
}
private static byte[] computeMD5(byte[] data) {
try {
return MessageDigest.getInstance("MD5").digest(data);
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
}
@@ -203,26 +154,36 @@ public final class ColorSpaces {
Key key = new Key(profileHeader);
synchronized (cache) {
ICC_ColorSpace cs = cache.get(key);
ICC_ColorSpace cs = getCachedCS(key);
if (cs == null) {
cs = new ICC_ColorSpace(profile);
validateColorSpace(cs);
// On LCMS, validation *alters* the profile header, need to re-generate key
key = profileCleaner.validationAltersProfileHeader()
? new Key(getProfileHeaderWithProfileId(cs.getProfile()))
: key;
cache.put(key, cs);
// On LCMS, validation *alters* the profile header, need to re-generate key
if (ColorProfiles.validationAltersProfileHeader()) {
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
}
}
return cs;
}
}
private static void validateColorSpace(final ICC_ColorSpace cs) {
private static ICC_ColorSpace getCachedCS(Key profileKey) {
synchronized (cache) {
return cache.get(profileKey);
}
}
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
return getCachedCS(new Key(profileHeader));
}
static void validateColorSpace(final ICC_ColorSpace cs) {
// Validate the color space, to avoid caching bad profiles/color spaces
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
@@ -233,89 +194,27 @@ public final class ColorSpaces {
}
/**
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
*/
@Deprecated
public static boolean isCS_sRGB(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
return ColorProfiles.isCS_sRGB(profile);
}
/**
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_GRAY
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
*/
@Deprecated
public static boolean isCS_GRAY(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
return ColorProfiles.isCS_GRAY(profile);
}
/**
* Tests whether an ICC color profile is 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>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* 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>
* </p>
*
* @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.
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
*/
@Deprecated
public static ICC_Profile validateProfile(final ICC_Profile profile) {
// Fix profile before validation
profileCleaner.fixProfile(profile);
validateColorSpace(new ICC_ColorSpace(profile));
return profile;
return ColorProfiles.validateProfile(profile);
}
/**
@@ -398,50 +297,6 @@ public final class ColorSpaces {
}
}
@SuppressWarnings("SameParameterValue")
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
private static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
private static final class Key {
private final byte[] data;
@@ -464,78 +319,4 @@ public final class ColorSpaces {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
}
}
// Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init
private static class sRGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
private static class CIEXYZ {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
private static class PYCC {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
private static class GRAY {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
private static class LINEAR_RGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
private static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
);
}
catch (SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.color;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.util.Locale;
import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider;
/**
* This class exists to force early invocation of {@code ProfileDeferralMgr.activateProfiles()},
* in an attempt to avoid JDK-6986863 and related bugs in Java < 17.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @see <a href="https://bugs.openjdk.java.net/browse/JDK-6986863">JDK-6986863</a>
*/
final class ProfileDeferralActivator {
static {
activateProfilesInternal();
}
private static void activateProfilesInternal() {
try {
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863 and friends.
// Relies on static initializer in ColorConvertOp to actually invoke ProfileDeferralMgr.activateProfiles()
Class.forName("java.awt.image.ColorConvertOp");
}
catch (Throwable disasters) {
System.err.println("ProfileDeferralMgr.activateProfiles() failed. ICC Color Profiles may not work properly, see stack trace below.");
System.err.println("For more information, see https://bugs.openjdk.java.net/browse/JDK-6986863");
System.err.println("Please upgrade to Java 17 or later where this bug is fixed, or ask your JRE provider to backport the fix.");
System.err.println();
System.err.println("If you can't update to Java 17, a possible workaround is to add");
System.err.println("\tClass.forName(\"java.awt.image.ColorConvertOp\");");
System.err.println("*early* in your application startup code, to force profile activation before profiles are accessed.");
System.err.println();
disasters.printStackTrace();
}
}
static void activateProfiles() {
// This method exists for other classes in the package to
// ensure this class' static initializer is run.
}
/**
* This is not a service provider, but exploits the SPI mechanism as a hook to force early profile activation.
*/
public static final class Spi extends ImageInputStreamSpi {
@Override public void onRegistration(ServiceRegistry registry, Class<?> category) {
activateProfiles();
deregisterProvider(registry, this, category);
}
@Override public String getDescription(Locale locale) {
return getClass().getName();
}
@Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) {
throw new UnsupportedOperationException();
}
}
}
@@ -158,19 +158,18 @@ public final class IIOUtil {
}
/**
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
*
* @param registry the registry to unregister from.
* @param provider the provider to unregister.
* @param category the category to unregister from.
*/
public static <T> void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class<T> category) {
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
registry.deregisterServiceProvider(category.cast(provider), category);
}
/**
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
*
* @param registry the registry to lookup from.
* @param providerClassName name of the provider class.
@@ -1,2 +1,4 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
# Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -0,0 +1,247 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.Assert.*;
public class ColorProfilesTest {
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
}
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorProfiles.isCS_sRGB(null);
}
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorProfiles.isCS_GRAY(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileNull() {
ColorProfiles.createProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileNull() throws IOException {
ColorProfiles.readProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawNull() {
ColorProfiles.createProfileRaw(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawNull() throws IOException {
ColorProfiles.readProfileRaw(null);
}
@Test
public void testCreateProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
assertArrayEquals(data, profileRaw.getData());
}
@Test
public void testReadProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertArrayEquals(data, profileRaw.getData());
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawBadData() {
ColorProfiles.createProfileRaw(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawBadData() throws IOException {
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileBadData() {
ColorProfiles.createProfile(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileBadData() throws IOException {
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
}
@Test
public void testCreateProfileBytesSame() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSame() throws IOException {
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileDifferent() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
assertNotSame(profile1, profile2);
}
@Test
public void testCreateProfileBytesSameAsCached() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileInputStreamSameAsCached() throws IOException {
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testCreateProfileBytesSameAsInternal() {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSameAsInternal() throws IOException {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
}
@@ -90,34 +90,6 @@ public class ColorSpacesTest {
assertTrue(created.isCS_sRGB());
}
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
}
@Test
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
@@ -166,11 +138,13 @@ public class ColorSpacesTest {
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
@@ -179,16 +153,19 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorSpaces.isCS_sRGB(null);
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
@@ -197,6 +174,7 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorSpaces.isCS_GRAY(null);
@@ -204,7 +182,7 @@ public class ColorSpacesTest {
@Test
public void testEqualHeadersDifferentProfile() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header...
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different)...
ICC_Profile profile1 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
@@ -0,0 +1,28 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import static org.mockito.Mockito.*;
public class ProfileDeferralActivatorTest {
@Test
public void testActivateProfiles() {
// Should just run with no exceptions...
ProfileDeferralActivator.activateProfiles();
}
@Test
public void testSpiRegistration() {
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
ServiceRegistry registry = mock(ServiceRegistry.class);
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
spi.onRegistration(registry, category);
verify(registry, only()).deregisterServiceProvider(spi, category);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -335,7 +336,7 @@ 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 (bogusAdobeDCT
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
if (DEBUG) {
@@ -614,7 +615,7 @@ public final class JPEGImageReader extends ImageReaderBase {
}
}
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) throws IOException {
// 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
@@ -630,7 +631,7 @@ public final class JPEGImageReader extends ImageReaderBase {
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
return ICC_Profile.getInstance(profileData);
return ColorProfiles.createProfile(profileData);
}
}
@@ -950,11 +951,7 @@ public final class JPEGImageReader extends ImageReaderBase {
return null;
}
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
int iccChunkDataSize = segment.data.length - segmentDataStart;
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
return readICCProfileSafe(stream, allowBadIndexes, iccSize, iccChunkDataSize);
return readICCProfileSafe(stream, allowBadIndexes);
}
else if (!segments.isEmpty()) {
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
@@ -989,9 +986,6 @@ public final class JPEGImageReader extends ImageReaderBase {
InputStream[] streams = new InputStream[count];
streams[badICC ? 0 : chunkNumber - 1] = stream;
int iccChunkDataSize = 0;
int iccSize = 0;
for (int i = 1; i < count; i++) {
Application segment = segments.get(i);
stream = new DataInputStream(segment.data());
@@ -1004,33 +998,20 @@ public final class JPEGImageReader extends ImageReaderBase {
int index = badICC ? i : chunkNumber - 1;
streams[index] = stream;
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
iccChunkDataSize += segment.data.length - segmentDataStart;
if (index == 0) {
iccSize = intFromBigEndian(segment.data, segmentDataStart);
}
}
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes, iccSize, iccChunkDataSize);
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
}
return null;
}
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile, final int iccSize, final int iccChunkDataSize) throws IOException {
if (iccSize < 0 || iccSize > iccChunkDataSize) {
processWarningOccurred(String.format("Truncated 'ICC_PROFILE' chunk(s), size: %d. Ignoring ICC profile.", iccSize));
return null;
}
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
try {
ICC_Profile profile = ICC_Profile.getInstance(stream);
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
}
catch (RuntimeException e) {
catch (IOException | 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()));
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -30,6 +30,7 @@
package com.twelvemonkeys.imageio.metadata.jpeg;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.psd.PSD;
@@ -42,7 +43,6 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
@@ -353,8 +353,8 @@ public final class JPEGSegmentUtil {
Directory psd = new PSDReader().read(stream);
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
if (iccEntry != null) {
ICC_ColorSpace colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance((byte[]) iccEntry.getValue()));
System.err.println("colorSpace: " + colorSpace);
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
System.err.println("ICC Profile: " + profile);
}
System.err.println("PSD: " + psd);
System.err.println(TIFFReader.HexDump.dump(segment.data));
@@ -30,11 +30,21 @@
package com.twelvemonkeys.imageio.metadata.xmp;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
@@ -43,13 +53,11 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.*;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
/**
* XMPReader
@@ -67,10 +75,9 @@ public final class XMPReader extends MetadataReader {
public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
DocumentBuilderFactory factory = createDocumentBuilderFactory();
// TODO: Consider parsing using SAX?
// TODO: Determine encoding and parse using a Reader...
// TODO: Refactor scanner to return inputstream?
@@ -79,9 +86,6 @@ public final class XMPReader extends MetadataReader {
builder.setErrorHandler(new DefaultHandler());
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
// serializer.serialize(document);
String toolkit = getToolkit(document);
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
@@ -92,10 +96,33 @@ public final class XMPReader extends MetadataReader {
throw new IIOException(e.getMessage(), e);
}
catch (ParserConfigurationException e) {
throw new RuntimeException(e); // TODO: Or IOException?
throw new RuntimeException(e);
}
}
private DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
// Security: Disable XInclude & expanding entity references ("bombs"), not needed for XMP
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
// Security: Enable "secure processing", to prevent DoS attacks
factory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// Security: Remove possibility to access external DTDs or Schema, not needed for XMP
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
// Security: Disable loading of external DTD and entities, not needed for XMP
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
return factory;
}
private String getToolkit(Document document) {
NodeList xmpmeta = document.getElementsByTagNameNS(XMP.NS_X, "xmpmeta");
@@ -109,7 +136,7 @@ public final class XMPReader extends MetadataReader {
}
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes, String toolkit) {
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
Map<String, List<Entry>> subdirs = new LinkedHashMap<>();
for (Node desc : asIterable(pNodes)) {
if (desc.getParentNode() != pParentNode) {
@@ -127,7 +154,7 @@ public final class XMPReader extends MetadataReader {
// Lookup
List<Entry> dir = subdirs.get(node.getNamespaceURI());
if (dir == null) {
dir = new ArrayList<Entry>();
dir = new ArrayList<>();
subdirs.put(node.getNamespaceURI(), dir);
}
@@ -139,7 +166,7 @@ public final class XMPReader extends MetadataReader {
else {
// TODO: This method contains loads of duplication an should be cleaned up...
// Support attribute short-hand syntax
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<String, List<Entry>>();
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<>();
parseAttributesForKnownElements(subsubdirs, node);
@@ -161,7 +188,7 @@ public final class XMPReader extends MetadataReader {
}
}
List<Directory> entries = new ArrayList<Directory>(subdirs.size());
List<Directory> entries = new ArrayList<>(subdirs.size());
// TODO: Should we still allow asking for a subdirectory by item id?
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
@@ -179,7 +206,7 @@ public final class XMPReader extends MetadataReader {
private RDFDescription parseAsResource(Node node) {
// See: http://www.w3.org/TR/REC-rdf-syntax/#section-Syntax-parsetype-resource
List<Entry> entries = new ArrayList<Entry>();
List<Entry> entries = new ArrayList<>();
for (Node child : asIterable(node.getChildNodes())) {
if (child.getNodeType() != Node.ELEMENT_NODE) {
@@ -204,7 +231,7 @@ public final class XMPReader extends MetadataReader {
List<Entry> dir = subdirs.get(attr.getNamespaceURI());
if (dir == null) {
dir = new ArrayList<Entry>();
dir = new ArrayList<>();
subdirs.put(attr.getNamespaceURI(), dir);
}
@@ -216,7 +243,7 @@ public final class XMPReader extends MetadataReader {
for (Node child : asIterable(node.getChildNodes())) {
if (XMP.NS_RDF.equals(child.getNamespaceURI()) && "Alt".equals(child.getLocalName())) {
// Support for <rdf:Alt><rdf:li> -> return a Map<String, Object> keyed on xml:lang
Map<String, Object> alternatives = new LinkedHashMap<String, Object>();
Map<String, Object> alternatives = new LinkedHashMap<>();
for (Node alternative : asIterable(child.getChildNodes())) {
if (XMP.NS_RDF.equals(alternative.getNamespaceURI()) && "li".equals(alternative.getLocalName())) {
NamedNodeMap attributes = alternative.getAttributes();
@@ -230,7 +257,7 @@ public final class XMPReader extends MetadataReader {
else if (XMP.NS_RDF.equals(child.getNamespaceURI()) && ("Seq".equals(child.getLocalName()) || "Bag".equals(child.getLocalName()))) {
// Support for <rdf:Seq><rdf:li> -> return array
// Support for <rdf:Bag><rdf:li> -> return array/unordered collection (how can a serialized collection not have order?)
List<Object> seq = new ArrayList<Object>();
List<Object> seq = new ArrayList<>();
for (Node sequence : asIterable(child.getChildNodes())) {
if (XMP.NS_RDF.equals(sequence.getNamespaceURI()) && "li".equals(sequence.getLocalName())) {
@@ -30,26 +30,32 @@
package com.twelvemonkeys.imageio.metadata.xmp;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import org.junit.Test;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
/**
* XMPReaderTest
@@ -483,4 +489,69 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/PixelYDimension"), hasValue("550"));
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA"));
}
@Test(timeout = 1500L)
public void testNoExternalRequest() throws Exception {
// TODO: Use dynamic port?
try (HTTPServer server = new HTTPServer(7777)) {
try {
createReader().read(getResourceAsIIS("/xmp/xmp-jpeg-xxe.xml"));
} catch (IOException ioe) {
if (ioe.getMessage().contains("501")) {
throw new AssertionError("Reading should not cause external requests", ioe);
}
// Any other exception is a bug (but might happen if the parser does not support certain features)
throw ioe;
}
}
}
private static class HTTPServer implements AutoCloseable {
private final ServerSocket server;
private final Thread thread;
HTTPServer(int port) throws IOException {
server = new ServerSocket(port, 1);
thread = new Thread(new Runnable() {
@Override public void run() {
serve();
}
});
thread.start();
}
private void serve() {
try {
Socket client = server.accept();
// Get the input stream, don't care about the request
try (InputStream inputStream = client.getInputStream()) {
while (inputStream.available() > 0) {
if (inputStream.read() == -1) {
break;
}
}
// Answer with 501, this will cause the client to throw IOException
try (OutputStream outputStream = client.getOutputStream()) {
outputStream.write("HTTP/1.0 501 Not Implemented\r\n\r\n".getBytes(StandardCharsets.UTF_8));
}
}
}
catch (IOException e) {
if (server.isClosed() && e instanceof SocketException) {
// Socket closed due to server close, all good
return;
}
throw new RuntimeException(e);
}
}
@Override public void close() throws Exception {
server.close();
thread.join(); // It's advised against throwing InterruptedException here, but this is not production code...
}
}
}
@@ -0,0 +1,35 @@
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><!DOCTYPE root [<!ENTITY % ext SYSTEM 'http://localhost:7777/xxx'> %ext;]>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.16'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about=''
xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'>
<xmpMM:InstanceID>xmp.iid:7EDC21BF-371B-4189-90AF-C83A54A6A190</xmpMM:InstanceID>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -30,6 +30,7 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.stream.ImageInputStream;
@@ -55,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
profile = ICC_Profile.getInstance(stream);
profile = ColorProfiles.readProfileRaw(stream);
}
}
@@ -59,8 +59,8 @@ import java.util.Collections;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification<a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
*/
public final class PSDImageWriter extends ImageWriterBase {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -30,14 +30,14 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.lang.Validate;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import com.twelvemonkeys.lang.Validate;
/**
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
*
@@ -198,6 +198,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
try {
decodeRow();
}
catch (ArrayIndexOutOfBoundsException e) {
// Mask the AIOOBE as an IOException
throw new IOException("Malformed CCITT stream", e);
}
catch (EOFException e) {
// TODO: Rewrite to avoid throw/catch for normal flow...
if (decodedLength != 0) {
@@ -30,48 +30,10 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static java.util.Arrays.asList;
import java.awt.*;
import java.awt.color.CMMException;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -99,6 +61,35 @@ import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.CMMException;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static java.util.Arrays.asList;
/**
* ImageReader implementation for Aldus/Adobe Tagged Image File Format (TIFF).
* <p>
@@ -2503,24 +2494,12 @@ public final class TIFFImageReader extends ImageReaderBase {
return value;
}
private ICC_Profile getICCProfile() throws IOException {
private ICC_Profile getICCProfile() {
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
if (entry != null) {
byte[] value = (byte[]) entry.getValue();
// Validate ICC profile size vs actual value size
int size = (value[0] & 0xff) << 24 | (value[1] & 0xff) << 16 | (value[2] & 0xff) << 8 | (value[3] & 0xff);
if (size < 0 || size > value.length) {
processWarningOccurred("Ignoring truncated ICC profile: Bad ICC profile size (" + size + ")");
return null;
}
try {
// WEIRDNESS: Reading profile from InputStream is somehow more compatible
// than reading from byte array (chops off extra bytes + validates profile).
ICC_Profile profile = ICC_Profile.getInstance(new ByteArrayInputStream(value));
return ColorSpaces.validateProfile(profile);
return ColorProfiles.createProfile((byte[]) entry.getValue());
}
catch (CMMException | IllegalArgumentException e) {
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
@@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
@@ -855,7 +855,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
// * is not sRGB (assuming sRGB to be the default RGB interpretation), and
// * is not gray scale (assuming photometric either BlackIsZero or WhiteIsZero)
ColorSpace colorSpace = colorModel.getColorSpace();
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorSpaces.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorProfiles.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
}
}
@@ -253,7 +253,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeMissingRows() throws IOException {
// See https://github.com/haraldk/TwelveMonkeys/pull/225 and https://github.com/haraldk/TwelveMonkeys/issues/232
InputStream inputStream = getClass().getResourceAsStream("/tiff/ccitt_tolessrows.tif");
InputStream inputStream = getResourceAsStream("/tiff/ccitt_tolessrows.tif");
// Skip until StripOffsets: 8
for (int i = 0; i < 8; i++) {
@@ -299,7 +299,7 @@ public class CCITTFaxDecoderStreamTest {
public void testMoreChangesThanColumnsFile() throws IOException {
// See https://github.com/haraldk/TwelveMonkeys/issues/328
// 26 changes on 24 columns: H0w1b, H1w1b, ..., H1w0b
InputStream stream = getClass().getResourceAsStream("/tiff/ccitt-too-many-changes.tif");
InputStream stream = getResourceAsStream("/tiff/ccitt-too-many-changes.tif");
// Skip bytes before StripOffsets: 86
for (int i = 0; i < 86; i++) {
@@ -336,7 +336,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testG3AOE() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("/tiff/ccitt/g3aoe.tif");
InputStream inputStream = getResourceAsStream("/tiff/ccitt/g3aoe.tif");
// Skip until StripOffsets: 8
for (int i = 0; i < 8; i++) {
@@ -353,4 +353,17 @@ public class CCITTFaxDecoderStreamTest {
byte[] bytes = new byte[216 * 1168]; // 1728 x 1168 pixel, 1 bpp => 216 bytes * 1168
new DataInputStream(stream).readFully(bytes);
}
@SuppressWarnings("StatementWithEmptyBody")
@Test(expected = IOException.class)
public void testAIOBEInCorruptStreamShouldThrowIOException() throws IOException {
// From #645
try (InputStream ccittFaxDecoderStream = new CCITTFaxDecoderStream(getResourceAsStream("/ccitt/645.ccitt"), 7, 4, 0, false)) {
while(ccittFaxDecoderStream.read() != -1); // Just read until the end
}
}
private InputStream getResourceAsStream(String name) {
return getClass().getResourceAsStream(name);
}
}
@@ -0,0 +1,7 @@
δΨ]L–Γ Hΰ„t\eΡΔGΘDn0£ά―θ‘σ™βθΎGFtGGΔmΡ|‘Ρβρ6ήGD|Η
N΄TJΝΕΡ„1Μ’²θΊH!eΒ"Ψ4Α":DqC”;#Άρ΄B"&$Κ@|6)Θ&V{ ΄Aq‡H·!;ς‡P‚Ϊ&8„‰c‚Π"θ$&΄GD|ΌEZ(#TΕ^|[`†96σPPα ‚¤Δ–1²?SHpdq,!Ζ!„“#°A$
@Η;r±AΔDDπΑ BvR
΅Α$G @… BVw "c¤%<e~Θθ(s1h£ΐ¤#/ΠQ.b0Δ$
ΒD8@…""Ct0„Y|Έt£Κph$‰Η;pAF
ώ":8Πΐν΅ε8bΈ±$9Η<φβΎ" ‰qRΘΰΉ<΅Θ8βqΒ ΠA28Λ ¬¨EΠEAP!a5²9Dq—FdG9NS1HJ›¤&β1†nM(DDB*HωΡΕH!)α>Pι>% ηΓ‘,ΓΟ
Λ”X+—"‡ ": ΔYΈ¤‘!Ιβ’rpcƒ.lL8"8.G0ΖH K‰C°ΌDp@„γβ'
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -32,6 +32,7 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
@@ -287,7 +288,7 @@ final class WebPImageReader extends ImageReaderBase {
long chunkStart = imageInput.getStreamPosition();
if (nextChunk == WebP.CHUNK_ICCP) {
iccProfile = ICC_Profile.getInstance(IIOUtil.createStreamAdapter(imageInput, chunkLength));
iccProfile = ColorProfiles.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
}
else {
processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk)));
@@ -365,7 +366,7 @@ final class WebPImageReader extends ImageReaderBase {
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
readHeader(imageIndex);
if (iccProfile != null && !ColorSpaces.isCS_sRGB(iccProfile)) {
if (iccProfile != null && !ColorProfiles.isCS_sRGB(iccProfile)) {
ICC_ColorSpace colorSpace = ColorSpaces.createColorSpace(iccProfile);
int[] bandOffsets = header.containsALPH ? new int[] {0, 1, 2, 3} : new int[] {0, 1, 2};
return ImageTypeSpecifiers.createInterleaved(colorSpace, bandOffsets, DataBuffer.TYPE_BYTE, header.containsALPH, false);
@@ -525,7 +526,7 @@ final class WebPImageReader extends ImageReaderBase {
if (!iccProfile.equals(destinationProfile)) {
if (DEBUG) {
System.err.println("Converting from " + iccProfile + " to " + (ColorSpaces.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
System.err.println("Converting from " + iccProfile + " to " + (ColorProfiles.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
}
WritableRaster raster = colorModel.hasAlpha()
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
<packaging>pom</packaging>
<name>Twelvemonkeys</name>
@@ -86,7 +86,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>HEAD</tag>
<tag>twelvemonkeys-3.8.0</tag>
</scm>
<properties>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0-SNAPSHOT</version>
<version>3.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -60,7 +60,7 @@
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<version>1.4</version>
<scope>provided</scope>
</dependency>