/* * $Id: PDFImage.java,v 1.2 2007/08/26 18:56:35 gil1 Exp $ * * $Date: 2007/08/26 18:56:35 $ * * 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 */ package gnu.jpdf; import java.awt.Image; import java.awt.image.*; import java.io.*; import java.util.*; import java.util.zip.*; /** *

* This implements the Image XObject. Calling one of the drawImage * methods of PDFGraphics will put all the necessary code into the * pdf file, and the image will be encoded in ascii base 85, then deflated in * zip format.

* * @author Eric Z. Beard (original version by Peter Mount) * @author Matthew Hreljac, mhreljac@hotmail.com * @version $Revision: 1.2 $, $Date: 2007/08/26 18:56:35 $ */ public class PDFImage extends PDFStream implements ImageObserver, Serializable { /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.jpdf. * The formatting was changed a little bit. * Images were not yet implemented, so the core of this * class was mostly rewritten * It is still licensed under the LGPL. * Got some help with base85 methods from Mathew Hreljac */ // Dimensions of the object. private int objwidth; private int objheight; // Dimensions of the image. private int width; private int height; private Image img; private byte[] jpegImageData; private String name; private String mask; private boolean interpolate; /** * Creates a new PDFImage instance. * */ public PDFImage() { super("/XObject"); } /** * Creates a new PDFImage instance. * * @param img an Image value */ public PDFImage(Image img, String mask, boolean interpolate) { this(); this.mask = mask; this.interpolate = interpolate; setImage(img, 0, 0, img.getWidth(this), img.getHeight(this), this); } public PDFImage(Image img) { this(img, null, false); } public boolean isInterpolate() { return interpolate; } /** * Creates a new PDFImage instance. * * @param img an Image value * @param x an int value * @param y an int value * @param w an int value * @param h an int value * @param obs an ImageObserver value */ public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs, String mask, boolean interpolate) { this(); objwidth = w; objheight = h; this.mask = mask; this.interpolate = interpolate; setImage(img, x, y, img.getWidth(this), img.getHeight(this), obs); } public PDFImage(byte jpegImageData[], int x, int y, int w, int h, ImageObserver obs, String mask, boolean interpolate) { this(); objwidth = w; objheight = h; this.mask = mask; this.interpolate = interpolate; setJpegImageData(jpegImageData, w, h); } public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs) { this(img, x, y, w, h, obs, null, false); } public PDFImage(byte jpegImageData[], int x, int y, int w, int h, ImageObserver obs) { this(jpegImageData, x, y, w, h, obs, null, false); } /** * Get the value of width. * * @return value of width. */ public int getWidth() { return width; } /** * Set the value of width. * * @param v Value to assign to width. */ public void setWidth(int v) { this.width = v; } /** * Get the value of height. * * @return value of height. */ public int getHeight() { return height; } /** * Set the value of height. * * @param v Value to assign to height. */ public void setHeight(int v) { this.height = v; } /** * Set the name * * @param n a String value */ public void setName(String n) { name = n; } /** * Get the name * * @return a String value */ public String getName() { return name; } /** * Set the image * * @param img an Image value * @param x an int value * @param y an int value * @param w an int value * @param h an int value * @param obs an ImageObserver value */ public void setImage(Image img, int x, int y, int w, int h, ImageObserver obs) { this.img = img; this.jpegImageData = null; width = w; height = h; } public void setJpegImageData(byte[] jpegImageData, int w, int h) { this.jpegImageData = jpegImageData; this.img = null; width = w; height = h; } /** *

* Adobe's base 85 does not follow the format used by ipv6 addresses. It * simply starts with 33 and goes straight up without skipping any * characters

* *

* Parts of this method contributed by Mathew Hreljac

* * @param stringToEncode a String value * @return a String value */ private String base85Encoding(String stringToEncode) throws NumberFormatException { if ((stringToEncode == null) || (stringToEncode.length() == 0)) { //System.out.println("PDFImage.base85Encoding() null or blank String"); return ""; } if ((stringToEncode.length() > 8) || ((stringToEncode.length() % 2) != 0)) { System.out.println("PDFImage.base85Encoding, Incorrect tuple length: " + stringToEncode.length()); return ""; } //System.out.println("str: " + stringToEncode); // String buffer to use to return the String encoding StringBuffer sb = new StringBuffer(); // Deal with a partial tuple (less than 8 hex digits) // From Adobe's docs: // "Given n (1, 2 or 3) bytes of binary data, the encoding first // appends 4 - n zero bytes to make a complete 4-tuple. This 4-tuple // is encoded in the usual way, but without applying the special // z-case. Finally, only the first n+1 characters of the resulting // 5-tuple are written out. Those characters are immediately followed // by the EOD marker, ~>" int numHexDigits = stringToEncode.length() / 2; int numAppendBytes = 4 - numHexDigits; for (int i = 0; i < numAppendBytes; i++) { stringToEncode += "00"; } Vector digitVector = new Vector(); long number = Long.parseLong(stringToEncode, 16); int remainder = 0; while (number >= 85) { remainder = (int) (number % 85); number = number / 85; digitVector.add(0, new Integer(remainder)); } digitVector.add(0, new Integer((int) number)); for (int i = 0; i < digitVector.size(); i++) { char c = (char) (((Integer) digitVector.elementAt(i)).intValue() + 33); sb.append(c); } String tuple = sb.toString(); int len = tuple.length(); switch (len) { case 1: tuple = "!!!!" + tuple; break; case 2: tuple = "!!!" + tuple; break; case 3: tuple = "!!" + tuple; break; case 4: tuple = "!" + tuple; break; default: break; } // end switch //System.out.println("enc tuple: " + tuple); return (tuple); } // end base85encoding /** * Writes the image to the stream * * @param os an OutputStream value * @exception IOException if an error occurs */ @Override public void writeStream(OutputStream os) throws IOException { // This is a non-deflated stream /* os.write("/Length ".getBytes("UTF-8")); // Account for stream\n ... >\nendstream os.write(Integer.toString(buf.size() + 18).getBytes("UTF-8")); os.write("\n/Filter /ASCII85Decode".getBytes("UTF-8")); os.write("\n>>\nstream\n".getBytes("UTF-8")); buf.writeTo(os); os.write(">\nendstream\nendobj\n\n".getBytes("UTF-8")); */ ByteArrayOutputStream b = new ByteArrayOutputStream(); if (jpegImageData == null) { DeflaterOutputStream dos = new DeflaterOutputStream(b); buf.writeTo(dos); dos.finish(); dos.close(); } else { buf.writeTo(b); } // FlatDecode is compatible with the java.util.zip.Deflater class //os.write("/Filter [/FlateDecode /ASCIIHexDecode]\n".getBytes("UTF-8")); if (jpegImageData != null) { os.write("/Filter /DCTDecode\n".getBytes("UTF-8")); } else { os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes("UTF-8")); } os.write("/Length ".getBytes("UTF-8")); os.write(Integer.toString(b.size()).getBytes("UTF-8")); os.write("\n>>\nstream\n".getBytes("UTF-8")); b.writeTo(os); os.write("\nendstream\nendobj\n".getBytes("UTF-8")); } // end writeStream /** *

* Compression needs to be improved here

* * @param os OutputStream to send the object to * @exception IOException on error */ @Override public void write(OutputStream os) throws IOException { writeStart(os); // write the extra details os.write("/Subtype /Image\n/Name ".getBytes("UTF-8")); os.write(name.getBytes("UTF-8")); os.write("\n/Width ".getBytes("UTF-8")); os.write(Integer.toString(width).getBytes("UTF-8")); os.write("\n/Height ".getBytes("UTF-8")); os.write(Integer.toString(height).getBytes("UTF-8")); os.write("\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n".getBytes("UTF-8")); if (mask != null) { os.write(("/SMask " + mask + "\n").getBytes("UTF-8")); } if (interpolate) { os.write("/Interpolate true\n".getBytes("UTF-8")); } // write the pixels to the stream //System.err.println("Processing image "+width+"x"+height+" pixels"); ByteArrayOutputStream bos = getStream(); if (jpegImageData != null) { bos.write(jpegImageData); } else { int w = width; int h = height; int x = 0; int y = 0; int[] pixels = new int[w * h]; PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w); try { pg.grabPixels(); } catch (InterruptedException e) { System.err.println("interrupted waiting for pixels!"); return; } if ((pg.getStatus() & ImageObserver.ABORT) != 0) { System.err.println("image fetch aborted or errored"); return; } StringBuffer out = new StringBuffer(); for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { //System.out.print("p[" + j * w + i+ "]=" + pixels[j * w + i] + "."); out.append(handlePixel(x + i, y + j, pixels[j * w + i])); if (out.toString().length() >= 8) { String tuple = out.substring(0, 8); out.delete(0, 8); // Convert !!!!! to 'z' String encTuple = base85Encoding(tuple); if (encTuple.equals("!!!!!")) { encTuple = "z"; } bos.write(encTuple.getBytes("UTF-8")); } } } // This should be the only partial tuple case, String lastTuple = base85Encoding(out.toString()); //System.out.println("lastTuple: " + lastTuple); bos.write(lastTuple.getBytes("UTF-8")); bos.write("~".getBytes("UTF-8")); } //System.out.println("Processing done"); // this will write the actual stream setDeflate(false); writeStream(os); // Note: we do not call writeEnd() on streams! } /** *

* Converts a pixel to a hex string

* * @param x an int value * @param y an int value * @param p an int value * @return a String value */ public static String handlePixel(int x, int y, int p) { int alpha = (p >> 24) & 0xff; int red = (p >> 16) & 0xff; int green = (p >> 8) & 0xff; int blue = (p) & 0xff; String redHex = Integer.toHexString(red); String greenHex = Integer.toHexString(green); String blueHex = Integer.toHexString(blue); if (redHex.length() == 1) { redHex = "0" + redHex; } if (greenHex.length() == 1) { greenHex = "0" + greenHex; } if (blueHex.length() == 1) { blueHex = "0" + blueHex; } return redHex + greenHex + blueHex; } // end handlePixel /** * Describe imageUpdate method here. * * @param img an Image value * @param infoflags an int value * @param x an int value * @param y an int value * @param w an int value * @param h an int value * @return a boolean value */ @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) { System.err.println("img=" + img + "\ninfoflags=" + infoflags + "\nx=" + x + " y=" + y + " w=" + w + " h=" + h); //if(img == this.img) { if (infoflags == ImageObserver.WIDTH) { width = w; } if (infoflags == ImageObserver.HEIGHT) { height = h; } //return true; //} return false; } } // end class PDFImage