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