diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java index 2d3214ea..8e3964c7 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java @@ -39,6 +39,7 @@ 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.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; @@ -96,6 +97,8 @@ public final class ColorSpaces { @SuppressWarnings("WeakerAccess") public static final int CS_GENERIC_CMYK = 5001; + static final int ICC_PROFILE_HEADER_SIZE = 128; + // Weak references to hold the color spaces while cached private static WeakReference adobeRGB1998 = new WeakReference<>(null); private static WeakReference genericCMYK = new WeakReference<>(null); @@ -104,16 +107,8 @@ public final class ColorSpaces { private static final Map cache = new LRUHashMap<>(10); 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() {} @@ -148,9 +143,11 @@ public final class ColorSpaces { private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) { // Get *entire profile data*... :-/ - byte[] data = profile.getData(); + return getProfileHeaderWithProfileId(profile.getData()); + } - // Clear out preferred CMM, platform & creator, as these does not affect the profile in any way + private static byte[] getProfileHeaderWithProfileId(byte[] data) { + // 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(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0); @@ -167,7 +164,7 @@ public final class ColorSpaces { System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length); // ICC profile header is the first 128 bytes - return Arrays.copyOf(data, 128); + return Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE); } private static byte[] computeMD5(byte[] data) { @@ -203,25 +200,35 @@ 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 (profileCleaner.validationAltersProfileHeader()) { + cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs); + } } return cs; } } + private static ICC_ColorSpace getCachedCS(Key profileKey) { + synchronized (cache) { + return cache.get(profileKey); + } + } + + private static ICC_ColorSpace getCachedCS(final byte[] profileHeader) { + return getCachedCS(new Key(profileHeader)); + } + private 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 @@ -318,6 +325,137 @@ public final class ColorSpaces { return profile; } + public static ICC_Profile readProfileRaw(final InputStream input) throws IOException { + return ICC_Profile.getInstance(input); + } + + public static ICC_Profile readProfile(final InputStream input) throws IOException { + // TODO: Implement this smarter? + // Could read the header 128 bytes, get size + magic, then read read rest into array and feed the byte[] method... + ICC_Profile profile = ICC_Profile.getInstance(input); + + if (profile == null) { + throw new IllegalArgumentException("Invalid ICC Profile Data"); + } + + return createProfile(profile.getData()); + } + + public static ICC_Profile createProfileRaw(final byte[] input) { + try { + return readProfileRaw(new ByteArrayInputStream(input)); + } + catch (IOException e) { + throw new IllegalArgumentException("Invalid ICC Profile Data", e); + } + } + + public static ICC_Profile createProfile(final 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, length < 128: " + input.length); + } + int size = intBigEndian(input, 0); + if (size < 0 || size > input.length) { + throw new IllegalArgumentException("Truncated ICC profile, length < " + size + ": " + input.length); + } + + if (input[36] != 'a' || input[37] != 'c' || input[38] != 's' || input[39] != 'p') { + throw new IllegalArgumentException("Not an ICC profile, missing file signature"); + } + + // Look up in cache before returning, these are already validated + byte[] profileHeader = getProfileHeaderWithProfileId(input); + int csType = getCsType(profileHeader); + + ICC_ColorSpace internal = getInternalCS(csType, profileHeader); + if (internal != null) { + return internal.getProfile(); + } + + ICC_ColorSpace cached = getCachedCS(profileHeader); + if (cached != null) { + return cached.getProfile(); + } + + // WEIRDNESS: 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 + byte[] profileBytes = input.length == size ? input : Arrays.copyOf(input, size); + ICC_Profile profile = ICC_Profile.getInstance(profileBytes); + + // We'll validate & cache by creating a color space and returning its profile... + // TODO: Rewrite with separate cache for profiles... + return createColorSpace(profile).getProfile(); + } + + 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); + + // TODO: Wonder why they didn't just use the sig as type, when there is obviously a 1:1 mapping... + + 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? + } + } + /** * Returns the color space specified by the given color space constant. *

@@ -465,7 +603,7 @@ public final class ColorSpaces { } } - // Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init + // Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init private static class sRGB { private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB)); } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ProfileDeferralActivator.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ProfileDeferralActivator.java new file mode 100644 index 00000000..1fcbce42 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ProfileDeferralActivator.java @@ -0,0 +1,67 @@ +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 Harald Kuhr + * @see JDK-6986863 + */ +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(); + } + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java index 4084a442..f8e32f31 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java @@ -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 void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class 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. diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java index 2520a968..20634554 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java @@ -35,6 +35,7 @@ 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 static org.junit.Assert.*; @@ -204,7 +205,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")); @@ -215,4 +216,69 @@ public class ColorSpacesTest { assertNotSame(cs1, cs2); } + + @Test + public void testReadProfileBytesSame() throws IOException { + ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")); + ICC_Profile profile1 = ColorSpaces.createProfile(profile.getData()); + ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData()); + + assertEquals(profile1, profile2); + assertSame(profile1, profile2); + } + + @Test + public void testReadProfileInputStreamSame() throws IOException { + ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")); + ICC_Profile profile2 = ColorSpaces.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 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")); + ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc")); + + assertNotSame(profile1, profile2); + } + + @Test + public void testReadProfileBytesSameAsCached() throws IOException { + ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")); + ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile); + ICC_Profile profile2 = ColorSpaces.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 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")); + + assertEquals(cs1.getProfile(), profile2); + assertSame(cs1.getProfile(), profile2); + } + + @Test + public void testReadProfileBytesSameAsInternal() { + ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB); + ICC_Profile profile2 = ColorSpaces.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 = ColorSpaces.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData())); + + assertEquals(profile1, profile2); + assertSame(profile1, profile2); + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 19540366..80a9883e 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -614,7 +614,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 +630,7 @@ public final class JPEGImageReader extends ImageReaderBase { intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first - return ICC_Profile.getInstance(profileData); + return ColorSpaces.createProfile(profileData); } } @@ -954,7 +954,7 @@ public final class JPEGImageReader extends ImageReaderBase { 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... @@ -1012,25 +1012,20 @@ public final class JPEGImageReader extends ImageReaderBase { } } - 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); + ICC_Profile profile = ColorSpaces.readProfileRaw(stream); // NOTE: Need to ensure we have a display profile *before* validating, for the caching to work return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile)); } - 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())); diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index a788d3d0..f2f0c637 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.metadata.jpeg; +import com.twelvemonkeys.imageio.color.ColorSpaces; 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 = ColorSpaces.createProfileRaw((byte[]) iccEntry.getValue()); + System.err.println("ICC Profile: " + profile); } System.err.println("PSD: " + psd); System.err.println(TIFFReader.HexDump.dump(segment.data)); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java index dd8f8e25..504a9c54 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.color.ColorSpaces; 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 = ColorSpaces.readProfile(stream); } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 0d82268c..f4249cfb 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -30,45 +30,6 @@ 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; @@ -99,6 +60,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). *

@@ -2507,20 +2497,8 @@ public final class TIFFImageReader extends ImageReaderBase { 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 ColorSpaces.createProfile((byte[]) entry.getValue()); } catch (CMMException | IllegalArgumentException e) { processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage()); diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java index 2aeca185..8a8aaeec 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java @@ -287,7 +287,7 @@ final class WebPImageReader extends ImageReaderBase { long chunkStart = imageInput.getStreamPosition(); if (nextChunk == WebP.CHUNK_ICCP) { - iccProfile = ICC_Profile.getInstance(IIOUtil.createStreamAdapter(imageInput, chunkLength)); + iccProfile = ColorSpaces.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength)); } else { processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk)));