package com.tom_roush.pdfbox.pdmodel.graphics.image;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.io.IOUtils;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Factory for creating a PDImageXObject containing a JPEG compressed image.
* @author John Hewson
*/
public final class JPEGFactory
{
private JPEGFactory()
{
}
/**
* Creates a new JPEG Image XObject from an input stream containing JPEG data.
*
* The input stream data will be preserved and embedded in the PDF file without modification.
* @param document the document where the image will be created
* @param stream a stream of JPEG data
* @return a new Image XObject
*
* @throws IOException if the input stream cannot be read
*/
public static PDImageXObject createFromStream(PDDocument document, InputStream stream)
throws IOException
{
// copy stream
ByteArrayInputStream byteStream = new ByteArrayInputStream(IOUtils.toByteArray(stream));
// read image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(byteStream, null, options);
byteStream.reset();
// create Image XObject from stream
PDImageXObject pdImage = new PDImageXObject(document, byteStream,
COSName.DCT_DECODE, options.outWidth, options.outHeight,
8, //awtImage.getColorModel().getComponentSize(0),
PDDeviceRGB.INSTANCE //getColorSpaceFromAWT(awtImage));
);
return pdImage;
}
/**
* Creates a new JPEG 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 the JPEG data cannot be written
*/
public static PDImageXObject createFromImage(PDDocument document, Bitmap image)
throws IOException
{
return createFromImage(document, image, 0.75f);
}
/**
* Creates a new JPEG Image XObject from a Buffered Image and a given quality.
* The image will be created at 72 DPI.
* @param document the document where the image will be created
* @param image the buffered image to embed
* @param quality the desired JPEG compression quality
* @return a new Image XObject
* @throws IOException if the JPEG data cannot be written
*/
public static PDImageXObject createFromImage(PDDocument document, Bitmap image,
float quality) throws IOException
{
return createFromImage(document, image, quality, 72);
}
/**
* Creates a new JPEG Image XObject from a Buffered Image, a given quality and DPI.
* @param document the document where the image will be created
* @param image the buffered image to embed
* @param quality the desired JPEG compression quality
* @param dpi the desired DPI (resolution) of the JPEG
* @return a new Image XObject
* @throws IOException if the JPEG data cannot be written
*/
public static PDImageXObject createFromImage(PDDocument document, Bitmap image,
float quality, int dpi) throws IOException
{
return createJPEG(document, image, quality, dpi);
}
// returns the alpha channel of an image
private static Bitmap getAlphaImage(Bitmap image) throws IOException
{
if (!image.hasAlpha())
{
return null;
}
return image.extractAlpha();
}
// Creates an Image XObject from a Buffered Image using JAI Image I/O
private static PDImageXObject createJPEG(PDDocument document, Bitmap image,
float quality, int dpi) throws IOException
{
Bitmap whiteImage = Bitmap.createBitmap(image.getWidth(), image.getHeight(),image.getConfig());
Bitmap alphaImage = getAlphaImage(image);
whiteImage.eraseColor(Color.WHITE);
Canvas canvas = new Canvas(whiteImage);
canvas.drawBitmap(image, 0f, 0f, null);
image.recycle();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
whiteImage.compress(Bitmap.CompressFormat.JPEG, (int)(quality * 100), bos);
byte[] bitmapData = bos.toByteArray();
ByteArrayInputStream byteStream = new ByteArrayInputStream(bitmapData);
PDImageXObject pdImage = new PDImageXObject(document, byteStream,
COSName.DCT_DECODE, whiteImage.getWidth(), whiteImage.getHeight(),
8,
PDDeviceRGB.INSTANCE
);
// alpha -> soft mask
if (alphaImage != null)
{
ByteArrayOutputStream aBos = new ByteArrayOutputStream();
// This is problematic at the moment as
// compress does not seem to support ALPHA_8 as returned by getAlphaImage()
boolean ok = alphaImage.compress(Bitmap.CompressFormat.JPEG, (int)(quality * 100), aBos);
System.err.println("Compressing alpha image: " + String.valueOf(ok) + " " + alphaImage.getConfig().toString());
byte[] aBitmapData = aBos.toByteArray();
ByteArrayInputStream aByteStream = new ByteArrayInputStream(aBitmapData);
PDImageXObject xAlpha = new PDImageXObject(document, aByteStream,
COSName.DCT_DECODE, alphaImage.getWidth(), alphaImage.getHeight(),
8,
PDDeviceGray.INSTANCE
);
pdImage.getCOSStream().setItem(COSName.SMASK, xAlpha);
}
return pdImage;
}
// private static void encodeImageToJPEGStream(BufferedImage image, float quality, int dpi,
// OutputStream out) throws IOException
// {
// // encode to JPEG
// ImageOutputStream ios = null;
// ImageWriter imageWriter = null;
// try
// {
// // find JAI writer
// imageWriter = ImageIO.getImageWritersBySuffix("jpeg").next();
// ios = ImageIO.createImageOutputStream(out);
// imageWriter.setOutput(ios);
// // add compression
// JPEGImageWriteParam jpegParam = (JPEGImageWriteParam)imageWriter.getDefaultWriteParam();
// jpegParam.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
// jpegParam.setCompressionQuality(quality);
// // add metadata
// ImageTypeSpecifier imageTypeSpecifier = new ImageTypeSpecifier(image);
// IIOMetadata data = imageWriter.getDefaultImageMetadata(imageTypeSpecifier, jpegParam);
// Element tree = (Element)data.getAsTree("javax_imageio_jpeg_image_1.0");
// Element jfif = (Element)tree.getElementsByTagName("app0JFIF").item(0);
// jfif.setAttribute("Xdensity", Integer.toString(dpi));
// jfif.setAttribute("Ydensity", Integer.toString(dpi));
// jfif.setAttribute("resUnits", "1"); // 1 = dots/inch
// // write
// imageWriter.write(data, new IIOImage(image, null, null), jpegParam);
// }
// finally
// {
// // clean up
// IOUtils.closeQuietly(out);
// if (ios != null)
// {
// ios.close();
// }
// if (imageWriter != null)
// {
// imageWriter.dispose();
// }
// }
// } TODO: PdfBox-Android
// returns a PDColorSpace for a given BufferedImage
// private static PDColorSpace getColorSpaceFromAWT(Bitmap awtImage)
// {
// if (awtImage.getColorModel().getNumComponents() == 1)
// {
// // 256 color (gray) JPEG
// return PDDeviceGray.INSTANCE;
// }
//
// ColorSpace awtColorSpace = awtImage.getColorModel().getColorSpace();
// if (awtColorSpace instanceof ICC_ColorSpace && !awtColorSpace.isCS_sRGB())
// {
// throw new UnsupportedOperationException("ICC color spaces not implemented");
// }
//
// switch (awtColorSpace.getType())
// {
// case ColorSpace.TYPE_RGB:
// return PDDeviceRGB.INSTANCE;
// case ColorSpace.TYPE_GRAY:
// return PDDeviceGray.INSTANCE;
// case ColorSpace.TYPE_CMYK:
// return PDDeviceCMYK.INSTANCE;
// default:
// throw new UnsupportedOperationException("color space not implemented: "
// + awtColorSpace.getType());
// }
// } TODO: PdfBox-Android
// returns the color channels of an image
private static Bitmap getColorImage(Bitmap image)
{
if (!image.hasAlpha())
{
return image;
}
if (!image.getConfig().name().contains("RGB"))
{
throw new UnsupportedOperationException("only RGB color spaces are implemented");
}
// create an RGB image without alpha
//BEWARE: the previous solution in the history
// g.setComposite(AlphaComposite.Src) and g.drawImage()
// didn't work properly for TYPE_4BYTE_ABGR.
// alpha values of 0 result in a black dest pixel!!!
Bitmap rgbImage = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
// return new ColorConvertOp(null).filter(image, rgbImage); TODO: PdfBox-Android
Paint alphaPaint = new Paint();
alphaPaint.setAlpha(0);
Canvas canvas = new Canvas(rgbImage);
canvas.drawBitmap(image, 0, 0, alphaPaint);
return rgbImage;
}
}