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