diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java similarity index 61% rename from imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java rename to imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java index 54715827..2223ec77 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java @@ -1,17 +1,17 @@ /* - * Copyright (c) 2008, Harald Kuhr + * Copyright (c) 2011, 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 "TwelveMonkeys" nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. + * 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 "TwelveMonkeys" 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 @@ -26,25 +26,24 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.twelvemonkeys.imageio.plugins.psd; +package com.twelvemonkeys.imageio.color; import java.awt.color.ColorSpace; /** - * CMYKColorSpace + * A fallback CMYK ColorSpace, in case none can be read from disk. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: CMYKColorSpace.java,v 1.0 Apr 30, 2008 1:38:13 PM haraldk Exp$ */ -// TODO: Move to com.twelvemonkeys.image? -// TODO: Read a ICC CMYK profile from classpath resource (from ECI)? ISO coated? final class CMYKColorSpace extends ColorSpace { static final ColorSpace INSTANCE = new CMYKColorSpace(); + final ColorSpace sRGB = getInstance(CS_sRGB); - CMYKColorSpace() { + private CMYKColorSpace() { super(ColorSpace.TYPE_CMYK, 4); } @@ -58,6 +57,7 @@ final class CMYKColorSpace extends ColorSpace { (1 - colorvalue[1]) * (1 - colorvalue[3]), (1 - colorvalue[2]) * (1 - colorvalue[3]) }; + // TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs // return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue)); } @@ -73,33 +73,35 @@ final class CMYKColorSpace extends ColorSpace { // Convert to CMYK values return new float[] {(c - k), (m - k), (y - k), k}; -/* -http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html -(Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or -adapt the following matrix.) + /* + http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html -Step 1: RGB to CMY + (Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or + adapt the following matrix.) -| C | | 1 | | R | -| M | = | 1 | - | G | -| Y | | 1 | | B | + Step 1: RGB to CMY -Step 2: CMY to CMYK + | C | | 1 | | R | + | M | = | 1 | - | G | + | Y | | 1 | | B | -| C' | | C | | min(C,M,Y) | -| M' | | M | | min(C,M,Y) | -| Y' | = | Y | - | min(C,M,Y) | -| K' | | min(C,M,Y) | | 0 | + Step 2: CMY to CMYK -Easier to calculate if K' is calculated first, because K' = min(C,M,Y): + | C' | | C | | min(C,M,Y) | + | M' | | M | | min(C,M,Y) | + | Y' | = | Y | - | min(C,M,Y) | + | K' | | min(C,M,Y) | | 0 | -| C' | | C | | K' | -| M' | | M | | K' | -| Y' | = | Y | - | K' | -| K' | | K'| | 0 | - */ -// return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue)); + Easier to calculate if K' is calculated first, because K' = min(C,M,Y): + + | C' | | C | | K' | + | M' | | M | | K' | + | Y' | = | Y | - | K' | + | K' | | K'| | 0 | + */ + + // return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue)); } public float[] toCIEXYZ(float[] colorvalue) { 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 new file mode 100644 index 00000000..2deffc11 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2011, 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 "TwelveMonkeys" 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 OWNER 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.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.util.Arrays; + +/** + * A helper class for working with ICC color profiles and color spaces. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$ + */ +public final class ColorSpaces { + // 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. */ + public static final int CS_ADOBE_RGB_98 = 5000; + + /** A best-effort "generic" CMYK color space. Either read from disk or built-in. */ + public static final int CS_GENERIC_CMYK = 5001; + + private static final LRUHashMap cache = new LRUHashMap(10); + + private ColorSpaces() {} + + /** + * Creates an ICC color space from the given ICC color profile. + *

