/* * $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.*; /** * <p>This implements the Image XObject. Calling one of the * <code>drawImage</code> methods of <code>PDFGraphics</code> 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.</p> * * @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 image. private int width; private int height; private Image img; private String name; /** * Creates a new <code>PDFImage</code> instance. * */ public PDFImage() { super("/XObject"); } /** * Creates a new <code>PDFImage</code> instance. * * @param img an <code>Image</code> value */ public PDFImage(Image img) { this(); setImage(img,img.getWidth(this), img.getHeight(this), this); } /** * Creates a new <code>PDFImage</code> instance. * * @param img an <code>Image</code> value * @param x an <code>int</code> value * @param y an <code>int</code> value * @param w an <code>int</code> value * @param h an <code>int</code> value * @param obs an <code>ImageObserver</code> value */ public PDFImage(Image img,ImageObserver obs) { this(); setImage(img, img.getWidth(this), img.getHeight(this), obs); } /** * 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 <code>String</code> value */ public void setName(String n) { name = n; } /** * Get the name * * @return a <code>String</code> value */ public String getName() { return name; } /** * Set the image * * @param img an <code>Image</code> value * @param x an <code>int</code> value * @param y an <code>int</code> value * @param w an <code>int</code> value * @param h an <code>int</code> value * @param obs an <code>ImageObserver</code> value */ public void setImage(Image img,int w,int h,ImageObserver obs) { this.img = img; width = w; height = h; } /** * <p>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</p> * * <p>Parts of this method contributed by Mathew Hreljac</p> * * @param stringToEncode a <code>String</code> value * @return a <code>String</code> 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<Integer> digitVector = new Vector<Integer>(); 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 <code>OutputStream</code> value * @exception IOException if an error occurs */ public void writeStream(OutputStream os) throws IOException { // This is a non-deflated stream /* os.write("/Length ".getBytes()); // Accout for stream\n ... >\nendstream os.write(Integer.toString(buf.size() + 18).getBytes()); os.write("\n/Filter /ASCII85Decode".getBytes()); os.write("\n>>\nstream\n".getBytes()); buf.writeTo(os); os.write(">\nendstream\nendobj\n\n".getBytes()); */ ByteArrayOutputStream b = new ByteArrayOutputStream(); DeflaterOutputStream dos = new DeflaterOutputStream(b); buf.writeTo(dos); dos.finish(); dos.close(); // FlatDecode is compatible with the java.util.zip.Deflater class //os.write("/Filter [/FlateDecode /ASCIIHexDecode]\n".getBytes()); os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes()); os.write("/Length ".getBytes()); os.write(Integer.toString(b.size()).getBytes()); os.write("\n>>\nstream\n".getBytes()); b.writeTo(os); os.write("\nendstream\nendobj\n".getBytes()); } // end writeStream /** * <p>Compression needs to be improved here</p> * * @param os OutputStream to send the object to * @exception IOException on error */ public void write(OutputStream os) throws IOException { writeStart(os); // write the extra details os.write("/Subtype /Image\n/Name ".getBytes()); os.write(name.getBytes()); os.write("\n/Width ".getBytes()); os.write(Integer.toString(width).getBytes()); os.write("\n/Height ".getBytes()); os.write(Integer.toString(height).getBytes()); os.write("\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n".getBytes()); // write the pixels to the stream //System.err.println("Processing image "+width+"x"+height+" pixels"); ByteArrayOutputStream bos = getStream(); 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()); } } } // This should be the only partial tuple case, String lastTuple = base85Encoding(out.toString()); //System.out.println("lastTuple: " + lastTuple); bos.write(lastTuple.getBytes()); bos.write("~".getBytes()); //System.out.println("Processing done"); // this will write the actual stream setDeflate(false); writeStream(os); // Note: we do not call writeEnd() on streams! } /** * <p>Converts a pixel to a hex string</p> * * @param x an <code>int</code> value * @param y an <code>int</code> value * @param p an <code>int</code> value * @return a <code>String</code> 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 <code>imageUpdate</code> method here. * * @param img an <code>Image</code> value * @param infoflags an <code>int</code> value * @param x an <code>int</code> value * @param y an <code>int</code> value * @param w an <code>int</code> value * @param h an <code>int</code> value * @return a <code>boolean</code> value */ 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