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