org.gmdev.pdftrick.utils.CustomExtraImgReader.java Source code

Java tutorial

Introduction

Here is the source code for org.gmdev.pdftrick.utils.CustomExtraImgReader.java

Source

/*
 * This file is part of the PdfTrick project.
 * Copyright: (C) 2014
 * Author: Gian Luca Mori
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information, please contact Gian Luca Mori at this
 * address: giansluca@gmail.com
 */
package org.gmdev.pdftrick.utils;

import java.awt.Image;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
import org.apache.commons.imaging.formats.jpeg.JpegImageParser;
import org.apache.commons.imaging.formats.jpeg.segments.GenericSegment;
import org.apache.commons.imaging.formats.jpeg.segments.Segment;
import org.apache.log4j.Logger;

import com.itextpdf.text.pdf.FilterHandlers;
import com.itextpdf.text.pdf.PRStream;
import com.itextpdf.text.pdf.PdfArray;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfObject;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStream;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.codec.PngWriter;
import com.itextpdf.text.pdf.parser.PdfImageObject;
import com.levigo.jbig2.JBIG2Globals;
import com.levigo.jbig2.JBIG2ImageReader;
import com.levigo.jbig2.JBIG2ImageReaderSpi;

/**
 * @author @author Gian Luca Mori
 */
public class CustomExtraImgReader {

    private static final Logger logger = Logger.getLogger(CustomExtraImgReader.class);

    private static final int COLOR_TYPE_RGB = 1;
    private static final int COLOR_TYPE_CMYK = 2;
    private static final int COLOR_TYPE_YCCK = 3;

    private static int colorType = COLOR_TYPE_RGB;
    private static boolean hasAdobeMarker = false;

    /**
     * Tead a JBIG2 image and give a BufferedImage
     * @param image
     * @return The BufferedImage obj
     */
    public static BufferedImage readJBIG2(PdfImageObject image) {
        BufferedImage buffImg = null;

        PdfDictionary dic = image.getDictionary();
        PdfDictionary decodedic = dic.getAsDict(PdfName.DECODEPARMS);
        PdfStream globalStream = decodedic.getAsStream(PdfName.JBIG2GLOBALS);

        try {
            byte[] byteArrayGlobal = PdfReader.getStreamBytes((PRStream) globalStream);

            InputStream in = new ByteArrayInputStream(image.getImageAsBytes());
            ImageInputStream stream = ImageIO.createImageInputStream(in);

            InputStream inG = new ByteArrayInputStream(byteArrayGlobal);
            ImageInputStream streamG = ImageIO.createImageInputStream(inG);

            JBIG2ImageReader reader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
            reader.setInput(stream);
            JBIG2Globals globals = reader.processGlobals(streamG);
            reader.setGlobals(globals);
            ImageReadParam param = reader.getDefaultReadParam();
            buffImg = reader.read(0, param);

            in.close();
            inG.close();

        } catch (Exception e) {
            logger.error("Exception", e);
            PdfTrickMessages.append("ERROR", Consts.SENDLOG_MSG);
        }
        return buffImg;
    }

