org.jpedal.color.GenericColorSpace Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
An Open Source JavaFX PDF Viewer
/*
* ===========================================
* 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
*
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.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 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 = JDeliHelper.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
}
}