org.jpedal.parser.image.ImageDecoder 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
The newest version!
/*
* ===========================================
* 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-2015 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
*
* ---------------
* ImageDecoder.java
* ---------------
*/
package org.jpedal.parser.image;
import com.idrsolutions.pdf.color.shading.BitReader;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.*;
import org.jpedal.PdfDecoderInt;
import org.jpedal.color.*;
import org.jpedal.constants.PDFImageProcessing;
import org.jpedal.exception.PdfException;
import org.jpedal.external.ErrorTracker;
import org.jpedal.external.ImageDataHandler;
import org.jpedal.external.ImageHandler;
import org.jpedal.images.ImageOps;
import org.jpedal.images.ImageTransformer;
import org.jpedal.images.ImageTransformerDouble;
import org.jpedal.images.SamplingFactory;
import org.jpedal.io.*;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfImageData;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.raw.*;
import org.jpedal.parser.*;
import org.jpedal.parser.image.data.ImageData;
import org.jpedal.parser.image.utils.*;
import org.jpedal.render.RenderUtils;
import org.jpedal.utils.LogWriter;
public class ImageDecoder extends BaseDecoder{
//Allow print to use transparency in printing instead of removing it
public static boolean allowPrintTransparency;
final PdfImageData pdfImages;
private boolean getSamplingOnly;
/**flag to show if image transparent*/
boolean isMask=true;
String imagesInFile;
PdfObjectCache cache;
boolean isPrinting;
final ImageHandler customImageHandler;
boolean useHiResImageForDisplay;
boolean isType3Font;
final int formLevel;
final PdfPageData pageData;
final ObjectStore objectStoreStreamRef;
/**flag to show raw images extracted*/
boolean clippedImagesExtracted=true;
private boolean extractRawCMYK;
/**flag to show raw images extracted*/
boolean finalImagesExtracted=true;
/**flag to show if we physical generate a scaled version of the
* images extracted*/
boolean createScaledVersion = true;
/**flag to show content is being rendered*/
boolean renderImages;
/**flag to show raw images extracted*/
boolean rawImagesExtracted=true;
//used internally to show optimisations
int optionsApplied;
/**name of current image in pdf*/
String currentImage = "";
final String formName;
final ErrorTracker errorTracker;
final PdfObjectReader currentPdfFile;
/**images on page*/
public final int imageCount;
public ImageDecoder(final int imageCount, final PdfObjectReader currentPdfFile, final ErrorTracker errorTracker, final ImageHandler customImageHandler, final ObjectStore objectStoreStreamRef, final PdfImageData pdfImages, final int formLevel, final PdfPageData pageData, final String imagesInFile, final String formName) {
this.imageCount=imageCount;
this.currentPdfFile=currentPdfFile;
this.errorTracker=errorTracker;
this.formName=formName;
this.customImageHandler=customImageHandler;
this.objectStoreStreamRef=objectStoreStreamRef;
this.pdfImages=pdfImages;
this.formLevel=formLevel;
this.pageData=pageData;
this.imagesInFile=imagesInFile;
}
private GenericColorSpace setupXObjectColorspace(final PdfObject XObject, final ImageData imageData){
final int width=imageData.getWidth();
final int height=imageData.getHeight();
final int depth=imageData.getDepth();
final PdfObject ColorSpace=XObject.getDictionary(PdfDictionary.ColorSpace);
//handle colour information
GenericColorSpace decodeColorData=new DeviceRGBColorSpace();
if(ColorSpace!=null){
decodeColorData= ColorspaceFactory.getColorSpaceInstance(currentPdfFile, ColorSpace, cache.XObjectColorspaces);
decodeColorData.setPrinting(isPrinting);
//track colorspace use
cache.put(PdfObjectCache.ColorspacesUsed, decodeColorData.getID(),"x");
if(depth==1 && decodeColorData.getID()== ColorSpaces.DeviceRGB && XObject.getDictionary(PdfDictionary.Mask)==null){
final byte[] data=decodeColorData.getIndexedMap();
//no index or first colour is white so use grayscale
if(decodeColorData.getIndexedMap()==null || (data.length==6 && data[0]==0 && data[1]==0 && data[2]==0)) {
decodeColorData = new DeviceGrayColorSpace();
}
}
}
//fix for odd itext file (/PDFdata/baseline_screens/debug3/Leistung.pdf)
final byte[] indexData=decodeColorData.getIndexedMap();
if(depth==8){
final byte[] objectData=imageData.getObjectData();
if(indexData!=null && decodeColorData.getID()==ColorSpaces.DeviceRGB && width*height==objectData.length) {
final PdfObject newMask = XObject.getDictionary(PdfDictionary.Mask);
if (newMask != null) {
final int[] maskArray = newMask.getIntArray(PdfDictionary.Mask);
//this specific case has all zeros
if (maskArray != null && maskArray.length == 2 && maskArray[0] == 255 && maskArray[0] == maskArray[1] && decodeColorData.getIndexedMap() != null && decodeColorData.getIndexedMap().length == 768) {
//see if index looks corrupt (ie all zeros) We exit as soon as we have disproved
boolean isCorrupt = true;
for (int jj = 0; jj < 768; jj++) {
if (indexData[jj] != 0) {
isCorrupt = false;
jj = 768;
}
}
if (isCorrupt) {
decodeColorData = new DeviceGrayColorSpace();
}
}
}
}
}
//pass through decode params
final PdfObject parms=XObject.getDictionary(PdfDictionary.DecodeParms);
if(parms!=null) {
decodeColorData.setDecodeParms(parms);
}
return decodeColorData;
}
public BufferedImage processImageXObject(PdfObject XObject, String image_name, byte[] objectData, final boolean saveRawData, final String details) throws PdfException {
BufferedImage image=null;
//add filename to make it unique
image_name = parserOptions.getFileName()+ '-' + image_name;
// System.out.println("XObject="+XObject+" "+XObject.getObjectRefAsString());
PdfObject newSMask=XObject.getDictionary(PdfDictionary.SMask);
final PdfObject newMask=XObject.getDictionary(PdfDictionary.Mask);
ImageData imageData=new ImageData(XObject, objectData);
imageData.getFilter(XObject);
GenericColorSpace decodeColorData = setupXObjectColorspace(XObject, imageData);
imageData.setCompCount(decodeColorData.getColorSpace().getNumComponents());
/**
* New code to apply SMask and Mask to data
* (note old isMask)
*/
byte[] convertedData=XObject.getConvertedData();
if(convertedData!=null){ //reuse converted mask data
objectData=convertedData;
decodeColorData=new DeviceRGBColorSpace();
//imageData.setObjectData(objectData);
imageData=null;
}else if(newSMask!=null || newMask!=null){
if(newSMask!=null && XObject.getInt(PdfDictionary.Width)==1 && XObject.getInt(PdfDictionary.Height)==1 && XObject.getInt(PdfDictionary.BitsPerComponent)==8){ //swap out the image with inverted SMask if empty
//silly case we handle in code below // /baseline_screens/11dec/grayscale.pdf
}else{
//WE NEED TO CONVERT JPG to raw DATA in IMAGE
if(imageData.isDCT()){
objectData=JPEGDecoder.getBytesFromJPEG(objectData,decodeColorData,XObject);
imageData.setObjectData(objectData);
XObject.setMixedArray(PdfDictionary.Filter,null);
XObject.setDecodedStream(objectData);
}else if(imageData.isJPX()){
objectData=JPeg2000ImageDecoder.getBytesFromJPEG2000(objectData,decodeColorData,XObject);
imageData.setObjectData(objectData);
XObject.setMixedArray(PdfDictionary.Filter,null);
XObject.setDecodedStream(objectData);
decodeColorData=new DeviceRGBColorSpace();
}
/**
* smask
*/
if(newSMask!=null){
///WE NEED TO CONVERT JPG to raw DATA in smask as well
ImageData smaskImageData=new ImageData(newSMask, null);
smaskImageData.getFilter(newSMask);
GenericColorSpace maskColorSpace = setupXObjectColorspace(newSMask, smaskImageData);
byte[] maskData =currentPdfFile.readStream(newSMask,true,true,false, false,false, newSMask.getCacheName(currentPdfFile.getObjectReader()));
if(1==1){
objectData = SMaskDecoder.applyJPX_JBIG_Smask(imageData, smaskImageData, maskData,XObject, newSMask, decodeColorData, maskColorSpace);
}else{ // old method
maskData = MaskDataDecoder.getSMaskData(maskData,smaskImageData, newSMask,setupXObjectColorspace(newSMask, smaskImageData));
objectData=SMaskDecoder.applySMask(maskData,imageData,decodeColorData, newSMask,XObject);
}
}else{ //mask
byte[] index=decodeColorData.getIndexedMap();
int[] maskArray=newMask.getIntArray(PdfDictionary.Mask);
if(index!=null){
index=decodeColorData.convertIndexToRGB(index);
if(maskArray!=null){
return geIndexedMaskImage(index, imageData, maskArray);
}
objectData=ColorSpaceConvertor.convertIndexToRGBByte(index, imageData.getWidth(), imageData.getHeight(), imageData.getCompCount(), imageData.getDepth(), objectData, false, false);
decodeColorData=new DeviceRGBColorSpace();
imageData.setObjectData(objectData);
decodeColorData.setIndex(null, 0);
// imageData.setCompCount(3);
// imageData.setDepth(8);
}
///WE NEED TO CONVERT JPG to raw DATA in mask as well
ImageData maskImageData=new ImageData(newMask, objectData);
if(maskArray!=null){
return MaskDataDecoder.applyMaskArray(imageData, maskArray);
}
byte[] maskData= currentPdfFile.readStream(newMask, true, true, false, false, false, newMask.getCacheName(currentPdfFile.getObjectReader()));
maskData = MaskDataDecoder.getSMaskData(maskData,maskImageData, newMask,setupXObjectColorspace(newMask, maskImageData));
objectData=MaskDecoder.applyMask(imageData,decodeColorData,newMask,XObject,maskData);
XObject.setConvertedData(objectData);
decodeColorData=new DeviceRGBColorSpace();
XObject.setDictionary(PdfDictionary.Mask, null);
}
// String dest="/Users/markee/Desktop/deviceRGB/"+org.jpedal.DevFlags.currentFile.substring(org.jpedal.DevFlags.currentFile.lastIndexOf("/"));
// ObjectStore.copy(org.jpedal.DevFlags.currentFile, dest);
//also set SMask to null (will set image to DeviceRGB below
//XObject.setDictionary(PdfDictionary.SMask, null);
imageData=null;
}
}
//reset if changed in Mask/SMask code
if(imageData==null){
imageData=new ImageData(XObject, objectData);
decodeColorData = new DeviceRGBColorSpace(true); //sets to 4 comp ARGB
imageData.setCompCount(4);
newSMask=null;
}
isMask= XObject.getBoolean(PdfDictionary.ImageMask);
//tell user and log
if(LogWriter.isOutput()) {
LogWriter.writeLog("Processing XObject: " + image_name + ' ' + XObject.getObjectRefAsString() + " width=" + imageData.getWidth() + " Height=" + imageData.getHeight() +
" Depth=" + imageData.getDepth() + " colorspace=" + decodeColorData);
}
/**
* allow user to process image
*/
if(customImageHandler != null && !(customImageHandler instanceof ImageDataHandler)){
image= customImageHandler.processImageData(gs,XObject);
}
//deal with special case of 1x1 pixel backed onto large inverted Smask which would be very slow in Generic code
//see (11dec/grayscale.pdf)
if(newSMask!=null && XObject.getInt(PdfDictionary.Width)==1 && XObject.getInt(PdfDictionary.Height)==1 && XObject.getInt(PdfDictionary.BitsPerComponent)==8){ //swap out the image with inverted SMask if empty
image = ConvertMaskToImage.convert(newSMask, currentPdfFile);
}else if(customImageHandler==null ||(image==null && !customImageHandler.alwaysIgnoreGenericHandler())) {
image = processImage(decodeColorData,
image_name,
imageData,
isMask,
XObject, saveRawData);
}
//add details to string so we can pass back
if(ImageCommands.trackImages && image!=null && details!=null){
setImageInfo(imageData, details, decodeColorData, image);
}
return image;
}
private static BufferedImage geIndexedMaskImage(byte[] index, ImageData imageData, int[] maskArray) {
int d = imageData.getDepth();
int p = 0;
int c = 0;
boolean[] invisible = new boolean[1 << d];
for (int i = 0; i < maskArray.length; i+=2) {
int start = maskArray[i];
int end = maskArray[i+1];
if(start==end){
invisible[start] = true;
}else{
for (int j = start; j < end; j++) {
invisible[j] = true;
}
}
}
int[] indexColors = new int[index.length / 3];
for (int i = 0; i < indexColors.length; i++) {
indexColors[i] = (255 << 24) | ((index[c++] & 0xff) << 16) | ((index[c++] & 0xff) << 8) | (index[c++] & 0xff);
}
BitReader reader = new BitReader(imageData.getObjectData(), d < 8);
BufferedImage img = new BufferedImage(imageData.getWidth(), imageData.getHeight(), BufferedImage.TYPE_INT_ARGB);
int output[] = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
int imageDim = imageData.getWidth() * imageData.getHeight();
int w = imageData.getWidth();
int wc = 0;
for (int i = 0; i < imageDim; i++) {
int v = reader.getPositive(d);
if(!invisible[v]){
output[p++] = indexColors[v];
}else{
p++;
}
wc++;
if (wc == w) {
int balance = 8 - (reader.getPointer() % 8);
wc = 0;
if (balance != 8) {
reader.getPositive(balance);
}
}
}
return img;
}
private void setImageInfo(final ImageData imageData, final String details, GenericColorSpace decodeColorData, BufferedImage image) {
final int width=imageData.getWidth();
final int height=imageData.getHeight();
//work out effective dpi
float dpi = gs.CTM[0][0];
if(dpi ==0) {
dpi = gs.CTM[0][1];
}
if(dpi <0) {
dpi = -dpi;
}
dpi =(int)(width/dpi*100);
//add details to string
final StringBuilder imageInfo=new StringBuilder(details);
imageInfo.append(" w=");
imageInfo.append(width);
imageInfo.append(" h=");
imageInfo.append(height);
imageInfo.append(' ');
imageInfo.append((int) dpi);
imageInfo.append(' ');
imageInfo.append(ColorSpaces.IDtoString(decodeColorData.getID()));
imageInfo.append(" (");
imageInfo.append(image.getWidth());
imageInfo.append(' ');
imageInfo.append(image.getHeight());
imageInfo.append(" type=");
imageInfo.append(image.getType());
imageInfo.append(')');
if(imagesInFile.isEmpty()) {
imagesInFile = imageInfo.toString();
} else {
imageInfo.append('\n');
imageInfo.append(imagesInFile);
imagesInFile=imageInfo.toString();
}
}
public void setSamplingOnly(final boolean getSamplingOnly){
this.getSamplingOnly=getSamplingOnly;
}
public String getImagesInFile() {
return this.imagesInFile;
}
public void setParameters(final boolean renderPage, final int renderMode, final int extractionMode, final boolean isPrinting, final boolean isType3Font, final boolean useHiResImageForDisplay) {
this.isPrinting=isPrinting;
this.isType3Font=isType3Font;
this.useHiResImageForDisplay=useHiResImageForDisplay;
renderImages=renderPage &&(renderMode & PdfDecoderInt.RENDERIMAGES )== PdfDecoderInt.RENDERIMAGES;
finalImagesExtracted=(extractionMode & PdfDecoderInt.FINALIMAGES) == PdfDecoderInt.FINALIMAGES;
extractRawCMYK=(extractionMode & PdfDecoderInt.CMYKIMAGES)==PdfDecoderInt.CMYKIMAGES;
clippedImagesExtracted=(extractionMode & PdfDecoderInt.CLIPPEDIMAGES)==PdfDecoderInt.CLIPPEDIMAGES;
rawImagesExtracted=(extractionMode & PdfDecoderInt.RAWIMAGES) == PdfDecoderInt.RAWIMAGES;
createScaledVersion = finalImagesExtracted || renderImages;
//Set flag to allow tunring on/off transparency optimisations in printing
String value = System.getProperty("org.jpedal.printTransparency");
if(value!=null){
ImageDecoder.allowPrintTransparency = isPrinting && value.equalsIgnoreCase("true");
}
}
/**
* save the current image, clipping and
* resizing. This gives us a
* clipped hires copy. In reparse, we don't
* need to repeat some actions we know already done.
*/
public void generateTransformedImage(BufferedImage image, final String image_name) {
float x = 0;
float y = 0;
final float w;
final float h;
//if valid image then process
//for the moment lock out rotated images as code broken (applies rotated clip to unrotated image)
if (image != null){
ImageTransformerDouble image_transformation=null;
final boolean isHTML=current.isHTMLorSVG();
if(isHTML) {
current.drawImage(parserOptions.getPageNumber(), image, gs, false, image_name, optionsApplied, -3);
}
/**
* scale the raw image to correct page size (at 72dpi)
*/
//this code is generic code being reused for HTML so breaks on several cases which we just lock out (ie rotation)
final int pageRotation=pageData.getRotation(parserOptions.getPageNumber());
final boolean ignoreRotation= isHTML && (useHiResImageForDisplay && pageRotation==90 ) ;
if(!ignoreRotation){
//if down-sampling image, we use larger size in HTML
//to preserve image quality. ie if image is 1200x1200 for 600x600 hole
//(which is 900x900 when we scale up again, we keep image as 900x900 not 600x600
//if image is already 600x600 we DO NOT upscale
//still needs to be debugged (especially rotated pages)
final float scaling=1;//newScaling=current.getScaling();
// if(isHTML && !useHiResImageForDisplay && newScaling>1 &&
// (
// (gs.CTM[0][0]!=0f && image.getWidth()>Math.abs(gs.CTM[0][0]*newScaling)) ||
// (gs.CTM[1][1]!=0f && image.getHeight()>Math.abs(gs.CTM[1][1]*newScaling)) ||
// (gs.CTM[1][0]!=0f && image.getHeight()>Math.abs(gs.CTM[1][0]*newScaling)) ||
// (gs.CTM[0][1]!=0f && image.getWidth()>Math.abs(gs.CTM[0][1]*newScaling)))){
// scaling=newScaling;
// }
//object to scale and clip. Creating instance does the scaling
if(!isHTML){
image_transformation = new ImageTransformerDouble(gs, image, createScaledVersion, scaling, pageRotation);
}
//extract images either scaled/clipped or scaled then clipped
if(image_transformation!=null){
image_transformation.doubleScaleTransformShear();
//get intermediate image and save
image = image_transformation.getImage();
}
}
//save the scaled/clipped version of image if allowed
if(!isHTML){
String image_type = objectStoreStreamRef.getImageType(currentImage);
if(image_type==null) {
image_type = "tif";
}
BufferedImage outputImage=image;
/**
* convert mask into proper image if saving clipped images
*/
if(isMask){
final int foreground = gs.nonstrokeColorSpace.getColor().getRGB();
final int[] maskCol = new int[4];
maskCol[0] = ((foreground >> 16) & 0xFF);
maskCol[1] = ((foreground >> 8) & 0xFF);
maskCol[2] = ((foreground) & 0xFF);
maskCol[3] = 255;
final BufferedImage img = new BufferedImage(outputImage.getWidth(), outputImage.getHeight(), outputImage.getType());
final Raster src = outputImage.getRaster();
final WritableRaster dest = img.getRaster();
final int[] values = new int[4];
for (int yy = 0; yy < outputImage.getHeight(); yy++) {
for (int xx = 0; xx < outputImage.getWidth(); xx++) {
//get raw color data
src.getPixel(xx, yy, values);
//System.out.println(values[0]+" "+values[1]+" "+values[2]+" "+values[3]+" ");
//if not transparent, fill with color
if (values[3] > 2) {
dest.setPixel(xx, yy, maskCol);
}
}
}
outputImage = img;
}
if(objectStoreStreamRef.saveStoredImage(
"CLIP_"+currentImage,
outputImage,
false,
false,
image_type)) {
errorTracker.addPageFailureMessage("Problem saving " + image);
}
}
/**
* HTML5/JavaFX code is piggybacking on existing functionality to extract hires clipped image.
* We do not need other functionality so we ignore that
*/
if(isHTML){
if (image != null) {
gs.x=x;
gs.y=y;
current.drawImage(parserOptions.getPageNumber(),image,gs,false,image_name,optionsApplied, -2);
}
}else{
if(finalImagesExtracted || renderImages) {
image_transformation.doubleScaleTransformScale();
}
//complete the image and workout co-ordinates
image_transformation.completeImage();
//get initial values
x = image_transformation.getImageX();
y = image_transformation.getImageY();
w = image_transformation.getImageW();
h = image_transformation.getImageH();
//get final image to allow for way we draw 'upside down'
image = image_transformation.getImage();
//allow for null image returned (ie if too small)
if (image != null) {
//store final image on disk & in memory
if(renderImages || finalImagesExtracted || clippedImagesExtracted || rawImagesExtracted){
pdfImages.setImageInfo(currentImage, parserOptions.getPageNumber(), x, y, w, h);
}
//add to screen being drawn
if ((renderImages || !parserOptions.isPageContent())) {
gs.x=x;
gs.y=y;
current.drawImage(parserOptions.getPageNumber(),image,gs,false,image_name,optionsApplied, -1);
}
/**save if required*/
if((!parserOptions.renderDirectly() && parserOptions.isPageContent() && finalImagesExtracted) &&
//save the scaled/clipped version of image if allowed
(ImageCommands.isExtractionAllowed(currentPdfFile))){
String image_type = objectStoreStreamRef.getImageType(currentImage);
if(image_type==null) //Fm can generate null value
{
image_type = "jpg";
}
objectStoreStreamRef.saveStoredImage(
currentImage,
ImageCommands.addBackgroundToMask(image, isMask),
false,
false,
image_type);
}
}
}
} else if(LogWriter.isOutput())
//flag no image and reset clip
{
LogWriter.writeLog("NO image written");
}
}
/**
* save the current image, clipping and resizing. Id reparse, we don't
* need to repeat some actions we know already done.
*/
/**
* save the current image, clipping and resizing.Id reparse, we don't
* need to repeat some actions we know already done.
* @param image
*/
public void generateTransformedImageSingle(BufferedImage image, final String image_name) {
float x, y, w, h;
//if valid image then process
if (image != null) {
// get clipped image and co-ords
final Area clipping_shape = gs.getClippingShape();
/**
* scale the raw image to correct page size (at 72dpi)
*/
//object to scale and clip. Creating instance does the scaling
final ImageTransformer image_transformation;
//object to scale and clip. Creating instance does the scaling
image_transformation =new ImageTransformer(gs,image);
//get initial values
x = image_transformation.getImageX();
y = image_transformation.getImageY();
w = image_transformation.getImageW();
h = image_transformation.getImageH();
//get back image, which will become null if TOO small
image = image_transformation.getImage();
//apply clip as well if exists and not inline image
if (image != null && customImageHandler!=null && clipping_shape != null && clipping_shape.getBounds().getWidth()>1 &&
clipping_shape.getBounds().getHeight()>1 && !customImageHandler.imageHasBeenScaled()) {
//see if clip is wider than image and ignore if so
final boolean ignore_image = clipping_shape.contains(x, y, w, h);
if (!ignore_image) {
//do the clipping
image_transformation.clipImage(clipping_shape);
//get ALTERED values
x = image_transformation.getImageX();
y = image_transformation.getImageY();
w = image_transformation.getImageW();
h = image_transformation.getImageH();
}
}
//alter image to allow for way we draw 'upside down'
image = image_transformation.getImage();
//allow for null image returned (ie if too small)
if (image != null) {
//store final image on disk & in memory
if(finalImagesExtracted || rawImagesExtracted){
pdfImages.setImageInfo(currentImage, parserOptions.getPageNumber(), x, y, w, h);
// if(includeImagesInData){
//
// float xx=x;
// float yy=y;
//
// if(clipping_shape!=null){
//
// int minX=(int)clipping_shape.getBounds().getMinX();
// int maxX=(int)clipping_shape.getBounds().getMaxX();
//
// int minY=(int)clipping_shape.getBounds().getMinY();
// int maxY=(int)clipping_shape.getBounds().getMaxY();
//
// if((xx>0 && xx0 && yy0 && imageData.getpY()>0 && (SamplingFactory.isPrintDownsampleEnabled || !isPrinting)){
sampling=setSampling(imageData, decodeColorData);
if(sampling>1 && multiplyer>1){
sampling = (int) (sampling/ multiplyer);
}
}
//get sampling and exit from this code as we don't need to go further
if(getSamplingOnly){
int w=imageData.getWidth();
int h=imageData.getHeight();
if(imageData.getpX()>0 && imageData.getpY()>0){
final float scaleX=(((float)w)/imageData.getpX());
final float scaleY=(((float)h)/imageData.getpY());
if(scaleX1){
isDownsampled=true;
boolean saveData=false;
//flatten out high res raw data in this case so we can store and resample (see deebug3/DOC002.PDF and DOC003.PDF
if(imageMask && imageData.getWidth()>2000 && imageData.getHeight()>2000 && imageData.getDepth()==1 && decodeColorData.getID()==ColorSpaces.DeviceRGB && gs.CTM[0][0]>0 && gs.CTM[1][1]>0){
saveData=true;
}
if(imageData.getDepth()==1 && (decodeColorData.getID()!=ColorSpaces.DeviceRGB || index==null)){
//save raw 1 bit data so w ecan resample in Viewer
//breaks if form rotated so only use at top level
// (sample file breaks so we added this as hack for fattura 451-10 del 31.10.10.pdf in customers3)
if(formLevel<2 && (saveData ||(!imageMask && saveRawData && decodeColorData.getID()==ColorSpaces.DeviceGray ))){
saveRawOneBitDataForResampling(saveData,imageData, index, arrayInverted, decodeColorData, maskCol, XObject);
}
//make 1 bit indexed flat
if(index!=null) {
index = decodeColorData.convertIndexToRGB(index);
}
decodeColorData=OneBitDownSampler.downSample(sampling, imageData, imageMask, arrayInverted, maskCol, index, decodeColorData);
}else if(imageData.getDepth()==8 && (Filters==null || (!imageData.isDCT() && !imageData.isJPX()))){
decodeColorData=EightBitDownSampler.downSample(imageData, decodeColorData, sampling, index);
}
}
return convertImageDataToJavaImage(image, imageData, decodeArray, Filters, index, decodeColorData, imageMask, isDownsampled, maskCol, name, arrayInverted, XObject, rawd);
}
private BufferedImage convertImageDataToJavaImage(BufferedImage image,final ImageData imageData, final float[] decodeArray, PdfArrayIterator Filters, byte[] index, GenericColorSpace decodeColorData, final boolean imageMask, boolean isDownsampled, final byte[] maskCol, final String name, boolean arrayInverted, final PdfObject XObject, final int rawd) throws RuntimeException, PdfException {
int w=imageData.getWidth();
int h=imageData.getHeight();
int d=imageData.getDepth();
byte[] data=imageData.getObjectData();
/**handle any decode array*/
if(decodeArray==null || decodeArray.length == 0){
}else if(Filters!=null &&(imageData.isJPX()||imageData.isDCT())){ //don't apply on jpegs
}else if(index==null){ //for the moment ignore if indexed (we may need to recode)
ImageCommands.applyDecodeArray(data, d, decodeArray,decodeColorData.getID());
}
if (imageMask) {
image = makeMaskImage(h, w, image, d, data, isDownsampled, imageData, imageMask, decodeColorData, maskCol, name);
} else if (image != null){
// Do nothing if we've already had the image decoded for us by user code.
}else if (Filters == null) { //handle no filters
//save out image
if(LogWriter.isOutput()) {
LogWriter.writeLog("Image " + name + ' ' + w + "W * " + h + "H with No Compression at BPC " + d);
}
image =makeImage(decodeColorData,w,h,d,data,imageData.getCompCount());
} else if (imageData.isDCT()) {
/**
* get image data,convert to BufferedImage from JPEG & save out
*/
if(decodeColorData.getID()== ColorSpaces.DeviceCMYK && extractRawCMYK){
if(LogWriter.isOutput()) {
LogWriter.writeLog("Raw CMYK image " + name + " saved.");
}
if(!objectStoreStreamRef.saveRawCMYKImage(data, name)) {
errorTracker.addPageFailureMessage("Problem saving Raw CMYK image " + name);
}
}
image = JPegImageDecoder.decode(name, w, h, arrayInverted, decodeColorData, data, decodeArray, imageData, XObject, errorTracker, parserOptions);
//set in makeImage so not set for JPEGS - we do it explicitly here
if(!isHTML){
optionsApplied=setRotationOptionsOnJPEGImage(gs, imageStatus, optionsApplied);
}
}else if(imageData.isJPX()){
image = JPeg2000ImageDecoder.decode(name, w, h, decodeColorData, data, decodeArray, imageData, d);
//set in makeImage so not set for JPEGS - we do it explicitly here
if(!isHTML){
optionsApplied=setRotationOptionsOnJPEGImage(gs, imageStatus, optionsApplied);
}
} else { //handle other types
if(LogWriter.isOutput()) {
LogWriter.writeLog(name + ' ' + w + "W * " + h + "H BPC=" + d + ' ' + decodeColorData);
}
image =makeImage(decodeColorData,w,h,d,data,imageData.getCompCount());
}
if (image != null) {
image = addOverPrint(decodeColorData, data, image, imageData);
if(image==null) {
return null;
}
if(!current.isHTMLorSVG() && !parserOptions.renderDirectly() && (finalImagesExtracted || rawImagesExtracted)) {
saveImage(name, createScaledVersion, image, "jpg");
}
}
if(image == null && !imageData.isRemoved()){
parserOptions.imagesProcessedFully=false;
}
//apply any transfer function
final PdfObject TR=gs.getTR();
if(TR!=null){ //array of values
image = ImageCommands.applyTR(image, TR, currentPdfFile);
}
PdfObject DecodeParms=XObject.getDictionary(PdfDictionary.DecodeParms);
//try to simulate some of blend by removing white if not bottom image
if(DecodeParms!=null && DecodeParms.getInt(PdfDictionary.Blend)!=PdfDictionary.Unknown && current.hasObjectsBehind(gs.CTM) && image!=null && image.getType()!=2 && image.getType()!=1 && (!imageData.isDCT() || DecodeParms.getInt(PdfDictionary.QFactor)==0)) {
image = ImageCommands.makeBlackandWhiteTransparent(image);
}
//sharpen 1 bit
if(rawd==1 && imageData.getpX()>0 && imageData.getpY()>0 && ImageCommands.sharpenDownsampledImages && (decodeColorData.getID()==ColorSpaces.DeviceGray || decodeColorData.getID()==ColorSpaces.DeviceRGB)){
final Kernel kernel = new Kernel(3, 3,
new float[] {
-1, -1, -1,
-1, 9, -1,
-1, -1, -1});
final BufferedImageOp op = new ConvolveOp(kernel);
image = op.filter(image, null);
}
/**
* transparency slows down printing so try to reduce if possible in printing
*/
if(!allowPrintTransparency && imageData.getMode()==ImageCommands.ID && isPrinting && image!=null && d==1 && maskCol!=null && maskCol[0]==0 && maskCol[1]==0 && maskCol[2]==0 && maskCol[3]==0){
final int iw=image.getWidth();
final int ih=image.getHeight();
final BufferedImage newImage=new BufferedImage(iw,ih,BufferedImage.TYPE_BYTE_GRAY);
newImage.getGraphics().setColor(Color.WHITE);
newImage.getGraphics().fillRect(0,0,iw,ih);
newImage.getGraphics().drawImage(image, 0, 0, null);
image=newImage;
}
if (imageMask && gs.nonstrokeColorSpace.getColor().isTexture()) { //case 19095 vistair
float mm[][] = gs.CTM;
AffineTransform affine = new AffineTransform(mm[0][0], mm[0][1], mm[1][0], mm[1][1], mm[2][0], mm[2][1]);
BufferedImage temp = ((PatternColorSpace)gs.nonstrokeColorSpace).getRawImage(w,h,affine);
BufferedImage scrap = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
if(temp!=null){
TexturePaint tp = new TexturePaint(temp, new Rectangle(0,0,temp.getWidth(),temp.getHeight()));
Graphics2D g2 = scrap.createGraphics();
g2.setPaint(tp);
Rectangle rect = new Rectangle(0,0,w,h);
g2.fill(rect);
}
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if (image.getRGB(x, y) == -16777216) { //255 0 0 0
int pRGB = scrap.getRGB(x, y);
image.setRGB(x, y, pRGB);
}
}
}
}
return image;
}
private BufferedImage makeMaskImage(int h, int w, BufferedImage image, int d, byte[] data, boolean isDownsampled, final ImageData imageData, final boolean imageMask, GenericColorSpace decodeColorData, final byte[] maskCol, final String name) {
/** create an image from the raw data*/
/**
* allow for 1 x 1 pixels scaled up or fine lines
*/
final float ratio=((float)h)/(float)w;
if((isPrinting && ratio<0.1f && w>4000 && h>1) || (ratio<0.001f && w>4000 && h>1) || (w==1 && h==1)){// && data[0]!=0){
ConvertMaskToShape.convert(gs, current, parserOptions);
image=null;
}else if(h==2 && d==1 && ImageCommands.isRepeatingLine(data, h)) {
ConvertImageToShape.convert(data, h, gs, current, parserOptions);
image=null;
}else {
image = MaskDecoder.createMaskImage(isDownsampled, (isPrinting && !allowPrintTransparency), gs, isType3Font, current,data, image, w, h, imageData, imageMask, d, decodeColorData, maskCol, name);
}
return image;
}
private BufferedImage addOverPrint(GenericColorSpace decodeColorData, byte[] data, BufferedImage image, final ImageData imageData) {
/**handle any soft mask*/
final int colorspaceID=decodeColorData.getID();
if(image!=null) {
image = ImageCommands.simulateOverprint(decodeColorData, data, imageData.isDCT(), imageData.isJPX(), image, colorspaceID, current, gs);
}
return image;
}
public void saveRawOneBitDataForResampling(boolean saveData, final ImageData imageData, byte[] index, boolean arrayInverted, GenericColorSpace decodeColorData, final byte[] maskCol, final PdfObject XObject) {
byte[] data=imageData.getObjectData();
//copy and turn upside down first
final int count=data.length;
byte[] turnedData=new byte[count];
System.arraycopy(data,0,turnedData,0,count);
boolean isInverted=!saveData && (parserOptions.renderDirectly() || useHiResImageForDisplay) && RenderUtils.isInverted(gs.CTM);
if(parserOptions.renderDirectly()){
isInverted=false;
}
if(isInverted){//invert at byte level with copy
turnedData = ImageOps.invertImage(turnedData, imageData.getWidth(), imageData.getHeight(), imageData.getDepth(), 1, index);
}
//invert all the bits if needed before we store
if(arrayInverted){
for(int aa=0;aamaxAllowed) {
pX = maxAllowed;
}
if(pY>maxAllowed) {
pY = maxAllowed;
}
}
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;
}
imageData.setpX(pX);
imageData.setpY(pY);
return sampling;
}
private void setDownsampledImageSize(ImageData imageData, PdfObject XObject, float multiplyer, GenericColorSpace decodeColorData) {
int w=imageData.getWidth();
int h=imageData.getHeight();
if(isPrinting && SamplingFactory.isPrintDownsampleEnabled && w<4000){
imageData.setpX(pageData.getCropBoxWidth(parserOptions.getPageNumber())*4);
imageData.setpY(pageData.getCropBoxHeight(parserOptions.getPageNumber())*4);
}else if(SamplingFactory.downsampleLevel== SamplingFactory.high || getSamplingOnly){// && w>500 && h>500){ // ignore small items
//ensure all positive for comparison
final float[][] CTM=new float[3][3];
for(int ii=0;ii<3;ii++){
for(int jj=0;jj<3;jj++){
if(gs.CTM[ii][jj]<0) {
CTM[ii][jj] = -gs.CTM[ii][jj];
} else {
CTM[ii][jj] = gs.CTM[ii][jj];
}
}
}
if(CTM[0][0]==0 || CTM[0][0]1){
imageData.setpX((int) (imageData.getpX()* multiplyer));
imageData.setpY((int) (imageData.getpX()* multiplyer));
}
//avoid for scanned text
if(imageData.getDepth()==1 && (XObject.getObjectType()!=PdfDictionary.Mask) &&
decodeColorData.getID()== ColorSpaces.DeviceGray && imageData.getHeight()<300){
imageData.setpX(0);
imageData.setpY(0);
}
}
/**
* needs to be explicitly set for JPEG images if getting image from object
*/
static int setRotationOptionsOnJPEGImage(final GraphicsState gs, final int imageStatus, final int optionsApplied) {
if(imageStatus>0 && gs.CTM[0][0]>0 && gs.CTM[0][1]>0 && gs.CTM[1][1]>0 && gs.CTM[1][0]<0){
/**/
//we need a different op for Image and viewer as we handle in diff ways
if(imageStatus==IMAGE_getImageFromPdfObject){
//optionsApplied=optionsApplied+ PDFImageProcessing.IMAGE_INVERTED;
gs.CTM[0][1]=-gs.CTM[0][1];
//gs.CTM[1][0]=gs.CTM[1][0];
gs.CTM[1][1]=-gs.CTM[1][1];
gs.CTM[2][1] -= gs.CTM[1][1];
}
/**/
}else if(optionsApplied>0 && gs.CTM[0][0]<0 && gs.CTM[1][1]<0 && gs.CTM[0][1]==0 && gs.CTM[1][0]==0){ //fix for elfo.pdf
//optionsApplied=optionsApplied+ PDFImageProcessing.IMAGE_INVERTED;
//gs.CTM[0][1]=-gs.CTM[0][1];
//gs.CTM[1][0]=gs.CTM[1][0];
gs.CTM[1][1]=-gs.CTM[1][1];
gs.CTM[2][1]=gs.CTM[2][1]-gs.CTM[1][1]+gs.CTM[1][0];
//gs.CTM[2][0]=gs.CTM[2][0]+30;
gs.CTM[2][0] -= gs.CTM[0][1];
}
return optionsApplied;
}
private void saveImage(final String name, final boolean createScaledVersion,
final BufferedImage image, String type) {
if (image!=null && image.getSampleModel().getNumBands() == 1) {
type = "tif";
}
if(parserOptions.isPageContent() &&(renderImages || finalImagesExtracted || clippedImagesExtracted || rawImagesExtracted)){
/**create copy and scale if required*/
objectStoreStreamRef.saveStoredImage(name,ImageCommands.addBackgroundToMask(image, isMask),false,createScaledVersion,type);
}
}
/**
* turn raw data into a BufferedImage
*/
private BufferedImage makeImage(final GenericColorSpace decodeColorData,int w,int h,int d, byte[] data, final int comp) {
//ensure correct size
if(decodeColorData.getID()== ColorSpaces.DeviceGray){
data = correctDataArraySize(d, w, h, data);
}
//final ColorSpace cs=decodeColorData.getColorSpace();
final int ID=decodeColorData.getID();
BufferedImage image = null;
byte[] index =decodeColorData.getIndexedMap();
optionsApplied=PDFImageProcessing.NOTHING;
if (index != null) {
image = IndexedImage.make(w, h, decodeColorData, index, d, data);
} else if (d == 1) {
image =BinaryImage.make(w, h, data, decodeColorData, d);
}else if(ID==ColorSpaces.Separation || ID==ColorSpaces.DeviceN){
if(LogWriter.isOutput()) {
LogWriter.writeLog("Converting Separation/DeviceN colorspace to sRGB ");
}
image=decodeColorData.dataToRGB(data,w,h);
} else{
switch(comp){
case 4: //handle CMYK or ICC or ARGB
if(decodeColorData.getID()==ColorSpaces.DeviceRGB){
image = ColorSpaceConvertor.createARGBImage(w,h,data);
}else{
image =ColorSpaceConvertor.convertFromICCCMYK(w,h,data);
}
break;
case 3:
image = ThreeComponentImage.make(d, data, index, w, h);
break;
case 1:
image =OneBitImage.make(d, w, h, data);
break;
}
}
return image;
}
static byte[] correctDataArraySize(final int d, final int w, final int h, byte[] data) {
if(d==1){
final int requiredSize=((w+7)>>3)*h;
final int oldSize=data.length;
if(oldSize