1 /*
   2  * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.plugins.png;
  27 
  28 import java.awt.Point;
  29 import java.awt.Rectangle;
  30 import java.awt.color.ColorSpace;
  31 import java.awt.image.BufferedImage;
  32 import java.awt.image.DataBuffer;
  33 import java.awt.image.DataBufferByte;
  34 import java.awt.image.DataBufferUShort;
  35 import java.awt.image.Raster;
  36 import java.awt.image.WritableRaster;
  37 import java.io.BufferedInputStream;
  38 import java.io.ByteArrayInputStream;
  39 import java.io.DataInputStream;
  40 import java.io.EOFException;
  41 import java.io.InputStream;
  42 import java.io.IOException;
  43 import java.io.SequenceInputStream;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.Enumeration;
  47 import java.util.Iterator;
  48 import java.util.zip.Inflater;
  49 import java.util.zip.InflaterInputStream;
  50 import javax.imageio.IIOException;
  51 import javax.imageio.ImageReader;
  52 import javax.imageio.ImageReadParam;
  53 import javax.imageio.ImageTypeSpecifier;
  54 import javax.imageio.metadata.IIOMetadata;
  55 import javax.imageio.spi.ImageReaderSpi;
  56 import javax.imageio.stream.ImageInputStream;
  57 import com.sun.imageio.plugins.common.InputStreamAdapter;
  58 import com.sun.imageio.plugins.common.ReaderUtil;
  59 import com.sun.imageio.plugins.common.SubImageInputStream;
  60 import java.io.ByteArrayOutputStream;
  61 import sun.awt.image.ByteInterleavedRaster;
  62 
  63 class PNGImageDataEnumeration implements Enumeration<InputStream> {
  64 
  65     boolean firstTime = true;
  66     ImageInputStream stream;
  67     int length;
  68 
  69     public PNGImageDataEnumeration(ImageInputStream stream)
  70         throws IOException {
  71         this.stream = stream;
  72         this.length = stream.readInt();
  73         int type = stream.readInt(); // skip chunk type
  74     }
  75 
  76     public InputStream nextElement() {
  77         try {
  78             firstTime = false;
  79             ImageInputStream iis = new SubImageInputStream(stream, length);
  80             return new InputStreamAdapter(iis);
  81         } catch (IOException e) {
  82             return null;
  83         }
  84     }
  85 
  86     public boolean hasMoreElements() {
  87         if (firstTime) {
  88             return true;
  89         }
  90 
  91         try {
  92             int crc = stream.readInt();
  93             this.length = stream.readInt();
  94             int type = stream.readInt();
  95             if (type == PNGImageReader.IDAT_TYPE) {
  96                 return true;
  97             } else {
  98                 return false;
  99             }
 100         } catch (IOException e) {
 101             return false;
 102         }
 103     }
 104 }
 105 
 106 public class PNGImageReader extends ImageReader {
 107 
 108     /*
 109      * Note: The following chunk type constants are autogenerated.  Each
 110      * one is derived from the ASCII values of its 4-character name.  For
 111      * example, IHDR_TYPE is calculated as follows:
 112      *            ('I' << 24) | ('H' << 16) | ('D' << 8) | 'R'
 113      */
 114 
 115     // Critical chunks
 116     static final int IHDR_TYPE = 0x49484452;
 117     static final int PLTE_TYPE = 0x504c5445;
 118     static final int IDAT_TYPE = 0x49444154;
 119     static final int IEND_TYPE = 0x49454e44;
 120 
 121     // Ancillary chunks
 122     static final int bKGD_TYPE = 0x624b4744;
 123     static final int cHRM_TYPE = 0x6348524d;
 124     static final int gAMA_TYPE = 0x67414d41;
 125     static final int hIST_TYPE = 0x68495354;
 126     static final int iCCP_TYPE = 0x69434350;
 127     static final int iTXt_TYPE = 0x69545874;
 128     static final int pHYs_TYPE = 0x70485973;
 129     static final int sBIT_TYPE = 0x73424954;
 130     static final int sPLT_TYPE = 0x73504c54;
 131     static final int sRGB_TYPE = 0x73524742;
 132     static final int tEXt_TYPE = 0x74455874;
 133     static final int tIME_TYPE = 0x74494d45;
 134     static final int tRNS_TYPE = 0x74524e53;
 135     static final int zTXt_TYPE = 0x7a545874;
 136 
 137     static final int PNG_COLOR_GRAY = 0;
 138     static final int PNG_COLOR_RGB = 2;
 139     static final int PNG_COLOR_PALETTE = 3;
 140     static final int PNG_COLOR_GRAY_ALPHA = 4;
 141     static final int PNG_COLOR_RGB_ALPHA = 6;
 142 
 143     // The number of bands by PNG color type
 144     static final int[] inputBandsForColorType = {
 145          1, // gray
 146         -1, // unused
 147          3, // rgb
 148          1, // palette
 149          2, // gray + alpha
 150         -1, // unused
 151          4  // rgb + alpha
 152     };
 153 
 154     static final int PNG_FILTER_NONE = 0;
 155     static final int PNG_FILTER_SUB = 1;
 156     static final int PNG_FILTER_UP = 2;
 157     static final int PNG_FILTER_AVERAGE = 3;
 158     static final int PNG_FILTER_PAETH = 4;
 159 
 160     static final int[] adam7XOffset = { 0, 4, 0, 2, 0, 1, 0 };
 161     static final int[] adam7YOffset = { 0, 0, 4, 0, 2, 0, 1 };
 162     static final int[] adam7XSubsampling = { 8, 8, 4, 4, 2, 2, 1, 1 };
 163     static final int[] adam7YSubsampling = { 8, 8, 8, 4, 4, 2, 2, 1 };
 164 
 165     private static final boolean debug = true;
 166 
 167     ImageInputStream stream = null;
 168 
 169     boolean gotHeader = false;
 170     boolean gotMetadata = false;
 171 
 172     ImageReadParam lastParam = null;
 173 
 174     long imageStartPosition = -1L;
 175 
 176     Rectangle sourceRegion = null;
 177     int sourceXSubsampling = -1;
 178     int sourceYSubsampling = -1;
 179     int sourceMinProgressivePass = 0;
 180     int sourceMaxProgressivePass = 6;
 181     int[] sourceBands = null;
 182     int[] destinationBands = null;
 183     Point destinationOffset = new Point(0, 0);
 184 
 185     PNGMetadata metadata = new PNGMetadata();
 186 
 187     DataInputStream pixelStream = null;
 188 
 189     BufferedImage theImage = null;
 190 
 191     // The number of source pixels processed
 192     int pixelsDone = 0;
 193 
 194     // The total number of pixels in the source image
 195     int totalPixels;
 196 
 197     public PNGImageReader(ImageReaderSpi originatingProvider) {
 198         super(originatingProvider);
 199     }
 200 
 201     @Override
 202     public void setInput(Object input,
 203                          boolean seekForwardOnly,
 204                          boolean ignoreMetadata) {
 205         super.setInput(input, seekForwardOnly, ignoreMetadata);
 206         this.stream = (ImageInputStream)input; // Always works
 207 
 208         // Clear all values based on the previous stream contents
 209         resetStreamSettings();
 210     }
 211 
 212     private String readNullTerminatedString(String charset, int maxLen) throws IOException {
 213         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 214         int b = 0;
 215         int count = 0;
 216         while ((maxLen > count++) && ((b = stream.read()) != 0)) {
 217             if (b == -1) throw new EOFException();
 218             baos.write(b);
 219         }
 220         if (b != 0) {
 221             throw new IIOException("Found non null terminated string");
 222         }
 223         return new String(baos.toByteArray(), charset);
 224     }
 225 
 226     private void readHeader() throws IIOException {
 227         if (gotHeader) {
 228             return;
 229         }
 230         if (stream == null) {
 231             throw new IllegalStateException("Input source not set!");
 232         }
 233 
 234         try {
 235             byte[] signature = new byte[8];
 236             stream.readFully(signature);
 237 
 238             if (signature[0] != (byte)137 ||
 239                 signature[1] != (byte)80 ||
 240                 signature[2] != (byte)78 ||
 241                 signature[3] != (byte)71 ||
 242                 signature[4] != (byte)13 ||
 243                 signature[5] != (byte)10 ||
 244                 signature[6] != (byte)26 ||
 245                 signature[7] != (byte)10) {
 246                 throw new IIOException("Bad PNG signature!");
 247             }
 248 
 249             int IHDR_length = stream.readInt();
 250             if (IHDR_length != 13) {
 251                 throw new IIOException("Bad length for IHDR chunk!");
 252             }
 253             int IHDR_type = stream.readInt();
 254             if (IHDR_type != IHDR_TYPE) {
 255                 throw new IIOException("Bad type for IHDR chunk!");
 256             }
 257 
 258             this.metadata = new PNGMetadata();
 259 
 260             int width = stream.readInt();
 261             int height = stream.readInt();
 262 
 263             // Re-use signature array to bulk-read these unsigned byte values
 264             stream.readFully(signature, 0, 5);
 265             int bitDepth          = signature[0] & 0xff;
 266             int colorType         = signature[1] & 0xff;
 267             int compressionMethod = signature[2] & 0xff;
 268             int filterMethod      = signature[3] & 0xff;
 269             int interlaceMethod   = signature[4] & 0xff;
 270 
 271             // Skip IHDR CRC
 272             stream.skipBytes(4);
 273 
 274             stream.flushBefore(stream.getStreamPosition());
 275 
 276             if (width <= 0) {
 277                 throw new IIOException("Image width <= 0!");
 278             }
 279             if (height <= 0) {
 280                 throw new IIOException("Image height <= 0!");
 281             }
 282             if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 &&
 283                 bitDepth != 8 && bitDepth != 16) {
 284                 throw new IIOException("Bit depth must be 1, 2, 4, 8, or 16!");
 285             }
 286             if (colorType != 0 && colorType != 2 && colorType != 3 &&
 287                 colorType != 4 && colorType != 6) {
 288                 throw new IIOException("Color type must be 0, 2, 3, 4, or 6!");
 289             }
 290             if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) {
 291                 throw new IIOException("Bad color type/bit depth combination!");
 292             }
 293             if ((colorType == PNG_COLOR_RGB ||
 294                  colorType == PNG_COLOR_RGB_ALPHA ||
 295                  colorType == PNG_COLOR_GRAY_ALPHA) &&
 296                 (bitDepth != 8 && bitDepth != 16)) {
 297                 throw new IIOException("Bad color type/bit depth combination!");
 298             }
 299             if (compressionMethod != 0) {
 300                 throw new IIOException("Unknown compression method (not 0)!");
 301             }
 302             if (filterMethod != 0) {
 303                 throw new IIOException("Unknown filter method (not 0)!");
 304             }
 305             if (interlaceMethod != 0 && interlaceMethod != 1) {
 306                 throw new IIOException("Unknown interlace method (not 0 or 1)!");
 307             }
 308 
 309             metadata.IHDR_present = true;
 310             metadata.IHDR_width = width;
 311             metadata.IHDR_height = height;
 312             metadata.IHDR_bitDepth = bitDepth;
 313             metadata.IHDR_colorType = colorType;
 314             metadata.IHDR_compressionMethod = compressionMethod;
 315             metadata.IHDR_filterMethod = filterMethod;
 316             metadata.IHDR_interlaceMethod = interlaceMethod;
 317             gotHeader = true;
 318         } catch (IOException e) {
 319             throw new IIOException("I/O error reading PNG header!", e);
 320         }
 321     }
 322 
 323     private void parse_PLTE_chunk(int chunkLength) throws IOException {
 324         if (metadata.PLTE_present) {
 325             processWarningOccurred(
 326 "A PNG image may not contain more than one PLTE chunk.\n" +
 327 "The chunk wil be ignored.");
 328             return;
 329         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
 330                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
 331             processWarningOccurred(
 332 "A PNG gray or gray alpha image cannot have a PLTE chunk.\n" +
 333 "The chunk wil be ignored.");
 334             return;
 335         }
 336 
 337         byte[] palette = new byte[chunkLength];
 338         stream.readFully(palette);
 339 
 340         int numEntries = chunkLength/3;
 341         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
 342             int maxEntries = 1 << metadata.IHDR_bitDepth;
 343             if (numEntries > maxEntries) {
 344                 processWarningOccurred(
 345 "PLTE chunk contains too many entries for bit depth, ignoring extras.");
 346                 numEntries = maxEntries;
 347             }
 348             numEntries = Math.min(numEntries, maxEntries);
 349         }
 350 
 351         // Round array sizes up to 2^2^n
 352         int paletteEntries;
 353         if (numEntries > 16) {
 354             paletteEntries = 256;
 355         } else if (numEntries > 4) {
 356             paletteEntries = 16;
 357         } else if (numEntries > 2) {
 358             paletteEntries = 4;
 359         } else {
 360             paletteEntries = 2;
 361         }
 362 
 363         metadata.PLTE_present = true;
 364         metadata.PLTE_red = new byte[paletteEntries];
 365         metadata.PLTE_green = new byte[paletteEntries];
 366         metadata.PLTE_blue = new byte[paletteEntries];
 367 
 368         int index = 0;
 369         for (int i = 0; i < numEntries; i++) {
 370             metadata.PLTE_red[i] = palette[index++];
 371             metadata.PLTE_green[i] = palette[index++];
 372             metadata.PLTE_blue[i] = palette[index++];
 373         }
 374     }
 375 
 376     private void parse_bKGD_chunk() throws IOException {
 377         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
 378             metadata.bKGD_colorType = PNG_COLOR_PALETTE;
 379             metadata.bKGD_index = stream.readUnsignedByte();
 380         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
 381                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
 382             metadata.bKGD_colorType = PNG_COLOR_GRAY;
 383             metadata.bKGD_gray = stream.readUnsignedShort();
 384         } else { // RGB or RGB_ALPHA
 385             metadata.bKGD_colorType = PNG_COLOR_RGB;
 386             metadata.bKGD_red = stream.readUnsignedShort();
 387             metadata.bKGD_green = stream.readUnsignedShort();
 388             metadata.bKGD_blue = stream.readUnsignedShort();
 389         }
 390 
 391         metadata.bKGD_present = true;
 392     }
 393 
 394     private void parse_cHRM_chunk() throws IOException {
 395         metadata.cHRM_whitePointX = stream.readInt();
 396         metadata.cHRM_whitePointY = stream.readInt();
 397         metadata.cHRM_redX = stream.readInt();
 398         metadata.cHRM_redY = stream.readInt();
 399         metadata.cHRM_greenX = stream.readInt();
 400         metadata.cHRM_greenY = stream.readInt();
 401         metadata.cHRM_blueX = stream.readInt();
 402         metadata.cHRM_blueY = stream.readInt();
 403 
 404         metadata.cHRM_present = true;
 405     }
 406 
 407     private void parse_gAMA_chunk() throws IOException {
 408         int gamma = stream.readInt();
 409         metadata.gAMA_gamma = gamma;
 410 
 411         metadata.gAMA_present = true;
 412     }
 413 
 414     private void parse_hIST_chunk(int chunkLength) throws IOException,
 415         IIOException
 416     {
 417         if (!metadata.PLTE_present) {
 418             throw new IIOException("hIST chunk without prior PLTE chunk!");
 419         }
 420 
 421         /* According to PNG specification length of
 422          * hIST chunk is specified in bytes and
 423          * hIST chunk consists of 2 byte elements
 424          * (so we expect length is even).
 425          */
 426         metadata.hIST_histogram = new char[chunkLength/2];
 427         stream.readFully(metadata.hIST_histogram,
 428                          0, metadata.hIST_histogram.length);
 429 
 430         metadata.hIST_present = true;
 431     }
 432 
 433     private void parse_iCCP_chunk(int chunkLength) throws IOException {
 434         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 435         int compressedProfileLength = chunkLength - keyword.length() - 2;
 436         if (compressedProfileLength <= 0) {
 437             throw new IIOException("iCCP chunk length is not proper");
 438         }
 439         metadata.iCCP_profileName = keyword;
 440 
 441         metadata.iCCP_compressionMethod = stream.readUnsignedByte();
 442 
 443         byte[] compressedProfile =
 444           new byte[compressedProfileLength];
 445         stream.readFully(compressedProfile);
 446         metadata.iCCP_compressedProfile = compressedProfile;
 447 
 448         metadata.iCCP_present = true;
 449     }
 450 
 451     private void parse_iTXt_chunk(int chunkLength) throws IOException {
 452         long chunkStart = stream.getStreamPosition();
 453 
 454         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 455         metadata.iTXt_keyword.add(keyword);
 456 
 457         int compressionFlag = stream.readUnsignedByte();
 458         metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1));
 459 
 460         int compressionMethod = stream.readUnsignedByte();
 461         metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
 462 
 463         String languageTag = readNullTerminatedString("UTF8", 80);
 464         metadata.iTXt_languageTag.add(languageTag);
 465 
 466         long pos = stream.getStreamPosition();
 467         int maxLen = (int)(chunkStart + chunkLength - pos);
 468         String translatedKeyword =
 469             readNullTerminatedString("UTF8", maxLen);
 470         metadata.iTXt_translatedKeyword.add(translatedKeyword);
 471 
 472         String text;
 473         pos = stream.getStreamPosition();
 474         int textLength = (int)(chunkStart + chunkLength - pos);
 475         if (textLength < 0) {
 476             throw new IIOException("iTXt chunk length is not proper");
 477         }
 478         byte[] b = new byte[textLength];
 479         stream.readFully(b);
 480 
 481         if (compressionFlag == 1) { // Decompress the text
 482             text = new String(inflate(b), "UTF8");
 483         } else {
 484             text = new String(b, "UTF8");
 485         }
 486         metadata.iTXt_text.add(text);
 487 
 488         // Check if the text chunk contains image creation time
 489         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 490             // Update Standard/Document/ImageCreationTime from text chunk
 491             int index = metadata.iTXt_text.size() - 1;
 492             metadata.decodeImageCreationTimeFromTextChunk(
 493                     metadata.iTXt_text.listIterator(index));
 494         }
 495     }
 496 
 497     private void parse_pHYs_chunk() throws IOException {
 498         metadata.pHYs_pixelsPerUnitXAxis = stream.readInt();
 499         metadata.pHYs_pixelsPerUnitYAxis = stream.readInt();
 500         metadata.pHYs_unitSpecifier = stream.readUnsignedByte();
 501 
 502         metadata.pHYs_present = true;
 503     }
 504 
 505     private void parse_sBIT_chunk() throws IOException {
 506         int colorType = metadata.IHDR_colorType;
 507         if (colorType == PNG_COLOR_GRAY ||
 508             colorType == PNG_COLOR_GRAY_ALPHA) {
 509             metadata.sBIT_grayBits = stream.readUnsignedByte();
 510         } else if (colorType == PNG_COLOR_RGB ||
 511                    colorType == PNG_COLOR_PALETTE ||
 512                    colorType == PNG_COLOR_RGB_ALPHA) {
 513             metadata.sBIT_redBits = stream.readUnsignedByte();
 514             metadata.sBIT_greenBits = stream.readUnsignedByte();
 515             metadata.sBIT_blueBits = stream.readUnsignedByte();
 516         }
 517 
 518         if (colorType == PNG_COLOR_GRAY_ALPHA ||
 519             colorType == PNG_COLOR_RGB_ALPHA) {
 520             metadata.sBIT_alphaBits = stream.readUnsignedByte();
 521         }
 522 
 523         metadata.sBIT_colorType = colorType;
 524         metadata.sBIT_present = true;
 525     }
 526 
 527     private void parse_sPLT_chunk(int chunkLength)
 528         throws IOException, IIOException {
 529         metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80);
 530         int remainingChunkLength = chunkLength -
 531                 (metadata.sPLT_paletteName.length() + 1);
 532         if (remainingChunkLength <= 0) {
 533             throw new IIOException("sPLT chunk length is not proper");
 534         }
 535 
 536         int sampleDepth = stream.readUnsignedByte();
 537         metadata.sPLT_sampleDepth = sampleDepth;
 538 
 539         int numEntries = remainingChunkLength/(4*(sampleDepth/8) + 2);
 540         metadata.sPLT_red = new int[numEntries];
 541         metadata.sPLT_green = new int[numEntries];
 542         metadata.sPLT_blue = new int[numEntries];
 543         metadata.sPLT_alpha = new int[numEntries];
 544         metadata.sPLT_frequency = new int[numEntries];
 545 
 546         if (sampleDepth == 8) {
 547             for (int i = 0; i < numEntries; i++) {
 548                 metadata.sPLT_red[i] = stream.readUnsignedByte();
 549                 metadata.sPLT_green[i] = stream.readUnsignedByte();
 550                 metadata.sPLT_blue[i] = stream.readUnsignedByte();
 551                 metadata.sPLT_alpha[i] = stream.readUnsignedByte();
 552                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
 553             }
 554         } else if (sampleDepth == 16) {
 555             for (int i = 0; i < numEntries; i++) {
 556                 metadata.sPLT_red[i] = stream.readUnsignedShort();
 557                 metadata.sPLT_green[i] = stream.readUnsignedShort();
 558                 metadata.sPLT_blue[i] = stream.readUnsignedShort();
 559                 metadata.sPLT_alpha[i] = stream.readUnsignedShort();
 560                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
 561             }
 562         } else {
 563             throw new IIOException("sPLT sample depth not 8 or 16!");
 564         }
 565 
 566         metadata.sPLT_present = true;
 567     }
 568 
 569     private void parse_sRGB_chunk() throws IOException {
 570         metadata.sRGB_renderingIntent = stream.readUnsignedByte();
 571 
 572         metadata.sRGB_present = true;
 573     }
 574 
 575     private void parse_tEXt_chunk(int chunkLength) throws IOException {
 576         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 577         int textLength = chunkLength - keyword.length() - 1;
 578         if (textLength < 0) {
 579             throw new IIOException("tEXt chunk length is not proper");
 580         }
 581         metadata.tEXt_keyword.add(keyword);
 582 
 583         byte[] b = new byte[textLength];
 584         stream.readFully(b);
 585         metadata.tEXt_text.add(new String(b, "ISO-8859-1"));
 586 
 587         // Check if the text chunk contains image creation time
 588         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 589             // Update Standard/Document/ImageCreationTime from text chunk
 590             int index = metadata.tEXt_text.size() - 1;
 591             metadata.decodeImageCreationTimeFromTextChunk(
 592                     metadata.tEXt_text.listIterator(index));
 593         }
 594     }
 595 
 596     private void parse_tIME_chunk() throws IOException {
 597         metadata.tIME_year = stream.readUnsignedShort();
 598         metadata.tIME_month = stream.readUnsignedByte();
 599         metadata.tIME_day = stream.readUnsignedByte();
 600         metadata.tIME_hour = stream.readUnsignedByte();
 601         metadata.tIME_minute = stream.readUnsignedByte();
 602         metadata.tIME_second = stream.readUnsignedByte();
 603 
 604         metadata.tIME_present = true;
 605     }
 606 
 607     private void parse_tRNS_chunk(int chunkLength) throws IOException {
 608         int colorType = metadata.IHDR_colorType;
 609         if (colorType == PNG_COLOR_PALETTE) {
 610             if (!metadata.PLTE_present) {
 611                 processWarningOccurred(
 612 "tRNS chunk without prior PLTE chunk, ignoring it.");
 613                 return;
 614             }
 615 
 616             // Alpha table may have fewer entries than RGB palette
 617             int maxEntries = metadata.PLTE_red.length;
 618             int numEntries = chunkLength;
 619             if (numEntries > maxEntries && maxEntries > 0) {
 620                 processWarningOccurred(
 621 "tRNS chunk has more entries than prior PLTE chunk, ignoring extras.");
 622                 numEntries = maxEntries;
 623             }
 624             metadata.tRNS_alpha = new byte[numEntries];
 625             metadata.tRNS_colorType = PNG_COLOR_PALETTE;
 626             stream.read(metadata.tRNS_alpha, 0, numEntries);
 627             stream.skipBytes(chunkLength - numEntries);
 628         } else if (colorType == PNG_COLOR_GRAY) {
 629             if (chunkLength != 2) {
 630                 processWarningOccurred(
 631 "tRNS chunk for gray image must have length 2, ignoring chunk.");
 632                 stream.skipBytes(chunkLength);
 633                 return;
 634             }
 635             metadata.tRNS_gray = stream.readUnsignedShort();
 636             metadata.tRNS_colorType = PNG_COLOR_GRAY;
 637         } else if (colorType == PNG_COLOR_RGB) {
 638             if (chunkLength != 6) {
 639                 processWarningOccurred(
 640 "tRNS chunk for RGB image must have length 6, ignoring chunk.");
 641                 stream.skipBytes(chunkLength);
 642                 return;
 643             }
 644             metadata.tRNS_red = stream.readUnsignedShort();
 645             metadata.tRNS_green = stream.readUnsignedShort();
 646             metadata.tRNS_blue = stream.readUnsignedShort();
 647             metadata.tRNS_colorType = PNG_COLOR_RGB;
 648         } else {
 649             processWarningOccurred(
 650 "Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it.");
 651             return;
 652         }
 653 
 654         metadata.tRNS_present = true;
 655     }
 656 
 657     private static byte[] inflate(byte[] b) throws IOException {
 658         InputStream bais = new ByteArrayInputStream(b);
 659         InputStream iis = new InflaterInputStream(bais);
 660         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 661 
 662         int c;
 663         try {
 664             while ((c = iis.read()) != -1) {
 665                 baos.write(c);
 666             }
 667         } finally {
 668             iis.close();
 669         }
 670         return baos.toByteArray();
 671     }
 672 
 673     private void parse_zTXt_chunk(int chunkLength) throws IOException {
 674         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 675         int textLength = chunkLength - keyword.length() - 2;
 676         if (textLength < 0) {
 677             throw new IIOException("zTXt chunk length is not proper");
 678         }
 679         metadata.zTXt_keyword.add(keyword);
 680 
 681         int method = stream.readUnsignedByte();
 682         metadata.zTXt_compressionMethod.add(method);
 683 
 684         byte[] b = new byte[textLength];
 685         stream.readFully(b);
 686         metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1"));
 687 
 688         // Check if the text chunk contains image creation time
 689         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 690             // Update Standard/Document/ImageCreationTime from text chunk
 691             int index = metadata.zTXt_text.size() - 1;
 692             metadata.decodeImageCreationTimeFromTextChunk(
 693                     metadata.zTXt_text.listIterator(index));
 694         }
 695     }
 696 
 697     private void readMetadata() throws IIOException {
 698         if (gotMetadata) {
 699             return;
 700         }
 701 
 702         readHeader();
 703 
 704         /*
 705          * Optimization: We can skip reading metadata if ignoreMetadata
 706          * flag is set and colorType is not PNG_COLOR_PALETTE. However,
 707          * we parse tRNS chunk to retrieve the transparent color from the
 708          * metadata. Doing so, helps PNGImageReader to appropriately
 709          * identify and set transparent pixels in the decoded image for
 710          * colorType PNG_COLOR_RGB and PNG_COLOR_GRAY.
 711          */
 712         int colorType = metadata.IHDR_colorType;
 713         if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) {
 714             try {
 715                 while (true) {
 716                     int chunkLength = stream.readInt();
 717 
 718                     // verify the chunk length first
 719                     if (chunkLength < 0 || chunkLength + 4 < 0) {
 720                         throw new IIOException("Invalid chunk length " + chunkLength);
 721                     }
 722 
 723                     int chunkType = stream.readInt();
 724 
 725                     if (chunkType == IDAT_TYPE) {
 726                         // We've reached the first IDAT chunk position
 727                         stream.skipBytes(-8);
 728                         imageStartPosition = stream.getStreamPosition();
 729                         /*
 730                          * According to PNG specification tRNS chunk must
 731                          * precede the first IDAT chunk. So we can stop
 732                          * reading metadata.
 733                          */
 734                         break;
 735                     } else if (chunkType == tRNS_TYPE) {
 736                         parse_tRNS_chunk(chunkLength);
 737                         // After parsing tRNS chunk we will skip 4 CRC bytes
 738                         stream.skipBytes(4);
 739                     } else {
 740                         // Skip the chunk plus the 4 CRC bytes that follow
 741                         stream.skipBytes(chunkLength + 4);
 742                     }
 743                 }
 744             } catch (IOException e) {
 745                 throw new IIOException("Error skipping PNG metadata", e);
 746             }
 747 
 748             gotMetadata = true;
 749             return;
 750         }
 751 
 752         try {
 753             loop: while (true) {
 754                 int chunkLength = stream.readInt();
 755                 int chunkType = stream.readInt();
 756                 // Initialize chunkCRC, value assigned has no significance
 757                 int chunkCRC = -1;
 758 
 759                 // verify the chunk length
 760                 if (chunkLength < 0) {
 761                     throw new IIOException("Invalid chunk length " + chunkLength);
 762                 };
 763 
 764                 try {
 765                     /*
 766                      * As per PNG specification all chunks should have
 767                      * 4 byte CRC. But there are some images where
 768                      * CRC is not present/corrupt for IEND chunk.
 769                      * And these type of images are supported by other
 770                      * decoders. So as soon as we hit chunk type
 771                      * for IEND chunk stop reading metadata.
 772                      */
 773                     if (chunkType != IEND_TYPE) {
 774                         stream.mark();
 775                         stream.seek(stream.getStreamPosition() + chunkLength);
 776                         chunkCRC = stream.readInt();
 777                         stream.reset();
 778                     }
 779                 } catch (IOException e) {
 780                     throw new IIOException("Invalid chunk length " + chunkLength);
 781                 }
 782 
 783                 switch (chunkType) {
 784                 case IDAT_TYPE:
 785                     // If chunk type is 'IDAT', we've reached the image data.
 786                     if (imageStartPosition == -1L) {
 787                         /*
 788                          * The PNG specification mandates that if colorType is
 789                          * PNG_COLOR_PALETTE then the PLTE chunk should appear
 790                          * before the first IDAT chunk.
 791                          */
 792                         if (colorType == PNG_COLOR_PALETTE &&
 793                             !(metadata.PLTE_present))
 794                         {
 795                             throw new IIOException("Required PLTE chunk"
 796                                     + " missing");
 797                         }
 798                         /*
 799                          * PNGs may contain multiple IDAT chunks containing
 800                          * a portion of image data. We store the position of
 801                          * the first IDAT chunk and continue with iteration
 802                          * of other chunks that follow image data.
 803                          */
 804                         imageStartPosition = stream.getStreamPosition() - 8;
 805                     }
 806                     // Move to the CRC byte location.
 807                     stream.skipBytes(chunkLength);
 808                     break;
 809                 case IEND_TYPE:
 810                     /*
 811                      * If the chunk type is 'IEND', we've reached end of image.
 812                      * Seek to the first IDAT chunk for subsequent decoding.
 813                      */
 814                     stream.seek(imageStartPosition);
 815 
 816                     /*
 817                      * flushBefore discards the portion of the stream before
 818                      * the indicated position. Hence this should be used after
 819                      * we complete iteration over available chunks including
 820                      * those that appear after the IDAT.
 821                      */
 822                     stream.flushBefore(stream.getStreamPosition());
 823                     break loop;
 824                 case PLTE_TYPE:
 825                     parse_PLTE_chunk(chunkLength);
 826                     break;
 827                 case bKGD_TYPE:
 828                     parse_bKGD_chunk();
 829                     break;
 830                 case cHRM_TYPE:
 831                     parse_cHRM_chunk();
 832                     break;
 833                 case gAMA_TYPE:
 834                     parse_gAMA_chunk();
 835                     break;
 836                 case hIST_TYPE:
 837                     parse_hIST_chunk(chunkLength);
 838                     break;
 839                 case iCCP_TYPE:
 840                     parse_iCCP_chunk(chunkLength);
 841                     break;
 842                 case iTXt_TYPE:
 843                     if (ignoreMetadata) {
 844                         stream.skipBytes(chunkLength);
 845                     } else {
 846                         parse_iTXt_chunk(chunkLength);
 847                     }
 848                     break;
 849                 case pHYs_TYPE:
 850                     parse_pHYs_chunk();
 851                     break;
 852                 case sBIT_TYPE:
 853                     parse_sBIT_chunk();
 854                     break;
 855                 case sPLT_TYPE:
 856                     parse_sPLT_chunk(chunkLength);
 857                     break;
 858                 case sRGB_TYPE:
 859                     parse_sRGB_chunk();
 860                     break;
 861                 case tEXt_TYPE:
 862                     parse_tEXt_chunk(chunkLength);
 863                     break;
 864                 case tIME_TYPE:
 865                     parse_tIME_chunk();
 866                     break;
 867                 case tRNS_TYPE:
 868                     parse_tRNS_chunk(chunkLength);
 869                     break;
 870                 case zTXt_TYPE:
 871                     if (ignoreMetadata) {
 872                         stream.skipBytes(chunkLength);
 873                     } else {
 874                         parse_zTXt_chunk(chunkLength);
 875                     }
 876                     break;
 877                 default:
 878                     // Read an unknown chunk
 879                     byte[] b = new byte[chunkLength];
 880                     stream.readFully(b);
 881 
 882                     StringBuilder chunkName = new StringBuilder(4);
 883                     chunkName.append((char)(chunkType >>> 24));
 884                     chunkName.append((char)((chunkType >> 16) & 0xff));
 885                     chunkName.append((char)((chunkType >> 8) & 0xff));
 886                     chunkName.append((char)(chunkType & 0xff));
 887 
 888                     int ancillaryBit = chunkType >>> 28;
 889                     if (ancillaryBit == 0) {
 890                         processWarningOccurred(
 891 "Encountered unknown chunk with critical bit set!");
 892                     }
 893 
 894                     metadata.unknownChunkType.add(chunkName.toString());
 895                     metadata.unknownChunkData.add(b);
 896                     break;
 897                 }
 898 
 899                 // double check whether all chunk data were consumed
 900                 if (chunkCRC != stream.readInt()) {
 901                     throw new IIOException("Failed to read a chunk of type " +
 902                             chunkType);
 903                 }
 904             }
 905         } catch (IOException e) {
 906             throw new IIOException("Error reading PNG metadata", e);
 907         }
 908 
 909         gotMetadata = true;
 910     }
 911 
 912     // Data filtering methods
 913 
 914     private static void decodeSubFilter(byte[] curr, int coff, int count,
 915                                         int bpp) {
 916         for (int i = bpp; i < count; i++) {
 917             int val;
 918 
 919             val = curr[i + coff] & 0xff;
 920             val += curr[i + coff - bpp] & 0xff;
 921 
 922             curr[i + coff] = (byte)val;
 923         }
 924     }
 925 
 926     private static void decodeUpFilter(byte[] curr, int coff,
 927                                        byte[] prev, int poff,
 928                                        int count) {
 929         for (int i = 0; i < count; i++) {
 930             int raw = curr[i + coff] & 0xff;
 931             int prior = prev[i + poff] & 0xff;
 932 
 933             curr[i + coff] = (byte)(raw + prior);
 934         }
 935     }
 936 
 937     private static void decodeAverageFilter(byte[] curr, int coff,
 938                                             byte[] prev, int poff,
 939                                             int count, int bpp) {
 940         int raw, priorPixel, priorRow;
 941 
 942         for (int i = 0; i < bpp; i++) {
 943             raw = curr[i + coff] & 0xff;
 944             priorRow = prev[i + poff] & 0xff;
 945 
 946             curr[i + coff] = (byte)(raw + priorRow/2);
 947         }
 948 
 949         for (int i = bpp; i < count; i++) {
 950             raw = curr[i + coff] & 0xff;
 951             priorPixel = curr[i + coff - bpp] & 0xff;
 952             priorRow = prev[i + poff] & 0xff;
 953 
 954             curr[i + coff] = (byte)(raw + (priorPixel + priorRow)/2);
 955         }
 956     }
 957 
 958     private static int paethPredictor(int a, int b, int c) {
 959         int p = a + b - c;
 960         int pa = Math.abs(p - a);
 961         int pb = Math.abs(p - b);
 962         int pc = Math.abs(p - c);
 963 
 964         if ((pa <= pb) && (pa <= pc)) {
 965             return a;
 966         } else if (pb <= pc) {
 967             return b;
 968         } else {
 969             return c;
 970         }
 971     }
 972 
 973     private static void decodePaethFilter(byte[] curr, int coff,
 974                                           byte[] prev, int poff,
 975                                           int count, int bpp) {
 976         int raw, priorPixel, priorRow, priorRowPixel;
 977 
 978         for (int i = 0; i < bpp; i++) {
 979             raw = curr[i + coff] & 0xff;
 980             priorRow = prev[i + poff] & 0xff;
 981 
 982             curr[i + coff] = (byte)(raw + priorRow);
 983         }
 984 
 985         for (int i = bpp; i < count; i++) {
 986             raw = curr[i + coff] & 0xff;
 987             priorPixel = curr[i + coff - bpp] & 0xff;
 988             priorRow = prev[i + poff] & 0xff;
 989             priorRowPixel = prev[i + poff - bpp] & 0xff;
 990 
 991             curr[i + coff] = (byte)(raw + paethPredictor(priorPixel,
 992                                                          priorRow,
 993                                                          priorRowPixel));
 994         }
 995     }
 996 
 997     private static final int[][] bandOffsets = {
 998         null,
 999         { 0 }, // G
1000         { 0, 1 }, // GA in GA order
1001         { 0, 1, 2 }, // RGB in RGB order
1002         { 0, 1, 2, 3 } // RGBA in RGBA order
1003     };
1004 
1005     private WritableRaster createRaster(int width, int height, int bands,
1006                                         int scanlineStride,
1007                                         int bitDepth) {
1008 
1009         DataBuffer dataBuffer;
1010         WritableRaster ras = null;
1011         Point origin = new Point(0, 0);
1012         if ((bitDepth < 8) && (bands == 1)) {
1013             dataBuffer = new DataBufferByte(height*scanlineStride);
1014             ras = Raster.createPackedRaster(dataBuffer,
1015                                             width, height,
1016                                             bitDepth,
1017                                             origin);
1018         } else if (bitDepth <= 8) {
1019             dataBuffer = new DataBufferByte(height*scanlineStride);
1020             ras = Raster.createInterleavedRaster(dataBuffer,
1021                                                  width, height,
1022                                                  scanlineStride,
1023                                                  bands,
1024                                                  bandOffsets[bands],
1025                                                  origin);
1026         } else {
1027             dataBuffer = new DataBufferUShort(height*scanlineStride);
1028             ras = Raster.createInterleavedRaster(dataBuffer,
1029                                                  width, height,
1030                                                  scanlineStride,
1031                                                  bands,
1032                                                  bandOffsets[bands],
1033                                                  origin);
1034         }
1035 
1036         return ras;
1037     }
1038 
1039     private void skipPass(int passWidth, int passHeight)
1040         throws IOException, IIOException  {
1041         if ((passWidth == 0) || (passHeight == 0)) {
1042             return;
1043         }
1044 
1045         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
1046         int bitsPerRow = Math.
1047                 multiplyExact((inputBands * metadata.IHDR_bitDepth), passWidth);
1048         int bytesPerRow = (bitsPerRow + 7) / 8;
1049 
1050         // Read the image row-by-row
1051         for (int srcY = 0; srcY < passHeight; srcY++) {
1052             // Skip filter byte and the remaining row bytes
1053             pixelStream.skipBytes(1 + bytesPerRow);
1054         }
1055     }
1056 
1057     private void updateImageProgress(int newPixels) {
1058         pixelsDone += newPixels;
1059         processImageProgress(100.0F*pixelsDone/totalPixels);
1060     }
1061 
1062     private void decodePass(int passNum,
1063                             int xStart, int yStart,
1064                             int xStep, int yStep,
1065                             int passWidth, int passHeight) throws IOException {
1066 
1067         if ((passWidth == 0) || (passHeight == 0)) {
1068             return;
1069         }
1070 
1071         WritableRaster imRas = theImage.getWritableTile(0, 0);
1072         int dstMinX = imRas.getMinX();
1073         int dstMaxX = dstMinX + imRas.getWidth() - 1;
1074         int dstMinY = imRas.getMinY();
1075         int dstMaxY = dstMinY + imRas.getHeight() - 1;
1076 
1077         // Determine which pixels will be updated in this pass
1078         int[] vals =
1079           ReaderUtil.computeUpdatedPixels(sourceRegion,
1080                                           destinationOffset,
1081                                           dstMinX, dstMinY,
1082                                           dstMaxX, dstMaxY,
1083                                           sourceXSubsampling,
1084                                           sourceYSubsampling,
1085                                           xStart, yStart,
1086                                           passWidth, passHeight,
1087                                           xStep, yStep);
1088         int updateMinX = vals[0];
1089         int updateMinY = vals[1];
1090         int updateWidth = vals[2];
1091         int updateXStep = vals[4];
1092         int updateYStep = vals[5];
1093 
1094         int bitDepth = metadata.IHDR_bitDepth;
1095         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
1096         int bytesPerPixel = (bitDepth == 16) ? 2 : 1;
1097         bytesPerPixel *= inputBands;
1098 
1099         int bitsPerRow = Math.multiplyExact((inputBands * bitDepth), passWidth);
1100         int bytesPerRow = (bitsPerRow + 7) / 8;
1101         int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow;
1102 
1103         // If no pixels need updating, just skip the input data
1104         if (updateWidth == 0) {
1105             for (int srcY = 0; srcY < passHeight; srcY++) {
1106                 // Update count of pixels read
1107                 updateImageProgress(passWidth);
1108                 /*
1109                  * If read has been aborted, just return
1110                  * processReadAborted will be called later
1111                  */
1112                 if (abortRequested()) {
1113                     return;
1114                 }
1115                 // Skip filter byte and the remaining row bytes
1116                 pixelStream.skipBytes(1 + bytesPerRow);
1117             }
1118             return;
1119         }
1120 
1121         // Backwards map from destination pixels
1122         // (dstX = updateMinX + k*updateXStep)
1123         // to source pixels (sourceX), and then
1124         // to offset and skip in passRow (srcX and srcXStep)
1125         int sourceX =
1126             (updateMinX - destinationOffset.x)*sourceXSubsampling +
1127             sourceRegion.x;
1128         int srcX = (sourceX - xStart)/xStep;
1129 
1130         // Compute the step factor in the source
1131         int srcXStep = updateXStep*sourceXSubsampling/xStep;
1132 
1133         byte[] byteData = null;
1134         short[] shortData = null;
1135         byte[] curr = new byte[bytesPerRow];
1136         byte[] prior = new byte[bytesPerRow];
1137 
1138         // Create a 1-row tall Raster to hold the data
1139         WritableRaster passRow = createRaster(passWidth, 1, inputBands,
1140                                               eltsPerRow,
1141                                               bitDepth);
1142 
1143         // Create an array suitable for holding one pixel
1144         int[] ps = passRow.getPixel(0, 0, (int[])null);
1145 
1146         DataBuffer dataBuffer = passRow.getDataBuffer();
1147         int type = dataBuffer.getDataType();
1148         if (type == DataBuffer.TYPE_BYTE) {
1149             byteData = ((DataBufferByte)dataBuffer).getData();
1150         } else {
1151             shortData = ((DataBufferUShort)dataBuffer).getData();
1152         }
1153 
1154         processPassStarted(theImage,
1155                            passNum,
1156                            sourceMinProgressivePass,
1157                            sourceMaxProgressivePass,
1158                            updateMinX, updateMinY,
1159                            updateXStep, updateYStep,
1160                            destinationBands);
1161 
1162         // Handle source and destination bands
1163         if (sourceBands != null) {
1164             passRow = passRow.createWritableChild(0, 0,
1165                                                   passRow.getWidth(), 1,
1166                                                   0, 0,
1167                                                   sourceBands);
1168         }
1169         if (destinationBands != null) {
1170             imRas = imRas.createWritableChild(0, 0,
1171                                               imRas.getWidth(),
1172                                               imRas.getHeight(),
1173                                               0, 0,
1174                                               destinationBands);
1175         }
1176 
1177         // Determine if all of the relevant output bands have the
1178         // same bit depth as the source data
1179         boolean adjustBitDepths = false;
1180         int[] outputSampleSize = imRas.getSampleModel().getSampleSize();
1181         for (int b = 0; b < inputBands; b++) {
1182             if (outputSampleSize[b] != bitDepth) {
1183                 adjustBitDepths = true;
1184                 break;
1185             }
1186         }
1187 
1188         // If the bit depths differ, create a lookup table per band to perform
1189         // the conversion
1190         int[][] scale = null;
1191         if (adjustBitDepths) {
1192             int maxInSample = (1 << bitDepth) - 1;
1193             int halfMaxInSample = maxInSample/2;
1194             scale = new int[inputBands][];
1195             for (int b = 0; b < inputBands; b++) {
1196                 int maxOutSample = (1 << outputSampleSize[b]) - 1;
1197                 scale[b] = new int[maxInSample + 1];
1198                 for (int s = 0; s <= maxInSample; s++) {
1199                     scale[b][s] =
1200                         (s*maxOutSample + halfMaxInSample)/maxInSample;
1201                 }
1202             }
1203         }
1204 
1205         // Limit passRow to relevant area for the case where we
1206         // will can setRect to copy a contiguous span
1207         boolean useSetRect = srcXStep == 1 &&
1208             updateXStep == 1 &&
1209             !adjustBitDepths &&
1210             (imRas instanceof ByteInterleavedRaster);
1211 
1212         if (useSetRect) {
1213             passRow = passRow.createWritableChild(srcX, 0,
1214                                                   updateWidth, 1,
1215                                                   0, 0,
1216                                                   null);
1217         }
1218 
1219         // Decode the (sub)image row-by-row
1220         for (int srcY = 0; srcY < passHeight; srcY++) {
1221             // Update count of pixels read
1222             updateImageProgress(passWidth);
1223             /*
1224              * If read has been aborted, just return
1225              * processReadAborted will be called later
1226              */
1227             if (abortRequested()) {
1228                 return;
1229             }
1230             // Read the filter type byte and a row of data
1231             int filter = pixelStream.read();
1232             try {
1233                 // Swap curr and prior
1234                 byte[] tmp = prior;
1235                 prior = curr;
1236                 curr = tmp;
1237 
1238                 pixelStream.readFully(curr, 0, bytesPerRow);
1239             } catch (java.util.zip.ZipException ze) {
1240                 // TODO - throw a more meaningful exception
1241                 throw ze;
1242             }
1243 
1244             switch (filter) {
1245             case PNG_FILTER_NONE:
1246                 break;
1247             case PNG_FILTER_SUB:
1248                 decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel);
1249                 break;
1250             case PNG_FILTER_UP:
1251                 decodeUpFilter(curr, 0, prior, 0, bytesPerRow);
1252                 break;
1253             case PNG_FILTER_AVERAGE:
1254                 decodeAverageFilter(curr, 0, prior, 0, bytesPerRow,
1255                                     bytesPerPixel);
1256                 break;
1257             case PNG_FILTER_PAETH:
1258                 decodePaethFilter(curr, 0, prior, 0, bytesPerRow,
1259                                   bytesPerPixel);
1260                 break;
1261             default:
1262                 throw new IIOException("Unknown row filter type (= " +
1263                                        filter + ")!");
1264             }
1265 
1266             // Copy data into passRow byte by byte
1267             if (bitDepth < 16) {
1268                 System.arraycopy(curr, 0, byteData, 0, bytesPerRow);
1269             } else {
1270                 int idx = 0;
1271                 for (int j = 0; j < eltsPerRow; j++) {
1272                     shortData[j] =
1273                         (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff));
1274                     idx += 2;
1275                 }
1276             }
1277 
1278             // True Y position in source
1279             int sourceY = srcY*yStep + yStart;
1280             if ((sourceY >= sourceRegion.y) &&
1281                 (sourceY < sourceRegion.y + sourceRegion.height) &&
1282                 (((sourceY - sourceRegion.y) %
1283                   sourceYSubsampling) == 0)) {
1284 
1285                 int dstY = destinationOffset.y +
1286                     (sourceY - sourceRegion.y)/sourceYSubsampling;
1287                 if (dstY < dstMinY) {
1288                     continue;
1289                 }
1290                 if (dstY > dstMaxY) {
1291                     break;
1292                 }
1293 
1294                /*
1295                 * For PNG images of color type PNG_COLOR_RGB or PNG_COLOR_GRAY
1296                 * that contain a specific transparent color (given by tRNS
1297                 * chunk), we compare the decoded pixel color with the color
1298                 * given by tRNS chunk to set the alpha on the destination.
1299                 */
1300                 boolean tRNSTransparentPixelPresent =
1301                     theImage.getSampleModel().getNumBands() == inputBands + 1 &&
1302                     metadata.hasTransparentColor();
1303                 if (useSetRect &&
1304                     !tRNSTransparentPixelPresent) {
1305                     imRas.setRect(updateMinX, dstY, passRow);
1306                 } else {
1307                     int newSrcX = srcX;
1308 
1309                     /*
1310                      * Create intermediate array to fill the extra alpha
1311                      * channel when tRNSTransparentPixelPresent is true.
1312                      */
1313                     final int[] temp = new int[inputBands + 1];
1314                     final int opaque = (bitDepth < 16) ? 255 : 65535;
1315                     for (int dstX = updateMinX;
1316                          dstX < updateMinX + updateWidth;
1317                          dstX += updateXStep) {
1318 
1319                         passRow.getPixel(newSrcX, 0, ps);
1320                         if (adjustBitDepths) {
1321                             for (int b = 0; b < inputBands; b++) {
1322                                 ps[b] = scale[b][ps[b]];
1323                             }
1324                         }
1325                         if (tRNSTransparentPixelPresent) {
1326                             if (metadata.tRNS_colorType == PNG_COLOR_RGB) {
1327                                 temp[0] = ps[0];
1328                                 temp[1] = ps[1];
1329                                 temp[2] = ps[2];
1330                                 if (ps[0] == metadata.tRNS_red &&
1331                                     ps[1] == metadata.tRNS_green &&
1332                                     ps[2] == metadata.tRNS_blue) {
1333                                     temp[3] = 0;
1334                                 } else {
1335                                     temp[3] = opaque;
1336                                 }
1337                             } else {
1338                                 // when tRNS_colorType is PNG_COLOR_GRAY
1339                                 temp[0] = ps[0];
1340                                 if (ps[0] == metadata.tRNS_gray) {
1341                                     temp[1] = 0;
1342                                 } else {
1343                                     temp[1] = opaque;
1344                                 }
1345                             }
1346                             imRas.setPixel(dstX, dstY, temp);
1347                         } else {
1348                             imRas.setPixel(dstX, dstY, ps);
1349                         }
1350                         newSrcX += srcXStep;
1351                     }
1352                 }
1353 
1354                 processImageUpdate(theImage,
1355                                    updateMinX, dstY,
1356                                    updateWidth, 1,
1357                                    updateXStep, updateYStep,
1358                                    destinationBands);
1359             }
1360         }
1361 
1362         processPassComplete(theImage);
1363     }
1364 
1365     private void decodeImage()
1366         throws IOException, IIOException  {
1367         int width = metadata.IHDR_width;
1368         int height = metadata.IHDR_height;
1369 
1370         this.pixelsDone = 0;
1371         this.totalPixels = width*height;
1372 
1373         if (metadata.IHDR_interlaceMethod == 0) {
1374             decodePass(0, 0, 0, 1, 1, width, height);
1375         } else {
1376             for (int i = 0; i <= sourceMaxProgressivePass; i++) {
1377                 int XOffset = adam7XOffset[i];
1378                 int YOffset = adam7YOffset[i];
1379                 int XSubsampling = adam7XSubsampling[i];
1380                 int YSubsampling = adam7YSubsampling[i];
1381                 int xbump = adam7XSubsampling[i + 1] - 1;
1382                 int ybump = adam7YSubsampling[i + 1] - 1;
1383 
1384                 if (i >= sourceMinProgressivePass) {
1385                     decodePass(i,
1386                                XOffset,
1387                                YOffset,
1388                                XSubsampling,
1389                                YSubsampling,
1390                                (width + xbump)/XSubsampling,
1391                                (height + ybump)/YSubsampling);
1392                 } else {
1393                     skipPass((width + xbump)/XSubsampling,
1394                              (height + ybump)/YSubsampling);
1395                 }
1396 
1397                 /*
1398                  * If read has been aborted, just return
1399                  * processReadAborted will be called later
1400                  */
1401                 if (abortRequested()) {
1402                     return;
1403                 }
1404             }
1405         }
1406     }
1407 
1408     private void readImage(ImageReadParam param) throws IIOException {
1409         readMetadata();
1410 
1411         int width = metadata.IHDR_width;
1412         int height = metadata.IHDR_height;
1413 
1414         // Init default values
1415         sourceXSubsampling = 1;
1416         sourceYSubsampling = 1;
1417         sourceMinProgressivePass = 0;
1418         sourceMaxProgressivePass = 6;
1419         sourceBands = null;
1420         destinationBands = null;
1421         destinationOffset = new Point(0, 0);
1422 
1423         // If an ImageReadParam is available, get values from it
1424         if (param != null) {
1425             sourceXSubsampling = param.getSourceXSubsampling();
1426             sourceYSubsampling = param.getSourceYSubsampling();
1427 
1428             sourceMinProgressivePass =
1429                 Math.max(param.getSourceMinProgressivePass(), 0);
1430             sourceMaxProgressivePass =
1431                 Math.min(param.getSourceMaxProgressivePass(), 6);
1432 
1433             sourceBands = param.getSourceBands();
1434             destinationBands = param.getDestinationBands();
1435             destinationOffset = param.getDestinationOffset();
1436         }
1437         Inflater inf = null;
1438         try {
1439             stream.seek(imageStartPosition);
1440 
1441             Enumeration<InputStream> e = new PNGImageDataEnumeration(stream);
1442             InputStream is = new SequenceInputStream(e);
1443 
1444            /* InflaterInputStream uses an Inflater instance which consumes
1445             * native (non-GC visible) resources. This is normally implicitly
1446             * freed when the stream is closed. However since the
1447             * InflaterInputStream wraps a client-supplied input stream,
1448             * we cannot close it.
1449             * But the app may depend on GC finalization to close the stream.
1450             * Therefore to ensure timely freeing of native resources we
1451             * explicitly create the Inflater instance and free its resources
1452             * when we are done with the InflaterInputStream by calling
1453             * inf.end();
1454             */
1455             inf = new Inflater();
1456             is = new InflaterInputStream(is, inf);
1457             is = new BufferedInputStream(is);
1458             this.pixelStream = new DataInputStream(is);
1459 
1460             /*
1461              * PNG spec declares that valid range for width
1462              * and height is [1, 2^31-1], so here we may fail to allocate
1463              * a buffer for destination image due to memory limitation.
1464              *
1465              * If the read operation triggers OutOfMemoryError, the same
1466              * will be wrapped in an IIOException at PNGImageReader.read
1467              * method.
1468              *
1469              * The recovery strategy for this case should be defined at
1470              * the level of application, so we will not try to estimate
1471              * the required amount of the memory and/or handle OOM in
1472              * any way.
1473              */
1474             theImage = getDestination(param,
1475                                       getImageTypes(0),
1476                                       width,
1477                                       height);
1478 
1479             Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1480             sourceRegion = new Rectangle(0, 0, 0, 0);
1481             computeRegions(param, width, height,
1482                            theImage,
1483                            sourceRegion, destRegion);
1484             destinationOffset.setLocation(destRegion.getLocation());
1485 
1486             // At this point the header has been read and we know
1487             // how many bands are in the image, so perform checking
1488             // of the read param.
1489             int colorType = metadata.IHDR_colorType;
1490             if (theImage.getSampleModel().getNumBands()
1491                 == inputBandsForColorType[colorType] + 1
1492                 && metadata.hasTransparentColor()) {
1493                 checkReadParamBandSettings(param,
1494                     inputBandsForColorType[colorType] + 1,
1495                     theImage.getSampleModel().getNumBands());
1496             } else {
1497                 checkReadParamBandSettings(param,
1498                     inputBandsForColorType[colorType],
1499                     theImage.getSampleModel().getNumBands());
1500             }
1501 
1502             clearAbortRequest();
1503             processImageStarted(0);
1504             if (abortRequested()) {
1505                 processReadAborted();
1506             } else {
1507                 decodeImage();
1508                 if (abortRequested()) {
1509                     processReadAborted();
1510                 } else {
1511                     processImageComplete();
1512                 }
1513             }
1514 
1515         } catch (IOException e) {
1516             throw new IIOException("Error reading PNG image data", e);
1517         } finally {
1518             if (inf != null) {
1519                 inf.end();
1520             }
1521         }
1522     }
1523 
1524     @Override
1525     public int getNumImages(boolean allowSearch) throws IIOException {
1526         if (stream == null) {
1527             throw new IllegalStateException("No input source set!");
1528         }
1529         if (seekForwardOnly && allowSearch) {
1530             throw new IllegalStateException
1531                 ("seekForwardOnly and allowSearch can't both be true!");
1532         }
1533         return 1;
1534     }
1535 
1536     @Override
1537     public int getWidth(int imageIndex) throws IIOException {
1538         if (imageIndex != 0) {
1539             throw new IndexOutOfBoundsException("imageIndex != 0!");
1540         }
1541 
1542         readHeader();
1543 
1544         return metadata.IHDR_width;
1545     }
1546 
1547     @Override
1548     public int getHeight(int imageIndex) throws IIOException {
1549         if (imageIndex != 0) {
1550             throw new IndexOutOfBoundsException("imageIndex != 0!");
1551         }
1552 
1553         readHeader();
1554 
1555         return metadata.IHDR_height;
1556     }
1557 
1558     @Override
1559     public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
1560       throws IIOException
1561     {
1562         if (imageIndex != 0) {
1563             throw new IndexOutOfBoundsException("imageIndex != 0!");
1564         }
1565 
1566         readHeader();
1567 
1568         ArrayList<ImageTypeSpecifier> l =
1569             new ArrayList<ImageTypeSpecifier>(1);
1570 
1571         ColorSpace rgb;
1572         ColorSpace gray;
1573         int[] bandOffsets;
1574 
1575         int bitDepth = metadata.IHDR_bitDepth;
1576         int colorType = metadata.IHDR_colorType;
1577 
1578         int dataType;
1579         if (bitDepth <= 8) {
1580             dataType = DataBuffer.TYPE_BYTE;
1581         } else {
1582             dataType = DataBuffer.TYPE_USHORT;
1583         }
1584 
1585         switch (colorType) {
1586         /*
1587          * For PNG images of color type PNG_COLOR_RGB or PNG_COLOR_GRAY that
1588          * contain a specific transparent color (given by tRNS chunk), we add
1589          * ImageTypeSpecifier(s) that support transparency to the list of
1590          * supported image types.
1591          */
1592         case PNG_COLOR_GRAY:
1593             readMetadata(); // Need tRNS chunk
1594 
1595             if (metadata.hasTransparentColor()) {
1596                 gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
1597                 bandOffsets = new int[2];
1598                 bandOffsets[0] = 0;
1599                 bandOffsets[1] = 1;
1600                 l.add(ImageTypeSpecifier.createInterleaved(gray,
1601                                                            bandOffsets,
1602                                                            dataType,
1603                                                            true,
1604                                                            false));
1605             }
1606             // Packed grayscale
1607             l.add(ImageTypeSpecifier.createGrayscale(bitDepth,
1608                                                      dataType,
1609                                                      false));
1610             break;
1611 
1612         case PNG_COLOR_RGB:
1613             readMetadata(); // Need tRNS chunk
1614 
1615             if (bitDepth == 8) {
1616                 if (metadata.hasTransparentColor()) {
1617                     l.add(ImageTypeSpecifier.createFromBufferedImageType(
1618                             BufferedImage.TYPE_4BYTE_ABGR));
1619                 }
1620                 // some standard types of buffered images
1621                 // which can be used as destination
1622                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1623                           BufferedImage.TYPE_3BYTE_BGR));
1624 
1625                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1626                           BufferedImage.TYPE_INT_RGB));
1627 
1628                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1629                           BufferedImage.TYPE_INT_BGR));
1630 
1631             }
1632 
1633             if (metadata.hasTransparentColor()) {
1634                 rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1635                 bandOffsets = new int[4];
1636                 bandOffsets[0] = 0;
1637                 bandOffsets[1] = 1;
1638                 bandOffsets[2] = 2;
1639                 bandOffsets[3] = 3;
1640 
1641                 l.add(ImageTypeSpecifier.
1642                     createInterleaved(rgb, bandOffsets,
1643                                       dataType, true, false));
1644             }
1645             // Component R, G, B
1646             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1647             bandOffsets = new int[3];
1648             bandOffsets[0] = 0;
1649             bandOffsets[1] = 1;
1650             bandOffsets[2] = 2;
1651             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1652                                                        bandOffsets,
1653                                                        dataType,
1654                                                        false,
1655                                                        false));
1656             break;
1657 
1658         case PNG_COLOR_PALETTE:
1659             readMetadata(); // Need tRNS chunk
1660 
1661             /*
1662              * The PLTE chunk spec says:
1663              *
1664              * The number of palette entries must not exceed the range that
1665              * can be represented in the image bit depth (for example, 2^4 = 16
1666              * for a bit depth of 4). It is permissible to have fewer entries
1667              * than the bit depth would allow. In that case, any out-of-range
1668              * pixel value found in the image data is an error.
1669              *
1670              * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE
1671              *
1672              * Consequently, the case when the palette length is smaller than
1673              * 2^bitDepth is legal in the view of PNG spec.
1674              *
1675              * However the spec of createIndexed() method demands the exact
1676              * equality of the palette lengh and number of possible palette
1677              * entries (2^bitDepth).
1678              *
1679              * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed}
1680              *
1681              * In order to avoid this contradiction we need to extend the
1682              * palette arrays to the limit defined by the bitDepth.
1683              */
1684 
1685             int plength = 1 << bitDepth;
1686 
1687             byte[] red = metadata.PLTE_red;
1688             byte[] green = metadata.PLTE_green;
1689             byte[] blue = metadata.PLTE_blue;
1690 
1691             if (metadata.PLTE_red.length < plength) {
1692                 red = Arrays.copyOf(metadata.PLTE_red, plength);
1693                 Arrays.fill(red, metadata.PLTE_red.length, plength,
1694                             metadata.PLTE_red[metadata.PLTE_red.length - 1]);
1695 
1696                 green = Arrays.copyOf(metadata.PLTE_green, plength);
1697                 Arrays.fill(green, metadata.PLTE_green.length, plength,
1698                             metadata.PLTE_green[metadata.PLTE_green.length - 1]);
1699 
1700                 blue = Arrays.copyOf(metadata.PLTE_blue, plength);
1701                 Arrays.fill(blue, metadata.PLTE_blue.length, plength,
1702                             metadata.PLTE_blue[metadata.PLTE_blue.length - 1]);
1703 
1704             }
1705 
1706             // Alpha from tRNS chunk may have fewer entries than
1707             // the RGB LUTs from the PLTE chunk; if so, pad with
1708             // 255.
1709             byte[] alpha = null;
1710             if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) {
1711                 if (metadata.tRNS_alpha.length == red.length) {
1712                     alpha = metadata.tRNS_alpha;
1713                 } else {
1714                     alpha = Arrays.copyOf(metadata.tRNS_alpha, red.length);
1715                     Arrays.fill(alpha,
1716                                 metadata.tRNS_alpha.length,
1717                                 red.length, (byte)255);
1718                 }
1719             }
1720 
1721             l.add(ImageTypeSpecifier.createIndexed(red, green,
1722                                                    blue, alpha,
1723                                                    bitDepth,
1724                                                    DataBuffer.TYPE_BYTE));
1725             break;
1726 
1727         case PNG_COLOR_GRAY_ALPHA:
1728             // Component G, A
1729             gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
1730             bandOffsets = new int[2];
1731             bandOffsets[0] = 0;
1732             bandOffsets[1] = 1;
1733             l.add(ImageTypeSpecifier.createInterleaved(gray,
1734                                                        bandOffsets,
1735                                                        dataType,
1736                                                        true,
1737                                                        false));
1738             break;
1739 
1740         case PNG_COLOR_RGB_ALPHA:
1741             if (bitDepth == 8) {
1742                 // some standard types of buffered images
1743                 // wich can be used as destination
1744                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1745                           BufferedImage.TYPE_4BYTE_ABGR));
1746 
1747                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1748                           BufferedImage.TYPE_INT_ARGB));
1749             }
1750 
1751             // Component R, G, B, A (non-premultiplied)
1752             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1753             bandOffsets = new int[4];
1754             bandOffsets[0] = 0;
1755             bandOffsets[1] = 1;
1756             bandOffsets[2] = 2;
1757             bandOffsets[3] = 3;
1758 
1759             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1760                                                        bandOffsets,
1761                                                        dataType,
1762                                                        true,
1763                                                        false));
1764             break;
1765 
1766         default:
1767             break;
1768         }
1769 
1770         return l.iterator();
1771     }
1772 
1773     /*
1774      * Super class implementation uses first element
1775      * of image types list as raw image type.
1776      *
1777      * Also, super implementation uses first element of this list
1778      * as default destination type image read param does not specify
1779      * anything other.
1780      *
1781      * However, in case of RGB and RGBA color types, raw image type
1782      * produces buffered image of custom type. It causes some
1783      * performance degradation of subsequent rendering operations.
1784      *
1785      * To resolve this contradiction we put standard image types
1786      * at the first positions of image types list (to produce standard
1787      * images by default) and put raw image type (which is custom)
1788      * at the last position of this list.
1789      *
1790      * After this changes we should override getRawImageType()
1791      * to return last element of image types list.
1792      */
1793     @Override
1794     public ImageTypeSpecifier getRawImageType(int imageIndex)
1795       throws IOException {
1796 
1797         Iterator<ImageTypeSpecifier> types = getImageTypes(imageIndex);
1798         ImageTypeSpecifier raw = null;
1799         do {
1800             raw = types.next();
1801         } while (types.hasNext());
1802         return raw;
1803     }
1804 
1805     @Override
1806     public ImageReadParam getDefaultReadParam() {
1807         return new ImageReadParam();
1808     }
1809 
1810     @Override
1811     public IIOMetadata getStreamMetadata()
1812         throws IIOException {
1813         return null;
1814     }
1815 
1816     @Override
1817     public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
1818         if (imageIndex != 0) {
1819             throw new IndexOutOfBoundsException("imageIndex != 0!");
1820         }
1821         readMetadata();
1822         return metadata;
1823     }
1824 
1825     @Override
1826     public BufferedImage read(int imageIndex, ImageReadParam param)
1827         throws IIOException {
1828         if (imageIndex != 0) {
1829             throw new IndexOutOfBoundsException("imageIndex != 0!");
1830         }
1831 
1832         try {
1833             readImage(param);
1834         } catch (IOException |
1835                  IllegalStateException |
1836                  IllegalArgumentException e)
1837         {
1838             throw e;
1839         } catch (Throwable e) {
1840             throw new IIOException("Caught exception during read: ", e);
1841         }
1842         return theImage;
1843     }
1844 
1845     @Override
1846     public void reset() {
1847         super.reset();
1848         resetStreamSettings();
1849     }
1850 
1851     private void resetStreamSettings() {
1852         gotHeader = false;
1853         gotMetadata = false;
1854         metadata = null;
1855         pixelStream = null;
1856         imageStartPosition = -1L;
1857     }
1858 }