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