Source code: com/sixlegs/image/png/PngImage.java
1 /*
2 com.sixlegs.image.png - Java package to read and display PNG images
3 Copyright (C) 1998, 1999, 2001 Chris Nokleberg
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19 */
20
21 package com.sixlegs.image.png;
22
23 import java.awt.Color;
24 import java.awt.image.ImageConsumer;
25 import java.awt.image.ImageProducer;
26 import java.io.BufferedInputStream;
27 import java.io.FileInputStream;
28 import java.io.EOFException;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.net.URL;
32 import java.util.Enumeration;
33 import java.util.Hashtable;
34 import java.util.Vector;
35
36 /**
37 * For more information visit <a href="http://www.sixlegs.com/">http://www.sixlegs.com/</a>
38 * @see java.awt.image.ImageProducer
39 * @version 1.2.3 May 14, 2002
40 * @author Chris Nokleberg <a href="mailto:chris@sixlegs.com"><chris@sixlegs.com></a>
41 */
42
43 public final class PngImage
44 implements ImageProducer
45 {
46 /* package */ static boolean allFatal = false;
47 /* package */ static final int BUFFER_SIZE = 8192;
48 private static boolean progressive = true;
49 private static Hashtable prototypes = new Hashtable();
50
51 /* package */ static final String ASCII_ENCODING = "US-ASCII";
52 /* package */ static final String LATIN1_ENCODING = "8859_1";
53 /* package */ static final String UTF8_ENCODING = "UTF8";
54 /* package */ static final long DEFAULT_GAMMA = 45455;
55 private static double DISPLAY_EXPONENT = 2.2;
56 private static double USER_EXPONENT = 1.0;
57
58 /* package */ Data data = new Data();
59 private Vector errorList;
60
61 final class Data {
62 private final Vector consumers = new Vector();
63 private final Hashtable chunks = new Hashtable();
64
65 private int[] pixels;
66 private boolean produceFailed;
67 private boolean useFlush = false;
68
69 /* package */ IDATInputStream in_idat;
70 /* package */ Chunk_IHDR header;
71 /* package */ Chunk_PLTE palette;
72 /* package */ final int[] gammaTable = new int[256];
73 /* package */ final Hashtable textChunks = new Hashtable();
74 /* package */ final Hashtable properties = new Hashtable();
75 /* package */ final Hashtable palettes = new Hashtable(1);
76 /* package */ final Vector gifExtensions = new Vector();
77
78 public Data() {}
79 }
80
81 /////////////////// start public ////////////////////////
82
83 public static final int COLOR_TYPE_GRAY = 0;
84 public static final int COLOR_TYPE_GRAY_ALPHA = 4;
85 public static final int COLOR_TYPE_PALETTE = 3;
86 public static final int COLOR_TYPE_RGB = 2;
87 public static final int COLOR_TYPE_RGB_ALPHA = 6;
88
89 public static final int INTERLACE_TYPE_NONE = 0;
90 public static final int INTERLACE_TYPE_ADAM7 = 1;
91
92 public static final int FILTER_TYPE_BASE = 0;
93 public static final int FILTER_TYPE_INTRAPIXEL = 64;
94
95 public static final int COMPRESSION_TYPE_BASE = 0;
96
97 public static final int UNIT_UNKNOWN = 0;
98 public static final int UNIT_METER = 1;
99 public static final int UNIT_PIXEL = 0;
100 public static final int UNIT_MICROMETER = 1;
101 public static final int UNIT_RADIAN = 2;
102
103 public static final int SRGB_PERCEPTUAL = 0;
104 public static final int SRGB_RELATIVE_COLORIMETRIC = 1;
105 public static final int SRGB_SATURATION_PRESERVING = 2;
106 public static final int SRGB_ABSOLUTE_COLORIMETRIC = 3;
107
108 /**
109 * Constructs a PngImage object from a local PNG file.
110 * @param filename full path to local PNG file
111 */
112 public PngImage(String filename)
113 throws IOException
114 {
115 FileInputStream fs = new FileInputStream(filename);
116 init(new BufferedInputStream(fs, BUFFER_SIZE));
117 }
118
119 /**
120 * Constructs a <code>PngImage</code> object from a URL.
121 * @param filename URL of PNG image
122 */
123 public PngImage(URL url)
124 throws IOException
125 {
126 InputStream is = url.openConnection().getInputStream();
127 init(new BufferedInputStream(is, BUFFER_SIZE));
128 }
129
130 /**
131 * Constructs a <code>PngImage</code> object from an input stream.
132 * Buffer the stream for better performance.
133 * @param filename URL of PNG image
134 * @see java.io.BufferedInputStream
135 */
136 public PngImage(InputStream is)
137 {
138 init(is);
139 }
140
141 /**
142 * Adds an <code>ImageConsumer</code> to the list of consumers interested in
143 * data for this image.
144 * @see java.awt.image.ImageConsumer
145 */
146 public void addConsumer(ImageConsumer ic)
147 {
148 if (data == null) return;
149 if (data.consumers.contains(ic)) return;
150 data.consumers.addElement(ic);
151 }
152
153 /**
154 * Determine if an <code>ImageConsumer</code> is on the list of consumers currently
155 * interested in data for this image.
156 * @return true if the consumer is on the list, false otherwise.
157 * @see java.awt.image.ImageConsumer
158 */
159 public boolean isConsumer(ImageConsumer ic)
160 {
161 if (data == null) return false;
162 return data.consumers.contains(ic);
163 }
164
165 /**
166 * Remove an <code>ImageConsumer</code> from the list of consumers interested in
167 * data for this image.
168 * @see java.awt.image.ImageConsumer
169 */
170 public void removeConsumer(ImageConsumer ic)
171 {
172 if (data == null) return;
173 data.consumers.removeElement(ic);
174 }
175
176 /**
177 * Adds an <code>ImageConsumer</code> to the list of consumers interested in
178 * data for this image, and immediately start delivery of the
179 * image data through the consumer/producer interface.
180 * @see java.awt.image.ImageConsumer
181 */
182 public void startProduction(ImageConsumer ic)
183 {
184 if (data == null) {
185 throw new RuntimeException("Object has been flushed.");
186 }
187 addConsumer(ic);
188 ImageConsumer[] ics = new ImageConsumer[data.consumers.size()];
189 data.consumers.copyInto(ics);
190 produceHelper(ics);
191 }
192
193 /**
194 * Requests delivery of image data to the specified <code>ImageConsumer</code>
195 * one more time in top-down, left-right order.
196 * @see #startProduction
197 * @see java.awt.image.ImageConsumer
198 */
199 public void requestTopDownLeftRightResend(ImageConsumer ic)
200 {
201 if (data == null || data.pixels == null) return;
202 startProduction(ic);
203 }
204
205 /**
206 * Sets the default desired final user exponent. Ideal setting
207 * depends on user viewing conditions. The default value is 1.0.
208 * Set greater than 1.0 to darken the mid-level tones, or less than
209 * 1.0 to lighten them.
210 * <p>
211 * This method sets the user exponent for new <code>PngImage</code> objects.
212 * It is not possible to change the user exponent of an existing
213 * <code>PngImage</code>.
214 * @param exponent desired user exponent
215 */
216 static public void setUserExponent(double exponent)
217 {
218 USER_EXPONENT = exponent;
219 }
220
221 /**
222 * Sets the default display exponent. Depends on monitor and gamma lookup
223 * table settings (if any). Default value is 2.2, which should
224 * work well with most PC displays. If the operating system has
225 * a gamma lookup table (Macintosh) the display exponent should be lower.
226 * <p>
227 * This method sets the display exponent for new <code>PngImage</code> objects.
228 * It is not possible to change the display exponent of an existing
229 * <code>PngImage</code>.
230 * @param exponent desired display exponent
231 */
232 static public void setDisplayExponent(double exponent)
233 {
234 DISPLAY_EXPONENT = exponent;
235 }
236
237 /**
238 * Checks if there were errors during image production.
239 * A good time to check this is when you implement the <code>ImageObserver</code>
240 * interface and the <code>ERROR</code> flag is set.
241 * @see java.awt.image.ImageObserver
242 * @see #getErrors
243 */
244 public boolean hasErrors()
245 {
246 if (errorList == null) return false;
247 return errorList.size() > 0;
248 }
249
250 public boolean hasFatalError()
251 {
252 return hasErrors() &&
253 !(errorList.elementAt(errorList.size() - 1) instanceof PngExceptionSoft);
254 }
255
256 /**
257 * Returns an <code>Enumeration</code> of all the errors that occurred during
258 * image production. This includes any non-fatal errors.
259 * @see #hasErrors
260 */
261 public Enumeration getErrors()
262 {
263 return errorList.elements();
264 }
265
266 /**
267 * Specifies whether all errors will abort the image production.
268 * Normally, a value error in a non-critical chunk causes the
269 * PNG loader to simply skip the offending chunk.
270 */
271 static public void setAllErrorsFatal(boolean allFatal)
272 {
273 PngImage.allFatal = allFatal;
274 }
275
276 /**
277 * Interlaced images can either be displayed when completely
278 * read (default) or progressively. When progressive display is
279 * enabled, a <code>PngImage</code> will call the <code>imageComplete</code>
280 * method of its registered image consumers with a <code>SINGLEFRAMEDONE</code>
281 * status after each pass. This, in turn, will trigger an <code>imageUpdate</code>
282 * with the <code>FRAMEBITS</code> flag set to watching <code>ImageObservers</code>.
283 * <p>
284 * <b>Note:</b> Images are only delivered progressively on the
285 * first production of the image data. Subsequent requests for the
286 * (cached) image data will send the image as a complete single
287 * frame.
288 * @see java.awt.image.ImageConsumer
289 * @see java.awt.image.ImageObserver
290 */
291 static public void setProgressiveDisplay(boolean progressive)
292 {
293 PngImage.progressive = progressive;
294 }
295
296 /**
297 * Get a suggested background color (from the bKGD chunk).
298 * @see #getProperty
299 * @return the suggested Color, or null if no valid bKGD was found.
300 */
301 public Color getBackgroundColor()
302 throws IOException
303 {
304 return (Color)getProperty("background");
305 }
306
307 /**
308 * Gets width of image in pixels.
309 * @see #getProperty
310 */
311 public int getWidth()
312 throws IOException
313 {
314 readToData();
315 return data.header.width;
316 }
317
318 /**
319 * Gets height of image in pixels.
320 * @see #getProperty
321 */
322 public int getHeight()
323 throws IOException
324 {
325 readToData();
326 return data.header.height;
327 }
328
329 /**
330 * Gets bit depth of image data.
331 * @see #getProperty
332 * @return 1, 2, 4, 8, or 16.
333 */
334 public int getBitDepth()
335 throws IOException
336 {
337 readToData();
338 return data.header.depth;
339 }
340
341 /**
342 * Gets the interlacing method used by this image.
343 * @see #getProperty
344 * @return one of the INTERLACE_TYPE_* constants.
345 */
346 public int getInterlaceType()
347 throws IOException {
348 readToData();
349 return data.header.interlace;
350 }
351
352 /**
353 * Gets the alpha and color properties of an image.
354 * An image can either be grayscale, grayscale with alpha channel,
355 * RGB, RGB with alpha channel, or paletted.
356 * @see #getProperty
357 * @return COLOR_TYPE_GRAY<br>
358 * COLOR_TYPE_GRAY_ALPHA<br>
359 * COLOR_TYPE_PALETTE<br>
360 * COLOR_TYPE_RGB<br>
361 * COLOR_TYPE_RGB_ALPHA
362 */
363 public int getColorType()
364 throws IOException
365 {
366 readToData();
367 return data.header.colorType;
368 }
369
370 /**
371 * Returns true if the image has an alpha channel.
372 * @see #getProperty
373 * @see #getColorType
374 */
375 public boolean hasAlphaChannel()
376 throws IOException
377 {
378 readToData();
379 return data.header.alphaUsed;
380 }
381
382 /**
383 * Returns true if the image is grayscale.
384 * @see #getProperty
385 * @see #getColorType
386 */
387 public boolean isGrayscale()
388 throws IOException
389 {
390 readToData();
391 return !data.header.colorUsed;
392 }
393
394 /**
395 * Returns true if the image is paletted.
396 * @see #getProperty
397 * @see #getColorType
398 */
399 public boolean isIndexedColor()
400 throws IOException
401 {
402 readToData();
403 return data.header.paletteUsed;
404 }
405
406 /**
407 * Gets a property of this image by name. If a property is not
408 * defined for a particular image, this method returns <code>null</code>.
409 * <p>
410 * <b>Note:</b> This method will only read up to the beginning of
411 * the image data unless the image data has already been read,
412 * either through the consumer/producer interface or by calling
413 * <code>getEverything</code>.
414 * <p>
415 * The following properties are guaranteed to be defined:
416 * <p>
417 * <center><table border=1 cellspacing=0 cellpadding=4 width="80%">
418 * <tr bgcolor="#E0E0E0"><td nowrap><b>Name</b></td><td nowrap><b>Type</b></td>
419 * <td><b>Description</b></td></tr>
420 * <tr><td nowrap>"width"</td><td nowrap><code>Integer</code></td>
421 * <td>Image width in pixels</td></tr>
422 * <tr><td nowrap>"height"</td><td nowrap><code>Integer</code></td>
423 * <td>Image height in pixels</td></tr>
424 * <tr><td nowrap>"interlace type"</td><td nowrap><code>Integer</code></td>
425 * <td>See <a href="#getInterlaceType">getInterlaceType</a></td></tr>
426 * <tr><td nowrap>"compression type"</td><td nowrap><code>Integer</code></td>
427 * <td><code>COMPRESSION_TYPE_BASE</code></td></tr>
428 * <tr><td nowrap>"filter type"</td><td nowrap><code>Integer</code></td>
429 * <td><code>FILTER_TYPE_BASE</code></td></tr>
430 * <tr><td nowrap>"color type"</td><td nowrap><code>Integer</code></td>
431 * <td>See <a href="#getColorType">getColorType</a></td></tr>
432 * <tr><td nowrap>"bit depth"</td><td nowrap><code>Integer</code></td>
433 * <td>1, 2, 4, 8, or 16 <sup><a href="#fn1">(1)</a></sup></td></tr>
434 * <tr><td nowrap>"gamma"</td><td nowrap><code>Long</code></td>
435 * <td>File gamma * 100000 <sup><a href="#fn2">(2)</a></sup></td></tr>
436 * <tr valign=top><td nowrap>"significant bits"</td><td nowrap><code>byte[]</code></td>
437 * <td>Significant bits per component: <br><nowrap><code>[r,g,b]</code></nowrap> or <nowrap><code>[r,g,b,alpha]</code></nowrap> <sup><a href="#fn3">(3)</a></sup></td></tr>
438 * </table></center>
439 * <center><table border=0 cellspacing=0 cellpadding=4 width="80%">
440 * <tr><td><b><sup><a name="fn1">1</a></sup></b> 16-bit pixel components are reduced to 8 bits<br>
441 * <b><sup><a name="fn2">2</a></sup></b> Uses value from <code>sRGB</code> or <code>gAMA</code> chunks,
442 * or default (<code>45455</code>)<br>
443 * <b><sup><a name="fn3">3</a></sup></b> For grayscale images, <code>r == g == b</code></td></tr>
444 * </table></center>
445
446 * <p>
447 * The following properties are optional:<p>
448 * <center><table border=1 cellspacing=0 cellpadding=4 width="80%">
449 * <tr bgcolor="#E0E0E0"><td nowrap><b>Name</b></td><td nowrap><b>Type</b></td>
450 * <td><b>Description</b></td></tr>
451 * <tr valign=top><td nowrap>"palette"</td><td nowrap><code>int[][]</td>
452 * <td>Palette or suggested palette (PLTE chunk):<br>
453 * <nowrap><code>[r,g,b][entry]</code></nowrap> or <nowrap><code>[r,g,b][entry]</code></nowrap></td></tr>
454 * <tr><td nowrap>"palette size"</td><td nowrap><code>Integer</td>
455 * <td>Size of palette, 1 - 256</td></tr>
456 * <tr valign=top><td nowrap>"histogram"</td><td nowrap><code>int[]</td>
457 * <td>Palette entry usage frequency</td></tr>
458 * <tr><td nowrap>"background"</td><td nowrap><code>java.awt.Color</td>
459 * <td>Suggested background color</td></tr>
460 * <tr><td nowrap>"background low bytes"</td><td nowrap><code>java.awt.Color</td>
461 * <td>The low (least significant) bytes of a 16-bit background color</td></tr>
462 * <tr><td nowrap>"background index"</td><td nowrap><code>Integer</td>
463 * <td>The palette index of the suggested background color</td></tr>
464 * <tr><td nowrap>"time"</td><td nowrap><code>java.util.Date</code></td>
465 * <td>Time of last image modification</td></tr>
466 * <tr><td nowrap>"pixel dimensions x"</td><td nowrap><code>Long</code></td>
467 * <td>Pixels per unit, X axis</td></tr>
468 * <tr><td nowrap>"pixel dimensions y"</td><td nowrap><code>Long</code></td>
469 * <td>Pixels per unit, Y axis</td></tr>
470 * <tr valign=top><td nowrap>"pixel dimensions unit"</td><td nowrap><code>Integer</code></td>
471 * <td><code>UNIT_UNKNOWN</code> or <code>UNIT_METER</code></td></tr>
472 * <tr valign=top><td nowrap>"image position x"</td><td nowrap><code>Integer</code></td>
473 * <td>Horizontal offset from left of page</td></tr>
474 * <tr valign=top><td nowrap>"image position y"</td><td nowrap><code>Integer</code></td>
475 * <td>Vertical offset from top of page</td></tr>
476 * <tr valign=top><td nowrap>"image position unit"</td><td nowrap><code>Integer</code></td>
477 * <td><code>UNIT_PIXEL</code> or <code>UNIT_MICROMETER</code></td></tr>
478 * <tr valign=top><td nowrap>"pixel scale x"</td><td nowrap><code>Double</code></td>
479 * <td>Pixel width, physical scale of subject</td></tr>
480 * <tr valign=top><td nowrap>"pixel scale y"</td><td nowrap><code>Double</code></td>
481 * <td>Pixel height, physical scale of subject</td></tr>
482 * <tr valign=top><td nowrap>"pixel scale unit"</td><td nowrap><code>Integer</code></td>
483 * <td><code>UNIT_METER</code> or <code>UNIT_RADIAN</code></td></tr>
484 * <tr valign=top><td nowrap>"chromaticity xy"</td><td nowrap><code>long[][]</code></td>
485 * <td>CIE x,y chromaticities * 100000: <nowrap><code>[white,r,g,b][x,y]</code></nowrap></td></tr>
486 * <tr valign=top><td nowrap>"chromaticity xyz"</td><td nowrap><code>double[][]</code></td>
487 * <td>CIE XYZ chromaticities: <nowrap><code>[white,r,g,b][X,Y,Z]</code></nowrap></td></tr>
488 * <tr valign=top><td nowrap nowrap>"srgb rendering intent"</td><td nowrap><code>Integer</code></td><td>
489 * <code> SRGB_PERCEPTUAL</code> or<br>
490 * <code> SRGB_RELATIVE_COLORIMETRIC</code> or<br>
491 * <code> SRGB_SATURATION_PRESERVING</code> or<br>
492 * <code> SRGB_ABSOLUTE_COLORIMETRIC</code></td></tr>
493 * <tr><td nowrap>"icc profile name"</td><td nowrap><code>String</code></td>
494 * <td>Internal ICC profile name </td></tr>
495 * <tr><td nowrap>"icc profile"</td><td nowrap><code>String</code></td>
496 * <td>Uncompressed ICC profile </td></tr>
497 * <tr><td nowrap>"pixel calibration purpose"</td><td nowrap><code>String</code></td>
498 * <td> Equation identifier</td></tr>
499 * <tr><td nowrap>"pixel calibration x0"</td><td nowrap><code>Integer</code></td>
500 * <td> Lower limit of original sample range</td></tr>
501 * <tr><td nowrap>"pixel calibration x1"</td><td nowrap><code>Integer</code></td>
502 * <td> Upper limit of original sample range</td></tr>
503 * <tr valign=top><td nowrap>"pixel calibration type"</td><td nowrap><code>Integer</code></td>
504 * <td>
505 * <code>0</code>: Linear mapping<br>
506 * <code>1</code>: Base-e exponential mapping<br>
507 * <code>2</code>: Arbitrary-base exponential mapping<br>
508 * <code>3</code>: Hyperbolic mapping
509 * </td></tr>
510 * <tr><td nowrap>"pixel calibration n"</td><td nowrap><code>Integer</code></td>
511 * <td> Number of parameters</td></tr>
512 * <tr><td nowrap>"pixel calibration unit"</td><td nowrap><code>String</code></td>
513 * <td> Symbol or description of unit</td></tr>
514 * <tr><td nowrap>"pixel calibration parameters"</td><td nowrap><code>double[]</code></td>
515 * <td> </td></tr>
516 * <tr><td nowrap>"gif disposal method"</td><td nowrap><code>Integer</code></td>
517 * <td>See GIF89a Graphic Control Extension specification</td></tr>
518 * <tr><td nowrap>"gif user input flag"</td><td nowrap><code>Integer</code></td>
519 * <td>See GIF89a Graphic Control Extension specification</td></tr>
520 * <tr><td nowrap>"gif delay time"</td><td nowrap><code>Integer</code></td>
521 * <td>See GIF89a Graphic Control Extension specification</td></tr>
522
523 * <tr><td nowrap>"transparency"</td><td nowrap><code>java.awt.Color</td>
524 * <td>Transparent color <sup><a href="#fn4">(4)</a></sup></td></tr>
525 * <tr><td nowrap>"transparency low bytes"</td><td nowrap><code>java.awt.Color</td>
526 * <td>The low (least significant) bytes of a 16-bit transparency color <sup><a href="#fn4">(4)</a></sup></td></tr>
527 * <tr><td nowrap>"transparency size"</td><td nowrap><code>Integer</td>
528 * <td>The number of palette entries with transparency information <sup><a href="#fn5">(5)</a></sup></td></tr>
529
530 * </table></center>
531 * <center><table border=0 cellspacing=0 cellpadding=4 width="80%">
532 * <tr><td><b><sup><a name="fn4">4</a></sup></b> Grayscale or truecolor images only<br>
533 * <b><sup><a name="fn5">5</a></sup></b> Indexed-color images only</td></tr>
534 * </table></center>
535
536 * <p>
537 * In addition, certain common (but still optional) text chunks
538 * are available through the <code>getProperty</code> interface:<p>
539 * <center><table border=1 cellspacing=0 cellpadding=4 width="80%">
540 * <tr bgcolor="#E0E0E0"><td nowrap><b>Name</b></td><td nowrap><b>Type</b></td>
541 * <td><b>Description</b></td></tr>
542 * <tr><td nowrap>"title"</td><td nowrap><code>TextChunk</code></td>
543 * <td>Short (one line) title or caption for image</td></tr>
544 * <tr><td nowrap>"author"</td><td nowrap><code>TextChunk</code></td>
545 * <td>Name of image's creator</td></tr>
546 * <tr><td nowrap>"description"</td><td nowrap><code>TextChunk</code></td>
547 * <td>Description of image (possibly long)</td></tr>
548 * <tr><td nowrap>"copyright"</td><td nowrap><code>TextChunk</code></td>
549 * <td>Copyright notice</td></tr>
550 * <tr><td nowrap>"creation time"</td><td nowrap><code>TextChunk</code></td>
551 * <td>Time of original image creation</td></tr>
552 * <tr><td nowrap>"software"</td><td nowrap><code>TextChunk</code></td>
553 * <td>Software used to create the image</td></tr>
554 * <tr><td nowrap>"disclaimer"</td><td nowrap><code>TextChunk</code></td>
555 * <td>Legal disclaimer</td></tr>
556 * <tr><td nowrap>"warning"</td><td nowrap><code>TextChunk</code></td>
557 * <td>Warning of nature of content</td></tr>
558 * <tr><td nowrap>"source"</td><td nowrap><code>TextChunk</code></td>
559 * <td>Device used to create the image</td></tr>
560 * <tr><td nowrap>"comment"</td><td nowrap><code>TextChunk</code></td>
561 * <td>Miscellaneous comment</td></tr>
562 * </table></center>
563
564 * @see #getEverything
565 * @see #getWidth
566 * @see #getHeight
567 * @see #getInterlaceType
568 * @see #getColorType
569 * @see #getTextChunk
570 * @see #getBackgroundColor
571 * @param name a property name
572 * @return the value of the named property.
573 */
574 public Object getProperty(String name)
575 throws IOException
576 {
577 readToData();
578 return data.properties.get(name);
579 }
580
581 /**
582 * Returns an <code>Enumeration</code> of the available properties.
583 * @see #getProperty
584 */
585 public Enumeration getProperties()
586 throws IOException
587 {
588 readToData();
589 return data.properties.keys();
590 }
591
592 /**
593 * Ensures that the entire PNG file has been read. No exceptions
594 * are throws; errors are available by calling <code>getErrors</code>.
595 * <p>
596 * <b>Note:</b> The consumer/producer interface automatically
597 * reads the entire PNG file. It usually is not necessary to call
598 * <code>getEverything</code> unless you do not need the actual
599 * image data.
600 * @see #getErrors
601 */
602 public void getEverything()
603 {
604 startProduction(new DummyImageConsumer());
605 }
606
607 public boolean hasChunk(String type)
608 {
609 return data.chunks.get(new Integer(Chunk.stringToType(type))) != null;
610 }
611
612 /**
613 * Register a <code>ChunkHandler</code> to handle a user defined
614 * chunk type.
615 * <p>
616 * The chunk type must be four characters, ancillary (lowercase first letter),
617 * and may not already be registered. You may register one of the supported
618 * ancillary chunk types (except <code>tRNS</code>) to override the standard behavior.
619 * @see ChunkHandler
620 * @param handler object to send chunk data to
621 * @param type chunk type
622 */
623 public static void registerChunk(ChunkHandler handler, String type)
624 throws PngException
625 {
626 if (type.length() < 4) {
627 throw new PngException("Invalid chunk type length.");
628 }
629
630 int type_int = Chunk.stringToType(type);
631
632 if (prototypes.containsKey(new Integer(type_int))) {
633 throw new PngException("Chunk type already registered.");
634 }
635 if ((type_int & 0x20000000) == 0) {
636 throw new PngException("Chunk must be ancillary.");
637 }
638
639 registerChunk(new UserChunk(handler, type_int));
640 }
641
642 /**
643 * Returns an <code>Enumeration</code> of the available suggested palette names.
644 * @see #getSuggestedPalette
645 */
646 public Enumeration getSuggestedPalettes()
647 throws IOException
648 {
649 readToData();
650 return data.palettes.keys();
651 }
652
653 /**
654 * Returns the suggested palette (sPLT chunk) specified by the
655 * palette name.
656 * @see #getSuggestedPalette
657 * @param name the name of the suggested palette
658 * @return <nowrap><code>[r,g,b,alpha,freq][entry]</code></nowrap>, or null if not present.
659 */
660 public int[][] getSuggestedPalette(String name)
661 throws IOException
662 {
663 readToData();
664 return (int[][])data.palettes.get(name);
665 }
666
667 /**
668 * Returns the specified text chunk.
669 * <p>
670 * <b>Note:</b> Text chunks may appear anywhere in the file. This
671 * method will only read up to the beginning of the image data
672 * unless the image data has already been read, either through the
673 * consumer/producer interface or by calling
674 * <code>getEverything</code>.
675 * @see #getTextChunks
676 * @see #getEverything
677 * @see #getProperty
678 * @param key the key of the desired chunk
679 * @return the text chunk, or null if not present.
680 */
681 public TextChunk getTextChunk(String key)
682 throws IOException
683 {
684 readToData();
685 return (TextChunk)data.textChunks.get(key);
686 }
687
688 /**
689 * Returns the keys of all known text chunks.
690 * <p>
691 * <b>Note:</b> Text chunks may appear anywhere in the file. This
692 * method will only read up to the beginning of the image data
693 * unless the image data has already been read, either through the
694 * consumer/producer interface or by calling
695 * <code>getEverything</code>.
696 * @see #getTextChunk
697 * @see #getEverything
698 * @return an <code>Enumeration</code> of the keys of text chunks read so far.
699 */
700 public Enumeration getTextChunks()
701 throws IOException
702 {
703 readToData();
704 return data.textChunks.elements();
705 }
706
707 /**
708 * Returns all known GIF Application Extensions.
709 * <p>
710 * <b>Note:</b> GIF Application Extensions may appear anywhere in
711 * the file. This method will only read up to the beginning of the
712 * image data unless the image data has already been read, either
713 * through the consumer/producer interface or by calling
714 * <code>getEverything</code>.
715 * @see #getEverything
716 * @return an <code>Enumeration</code> of all GifExtension objects read so far.
717 * @see GifExtension
718 */
719 public Enumeration getGifExtensions()
720 throws IOException
721 {
722 readToData();
723 return data.gifExtensions.elements();
724 }
725
726 /**
727 * Readies this PngImage to be flushed after the next image
728 * production, to free memory (default false).
729 * <p>
730 * After flushing, you may only call the <code>getErrors</code>
731 * and <code>hasErrors</code> methods on this object. The pixel
732 * data will no longer be available through the consumer/producer
733 * interface.
734 * <p>
735 * <b>Note:</b> Using a PixelGrabber object on an Image produced
736 * by this PngImage object will ask for a second production of the
737 * pixel data, which will fail if the object has been flushed.
738 * @see #getErrors
739 * @see #hasErrors
740 */
741 public void setFlushAfterNextProduction(boolean useFlush)
742 {
743 data.useFlush = useFlush;
744 }
745
746 private void flush()
747 {
748 if (data != null) {
749 try {
750 data.in_idat.close();
751 } catch (IOException e) {
752 // TODO: ignore?
753 }
754 data = null;
755 }
756 }
757
758 /////////////////// end public ////////////////////////
759
760 static {
761 registerChunk(new Chunk_IHDR());
762 registerChunk(new Chunk_PLTE());
763 registerChunk(new Chunk_IDAT());
764 registerChunk(new Chunk_IEND());
765 registerChunk(new Chunk_tRNS());
766 }
767
768 private void init(InputStream in_raw)
769 {
770 data.properties.put("gamma", new Long(DEFAULT_GAMMA));
771 data.in_idat = new IDATInputStream(this, in_raw);
772 }
773
774 private synchronized void readToData()
775 throws IOException
776 {
777 try {
778 if (data == null) {
779 throw new EOFException("Object has been flushed.");
780 }
781 data.in_idat.readToData();
782 } catch (PngException e) {
783 addError(e);
784 throw e;
785 }
786 }
787
788 private static void registerChunk(Chunk proto)
789 {
790 prototypes.put(new Integer(proto.type), proto);
791 }
792
793 /* package */ static Chunk getRegisteredChunk(int type)
794 {
795 Integer type_obj = new Integer(type);
796 if (prototypes.containsKey(type_obj)) {
797 return ((Chunk)prototypes.get(type_obj)).copy();
798 } else {
799 try {
800 String clsName =
801 "com.sixlegs.image.png.Chunk_" + Chunk.typeToString(type);
802 registerChunk((Chunk)Class.forName(clsName).newInstance());
803 return getRegisteredChunk(type);
804 } catch (Exception e) {
805 return new Chunk(type);
806 }
807 }
808 }
809
810 /* package */ Chunk getChunk(int type)
811 {
812 return (Chunk)data.chunks.get(new Integer(type));
813 }
814
815 /* package */ void putChunk(int type, Chunk c)
816 {
817 data.chunks.put(new Integer(type), c);
818 }
819
820 /* package */ void addError(Exception e)
821 {
822 if (errorList == null) {
823 errorList = new Vector();
824 }
825 errorList.addElement(e);
826 }
827
828 /* package */ void fillGammaTable()
829 {
830 try {
831 long file_gamma = ((Long)getProperty("gamma")).longValue();
832 int max = (data.header.paletteUsed ? 0xFF : (1 << data.header.outputDepth) - 1);
833 double decoding_exponent =
834 (USER_EXPONENT * 100000d / (file_gamma * DISPLAY_EXPONENT));
835 for (int i = 0; i <= max; i++) {
836 int v = (int)(Math.pow((double)i / max, decoding_exponent) * 0xFF);
837 if (!data.header.colorUsed) {
838 data.gammaTable[i] = v | v << 8 | v << 16;
839 } else {
840 data.gammaTable[i] = v;
841 }
842 }
843 if (data.palette != null) data.palette.calculate();
844 } catch (IOException e) { }
845 }
846
847 private synchronized void produceHelper(ImageConsumer[] ics)
848 {
849 try {
850 readToData();
851 for (int i = 0; i < ics.length; i++) {
852 ics[i].setDimensions(data.header.width, data.header.height);
853 ics[i].setProperties(data.properties);
854 ics[i].setColorModel(data.header.model);
855 if (data.produceFailed) ics[i].imageComplete(ImageConsumer.IMAGEERROR);
856 }
857 if (data.produceFailed) return;
858 if (data.pixels == null) {
859 firstProduction(ics);
860 } else {
861 setHints(ics);
862 for (int i = 0; i < ics.length; i++) {
863 ics[i].setPixels(0,
864 0,
865 data.header.width,
866 data.header.height,
867 data.header.model,
868 data.pixels,
869 0,
870 data.header.width);
871 ics[i].imageComplete(ImageConsumer.STATICIMAGEDONE);
872 }
873 }
874 } catch (IOException e) {
875 data.produceFailed = true;
876 addError(e);
877 for (int i = 0; i < ics.length; i++) {
878 ics[i].imageComplete(ImageConsumer.IMAGEERROR);
879 }
880 }
881 if (data.useFlush) flush();
882 }
883
884 private void firstProduction(ImageConsumer[] ics)
885 throws IOException
886 {
887 UnfilterInputStream in_filter
888 = new UnfilterInputStream(this, data.in_idat);
889 InputStream is =
890 new BufferedInputStream(in_filter, BUFFER_SIZE);
891 PixelInputStream pis =
892 new PixelInputStream(this, is);
893
894 setHints(ics);
895
896 if (data.header.interlace == INTERLACE_TYPE_NONE) {
897 // this is just for optimization
898 // omitting it will cause NullInterlacer to be used
899 produceNonInterlaced(ics, pis);
900 } else {
901 produceInterlaced(ics, pis);
902 }
903
904 for (int i = 0; i < ics.length; i++) {
905 ics[i].imageComplete(ImageConsumer.STATICIMAGEDONE);
906 }
907 }
908
909 private void setHints(ImageConsumer[] ics)
910 {
911 for (int i = 0; i < ics.length; i++) {
912 if (progressive &&
913 data.pixels == null &&
914 (data.header.interlace != INTERLACE_TYPE_NONE)) {
915 ics[i].setHints(ImageConsumer.RANDOMPIXELORDER);
916 } else {
917 ics[i].setHints(ImageConsumer.TOPDOWNLEFTRIGHT |
918 ImageConsumer.SINGLEPASS |
919 ImageConsumer.SINGLEFRAME |
920 ImageConsumer.COMPLETESCANLINES);
921 }
922 }
923 }
924
925 private void produceNonInterlaced(ImageConsumer[] ics, PixelInputStream pis)
926 throws IOException
927 {
928 int w = data.header.width, h = data.header.height;
929
930 // if we're going to flush, don't bother saving pixel data
931 if (!data.useFlush) {
932 data.pixels = new int[w * h];
933 }
934
935 int[] rowbuf = new int[w + 8];
936 int pixelsWidth = w;
937 int extra = w % pis.fillSize;
938 if (extra > 0) pixelsWidth += (pis.fillSize - extra);
939 int off = 0;
940 for (int y = 0; y < h; y++) {
941 pis.read(rowbuf, 0, pixelsWidth);
942 for (int i = 0; i < ics.length; i++) {
943 ics[i].setPixels(0, y, w, 1, data.header.model, rowbuf, 0, pixelsWidth);
944 }
945 if (!data.useFlush) {
946 System.arraycopy(rowbuf, 0, data.pixels, w * y, w);
947 }
948 }
949 }
950
951 private void produceInterlaced(ImageConsumer[] ics, PixelInputStream pis)
952 throws IOException
953 {
954 int w = data.header.width, h = data.header.height;
955 data.pixels = new int[w * h];
956 int[] rowbuf = new int[w + 8];
957
958 int numPasses = data.header.interlacer.numPasses();
959 Interlacer lace = data.header.interlacer;
960
961 for (int pass = 0; pass < numPasses; pass++) {
962 int passWidth = lace.getPassWidth(pass);
963 int extra = passWidth % pis.fillSize;
964 if (extra > 0) passWidth += (pis.fillSize - extra);
965
966 int blockWidth = (progressive ? lace.getBlockWidth(pass) : 1);
967 int blockHeight = (progressive ? lace.getBlockHeight(pass) : 1);
968 int rowIncrement = lace.getSpacingY(pass);
969 int colIncrement = lace.getSpacingX(pass);
970 int offIncrement = rowIncrement * w;
971 int colStart = lace.getOffsetX(pass);
972 int row = lace.getOffsetY(pass);
973 int off = row * w;
974
975 while (row < h) {
976 pis.read(rowbuf, 0, passWidth);
977 int col = colStart;
978 int x = 0;
979 while (col < w) {
980 int bw = Math.min(blockWidth, w - col);
981 int bh = Math.min(blockHeight, h - row);
982 int poff = off + col;
983 int pix = rowbuf[x++];
984 while (bh-- > 0) {
985 int poffend = poff + bw;
986 while (poff < poffend) {
987 data.pixels[poff++] = pix;
988 }
989 poff += w - bw;
990 }
991 col += colIncrement;
992 }
993 off += offIncrement;
994 row += rowIncrement;
995 }
996 if (progressive) {
997 for (int i = 0; i < ics.length; i++) {
998 ics[i].setPixels(0, 0, w, h, data.header.model, data.pixels, 0, w);
999 ics[i].imageComplete(ImageConsumer.SINGLEFRAMEDONE);
1000 }
1001 }
1002 }
1003 if (!progressive) {
1004 for (int i = 0; i < ics.length; i++) {
1005 ics[i].setPixels(0, 0, w, h, data.header.model, data.pixels, 0, w);
1006 }
1007 }
1008 }
1009}