001: /*
002: * $Id: PDFImage.java,v 1.3 2002/03/04 20:33:18 ezb Exp $
003: *
004: * $Date: 2002/03/04 20:33:18 $
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package gnu.jpdf;
021:
022: import java.awt.Image;
023: import java.awt.image.*;
024: import java.io.*;
025: import java.util.*;
026: import java.util.zip.*;
027:
028: /**
029: * <p>This implements the Image XObject. Calling one of the
030: * <code>drawImage</code> methods of <code>PDFGraphics</code> will
031: * put all the necessary code into the pdf file, and the image will
032: * be encoded in ascii base 85, then deflated in zip format.</p>
033: *
034: * @author Eric Z. Beard (original version by Peter Mount)
035: * @author Matthew Hreljac, mhreljac@hotmail.com
036: * @author $Author: ezb $
037: * @version $Revision: 1.3 $, $Date: 2002/03/04 20:33:18 $
038: */
039: public class PDFImage extends PDFStream implements ImageObserver,
040: Serializable {
041: /*
042: * NOTE: The original class is the work of Peter T. Mount, who released it
043: * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as
044: * follows:
045: * The package name was changed to gnu.jpdf.
046: * The formatting was changed a little bit.
047: * Images were not yet implemented, so the core of this
048: * class was mostly rewritten
049: * It is still licensed under the LGPL.
050: * Got some help with base85 methods from Mathew Hreljac
051: */
052:
053: // Dimensions of the object.
054: private int objwidth;
055: private int objheight;
056:
057: // Dimensions of the image.
058: private int width;
059: private int height;
060: private Image img;
061: private String name;
062:
063: /**
064: * Creates a new <code>PDFImage</code> instance.
065: *
066: */
067: public PDFImage() {
068: super ("/XObject");
069: }
070:
071: /**
072: * Creates a new <code>PDFImage</code> instance.
073: *
074: * @param img an <code>Image</code> value
075: */
076: public PDFImage(Image img) {
077: this ();
078: setImage(img, 0, 0, img.getWidth(this ), img.getHeight(this ),
079: this );
080: }
081:
082: /**
083: * Creates a new <code>PDFImage</code> instance.
084: *
085: * @param img an <code>Image</code> value
086: * @param x an <code>int</code> value
087: * @param y an <code>int</code> value
088: * @param w an <code>int</code> value
089: * @param h an <code>int</code> value
090: * @param obs an <code>ImageObserver</code> value
091: */
092: public PDFImage(Image img, int x, int y, int w, int h,
093: ImageObserver obs) {
094: this ();
095: objwidth = w;
096: objheight = h;
097: setImage(img, x, y, img.getWidth(this ), img.getHeight(this ),
098: obs);
099: }
100:
101: /**
102: * Get the value of width.
103: * @return value of width.
104: */
105: public int getWidth() {
106: return width;
107: }
108:
109: /**
110: * Set the value of width.
111: * @param v Value to assign to width.
112: */
113: public void setWidth(int v) {
114: this .width = v;
115: }
116:
117: /**
118: * Get the value of height.
119: * @return value of height.
120: */
121: public int getHeight() {
122: return height;
123: }
124:
125: /**
126: * Set the value of height.
127: * @param v Value to assign to height.
128: */
129: public void setHeight(int v) {
130: this .height = v;
131: }
132:
133: /**
134: * Set the name
135: *
136: * @param n a <code>String</code> value
137: */
138: public void setName(String n) {
139: name = n;
140: }
141:
142: /**
143: * Get the name
144: *
145: * @return a <code>String</code> value
146: */
147: public String getName() {
148: return name;
149: }
150:
151: /**
152: * Set the image
153: *
154: * @param img an <code>Image</code> value
155: * @param x an <code>int</code> value
156: * @param y an <code>int</code> value
157: * @param w an <code>int</code> value
158: * @param h an <code>int</code> value
159: * @param obs an <code>ImageObserver</code> value
160: */
161: public void setImage(Image img, int x, int y, int w, int h,
162: ImageObserver obs) {
163: this .img = img;
164: width = w;
165: height = h;
166: }
167:
168: /**
169: * <p>Adobe's base 85 does not follow the format used by ipv6
170: * addresses. It simply starts with 33 and goes straight up without
171: * skipping any characters</p>
172: *
173: * <p>Parts of this method contributed by Mathew Hreljac</p>
174: *
175: * @param stringToEncode a <code>String</code> value
176: * @return a <code>String</code> value
177: */
178: private String base85Encoding(String stringToEncode)
179: throws NumberFormatException {
180: if ((stringToEncode == null) || (stringToEncode.length() == 0)) {
181: //System.out.println("PDFImage.base85Encoding() null or blank String");
182: return "";
183: }
184: if ((stringToEncode.length() > 8)
185: || ((stringToEncode.length() % 2) != 0)) {
186: System.out
187: .println("PDFImage.base85Encoding, Incorrect tuple length: "
188: + stringToEncode.length());
189: return "";
190: }
191: //System.out.println("str: " + stringToEncode);
192: // String buffer to use to return the String encoding
193: StringBuffer sb = new StringBuffer();
194:
195: // Deal with a partial tuple (less than 8 hex digits)
196: // From Adobe's docs:
197: // "Given n (1, 2 or 3) bytes of binary data, the encoding first
198: // appends 4 - n zero bytes to make a complete 4-tuple. This 4-tuple
199: // is encoded in the usual way, but without applying the special
200: // z-case. Finally, only the first n+1 characters of the resulting
201: // 5-tuple are written out. Those characters are immediately followed
202: // by the EOD marker, ~>"
203:
204: int numHexDigits = stringToEncode.length() / 2;
205: int numAppendBytes = 4 - numHexDigits;
206: for (int i = 0; i < numAppendBytes; i++) {
207: stringToEncode += "00";
208: }
209: // Debugging
210: if (numAppendBytes > 0) {
211: System.out.println("numAppendBytes: " + numAppendBytes
212: + "stringToEncode: " + stringToEncode);
213: }
214: Vector digitVector = new Vector();
215: long number = Long.parseLong(stringToEncode, 16);
216: int remainder = 0;
217:
218: while (number >= 85) {
219: remainder = (int) (number % 85);
220: number = number / 85;
221: digitVector.add(0, new Integer(remainder));
222: }
223: digitVector.add(0, new Integer((int) number));
224:
225: for (int i = 0; i < digitVector.size(); i++) {
226: char c = (char) (((Integer) digitVector.elementAt(i))
227: .intValue() + 33);
228: sb.append(c);
229: }
230: String tuple = sb.toString();
231: int len = tuple.length();
232: switch (len) {
233: case 1:
234: tuple = "!!!!" + tuple;
235: break;
236: case 2:
237: tuple = "!!!" + tuple;
238: break;
239: case 3:
240: tuple = "!!" + tuple;
241: break;
242: case 4:
243: tuple = "!" + tuple;
244: break;
245: default:
246: break;
247: } // end switch
248: //System.out.println("enc tuple: " + tuple);
249:
250: return (tuple);
251: } // end base85encoding
252:
253: /**
254: * Writes the image to the stream
255: *
256: * @param os an <code>OutputStream</code> value
257: * @exception IOException if an error occurs
258: */
259: public void writeStream(OutputStream os) throws IOException {
260: // This is a non-deflated stream
261: /*
262: os.write("/Length ".getBytes());
263: // Accout for stream\n ... >\nendstream
264: os.write(Integer.toString(buf.size() + 18).getBytes());
265: os.write("\n/Filter /ASCII85Decode".getBytes());
266: os.write("\n>>\nstream\n".getBytes());
267: buf.writeTo(os);
268: os.write(">\nendstream\nendobj\n\n".getBytes());
269: */
270: ByteArrayOutputStream b = new ByteArrayOutputStream();
271: DeflaterOutputStream dos = new DeflaterOutputStream(b);
272: buf.writeTo(dos);
273: dos.finish();
274: dos.close();
275:
276: // FlatDecode is compatible with the java.util.zip.Deflater class
277: //os.write("/Filter [/FlateDecode /ASCIIHexDecode]\n".getBytes());
278: os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes());
279: os.write("/Length ".getBytes());
280: os.write(Integer.toString(b.size()).getBytes());
281: os.write("\n>>\nstream\n".getBytes());
282: b.writeTo(os);
283: os.write("\nendstream\nendobj\n".getBytes());
284:
285: } // end writeStream
286:
287: /**
288: * <p>Compression needs to be improved here</p>
289: *
290: * @param os OutputStream to send the object to
291: * @exception IOException on error
292: */
293: public void write(OutputStream os) throws IOException {
294: writeStart(os);
295:
296: // write the extra details
297: os.write("/Subtype /Image\n/Name ".getBytes());
298: os.write(name.getBytes());
299: os.write("\n/Width ".getBytes());
300: os.write(Integer.toString(width).getBytes());
301: os.write("\n/Height ".getBytes());
302: os.write(Integer.toString(height).getBytes());
303: os.write("\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n"
304: .getBytes());
305:
306: // write the pixels to the stream
307: //System.err.println("Processing image "+width+"x"+height+" pixels");
308: ByteArrayOutputStream bos = getStream();
309:
310: int w = width;
311: int h = height;
312: int x = 0;
313: int y = 0;
314: int[] pixels = new int[w * h];
315: PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0,
316: w);
317: try {
318: pg.grabPixels();
319: } catch (InterruptedException e) {
320: System.err.println("interrupted waiting for pixels!");
321: return;
322: }
323: if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
324: System.err.println("image fetch aborted or errored");
325: return;
326: }
327: StringBuffer out = new StringBuffer();
328: for (int j = 0; j < h; j++) {
329: for (int i = 0; i < w; i++) {
330: //System.out.print("p[" + j * w + i+ "]=" + pixels[j * w + i] + ".");
331: out
332: .append(handlePixel(x + i, y + j, pixels[j * w
333: + i]));
334: if (out.toString().length() >= 8) {
335: String tuple = out.substring(0, 8);
336: out.delete(0, 8);
337: // Convert !!!!! to 'z'
338: String encTuple = base85Encoding(tuple);
339: if (encTuple.equals("!!!!!")) {
340: encTuple = "z";
341: }
342: bos.write(encTuple.getBytes());
343: }
344: }
345: }
346: // This should be the only partial tuple case,
347:
348: String lastTuple = base85Encoding(out.toString());
349: //System.out.println("lastTuple: " + lastTuple);
350: bos.write(lastTuple.getBytes());
351: bos.write("~".getBytes());
352:
353: //System.out.println("Processing done");
354:
355: // this will write the actual stream
356: setDeflate(false);
357:
358: writeStream(os);
359:
360: // Note: we do not call writeEnd() on streams!
361: }
362:
363: /**
364: * <p>Converts a pixel to a hex string</p>
365: *
366: * @param x an <code>int</code> value
367: * @param y an <code>int</code> value
368: * @param p an <code>int</code> value
369: * @return a <code>String</code> value
370: */
371: public static String handlePixel(int x, int y, int p) {
372: int alpha = (p >> 24) & 0xff;
373: int red = (p >> 16) & 0xff;
374: int green = (p >> 8) & 0xff;
375: int blue = (p) & 0xff;
376: String redHex = Integer.toHexString(red);
377: String greenHex = Integer.toHexString(green);
378: String blueHex = Integer.toHexString(blue);
379: if (redHex.length() == 1) {
380: redHex = "0" + redHex;
381: }
382: if (greenHex.length() == 1) {
383: greenHex = "0" + greenHex;
384: }
385: if (blueHex.length() == 1) {
386: blueHex = "0" + blueHex;
387: }
388: return redHex + greenHex + blueHex;
389: } // end handlePixel
390:
391: /**
392: * Describe <code>imageUpdate</code> method here.
393: *
394: * @param img an <code>Image</code> value
395: * @param infoflags an <code>int</code> value
396: * @param x an <code>int</code> value
397: * @param y an <code>int</code> value
398: * @param w an <code>int</code> value
399: * @param h an <code>int</code> value
400: * @return a <code>boolean</code> value
401: */
402: public boolean imageUpdate(Image img, int infoflags, int x, int y,
403: int w, int h) {
404: System.err.println("img=" + img + "\ninfoflags=" + infoflags
405: + "\nx=" + x + " y=" + y + " w=" + w + " h=" + h);
406: //if(img == this.img) {
407: if (infoflags == ImageObserver.WIDTH)
408: width = w;
409: if (infoflags == ImageObserver.HEIGHT)
410: height = h;
411:
412: //return true;
413: //}
414: return false;
415: }
416:
417: } // end class PDFImage
|