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-2016 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* 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.JDeliHelper;
import org.jpedal.examples.handlers.DefaultImageHelper;
import org.jpedal.exception.PdfException;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
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=CMYKtoRGB.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=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;ywGapLeft) {
wCount=wGapLeft;
}
if(hCount>hGapLeft) {
hCount=hGapLeft;
}
for(jj=0;jj>(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=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;ywGapLeft) {
wCount=wGapLeft;
}
if(hCount>hGapLeft) {
hCount=hGapLeft;
}
for(jj=0;jj>(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>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";
}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 void setDecodeParms(final PdfObject parms) {
this.decodeParms=parms;
}
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(int rawValue) {
value=rawValue;
rawCSType=rawValue;
}
void setRawColorSpace(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 = JDeliHelper.JPEG2000ToRGBImage(data);
if(image!=null){
image = cleanupImage(image, pX, pY);
}
} catch (Exception ex) {//rethrow as Pdfexception
throw new PdfException(ex.getMessage());
}
return image;
}
public void invalidateCaching(int color) {
//does nothing
}
}