/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.image;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.awt.image.*;
import java.awt.Color;

/** PNG - Portable Network Graphics - image file reader.
    See <a href=http://www.ietf.org/rfc/rfc2083.txt>RFC2083</a> for details. */

/* this is changed
public class PNGImageDecoder extends FilterInputStream implements Runnable
{ */

public class PNGImageDecoder extends ImageDecoder
{
    private static final int GRAY=0;
    private static final int PALETTE=1;
    private static final int COLOR=2;
    private static final int ALPHA=4;

    private static final int bKGDChunk = 0x624B4744;
    private static final int cHRMChunk = 0x6348524D;
    private static final int gAMAChunk = 0x67414D41;
    private static final int hISTChunk = 0x68495354;
    private static final int IDATChunk = 0x49444154;
    private static final int IENDChunk = 0x49454E44;
    private static final int IHDRChunk = 0x49484452;
    private static final int PLTEChunk = 0x504C5445;
    private static final int pHYsChunk = 0x70485973;
    private static final int sBITChunk = 0x73424954;
    private static final int tEXtChunk = 0x74455874;
    private static final int tIMEChunk = 0x74494D45;
    private static final int tRNSChunk = 0x74524E53;
    private static final int zTXtChunk = 0x7A545874;

    private int width;
    private int height;
    private int bitDepth;
    private int colorType;
    private int compressionMethod;
    private int filterMethod;
    private int interlaceMethod;
    private int gamma = 100000;
    private java.util.Hashtable properties;
  /* this is not needed
    ImageConsumer target;
    */
    private ColorModel cm;
    private byte[] red_map, green_map, blue_map, alpha_map;
    private int transparentPixel = -1;
    private byte[]  transparentPixel_16 = null; // we need 6 bytes to store 16bpp value
    private static ColorModel greyModels[] = new ColorModel[4];
  /* this is not needed
     PNGImageDecoder next;
     */

