Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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">&lt;chris@sixlegs.com&gt;</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> &nbsp;</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}