org.apache.pdfbox.pdmodel.graphics.color.PDColorState Maven / Gradle / Ivy
Show all versions of pdfbox Show documentation
/*
* 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.Paint;
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 Ben Litchfield
*
*/
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 = null;
static
{
try
{
iccOverrideColor = Color.getColor("org.apache.pdfbox.ICC_override_color");
}
catch (SecurityException e)
{
}
}
/**
* 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.
*
* The default override can be specified by setting the color code in
* org.apache.pdfbox.ICC_override_color
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 null
to disable the override
* @see PDFBOX-511
* @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;
private Paint paint = null;
/**
* Default constructor.
*
*/
public PDColorState()
{
setColorSpaceValue(new float[] { 0 });
}
/**
* {@inheritDoc}
*/
public Object clone()
{
PDColorState retval = new PDColorState();
retval.colorSpace = colorSpace;
retval.colorSpaceValue.clear();
retval.colorSpaceValue.addAll(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;
}
/**
* Returns the Java AWT paint based on the current pattern.
*
* @param pageHeight the height of the current page
* @return current Java AWT paint
*
* @throws IOException if the current color can not be created
*/
public Paint getPaint(int pageHeight) throws IOException
{
if (paint == null && pattern != null)
{
paint = pattern.getPaint(pageHeight);
}
return paint;
}
/**
* 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
{
String csName = colorSpace.getName();
if (PDDeviceRGB.NAME.equals(csName) && 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 (PDLab.NAME.equals(csName))
{
// transform the color values from Lab- to RGB-space
float[] csComponents = colorSpace.getJavaColorSpace().toRGB(components);
return new Color(csComponents[0], csComponents[1], csComponents[2]);
}
else
{
if (components.length == 1)
{
if (PDSeparation.NAME.equals(csName))
{
// Use that component as a single-integer RGB value
return new Color((int) components[0]);
}
if (PDDeviceGray.NAME.equals(csName))
{
// 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/paint cache and current pattern
color = null;
paint = 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/paint cache and current pattern
color = null;
paint = 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)
{
pattern = patternValue;
// Clear color/paint cache
color = null;
paint = null;
}
}