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