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