org.jpedal.color.GenericColorSpace Maven / Gradle / Ivy
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2017 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
@LICENSE@
*
* ---------------
* GenericColorSpace.java
* ---------------
*/
package org.jpedal.color;
//standard java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import org.jpedal.examples.handlers.DefaultImageHelper;
import org.jpedal.exception.PdfException;
import org.jpedal.external.ExternalHandlers;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.utils.LogWriter;
import org.w3c.dom.NodeList;
/**
* Provides Color functionality and conversion for pdf decoding
*/
public class GenericColorSpace {
boolean isConverted;
/**
* actual raw value
*/
float[] rawValues;
Map patterns; //holds new PdfObjects
/**
* size for indexed colorspaces
*/
private int size;
/**
* holds cmyk values if present
*/
float c = -1;
float y = -1;
float m = -1;
float k = -1;
/**
* matrices for calculating CIE XYZ colour
*/
float[] W;
float[] G;
float[] Ma;
// float[] B;
float[] R;
/**
* defines rgb colorspace
*/
static ColorSpace rgbCS;
public static final String cb = "
* Convert DCT encoded image bytestream to sRGB
*
* It uses the internal Java classes and the Adobe icm to convert CMYK and
* YCbCr-Alpha - the data is still DCT encoded.
*
* The Sun class JPEGDecodeParam.java is worth examining because it contains
* lots of interesting comments
*
* I tried just using the new IOImage.read() but on type 3 images, all my
* clipping code stopped working so I am still using 1.3
*/
protected final BufferedImage nonRGBJPEGToRGBImage(
final byte[] data, int w, int h, final int pX, final int pY) {
boolean isProcessed = false;
BufferedImage image = null;
ByteArrayInputStream in = null;
ImageReader iir = null;
ImageInputStream iin = null;
try {
if (CSToRGB == null) {
initCMYKColorspace();
}
CSToRGB = new ColorConvertOp(cs, rgbCS, ColorSpaces.hints);
in = new ByteArrayInputStream(data);
final int cmykType = getJPEGTransform(data);
//suggestion from Carol
try {
final Iterator iterator = ImageIO.getImageReadersByFormatName("JPEG");
while (iterator.hasNext()) {
final ImageReader o = iterator.next();
iir = o;
if (iir.canReadRaster()) {
break;
}
}
} catch (final Exception e) {
LogWriter.writeLog("Unable to find jars on classpath " + e);
return null;
}
//iir = (ImageReader)ImageIO.getImageReadersByFormatName("JPEG").next();
ImageIO.setUseCache(false);
iin = ImageIO.createImageInputStream((in));
iir.setInput(iin, true); //new MemoryCacheImageInputStream(in));
Raster ras = iir.readRaster(0, null);
if (cmykType == 0 && cs.getNumComponents() == 1) { //it is actually gray
image = JPEGDecoder.grayJPEGToRGBImage(data, pX, pY);
if (image != null) {
isProcessed = true;
}
} else if (cs.getNumComponents() == 4) { //if 4 col CMYK of ICC translate
isProcessed = true;
try {
if (cmykType == 2) {
hasYCCKimages = true;
image = ColorSpaceConvertor.iccConvertCMYKImageToRGB(((DataBufferByte) ras.getDataBuffer()).getData(), w, h);
} else {
ras = cleanupRaster(ras, pX, pY, 4);
w = ras.getWidth();
h = ras.getHeight();
image = ExternalHandlers.ImageLib.convert(ras, w, h);
}
} catch (final Exception e) {
LogWriter.writeLog("Problem with JPEG conversion " + e);
}
} else if (cmykType != 0) {
image = iir.read(0);
image = cleanupImage(image, pX, pY);
isProcessed = true;
}
//test
if (!isProcessed) {
final WritableRaster rgbRaster;
if (cmykType == 4) { //CMYK
ras = cleanupRaster(ras, pX, pY, 4);
final int width = ras.getWidth();
final int height = ras.getHeight();
rgbRaster = rgbModel.createCompatibleWritableRaster(width, height);
CSToRGB.filter(ras, rgbRaster);
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
image.setData(rgbRaster);
} else {
boolean isYCC = false;
try {
final IIOMetadata metadata = iir.getImageMetadata(0);
final String metadataFormat = metadata.getNativeMetadataFormatName();
final IIOMetadataNode iioNode = (IIOMetadataNode) metadata.getAsTree(metadataFormat);
final NodeList children = iioNode.getElementsByTagName("app14Adobe");
if (children.getLength() > 0) {
isYCC = true;
}
} catch (final Exception ee) {
LogWriter.writeLog("[PDF] Unable to read metadata on Jpeg " + ee);
}
LogWriter.writeLog("COLOR_ID_YCbCr image");
if (isYCC) { //sample file debug2/pdf4134.pdf suggests we need this change
image = DefaultImageHelper.read(data);
} else {
//try with iccConvertCMYKImageToRGB(final byte[] buffer,int w,int h) and delete if works
image = ColorSpaceConvertor.algorithmicConvertYCbCrToRGB(((DataBufferByte) ras.getDataBuffer()).getData(), w, h);
}
image = cleanupImage(image, pX, pY);
image = ColorSpaceConvertor.convertToRGB(image);
}
}
} catch (final Exception ee) {
image = null;
LogWriter.writeLog("Couldn't read JPEG, not even raster: " + ee);
} catch (final Error err) {
LogWriter.writeLog("JPeg error " + err);
if (iir != null) {
iir.dispose();
}
if (iin != null) {
try {
iin.flush();
} catch (final IOException e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
try {
if (in != null) {
in.close();
}
if (iir != null) {
iir.dispose();
}
if (iin != null) {
iin.close();
}
} catch (final Exception ee) {
LogWriter.writeLog("Problem closing " + ee);
}
return image;
}
protected static BufferedImage cleanupImage(BufferedImage image, final int pX, final int pY) {
try {
final int imageType = image.getType();
if (getSampling(image.getWidth(), image.getHeight(), pX, pY) <= 1 || imageType == BufferedImage.TYPE_CUSTOM) {
return image;
} else if (imageType == BufferedImage.TYPE_3BYTE_BGR) {
return cleanupBGRImage(image, pX, pY);
} else {
if (imageType == 5) {
image = ColorSpaceConvertor.convertToRGB(image);
}
final Raster ras = cleanupRaster(image.getData(), pX, pY, image.getColorModel().getNumColorComponents());
image = new BufferedImage(ras.getWidth(), ras.getHeight(), image.getType());
image.setData(ras);
return image;
}
} catch (final Error err) {
LogWriter.writeLog("[PDF] Error in cleanupImage " + err);
}
return image;
}
private static int getSampling(final int w, final int h, final int pX, final int pY) {
int sampling = 1; //keep as multiple of 2
int newW = w, newH = h;
if (pX > 0 && pY > 0) {
final int smallestH = pY << 2; //double so comparison works
final int smallestW = pX << 2;
//cannot be smaller than page
while (newW > smallestW && newH > smallestH) {
sampling <<= 1;
newW >>= 1;
newH >>= 1;
}
int scaleX = w / pX;
if (scaleX < 1) {
scaleX = 1;
}
int scaleY = h / pY;
if (scaleY < 1) {
scaleY = 1;
}
//choose smaller value so at least size of page
sampling = scaleX;
if (sampling > scaleY) {
sampling = scaleY;
}
}
return sampling;
}
protected static Raster cleanupRaster(Raster ras, final int pX, final int pY, final int comp) {
/*
* allow user to disable this function and just return raw data
*/
final String avoidCleanupRaster = System.getProperty("org.jpedal.avoidCleanupRaster");
if (avoidCleanupRaster != null && avoidCleanupRaster.toLowerCase().contains("true")) {
return ras;
}
byte[] buffer = null;
int[] intBuffer = null;
final int type;
final DataBuffer data = ras.getDataBuffer();
if (data instanceof DataBufferInt) {
type = 1;
} else {
type = 0;
}
if (type == 1) {
intBuffer = ((DataBufferInt) data).getData();
} else {
final int layerCount = ras.getNumBands();
if (layerCount == comp) {
buffer = ((DataBufferByte) data).getData();
} else if (layerCount == 1) {
final byte[] rawBuffer = ((DataBufferByte) ras.getDataBuffer()).getData();
final int size = rawBuffer.length;
final int realSize = size * comp;
int j = 0, i = 0;
buffer = new byte[realSize];
while (true) {
for (int a = 0; a < comp; a++) {
buffer[j] = rawBuffer[i];
j++;
}
i++;
if (i >= size) {
break;
}
}
} else if (LogWriter.isRunningFromIDE) {
throw new RuntimeException("Unknown case " + layerCount + " in GenericColorspace");
}
}
int sampling = 1; //keep as multiple of 2
final int w = ras.getWidth();
final int h = ras.getHeight();
int newW = w, newH = h;
if (pX > 0 && pY > 0) {
final int smallestH = pY << 2; //double so comparison works
final int smallestW = pX << 2;
//cannot be smaller than page
while (newW > smallestW && newH > smallestH) {
sampling <<= 1;
newW >>= 1;
newH >>= 1;
}
int scaleX = w / pX;
if (scaleX < 1) {
scaleX = 1;
}
int scaleY = h / pY;
if (scaleY < 1) {
scaleY = 1;
}
//choose smaller value so at least size of page
sampling = scaleX;
if (sampling > scaleY) {
sampling = scaleY;
}
}
//switch to 8 bit and reduce bw image size by averaging
if (sampling > 1) {
newW = w / sampling;
newH = h / sampling;
int x, y, xx, yy, jj, origLineLength = w;
try {
final byte[] newData = new byte[newW * newH * comp];
if (type == 0) {
origLineLength = w * comp;
}
for (y = 0; y < newH; y++) {
for (x = 0; x < newW; x++) {
//allow for edges in number of pixels left
int wCount = sampling, hCount = sampling;
final int wGapLeft = w - x;
final int hGapLeft = h - y;
if (wCount > wGapLeft) {
wCount = wGapLeft;
}
if (hCount > hGapLeft) {
hCount = hGapLeft;
}
for (jj = 0; jj < comp; jj++) {
int byteTotal = 0, count = 0;
//count pixels in sample we will make into a pixel (ie 2x2 is 4 pixels , 4x4 is 16 pixels)
for (yy = 0; yy < hCount; yy++) {
for (xx = 0; xx < wCount; xx++) {
if (type == 0) {
byteTotal += (buffer[((yy + (y * sampling)) * origLineLength) + (((x * sampling * comp) + (xx * comp) + jj))] & 255);
} else {
byteTotal += ((intBuffer[((yy + (y * sampling)) * origLineLength) + (x * sampling) + xx] >> (8 * (2 - jj))) & 255);
}
count++;
}
}
//set value as white or average of pixels
if (count > 0) {
newData[jj + (x * comp) + (newW * y * comp)] = (byte) ((byteTotal) / count);
}
}
}
}
final int[] bands = new int[comp];
for (int jj2 = 0; jj2 < comp; jj2++) {
bands[jj2] = jj2;
}
ras = Raster.createInterleavedRaster(new DataBufferByte(newData, newData.length), newW, newH, newW * comp, comp, bands, null);
} catch (final Exception e) {
LogWriter.writeLog("Problem with Image " + e);
}
}
return ras;
}
private static BufferedImage cleanupBGRImage(BufferedImage img, final int pX, final int pY) {
/*
* allow user to disable this function and just return raw data
*/
final String avoidCleanupRaster = System.getProperty("org.jpedal.avoidCleanupRaster");
if (avoidCleanupRaster != null && avoidCleanupRaster.toLowerCase().contains("true")) {
return img;
}
//hack to fix bug on Linux (also effects Windows 1.5 so disabled)
if (System.getProperty("java.version").startsWith("1.5")) {
//if(PdfDecoder.isRunningOnLinux && System.getProperty("java.version").startsWith("1.5")){
return img;
}
final Raster ras = img.getData();
final int comp = img.getColorModel().getNumColorComponents();
byte[] buffer = null;
int[] intBuffer = null;
final int type;
final DataBuffer data = ras.getDataBuffer();
if (data instanceof DataBufferInt) {
type = 1;
} else {
type = 0;
}
if (type == 1) {
intBuffer = ((DataBufferInt) data).getData();
} else {
final int layerCount = ras.getNumBands();
if (layerCount == comp) {
buffer = ((DataBufferByte) data).getData();
} else if (layerCount == 1) {
final byte[] rawBuffer = ((DataBufferByte) ras.getDataBuffer()).getData();
final int size = rawBuffer.length;
final int realSize = size * comp;
int j = 0, i = 0;
buffer = new byte[realSize];
while (true) {
for (int a = 0; a < comp; a++) {
buffer[j] = rawBuffer[i];
j++;
}
i++;
if (i >= size) {
break;
}
}
} else if (LogWriter.isRunningFromIDE) {
throw new RuntimeException("Unknown case in GenericColorspace");
}
}
int sampling = 1; //keep as multiple of 2
final int w = ras.getWidth();
final int h = ras.getHeight();
int newW = w, newH = h;
if (pX > 0 && pY > 0) {
final int smallestH = pY << 2; //double so comparison works
final int smallestW = pX << 2;
//cannot be smaller than page
while (newW > smallestW && newH > smallestH) {
sampling <<= 1;
newW >>= 1;
newH >>= 1;
}
int scaleX = w / pX;
if (scaleX < 1) {
scaleX = 1;
}
int scaleY = h / pY;
if (scaleY < 1) {
scaleY = 1;
}
//choose smaller value so at least size of page
sampling = scaleX;
if (sampling > scaleY) {
sampling = scaleY;
}
}
//switch to 8 bit and reduce bw image size by averaging
if (sampling > 1) {
final WritableRaster newRas = ((WritableRaster) ras);
newW = w / sampling;
newH = h / sampling;
int x, y, xx, yy, jj, origLineLength = w;
try {
final int[] newData = new int[comp];
if (type == 0) {
origLineLength = w * comp;
}
for (y = 0; y < newH; y++) {
for (x = 0; x < newW; x++) {
//allow for edges in number of pixels left
int wCount = sampling, hCount = sampling;
final int wGapLeft = w - x;
final int hGapLeft = h - y;
if (wCount > wGapLeft) {
wCount = wGapLeft;
}
if (hCount > hGapLeft) {
hCount = hGapLeft;
}
for (jj = 0; jj < comp; jj++) {
int byteTotal = 0, count = 0;
//count pixels in sample we will make into a pixel (ie 2x2 is 4 pixels , 4x4 is 16 pixels)
for (yy = 0; yy < hCount; yy++) {
for (xx = 0; xx < wCount; xx++) {
if (type == 0) {
byteTotal += (buffer[((yy + (y * sampling)) * origLineLength) + (((x * sampling * comp) + (xx * comp) + jj))] & 255);
} else {
byteTotal += ((intBuffer[((yy + (y * sampling)) * origLineLength) + (x * sampling) + xx] >> (8 * (2 - jj))) & 255);
}
count++;
}
}
//set value as white or average of pixels
if (count > 0) {
switch (jj) {
case 0:
newData[2] = ((byteTotal) / count);
break;
case 2:
newData[0] = ((byteTotal) / count);
break;
default:
newData[jj] = ((byteTotal) / count);
break;
}
}
}
//write back into ras
newRas.setPixels(x, y, 1, 1, newData); //changed to setPixels from setPixel for JAVA ME
//System.out.println(x+"/"+newW+" "+y+"/"+newH+" "+newData[0]+" "+newData[1]);
}
}
//put back data and trim
img = new BufferedImage(newW, newH, img.getType());
img.setData(newRas);
//img=img.getSubimage(0,0,newW,newH); slower so replaced
} catch (final Exception e) {
LogWriter.writeLog("Problem with Image " + e);
}
}
return img;
}
/**
* Toms routine to read the image type - you can also use int colorType =
* decoder.getJPEGDecodeParam().getEncodedColorID();
*/
private static int getJPEGTransform(final byte[] data) {
int xform = 0;
final int dataLength = data.length;
for (int i = 0, imax = dataLength - 2; i < imax;) {
final int type = data[i + 1] & 0xff; // want unsigned bytes!
//out_.println("+"+i+": "+Integer.toHexString(type)/*+", len="+len*/);
i += 2; // 0xff and type
if (type == 0x01 || (0xd0 <= type && type <= 0xda)) {
} else if (type == 0xda) {
i = i + ((data[i] & 0xff) << 8) + (data[i + 1] & 0xff);
while (true) {
for (; i < imax; i++) {
if ((data[i] & 0xff) == 0xff && data[i + 1] != 0) {
break;
}
}
final int rst = data[i + 1] & 0xff;
if (0xd0 <= rst && rst <= 0xd7) {
i += 2;
} else {
break;
}
}
} else if (i + 1 < dataLength) {
/*if (0xc0 <= type&&type <= 0xcf) { // SOF
* Nf = data[i+7] & 0xff; // 1, 3=YCbCr, 4=YCCK or CMYK
* } else*/
if ((type == 0xee)
&& // Adobe
(data[i + 2] == 'A' && data[i + 3] == 'd' && data[i + 4] == 'o' && data[i + 5] == 'b' && data[i + 6] == 'e')) {
xform = data[i + 13] & 0xff;
break;
}
i = i + ((data[i] & 0xff) << 8) + (data[i + 1] & 0xff);
}
}
return xform;
}
public void setIndex(final byte[] IndexedColorMap, final int size) {
// set the data for an object
this.IndexedColorMap = IndexedColorMap;
this.size = size;
// System.out.println("Set index ="+IndexedColorMap);
}
/**
* lookup a component for index colorspace
*/
protected int getIndexedColorComponent(final int count) {
int value = 255;
if (IndexedColorMap != null) {
value = IndexedColorMap[count];
if (value < 0) {
value = 256 + value;
}
}
return value;
}
/**
* return indexed COlorMap
*/
public byte[] getIndexedMap() {
//return IndexedColorMap;
/**/
if (IndexedColorMap == null) {
return null;
}
final int size = IndexedColorMap.length;
final byte[] copy = new byte[size];
System.arraycopy(IndexedColorMap, 0, copy, 0, size);
return copy;
/**/
}
/**
* convert color value to sRGB color
*/
public void setColor(final String[] value, final int operandCount) {
}
/**
* convert color value to sRGB color
*/
public void setColor(final float[] value, final int operandCount) {
if (LogWriter.isRunningFromIDE) {
throw new RuntimeException(" called setColor(float[] in Generic for " + this);
}
}
/**
* convert byte[] datastream JPEG to an image in RGB
*/
public BufferedImage JPEGToRGBImage(final byte[] data, final int w, final int h, final int pX, final int pY) {
BufferedImage image;
try {
image = DefaultImageHelper.read(data);
if (image != null && !fasterPNG) {
if (value != ColorSpaces.DeviceGray) { //crashes Linux
image = cleanupImage(image, pX, pY);
}
if (value != ColorSpaces.DeviceGray) {
image = ColorSpaceConvertor.convertToRGB(image);
}
}
//don ot alter to IOException as breaks file
} catch (final Exception ee) {
image = null;
LogWriter.writeLog("Problem reading JPEG: " + ee);
}
// if(arrayInverted && this.value ==ColorSpaces.DeviceGray ) {
//
// final DataBufferByte rgb = (DataBufferByte) image.getRaster().getDataBuffer();
// final byte[] rawData=rgb.getData();
//
// for(int aa=0;aa> 16) & 0xFF);
rgb[j2 + 1] = (byte) ((foreground >> 8) & 0xFF);
rgb[j2 + 2] = (byte) ((foreground) & 0xFF);
j2 += 3;
if (len - 4 - ii < 4) {
ii = len;
}
}
return rgb;
}
try {
/*turn it into a BufferedImage so we can convert then extract the data*/
final int width = data.length / compCount;
final int height = 1;
final DataBuffer db = new DataBufferByte(data, data.length);
final WritableRaster raster = Raster.createInterleavedRaster(db, width, height, width * compCount, compCount, bands4, null);
if (CSToRGB == null) {
initCMYKColorspace();
CSToRGB = new ColorConvertOp(cs, rgbCS, ColorSpaces.hints);
}
final WritableRaster rgbRaster
= rgbModel.createCompatibleWritableRaster(width, height);
CSToRGB.filter(raster, rgbRaster);
final DataBuffer convertedData = rgbRaster.getDataBuffer();
final int size = width * height * 3;
data = new byte[size];
for (int ii = 0; ii < size; ii++) {
data[ii] = (byte) convertedData.getElem(ii);
}
} catch (final Exception ee) {
LogWriter.writeLog("Exception " + ee + " converting colorspace");
}
return data;
}
/**
* convert Index to RGB
*/
public byte[] convertIndexToRGB(final byte[] index) {
return index;
}
/**
* get an xml string with the color info
*/
public String getXMLColorToken() {
final String colorToken;
//only cal if not set
if (c == -1) { //approximate
if (currentColor instanceof Color) {
final Color col = (Color) currentColor;
final float c = (255 - col.getRed()) / 255f;
final float m = (255 - col.getGreen()) / 255f;
final float y = (255 - col.getBlue()) / 255f;
float k = c;
if (k < m) {
k = m;
}
if (k < y) {
k = y;
}
colorToken = GenericColorSpace.cb + "C='" + c + "' M='" + m + "' Y='" + y + "' K='" + k + "' >";
} else {
colorToken = GenericColorSpace.cb + "type='shading'>";
}
} else {
colorToken = GenericColorSpace.cb + "C='" + c + "' M='" + m + "' Y='" + y + "' K='" + k + "' >";
}
return colorToken;
}
/**
* pass in list of patterns
*/
public void setPattern(final Map patterns) {
this.patterns = patterns;
}
/**
* used by generic decoder to asign color
*/
public void setColor(final PdfPaint col) {
this.currentColor = col;
}
/**
* return number of values used for color (ie 3 for rgb)
*/
public int getColorComponentCount() {
return componentCount;
}
/**
* pattern colorspace needs access to graphicsState
*/
public void setGS(final GraphicsState currentGraphicsState) {
this.gs = currentGraphicsState;
}
/**
* return raw values - only currently works for CMYK
*/
public float[] getRawValues() {
return rawValues;
}
/**
* flag to show if YCCK image decoded so we can draw attention to user
*
* @return
*/
public boolean isImageYCCK() {
return hasYCCKimages;
}
public boolean isIndexConverted() {
return isConverted;
}
/**
* method to flush any caching values for cases where may need resetting (ie
* in CMYK where restore will render last values in setColor used for
* caching invalid
*/
public void clearCache() {
}
public static ColorSpace getColorSpaceInstance() {
ColorSpace rgbCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
final String profile = System.getProperty("org.jpedal.RGBprofile");
if (profile != null) {
try {
rgbCS = new ICC_ColorSpace(ICC_Profile.getInstance(new FileInputStream(profile)));
System.out.println("use " + profile);
} catch (final Exception e) {
LogWriter.writeLog("[PDF] Problem " + e.getMessage() + " with ICC data ");
}
}
return rgbCS;
}
/**
* ColorSpace.value in ICC
*
* @return
*/
int getType() {
return type;
}
/**
* set PDF type (ColorSpaces variable)
*
* @param rawValue
*/
void setType(final int rawValue) {
value = rawValue;
rawCSType = rawValue;
}
void setRawColorSpace(final int rawType) {
rawCSType = rawType;
}
public int getRawColorSpacePDFType() {
return rawCSType;
}
public BufferedImage JPEG2000ToImage(final byte[] data, final int pX, final int pY) throws PdfException {
BufferedImage image;
try {
image = ExternalHandlers.ImageLib.JPEG2000ToRGBImage(data);
if (image != null) {
image = cleanupImage(image, pX, pY);
}
} catch (final Exception ex) { //rethrow as Pdfexception
throw new PdfException(ex.getMessage());
}
return image;
}
public void invalidateCaching(final int color) {
//does nothing
}
}