All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.parser.image.ImageDecoder 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
 *
     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.Graphics2D;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import org.jpedal.color.*;
import org.jpedal.exception.PdfException;
import org.jpedal.external.ErrorTracker;
import org.jpedal.external.ImageDataHandler;
import org.jpedal.external.ImageHandler;
import org.jpedal.images.ImageTransformer;
import org.jpedal.images.ImageTransformerDouble;
import org.jpedal.images.SamplingFactory;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.io.ObjectStore;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.PdfImageData;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.raw.PdfArrayIterator;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.parser.BaseDecoder;
import org.jpedal.parser.ParserOptions;
import org.jpedal.parser.PdfObjectCache;
import org.jpedal.parser.ValueTypes;
import org.jpedal.parser.image.data.ImageData;
import org.jpedal.parser.image.downsample.DownSampler;
import org.jpedal.parser.image.mask.MaskDataDecoder;
import org.jpedal.parser.image.mask.MaskDecoder;
import org.jpedal.parser.image.mask.SMaskDecoder;
import org.jpedal.parser.image.utils.ConvertMaskToImage;
import org.jpedal.render.SwingDisplay;
import org.jpedal.utils.LogWriter;

public class ImageDecoder extends BaseDecoder {

    private boolean cacheLargeImages;

    //Allow print to use transparency in printing instead of removing it
    public static boolean allowPrintTransparency;

    protected ParserOptions parserOptions;

    final PdfImageData pdfImages;

    private boolean getSamplingOnly;

    //flag to show if image transparent*/
    boolean isMask = true;

    String imagesInFile;

    PdfObjectCache cache;

    final ImageHandler customImageHandler;

    final PdfPageData pageData;

    final ObjectStore objectStoreStreamRef;


