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