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