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