/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.pdfbox.pdmodel.graphics.color; import java.awt.Color; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.io.IOException; import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.pdmodel.graphics.pattern.PDPatternResources; /** * This class represents a color space and the color value for that colorspace. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.7 $ */ public class PDColorState implements Cloneable { /** * Log instance. */ private static final Log LOG = LogFactory.getLog(PDColorState.class); /** * The default color that can be set to replace all colors in * {@link ICC_ColorSpace ICC color spaces}. * * @see #setIccOverrideColor(Color) */ private static volatile Color iccOverrideColor = Color.getColor("org.apache.pdfbox.ICC_override_color"); /** * Sets the default color to replace all colors in * {@link ICC_ColorSpace ICC color spaces}. This will work around * a potential JVM crash caused by broken native ICC color manipulation * code in the Sun class libraries. * <p> * The default override can be specified by setting the color code in * <code>org.apache.pdfbox.ICC_override_color</code> system property * (see {@link Color#getColor(String)}. If this system property is not * specified, then the override is not enabled unless this method is * explicitly called. * * @param color ICC override color, * or <code>null</code> to disable the override * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-511">PDFBOX-511</a> * @since Apache PDFBox 0.8.1 */ public static void setIccOverrideColor(Color color) { iccOverrideColor = color; } private PDColorSpace colorSpace = new PDDeviceGray(); private COSArray colorSpaceValue = new COSArray(); private PDPatternResources pattern = null; /** * Cached Java AWT color based on the current color space and value. * The value is cleared whenever the color space or value is set. * * @see #getJavaColor() */ private Color color = null; /** * Default constructor. * */ public PDColorState() { setColorSpaceValue( new float[] {0}); } /** * {@inheritDoc} */ public Object clone() { PDColorState retval = new PDColorState(); retval.colorSpace = this.colorSpace; retval.colorSpaceValue.clear(); retval.colorSpaceValue.addAll( this.colorSpaceValue ); retval.setPattern(getPattern()); return retval; } /** * Returns the Java AWT color based on the current color space and value. * * @return current Java AWT color * @throws IOException if the current color can not be created */ public Color getJavaColor() throws IOException { if (color == null && colorSpaceValue.size() > 0) { color = createColor(); } return color; } /** * Create the current color from the colorspace and values. * @return The current awt color. * @throws IOException If there is an error creating the color. */ private Color createColor() throws IOException { float[] components = colorSpaceValue.toFloatArray(); try { if( colorSpace.getName().equals(PDDeviceRGB.NAME) && components.length == 3 ) { //for some reason, when using RGB and the RGB colorspace //the new Color doesn't maintain exactly the same values //I think some color conversion needs to take place first //for now we will just make rgb a special case. return new Color( components[0], components[1], components[2] ); } else { if (components.length == 1) { if (colorSpace.getName().equals(PDSeparation.NAME)) { //Use that component as a single-integer RGB value return new Color((int)components[0]); } if (colorSpace.getName().equals(PDDeviceGray.NAME)) { // Handling DeviceGray as a special case as with JVM 1.5.0_15 // and maybe others printing on Windows fails with an // ArrayIndexOutOfBoundsException when selecting colors // and strokes e.g. sun.awt.windows.WPrinterJob.setTextColor return new Color(components[0],components[0],components[0]); } } Color override = iccOverrideColor; ColorSpace cs = colorSpace.getJavaColorSpace(); if (cs instanceof ICC_ColorSpace && override != null) { LOG.warn( "Using an ICC override color to avoid a potential" + " JVM crash (see PDFBOX-511)"); return override; } else { return new Color( cs, components, 1f ); } } } // Catch IOExceptions from PDColorSpace.getJavaColorSpace(), but // possibly also IllegalArgumentExceptions or other RuntimeExceptions // from the potentially complex color management code. catch (Exception e) { Color cGuess; String sMsg = "Unable to create the color instance " + Arrays.toString(components) + " in color space " + colorSpace + "; guessing color ... "; try { switch(components.length) { case 1://Use that component as a single-integer RGB value cGuess = new Color((int)components[0]); sMsg += "\nInterpretating as single-integer RGB"; break; case 3: //RGB cGuess = new Color(components[0],components[1],components[2]); sMsg += "\nInterpretating as RGB"; break; case 4: //CMYK //do a rough conversion to RGB as I'm not getting the CMYK to work. //http://www.codeproject.com/KB/applications/xcmyk.aspx float r, g, b, k; k = components[3]; r = components[0] * (1f - k) + k; g = components[1] * (1f - k) + k; b = components[2] * (1f - k) + k; r = (1f - r); g = (1f - g); b = (1f - b); cGuess = new Color( r,g,b ); sMsg += "\nInterpretating as CMYK"; break; default: sMsg += "\nUnable to guess using " + components.length + " components; using black instead"; cGuess = Color.BLACK; } } catch (Exception e2) { sMsg += "\nColor interpolation failed; using black instead\n"; sMsg += e2.toString(); cGuess = Color.BLACK; } LOG.warn(sMsg, e); return cGuess; } } /** * Constructor with an existing color set. Default colorspace is PDDeviceGray. * * @param csValues The color space values. */ public PDColorState( COSArray csValues ) { colorSpaceValue = csValues; } /** * This will get the current colorspace. * * @return The current colorspace. */ public PDColorSpace getColorSpace() { return colorSpace; } /** * This will set the current colorspace. * * @param value The new colorspace. */ public void setColorSpace(PDColorSpace value) { colorSpace = value; // Clear color cache and current pattern color = null; pattern = null; } /** * This will get the color space values. Either 1 for gray or 3 for RGB. * * @return The colorspace values. */ public float[] getColorSpaceValue() { return colorSpaceValue.toFloatArray(); } /** * This will get the color space values. Either 1 for gray or 3 for RGB. * * @return The colorspace values. */ public COSArray getCOSColorSpaceValue() { return colorSpaceValue; } /** * This will update the colorspace values. * * @param value The new colorspace values. */ public void setColorSpaceValue(float[] value) { colorSpaceValue.setFloatArray( value ); // Clear color cache and current pattern color = null; pattern = null; } /** * This will get the current pattern. * * @return The current pattern. */ public PDPatternResources getPattern() { return pattern; } /** * This will update the current pattern. * * @param patternValue The new pattern. */ public void setPattern(PDPatternResources patternValue) { this.pattern = patternValue; // Clear color cache color = null; } }