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