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