    private void property(String key,Object value) {
        if(value==null) return;
        if(properties==null) properties=new java.util.Hashtable();
        properties.put(key,value);
    }
    private void property(String key,float value) {
        property(key,new Float(value));
    }
    private final void pngassert(boolean b) throws IOException {
        if(!b) {
            PNGException e = new PNGException("Broken file");
            e.printStackTrace();
            throw e;
        }
    }
    protected boolean handleChunk(int key, byte[] buf, int st, int len)
        throws IOException {
        switch(key) {
            case bKGDChunk:
                Color c = null;
                switch(colorType) {
                    case COLOR:
                    case COLOR|ALPHA:
                        pngassert(len==6);
                        c = new Color(buf[st]&0xff,buf[st+2]&0xff,buf[st+4]&0xff);
                        break;
                    case COLOR|PALETTE:
                    case COLOR|PALETTE|ALPHA:
                        pngassert(len==1);
                        int ix = buf[st]&0xFF;
                        pngassert(red_map!=null && ix<red_map.length);
                        c = new Color(red_map[ix]&0xff,green_map[ix]&0xff,blue_map[ix]&0xff);
                        break;
                    case GRAY:
                    case GRAY|ALPHA:
                        pngassert(len==2);
                        int t = buf[st]&0xFF;
                        c = new Color(t,t,t);
                        break;
                }
                if(c!=null) property("background",c);
                break;
            case cHRMChunk:
                property("chromaticities",
                    new Chromaticities(
                        getInt(st),
                        getInt(st+4),
                        getInt(st+8),
                        getInt(st+12),
                        getInt(st+16),
                        getInt(st+20),
                        getInt(st+24),
                        getInt(st+28)));
                break;
            case gAMAChunk:
                if(len!=4) throw new PNGException("bogus gAMA");
                gamma = getInt(st);
                if(gamma!=100000) property("gamma",gamma/100000.0f);
                break;
            case hISTChunk: break;
            case IDATChunk: return false;
            case IENDChunk: break;
            case IHDRChunk:
                if(len!=13
                    ||(width = getInt(st))==0
                    ||(height = getInt(st+4))==0
                    ) throw new PNGException("bogus IHDR");
                bitDepth = getByte(st+8);
                colorType = getByte(st+9);
                compressionMethod = getByte(st+10);
                filterMethod = getByte(st+11);
                interlaceMethod = getByte(st+12);
                /* this is not needed
                  if(target!=null) target.setDimensions(width,height);
                  */
                break;
            case PLTEChunk:
                {   int tsize = len/3;
                    red_map = new byte[tsize];
                    green_map = new byte[tsize];
                    blue_map = new byte[tsize];
                    for(int i=0,j=st; i<tsize; i++, j+=3) {
                        red_map[i] = buf[j];
                        green_map[i] = buf[j+1];
                        blue_map[i] = buf[j+2];
                    }
                }
                break;
            case pHYsChunk: break;
            case sBITChunk: break;
            case tEXtChunk:
                int klen = 0;
                while(klen<len && buf[st+klen]!=0) klen++;
                if(klen<len) {
                    String tkey = new String(buf,st,klen);
                    String tvalue = new String(buf,st+klen+1,len-klen-1);
                    property(tkey,tvalue);
                }
                break;
            case tIMEChunk:
                property("modtime",new GregorianCalendar(
                    getShort(st+0),
                    getByte(st+2)-1,
                    getByte(st+3),
                    getByte(st+4),
                    getByte(st+5),
                    getByte(st+6)).getTime());
                break;
            case tRNSChunk:
                switch(colorType) {
                    case PALETTE|COLOR:
                    case PALETTE|COLOR|ALPHA:
                        int alen = len;
                        if(red_map!=null) alen = red_map.length;
                        alpha_map = new byte[alen];
                        System.arraycopy(buf,st,alpha_map,0,len<alen ? len : alen);
                        while (--alen>=len) alpha_map[alen] = (byte)0xFF;
                        break;
                    case COLOR: // doesn't deal with 16 bit colors properly
                    case COLOR|ALPHA: // doesn't deal with 16 bit colors properly
                        pngassert(len==6);
                        if (bitDepth == 16) {
                            transparentPixel_16 = new byte[6];
                            for (int i = 0; i < 6; i++) {
                                transparentPixel_16[i] = (byte)getByte(st + i);
                            }
                        } else {
                            transparentPixel =
                                      ((getShort(st + 0)&0xFF)<<16)
                                    | ((getShort(st + 2)&0xFF)<< 8)
                                    | ((getShort(st + 4)&0xFF)    );
                        }
                        break;
                    case GRAY:  // doesn't deal with 16 bit colors properly
                    case GRAY|ALPHA:  // doesn't deal with 16 bit colors properly
                        pngassert(len==2);
                        /* REMIND: Discarding the LSB for 16 bit depth here
                         * means that the all pixels which match the MSB
                         * will be treated as transparent.
                         */
                        int t = getShort(st);
                        t = 0xFF & ((bitDepth == 16) ? (t >> 8) : t);
                        transparentPixel = (t<<16) | (t<< 8) | t;
                        break;
                }
                break;
            case zTXtChunk: break;
        }
        return true;
    }
    public class PNGException extends IOException {
        PNGException(String s) { super(s); }
    }
  /* this is changed
     public void run() {
     */
  public void produceImage() throws IOException, ImageFormatException {
    /* this is not needed
       ImageConsumer t = target;
       if(t!=null) try {
       */
    try {
            for(int i=0; i<signature.length; i++)
              if((signature[i]&0xFF)!=underlyingInputStream.read())
                throw new PNGException("Chunk signature mismatch");

            InputStream is = new BufferedInputStream(new InflaterInputStream(inputStream,new Inflater()));

            getData();

            byte[] bPixels = null;
            int[] wPixels = null;
            int pixSize = width;
            int rowStride;
            int logDepth = 0;
            switch(bitDepth) {
                case  1: logDepth = 0; break;
                case  2: logDepth = 1; break;
                case  4: logDepth = 2; break;
                case  8: logDepth = 3; break;
                case 16: logDepth = 4; break;
                default: throw new PNGException("invalid depth");
            }
            if(interlaceMethod!=0) {pixSize *= height;rowStride=width;}
            else rowStride = 0;
            int combinedType = colorType|(bitDepth<<3);
            int bitMask = (1<<(bitDepth>=8?8:bitDepth))-1;
            //Figure out the color model
            switch(colorType) {
                case COLOR|PALETTE:
                case COLOR|PALETTE|ALPHA:
                    if(red_map==null) throw new PNGException("palette expected");
                    if(alpha_map==null)
                        cm = new IndexColorModel(bitDepth,red_map.length,
                            red_map,green_map,blue_map);
                    else
                        cm = new IndexColorModel(bitDepth,red_map.length,
                            red_map,green_map,blue_map,alpha_map);
                    bPixels = new byte[pixSize];
                    break;
                case GRAY:
                    {   int llog = logDepth>=4 ? 3 : logDepth;
                        if((cm=greyModels[llog]) == null) {
                            int size = 1<<(1<<llog);

                            byte ramp[] = new byte[size];
                            for(int i = 0; i<size; i++) ramp[i] = (byte)(255*i/(size-1));

                            if (transparentPixel == -1) {
                                cm = new IndexColorModel(bitDepth,ramp.length,ramp,ramp,ramp);
                            } else {
                                cm = new IndexColorModel(bitDepth,ramp.length,ramp,ramp,ramp,
                                                         (transparentPixel & 0xFF));
                            }
                            greyModels[llog] = cm;
                        }
                    }
                    bPixels = new byte[pixSize];
                    break;
                case COLOR:
                case COLOR|ALPHA:
                case GRAY|ALPHA:
                    cm = ColorModel.getRGBdefault();
                    wPixels = new int[pixSize];
                    break;
                default:
                    throw new PNGException("invalid color type");
            }
            /* this is going to be set in the pixel store
              t.setColorModel(cm);
            t.setHints(interlaceMethod !=0
                       ? ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES
                       : ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
                         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
                         */
            // code added to make it work with ImageDecoder architecture
            setDimensions(width, height);
            setColorModel(cm);
            int flags = (interlaceMethod !=0
                       ? ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES
                       : ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
                         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
            setHints(flags);
            headerComplete();
            // end of adding

            int samplesPerPixel = ((colorType&PALETTE)!=0 ? 1
                                 : ((colorType&COLOR)!=0 ? 3 : 1)+((colorType&ALPHA)!=0?1:0));
            int bitsPerPixel = samplesPerPixel*bitDepth;
            int bytesPerPixel = (bitsPerPixel+7)>>3;
            int pass, passLimit;
            if(interlaceMethod==0) { pass = -1; passLimit = 0; }
            else { pass = 0; passLimit = 7; }
            // These loops are far from being tuned.  They're this way to make them easy to
            // debug.  Tuning comes later.
            /* code changed. target not needed here
               while(++pass<=passLimit && (t=target)!=null) {
               */
            while(++pass<=passLimit) {
                int row = startingRow[pass];
                int rowInc = rowIncrement[pass];
                int colInc = colIncrement[pass];
                int bWidth = blockWidth[pass];
                int bHeight = blockHeight[pass];
                int sCol = startingCol[pass];
                int rowPixelWidth = (width-sCol+(colInc-1))/colInc;
                int rowByteWidth = ((rowPixelWidth*bitsPerPixel)+7)>>3;
                if(rowByteWidth==0) continue;
                int pixelBufferInc = interlaceMethod==0 ? rowInc*width : 0;
                int rowOffset = rowStride*row;
                boolean firstRow = true;

                byte[] rowByteBuffer = new byte[rowByteWidth];
                byte[] prevRowByteBuffer = new byte[rowByteWidth];
                /* code changed. target not needed here
                   while (row < height && (t=target)!=null) {
                   */
                while (row < height) {
                    int rowFilter = is.read();
                    for (int rowFillPos=0;rowFillPos<rowByteWidth; ) {
                        int n = is.read(rowByteBuffer,rowFillPos,rowByteWidth-rowFillPos);
                        if(n<=0) throw new PNGException("missing data");
                        rowFillPos+=n;
                    }
                    filterRow(rowByteBuffer,
                              firstRow ? null : prevRowByteBuffer,
                              rowFilter, rowByteWidth, bytesPerPixel);
                    int col = sCol;
                    int spos=0;
                    int pixel = 0;
                    while (col < width) {
                        if(wPixels !=null) {
                            switch(combinedType) {
                                case COLOR|ALPHA|(8<<3):
                                    wPixels[col+rowOffset] =
                                          ((rowByteBuffer[spos  ]&0xFF)<<16)
                                        | ((rowByteBuffer[spos+1]&0xFF)<< 8)
                                        | ((rowByteBuffer[spos+2]&0xFF)    )
                                        | ((rowByteBuffer[spos+3]&0xFF)<<24);
                                    spos+=4;
                                    break;
                                case COLOR|ALPHA|(16<<3):
                                    wPixels[col+rowOffset] =
                                          ((rowByteBuffer[spos  ]&0xFF)<<16)
                                        | ((rowByteBuffer[spos+2]&0xFF)<< 8)
                                        | ((rowByteBuffer[spos+4]&0xFF)    )
                                        | ((rowByteBuffer[spos+6]&0xFF)<<24);
                                    spos+=8;
                                    break;
                                case COLOR|(8<<3):
                                    pixel =
                                          ((rowByteBuffer[spos  ]&0xFF)<<16)
                                        | ((rowByteBuffer[spos+1]&0xFF)<< 8)
                                        | ((rowByteBuffer[spos+2]&0xFF)    );
                                    if (pixel != transparentPixel) {
                                        pixel |= 0xff000000;
                                    }
                                    wPixels[col+rowOffset] = pixel;
                                    spos+=3;
                                    break;
                                case COLOR|(16<<3):
                                    pixel =
                                              ((rowByteBuffer[spos  ]&0xFF)<<16)
                                            | ((rowByteBuffer[spos+2]&0xFF)<< 8)
                                            | ((rowByteBuffer[spos+4]&0xFF)    );

                                    boolean isTransparent = (transparentPixel_16 != null);
                                    for (int i = 0; isTransparent && (i < 6); i++) {
                                        isTransparent &=
                                                (rowByteBuffer[spos + i] & 0xFF) == (transparentPixel_16[i] & 0xFF);
                                    }
                                    if (!isTransparent)  {
                                        pixel |= 0xff000000;
                                    }
                                    wPixels[col+rowOffset] = pixel;
                                    spos+=6;
                                    break;
                                case GRAY|ALPHA|(8<<3):
                                    { int tx = rowByteBuffer[spos]&0xFF;
                                      wPixels[col+rowOffset] =
                                          (tx<<16)|(tx<<8)|tx
                                        |((rowByteBuffer[spos+1]&0xFF)<<24); }
                                    spos+=2;
                                    break;
                                case GRAY|ALPHA|(16<<3):
                                    { int tx = rowByteBuffer[spos]&0xFF;
                                      wPixels[col+rowOffset] =
                                          (tx<<16)|(tx<<8)|tx
                                        |((rowByteBuffer[spos+2]&0xFF)<<24); }
                                    spos+=4;
                                    break;
                                default: throw new PNGException("illegal type/depth");
                            }
                        } else switch(bitDepth) {
                            case 1:
                                bPixels[col+rowOffset] =
                                    (byte)((rowByteBuffer[spos>>3]>>(7-(spos&7)))&1);
                                spos++;
                                break;
                            case 2:
                                bPixels[col+rowOffset] =
                                    (byte)((rowByteBuffer[spos>>2]>>((3-(spos&3))*2))&3);
                                spos++;
                                break;
                            case 4:
                                bPixels[col+rowOffset] =
                                    (byte)((rowByteBuffer[spos>>1]>>((1-(spos&1))*4))&15);
                                spos++;
                                break;
                            case 8: bPixels[col+rowOffset] = rowByteBuffer[spos++];
                                break;
                            case 16: bPixels[col+rowOffset] = rowByteBuffer[spos]; spos+=2;
                                break;
                            default: throw new PNGException("illegal type/depth");
                        }
                        /*visit (row, col,
                            min (bHeight, height - row),
                            min (bWidth, width - col)); */
                        col += colInc;
                    }
                    if(interlaceMethod==0)
                      if(wPixels!=null) {
                        /* code changed. target not needed here
                          t.setPixels(0,row,width,1,cm,wPixels,0,width);
                          */
                       // code added to make it work with ImageDecoder arch
                        sendPixels(0,row,width,1,wPixels,0,width);
                        // end of adding
                      }
                      else {
                        /* code changed. target not needed here
                           t.setPixels(0,row,width,1,cm,bPixels,0,width);
                           */
                        // code added to make it work with ImageDecoder arch
                        sendPixels(0,row,width,1,bPixels,0,width);
                        //end of adding
                      }
                    row += rowInc;
                    rowOffset += rowInc*rowStride;
                    byte[] T = rowByteBuffer;
                    rowByteBuffer = prevRowByteBuffer;
                    prevRowByteBuffer = T;
                    firstRow = false;
                }
                if(interlaceMethod!=0)
                  if(wPixels!=null) {
                    /* code changed. target not needed here
                       t.setPixels(0,0,width,height,cm,wPixels,0,width);
                       */
                    // code added to make it work with ImageDecoder arch
                      sendPixels(0,0,width,height,wPixels,0,width);
                      //end of adding
                  }
                  else {
                     /* code changed. target not needed here
                        t.setPixels(0,0,width,height,cm,bPixels,0,width);
                        */
                    // code added to make it work with ImageDecoder arch
                      sendPixels(0,0,width,height,bPixels,0,width);
                      //end of adding
                  }
            }

   /* Here, the function "visit(row,column,height,width)" obtains the
      next transmitted pixel and paints a rectangle of the specified
      height and width, whose upper-left corner is at the specified row
      and column, using the color indicated by the pixel.  Note that row
      and column are measured from 0,0 at the upper left corner. */

            /* code not needed, don't deal with target
             if((t=target)!=null) {
               if(properties!=null) t.setProperties(properties);
                 t.imageComplete(ImageConsumer.STATICIMAGEDONE);
                 */

              imageComplete(ImageConsumer.STATICIMAGEDONE, true);

              /* code not needed }
               is.close();
               */
        } catch(IOException e) {
            if(!aborted) {
                /* code not needed
                   if((t=target)!=null) {
                   PNGEncoder.prChunk(e.toString(),inbuf,pos,limit-pos,true);
                */
                property("error", e);
                /* code not needed
                   t.setProperties(properties);
                   t.imageComplete(ImageConsumer.IMAGEERROR|ImageConsumer.STATICIMAGEDONE);
                */
                imageComplete(ImageConsumer.IMAGEERROR|ImageConsumer.STATICIMAGEDONE, true);
                throw e;
            }
        } finally {
          try { close(); } catch(Throwable e){}
          /* code not needed
             target = null;
             endTurn();
             */
        }
    }

    private boolean sendPixels(int x, int y, int w, int h, int[] pixels,
                               int offset, int pixlength) {
        int count = setPixels(x, y, w, h, cm,
                              pixels, offset, pixlength);
        if (count <= 0) {
            aborted = true;
        }
        return !aborted;
    }
    private boolean sendPixels(int x, int y, int w, int h, byte[] pixels,
                               int offset, int pixlength) {
        int count = setPixels(x, y, w, h, cm,
                              pixels, offset, pixlength);
        if (count <= 0) {
            aborted = true;
        }
        return !aborted;
    }

    private void filterRow(byte rowByteBuffer[], byte[] prevRow,
                           int rowFilter, int rowByteWidth, int bytesPerSample)
        throws IOException {
        int x = 0;
        switch (rowFilter) {
          case 0:
            break;
          case 1:
            for (x = bytesPerSample; x < rowByteWidth; x++)
                rowByteBuffer[x] += rowByteBuffer[x - bytesPerSample];
            break;
          case 2:
            if (prevRow != null)
                for ( ; x < rowByteWidth; x++)
                    rowByteBuffer[x] += prevRow[x];
            break;
          case 3:
            if (prevRow != null) {
                for ( ; x < bytesPerSample; x++)
                    rowByteBuffer[x] += (0xff & prevRow[x])>>1;
                for ( ; x < rowByteWidth; x++)
                    rowByteBuffer[x] += ((prevRow[x]&0xFF) + (rowByteBuffer[x - bytesPerSample]&0xFF))>>1;
            } else
                for (x = bytesPerSample; x < rowByteWidth; x++)
                    rowByteBuffer[x] += (rowByteBuffer[x - bytesPerSample]&0xFF)>>1;
            break;
          case 4:
            if (prevRow != null) {
                for ( ; x < bytesPerSample; x++)
                    rowByteBuffer[x] += prevRow[x];
                for ( ; x < rowByteWidth; x++) {
                    int a, b, c, p, pa, pb, pc, rval;
                    a = rowByteBuffer[x - bytesPerSample]&0xFF;
                    b = prevRow[x]&0xFF;
                    c = prevRow[x - bytesPerSample]&0xFF;
                    p = a + b - c;
                    pa = p > a ? p - a : a - p;
                    pb = p > b ? p - b : b - p;
                    pc = p > c ? p - c : c - p;
                    rowByteBuffer[x] += (pa <= pb) && (pa <= pc) ? a : pb <= pc ? b : c;
                }
            } else
                for (x = bytesPerSample; x < rowByteWidth; x++)
                    rowByteBuffer[x] += rowByteBuffer[x - bytesPerSample];
            break;
          default:
            throw new PNGException("Illegal filter");
        }
    }
    private static final byte[] startingRow =  { 0, 0, 0, 4, 0, 2, 0, 1 };
    private static final byte[] startingCol =  { 0, 0, 4, 0, 2, 0, 1, 0 };
    private static final byte[] rowIncrement = { 1, 8, 8, 8, 4, 4, 2, 2 };
    private static final byte[] colIncrement = { 1, 8, 8, 4, 4, 2, 2, 1 };
    private static final byte[] blockHeight =  { 1, 8, 8, 4, 4, 2, 2, 1 };
    private static final byte[] blockWidth =   { 1, 8, 4, 4, 2, 2, 1, 1 };

    //abstract public class ChunkReader extends FilterInputStream {
  int pos, limit;
    int chunkStart;
   int chunkKey, chunkLength, chunkCRC;
    boolean seenEOF;

    private static final byte[] signature = { (byte) 137, (byte) 80, (byte) 78,
        (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10 };

  PNGFilterInputStream inputStream;
  InputStream underlyingInputStream;

  /* code changed
    public PNGImageDecoder(InputStream in, ImageConsumer t) throws IOException {
    */
  public PNGImageDecoder(InputStreamImageSource src, InputStream input) throws IOException {
    // code added
    super(src, input);
    inputStream = new PNGFilterInputStream(this, input);
    underlyingInputStream = inputStream.underlyingInputStream;
    // end of adding
    /* code changed
       super(in);
       target = t;
       waitTurn();
       new Thread(this).start();
       */
    }
  /* code changed to make it work with ImageDecoder architecture
    static int ThreadLimit = 10;
    private synchronized static void waitTurn() {
        try {
            while(ThreadLimit<=0) PNGImageDecoder.class.wait(1000);
        } catch(InterruptedException e){}
        ThreadLimit--;
    }
    private synchronized static void endTurn() {
        if(ThreadLimit<=0) PNGImageDecoder.class.notify();
        ThreadLimit++;
    }
    */
    byte[] inbuf = new byte[4096];
    private void fill() throws IOException {
        if(!seenEOF) {
            if(pos>0 && pos<limit) {
                System.arraycopy(inbuf,pos,inbuf,0,limit-pos);
                limit = limit-pos;
                pos = 0;
            } else if(pos>=limit) {
                pos = 0; limit = 0;
            }
            int bsize = inbuf.length;
            while(limit<bsize) {
                int n = underlyingInputStream.read(inbuf,limit,bsize-limit);
                if(n<=0) { seenEOF=true; break; }
                limit += n;
            }
        }
    }
    private boolean need(int n) throws IOException {
        if(limit-pos>=n) return true;
        fill();
        if(limit-pos>=n) return true;
        if(seenEOF) return false;
        byte nin[] = new byte[n+100];
        System.arraycopy(inbuf,pos,nin,0,limit-pos);
        limit = limit-pos;
        pos = 0;
        inbuf = nin;
        fill();
        return limit-pos>=n;
    }
    private final int getInt(int pos) {
        return ((inbuf[pos  ]&0xFF)<<24)
             | ((inbuf[pos+1]&0xFF)<<16)
             | ((inbuf[pos+2]&0xFF)<< 8)
             | ((inbuf[pos+3]&0xFF)    );
    }
    private final int getShort(int pos) {
        return (short)(((inbuf[pos  ]&0xFF)<<8)
                     | ((inbuf[pos+1]&0xFF)   ));
    }
    private final int getByte(int pos) {
        return inbuf[pos]&0xFF;
    }
    private final boolean getChunk() throws IOException {
        chunkLength = 0;
        if (!need(8)) return false;
        chunkLength = getInt(pos);
        chunkKey = getInt(pos+4);
        if(chunkLength<0) throw new PNGException("bogus length: "+chunkLength);
        if (!need(chunkLength+12)) return false;
        chunkCRC = getInt(pos+8+chunkLength);
        chunkStart = pos+8;
        int calcCRC = crc(inbuf,pos+4,chunkLength+4);
        if(chunkCRC!=calcCRC && checkCRC) throw new PNGException("crc corruption");
        pos+=chunkLength+12;
        return true;
    }
    private void readAll() throws IOException {
        while(getChunk()) handleChunk(chunkKey,inbuf,chunkStart,chunkLength);
    }
    boolean getData() throws IOException {
        while(chunkLength==0 && getChunk())
            if(handleChunk(chunkKey,inbuf,chunkStart,chunkLength))
                chunkLength = 0;
        return chunkLength>0;
    }
    //abstract protected boolean handleChunk(int key, byte[] buf, int st, int len)
    //    throws IOException;
    private static boolean checkCRC = true;
    public static boolean getCheckCRC() { return checkCRC; }
    public static void setCheckCRC(boolean c) { checkCRC = c; }

    protected void wrc(int c) {
        c = c&0xFF;
        if(c<=' '||c>'z') c = '?';
        System.out.write(c);
    }
    protected void wrk(int n) {
        wrc(n>>24);
        wrc(n>>16);
        wrc(n>>8);
        wrc(n);
    }
    public void print() {
        wrk(chunkKey);
        System.out.print(" "+chunkLength+"\n");
    }

    /* Table of CRCs of all 8-bit messages. */
    private static final int[] crc_table = new int[256];

    /* Make the table for a fast CRC. */
    static {
        for (int n = 0; n < 256; n++) {
            int c = n;
            for (int k = 0; k < 8; k++)
                if ((c & 1) != 0)
                    c = 0xedb88320 ^ (c >>> 1);
                else
                    c = c >>> 1;
            crc_table[n] = c;
        }
    }

    /* Update a running CRC with the bytes buf[0..len-1]--the CRC
    should be initialized to all 1's, and the transmitted value
    is the 1's complement of the final running CRC (see the
    crc() routine below)). */

    static private int update_crc(int crc, byte[] buf, int offset, int len) {
        int c = crc;
        while (--len>=0)
            c = crc_table[(c ^ buf[offset++]) & 0xff] ^ (c >>> 8);
        return c;
    }

    /* Return the CRC of the bytes buf[0..len-1]. */
    static private int crc(byte[] buf, int offset, int len) {
        return update_crc(0xffffffff, buf, offset, len) ^ 0xffffffff;
    }
    public static class Chromaticities {
        public float whiteX, whiteY, redX, redY, greenX, greenY, blueX, blueY;
        Chromaticities(int wx, int wy, int rx, int ry, int gx, int gy, int bx, int by) {
            whiteX = wx/100000.0f;
            whiteY = wy/100000.0f;
            redX = rx/100000.0f;
            redY = ry/100000.0f;
            greenX = gx/100000.0f;
            greenY = gy/100000.0f;
            blueX = bx/100000.0f;
            blueY = by/100000.0f;
        }
        public String toString() {
            return "Chromaticities(white="+whiteX+","+whiteY+";red="+
                redX+","+redY+";green="+
                greenX+","+greenY+";blue="+
                blueX+","+blueY+")";
        }
    }
}

// the following class are added to make it work with ImageDecoder architecture

class PNGFilterInputStream extends FilterInputStream {
  PNGImageDecoder owner;
  public InputStream underlyingInputStream;
  public PNGFilterInputStream(PNGImageDecoder owner, InputStream is) {
    super(is);
    underlyingInputStream = in;
    this.owner = owner;
  }

    public int available() throws IOException {
        return owner.limit-owner.pos+in.available();}
    public boolean markSupported() { return false; }
    public int read() throws IOException {
        if(owner.chunkLength<=0) if(!owner.getData()) return -1;
        owner.chunkLength--;
        return owner.inbuf[owner.chunkStart++]&0xFF;
    }
    public int read(byte[] b) throws IOException{return read(b,0,b.length);}
    public int read(byte[] b, int st, int len) throws IOException {
        if(owner.chunkLength<=0) if(!owner.getData()) return -1;
        if(owner.chunkLength<len) len = owner.chunkLength;
        System.arraycopy(owner.inbuf,owner.chunkStart,b,st,len);
        owner.chunkLength-=len;
        owner.chunkStart+=len;
        return len;
    }
  public long skip(long n) throws IOException {
        int i;
        for(i = 0; i<n && read()>=0; i++);
        return i;
    }


}
