/* * Copyright 2014 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tom_roush.pdfbox.pdmodel.graphics.image; import android.graphics.Bitmap; import android.graphics.Color; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.filter.Filter; import com.tom_roush.pdfbox.filter.FilterFactory; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import com.tom_roush.pdfbox.util.awt.AWTColor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * Factory for creating a PDImageXObject containing a lossless compressed image. * * @author Tilman Hausherr */ public final class LosslessFactory { private LosslessFactory() { } /** * Creates a new lossless encoded Image XObject from a Buffered Image. * * @param document the document where the image will be created * @param image the buffered image to embed * @return a new Image XObject * @throws IOException if something goes wrong */ public static PDImageXObject createFromImage(PDDocument document, Bitmap image) throws IOException { int bpc; PDDeviceColorSpace deviceColorSpace; ByteArrayOutputStream bos = new ByteArrayOutputStream(); int height = image.getHeight(); int width = image.getWidth(); // if ((image.getType() == BufferedImage.TYPE_BYTE_GRAY && image.getColorModel().getPixelSize() <= 8) // || (image.getType() == BufferedImage.TYPE_BYTE_BINARY && image.getColorModel().getPixelSize() == 1)) // { // MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(bos); // // // grayscale images need one color per sample // bpc = image.getColorModel().getPixelSize(); // deviceColorSpace = PDDeviceGray.INSTANCE; // for (int y = 0; y < height; ++y) // { // for (int x = 0; x < width; ++x) // { // mcios.writeBits(image.getRGB(x, y) & 0xFF, bpc); // } // while (mcios.getBitOffset() != 0) // { // mcios.writeBit(0); // } // } // mcios.flush(); // mcios.close(); // } // else // { // RGB bpc = 8; deviceColorSpace = PDDeviceRGB.INSTANCE; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { AWTColor color = new AWTColor(image.getPixel(x, y)); bos.write(color.getAlpha() == 0 ? 255 : color.getRed()); bos.write(color.getAlpha() == 0 ? 255 : color.getGreen()); bos.write(color.getAlpha() == 0 ? 255 : color.getBlue()); } } // } TODO: PdfBox-Android PDImageXObject pdImage = prepareImageXObject(document, bos.toByteArray(), image.getWidth(), image.getHeight(), bpc, deviceColorSpace); // alpha -> soft mask PDImage xAlpha = createAlphaFromARGBImage(document, image); if (xAlpha != null) { pdImage.getCOSStream().setItem(COSName.SMASK, xAlpha); } return pdImage; } /** * Creates a grayscale Flate encoded PDImageXObject from the alpha channel * of an image. * * @param document the document where the image will be created. * @param image an ARGB image. * * @return the alpha channel of an image as a grayscale image. * * @throws IOException if something goes wrong */ private static PDImageXObject createAlphaFromARGBImage(PDDocument document, Bitmap image) throws IOException { // this implementation makes the assumption that the raster uses // SinglePixelPackedSampleModel, i.e. the values can be used 1:1 for // the stream. // Sadly the type of the databuffer is TYPE_INT and not TYPE_BYTE. if (!image.hasAlpha()) { return null; } // extract the alpha information // WritableRaster alphaRaster = image.getAlphaRaster(); // if (alphaRaster == null) // { // // happens sometimes (PDFBOX-2654) despite colormodel claiming to have alpha // return createAlphaFromARGBImage2(document, image); // } // int[] pixels = alphaRaster.getPixels(0, 0, // alphaRaster.getSampleModel().getWidth(), // alphaRaster.getSampleModel().getHeight(), // (int[]) null); int[] pixels = new int[image.getHeight() * image.getWidth()]; // image.getPixels(pixels, 0, image.getWidth(), 0, 0, image.getHeight(), image.getWidth()); for(int y = 0; y < image.getHeight(); y++) { for(int x = 0; x < image.getWidth(); x++) { pixels[x + y * image.getWidth()] = image.getPixel(x, y); } } ByteArrayOutputStream bos = new ByteArrayOutputStream(); int bpc; // if (image.getTransparency() == Transparency.BITMASK) // { // bpc = 1; // MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(bos); // int width = alphaRaster.getSampleModel().getWidth(); // int p = 0; // for (int pixel : pixels) // { // mcios.writeBit(pixel); // ++p; // if (p % width == 0) // { // while (mcios.getBitOffset() != 0) // { // mcios.writeBit(0); // } // } // } // mcios.flush(); // mcios.close(); // } // else // { bpc = 8; for (int pixel : pixels) { bos.write(Color.alpha(pixel)); } // } PDImageXObject pdImage = prepareImageXObject(document, bos.toByteArray(), image.getWidth(), image.getHeight(), bpc, PDDeviceGray.INSTANCE); return pdImage; } // create alpha image the hard way: get the alpha through getRGB() // private static PDImageXObject createAlphaFromARGBImage2(PDDocument document, Bitmap bi) // throws IOException // { // ByteArrayOutputStream bos = new ByteArrayOutputStream(); // int bpc; // if (bi.getTransparency() == Transparency.BITMASK) // { // bpc = 1; // MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(bos); // for (int y = 0, h = bi.getHeight(); y < h; ++y) // { // for (int x = 0, w = bi.getWidth(); x < w; ++x) // { // int alpha = bi.getRGB(x, y) >>> 24; // mcios.writeBit(alpha); // } // while (mcios.getBitOffset() != 0) // { // mcios.writeBit(0); // } // } // mcios.flush(); // mcios.close(); // } // else // { // bpc = 8; // for (int y = 0, h = bi.getHeight(); y < h; ++y) // { // for (int x = 0, w = bi.getWidth(); x < w; ++x) // { // int alpha = Color.alpha(bi.getPixel(x, y)); // bos.write(alpha); // } // } // } // // PDImageXObject pdImage = prepareImageXObject(document, bos.toByteArray(), // bi.getWidth(), bi.getHeight(), bpc, PDDeviceGray.INSTANCE); // // return pdImage; // } /** * Create a PDImageXObject while making a decision whether not to * compress, use Flate filter only, or Flate and LZW filters. * * @param document The document. * @param byteArray array with data. * @param width the image width * @param height the image height * @param bitsPerComponent the bits per component * @param initColorSpace the color space * @return the newly created PDImageXObject with the data compressed. * @throws IOException */ private static PDImageXObject prepareImageXObject(PDDocument document, byte [] byteArray, int width, int height, int bitsPerComponent, PDColorSpace initColorSpace) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Filter filter = FilterFactory.INSTANCE.getFilter(COSName.FLATE_DECODE); filter.encode(new ByteArrayInputStream(byteArray), baos, new COSDictionary(), 0); ByteArrayInputStream encodedByteStream = new ByteArrayInputStream(baos.toByteArray()); return new PDImageXObject(document, encodedByteStream, COSName.FLATE_DECODE, width, height, bitsPerComponent, initColorSpace); } }