/*
* 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.pobjects.functions.Function;
import org.icepdf.core.util.ColorUtil;
import org.icepdf.core.util.Library;
import java.awt.*;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>Separation Color Space background:</p>
* <ul>
* <li>Color output devices produce full color by combining primary or process
* colorants in varying amounts. On an additive color device such as a display,
* the primary colorants consist of red, green, and blue phosphors; on a
* subtractive device such as a printer, they typically consist of cyan, magenta,
* yellow, and sometimes black inks. In addition, some devices can apply special
* colorants, often called spot colorants, to produce effects that cannot be
* achieved with the standard process colorants alone. Examples include metallic
* and fluorescent colors and special textures.</li>
* </ul>
* <p>A Separation color space (PDF 1.2) provides a means for specifying the use
* of additional colorants or for isolating the control of individual color
* components of a device color space for a subtractive device. When such a space
* is the current color space, the current color is a single-component value,
* called a tint, that controls the application of the given colorant or color
* components only.</p>
* <p>A Separation color space is defined as follows:<br>
* [/Separation name alternateSpace tintTransform]
* </p>
* <ul>
* <li>The <i>alternateSpace</i> parameter must be an array or name object that
* identifies the alternate color space, which can be any device or
* CIE-based color space but not another special color space (Pattern,
* Indexed, Separation, or DeviceN).</li>
* <li>The <i>tintTransform</i> parameter must be a function.
* During subsequent painting operations, an application
* calls this function to transform a tint value into color component values
* in the alternate color space. The function is called with the tint value
* and must return the corresponding color component values. That is, the
* number of components and the interpretation of their values depend on the
* alternate color space.</li>
* </ul>
*
* @since 1.0
*/
public class Separation extends PColorSpace {
public static final Name SEPARATION_KEY = new Name("Separation");
// named colour reference if valid conversion took place
protected Color namedColor;
// alternative colour space, named colour can not be resolved.
protected PColorSpace alternate;
// transform for colour tint, named function type
protected Function tintTransform;
// The special colorant name All shall refer collectively to all colorants
// available on an output device, including those for the standard process
// colorants. When a Separation space with this colorant name is the current
// colour space, painting operators shall apply tint values to all available
// colorants at once.
private boolean isAll;
public static final String COLORANT_ALL = "all";
// The special colorant name None shall not produce any visible output.
// Painting operations in a Separationspace with this colorant name shall
// have no effect on the current page.
private boolean isNone;
public static final String COLORANT_NONE = "none";
private float tint = 1.0f;
// basic cache to speed up the lookup.
private ConcurrentHashMap<Integer, Color> colorTable1B;
private ConcurrentHashMap<Integer, Color> colorTable3B;
private ConcurrentHashMap<Integer, Color> colorTable4B;
/**
* Create a new Seperation colour space. Separation is specified using
* [/Seperation name alternateSpace tintTransform]
*
* @param l library
* @param h dictionary entries
* @param name name of colourspace, always seperation
* @param alternateSpace name of alternative colour space
* @param tintTransform function which defines the tint transform
*/
protected Separation(Library l, HashMap h, Object name, Object alternateSpace, Object tintTransform) {
super(l, h);
alternate = getColorSpace(l, alternateSpace);
colorTable1B = new ConcurrentHashMap<Integer, Color>(256);
colorTable3B = new ConcurrentHashMap<Integer, Color>(256);
colorTable4B = new ConcurrentHashMap<Integer, Color>(256);
this.tintTransform = Function.getFunction(l, l.getObject(tintTransform));
// see if name can be converted to a known colour.
if (name instanceof Name) {
String colorName = ((Name) name).getName().toLowerCase();
// check for additive colours we can work with .
if (!(colorName.equals("red") || colorName.equals("blue")
|| colorName.equals("blue") || colorName.equals("black")
|| colorName.equals("cyan") || colorName.equals("brown")
|| colorName.equals("auto"))) {
// sniff out All or Null
if (colorName.equals(COLORANT_ALL)) {
isAll = true;
} else if (colorName.equals(COLORANT_NONE)) {
isNone = true;
}
// return as we don't care about the namedColor if subtractive.
return;
}
// get colour value if any
int colorVaue = ColorUtil.convertNamedColor(colorName.toLowerCase());
if (colorVaue != -1) {
namedColor = new Color(colorVaue);
}
// quick check for auto color which we'll paint as black
if (colorName.equalsIgnoreCase("auto")) {
namedColor = Color.BLACK;
}
}
}
/**
* Returns the number of components in this colour space.
*
* @return number of components
*/
public int getNumComponents() {
return 1;
}
public boolean isNamedColor() {
return namedColor != null;
}
/**
* Gets the colour in RGB represented by the array of colour components
*
* @param components array of component colour data
* @param fillAndStroke true indicates a fill or stroke operation, so we
* will try to used the named colour and tint. This
* is generally not do for images.
* @return new RGB colour composed from the components array.
*/
public Color getColor(float[] components, boolean fillAndStroke) {
// there are couple notes in the spec that say that even know namedColor
// is for subtractive color devices, if the named colour can be represented
// in a additive device then it should be used over the alternate colour.
if (namedColor != null) {
// apply tint
tint = components[0];
// apply tint as an alpha value.
float[] colour = namedColor.getComponents(null);
// namedColor = new Color(colour[0] * tint, colour[1] * tint, colour[2] * tint);
Color namedColor = new Color(colour[0], colour[1], colour[2], tint);
// The color model doesn't actually have transparency, so white with an alpha of 0.
// is still just white, not transparent.
if (tint < 0.1f && colour[0] == 0 && colour[1] == 0 && colour[2] == 0) {
return Color.WHITE;
}
return namedColor;
}
// the function couldn't be initiated then use the alternative colour
// space. The alternate colour space can be any device or CIE-based
// colour space. However Separation is usually specified using only one
// component so we must generate the output colour
if (tintTransform == null) {
float colour = components[0];
// copy the colour values into the needed length of the alternate colour
float[] alternateColour = new float[alternate.getNumComponents()];
for (int i = 0, max = alternate.getNumComponents(); i < max; i++) {
alternateColour[i] = colour;
}
return alternate.getColor(alternateColour);
}
if (alternate != null && !isNone) {
// component is our key which we can use to avoid doing the tintTransform.
int key = 0;
int bands = components.length;
for (int i = 0, bit = 0; i < bands; i++, bit += 8) {
key |= (((int) (components[i] * 255) & 0xff) << bit);
}
if (bands == 1) {
return addColorToCache(colorTable1B, key, alternate, tintTransform, components);
} else if (bands == 3) {
return addColorToCache(colorTable3B, key, alternate, tintTransform, components);
} else if (bands == 4) {
return addColorToCache(colorTable4B, key, alternate, tintTransform, components);
}
}
if (isNone) {
return new Color(0, 0, 0, 0);
}
// return the named colour if it was resolved, otherwise assemble the
// alternative colour.
// -- Only applies to subtractive devices, screens are additive but I'm
// leaving this in encase something goes horribly wrong.
return namedColor;
}
private static Color addColorToCache(
ConcurrentHashMap<Integer, Color> colorCache, int key,
PColorSpace alternate, Function tintTransform, float[] f) {
Color color = colorCache.get(key);
if (color == null) {
float y[] = tintTransform.calculate(reverse(f));
color = alternate.getColor(reverse(y));
colorCache.put(key, color);
return color;
} else {
return color;
}
}
public float getTint() {
return tint;
}
}