2003/12/16 20:55:24
[org.ibex.core.git] / src / org / xwt / translators / PNG.java
1 /*
2  * This file was adapted from Jason Marshall's PNGImageProducer.java
3  *
4  * Copyright (c) 1997, Jason Marshall.  All Rights Reserved
5  *
6  * The author makes no representations or warranties regarding the suitability,
7  * reliability or stability of this code.  This code is provided AS IS.  The
8  * author shall not be liable for any damages suffered as a result of using,
9  * modifying or redistributing this software or any derivitives thereof.
10  * Permission to use, reproduce, modify and/or (re)distribute this software is
11  * hereby granted.
12  */
13
14 package org.xwt.translators;
15
16 import org.xwt.*;
17 import org.xwt.util.*;
18 import java.io.*;
19 import java.util.Hashtable;
20 import java.util.Vector;
21 import java.util.Enumeration;
22 import java.util.zip.*;
23
24 /** Converts an InputStream carrying a PNG image into an ARGB int[] */
25 public class PNG {
26
27     public PNG() { }
28     
29     private static Queue instances = new Queue(10);
30
31     public static void load(InputStream is, Picture p) {
32         PNG g = (PNG)instances.remove(false);
33         if (g == null) g = new PNG();
34         try {
35             g._load(is, p);
36         } catch (Exception e) {
37             if (Log.on) Log.log(PNG.class, e);
38             return;
39         }
40         // FIXME: must reset fields
41         // if (instances.size() < 10) instances.append(g);
42     }
43
44     // Public Methods ///////////////////////////////////////////////////////////////////////////////
45
46     private Picture p;
47
48     /** process a PNG as an inputstream; returns null if there is an error
49         @param name A string describing the image, to be used when logging errors
50     */
51     private void _load(InputStream is, Picture p) throws IOException {
52         this.p = p;
53         if (is instanceof BufferedInputStream) underlyingStream = (BufferedInputStream)is;
54         else underlyingStream = new BufferedInputStream(is);
55         target_offset = 0;
56         inputStream = new DataInputStream(underlyingStream);
57         
58         // consume the header
59         if ((inputStream.read() != 137) || (inputStream.read() != 80) || (inputStream.read() != 78) || (inputStream.read() != 71) ||
60             (inputStream.read() != 13) || (inputStream.read() != 10) || (inputStream.read() != 26) || (inputStream.read() != 10)) {
61             Log.log(this, "PNG: error: input file is not a PNG file");
62             data = new int[] { };
63             p.width = p.height = 0;
64             return;
65         }
66         
67         DONE: while (!error) {
68             if (needChunkInfo) {
69                 chunkLength = inputStream.readInt();
70                 chunkType = inputStream.readInt();
71                 needChunkInfo = false;
72             }
73             
74             switch (chunkType) {
75             case CHUNK_bKGD: inputStream.skip(chunkLength); break;
76             case CHUNK_cHRM: inputStream.skip(chunkLength); break;
77             case CHUNK_gAMA: inputStream.skip(chunkLength); break;
78             case CHUNK_hIST: inputStream.skip(chunkLength); break;
79             case CHUNK_pHYs: inputStream.skip(chunkLength); break;
80             case CHUNK_sBIT: inputStream.skip(chunkLength); break;
81             case CHUNK_tEXt: inputStream.skip(chunkLength); break;
82             case CHUNK_zTXt: inputStream.skip(chunkLength); break;
83             case CHUNK_tIME: inputStream.skip(chunkLength); break;
84                 
85             case CHUNK_IHDR: handleIHDR(); break;
86             case CHUNK_PLTE: handlePLTE(); break;
87             case CHUNK_tRNS: handletRNS(); break;
88                 
89             case CHUNK_IDAT: handleIDAT(); break;
90                 
91             case CHUNK_IEND: break DONE;
92             default:
93                 System.err.println("unrecognized chunk type " +
94                                    Integer.toHexString(chunkType) + ". skipping");
95                 inputStream.skip(chunkLength);
96             }
97             
98             int crc = inputStream.readInt();
99             needChunkInfo = true;
100         }
101         p.isLoaded = true;
102     }
103
104     // Chunk Handlers ///////////////////////////////////////////////////////////////////////
105
106     /** handle data chunk */
107     private void handleIDAT() throws IOException {
108         if (p.width == -1 || p.height == -1) throw new IOException("never got image width/height");
109         switch (depth) {
110         case 1: mask = 0x1; break;
111         case 2: mask = 0x3; break;
112         case 4: mask = 0xf; break;
113         case 8: case 16: mask = 0xff; break;
114         default: mask = 0x0; break;
115         }
116         if (depth < 8) smask = mask << depth;
117         else smask = mask << 8;
118
119         int count = p.width * p.height;
120
121         switch (colorType) {
122         case 0:
123         case 2:
124         case 6:
125         case 4:
126             ipixels = new int[count];
127             pixels = ipixels;
128             break;
129         case 3:
130             bpixels = new byte[count];
131             pixels = bpixels;
132             break;
133         default:
134             throw new IOException("Image has unknown color type");
135         }
136         if (interlaceMethod != 0) multipass = true;
137         readImageData();
138     }
139
140     /** handle header chunk */
141     private void handleIHDR() throws IOException {
142         if (headerFound) throw new IOException("Extraneous IHDR chunk encountered.");
143         if (chunkLength != 13) throw new IOException("IHDR chunk length wrong: " + chunkLength);
144         p.width = inputStream.readInt();
145         p.height = inputStream.readInt();
146         depth = inputStream.read();
147         colorType = inputStream.read();
148         compressionMethod = inputStream.read();
149         filterMethod = inputStream.read();
150         interlaceMethod = inputStream.read();
151     }
152
153     /** handle pallette chunk */
154     private void handlePLTE() throws IOException {
155         if (colorType == 3) {
156             palette = new byte[chunkLength];
157             inputStream.readFully(palette);
158         } else {
159             // Ignore suggested palette
160             inputStream.skip(chunkLength);
161         }
162     }
163
164     /** handle transparency chunk; modifies palette */
165     private void handletRNS() throws IOException {
166         int chunkLen = chunkLength;
167         if (palette == null) {
168             if (Log.on) Log.log(this, "warning: tRNS chunk encountered before pLTE; ignoring alpha channel");
169             inputStream.skip(chunkLength);
170             return;
171         }
172         int len = palette.length;
173         if (colorType == 3) {
174             transparency = true;
175
176             int transLength = len/3;
177             byte[] trans = new byte[transLength];
178             for (int i = 0; i < transLength; i++) trans[i] = (byte) 0xff;
179             inputStream.readFully(trans, 0, chunkLength);
180
181             byte[] newPalette = new byte[len + transLength];
182             for (int i = newPalette.length; i > 0;) {
183                 newPalette[--i] = trans[--transLength];
184                 newPalette[--i] = palette[--len];
185                 newPalette[--i] = palette[--len];
186                 newPalette[--i] = palette[--len];
187             }
188             palette = newPalette;
189
190         } else {
191             inputStream.skip(chunkLength);
192         }
193     }
194
195     /// Helper functions for IDAT ///////////////////////////////////////////////////////////////////////////////////////////
196
197     /** Read Image data in off of a compression stream */
198     private void readImageData() throws IOException {
199         InputStream dataStream = new SequenceInputStream(new IDATEnumeration(this));
200         DataInputStream dis = new DataInputStream(new BufferedInputStream(new InflaterInputStream(dataStream, new Inflater())));
201         int bps, filterOffset;
202         switch (colorType) {
203           case 0: case 3: bps = depth; break;
204           case 2: bps = 3 * depth; break;
205           case 4: bps = depth<<1; break;
206           case 6: bps = depth<<2; break;
207           default: throw new IOException("Unknown color type encountered.");
208         }
209
210         filterOffset = (bps + 7) >> 3;
211
212         for (pass = (multipass ? 1 : 0); pass < 8; pass++) {
213             int pass = this.pass;
214             int rInc = rowInc[pass];
215             int cInc = colInc[pass];
216             int sCol = startingCol[pass];
217             int val = (p.width - sCol + cInc - 1) / cInc;
218             int samples = val * filterOffset;
219             int rowSize = (val * bps)>>3;
220             int sRow = startingRow[pass];
221             if (p.height <= sRow || rowSize == 0) continue;
222             int sInc = rInc * p.width;
223             byte inbuf[] = new byte[rowSize];
224             int pix[] = new int[rowSize];
225             int upix[] = null;
226             int temp[] = new int[rowSize];
227             int nextY = sRow;               // next Y value and number of rows to report to sendPixels
228             int rows = 0;
229             int rowStart = sRow * p.width;
230
231             for (int y = sRow; y < p.height; y += rInc, rowStart += sInc) {
232                 rows += rInc;
233                 int rowFilter = dis.read();
234                 dis.readFully(inbuf);
235                 if (!filterRow(inbuf, pix, upix, rowFilter, filterOffset)) throw new IOException("Unknown filter type: " + rowFilter);
236                 insertPixels(pix, rowStart + sCol, samples);
237                 if (multipass && (pass < 6)) blockFill(rowStart);
238                 upix = pix;
239                 pix = temp;
240                 temp = upix;
241             }
242             if (!multipass) break;
243         }
244         while(dis.read() != -1) System.err.println("Leftover data encountered.");
245
246         // 24-bit color is our native format
247         if (colorType == 2 || colorType == 6) {
248             data = (int[])pixels;
249             if (colorType == 2) {
250                 for(int i=0; i<data.length; i++)
251                     data[i] |= 0xFF000000;
252             }
253
254         } else if (colorType == 3) {
255             byte[] pix = (byte[])pixels;
256             data = new int[pix.length];
257             for(int i=0; i<pix.length; i++) {
258                 if (transparency) {
259                     data[i] =
260                         ((palette[4 * (pix[i] & 0xff) + 3] & 0xff) << 24) |
261                         ((palette[4 * (pix[i] & 0xff) + 0] & 0xff) << 16) |
262                         ((palette[4 * (pix[i] & 0xff) + 1] & 0xff) << 8) |
263                         (palette[4 * (pix[i] & 0xff) + 2] & 0xff);
264                 } else {
265                     data[i] =
266                         0xFF000000 |
267                         ((palette[3 * (pix[i] & 0xff) + 0] & 0xff) << 16) |
268                         ((palette[3 * (pix[i] & 0xff) + 1] & 0xff) << 8) |
269                         (palette[3 * (pix[i] & 0xff) + 2] & 0xff);
270                 }
271             }
272
273         } else if (colorType == 0 || colorType == 4) {
274             if (depth == 16) depth = 8;
275             int[] pix = (int[])pixels;
276             data = new int[pix.length];
277             for(int i=0; i<pix.length; i ++) {
278                 if (colorType == 0) {
279                     int val = (pix[i] & 0xff) << (8 - depth);
280                     data[i] =
281                         0xFF000000 | 
282                         (val << 16) | 
283                         (val << 8) | 
284                         val;
285                 } else {
286                     int alpha = (pix[i] & mask) << (8 - depth);
287                     int val = ((pix[i] & smask) >> depth) << (8 - depth);
288                     data[i] =
289                         (alpha << 24) |
290                         (val << 16) | 
291                         (val << 8) | 
292                         val;
293                 }
294             }
295         }
296
297     }
298
299     private void insertGreyPixels(int pix[], int offset, int samples) {
300         int p = pix[0];
301         int ipix[] = ipixels;
302         int cInc = colInc[pass];
303         int rs = 0;
304
305         if (colorType == 0) {
306             switch (depth) {
307             case 1:
308                 for (int j = 0; j < samples; j++, offset += cInc) {
309                     if (rs != 0) rs--;
310                     else { rs = 7; p = pix[j>>3]; }
311                     ipix[offset] = (p>>rs) & 0x1;
312                 }
313                 break;
314             case 2:
315                 for (int j = 0; j < samples; j++, offset += cInc) {
316                     if (rs != 0) rs -= 2;
317                     else { rs = 6; p = pix[j>>2]; }
318                     ipix[offset] = (p>>rs) & 0x3;
319                 }
320                 break;
321             case 4:
322                 for (int j = 0; j < samples; j++, offset += cInc) {
323                     if (rs != 0) rs = 0;
324                     else { rs = 4; p = pix[j>>1]; }
325                     ipix[offset] = (p>>rs) & 0xf;
326                 }
327                 break;
328             case 8:
329                 for (int j = 0; j < samples; offset += cInc) ipix[offset] = (byte) pix[j++];
330                 break;
331             case 16:
332                 samples = samples<<1;
333                 for (int j = 0; j < samples; j += 2, offset += cInc) ipix[offset] = pix[j];
334                 break;
335             default: break;
336             }
337         } else if (colorType == 4) {
338             if (depth == 8) {
339                 for (int j = 0; j < samples; offset += cInc) ipix[offset] = (pix[j++]<<8) | pix[j++];
340             } else {
341                 samples = samples<<1;
342                 for (int j = 0; j < samples; j += 2, offset += cInc) ipix[offset] = (pix[j]<<8) | pix[j+=2];
343             }
344         }
345     }
346
347     private void insertPalettedPixels(int pix[], int offset, int samples) {
348         int rs = 0;
349         int p = pix[0];
350         byte bpix[] = bpixels;
351         int cInc = colInc[pass];
352
353         switch (depth) {
354           case 1:
355             for (int j = 0; j < samples; j++, offset += cInc) {
356                 if (rs != 0) rs--;
357                 else { rs = 7; p = pix[j>>3]; }
358                 bpix[offset] = (byte) ((p>>rs) & 0x1);
359             }
360             break;
361           case 2:
362             for (int j = 0; j < samples; j++, offset += cInc) {
363                 if (rs != 0) rs -= 2;
364                 else { rs = 6; p = pix[j>>2]; }
365                 bpix[offset] = (byte) ((p>>rs) & 0x3);
366             }
367             break;
368           case 4:
369             for (int j = 0; j < samples; j++, offset += cInc) {
370                 if (rs != 0) rs = 0;
371                 else { rs = 4; p = pix[j>>1]; }
372                 bpix[offset] = (byte) ((p>>rs) & 0xf);
373             }
374             break;
375           case 8:
376             for (int j = 0; j < samples; j++, offset += cInc) bpix[offset] = (byte) pix[j];
377             break;
378         }
379     }
380
381     private void insertPixels(int pix[], int offset, int samples) {
382         switch (colorType) {
383         case 0:
384         case 4:
385             insertGreyPixels(pix, offset, samples);
386             break;
387         case 2: {
388             int j = 0;
389             int ipix[] = ipixels;
390             int cInc = colInc[pass];
391             if (depth == 8) {
392                 for (j = 0; j < samples; offset += cInc)
393                     ipix[offset] = (pix[j++]<<16) | (pix[j++]<<8) | pix[j++];
394             } else {
395                 samples = samples<<1;
396                 for (j = 0; j < samples; j += 2, offset += cInc)
397                     ipix[offset] = (pix[j]<<16) | (pix[j+=2]<<8) | pix[j+=2];
398             }
399             break; }
400         case 3:
401             insertPalettedPixels(pix, offset, samples);
402             break;
403         case 6: {
404             int j = 0;
405             int ipix[] = ipixels;
406             int cInc = colInc[pass];
407             if (depth == 8) {
408                 for (j = 0; j < samples; offset += cInc) {
409                     ipix[offset] = (pix[j++]<<16) | (pix[j++]<<8) | pix[j++] |
410                                     (pix[j++]<<24);
411                 }
412             } else {
413                 samples = samples<<1;
414                 for (j = 0; j < samples; j += 2, offset += cInc) {
415                     ipix[offset] = (pix[j]<<16) | (pix[j+=2]<<8) | pix[j+=2] |
416                                     (pix[j+=2]<<24);
417                 }
418             }
419             break; }
420           default:
421             break;
422         }
423     }
424
425     private void blockFill(int rowStart) {
426         int counter;
427         int dw = p.width;
428         int pass = this.pass;
429         int w = blockWidth[pass];
430         int sCol = startingCol[pass];
431         int cInc = colInc[pass];
432         int wInc = cInc - w;
433         int maxW = rowStart + dw - w;
434         int len;
435         int h = blockHeight[pass];
436         int maxH = rowStart + (dw * h);
437         int startPos = rowStart + sCol;
438         counter = startPos;
439
440         if (colorType == 3) {
441             byte bpix[] = bpixels;
442             byte pixel;
443             len = bpix.length;
444             for (; counter <= maxW;) {
445                 int end = counter + w;
446                 pixel = bpix[counter++];
447                 for (; counter < end; counter++) bpix[counter] = pixel;
448                 counter += wInc;
449             }
450             maxW += w;
451             if (counter < maxW)
452                 for (pixel = bpix[counter++]; counter < maxW; counter++)
453                     bpix[counter] = pixel;
454             if (len < maxH) maxH = len;
455             for (counter = startPos + dw; counter < maxH; counter += dw)
456                 System.arraycopy(bpix, startPos, bpix, counter, dw - sCol);
457         } else {
458             int ipix[] = ipixels;
459             int pixel;
460             len = ipix.length;
461             for (; counter <= maxW;) {
462                 int end = counter + w;
463                 pixel = ipix[counter++];
464                 for (; counter < end; counter++)
465                     ipix[counter] = pixel;
466                 counter += wInc;
467             }
468             maxW += w;
469             if (counter < maxW)
470                 for (pixel = ipix[counter++]; counter < maxW; counter++)
471                     ipix[counter] = pixel;
472             if (len < maxH) maxH = len;
473             for (counter = startPos + dw; counter < maxH; counter += dw)
474                 System.arraycopy(ipix, startPos, ipix, counter, dw - sCol);
475         }
476     }
477
478     private boolean filterRow(byte inbuf[], int pix[], int upix[], int rowFilter, int boff) {
479         int rowWidth = pix.length;
480         switch (rowFilter) {
481         case 0: {
482             for (int x = 0; x < rowWidth; x++) pix[x] = 0xff & inbuf[x];
483             break; }
484         case 1: {
485             int x = 0;
486             for ( ; x < boff; x++) pix[x] = 0xff & inbuf[x];
487             for ( ; x < rowWidth; x++) pix[x] = 0xff & (inbuf[x] + pix[x - boff]);
488             break; }
489         case 2: {
490             if (upix != null) {
491                 for (int x = 0; x < rowWidth; x++)
492                     pix[x] = 0xff & (upix[x] + inbuf[x]);
493             } else {
494                 for (int x = 0; x < rowWidth; x++)
495                     pix[x] = 0xff & inbuf[x];
496             }
497             break; }
498         case 3: {
499             if (upix != null) {
500                 int x = 0;
501                 for ( ; x < boff; x++) {
502                     int rval = upix[x];
503                     pix[x] = 0xff & ((rval>>1) + inbuf[x]);
504                 }
505                 for ( ; x < rowWidth; x++) {
506                     int rval = upix[x] + pix[x - boff];
507                     pix[x] = 0xff & ((rval>>1) + inbuf[x]);
508                 }
509             } else {
510                 int x = 0;
511                 for ( ; x < boff; x++) pix[x] = 0xff & inbuf[x];
512                 for ( ; x < rowWidth; x++) {
513                     int rval = pix[x - boff];
514                     pix[x] = 0xff & ((rval>>1) + inbuf[x]);
515                 }
516             }
517             break; }
518         case 4: {
519             if (upix != null) {
520                 int x = 0;
521                 for ( ; x < boff; x++) pix[x] = 0xff & (upix[x] + inbuf[x]);
522                 for ( ; x < rowWidth; x++) {
523                     int a, b, c, p, pa, pb, pc, rval;
524                     a = pix[x - boff];
525                     b = upix[x];
526                     c = upix[x - boff];
527                     p = a + b - c;
528                     pa = p > a ? p - a : a - p;
529                     pb = p > b ? p - b : b - p;
530                     pc = p > c ? p - c : c - p;
531                     if ((pa <= pb) && (pa <= pc)) rval = a;
532                     else if (pb <= pc) rval = b;
533                     else rval = c;
534                     pix[x] = 0xff & (rval + inbuf[x]);
535                 }
536             } else {
537                 int x = 0;
538                 for ( ; x < boff; x++) pix[x] = 0xff & inbuf[x];
539                 for ( ; x < rowWidth; x++) {
540                     int rval = pix[x - boff];
541                     pix[x] = 0xff & (rval + inbuf[x]);
542                 }
543             }
544             break; }
545         default: return false;
546         }
547         return true;
548     }
549
550     // Private Data ///////////////////////////////////////////////////////////////////////////////////////
551     
552     private int target_offset = 0;
553     private int sigmask = 0xffff;
554     private Object pixels = null;
555     private int ipixels[] = null;
556     private byte bpixels[] = null;
557     private boolean multipass = false;
558     private boolean complete = false;
559     private boolean error = false;
560
561     private int[] data = null;
562
563     private InputStream underlyingStream = null;
564     private DataInputStream inputStream = null;
565     private Thread controlThread = null;
566     private boolean infoAvailable = false;
567     private int updateDelay = 750;
568
569     // Image decoding state variables
570     private boolean headerFound = false;
571     private int compressionMethod = -1;
572     private int depth = -1;
573     private int colorType = -1;
574     private int filterMethod = -1;
575     private int interlaceMethod = -1;
576     private int pass = 0;
577     private byte palette[] = null;
578     private int mask = 0x0;
579     private int smask = 0x0;
580     private boolean transparency = false;
581
582     private int chunkLength = 0;
583     private int chunkType = 0;
584     private boolean needChunkInfo = true;
585
586     private static final int CHUNK_bKGD = 0x624B4744;   // "bKGD"
587     private static final int CHUNK_cHRM = 0x6348524D;   // "cHRM"
588     private static final int CHUNK_gAMA = 0x67414D41;   // "gAMA"
589     private static final int CHUNK_hIST = 0x68495354;   // "hIST"
590     private static final int CHUNK_IDAT = 0x49444154;   // "IDAT"
591     private static final int CHUNK_IEND = 0x49454E44;   // "IEND"
592     private static final int CHUNK_IHDR = 0x49484452;   // "IHDR"
593     private static final int CHUNK_PLTE = 0x504C5445;   // "PLTE"
594     private static final int CHUNK_pHYs = 0x70485973;   // "pHYs"
595     private static final int CHUNK_sBIT = 0x73424954;   // "sBIT"
596     private static final int CHUNK_tEXt = 0x74455874;   // "tEXt"
597     private static final int CHUNK_tIME = 0x74494D45;   // "tIME"
598     private static final int CHUNK_tRNS = 0x74524E53;   // "tIME"
599     private static final int CHUNK_zTXt = 0x7A545874;   // "zTXt"
600
601     private static final int startingRow[]  =  { 0, 0, 0, 4, 0, 2, 0, 1 };
602     private static final int startingCol[]  =  { 0, 0, 4, 0, 2, 0, 1, 0 };
603     private static final int rowInc[]       =  { 1, 8, 8, 8, 4, 4, 2, 2 };
604     private static final int colInc[]       =  { 1, 8, 8, 4, 4, 2, 2, 1 };
605     private static final int blockHeight[]  =  { 1, 8, 8, 4, 4, 2, 2, 1 };
606     private static final int blockWidth[]   =  { 1, 8, 4, 4, 2, 2, 1, 1 };
607
608     // Helper Classes ////////////////////////////////////////////////////////////////////
609
610     private static class MeteredInputStream extends FilterInputStream {
611         int bytesLeft;
612         int marked;
613         
614         public MeteredInputStream(InputStream in, int size) {
615             super(in);
616             bytesLeft = size;
617         }
618         
619         public final int read() throws IOException {
620             if (bytesLeft > 0) {
621                 int val = in.read();
622                 if (val != -1) bytesLeft--;
623                 return val;
624             }
625             return -1;
626         }
627         
628         public final int read(byte b[]) throws IOException {
629             return read(b, 0, b.length);
630         }
631         
632         public final int read(byte b[], int off, int len) throws IOException {
633             if (bytesLeft > 0) {
634                 len = (len > bytesLeft ? bytesLeft : len);
635                 int read = in.read(b, off, len);
636                 if (read > 0) bytesLeft -= read;
637                 return read;
638             }
639             return -1;
640         }
641         
642         public final long skip(long n) throws IOException {
643             n = (n > bytesLeft ? bytesLeft : n);
644             long skipped = in.skip(n);
645             if (skipped > 0) bytesLeft -= skipped;
646             return skipped;
647         }
648         
649         public final int available() throws IOException {
650             int n = in.available();
651             return (n > bytesLeft ? bytesLeft : n);
652         }
653         
654         public final void close() throws IOException { /* Eat this */ }
655
656         public final void mark(int readlimit) {
657             marked = bytesLeft;
658             in.mark(readlimit);
659         }
660         
661         public final void reset() throws IOException {
662             in.reset();
663             bytesLeft = marked;
664         }
665         
666         public final boolean markSupported() { return in.markSupported(); }
667     }
668
669     /** Support class, used to eat the IDAT headers dividing up the deflated stream */
670     private static class IDATEnumeration implements Enumeration {
671         InputStream underlyingStream;
672         PNG owner;
673         boolean firstStream = true;
674         
675         public IDATEnumeration(PNG owner) {
676             this.owner = owner;
677             this.underlyingStream = owner.underlyingStream;
678         }
679         
680         public Object nextElement() {
681             firstStream = false;
682             return new MeteredInputStream(underlyingStream, owner.chunkLength);
683         }
684         
685         public boolean hasMoreElements() {
686             DataInputStream dis = new DataInputStream(underlyingStream);
687             if (!firstStream) {
688                 try {
689                     int crc = dis.readInt();
690                     owner.needChunkInfo = false;
691                     owner.chunkLength = dis.readInt();
692                     owner.chunkType = dis.readInt();
693                 } catch (IOException ioe) {
694                     return false;
695                 }
696             }
697             if (owner.chunkType == PNG.CHUNK_IDAT) return true;
698             return false;
699         }
700     }
701
702 }