1 /* 2 * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.imageio.plugins.png; 27 28 import java.awt.Point; 29 import java.awt.Rectangle; 30 import java.awt.color.ColorSpace; 31 import java.awt.image.BufferedImage; 32 import java.awt.image.DataBuffer; 33 import java.awt.image.DataBufferByte; 34 import java.awt.image.DataBufferUShort; 35 import java.awt.image.Raster; 36 import java.awt.image.WritableRaster; 37 import java.io.BufferedInputStream; 38 import java.io.ByteArrayInputStream; 39 import java.io.DataInputStream; 40 import java.io.EOFException; 41 import java.io.InputStream; 42 import java.io.IOException; 43 import java.io.SequenceInputStream; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Enumeration; 47 import java.util.Iterator; 48 import java.util.zip.Inflater; 49 import java.util.zip.InflaterInputStream; 50 import javax.imageio.IIOException; 51 import javax.imageio.ImageReader; 52 import javax.imageio.ImageReadParam; 53 import javax.imageio.ImageTypeSpecifier; 54 import javax.imageio.metadata.IIOMetadata; 55 import javax.imageio.spi.ImageReaderSpi; 56 import javax.imageio.stream.ImageInputStream; 57 import com.sun.imageio.plugins.common.InputStreamAdapter; 58 import com.sun.imageio.plugins.common.ReaderUtil; 59 import com.sun.imageio.plugins.common.SubImageInputStream; 60 import java.io.ByteArrayOutputStream; 61 import sun.awt.image.ByteInterleavedRaster; 62 63 class PNGImageDataEnumeration implements Enumeration<InputStream> { 64 65 boolean firstTime = true; 66 ImageInputStream stream; 67 int length; 68 PNGImageDataEnumeration(ImageInputStream stream)69 public PNGImageDataEnumeration(ImageInputStream stream) 70 throws IOException { 71 this.stream = stream; 72 this.length = stream.readInt(); 73 int type = stream.readInt(); // skip chunk type 74 } 75 nextElement()76 public InputStream nextElement() { 77 try { 78 firstTime = false; 79 ImageInputStream iis = new SubImageInputStream(stream, length); 80 return new InputStreamAdapter(iis); 81 } catch (IOException e) { 82 return null; 83 } 84 } 85 hasMoreElements()86 public boolean hasMoreElements() { 87 if (firstTime) { 88 return true; 89 } 90 91 try { 92 int crc = stream.readInt(); 93 this.length = stream.readInt(); 94 int type = stream.readInt(); 95 if (type == PNGImageReader.IDAT_TYPE) { 96 return true; 97 } else { 98 return false; 99 } 100 } catch (IOException e) { 101 return false; 102 } 103 } 104 } 105 106 public class PNGImageReader extends ImageReader { 107 108 /* 109 * Note: The following chunk type constants are autogenerated. Each 110 * one is derived from the ASCII values of its 4-character name. For 111 * example, IHDR_TYPE is calculated as follows: 112 * ('I' << 24) | ('H' << 16) | ('D' << 8) | 'R' 113 */ 114 115 // Critical chunks 116 static final int IHDR_TYPE = 0x49484452; 117 static final int PLTE_TYPE = 0x504c5445; 118 static final int IDAT_TYPE = 0x49444154; 119 static final int IEND_TYPE = 0x49454e44; 120 121 // Ancillary chunks 122 static final int bKGD_TYPE = 0x624b4744; 123 static final int cHRM_TYPE = 0x6348524d; 124 static final int gAMA_TYPE = 0x67414d41; 125 static final int hIST_TYPE = 0x68495354; 126 static final int iCCP_TYPE = 0x69434350; 127 static final int iTXt_TYPE = 0x69545874; 128 static final int pHYs_TYPE = 0x70485973; 129 static final int sBIT_TYPE = 0x73424954; 130 static final int sPLT_TYPE = 0x73504c54; 131 static final int sRGB_TYPE = 0x73524742; 132 static final int tEXt_TYPE = 0x74455874; 133 static final int tIME_TYPE = 0x74494d45; 134 static final int tRNS_TYPE = 0x74524e53; 135 static final int zTXt_TYPE = 0x7a545874; 136 137 static final int PNG_COLOR_GRAY = 0; 138 static final int PNG_COLOR_RGB = 2; 139 static final int PNG_COLOR_PALETTE = 3; 140 static final int PNG_COLOR_GRAY_ALPHA = 4; 141 static final int PNG_COLOR_RGB_ALPHA = 6; 142 143 // The number of bands by PNG color type 144 static final int[] inputBandsForColorType = { 145 1, // gray 146 -1, // unused 147 3, // rgb 148 1, // palette 149 2, // gray + alpha 150 -1, // unused 151 4 // rgb + alpha 152 }; 153 154 static final int PNG_FILTER_NONE = 0; 155 static final int PNG_FILTER_SUB = 1; 156 static final int PNG_FILTER_UP = 2; 157 static final int PNG_FILTER_AVERAGE = 3; 158 static final int PNG_FILTER_PAETH = 4; 159 160 static final int[] adam7XOffset = { 0, 4, 0, 2, 0, 1, 0 }; 161 static final int[] adam7YOffset = { 0, 0, 4, 0, 2, 0, 1 }; 162 static final int[] adam7XSubsampling = { 8, 8, 4, 4, 2, 2, 1, 1 }; 163 static final int[] adam7YSubsampling = { 8, 8, 8, 4, 4, 2, 2, 1 }; 164 165 private static final boolean debug = true; 166 167 ImageInputStream stream = null; 168 169 boolean gotHeader = false; 170 boolean gotMetadata = false; 171 172 ImageReadParam lastParam = null; 173 174 long imageStartPosition = -1L; 175 176 Rectangle sourceRegion = null; 177 int sourceXSubsampling = -1; 178 int sourceYSubsampling = -1; 179 int sourceMinProgressivePass = 0; 180 int sourceMaxProgressivePass = 6; 181 int[] sourceBands = null; 182 int[] destinationBands = null; 183 Point destinationOffset = new Point(0, 0); 184 185 PNGMetadata metadata = new PNGMetadata(); 186 187 DataInputStream pixelStream = null; 188 189 BufferedImage theImage = null; 190 191 // The number of source pixels processed 192 int pixelsDone = 0; 193 194 // The total number of pixels in the source image 195 int totalPixels; 196 PNGImageReader(ImageReaderSpi originatingProvider)197 public PNGImageReader(ImageReaderSpi originatingProvider) { 198 super(originatingProvider); 199 } 200 setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)201 public void setInput(Object input, 202 boolean seekForwardOnly, 203 boolean ignoreMetadata) { 204 super.setInput(input, seekForwardOnly, ignoreMetadata); 205 this.stream = (ImageInputStream)input; // Always works 206 207 // Clear all values based on the previous stream contents 208 resetStreamSettings(); 209 } 210 readNullTerminatedString(String charset, int maxLen)211 private String readNullTerminatedString(String charset, int maxLen) throws IOException { 212 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 213 int b = 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 readHeader()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 parse_PLTE_chunk(int chunkLength)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 parse_bKGD_chunk()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 parse_cHRM_chunk()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 parse_gAMA_chunk()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 parse_hIST_chunk(int chunkLength)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 parse_iCCP_chunk(int chunkLength)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 parse_iTXt_chunk(int chunkLength)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 parse_pHYs_chunk()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 parse_sBIT_chunk()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 parse_sPLT_chunk(int chunkLength)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 parse_sRGB_chunk()568 private void parse_sRGB_chunk() throws IOException { 569 metadata.sRGB_renderingIntent = stream.readUnsignedByte(); 570 571 metadata.sRGB_present = true; 572 } 573 parse_tEXt_chunk(int chunkLength)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 parse_tIME_chunk()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 parse_tRNS_chunk(int chunkLength)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 inflate(byte[] b)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 parse_zTXt_chunk(int chunkLength)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 readMetadata()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 decodeSubFilter(byte[] curr, int coff, int count, int bpp)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 decodeUpFilter(byte[] curr, int coff, byte[] prev, int poff, int count)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 decodeAverageFilter(byte[] curr, int coff, byte[] prev, int poff, int count, int bpp)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 paethPredictor(int a, int b, int c)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 decodePaethFilter(byte[] curr, int coff, byte[] prev, int poff, int count, int bpp)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 createRaster(int width, int height, int bands, int scanlineStride, int bitDepth)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 skipPass(int passWidth, int passHeight)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 updateImageProgress(int newPixels)1056 private void updateImageProgress(int newPixels) { 1057 pixelsDone += newPixels; 1058 processImageProgress(100.0F*pixelsDone/totalPixels); 1059 } 1060 decodePass(int passNum, int xStart, int yStart, int xStep, int yStep, int passWidth, int passHeight)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 decodeImage()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 readImage(ImageReadParam param)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 getNumImages(boolean allowSearch)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 getWidth(int imageIndex)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 getHeight(int imageIndex)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 getImageTypes(int imageIndex)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 */ getRawImageType(int imageIndex)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 getDefaultReadParam()1799 public ImageReadParam getDefaultReadParam() { 1800 return new ImageReadParam(); 1801 } 1802 getStreamMetadata()1803 public IIOMetadata getStreamMetadata() 1804 throws IIOException { 1805 return null; 1806 } 1807 getImageMetadata(int imageIndex)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 read(int imageIndex, ImageReadParam param)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 reset()1835 public void reset() { 1836 super.reset(); 1837 resetStreamSettings(); 1838 } 1839 resetStreamSettings()1840 private void resetStreamSettings() { 1841 gotHeader = false; 1842 gotMetadata = false; 1843 metadata = null; 1844 pixelStream = null; 1845 imageStartPosition = -1L; 1846 } 1847 } 1848