/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.icepdf.core.pobjects.graphics;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.util.Defs;
import org.icepdf.core.util.Library;
import java.awt.*;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.logging.Logger;
/**
* Device CMYK colour space definitions. The primary purpose of this colour
* space is to convert cymk colours to rgb. No ICC profile is used in this
* process and the generated rgb colour is just and approximation.
*/
public class DeviceCMYK extends PColorSpace {
private static final Logger logger =
Logger.getLogger(DeviceCMYK.class.toString());
public static final Name DEVICECMYK_KEY = new Name("DeviceCMYK");
public static final Name CMYK_KEY = new Name("CMYK");
private static final DeviceGray DEVICE_GRAY = new DeviceGray(null, null);
// default cmyk value, > 255 will lighten the image.
private static float blackRatio;
// CMYK ICC color profile.
private static ICC_ColorSpace iccCmykColorSpace;
// disable icc color profile lookups as they can be slow. n
private static boolean disableICCCmykColorSpace;
static {
// black ratio
blackRatio = (float) Defs.doubleProperty("org.icepdf.core.cmyk.colorant.black", 1.0);
disableICCCmykColorSpace = Defs.booleanProperty("org.icepdf.core.cmyk.disableICCProfile", false);
// check for a custom CMYK ICC colour profile specified using system properties.
iccCmykColorSpace = getIccCmykColorSpace();
}
public DeviceCMYK(Library l, HashMap h) {
super(l, h);
}
public int getNumComponents() {
return 4;
}
/**
* Converts a 4 component cmyk colour to rgb. With out a valid ICC colour
* profile this is just an approximation.
*
* @param f 4 component values of the cmyk, assumes compoents between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
public Color getColor(float[] f, boolean fillAndStroke) {
return alternative2(f);
}
/**
* Ah yes the many possible ways to go from cmyk to rgb. Everybody has
* an opinion but no one has the solution that is 100%
*/
/**
* Adobe photo shop algorithm or so they say. K is assumed to be f[0]
*
* @param f 4 component values of the cmyk, assumes comopents between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
private static Color alternative1(float[] f) {
float c = f[3];
float m = f[2];
float y = f[1];
float k = f[0];
float r = 1.0f - Math.min(1.0f, c + k);
float g = 1.0f - Math.min(1.0f, m + k);
float b = 1.0f - Math.min(1.0f, y + k);
return new Color(r, g, b);
}
/**
* @param f 4 component values of the cmyk, assumes components between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
private static Color alternative3(float[] f) {
float c = f[3];
float m = f[2];
float y = f[1];
float k = f[0];
float r = 1.0f - Math.min(1.0f, (c * (1 - k)) + k);
float g = 1.0f - Math.min(1.0f, (m * (1 - k)) + k);
float b = 1.0f - Math.min(1.0f, (y * (1 - k)) + k);
return new Color(r, g, b);
}
/**
* Auto cad color model
* var R=Math.round((1-C)*(1-K)*255);
* var B=Math.round((1-Y)*(1-K)*255);
* var G=Math.round((1-M)*(1-K)*255);
*
* @param f 4 component values of the cmyk, assumes compoents between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
private static Color getAutoCadColor(float[] f) {
float c = f[3];
float m = f[2];
float y = f[1];
float k = f[0];
int red = Math.round((1.0f - c) * (1.0f - k) * 255);
int blue = Math.round((1.0f - y) * (1.0f - k) * 255);
int green = Math.round((1.0f - m) * (1.0f - k) * 255);
return new Color(red, green, blue);
}
/**
* GNU Ghost Script algorithm or so they say.
* <br>
* rgb[0] = colors * (255 - cyan)/255;
* rgb[1] = colors * (255 - magenta)/255;
* rgb[2] = colors * (255 - yellow)/255;
*
* @param f 4 component values of the cmyk, assumes compoents between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
private static Color getGhostColor(float[] f) {
int cyan = (int) (f[3] * 255);
int magenta = (int) (f[2] * 255);
int yellow = (int) (f[1] * 255);
int black = (int) (f[0] * 255);
float colors = 255 - black;
float[] rgb = new float[3];
rgb[0] = colors * (255 - cyan) / 255;
rgb[1] = colors * (255 - magenta) / 255;
rgb[2] = colors * (255 - yellow) / 255;
return new Color((int) rgb[0], (int) rgb[1], (int) rgb[2]);
}
/**
* Current runner for conversion that looks closest to acrobat.
* The algorithm is a little expensive but it does the best approximation.
*
* @param f 4 component values of the cmyk, assumes compoents between
* 0.0 and 1.0
* @return valid rgb colour object.
*/
private static Color alternative2(float[] f) {
float inCyan = f[3];
float inMagenta = f[2];
float inYellow = f[1];
float inBlack = f[0];
// check if we have a valid ICC profile to work with
if (!disableICCCmykColorSpace && iccCmykColorSpace != null) {
try {
f = iccCmykColorSpace.toRGB(reverse(f));
return new Color(f[0], f[1], f[2]);
} catch (Throwable e) {
logger.warning("Error using iccCmykColorSpace in DeviceCMYK.");
}
}
// soften the amount of black, but exclude explicit black colorant.
if (!(inCyan == 0 && inMagenta == 0 && inYellow == 0)) {
inBlack = inBlack * blackRatio;
}
// if only the black colorant then we can treat the colour as gray,
// cmyk is subtractive.
else {
f[0] = 1.0f - f[0];
return DEVICE_GRAY.getColor(f);
}
double c, m, y, aw, ac, am, ay, ar, ag, ab;
c = clip(0.0, 1.0, inCyan + inBlack);
m = clip(0.0, 1.0, inMagenta + inBlack);
y = clip(0.0, 1.0, inYellow + inBlack);
aw = (1 - c) * (1 - m) * (1 - y);
ac = c * (1 - m) * (1 - y);
am = (1 - c) * m * (1 - y);
ay = (1 - c) * (1 - m) * y;
ar = (1 - c) * m * y;
ag = c * (1 - m) * y;
ab = c * m * (1 - y);
float outRed = (float) clip(0.0, 1.0, aw + 0.9137 * am + 0.9961 * ay + 0.9882 * ar);
float outGreen = (float) clip(0.0, 1.0, aw + 0.6196 * ac + ay + 0.5176 * ag);
float outBlue = (float) clip(0.0, 1.0, aw + 0.7804 * ac + 0.5412 * am + 0.0667 * ar + 0.2118 * ag + 0.4863 * ab);
return new Color(outRed, outGreen, outBlue);
}
/**
* Clips the value according to the specified floor and ceiling.
*
* @param floor floor value of clip
* @param ceiling ceiling value of clip
* @param value value to clip.
* @return clipped value.
*/
private static double clip(double floor, double ceiling, double value) {
if (value < floor) {
value = floor;
}
if (value > ceiling) {
value = ceiling;
}
return value;
}
/**
* Gets the ICC Color Profile found in the icepdf-core.jar at the location
* /org/icepdf/core/pobjects/graphics/res/ or the ICC Color Profiel
* specified by the system property org.icepdf.core.pobjects.graphics.cmyk.
*
* @return associated ICC CMYK Color space.
*/
public static ICC_ColorSpace getIccCmykColorSpace() {
// would prefer to only have one instance but becuase of JDK-8033238
// we can run into decode issue if we share the profile across
String customCMYKProfilePath = null;
try {
Object profileStream;
customCMYKProfilePath = Defs.sysProperty("org.icepdf.core.pobjects.graphics.cmyk");
if (customCMYKProfilePath == null) {
customCMYKProfilePath = "/org/icepdf/core/pobjects/graphics/res/CoatedFOGRA27.icc";
profileStream = DeviceCMYK.class.getResourceAsStream(customCMYKProfilePath);
} else {
profileStream = new FileInputStream(customCMYKProfilePath);
}
ICC_Profile icc_profile = ICC_Profile.getInstance((InputStream) profileStream);
return new ICC_ColorSpace(icc_profile);
} catch (Exception exception) {
logger.warning("Error loading ICC color profile: " + customCMYKProfilePath);
}
return null;
}
/**
* Determines if the ICC CMYK color space should be used to convert
* CMYK images to RGB.
*
* @return true if the ICC CMYK color space should be used, false otherwise.
*/
public static boolean isDisableICCCmykColorSpace() {
return disableICCCmykColorSpace;
}
/**
* Set the value of the disableICCCmykColorSpace property. This property
* can be set using the system property org.icepdf.core.cmyk.disableICCProfile
* or overridden using this mehtod.
*
* @param disableICCCmykColorSpace true to disable the ICC CMYK color space
* conversion, false otherwise.
*/
public static void setDisableICCCmykColorSpace(boolean disableICCCmykColorSpace) {
DeviceCMYK.disableICCCmykColorSpace = disableICCCmykColorSpace;
}
}