    /**
     * Read a png image with if all other method fails
     * @param ref
     * @param resultFile
     * @return The BufferedImage obj
     * @throws IOException
     * @throws ImageReadException
     */
    public static BufferedImage readIndexedPNG(int ref, String resultFile) throws IOException, ImageReadException {

        PdfReader reader = new PdfReader(resultFile);
        PRStream stream = (PRStream) reader.getPdfObject(ref);
        PdfDictionary dic = stream;
        byte[] content = PdfReader.getStreamBytesRaw(stream);

        int width = dic.getAsNumber(PdfName.WIDTH).intValue();
        int height = dic.getAsNumber(PdfName.HEIGHT).intValue();
        int pngBitDepth = dic.getAsNumber(PdfName.BITSPERCOMPONENT).intValue();

        PdfObject colorspace = dic.getDirectObject(PdfName.COLORSPACE);
        PdfArray decode = dic.getAsArray(PdfName.DECODE);
        PdfArray carray = (PdfArray) colorspace;
        PdfObject id2 = carray.getDirectObject(3);

        byte[] palette = null;
        if (id2 instanceof PdfString) {
            palette = ((PdfString) id2).getBytes();
        } else if (id2 instanceof PRStream) {
            palette = PdfReader.getStreamBytes(((PRStream) id2));
        }

        Map<PdfName, FilterHandlers.FilterHandler> handlers = new HashMap<PdfName, FilterHandlers.FilterHandler>(
                FilterHandlers.getDefaultFilterHandlers());
        byte[] imageBytes = PdfReader.decodeBytes(content, dic, handlers);

        int stride = (width * pngBitDepth + 7) / 8;
        ByteArrayOutputStream ms = new ByteArrayOutputStream();
        PngWriter png = new PngWriter(ms);

        if (decode != null) {
            if (pngBitDepth == 1) {
                // if the decode array is 1,0, then we need to invert the image
                if (decode.getAsNumber(0).intValue() == 1 && decode.getAsNumber(1).intValue() == 0) {
                    int len = imageBytes.length;
                    for (int t = 0; t < len; ++t) {
                        imageBytes[t] ^= 0xff;
                    }
                } else {
                    // if the decode array is 0,1, do nothing.  It's possible that the array could be 0,0 or 1,1 - but that would be silly, so we'll just ignore that case
                }
            } else {
                // todo: add decode transformation for other depths
            }
        }
        int pngColorType = 0;
        png.writeHeader(width, height, pngBitDepth, pngColorType);

        if (palette != null) {
            png.writePalette(palette);
        }
        png.writeData(imageBytes, stride);
        png.writeEnd();

        imageBytes = ms.toByteArray();

        InputStream in = new ByteArrayInputStream(imageBytes);
        ImageInputStream ima_stream = ImageIO.createImageInputStream(in);

        BufferedImage buffImg = null;
        BufferedImage buffPic = ImageIO.read(ima_stream);

        // check if image contains a mask image ... experimental for this type of image
        BufferedImage buffMask = null;
        PRStream maskStream = (PRStream) dic.getAsStream(PdfName.SMASK);
        if (maskStream != null) {
            PdfImageObject maskImage = new PdfImageObject(maskStream);
            buffMask = maskImage.getBufferedImage();

            Image img = PdfTrickUtils.TransformGrayToTransparency(buffMask);
            buffImg = PdfTrickUtils.ApplyTransparency(buffPic, img);
        } else {
            buffImg = buffPic;
        }

        reader.close();
        ms.close();
        in.close();
        return buffImg;
    }

    /**
     * Read a JPG image with CMYK ICC profile
     * @param imageByteArray
     * @return The BufferedImage obj
     * @throws IOException
     * @throws ImageReadException
     */
    public static BufferedImage readCMYK_JPG(byte[] imageByteArray) throws IOException, ImageReadException {

        colorType = COLOR_TYPE_RGB;
        hasAdobeMarker = false;

        InputStream in = new ByteArrayInputStream(imageByteArray);
        ImageInputStream stream = ImageIO.createImageInputStream(in);
        Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);

        ImageReader reader = iter.next();
        reader.setInput(stream);

        BufferedImage image = null;
        ICC_Profile profile = null;

        colorType = COLOR_TYPE_CMYK;
        checkAdobeMarker(imageByteArray);
        profile = Imaging.getICCProfile(imageByteArray);

        WritableRaster raster = (WritableRaster) reader.readRaster(0, null);

        if (colorType == COLOR_TYPE_YCCK) {
            convertYcckToCmyk(raster);
        }
        if (hasAdobeMarker) {
            //convertInvertedColors(raster);
        }
        image = convertCmykToRgb(raster, profile);

