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