code: /*
   * $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 pdf;
  
  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 object.
    private int objwidth;
    private int objheight;
  
    // 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, 0, 0, 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,int x,int y,int w,int h,ImageObserver obs) {
      this();
      objwidth = w;
      objheight = h;
      setImage(img, x, y, 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 x,int y,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
  