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