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