1 /*
   2  * Copyright (c) 2000, 2017, 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         metadata.iCCP_profileName = keyword;
 432 
 433         metadata.iCCP_compressionMethod = stream.readUnsignedByte();
 434 
 435         byte[] compressedProfile =
 436           new byte[chunkLength - keyword.length() - 2];
 437         stream.readFully(compressedProfile);
 438         metadata.iCCP_compressedProfile = compressedProfile;
 439 
 440         metadata.iCCP_present = true;
 441     }
 442 
 443     private void parse_iTXt_chunk(int chunkLength) throws IOException {
 444         long chunkStart = stream.getStreamPosition();
 445 
 446         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 447         metadata.iTXt_keyword.add(keyword);
 448 
 449         int compressionFlag = stream.readUnsignedByte();
 450         metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1));
 451 
 452         int compressionMethod = stream.readUnsignedByte();
 453         metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
 454 
 455         String languageTag = readNullTerminatedString("UTF8", 80);
 456         metadata.iTXt_languageTag.add(languageTag);
 457 
 458         long pos = stream.getStreamPosition();
 459         int maxLen = (int)(chunkStart + chunkLength - pos);
 460         String translatedKeyword =
 461             readNullTerminatedString("UTF8", maxLen);
 462         metadata.iTXt_translatedKeyword.add(translatedKeyword);
 463 
 464         String text;
 465         pos = stream.getStreamPosition();
 466         byte[] b = new byte[(int)(chunkStart + chunkLength - pos)];
 467         stream.readFully(b);
 468 
 469         if (compressionFlag == 1) { // Decompress the text
 470             text = new String(inflate(b), "UTF8");
 471         } else {
 472             text = new String(b, "UTF8");
 473         }
 474         metadata.iTXt_text.add(text);
 475 
 476         // Check if the text chunk contains image creation time
 477         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 478             // Update Standard/Document/ImageCreationTime from text chunk
 479             int index = metadata.iTXt_text.size() - 1;
 480             metadata.decodeImageCreationTimeFromTextChunk(
 481                     metadata.iTXt_text.listIterator(index));
 482         }
 483     }
 484 
 485     private void parse_pHYs_chunk() throws IOException {
 486         metadata.pHYs_pixelsPerUnitXAxis = stream.readInt();
 487         metadata.pHYs_pixelsPerUnitYAxis = stream.readInt();
 488         metadata.pHYs_unitSpecifier = stream.readUnsignedByte();
 489 
 490         metadata.pHYs_present = true;
 491     }
 492 
 493     private void parse_sBIT_chunk() throws IOException {
 494         int colorType = metadata.IHDR_colorType;
 495         if (colorType == PNG_COLOR_GRAY ||
 496             colorType == PNG_COLOR_GRAY_ALPHA) {
 497             metadata.sBIT_grayBits = stream.readUnsignedByte();
 498         } else if (colorType == PNG_COLOR_RGB ||
 499                    colorType == PNG_COLOR_PALETTE ||
 500                    colorType == PNG_COLOR_RGB_ALPHA) {
 501             metadata.sBIT_redBits = stream.readUnsignedByte();
 502             metadata.sBIT_greenBits = stream.readUnsignedByte();
 503             metadata.sBIT_blueBits = stream.readUnsignedByte();
 504         }
 505 
 506         if (colorType == PNG_COLOR_GRAY_ALPHA ||
 507             colorType == PNG_COLOR_RGB_ALPHA) {
 508             metadata.sBIT_alphaBits = stream.readUnsignedByte();
 509         }
 510 
 511         metadata.sBIT_colorType = colorType;
 512         metadata.sBIT_present = true;
 513     }
 514 
 515     private void parse_sPLT_chunk(int chunkLength)
 516         throws IOException, IIOException {
 517         metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80);
 518         chunkLength -= metadata.sPLT_paletteName.length() + 1;
 519 
 520         int sampleDepth = stream.readUnsignedByte();
 521         metadata.sPLT_sampleDepth = sampleDepth;
 522 
 523         int numEntries = chunkLength/(4*(sampleDepth/8) + 2);
 524         metadata.sPLT_red = new int[numEntries];
 525         metadata.sPLT_green = new int[numEntries];
 526         metadata.sPLT_blue = new int[numEntries];
 527         metadata.sPLT_alpha = new int[numEntries];
 528         metadata.sPLT_frequency = new int[numEntries];
 529 
 530         if (sampleDepth == 8) {
 531             for (int i = 0; i < numEntries; i++) {
 532                 metadata.sPLT_red[i] = stream.readUnsignedByte();
 533                 metadata.sPLT_green[i] = stream.readUnsignedByte();
 534                 metadata.sPLT_blue[i] = stream.readUnsignedByte();
 535                 metadata.sPLT_alpha[i] = stream.readUnsignedByte();
 536                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
 537             }
 538         } else if (sampleDepth == 16) {
 539             for (int i = 0; i < numEntries; i++) {
 540                 metadata.sPLT_red[i] = stream.readUnsignedShort();
 541                 metadata.sPLT_green[i] = stream.readUnsignedShort();
 542                 metadata.sPLT_blue[i] = stream.readUnsignedShort();
 543                 metadata.sPLT_alpha[i] = stream.readUnsignedShort();
 544                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
 545             }
 546         } else {
 547             throw new IIOException("sPLT sample depth not 8 or 16!");
 548         }
 549 
 550         metadata.sPLT_present = true;
 551     }
 552 
 553     private void parse_sRGB_chunk() throws IOException {
 554         metadata.sRGB_renderingIntent = stream.readUnsignedByte();
 555 
 556         metadata.sRGB_present = true;
 557     }
 558 
 559     private void parse_tEXt_chunk(int chunkLength) throws IOException {
 560         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 561         metadata.tEXt_keyword.add(keyword);
 562 
 563         byte[] b = new byte[chunkLength - keyword.length() - 1];
 564         stream.readFully(b);
 565         metadata.tEXt_text.add(new String(b, "ISO-8859-1"));
 566 
 567         // Check if the text chunk contains image creation time
 568         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 569             // Update Standard/Document/ImageCreationTime from text chunk
 570             int index = metadata.tEXt_text.size() - 1;
 571             metadata.decodeImageCreationTimeFromTextChunk(
 572                     metadata.tEXt_text.listIterator(index));
 573         }
 574     }
 575 
 576     private void parse_tIME_chunk() throws IOException {
 577         metadata.tIME_year = stream.readUnsignedShort();
 578         metadata.tIME_month = stream.readUnsignedByte();
 579         metadata.tIME_day = stream.readUnsignedByte();
 580         metadata.tIME_hour = stream.readUnsignedByte();
 581         metadata.tIME_minute = stream.readUnsignedByte();
 582         metadata.tIME_second = stream.readUnsignedByte();
 583 
 584         metadata.tIME_present = true;
 585     }
 586 
 587     private void parse_tRNS_chunk(int chunkLength) throws IOException {
 588         int colorType = metadata.IHDR_colorType;
 589         if (colorType == PNG_COLOR_PALETTE) {
 590             if (!metadata.PLTE_present) {
 591                 processWarningOccurred(
 592 "tRNS chunk without prior PLTE chunk, ignoring it.");
 593                 return;
 594             }
 595 
 596             // Alpha table may have fewer entries than RGB palette
 597             int maxEntries = metadata.PLTE_red.length;
 598             int numEntries = chunkLength;
 599             if (numEntries > maxEntries) {
 600                 processWarningOccurred(
 601 "tRNS chunk has more entries than prior PLTE chunk, ignoring extras.");
 602                 numEntries = maxEntries;
 603             }
 604             metadata.tRNS_alpha = new byte[numEntries];
 605             metadata.tRNS_colorType = PNG_COLOR_PALETTE;
 606             stream.read(metadata.tRNS_alpha, 0, numEntries);
 607             stream.skipBytes(chunkLength - numEntries);
 608         } else if (colorType == PNG_COLOR_GRAY) {
 609             if (chunkLength != 2) {
 610                 processWarningOccurred(
 611 "tRNS chunk for gray image must have length 2, ignoring chunk.");
 612                 stream.skipBytes(chunkLength);
 613                 return;
 614             }
 615             metadata.tRNS_gray = stream.readUnsignedShort();
 616             metadata.tRNS_colorType = PNG_COLOR_GRAY;
 617         } else if (colorType == PNG_COLOR_RGB) {
 618             if (chunkLength != 6) {
 619                 processWarningOccurred(
 620 "tRNS chunk for RGB image must have length 6, ignoring chunk.");
 621                 stream.skipBytes(chunkLength);
 622                 return;
 623             }
 624             metadata.tRNS_red = stream.readUnsignedShort();
 625             metadata.tRNS_green = stream.readUnsignedShort();
 626             metadata.tRNS_blue = stream.readUnsignedShort();
 627             metadata.tRNS_colorType = PNG_COLOR_RGB;
 628         } else {
 629             processWarningOccurred(
 630 "Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it.");
 631             return;
 632         }
 633 
 634         metadata.tRNS_present = true;
 635     }
 636 
 637     private static byte[] inflate(byte[] b) throws IOException {
 638         InputStream bais = new ByteArrayInputStream(b);
 639         InputStream iis = new InflaterInputStream(bais);
 640         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 641 
 642         int c;
 643         try {
 644             while ((c = iis.read()) != -1) {
 645                 baos.write(c);
 646             }
 647         } finally {
 648             iis.close();
 649         }
 650         return baos.toByteArray();
 651     }
 652 
 653     private void parse_zTXt_chunk(int chunkLength) throws IOException {
 654         String keyword = readNullTerminatedString("ISO-8859-1", 80);
 655         metadata.zTXt_keyword.add(keyword);
 656 
 657         int method = stream.readUnsignedByte();
 658         metadata.zTXt_compressionMethod.add(method);
 659 
 660         byte[] b = new byte[chunkLength - keyword.length() - 2];
 661         stream.readFully(b);
 662         metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1"));
 663 
 664         // Check if the text chunk contains image creation time
 665         if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) {
 666             // Update Standard/Document/ImageCreationTime from text chunk
 667             int index = metadata.zTXt_text.size() - 1;
 668             metadata.decodeImageCreationTimeFromTextChunk(
 669                     metadata.zTXt_text.listIterator(index));
 670         }
 671     }
 672 
 673     private void readMetadata() throws IIOException {
 674         if (gotMetadata) {
 675             return;
 676         }
 677 
 678         readHeader();
 679 
 680         /*
 681          * Optimization: We can skip the remaining metadata if the
 682          * ignoreMetadata flag is set, and only if this is not a palette
 683          * image (in that case, we need to read the metadata to get the
 684          * tRNS chunk, which is needed for the getImageTypes() method).
 685          */
 686         int colorType = metadata.IHDR_colorType;
 687         if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) {
 688             try {
 689                 while (true) {
 690                     int chunkLength = stream.readInt();
 691 
 692                     // verify the chunk length first
 693                     if (chunkLength < 0 || chunkLength + 4 < 0) {
 694                         throw new IIOException("Invalid chunk length " + chunkLength);
 695                     }
 696 
 697                     int chunkType = stream.readInt();
 698 
 699                     if (chunkType == IDAT_TYPE) {
 700                         // We've reached the image data
 701                         stream.skipBytes(-8);
 702                         imageStartPosition = stream.getStreamPosition();
 703                         break;
 704                     } else {
 705                         // Skip the chunk plus the 4 CRC bytes that follow
 706                         stream.skipBytes(chunkLength + 4);
 707                     }
 708                 }
 709             } catch (IOException e) {
 710                 throw new IIOException("Error skipping PNG metadata", e);
 711             }
 712 
 713             gotMetadata = true;
 714             return;
 715         }
 716 
 717         try {
 718             loop: while (true) {
 719                 int chunkLength = stream.readInt();
 720                 int chunkType = stream.readInt();
 721                 int chunkCRC;
 722 
 723                 // verify the chunk length
 724                 if (chunkLength < 0) {
 725                     throw new IIOException("Invalid chunk length " + chunkLength);
 726                 };
 727 
 728                 try {
 729                     stream.mark();
 730                     stream.seek(stream.getStreamPosition() + chunkLength);
 731                     chunkCRC = stream.readInt();
 732                     stream.reset();
 733                 } catch (IOException e) {
 734                     throw new IIOException("Invalid chunk length " + chunkLength);
 735                 }
 736 
 737                 switch (chunkType) {
 738                 case IDAT_TYPE:
 739                     // If chunk type is 'IDAT', we've reached the image data.
 740                     if (imageStartPosition == -1L) {
 741                         /*











 742                          * PNGs may contain multiple IDAT chunks containing
 743                          * a portion of image data. We store the position of
 744                          * the first IDAT chunk and continue with iteration
 745                          * of other chunks that follow image data.
 746                          */
 747                         imageStartPosition = stream.getStreamPosition() - 8;
 748                     }
 749                     // Move to the CRC byte location.
 750                     stream.skipBytes(chunkLength);
 751                     break;
 752                 case IEND_TYPE:
 753                     /*
 754                      * If the chunk type is 'IEND', we've reached end of image.
 755                      * Seek to the first IDAT chunk for subsequent decoding.
 756                      */
 757                     stream.seek(imageStartPosition);
 758 
 759                     /*
 760                      * flushBefore discards the portion of the stream before
 761                      * the indicated position. Hence this should be used after
 762                      * we complete iteration over available chunks including
 763                      * those that appear after the IDAT.
 764                      */
 765                     stream.flushBefore(stream.getStreamPosition());
 766                     break loop;
 767                 case PLTE_TYPE:
 768                     parse_PLTE_chunk(chunkLength);
 769                     break;
 770                 case bKGD_TYPE:
 771                     parse_bKGD_chunk();
 772                     break;
 773                 case cHRM_TYPE:
 774                     parse_cHRM_chunk();
 775                     break;
 776                 case gAMA_TYPE:
 777                     parse_gAMA_chunk();
 778                     break;
 779                 case hIST_TYPE:
 780                     parse_hIST_chunk(chunkLength);
 781                     break;
 782                 case iCCP_TYPE:
 783                     parse_iCCP_chunk(chunkLength);
 784                     break;
 785                 case iTXt_TYPE:
 786                     if (ignoreMetadata) {
 787                         stream.skipBytes(chunkLength);
 788                     } else {
 789                         parse_iTXt_chunk(chunkLength);
 790                     }
 791                     break;
 792                 case pHYs_TYPE:
 793                     parse_pHYs_chunk();
 794                     break;
 795                 case sBIT_TYPE:
 796                     parse_sBIT_chunk();
 797                     break;
 798                 case sPLT_TYPE:
 799                     parse_sPLT_chunk(chunkLength);
 800                     break;
 801                 case sRGB_TYPE:
 802                     parse_sRGB_chunk();
 803                     break;
 804                 case tEXt_TYPE:
 805                     parse_tEXt_chunk(chunkLength);
 806                     break;
 807                 case tIME_TYPE:
 808                     parse_tIME_chunk();
 809                     break;
 810                 case tRNS_TYPE:
 811                     parse_tRNS_chunk(chunkLength);
 812                     break;
 813                 case zTXt_TYPE:
 814                     if (ignoreMetadata) {
 815                         stream.skipBytes(chunkLength);
 816                     } else {
 817                         parse_zTXt_chunk(chunkLength);
 818                     }
 819                     break;
 820                 default:
 821                     // Read an unknown chunk
 822                     byte[] b = new byte[chunkLength];
 823                     stream.readFully(b);
 824 
 825                     StringBuilder chunkName = new StringBuilder(4);
 826                     chunkName.append((char)(chunkType >>> 24));
 827                     chunkName.append((char)((chunkType >> 16) & 0xff));
 828                     chunkName.append((char)((chunkType >> 8) & 0xff));
 829                     chunkName.append((char)(chunkType & 0xff));
 830 
 831                     int ancillaryBit = chunkType >>> 28;
 832                     if (ancillaryBit == 0) {
 833                         processWarningOccurred(
 834 "Encountered unknown chunk with critical bit set!");
 835                     }
 836 
 837                     metadata.unknownChunkType.add(chunkName.toString());
 838                     metadata.unknownChunkData.add(b);
 839                     break;
 840                 }
 841 
 842                 // double check whether all chunk data were consumed
 843                 if (chunkCRC != stream.readInt()) {
 844                     throw new IIOException("Failed to read a chunk of type " +
 845                             chunkType);
 846                 }
 847             }
 848         } catch (IOException e) {
 849             throw new IIOException("Error reading PNG metadata", e);
 850         }
 851 
 852         gotMetadata = true;
 853     }
 854 
 855     // Data filtering methods
 856 
 857     private static void decodeSubFilter(byte[] curr, int coff, int count,
 858                                         int bpp) {
 859         for (int i = bpp; i < count; i++) {
 860             int val;
 861 
 862             val = curr[i + coff] & 0xff;
 863             val += curr[i + coff - bpp] & 0xff;
 864 
 865             curr[i + coff] = (byte)val;
 866         }
 867     }
 868 
 869     private static void decodeUpFilter(byte[] curr, int coff,
 870                                        byte[] prev, int poff,
 871                                        int count) {
 872         for (int i = 0; i < count; i++) {
 873             int raw = curr[i + coff] & 0xff;
 874             int prior = prev[i + poff] & 0xff;
 875 
 876             curr[i + coff] = (byte)(raw + prior);
 877         }
 878     }
 879 
 880     private static void decodeAverageFilter(byte[] curr, int coff,
 881                                             byte[] prev, int poff,
 882                                             int count, int bpp) {
 883         int raw, priorPixel, priorRow;
 884 
 885         for (int i = 0; i < bpp; i++) {
 886             raw = curr[i + coff] & 0xff;
 887             priorRow = prev[i + poff] & 0xff;
 888 
 889             curr[i + coff] = (byte)(raw + priorRow/2);
 890         }
 891 
 892         for (int i = bpp; i < count; i++) {
 893             raw = curr[i + coff] & 0xff;
 894             priorPixel = curr[i + coff - bpp] & 0xff;
 895             priorRow = prev[i + poff] & 0xff;
 896 
 897             curr[i + coff] = (byte)(raw + (priorPixel + priorRow)/2);
 898         }
 899     }
 900 
 901     private static int paethPredictor(int a, int b, int c) {
 902         int p = a + b - c;
 903         int pa = Math.abs(p - a);
 904         int pb = Math.abs(p - b);
 905         int pc = Math.abs(p - c);
 906 
 907         if ((pa <= pb) && (pa <= pc)) {
 908             return a;
 909         } else if (pb <= pc) {
 910             return b;
 911         } else {
 912             return c;
 913         }
 914     }
 915 
 916     private static void decodePaethFilter(byte[] curr, int coff,
 917                                           byte[] prev, int poff,
 918                                           int count, int bpp) {
 919         int raw, priorPixel, priorRow, priorRowPixel;
 920 
 921         for (int i = 0; i < bpp; i++) {
 922             raw = curr[i + coff] & 0xff;
 923             priorRow = prev[i + poff] & 0xff;
 924 
 925             curr[i + coff] = (byte)(raw + priorRow);
 926         }
 927 
 928         for (int i = bpp; i < count; i++) {
 929             raw = curr[i + coff] & 0xff;
 930             priorPixel = curr[i + coff - bpp] & 0xff;
 931             priorRow = prev[i + poff] & 0xff;
 932             priorRowPixel = prev[i + poff - bpp] & 0xff;
 933 
 934             curr[i + coff] = (byte)(raw + paethPredictor(priorPixel,
 935                                                          priorRow,
 936                                                          priorRowPixel));
 937         }
 938     }
 939 
 940     private static final int[][] bandOffsets = {
 941         null,
 942         { 0 }, // G
 943         { 0, 1 }, // GA in GA order
 944         { 0, 1, 2 }, // RGB in RGB order
 945         { 0, 1, 2, 3 } // RGBA in RGBA order
 946     };
 947 
 948     private WritableRaster createRaster(int width, int height, int bands,
 949                                         int scanlineStride,
 950                                         int bitDepth) {
 951 
 952         DataBuffer dataBuffer;
 953         WritableRaster ras = null;
 954         Point origin = new Point(0, 0);
 955         if ((bitDepth < 8) && (bands == 1)) {
 956             dataBuffer = new DataBufferByte(height*scanlineStride);
 957             ras = Raster.createPackedRaster(dataBuffer,
 958                                             width, height,
 959                                             bitDepth,
 960                                             origin);
 961         } else if (bitDepth <= 8) {
 962             dataBuffer = new DataBufferByte(height*scanlineStride);
 963             ras = Raster.createInterleavedRaster(dataBuffer,
 964                                                  width, height,
 965                                                  scanlineStride,
 966                                                  bands,
 967                                                  bandOffsets[bands],
 968                                                  origin);
 969         } else {
 970             dataBuffer = new DataBufferUShort(height*scanlineStride);
 971             ras = Raster.createInterleavedRaster(dataBuffer,
 972                                                  width, height,
 973                                                  scanlineStride,
 974                                                  bands,
 975                                                  bandOffsets[bands],
 976                                                  origin);
 977         }
 978 
 979         return ras;
 980     }
 981 
 982     private void skipPass(int passWidth, int passHeight)
 983         throws IOException, IIOException  {
 984         if ((passWidth == 0) || (passHeight == 0)) {
 985             return;
 986         }
 987 
 988         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
 989         int bytesPerRow = (inputBands*passWidth*metadata.IHDR_bitDepth + 7)/8;
 990 
 991         // Read the image row-by-row
 992         for (int srcY = 0; srcY < passHeight; srcY++) {
 993             // Skip filter byte and the remaining row bytes
 994             pixelStream.skipBytes(1 + bytesPerRow);
 995         }
 996     }
 997 
 998     private void updateImageProgress(int newPixels) {
 999         pixelsDone += newPixels;
1000         processImageProgress(100.0F*pixelsDone/totalPixels);
1001     }
1002 
1003     private void decodePass(int passNum,
1004                             int xStart, int yStart,
1005                             int xStep, int yStep,
1006                             int passWidth, int passHeight) throws IOException {
1007 
1008         if ((passWidth == 0) || (passHeight == 0)) {
1009             return;
1010         }
1011 
1012         WritableRaster imRas = theImage.getWritableTile(0, 0);
1013         int dstMinX = imRas.getMinX();
1014         int dstMaxX = dstMinX + imRas.getWidth() - 1;
1015         int dstMinY = imRas.getMinY();
1016         int dstMaxY = dstMinY + imRas.getHeight() - 1;
1017 
1018         // Determine which pixels will be updated in this pass
1019         int[] vals =
1020           ReaderUtil.computeUpdatedPixels(sourceRegion,
1021                                           destinationOffset,
1022                                           dstMinX, dstMinY,
1023                                           dstMaxX, dstMaxY,
1024                                           sourceXSubsampling,
1025                                           sourceYSubsampling,
1026                                           xStart, yStart,
1027                                           passWidth, passHeight,
1028                                           xStep, yStep);
1029         int updateMinX = vals[0];
1030         int updateMinY = vals[1];
1031         int updateWidth = vals[2];
1032         int updateXStep = vals[4];
1033         int updateYStep = vals[5];
1034 
1035         int bitDepth = metadata.IHDR_bitDepth;
1036         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
1037         int bytesPerPixel = (bitDepth == 16) ? 2 : 1;
1038         bytesPerPixel *= inputBands;
1039 
1040         int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8;
1041         int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow;
1042 
1043         // If no pixels need updating, just skip the input data
1044         if (updateWidth == 0) {
1045             for (int srcY = 0; srcY < passHeight; srcY++) {
1046                 // Update count of pixels read
1047                 updateImageProgress(passWidth);
1048                 /*
1049                  * If read has been aborted, just return
1050                  * processReadAborted will be called later
1051                  */
1052                 if (abortRequested()) {
1053                     return;
1054                 }
1055                 // Skip filter byte and the remaining row bytes
1056                 pixelStream.skipBytes(1 + bytesPerRow);
1057             }
1058             return;
1059         }
1060 
1061         // Backwards map from destination pixels
1062         // (dstX = updateMinX + k*updateXStep)
1063         // to source pixels (sourceX), and then
1064         // to offset and skip in passRow (srcX and srcXStep)
1065         int sourceX =
1066             (updateMinX - destinationOffset.x)*sourceXSubsampling +
1067             sourceRegion.x;
1068         int srcX = (sourceX - xStart)/xStep;
1069 
1070         // Compute the step factor in the source
1071         int srcXStep = updateXStep*sourceXSubsampling/xStep;
1072 
1073         byte[] byteData = null;
1074         short[] shortData = null;
1075         byte[] curr = new byte[bytesPerRow];
1076         byte[] prior = new byte[bytesPerRow];
1077 
1078         // Create a 1-row tall Raster to hold the data
1079         WritableRaster passRow = createRaster(passWidth, 1, inputBands,
1080                                               eltsPerRow,
1081                                               bitDepth);
1082 
1083         // Create an array suitable for holding one pixel
1084         int[] ps = passRow.getPixel(0, 0, (int[])null);
1085 
1086         DataBuffer dataBuffer = passRow.getDataBuffer();
1087         int type = dataBuffer.getDataType();
1088         if (type == DataBuffer.TYPE_BYTE) {
1089             byteData = ((DataBufferByte)dataBuffer).getData();
1090         } else {
1091             shortData = ((DataBufferUShort)dataBuffer).getData();
1092         }
1093 
1094         processPassStarted(theImage,
1095                            passNum,
1096                            sourceMinProgressivePass,
1097                            sourceMaxProgressivePass,
1098                            updateMinX, updateMinY,
1099                            updateXStep, updateYStep,
1100                            destinationBands);
1101 
1102         // Handle source and destination bands
1103         if (sourceBands != null) {
1104             passRow = passRow.createWritableChild(0, 0,
1105                                                   passRow.getWidth(), 1,
1106                                                   0, 0,
1107                                                   sourceBands);
1108         }
1109         if (destinationBands != null) {
1110             imRas = imRas.createWritableChild(0, 0,
1111                                               imRas.getWidth(),
1112                                               imRas.getHeight(),
1113                                               0, 0,
1114                                               destinationBands);
1115         }
1116 
1117         // Determine if all of the relevant output bands have the
1118         // same bit depth as the source data
1119         boolean adjustBitDepths = false;
1120         int[] outputSampleSize = imRas.getSampleModel().getSampleSize();
1121         int numBands = outputSampleSize.length;
1122         for (int b = 0; b < numBands; b++) {
1123             if (outputSampleSize[b] != bitDepth) {
1124                 adjustBitDepths = true;
1125                 break;
1126             }
1127         }
1128 
1129         // If the bit depths differ, create a lookup table per band to perform
1130         // the conversion
1131         int[][] scale = null;
1132         if (adjustBitDepths) {
1133             int maxInSample = (1 << bitDepth) - 1;
1134             int halfMaxInSample = maxInSample/2;
1135             scale = new int[numBands][];
1136             for (int b = 0; b < numBands; b++) {
1137                 int maxOutSample = (1 << outputSampleSize[b]) - 1;
1138                 scale[b] = new int[maxInSample + 1];
1139                 for (int s = 0; s <= maxInSample; s++) {
1140                     scale[b][s] =
1141                         (s*maxOutSample + halfMaxInSample)/maxInSample;
1142                 }
1143             }
1144         }
1145 
1146         // Limit passRow to relevant area for the case where we
1147         // will can setRect to copy a contiguous span
1148         boolean useSetRect = srcXStep == 1 &&
1149             updateXStep == 1 &&
1150             !adjustBitDepths &&
1151             (imRas instanceof ByteInterleavedRaster);
1152 
1153         if (useSetRect) {
1154             passRow = passRow.createWritableChild(srcX, 0,
1155                                                   updateWidth, 1,
1156                                                   0, 0,
1157                                                   null);
1158         }
1159 
1160         // Decode the (sub)image row-by-row
1161         for (int srcY = 0; srcY < passHeight; srcY++) {
1162             // Update count of pixels read
1163             updateImageProgress(passWidth);
1164             /*
1165              * If read has been aborted, just return
1166              * processReadAborted will be called later
1167              */
1168             if (abortRequested()) {
1169                 return;
1170             }
1171             // Read the filter type byte and a row of data
1172             int filter = pixelStream.read();
1173             try {
1174                 // Swap curr and prior
1175                 byte[] tmp = prior;
1176                 prior = curr;
1177                 curr = tmp;
1178 
1179                 pixelStream.readFully(curr, 0, bytesPerRow);
1180             } catch (java.util.zip.ZipException ze) {
1181                 // TODO - throw a more meaningful exception
1182                 throw ze;
1183             }
1184 
1185             switch (filter) {
1186             case PNG_FILTER_NONE:
1187                 break;
1188             case PNG_FILTER_SUB:
1189                 decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel);
1190                 break;
1191             case PNG_FILTER_UP:
1192                 decodeUpFilter(curr, 0, prior, 0, bytesPerRow);
1193                 break;
1194             case PNG_FILTER_AVERAGE:
1195                 decodeAverageFilter(curr, 0, prior, 0, bytesPerRow,
1196                                     bytesPerPixel);
1197                 break;
1198             case PNG_FILTER_PAETH:
1199                 decodePaethFilter(curr, 0, prior, 0, bytesPerRow,
1200                                   bytesPerPixel);
1201                 break;
1202             default:
1203                 throw new IIOException("Unknown row filter type (= " +
1204                                        filter + ")!");
1205             }
1206 
1207             // Copy data into passRow byte by byte
1208             if (bitDepth < 16) {
1209                 System.arraycopy(curr, 0, byteData, 0, bytesPerRow);
1210             } else {
1211                 int idx = 0;
1212                 for (int j = 0; j < eltsPerRow; j++) {
1213                     shortData[j] =
1214                         (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff));
1215                     idx += 2;
1216                 }
1217             }
1218 
1219             // True Y position in source
1220             int sourceY = srcY*yStep + yStart;
1221             if ((sourceY >= sourceRegion.y) &&
1222                 (sourceY < sourceRegion.y + sourceRegion.height) &&
1223                 (((sourceY - sourceRegion.y) %
1224                   sourceYSubsampling) == 0)) {
1225 
1226                 int dstY = destinationOffset.y +
1227                     (sourceY - sourceRegion.y)/sourceYSubsampling;
1228                 if (dstY < dstMinY) {
1229                     continue;
1230                 }
1231                 if (dstY > dstMaxY) {
1232                     break;
1233                 }
1234 
1235                 if (useSetRect) {
1236                     imRas.setRect(updateMinX, dstY, passRow);
1237                 } else {
1238                     int newSrcX = srcX;
1239 
1240                     for (int dstX = updateMinX;
1241                          dstX < updateMinX + updateWidth;
1242                          dstX += updateXStep) {
1243 
1244                         passRow.getPixel(newSrcX, 0, ps);
1245                         if (adjustBitDepths) {
1246                             for (int b = 0; b < numBands; b++) {
1247                                 ps[b] = scale[b][ps[b]];
1248                             }
1249                         }
1250                         imRas.setPixel(dstX, dstY, ps);
1251                         newSrcX += srcXStep;
1252                     }
1253                 }
1254 
1255                 processImageUpdate(theImage,
1256                                    updateMinX, dstY,
1257                                    updateWidth, 1,
1258                                    updateXStep, updateYStep,
1259                                    destinationBands);
1260             }
1261         }
1262 
1263         processPassComplete(theImage);
1264     }
1265 
1266     private void decodeImage()
1267         throws IOException, IIOException  {
1268         int width = metadata.IHDR_width;
1269         int height = metadata.IHDR_height;
1270 
1271         this.pixelsDone = 0;
1272         this.totalPixels = width*height;
1273 
1274         if (metadata.IHDR_interlaceMethod == 0) {
1275             decodePass(0, 0, 0, 1, 1, width, height);
1276         } else {
1277             for (int i = 0; i <= sourceMaxProgressivePass; i++) {
1278                 int XOffset = adam7XOffset[i];
1279                 int YOffset = adam7YOffset[i];
1280                 int XSubsampling = adam7XSubsampling[i];
1281                 int YSubsampling = adam7YSubsampling[i];
1282                 int xbump = adam7XSubsampling[i + 1] - 1;
1283                 int ybump = adam7YSubsampling[i + 1] - 1;
1284 
1285                 if (i >= sourceMinProgressivePass) {
1286                     decodePass(i,
1287                                XOffset,
1288                                YOffset,
1289                                XSubsampling,
1290                                YSubsampling,
1291                                (width + xbump)/XSubsampling,
1292                                (height + ybump)/YSubsampling);
1293                 } else {
1294                     skipPass((width + xbump)/XSubsampling,
1295                              (height + ybump)/YSubsampling);
1296                 }
1297 
1298                 /*
1299                  * If read has been aborted, just return
1300                  * processReadAborted will be called later
1301                  */
1302                 if (abortRequested()) {
1303                     return;
1304                 }
1305             }
1306         }
1307     }
1308 
1309     private void readImage(ImageReadParam param) throws IIOException {
1310         readMetadata();
1311 
1312         int width = metadata.IHDR_width;
1313         int height = metadata.IHDR_height;
1314 
1315         // Init default values
1316         sourceXSubsampling = 1;
1317         sourceYSubsampling = 1;
1318         sourceMinProgressivePass = 0;
1319         sourceMaxProgressivePass = 6;
1320         sourceBands = null;
1321         destinationBands = null;
1322         destinationOffset = new Point(0, 0);
1323 
1324         // If an ImageReadParam is available, get values from it
1325         if (param != null) {
1326             sourceXSubsampling = param.getSourceXSubsampling();
1327             sourceYSubsampling = param.getSourceYSubsampling();
1328 
1329             sourceMinProgressivePass =
1330                 Math.max(param.getSourceMinProgressivePass(), 0);
1331             sourceMaxProgressivePass =
1332                 Math.min(param.getSourceMaxProgressivePass(), 6);
1333 
1334             sourceBands = param.getSourceBands();
1335             destinationBands = param.getDestinationBands();
1336             destinationOffset = param.getDestinationOffset();
1337         }
1338         Inflater inf = null;
1339         try {
1340             stream.seek(imageStartPosition);
1341 
1342             Enumeration<InputStream> e = new PNGImageDataEnumeration(stream);
1343             InputStream is = new SequenceInputStream(e);
1344 
1345            /* InflaterInputStream uses an Inflater instance which consumes
1346             * native (non-GC visible) resources. This is normally implicitly
1347             * freed when the stream is closed. However since the
1348             * InflaterInputStream wraps a client-supplied input stream,
1349             * we cannot close it.
1350             * But the app may depend on GC finalization to close the stream.
1351             * Therefore to ensure timely freeing of native resources we
1352             * explicitly create the Inflater instance and free its resources
1353             * when we are done with the InflaterInputStream by calling
1354             * inf.end();
1355             */
1356             inf = new Inflater();
1357             is = new InflaterInputStream(is, inf);
1358             is = new BufferedInputStream(is);
1359             this.pixelStream = new DataInputStream(is);
1360 
1361             /*
1362              * PNG spec declares that valid range for width
1363              * and height is [1, 2^31-1], so here we may fail to allocate
1364              * a buffer for destination image due to memory limitation.
1365              *
1366              * If the read operation triggers OutOfMemoryError, the same
1367              * will be wrapped in an IIOException at PNGImageReader.read
1368              * method.
1369              *
1370              * The recovery strategy for this case should be defined at
1371              * the level of application, so we will not try to estimate
1372              * the required amount of the memory and/or handle OOM in
1373              * any way.
1374              */
1375             theImage = getDestination(param,
1376                                       getImageTypes(0),
1377                                       width,
1378                                       height);
1379 
1380             Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1381             sourceRegion = new Rectangle(0, 0, 0, 0);
1382             computeRegions(param, width, height,
1383                            theImage,
1384                            sourceRegion, destRegion);
1385             destinationOffset.setLocation(destRegion.getLocation());
1386 
1387             // At this point the header has been read and we know
1388             // how many bands are in the image, so perform checking
1389             // of the read param.
1390             int colorType = metadata.IHDR_colorType;
1391             checkReadParamBandSettings(param,
1392                                        inputBandsForColorType[colorType],
1393                                       theImage.getSampleModel().getNumBands());
1394 
1395             clearAbortRequest();
1396             processImageStarted(0);
1397             if (abortRequested()) {
1398                 processReadAborted();
1399             } else {
1400                 decodeImage();
1401                 if (abortRequested()) {
1402                     processReadAborted();
1403                 } else {
1404                     processImageComplete();
1405                 }
1406             }
1407 
1408         } catch (IOException e) {
1409             throw new IIOException("Error reading PNG image data", e);
1410         } finally {
1411             if (inf != null) {
1412                 inf.end();
1413             }
1414         }
1415     }
1416 
1417     public int getNumImages(boolean allowSearch) throws IIOException {
1418         if (stream == null) {
1419             throw new IllegalStateException("No input source set!");
1420         }
1421         if (seekForwardOnly && allowSearch) {
1422             throw new IllegalStateException
1423                 ("seekForwardOnly and allowSearch can't both be true!");
1424         }
1425         return 1;
1426     }
1427 
1428     public int getWidth(int imageIndex) throws IIOException {
1429         if (imageIndex != 0) {
1430             throw new IndexOutOfBoundsException("imageIndex != 0!");
1431         }
1432 
1433         readHeader();
1434 
1435         return metadata.IHDR_width;
1436     }
1437 
1438     public int getHeight(int imageIndex) throws IIOException {
1439         if (imageIndex != 0) {
1440             throw new IndexOutOfBoundsException("imageIndex != 0!");
1441         }
1442 
1443         readHeader();
1444 
1445         return metadata.IHDR_height;
1446     }
1447 
1448     public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
1449       throws IIOException
1450     {
1451         if (imageIndex != 0) {
1452             throw new IndexOutOfBoundsException("imageIndex != 0!");
1453         }
1454 
1455         readHeader();
1456 
1457         ArrayList<ImageTypeSpecifier> l =
1458             new ArrayList<ImageTypeSpecifier>(1);
1459 
1460         ColorSpace rgb;
1461         ColorSpace gray;
1462         int[] bandOffsets;
1463 
1464         int bitDepth = metadata.IHDR_bitDepth;
1465         int colorType = metadata.IHDR_colorType;
1466 
1467         int dataType;
1468         if (bitDepth <= 8) {
1469             dataType = DataBuffer.TYPE_BYTE;
1470         } else {
1471             dataType = DataBuffer.TYPE_USHORT;
1472         }
1473 
1474         switch (colorType) {
1475         case PNG_COLOR_GRAY:
1476             // Packed grayscale
1477             l.add(ImageTypeSpecifier.createGrayscale(bitDepth,
1478                                                      dataType,
1479                                                      false));
1480             break;
1481 
1482         case PNG_COLOR_RGB:
1483             if (bitDepth == 8) {
1484                 // some standard types of buffered images
1485                 // which can be used as destination
1486                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1487                           BufferedImage.TYPE_3BYTE_BGR));
1488 
1489                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1490                           BufferedImage.TYPE_INT_RGB));
1491 
1492                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1493                           BufferedImage.TYPE_INT_BGR));
1494 
1495             }
1496             // Component R, G, B
1497             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1498             bandOffsets = new int[3];
1499             bandOffsets[0] = 0;
1500             bandOffsets[1] = 1;
1501             bandOffsets[2] = 2;
1502             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1503                                                        bandOffsets,
1504                                                        dataType,
1505                                                        false,
1506                                                        false));
1507             break;
1508 
1509         case PNG_COLOR_PALETTE:
1510             readMetadata(); // Need tRNS chunk
1511 
1512             /*
1513              * The PLTE chunk spec says:
1514              *
1515              * The number of palette entries must not exceed the range that
1516              * can be represented in the image bit depth (for example, 2^4 = 16
1517              * for a bit depth of 4). It is permissible to have fewer entries
1518              * than the bit depth would allow. In that case, any out-of-range
1519              * pixel value found in the image data is an error.
1520              *
1521              * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE
1522              *
1523              * Consequently, the case when the palette length is smaller than
1524              * 2^bitDepth is legal in the view of PNG spec.
1525              *
1526              * However the spec of createIndexed() method demands the exact
1527              * equality of the palette lengh and number of possible palette
1528              * entries (2^bitDepth).
1529              *
1530              * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed}
1531              *
1532              * In order to avoid this contradiction we need to extend the
1533              * palette arrays to the limit defined by the bitDepth.
1534              */
1535 
1536             int plength = 1 << bitDepth;
1537 
1538             byte[] red = metadata.PLTE_red;
1539             byte[] green = metadata.PLTE_green;
1540             byte[] blue = metadata.PLTE_blue;
1541 
1542             if (metadata.PLTE_red.length < plength) {
1543                 red = Arrays.copyOf(metadata.PLTE_red, plength);
1544                 Arrays.fill(red, metadata.PLTE_red.length, plength,
1545                             metadata.PLTE_red[metadata.PLTE_red.length - 1]);
1546 
1547                 green = Arrays.copyOf(metadata.PLTE_green, plength);
1548                 Arrays.fill(green, metadata.PLTE_green.length, plength,
1549                             metadata.PLTE_green[metadata.PLTE_green.length - 1]);
1550 
1551                 blue = Arrays.copyOf(metadata.PLTE_blue, plength);
1552                 Arrays.fill(blue, metadata.PLTE_blue.length, plength,
1553                             metadata.PLTE_blue[metadata.PLTE_blue.length - 1]);
1554 
1555             }
1556 
1557             // Alpha from tRNS chunk may have fewer entries than
1558             // the RGB LUTs from the PLTE chunk; if so, pad with
1559             // 255.
1560             byte[] alpha = null;
1561             if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) {
1562                 if (metadata.tRNS_alpha.length == red.length) {
1563                     alpha = metadata.tRNS_alpha;
1564                 } else {
1565                     alpha = Arrays.copyOf(metadata.tRNS_alpha, red.length);
1566                     Arrays.fill(alpha,
1567                                 metadata.tRNS_alpha.length,
1568                                 red.length, (byte)255);
1569                 }
1570             }
1571 
1572             l.add(ImageTypeSpecifier.createIndexed(red, green,
1573                                                    blue, alpha,
1574                                                    bitDepth,
1575                                                    DataBuffer.TYPE_BYTE));
1576             break;
1577 
1578         case PNG_COLOR_GRAY_ALPHA:
1579             // Component G, A
1580             gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
1581             bandOffsets = new int[2];
1582             bandOffsets[0] = 0;
1583             bandOffsets[1] = 1;
1584             l.add(ImageTypeSpecifier.createInterleaved(gray,
1585                                                        bandOffsets,
1586                                                        dataType,
1587                                                        true,
1588                                                        false));
1589             break;
1590 
1591         case PNG_COLOR_RGB_ALPHA:
1592             if (bitDepth == 8) {
1593                 // some standard types of buffered images
1594                 // wich can be used as destination
1595                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1596                           BufferedImage.TYPE_4BYTE_ABGR));
1597 
1598                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1599                           BufferedImage.TYPE_INT_ARGB));
1600             }
1601 
1602             // Component R, G, B, A (non-premultiplied)
1603             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1604             bandOffsets = new int[4];
1605             bandOffsets[0] = 0;
1606             bandOffsets[1] = 1;
1607             bandOffsets[2] = 2;
1608             bandOffsets[3] = 3;
1609 
1610             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1611                                                        bandOffsets,
1612                                                        dataType,
1613                                                        true,
1614                                                        false));
1615             break;
1616 
1617         default:
1618             break;
1619         }
1620 
1621         return l.iterator();
1622     }
1623 
1624     /*
1625      * Super class implementation uses first element
1626      * of image types list as raw image type.
1627      *
1628      * Also, super implementation uses first element of this list
1629      * as default destination type image read param does not specify
1630      * anything other.
1631      *
1632      * However, in case of RGB and RGBA color types, raw image type
1633      * produces buffered image of custom type. It causes some
1634      * performance degradation of subsequent rendering operations.
1635      *
1636      * To resolve this contradiction we put standard image types
1637      * at the first positions of image types list (to produce standard
1638      * images by default) and put raw image type (which is custom)
1639      * at the last position of this list.
1640      *
1641      * After this changes we should override getRawImageType()
1642      * to return last element of image types list.
1643      */
1644     public ImageTypeSpecifier getRawImageType(int imageIndex)
1645       throws IOException {
1646 
1647         Iterator<ImageTypeSpecifier> types = getImageTypes(imageIndex);
1648         ImageTypeSpecifier raw = null;
1649         do {
1650             raw = types.next();
1651         } while (types.hasNext());
1652         return raw;
1653     }
1654 
1655     public ImageReadParam getDefaultReadParam() {
1656         return new ImageReadParam();
1657     }
1658 
1659     public IIOMetadata getStreamMetadata()
1660         throws IIOException {
1661         return null;
1662     }
1663 
1664     public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
1665         if (imageIndex != 0) {
1666             throw new IndexOutOfBoundsException("imageIndex != 0!");
1667         }
1668         readMetadata();
1669         return metadata;
1670     }
1671 
1672     public BufferedImage read(int imageIndex, ImageReadParam param)
1673         throws IIOException {
1674         if (imageIndex != 0) {
1675             throw new IndexOutOfBoundsException("imageIndex != 0!");
1676         }
1677 
1678         try {
1679             readImage(param);
1680         } catch (IOException |
1681                  IllegalStateException |
1682                  IllegalArgumentException e)
1683         {
1684             throw e;
1685         } catch (Throwable e) {
1686             throw new IIOException("Caught exception during read: ", e);
1687         }
1688         return theImage;
1689     }
1690 
1691     public void reset() {
1692         super.reset();
1693         resetStreamSettings();
1694     }
1695 
1696     private void resetStreamSettings() {
1697         gotHeader = false;
1698         gotMetadata = false;
1699         metadata = null;
1700         pixelStream = null;
1701         imageStartPosition = -1L;
1702     }
1703 }
--- EOF ---