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 = 0;
 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         if (b != 0) {
 220             throw new IIOException("Found non null terminated string");
 221         }
 222         return new String(baos.toByteArray(), charset);
 223     }
 224 
 225     private void readHeader() throws IIOException {
 226         if (gotHeader) {
 227             return;
 228         }
 229         if (stream == null) {
 230             throw new IllegalStateException("Input source not set!");
 231         }
 232 
 233         try {
 234             byte[] signature = new byte[8];
 235             stream.readFully(signature);
 236 
 237             if (signature[0] != (byte)137 ||
 238                 signature[1] != (byte)80 ||
 239                 signature[2] != (byte)78 ||
 240                 signature[3] != (byte)71 ||
 241                 signature[4] != (byte)13 ||
 242                 signature[5] != (byte)10 ||
 243                 signature[6] != (byte)26 ||
 244                 signature[7] != (byte)10) {
 245                 throw new IIOException("Bad PNG signature!");
 246             }
 247 
 248             int IHDR_length = stream.readInt();
 249             if (IHDR_length != 13) {
 250                 throw new IIOException("Bad length for IHDR chunk!");
 251             }
 252             int IHDR_type = stream.readInt();
 253             if (IHDR_type != IHDR_TYPE) {
 254                 throw new IIOException("Bad type for IHDR chunk!");
 255             }
 256 
 257             this.metadata = new PNGMetadata();
 258 
 259             int width = stream.readInt();
 260             int height = stream.readInt();
 261 
 262             // Re-use signature array to bulk-read these unsigned byte values
 263             stream.readFully(signature, 0, 5);
 264             int bitDepth          = signature[0] & 0xff;
 265             int colorType         = signature[1] & 0xff;
 266             int compressionMethod = signature[2] & 0xff;
 267             int filterMethod      = signature[3] & 0xff;
 268             int interlaceMethod   = signature[4] & 0xff;
 269 
 270             // Skip IHDR CRC
 271             stream.skipBytes(4);
 272 
 273             stream.flushBefore(stream.getStreamPosition());
 274 
 275             if (width <= 0) {
 276                 throw new IIOException("Image width <= 0!");
 277             }
 278             if (height <= 0) {
 279                 throw new IIOException("Image height <= 0!");
 280             }
 281             if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 &&
 282                 bitDepth != 8 && bitDepth != 16) {
 283                 throw new IIOException("Bit depth must be 1, 2, 4, 8, or 16!");
 284             }
 285             if (colorType != 0 && colorType != 2 && colorType != 3 &&
 286                 colorType != 4 && colorType != 6) {
 287                 throw new IIOException("Color type must be 0, 2, 3, 4, or 6!");
 288             }
 289             if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) {
 290                 throw new IIOException("Bad color type/bit depth combination!");
 291             }
 292             if ((colorType == PNG_COLOR_RGB ||
 293                  colorType == PNG_COLOR_RGB_ALPHA ||
 294                  colorType == PNG_COLOR_GRAY_ALPHA) &&
 295                 (bitDepth != 8 && bitDepth != 16)) {
 296                 throw new IIOException("Bad color type/bit depth combination!");
 297             }
 298             if (compressionMethod != 0) {
 299                 throw new IIOException("Unknown compression method (not 0)!");
 300             }
 301             if (filterMethod != 0) {
 302                 throw new IIOException("Unknown filter method (not 0)!");
 303             }
 304             if (interlaceMethod != 0 && interlaceMethod != 1) {
 305                 throw new IIOException("Unknown interlace method (not 0 or 1)!");
 306             }
 307 
 308             metadata.IHDR_present = true;
 309             metadata.IHDR_width = width;
 310             metadata.IHDR_height = height;
 311             metadata.IHDR_bitDepth = bitDepth;
 312             metadata.IHDR_colorType = colorType;
 313             metadata.IHDR_compressionMethod = compressionMethod;
 314             metadata.IHDR_filterMethod = filterMethod;
 315             metadata.IHDR_interlaceMethod = interlaceMethod;
 316             gotHeader = true;
 317         } catch (IOException e) {
 318             throw new IIOException("I/O error reading PNG header!", e);
 319         }
 320     }
 321 
 322     private void parse_PLTE_chunk(int chunkLength) throws IOException {
 323         if (metadata.PLTE_present) {
 324             processWarningOccurred(
 325 "A PNG image may not contain more than one PLTE chunk.\n" +
 326 "The chunk wil be ignored.");
 327             return;
 328         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
 329                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
 330             processWarningOccurred(
 331 "A PNG gray or gray alpha image cannot have a PLTE chunk.\n" +
 332 "The chunk wil be ignored.");
 333             return;
 334         }
 335 
 336         byte[] palette = new byte[chunkLength];
 337         stream.readFully(palette);
 338 
 339         int numEntries = chunkLength/3;
 340         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
 341             int maxEntries = 1 << metadata.IHDR_bitDepth;
 342             if (numEntries > maxEntries) {
 343                 processWarningOccurred(
 344 "PLTE chunk contains too many entries for bit depth, ignoring extras.");
 345                 numEntries = maxEntries;
 346             }
 347             numEntries = Math.min(numEntries, maxEntries);
 348         }
 349 
 350         // Round array sizes up to 2^2^n
 351         int paletteEntries;
 352         if (numEntries > 16) {
 353             paletteEntries = 256;
 354         } else if (numEntries > 4) {
 355             paletteEntries = 16;
 356         } else if (numEntries > 2) {
 357             paletteEntries = 4;
 358         } else {
 359             paletteEntries = 2;
 360         }
 361 
 362         metadata.PLTE_present = true;
 363         metadata.PLTE_red = new byte[paletteEntries];
 364         metadata.PLTE_green = new byte[paletteEntries];
 365         metadata.PLTE_blue = new byte[paletteEntries];
 366 
 367         int index = 0;
 368         for (int i = 0; i < numEntries; i++) {
 369             metadata.PLTE_red[i] = palette[index++];
 370             metadata.PLTE_green[i] = palette[index++];
 371             metadata.PLTE_blue[i] = palette[index++];
 372         }
 373     }
 374 
 375     private void parse_bKGD_chunk() throws IOException {
 376         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
 377             metadata.bKGD_colorType = PNG_COLOR_PALETTE;
 378             metadata.bKGD_index = stream.readUnsignedByte();
 379         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
 380                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
 381             metadata.bKGD_colorType = PNG_COLOR_GRAY;
 382             metadata.bKGD_gray = stream.readUnsignedShort();
 383         } else { // RGB or RGB_ALPHA
 384             metadata.bKGD_colorType = PNG_COLOR_RGB;
 385             metadata.bKGD_red = stream.readUnsignedShort();
 386             metadata.bKGD_green = stream.readUnsignedShort();
 387             metadata.bKGD_blue = stream.readUnsignedShort();
 388         }
 389 
 390         metadata.bKGD_present = true;
 391     }
 392 
 393     private void parse_cHRM_chunk() throws IOException {
 394         metadata.cHRM_whitePointX = stream.readInt();
 395         metadata.cHRM_whitePointY = stream.readInt();
 396         metadata.cHRM_redX = stream.readInt();
 397         metadata.cHRM_redY = stream.readInt();
 398         metadata.cHRM_greenX = stream.readInt();
 399         metadata.cHRM_greenY = stream.readInt();
 400         metadata.cHRM_blueX = stream.readInt();
 401         metadata.cHRM_blueY = stream.readInt();
 402 
 403         metadata.cHRM_present = true;
 404     }
 405 
 406     private void parse_gAMA_chunk() throws IOException {
 407         int gamma = stream.readInt();
 408         metadata.gAMA_gamma = gamma;
 409 
 410         metadata.gAMA_present = true;
 411     }
 412 
 413     private void parse_hIST_chunk(int chunkLength) throws IOException,
 414         IIOException
 415     {
 416         if (!metadata.PLTE_present) {
 417             throw new IIOException("hIST chunk without prior PLTE chunk!");
 418         }
 419 
 420         /* According to PNG specification length of
 421          * hIST chunk is specified in bytes and
 422          * hIST chunk consists of 2 byte elements
 423          * (so we expect length is even).
 424          */
 425         metadata.hIST_histogram = new char[chunkLength/2];
 426         stream.readFully(metadata.hIST_histogram,
 427                          0, metadata.hIST_histogram.length);
 428 
 429         metadata.hIST_present = true;
 430     }
 431 
 432     private void parse_iCCP_chunk(int chunkLength) throws IOException {
 433         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 434         int compressedProfileLength = chunkLength - keyword.length() - 2;
 435         if (compressedProfileLength <= 0) {
 436             throw new IIOException("iCCP chunk length is not proper");
 437         }
 438         metadata.iCCP_profileName = keyword;
 439 
 440         metadata.iCCP_compressionMethod = stream.readUnsignedByte();
 441 
 442         byte[] compressedProfile =
 443           new byte[compressedProfileLength];
 444         stream.readFully(compressedProfile);
 445         metadata.iCCP_compressedProfile = compressedProfile;
 446 
 447         metadata.iCCP_present = true;
 448     }
 449 
 450     private void parse_iTXt_chunk(int chunkLength) throws IOException {
 451         long chunkStart = stream.getStreamPosition();
 452 
 453         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 454         metadata.iTXt_keyword.add(keyword);
 455 
 456         int compressionFlag = stream.readUnsignedByte();
 457         metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1));
 458 
 459         int compressionMethod = stream.readUnsignedByte();
 460         metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
 461 
 462         String languageTag = readNullTerminatedString("UTF8", 80);


 463         metadata.iTXt_languageTag.add(languageTag);
 464 
 465         long pos = stream.getStreamPosition();
 466         int maxLen = (int)(chunkStart + chunkLength - pos);



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