+ * For standard Java color spaces, the built-in instance is returned. + * Otherwise, color spaces are looked up from cache and created on demand. + * + * @param profile the ICC color profile. May not be {@code null}. + * @return an ICC color space + * @throws IllegalArgumentException if {@code profile} is {@code null} + */ + public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) { + Validate.notNull(profile, "profile"); + + byte[] profileHeader = profile.getData(ICC_Profile.icSigHead); + + ICC_ColorSpace cs = getInternalCS(profile, profileHeader); + if (cs != null) { + return cs; + } + + // Special case for color profiles with rendering intent != 0, see isOffendingColorProfile method + // NOTE: Rendering intent is really a 4 byte value, but legal values are 0-3 (ICC1v42_2006_05_1.pdf, 7.2.15, p. 19) + if (profileHeader[ICC_Profile.icHdrRenderingIntent] != 0) { + profileHeader[ICC_Profile.icHdrRenderingIntent] = 0; + + // Test again if this is an internal CS + cs = getInternalCS(profile, profileHeader); + if (cs != null) { + return cs; + } + + // Fix profile + profile.setData(ICC_Profile.icSigHead, profileHeader); + } + + return getCachedCS(profile, profileHeader); + } + + private static ICC_ColorSpace getInternalCS(final ICC_Profile profile, final byte[] profileHeader) { + if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) { + return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB); + } + if (profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) { + return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY); + } + if (profile.getColorSpaceType() == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) { + return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC); + } + if (profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) { + return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); + } + if (profile.getColorSpaceType() == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) { + return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); + } + + return null; + } + + private static ICC_ColorSpace getCachedCS(final ICC_Profile profile, final byte[] profileHeader) { + Key key = new Key(profileHeader); + + synchronized (cache) { + ICC_ColorSpace cs = cache.get(key); + + if (cs == null) { + cs = new ICC_ColorSpace(profile); + cache.put(key, cs); + } + + return cs; + } + } + + /** + * Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}. + *

+ * + * 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}. + * + * + * @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} + */ + public static boolean isOffendingColorProfile(final ICC_Profile profile) { + Validate.notNull(profile, "profile"); + + // NOTE: + // Several embedded ICC color profiles are non-compliant with Java 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.. ;-) + + // This is particularly annoying, as the byte copying isn't really necessary, + // except the getRenderingIntent method is package protected in java.awt.color + byte[] data = profile.getData(ICC_Profile.icSigHead); + return data[ICC_Profile.icHdrRenderingIntent] != 0; + } + + // TODO: Use internal cache (needs mapping between ID and Key...) + // TODO: Allow system-property/config file on class path to configure location of color profiles + // TODO: Document how to download, install and configure Adobe color profiles or other profiles + + /** + * Gets or creates the color space specified by the given color space constant. + *

+ * For standard Java color spaces, the built-in instance is returned. + * Otherwise, color spaces are looked up from cache and created on demand. + * + * @param colorSpace the color space constant. + * @return the {@link ColorSpace} specified by the color space constant. + * @throws IllegalArgumentException if {@code colorSpace} is not one of the defined color spaces ({@code CS_*}). + * @see ColorSpace + * @see ColorSpaces#CS_ADOBE_RGB_98 + * @see ColorSpaces#CS_GENERIC_CMYK + */ + public static ColorSpace getColorSpace(int colorSpace) { + switch (colorSpace) { + // Default cases for convenience + case ColorSpace.CS_sRGB: + case ColorSpace.CS_GRAY: + case ColorSpace.CS_PYCC: + case ColorSpace.CS_CIEXYZ: + case ColorSpace.CS_LINEAR_RGB: + return ColorSpace.getInstance(colorSpace); + + case CS_ADOBE_RGB_98: + // TODO: Read profile specified by config file instead of hard coded + try { + // This works for OS X only + return createColorSpace(ICC_Profile.getInstance("/System/Library/ColorSync/Profiles/AdobeRGB1998.icc")); + } + catch (IOException ignore) { + } + + // Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile + InputStream stream = ColorSpaces.class.getResourceAsStream("/profiles/ClayRGB1998.icc"); + try { + return createColorSpace(ICC_Profile.getInstance(stream)); + } + catch (IOException ignore) { + } + finally { + FileUtil.close(stream); + } + + // Should never happen... + throw new RuntimeException("Could not read AdobeRGB1998 profile"); + + case CS_GENERIC_CMYK: + // TODO: Read profile specified by config file instead of hard coded + // TODO: C:\Windows\System32\spool\drivers\color\RSWOP.icm for Windows Vista? + try { + // This works for OS X only + return createColorSpace(ICC_Profile.getInstance("/System/Library/ColorSync/Profiles/Generic CMYK Profile.icc")); + } + catch (IOException ignore) { + } + + // Fall back to generic CMYK ColorSpace, which is *insanely slow* using ColorConvertOp... :-P + return CMYKColorSpace.getInstance(); + default: + + // TODO: Allow more customizable models based on the config file? + } + + throw new IllegalArgumentException(String.format("Unsupported color space: %s", colorSpace)); + } + + private static final class Key { + private final byte[] data; + + public Key(byte[] data) { + this.data = data; + } + + @Override + public boolean equals(Object other) { + return other instanceof Key && Arrays.equals(data, ((Key) other).data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + } + + // 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 = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead); + } + private static class CIEXYZ { + private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead); + } + private static class PYCC { + private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead); + } + private static class GRAY { + private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead); + } + private static class LINEAR_RGB { + private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData(ICC_Profile.icSigHead); + } +} diff --git a/imageio/imageio-core/src/main/resources/profiles/ClayRGB1998.icc b/imageio/imageio-core/src/main/resources/profiles/ClayRGB1998.icc new file mode 100644 index 00000000..1eedf093 Binary files /dev/null and b/imageio/imageio-core/src/main/resources/profiles/ClayRGB1998.icc differ 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 new file mode 100644 index 00000000..095652d8 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2011, 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 "TwelveMonkeys" 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 OWNER 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 org.junit.Test; + +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; + +import static org.junit.Assert.*; + +/** + * ColorSpacesTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: ColorSpacesTest.java,v 1.0 07.02.11 14.32 haraldk Exp$ + */ +public class ColorSpacesTest { + @Test + public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_sRGB() { + ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_sRGB); + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(created, ColorSpace.getInstance(ColorSpace.CS_sRGB)); + assertTrue(created.isCS_sRGB()); + } + + @Test + public void testCreateColorSpaceFromKnownProfileDataReturnsInternalCS_sRGB() { + ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB); + byte[] data = internal.getData(); + assertNotSame(internal.getData(), data); // Sanity check + + ICC_Profile profile = ICC_Profile.getInstance(data); + assertNotSame(internal, profile); // Sanity check + + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created); + 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 + 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); + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(ColorSpace.getInstance(ColorSpace.CS_GRAY), created); + } + + @Test + public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_PYCC() { + ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_PYCC); + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(ColorSpace.getInstance(ColorSpace.CS_PYCC), created); + } + + @Test + public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_CIEXYZ() { + ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ); + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), created); + } + + @Test + public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_LINEAR_RGB() { + ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB); + ICC_ColorSpace created = ColorSpaces.createColorSpace(profile); + assertSame(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), created); + } + + @Test + public void testAdobeRGB98NotNull() { + assertNotNull(ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_98)); + } + + @Test + public void testAdobeRGB98IsTypeRGB() { + assertEquals(ColorSpace.TYPE_RGB, ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_98).getType()); + } + + @Test + public void testAdobeRGB98AlwaysSame() { + ColorSpace cs = ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_98); + assertSame(cs, ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_98)); + + if (cs instanceof ICC_ColorSpace) { + ICC_ColorSpace iccCs = (ICC_ColorSpace) cs; + assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile())); + } + else { + System.err.println("Not an ICC_ColorSpace: " + cs); + } + } + + @Test + public void testCMYKNotNull() { + assertNotNull(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK)); + } + + @Test + public void testCMYKIsTypeCMYK() { + assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType()); + } + + @Test + public void testCMYKAlwaysSame() { + ColorSpace cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK); + assertSame(cs, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK)); + + if (cs instanceof ICC_ColorSpace) { + ICC_ColorSpace iccCs = (ICC_ColorSpace) cs; + assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile())); + } + else { + System.err.println("Not an ICC_ColorSpace: " + cs); + } + } +}