Added caching of AdobeRGB1998 and Generic CMYK profiles for use in getInstance methods.

Added debug info.
Refactored profile reading slightly.
This commit is contained in:
Harald Kuhr 2011-02-22 15:25:19 +01:00
parent ba4ff3dc45
commit 6cc97e3721

View File

@ -39,6 +39,7 @@ import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile; import java.awt.color.ICC_Profile;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
@ -68,6 +69,8 @@ import java.util.Properties;
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$ * @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/ */
public final class ColorSpaces { public final class ColorSpaces {
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions // 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. */ /** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
@ -76,6 +79,11 @@ public final class ColorSpaces {
/** A best-effort "generic" CMYK color space. Either read from disk or built-in. */ /** A best-effort "generic" CMYK color space. Either read from disk or built-in. */
public static final int CS_GENERIC_CMYK = 5001; public static final int CS_GENERIC_CMYK = 5001;
// Weak references to hold the color spaces while cached
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<ICC_Profile>(null);
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<ICC_Profile>(null);
// Cache for the latest used color spaces
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<Key, ICC_ColorSpace>(10); private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<Key, ICC_ColorSpace>(10);
private ColorSpaces() {} private ColorSpaces() {}
@ -122,16 +130,16 @@ public final class ColorSpaces {
if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) { if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB); return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
} }
if (profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) { else if (profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY); return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
} }
if (profile.getColorSpaceType() == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) { else if (profile.getColorSpaceType() == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC); return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
} }
if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) { else if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
} }
if (profile.getColorSpaceType() == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) { else if (profile.getColorSpaceType() == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
} }
@ -178,6 +186,7 @@ public final class ColorSpaces {
// This is particularly annoying, as the byte copying isn't really necessary, // This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color // except the getRenderingIntent method is package protected in java.awt.color
byte[] data = profile.getData(ICC_Profile.icSigHead); byte[] data = profile.getData(ICC_Profile.icSigHead);
return data[ICC_Profile.icHdrRenderingIntent] != 0; return data[ICC_Profile.icHdrRenderingIntent] != 0;
} }
@ -195,7 +204,7 @@ public final class ColorSpaces {
* @see ColorSpaces#CS_GENERIC_CMYK * @see ColorSpaces#CS_GENERIC_CMYK
*/ */
public static ColorSpace getColorSpace(int colorSpace) { public static ColorSpace getColorSpace(int colorSpace) {
// TODO: Use internal cache for AdobeRGB and CMYK! (needs mapping between ID and Key...) ICC_Profile profile;
switch (colorSpace) { switch (colorSpace) {
// Default cases for convenience // Default cases for convenience
@ -207,38 +216,49 @@ public final class ColorSpaces {
return ColorSpace.getInstance(colorSpace); return ColorSpace.getInstance(colorSpace);
case CS_ADOBE_RGB_1998: case CS_ADOBE_RGB_1998:
try { profile = adobeRGB1998.get();
String profile = Profiles.MAP.getProperty("ADOBE_RGB_1998");
return createColorSpace(ICC_Profile.getInstance(profile));
}
catch (IOException ignore) {
}
if (profile == null) {
// Try to get system default or user-defined profile
profile = readProfileFromPath(Profiles.getPath("ADOBE_RGB_1998"));
if (profile == null) {
// Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile, // Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile,
// identical for all practical purposes // identical for all practical purposes
InputStream stream = ColorSpaces.class.getResourceAsStream("/profiles/ClayRGB1998.icc"); profile = readProfileFromClasspathResource("/profiles/ClayRGB1998.icc");
try {
return createColorSpace(ICC_Profile.getInstance(stream)); if (profile == null) {
// Should never happen given we now bundle fallback profile...
throw new IllegalStateException("Could not read AdobeRGB1998 profile");
} }
catch (IOException ignore) {
}
finally {
FileUtil.close(stream);
} }
// Should never happen given we now bundle the profile... adobeRGB1998 = new WeakReference<ICC_Profile>(profile);
throw new RuntimeException("Could not read AdobeRGB1998 profile"); }
return createColorSpace(profile);
case CS_GENERIC_CMYK: case CS_GENERIC_CMYK:
try { profile = genericCMYK.get();
String profile = Profiles.MAP.getProperty("GENERIC_CMYK");
return createColorSpace(ICC_Profile.getInstance(profile)); if (profile == null) {
} // Try to get system default or user-defined profile
catch (IOException ignore) { profile = readProfileFromPath(Profiles.getPath("GENERIC_CMYK"));
if (profile == null) {
if (DEBUG) {
System.out.println("Using fallback profile");
} }
// Fall back to generic CMYK ColorSpace, which is *insanely slow* using ColorConvertOp... :-P // Fall back to generic CMYK ColorSpace, which is *insanely slow* using ColorConvertOp... :-P
return CMYKColorSpace.getInstance(); return CMYKColorSpace.getInstance();
}
genericCMYK = new WeakReference<ICC_Profile>(profile);
}
return createColorSpace(profile);
default: default:
// TODO: Allow more customizable models based on the config file? // TODO: Allow more customizable models based on the config file?
@ -247,6 +267,50 @@ public final class ColorSpaces {
throw new IllegalArgumentException(String.format("Unsupported color space: %s", colorSpace)); throw new IllegalArgumentException(String.format("Unsupported color space: %s", colorSpace));
} }
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 profilePath from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
private static final class Key { private static final class Key {
private final byte[] data; private final byte[] data;
@ -283,9 +347,9 @@ public final class ColorSpaces {
} }
private static class Profiles { private static class Profiles {
static final Properties MAP = loadProfiles(Platform.os()); private static final Properties PROFILES = loadProfiles(Platform.os());
private static Properties loadProfiles(Platform.OperatingSystem os) { private static Properties loadProfiles(final Platform.OperatingSystem os) {
Properties systemDefaults; Properties systemDefaults;
try { try {
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os); systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os);
@ -305,7 +369,16 @@ public final class ColorSpaces {
catch (IOException ignore) { catch (IOException ignore) {
} }
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles; return profiles;
} }
public static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
} }
} }