        in.close();
        reader.dispose();
        return image;
    }

    /**
     * Check if the images has Adobe byte marker and if is a YCCK type
     * @param imageByteArray
     * @throws IOException
     * @throws ImageReadException
     */
    private static void checkAdobeMarker(byte[] imageByteArray) throws IOException, ImageReadException {
        JpegImageParser parser = new JpegImageParser();
        ByteSource byteSource = new ByteSourceArray(imageByteArray);
        List<Segment> segments = parser.readSegments(byteSource, new int[] { 0xffee }, true);
        if (segments != null && segments.size() >= 1) {
            //UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
            //App14Segment app14Segment = (App14Segment) segments.get(0);
            GenericSegment app14Segment = (GenericSegment) segments.get(0);
            byte[] data = app14Segment.getSegmentData();
            if (data.length >= 12 && data[0] == 'A' && data[1] == 'd' && data[2] == 'o' && data[3] == 'b'
                    && data[4] == 'e') {
                hasAdobeMarker = true;
                byte[] data_2 = app14Segment.getSegmentData();
                int transform = data_2[11] & 0xff;
                if (transform == 2)
                    colorType = COLOR_TYPE_YCCK;
            }
        }
    }

    /**
     * Convert image profile from Ycck to Cmyk
     * @param raster
     */
    private static void convertYcckToCmyk(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);

            for (int x = 0; x < stride; x += 4) {
                int y = pixelRow[x];
                int cb = pixelRow[x + 1];
                int cr = pixelRow[x + 2];

                int c = (int) (y + 1.402 * cr - 178.956);
                int m = (int) (y - 0.34414 * cb - 0.71414 * cr + 135.95984);
                y = (int) (y + 1.772 * cb - 226.316);

                if (c < 0)
                    c = 0;
                else if (c > 255)
                    c = 255;
                if (m < 0)
                    m = 0;
                else if (m > 255)
                    m = 255;
                if (y < 0)
                    y = 0;
                else if (y > 255)
                    y = 255;

                pixelRow[x] = 255 - c;
                pixelRow[x + 1] = 255 - m;
                pixelRow[x + 2] = 255 - y;
            }
            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    /**
     * Invert pixel color if the image has a adobe marker ... not used now
     * @param raster
     */
    @SuppressWarnings("unused")
    private static void convertInvertedColors(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);
            for (int x = 0; x < stride; x++)
                pixelRow[x] = 255 - pixelRow[x];
            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    /**
     * Convert image from Cmyk to Rgb profile
     * @param cmykRaster
     * @param cmykProfile
     * @return The BufferedImage obj
     * @throws IOException
     */
    private static BufferedImage convertCmykToRgb(Raster cmykRaster, ICC_Profile cmykProfile) throws IOException {
        if (cmykProfile == null) {
            cmykProfile = ICC_Profile.getInstance(
                    CustomExtraImgReader.class.getResourceAsStream(Consts.RESOURCEPATH + Consts.GENERICICCFILE));
        }
        if (cmykProfile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) {
            byte[] profileData = cmykProfile.getData();
            if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) {
                intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass);
                cmykProfile = ICC_Profile.getInstance(profileData);
            }
        }

        ICC_ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile);
        BufferedImage rgbImage = new BufferedImage(cmykRaster.getWidth(), cmykRaster.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        WritableRaster rgbRaster = rgbImage.getRaster();
        ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
        ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
        cmykToRgb.filter(cmykRaster, rgbRaster);
        return rgbImage;
    }

    /**
     * Correct too bright problem in rgb conversion
     * @param value
     * @param array
     * @param index
     */
    private static void intToBigEndian(int value, byte[] array, int index) {
        array[index] = (byte) (value >> 24);
        array[index + 1] = (byte) (value >> 16);
        array[index + 2] = (byte) (value >> 8);
        array[index + 3] = (byte) (value);
    }

}