    //name of current image in pdf
    String currentImage = "";

    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 PdfPageData pageData, final String imagesInFile) {

        this.imageCount = imageCount;

        this.currentPdfFile = currentPdfFile;
        this.errorTracker = errorTracker;

        this.customImageHandler = customImageHandler;
        this.objectStoreStreamRef = objectStoreStreamRef;

        this.pdfImages = pdfImages;
        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();

        //handle colour information
        GenericColorSpace decodeColorData = new DeviceRGBColorSpace();

        final PdfArrayIterator ColorSpace = XObject.getMixedArray(PdfDictionary.ColorSpace);
        if (ColorSpace.getTokenCount() > 0) { //if not set will be zero
            decodeColorData = ColorspaceFactory.getColorSpaceInstance(currentPdfFile, ColorSpace);
        }

        decodeColorData.setPrinting(parserOptions.isPrinting());

        //track colorspace use
        cache.put(PdfObjectCache.ColorspacesUsed, decodeColorData.getID(), "x");

        //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);
                final int[] maskArray = XObject.getIntArray(PdfDictionary.Mask);

                if (newMask != null || maskArray != null) {

                    //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();
                        }
                    }
                }
            }
        }

        return decodeColorData;
    }

    public BufferedImage processImageXObject(final PdfObject XObject, String image_name, byte[] objectData, 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);
        final int[] maskArray = XObject.getIntArray(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)
         */
        final 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 || maskArray != 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);
                    if (decodeColorData.getID() == ColorSpaces.DeviceN) {
                        objectData = ((DeviceNColorSpace) decodeColorData).getRGBBytes(objectData, imageData.getWidth(), imageData.getHeight());
                    }
                    imageData.setObjectData(objectData);
                    XObject.setMixedArray(PdfDictionary.Filter, null);
                    XObject.setDecodedStream(objectData);
                    decodeColorData = new DeviceRGBColorSpace();
                }

                if (newSMask != null) {
                    ///WE NEED TO CONVERT JPG to raw DATA in smask as well
                    final ImageData smaskImageData = new ImageData(newSMask, null);
                    smaskImageData.getFilter(newSMask);
                    final 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);
                    }

                    XObject.setConvertedData(objectData);

                } else { //mask

                    byte[] index = decodeColorData.getIndexedMap();

                    if (index != null) {
                        index = decodeColorData.convertIndexToRGB(index);

                        if (maskArray != null) {
                            return getIndexedMaskImage(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
                    final ImageData maskImageData;
                    if (newMask == null) {
                        maskImageData = new ImageData(XObject, objectData);
                    } else {
                        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);
                    XObject.setIntArray(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);

        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); //user gets raw JPEG data
        }

        //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, imageData, isMask, XObject);
        }

        //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 getIndexedMaskImage(final byte[] index, final ImageData imageData, final int[] maskArray) {
        final int d = imageData.getDepth();
        int p = 0;
        int c = 0;

        final boolean[] invisible = new boolean[1 << d];

        for (int i = 0; i < maskArray.length; i += 2) {
            final int start = maskArray[i];
            final int end = maskArray[i + 1];

            if (start == end) {
                invisible[start] = true;
            } else {
                for (int j = start; j < end; j++) {
                    invisible[j] = true;
                }
            }
        }

        final 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);
        }

        final BitReader reader = new BitReader(imageData.getObjectData(), d < 8);

        final BufferedImage img = new BufferedImage(imageData.getWidth(), imageData.getHeight(), BufferedImage.TYPE_INT_ARGB);
        final int[] output = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();

        final int imageDim = imageData.getWidth() * imageData.getHeight();
        final int w = imageData.getWidth();
        int wc = 0;
        for (int i = 0; i < imageDim; i++) {
            final int v = reader.getPositive(d);
            if (!invisible[v]) {
                output[p++] = indexColors[v];
            } else {
                p++;
            }
            wc++;
            if (wc == w) {
                final 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, final GenericColorSpace decodeColorData, final 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;
    }

    @Override
    public void setParams(final ParserOptions parserOptions) {

        this.parserOptions = parserOptions;


        //Set flag to allow tunring on/off transparency optimisations in printing
        String value = System.getProperty("org.jpedal.printTransparency");
        if (value != null) {
            ImageDecoder.allowPrintTransparency = parserOptions.isPrinting() && value.equalsIgnoreCase("true");
        }

        //Set flag to allow tunring on/off transparency optimisations in printing
        value = System.getProperty("org.jpedal.viewerLargeImageCaching");
        if (value != null) {
            cacheLargeImages = value.equalsIgnoreCase("true");
        }
    }

    /**
     * save the current image, clipping and
     * resizing clip. This gives us a  clipped hires copy.
     */
    public void generateClippedImage(BufferedImage image) {

        final int pageRotation = pageData.getRotation(parserOptions.getPageNumber());

        //object to scale and clip. Creating instance does the scaling
        final ImageTransformerDouble image_transformation = new ImageTransformerDouble(gs, image, parserOptions.createScaledVersion(), 1, 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();
        }

        //convert mask into proper image if saving clipped images
        if (isMask) {
            image = convertMaskToImage(image, gs.nonstrokeColorSpace.getColor().getRGB());
        }

        //@suda - image saved here is being saved out as garbage. Do we need to alter colorspace or issue in our image decoder.      
        //releate comment in JDeliHelper
        if (objectStoreStreamRef.saveStoredImageAsBytes("CLIP_" + currentImage, image, false)) {
            errorTracker.addPageFailureMessage("Problem saving " + image);
        }

        //complete the image and workout co-ordinates
        image_transformation.completeImage();

        //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 (parserOptions.imagesNeeded()) {

                //get initial values
                final float x = image_transformation.getImageX();
                final float y = image_transformation.getImageY();
                final float w = image_transformation.getImageW();
                final float h = image_transformation.getImageH();

                pdfImages.setImageInfo(currentImage, parserOptions.getPageNumber(), x, y, w, h);
            }
//
//            //save the scaled/clipped version of image if allowed
//            if(parserOptions.isFinalImagesExtracted()){
//                
//                image_transformation.doubleScaleTransformScale();
//                
//                objectStoreStreamRef.saveStoredImage(
//                        currentImage,
//                        ImageCommands.addBackgroundToMask(image, isMask),
//                        false,
//                        "png");
//
//            }
        }
    }

    private static BufferedImage convertMaskToImage(final BufferedImage outputImage, final int foreground) {

        final int[] maskCol = {((foreground >> 16) & 0xFF), ((foreground >> 8) & 0xFF), ((foreground) & 0xFF), 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];
        final int w = outputImage.getWidth(), h = outputImage.getHeight();
        for (int yy = 0; yy < h; yy++) {
            for (int xx = 0; xx < w; 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);
                }
            }
        }
        return img;
    }

    /**
     * save the current image in raw and scaled/clipped version
     *
     * @param image
     */
    public void generateTransformedImageSingle(BufferedImage image) {

        //
        if (parserOptions.isRawImagesExtracted()) {
            objectStoreStreamRef.saveStoredImageAsBytes('R' + currentImage, image, false);
        }

        // get clipped image and co-ords
        final Area clipping_shape = gs.getClippingShape();

        //object to scale and clip. Creating instance does the scaling
        final ImageTransformer image_transformation = new ImageTransformer(gs, image);

        //get initial values
        float x = image_transformation.getImageX();
        float y = image_transformation.getImageY();
        float w = image_transformation.getImageW();
        float h = image_transformation.getImageH();

        //apply clip as well if exists and not inline image
        if (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
            if (!clipping_shape.contains(x, y, w, h)) {
                //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();
            }
        }

        image = image_transformation.getImage();

        //allow for null image returned (ie if too small)
        if (image != null) {

            pdfImages.setImageInfo(currentImage, parserOptions.getPageNumber(), x, y, w, h);

            //save the scaled/clipped version of image if allowed
            if (parserOptions.isFinalImagesExtracted()) {
                objectStoreStreamRef.saveStoredImageAsBytes(currentImage, ImageCommands.addBackgroundToMask(image, isMask), false);
            }
        }
    }

    /**
     * read in the image and process and save raw image
     */
    BufferedImage processImage(GenericColorSpace decodeColorData,
                               final ImageData imageData, final boolean imageMask,
                               final PdfObject XObject) throws PdfException {

        //track its use
        cache.put(PdfObjectCache.ColorspacesUsed, decodeColorData.getID(), "x");

        imageData.getFilter(XObject);

        BufferedImage image = null; //

        //allow user to process image
        if (customImageHandler instanceof ImageDataHandler) {
            image = customImageHandler.processImageData(gs, XObject);
        } else if (imageData.isJPX()) {
            removeJPXEncodingFromImageData(imageData, XObject);
        } else if (imageData.isDCT()) {

            int adobeColorTransform = 1;
            final PdfObject decodeParms = XObject.getDictionary(PdfDictionary.DecodeParms);
            if (decodeParms != null) {
                adobeColorTransform = decodeParms.getInt(PdfDictionary.ColorTransform);
            }

            byte[] objectData;
            try {
                objectData = JPEGDecoder.getUnconvertedBytesFromJPEG(imageData.getObjectData(), adobeColorTransform);
                if (objectData == null) { //fix for lgpl version
                    objectData = JPEGDecoder.getBytesFromJPEGWithImageIO(imageData.getObjectData(), decodeColorData, XObject);
                    decodeColorData = new DeviceRGBColorSpace();
                    imageData.setCompCount(3);
                    imageData.setDecodeArray(null);
                }
            } catch (final Exception e) {

                LogWriter.writeLog("[PDF] Exception " + e + " Processing JPEG data in ImageDecoder");

                objectData = JPEGDecoder.getBytesFromJPEGWithImageIO(imageData.getObjectData(), decodeColorData, XObject);
                decodeColorData = new DeviceRGBColorSpace();
                imageData.setCompCount(3);
                imageData.setDecodeArray(null);
            }
            XObject.setMixedArray(PdfDictionary.Filter, null);
            XObject.setDecodedStream(objectData);
            imageData.setObjectData(objectData);
            imageData.setDCT(false);
            imageData.setDepth(8);

            imageData.wasDCT(true);
        }

        if (image != null) {
            return image;
        } else {
            return convertDataToImage(imageMask, imageData, XObject, decodeColorData);
        }
    }

    static void removeJPXEncodingFromImageData(final ImageData imageData, final PdfObject XObject) {

        final byte[] objectData = JPeg2000ImageDecoder.getUnconvertedBytesFromJPEG2000(imageData.getObjectData());
        imageData.setObjectData(objectData);
        XObject.setMixedArray(PdfDictionary.Filter, null);
        XObject.setDecodedStream(objectData);
        imageData.setDepth(8);
        imageData.setIsJPX(false);
    }

    private BufferedImage convertDataToImage(final boolean imageMask, final ImageData imageData, final PdfObject XObject, GenericColorSpace decodeColorData) {

        int sampling = 1;

        BufferedImage image;

        //setup any imageMask
        byte[] maskCol = null;
        if (imageMask) {
            maskCol = ImageCommands.getMaskColor(gs);
        }

        //setup sub-sampling
        if (parserOptions.isRenderPage() && streamType != ValueTypes.PATTERN) {
            setDownsampledImageSize(imageData, XObject, multiplyer, decodeColorData);
        }

        //down-sample size if displaying (some cases excluded at present)
        if (parserOptions.isRenderPage() &&
                decodeColorData.getID() != ColorSpaces.ICC &&
                imageData.getMode() != ImageCommands.ID &&
                (imageData.getDepth() == 1 || imageData.getDepth() == 8)
                && imageData.getpX() > 0 && imageData.getpY() > 0 && (SamplingFactory.isPrintDownsampleEnabled || !parserOptions.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) {

            final int w = imageData.getWidth();
            final 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 (scaleX > 100 || scaleY > 100) {
                    //ignore 
                } else if (scaleX < scaleY) {
                    parserOptions.setSamplingUsed(scaleX);
                } else {
                    parserOptions.setSamplingUsed(scaleY);
                }
            }
            return null;
        }

        //handle any decode array
        final float[] decodeArray = imageData.getDecodeArray();
        if (decodeArray != null && decodeArray.length > 0 && decodeColorData.getIndexedMap() == null) { //for the moment ignore if indexed (we may need to recode)
            ImageCommands.applyDecodeArray(imageData.getObjectData(), imageData.getDepth(), decodeArray, decodeColorData.getID());

        }

        //apply any transfer function directly to data (does not work on DCT data)
        final Object[] TRvalues = gs.getTR();
        if (TRvalues != null) { //array of values
            ImageCommands.applyTR(imageData, TRvalues, currentPdfFile);
        }


        //switch to 8 bit and reduce bw image size by averaging
        if (sampling > 1 && !imageData.wasDCT()) {

            //choose whether we cache raw data so we can redecode images at different resolutions in Viewer
            if (cacheLargeImages && decodeColorData.getIndexedMap() == null) {

                decodeColorData.dataToRGBByteArray(imageData.getObjectData(), imageData.getWidth(), imageData.getHeight());

                if (SwingDisplay.testSampling) {
                    System.out.println("cached image full size= " + imageData.getWidth() + ", " + imageData.getHeight() + " Bits=" + imageData.getDepth() +
                            " " + decodeColorData + " count=" + imageData.getCompCount() + " bytes=" + imageData.getObjectData().length);
                }

                objectStoreStreamRef.saveRawImageData(parserOptions.getPageNumber() + String.valueOf(imageCount), imageData.getObjectData(),
                        imageData.getWidth(), imageData.getHeight(), imageData.getDepth(), imageData.getpX(), imageData.getpY(),
                        maskCol, ColorSpaces.DeviceRGB);

            }

            decodeColorData = DownSampler.downSampleImage(decodeColorData, imageData, maskCol, sampling);
        }


        if (maskCol != null) {
            image = ImageDataToJavaImage.makeMaskImage(parserOptions, gs, current, imageData, decodeColorData, maskCol);
        } else { //handle other types

            LogWriter.writeLog(imageData.getWidth() + "W * " + imageData.getHeight() + "H BPC=" + imageData.getDepth() + ' ' + decodeColorData);

            image = ImageDataToJavaImage.makeImage(decodeColorData, imageData);

        }

        if (image == null && !imageData.isRemoved()) {
            parserOptions.imagesProcessedFully = false;
        }

        if (maskCol != null && gs.nonstrokeColorSpace.getColor().isTexture()) {  //case 19095 vistair
            convertTextureToImage(imageData, image);
        }

        //image=ImageDataToJavaImage.sharpen(image);

        return image;
    }

    private void convertTextureToImage(final ImageData imageData, final BufferedImage image) {

        final float[][] mm = gs.CTM;
        final int w = imageData.getWidth();
        final int h = imageData.getHeight();

        final AffineTransform affine = new AffineTransform(mm[0][0], mm[0][1], mm[1][0], mm[1][1], mm[2][0], mm[2][1]);

        final BufferedImage temp = ((PatternColorSpace) gs.nonstrokeColorSpace).getRawImage(affine);
        final BufferedImage scrap = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

        if (temp != null) {
            final TexturePaint tp = new TexturePaint(temp, new Rectangle(0, 0, temp.getWidth(), temp.getHeight()));
            final Graphics2D g2 = scrap.createGraphics();
            g2.setPaint(tp);
            final 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
                    final int pRGB = scrap.getRGB(x, y);
                    image.setRGB(x, y, pRGB);
                }
            }
        }
    }

    private int setSampling(final ImageData imageData, final GenericColorSpace decodeColorData) {

        //see what we could reduce to and still be big enough for page
        int sampling = 1;

        final int w = imageData.getWidth();
        final int h = imageData.getHeight();

        int newW = w;
        int newH = h;

        int pX = imageData.getpX();
        int pY = imageData.getpY();
        //limit size (allow bigger grayscale
        if (multiplyer <= 1 && !parserOptions.isPrinting()) {

            int maxAllowed = 1000;
            if (decodeColorData.getID() == ColorSpaces.DeviceGray) {
                maxAllowed = 4000;
            }
            if (pX > maxAllowed) {
                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(final ImageData imageData, final PdfObject XObject, final float multiplyer, final GenericColorSpace decodeColorData) {

        final int w = imageData.getWidth();
        final int h = imageData.getHeight();

        if (parserOptions.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] < CTM[0][1]) {
                imageData.setpX((int) (CTM[0][1]));
            } else {
                imageData.setpX((int) (CTM[0][0]));
            }

            if (CTM[1][1] == 0 || CTM[1][1] < CTM[1][0]) {
                imageData.setpY((int) (CTM[1][0]));
            } else {
                imageData.setpY((int) (CTM[1][1]));
            }

            //don't bother on small itemsS
            if (!getSamplingOnly && (w < 500 || (h < 600 && w < 1000) || imageData.isJPX())) { //change??
                imageData.setpX(0);
                imageData.setpX(0);

            }

        } else if (SamplingFactory.downsampleLevel == SamplingFactory.medium) {
            imageData.setpX(pageData.getCropBoxWidth(parserOptions.getPageNumber()));
            imageData.setpY(pageData.getCropBoxHeight(parserOptions.getPageNumber()));
        }

        final boolean hasMask = XObject.getRawObjectType() == PdfDictionary.Mask || XObject.getIntArray(PdfDictionary.Mask) != null;
        /*
         * turn off all scaling and allow user to control if switched off or HTML/svg/JavaFX
         * (we still trap very large images in these cases as they blow away the
         * memory footprint)
         */
        final int maxHTMLImageSize = 4000;
        if ((w < maxHTMLImageSize && h < maxHTMLImageSize &&
                current.isHTMLorSVG() &&
                (imageData.getDepth() != 1 || !hasMask))) {
            imageData.setpX(-1);
            imageData.setpY(1);
        }

        //needs to be factored in or images poor on hires modes
        if ((imageData.isDCT() || imageData.isJPX()) && multiplyer > 1) {
            imageData.setpX((int) (imageData.getpX() * multiplyer));
            imageData.setpY((int) (imageData.getpX() * multiplyer));
        }

        //avoid for scanned text
        if (imageData.getDepth() == 1 && !hasMask &&
                decodeColorData.getID() == ColorSpaces.DeviceGray && imageData.getHeight() < 300) {

            imageData.setpX(0);
            imageData.setpY(0);
        }

    }


    public void setRes(final PdfObjectCache cache) {
        this.cache = cache;
    }

    public int processImage(final String s, final int dataPointer, final PdfObject xObject) throws Exception {
        return 0;
    }

    public int processImage(final int dataPointer, final int startInlineStream, final byte[] stream, final int tokenNumber) throws Exception {
        return 0;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy