1 /* 2 * Copyright (c) 2000, 2013, 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 metadata.iCCP_profileName = keyword; 432 433 metadata.iCCP_compressionMethod = stream.readUnsignedByte(); 434 435 byte[] compressedProfile = 436 new byte[chunkLength - keyword.length() - 2]; 437 stream.readFully(compressedProfile); 438 metadata.iCCP_compressedProfile = compressedProfile; 439 440 metadata.iCCP_present = true; 441 } 442 443 private void parse_iTXt_chunk(int chunkLength) throws IOException { 444 long chunkStart = stream.getStreamPosition(); 445 446 String keyword = readNullTerminatedString("ISO-8859-1", 80); 447 metadata.iTXt_keyword.add(keyword); 448 449 int compressionFlag = stream.readUnsignedByte(); 450 metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1)); 451 452 int compressionMethod = stream.readUnsignedByte(); 453 metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); 454 455 String languageTag = readNullTerminatedString("UTF8", 80); 456 metadata.iTXt_languageTag.add(languageTag); 457 458 long pos = stream.getStreamPosition(); 459 int maxLen = (int)(chunkStart + chunkLength - pos); 460 String translatedKeyword = 461 readNullTerminatedString("UTF8", maxLen); 462 metadata.iTXt_translatedKeyword.add(translatedKeyword); 463 464 String text; 465 pos = stream.getStreamPosition(); 466 byte[] b = new byte[(int)(chunkStart + chunkLength - pos)]; 467 stream.readFully(b); 468 469 if (compressionFlag == 1) { // Decompress the text 470 text = new String(inflate(b), "UTF8"); 471 } else { 472 text = new String(b, "UTF8"); 473 } 474 metadata.iTXt_text.add(text); 475 } 476 477 private void parse_pHYs_chunk() throws IOException { 478 metadata.pHYs_pixelsPerUnitXAxis = stream.readInt(); 479 metadata.pHYs_pixelsPerUnitYAxis = stream.readInt(); 480 metadata.pHYs_unitSpecifier = stream.readUnsignedByte(); 481 482 metadata.pHYs_present = true; 483 } 484 485 private void parse_sBIT_chunk() throws IOException { 486 int colorType = metadata.IHDR_colorType; 487 if (colorType == PNG_COLOR_GRAY || 488 colorType == PNG_COLOR_GRAY_ALPHA) { 489 metadata.sBIT_grayBits = stream.readUnsignedByte(); 490 } else if (colorType == PNG_COLOR_RGB || 491 colorType == PNG_COLOR_PALETTE || 492 colorType == PNG_COLOR_RGB_ALPHA) { 493 metadata.sBIT_redBits = stream.readUnsignedByte(); 494 metadata.sBIT_greenBits = stream.readUnsignedByte(); 495 metadata.sBIT_blueBits = stream.readUnsignedByte(); 496 } 497 498 if (colorType == PNG_COLOR_GRAY_ALPHA || 499 colorType == PNG_COLOR_RGB_ALPHA) { 500 metadata.sBIT_alphaBits = stream.readUnsignedByte(); 501 } 502 503 metadata.sBIT_colorType = colorType; 504 metadata.sBIT_present = true; 505 } 506 507 private void parse_sPLT_chunk(int chunkLength) 508 throws IOException, IIOException { 509 metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80); 510 chunkLength -= metadata.sPLT_paletteName.length() + 1; 511 512 int sampleDepth = stream.readUnsignedByte(); 513 metadata.sPLT_sampleDepth = sampleDepth; 514 515 int numEntries = chunkLength/(4*(sampleDepth/8) + 2); 516 metadata.sPLT_red = new int[numEntries]; 517 metadata.sPLT_green = new int[numEntries]; 518 metadata.sPLT_blue = new int[numEntries]; 519 metadata.sPLT_alpha = new int[numEntries]; 520 metadata.sPLT_frequency = new int[numEntries]; 521 522 if (sampleDepth == 8) { 523 for (int i = 0; i < numEntries; i++) { 524 metadata.sPLT_red[i] = stream.readUnsignedByte(); 525 metadata.sPLT_green[i] = stream.readUnsignedByte(); 526 metadata.sPLT_blue[i] = stream.readUnsignedByte(); 527 metadata.sPLT_alpha[i] = stream.readUnsignedByte(); 528 metadata.sPLT_frequency[i] = stream.readUnsignedShort(); 529 } 530 } else if (sampleDepth == 16) { 531 for (int i = 0; i < numEntries; i++) { 532 metadata.sPLT_red[i] = stream.readUnsignedShort(); 533 metadata.sPLT_green[i] = stream.readUnsignedShort(); 534 metadata.sPLT_blue[i] = stream.readUnsignedShort(); 535 metadata.sPLT_alpha[i] = stream.readUnsignedShort(); 536 metadata.sPLT_frequency[i] = stream.readUnsignedShort(); 537 } 538 } else { 539 throw new IIOException("sPLT sample depth not 8 or 16!"); 540 } 541 542 metadata.sPLT_present = true; 543 } 544 545 private void parse_sRGB_chunk() throws IOException { 546 metadata.sRGB_renderingIntent = stream.readUnsignedByte(); 547 548 metadata.sRGB_present = true; 549 } 550 551 private void parse_tEXt_chunk(int chunkLength) throws IOException { 552 String keyword = readNullTerminatedString("ISO-8859-1", 80); 553 metadata.tEXt_keyword.add(keyword); 554 555 byte[] b = new byte[chunkLength - keyword.length() - 1]; 556 stream.readFully(b); 557 metadata.tEXt_text.add(new String(b, "ISO-8859-1")); 558 } 559 560 private void parse_tIME_chunk() throws IOException { 561 metadata.tIME_year = stream.readUnsignedShort(); 562 metadata.tIME_month = stream.readUnsignedByte(); 563 metadata.tIME_day = stream.readUnsignedByte(); 564 metadata.tIME_hour = stream.readUnsignedByte(); 565 metadata.tIME_minute = stream.readUnsignedByte(); 566 metadata.tIME_second = stream.readUnsignedByte(); 567 568 metadata.tIME_present = true; 569 } 570 571 private void parse_tRNS_chunk(int chunkLength) throws IOException { 572 int colorType = metadata.IHDR_colorType; 573 if (colorType == PNG_COLOR_PALETTE) { 574 if (!metadata.PLTE_present) { 575 processWarningOccurred( 576 "tRNS chunk without prior PLTE chunk, ignoring it."); 577 return; 578 } 579 580 // Alpha table may have fewer entries than RGB palette 581 int maxEntries = metadata.PLTE_red.length; 582 int numEntries = chunkLength; 583 if (numEntries > maxEntries) { 584 processWarningOccurred( 585 "tRNS chunk has more entries than prior PLTE chunk, ignoring extras."); 586 numEntries = maxEntries; 587 } 588 metadata.tRNS_alpha = new byte[numEntries]; 589 metadata.tRNS_colorType = PNG_COLOR_PALETTE; 590 stream.read(metadata.tRNS_alpha, 0, numEntries); 591 stream.skipBytes(chunkLength - numEntries); 592 } else if (colorType == PNG_COLOR_GRAY) { 593 if (chunkLength != 2) { 594 processWarningOccurred( 595 "tRNS chunk for gray image must have length 2, ignoring chunk."); 596 stream.skipBytes(chunkLength); 597 return; 598 } 599 metadata.tRNS_gray = stream.readUnsignedShort(); 600 metadata.tRNS_colorType = PNG_COLOR_GRAY; 601 } else if (colorType == PNG_COLOR_RGB) { 602 if (chunkLength != 6) { 603 processWarningOccurred( 604 "tRNS chunk for RGB image must have length 6, ignoring chunk."); 605 stream.skipBytes(chunkLength); 606 return; 607 } 608 metadata.tRNS_red = stream.readUnsignedShort(); 609 metadata.tRNS_green = stream.readUnsignedShort(); 610 metadata.tRNS_blue = stream.readUnsignedShort(); 611 metadata.tRNS_colorType = PNG_COLOR_RGB; 612 } else { 613 processWarningOccurred( 614 "Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it."); 615 return; 616 } 617 618 metadata.tRNS_present = true; 619 } 620 621 private static byte[] inflate(byte[] b) throws IOException { 622 InputStream bais = new ByteArrayInputStream(b); 623 InputStream iis = new InflaterInputStream(bais); 624 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 625 626 int c; 627 try { 628 while ((c = iis.read()) != -1) { 629 baos.write(c); 630 } 631 } finally { 632 iis.close(); 633 } 634 return baos.toByteArray(); 635 } 636 637 private void parse_zTXt_chunk(int chunkLength) throws IOException { 638 String keyword = readNullTerminatedString("ISO-8859-1", 80); 639 metadata.zTXt_keyword.add(keyword); 640 641 int method = stream.readUnsignedByte(); 642 metadata.zTXt_compressionMethod.add(method); 643 644 byte[] b = new byte[chunkLength - keyword.length() - 2]; 645 stream.readFully(b); 646 metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1")); 647 } 648 649 private void readMetadata() throws IIOException { 650 if (gotMetadata) { 651 return; 652 } 653 654 readHeader(); 655 656 /* 657 * Optimization: We can skip the remaining metadata if the 658 * ignoreMetadata flag is set, and only if this is not a palette 659 * image (in that case, we need to read the metadata to get the 660 * tRNS chunk, which is needed for the getImageTypes() method). 661 */ 662 int colorType = metadata.IHDR_colorType; 663 if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) { 664 try { 665 while (true) { 666 int chunkLength = stream.readInt(); 667 668 // verify the chunk length first 669 if (chunkLength < 0 || chunkLength + 4 < 0) { 670 throw new IIOException("Invalid chunk length " + chunkLength); 671 } 672 673 int chunkType = stream.readInt(); 674 675 if (chunkType == IDAT_TYPE) { 676 // We've reached the image data 677 stream.skipBytes(-8); 678 imageStartPosition = stream.getStreamPosition(); 679 break; 680 } else { 681 // Skip the chunk plus the 4 CRC bytes that follow 682 stream.skipBytes(chunkLength + 4); 683 } 684 } 685 } catch (IOException e) { 686 throw new IIOException("Error skipping PNG metadata", e); 687 } 688 689 gotMetadata = true; 690 return; 691 } 692 693 try { 694 loop: while (true) { 695 int chunkLength = stream.readInt(); 696 int chunkType = stream.readInt(); 697 int chunkCRC; 698 699 // verify the chunk length 700 if (chunkLength < 0) { 701 throw new IIOException("Invalid chunk length " + chunkLength); 702 }; 703 704 try { 705 stream.mark(); 706 stream.seek(stream.getStreamPosition() + chunkLength); 707 chunkCRC = stream.readInt(); 708 stream.reset(); 709 } catch (IOException e) { 710 throw new IIOException("Invalid chunk length " + chunkLength); 711 } 712 713 switch (chunkType) { 714 case IDAT_TYPE: 715 // If chunk type is 'IDAT', we've reached the image data. 716 stream.skipBytes(-8); 717 imageStartPosition = stream.getStreamPosition(); 718 break loop; 719 case PLTE_TYPE: 720 parse_PLTE_chunk(chunkLength); 721 break; 722 case bKGD_TYPE: 723 parse_bKGD_chunk(); 724 break; 725 case cHRM_TYPE: 726 parse_cHRM_chunk(); 727 break; 728 case gAMA_TYPE: 729 parse_gAMA_chunk(); 730 break; 731 case hIST_TYPE: 732 parse_hIST_chunk(chunkLength); 733 break; 734 case iCCP_TYPE: 735 parse_iCCP_chunk(chunkLength); 736 break; 737 case iTXt_TYPE: 738 parse_iTXt_chunk(chunkLength); 739 break; 740 case pHYs_TYPE: 741 parse_pHYs_chunk(); 742 break; 743 case sBIT_TYPE: 744 parse_sBIT_chunk(); 745 break; 746 case sPLT_TYPE: 747 parse_sPLT_chunk(chunkLength); 748 break; 749 case sRGB_TYPE: 750 parse_sRGB_chunk(); 751 break; 752 case tEXt_TYPE: 753 parse_tEXt_chunk(chunkLength); 754 break; 755 case tIME_TYPE: 756 parse_tIME_chunk(); 757 break; 758 case tRNS_TYPE: 759 parse_tRNS_chunk(chunkLength); 760 break; 761 case zTXt_TYPE: 762 parse_zTXt_chunk(chunkLength); 763 break; 764 default: 765 // Read an unknown chunk 766 byte[] b = new byte[chunkLength]; 767 stream.readFully(b); 768 769 StringBuilder chunkName = new StringBuilder(4); 770 chunkName.append((char)(chunkType >>> 24)); 771 chunkName.append((char)((chunkType >> 16) & 0xff)); 772 chunkName.append((char)((chunkType >> 8) & 0xff)); 773 chunkName.append((char)(chunkType & 0xff)); 774 775 int ancillaryBit = chunkType >>> 28; 776 if (ancillaryBit == 0) { 777 processWarningOccurred( 778 "Encountered unknown chunk with critical bit set!"); 779 } 780 781 metadata.unknownChunkType.add(chunkName.toString()); 782 metadata.unknownChunkData.add(b); 783 break; 784 } 785 786 // double check whether all chunk data were consumed 787 if (chunkCRC != stream.readInt()) { 788 throw new IIOException("Failed to read a chunk of type " + 789 chunkType); 790 } 791 stream.flushBefore(stream.getStreamPosition()); 792 } 793 } catch (IOException e) { 794 throw new IIOException("Error reading PNG metadata", e); 795 } 796 797 gotMetadata = true; 798 } 799 800 // Data filtering methods 801 802 private static void decodeSubFilter(byte[] curr, int coff, int count, 803 int bpp) { 804 for (int i = bpp; i < count; i++) { 805 int val; 806 807 val = curr[i + coff] & 0xff; 808 val += curr[i + coff - bpp] & 0xff; 809 810 curr[i + coff] = (byte)val; 811 } 812 } 813 814 private static void decodeUpFilter(byte[] curr, int coff, 815 byte[] prev, int poff, 816 int count) { 817 for (int i = 0; i < count; i++) { 818 int raw = curr[i + coff] & 0xff; 819 int prior = prev[i + poff] & 0xff; 820 821 curr[i + coff] = (byte)(raw + prior); 822 } 823 } 824 825 private static void decodeAverageFilter(byte[] curr, int coff, 826 byte[] prev, int poff, 827 int count, int bpp) { 828 int raw, priorPixel, priorRow; 829 830 for (int i = 0; i < bpp; i++) { 831 raw = curr[i + coff] & 0xff; 832 priorRow = prev[i + poff] & 0xff; 833 834 curr[i + coff] = (byte)(raw + priorRow/2); 835 } 836 837 for (int i = bpp; i < count; i++) { 838 raw = curr[i + coff] & 0xff; 839 priorPixel = curr[i + coff - bpp] & 0xff; 840 priorRow = prev[i + poff] & 0xff; 841 842 curr[i + coff] = (byte)(raw + (priorPixel + priorRow)/2); 843 } 844 } 845 846 private static int paethPredictor(int a, int b, int c) { 847 int p = a + b - c; 848 int pa = Math.abs(p - a); 849 int pb = Math.abs(p - b); 850 int pc = Math.abs(p - c); 851 852 if ((pa <= pb) && (pa <= pc)) { 853 return a; 854 } else if (pb <= pc) { 855 return b; 856 } else { 857 return c; 858 } 859 } 860 861 private static void decodePaethFilter(byte[] curr, int coff, 862 byte[] prev, int poff, 863 int count, int bpp) { 864 int raw, priorPixel, priorRow, priorRowPixel; 865 866 for (int i = 0; i < bpp; i++) { 867 raw = curr[i + coff] & 0xff; 868 priorRow = prev[i + poff] & 0xff; 869 870 curr[i + coff] = (byte)(raw + priorRow); 871 } 872 873 for (int i = bpp; i < count; i++) { 874 raw = curr[i + coff] & 0xff; 875 priorPixel = curr[i + coff - bpp] & 0xff; 876 priorRow = prev[i + poff] & 0xff; 877 priorRowPixel = prev[i + poff - bpp] & 0xff; 878 879 curr[i + coff] = (byte)(raw + paethPredictor(priorPixel, 880 priorRow, 881 priorRowPixel)); 882 } 883 } 884 885 private static final int[][] bandOffsets = { 886 null, 887 { 0 }, // G 888 { 0, 1 }, // GA in GA order 889 { 0, 1, 2 }, // RGB in RGB order 890 { 0, 1, 2, 3 } // RGBA in RGBA order 891 }; 892 893 private WritableRaster createRaster(int width, int height, int bands, 894 int scanlineStride, 895 int bitDepth) { 896 897 DataBuffer dataBuffer; 898 WritableRaster ras = null; 899 Point origin = new Point(0, 0); 900 if ((bitDepth < 8) && (bands == 1)) { 901 dataBuffer = new DataBufferByte(height*scanlineStride); 902 ras = Raster.createPackedRaster(dataBuffer, 903 width, height, 904 bitDepth, 905 origin); 906 } else if (bitDepth <= 8) { 907 dataBuffer = new DataBufferByte(height*scanlineStride); 908 ras = Raster.createInterleavedRaster(dataBuffer, 909 width, height, 910 scanlineStride, 911 bands, 912 bandOffsets[bands], 913 origin); 914 } else { 915 dataBuffer = new DataBufferUShort(height*scanlineStride); 916 ras = Raster.createInterleavedRaster(dataBuffer, 917 width, height, 918 scanlineStride, 919 bands, 920 bandOffsets[bands], 921 origin); 922 } 923 924 return ras; 925 } 926 927 private void skipPass(int passWidth, int passHeight) 928 throws IOException, IIOException { 929 if ((passWidth == 0) || (passHeight == 0)) { 930 return; 931 } 932 933 int inputBands = inputBandsForColorType[metadata.IHDR_colorType]; 934 int bytesPerRow = (inputBands*passWidth*metadata.IHDR_bitDepth + 7)/8; 935 936 // Read the image row-by-row 937 for (int srcY = 0; srcY < passHeight; srcY++) { 938 // Skip filter byte and the remaining row bytes 939 pixelStream.skipBytes(1 + bytesPerRow); 940 941 // If read has been aborted, just return 942 // processReadAborted will be called later 943 if (abortRequested()) { 944 return; 945 } 946 } 947 } 948 949 private void updateImageProgress(int newPixels) { 950 pixelsDone += newPixels; 951 processImageProgress(100.0F*pixelsDone/totalPixels); 952 } 953 954 private void decodePass(int passNum, 955 int xStart, int yStart, 956 int xStep, int yStep, 957 int passWidth, int passHeight) throws IOException { 958 959 if ((passWidth == 0) || (passHeight == 0)) { 960 return; 961 } 962 963 WritableRaster imRas = theImage.getWritableTile(0, 0); 964 int dstMinX = imRas.getMinX(); 965 int dstMaxX = dstMinX + imRas.getWidth() - 1; 966 int dstMinY = imRas.getMinY(); 967 int dstMaxY = dstMinY + imRas.getHeight() - 1; 968 969 // Determine which pixels will be updated in this pass 970 int[] vals = 971 ReaderUtil.computeUpdatedPixels(sourceRegion, 972 destinationOffset, 973 dstMinX, dstMinY, 974 dstMaxX, dstMaxY, 975 sourceXSubsampling, 976 sourceYSubsampling, 977 xStart, yStart, 978 passWidth, passHeight, 979 xStep, yStep); 980 int updateMinX = vals[0]; 981 int updateMinY = vals[1]; 982 int updateWidth = vals[2]; 983 int updateXStep = vals[4]; 984 int updateYStep = vals[5]; 985 986 int bitDepth = metadata.IHDR_bitDepth; 987 int inputBands = inputBandsForColorType[metadata.IHDR_colorType]; 988 int bytesPerPixel = (bitDepth == 16) ? 2 : 1; 989 bytesPerPixel *= inputBands; 990 991 int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; 992 int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow; 993 994 // If no pixels need updating, just skip the input data 995 if (updateWidth == 0) { 996 for (int srcY = 0; srcY < passHeight; srcY++) { 997 // Update count of pixels read 998 updateImageProgress(passWidth); 999 // Skip filter byte and the remaining row bytes 1000 pixelStream.skipBytes(1 + bytesPerRow); 1001 } 1002 return; 1003 } 1004 1005 // Backwards map from destination pixels 1006 // (dstX = updateMinX + k*updateXStep) 1007 // to source pixels (sourceX), and then 1008 // to offset and skip in passRow (srcX and srcXStep) 1009 int sourceX = 1010 (updateMinX - destinationOffset.x)*sourceXSubsampling + 1011 sourceRegion.x; 1012 int srcX = (sourceX - xStart)/xStep; 1013 1014 // Compute the step factor in the source 1015 int srcXStep = updateXStep*sourceXSubsampling/xStep; 1016 1017 byte[] byteData = null; 1018 short[] shortData = null; 1019 byte[] curr = new byte[bytesPerRow]; 1020 byte[] prior = new byte[bytesPerRow]; 1021 1022 // Create a 1-row tall Raster to hold the data 1023 WritableRaster passRow = createRaster(passWidth, 1, inputBands, 1024 eltsPerRow, 1025 bitDepth); 1026 1027 // Create an array suitable for holding one pixel 1028 int[] ps = passRow.getPixel(0, 0, (int[])null); 1029 1030 DataBuffer dataBuffer = passRow.getDataBuffer(); 1031 int type = dataBuffer.getDataType(); 1032 if (type == DataBuffer.TYPE_BYTE) { 1033 byteData = ((DataBufferByte)dataBuffer).getData(); 1034 } else { 1035 shortData = ((DataBufferUShort)dataBuffer).getData(); 1036 } 1037 1038 processPassStarted(theImage, 1039 passNum, 1040 sourceMinProgressivePass, 1041 sourceMaxProgressivePass, 1042 updateMinX, updateMinY, 1043 updateXStep, updateYStep, 1044 destinationBands); 1045 1046 // Handle source and destination bands 1047 if (sourceBands != null) { 1048 passRow = passRow.createWritableChild(0, 0, 1049 passRow.getWidth(), 1, 1050 0, 0, 1051 sourceBands); 1052 } 1053 if (destinationBands != null) { 1054 imRas = imRas.createWritableChild(0, 0, 1055 imRas.getWidth(), 1056 imRas.getHeight(), 1057 0, 0, 1058 destinationBands); 1059 } 1060 1061 // Determine if all of the relevant output bands have the 1062 // same bit depth as the source data 1063 boolean adjustBitDepths = false; 1064 int[] outputSampleSize = imRas.getSampleModel().getSampleSize(); 1065 int numBands = outputSampleSize.length; 1066 for (int b = 0; b < numBands; b++) { 1067 if (outputSampleSize[b] != bitDepth) { 1068 adjustBitDepths = true; 1069 break; 1070 } 1071 } 1072 1073 // If the bit depths differ, create a lookup table per band to perform 1074 // the conversion 1075 int[][] scale = null; 1076 if (adjustBitDepths) { 1077 int maxInSample = (1 << bitDepth) - 1; 1078 int halfMaxInSample = maxInSample/2; 1079 scale = new int[numBands][]; 1080 for (int b = 0; b < numBands; b++) { 1081 int maxOutSample = (1 << outputSampleSize[b]) - 1; 1082 scale[b] = new int[maxInSample + 1]; 1083 for (int s = 0; s <= maxInSample; s++) { 1084 scale[b][s] = 1085 (s*maxOutSample + halfMaxInSample)/maxInSample; 1086 } 1087 } 1088 } 1089 1090 // Limit passRow to relevant area for the case where we 1091 // will can setRect to copy a contiguous span 1092 boolean useSetRect = srcXStep == 1 && 1093 updateXStep == 1 && 1094 !adjustBitDepths && 1095 (imRas instanceof ByteInterleavedRaster); 1096 1097 if (useSetRect) { 1098 passRow = passRow.createWritableChild(srcX, 0, 1099 updateWidth, 1, 1100 0, 0, 1101 null); 1102 } 1103 1104 // Decode the (sub)image row-by-row 1105 for (int srcY = 0; srcY < passHeight; srcY++) { 1106 // Update count of pixels read 1107 updateImageProgress(passWidth); 1108 1109 // Read the filter type byte and a row of data 1110 int filter = pixelStream.read(); 1111 try { 1112 // Swap curr and prior 1113 byte[] tmp = prior; 1114 prior = curr; 1115 curr = tmp; 1116 1117 pixelStream.readFully(curr, 0, bytesPerRow); 1118 } catch (java.util.zip.ZipException ze) { 1119 // TODO - throw a more meaningful exception 1120 throw ze; 1121 } 1122 1123 switch (filter) { 1124 case PNG_FILTER_NONE: 1125 break; 1126 case PNG_FILTER_SUB: 1127 decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel); 1128 break; 1129 case PNG_FILTER_UP: 1130 decodeUpFilter(curr, 0, prior, 0, bytesPerRow); 1131 break; 1132 case PNG_FILTER_AVERAGE: 1133 decodeAverageFilter(curr, 0, prior, 0, bytesPerRow, 1134 bytesPerPixel); 1135 break; 1136 case PNG_FILTER_PAETH: 1137 decodePaethFilter(curr, 0, prior, 0, bytesPerRow, 1138 bytesPerPixel); 1139 break; 1140 default: 1141 throw new IIOException("Unknown row filter type (= " + 1142 filter + ")!"); 1143 } 1144 1145 // Copy data into passRow byte by byte 1146 if (bitDepth < 16) { 1147 System.arraycopy(curr, 0, byteData, 0, bytesPerRow); 1148 } else { 1149 int idx = 0; 1150 for (int j = 0; j < eltsPerRow; j++) { 1151 shortData[j] = 1152 (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff)); 1153 idx += 2; 1154 } 1155 } 1156 1157 // True Y position in source 1158 int sourceY = srcY*yStep + yStart; 1159 if ((sourceY >= sourceRegion.y) && 1160 (sourceY < sourceRegion.y + sourceRegion.height) && 1161 (((sourceY - sourceRegion.y) % 1162 sourceYSubsampling) == 0)) { 1163 1164 int dstY = destinationOffset.y + 1165 (sourceY - sourceRegion.y)/sourceYSubsampling; 1166 if (dstY < dstMinY) { 1167 continue; 1168 } 1169 if (dstY > dstMaxY) { 1170 break; 1171 } 1172 1173 if (useSetRect) { 1174 imRas.setRect(updateMinX, dstY, passRow); 1175 } else { 1176 int newSrcX = srcX; 1177 1178 for (int dstX = updateMinX; 1179 dstX < updateMinX + updateWidth; 1180 dstX += updateXStep) { 1181 1182 passRow.getPixel(newSrcX, 0, ps); 1183 if (adjustBitDepths) { 1184 for (int b = 0; b < numBands; b++) { 1185 ps[b] = scale[b][ps[b]]; 1186 } 1187 } 1188 imRas.setPixel(dstX, dstY, ps); 1189 newSrcX += srcXStep; 1190 } 1191 } 1192 1193 processImageUpdate(theImage, 1194 updateMinX, dstY, 1195 updateWidth, 1, 1196 updateXStep, updateYStep, 1197 destinationBands); 1198 1199 // If read has been aborted, just return 1200 // processReadAborted will be called later 1201 if (abortRequested()) { 1202 return; 1203 } 1204 } 1205 } 1206 1207 processPassComplete(theImage); 1208 } 1209 1210 private void decodeImage() 1211 throws IOException, IIOException { 1212 int width = metadata.IHDR_width; 1213 int height = metadata.IHDR_height; 1214 1215 this.pixelsDone = 0; 1216 this.totalPixels = width*height; 1217 1218 clearAbortRequest(); 1219 1220 if (metadata.IHDR_interlaceMethod == 0) { 1221 decodePass(0, 0, 0, 1, 1, width, height); 1222 } else { 1223 for (int i = 0; i <= sourceMaxProgressivePass; i++) { 1224 int XOffset = adam7XOffset[i]; 1225 int YOffset = adam7YOffset[i]; 1226 int XSubsampling = adam7XSubsampling[i]; 1227 int YSubsampling = adam7YSubsampling[i]; 1228 int xbump = adam7XSubsampling[i + 1] - 1; 1229 int ybump = adam7YSubsampling[i + 1] - 1; 1230 1231 if (i >= sourceMinProgressivePass) { 1232 decodePass(i, 1233 XOffset, 1234 YOffset, 1235 XSubsampling, 1236 YSubsampling, 1237 (width + xbump)/XSubsampling, 1238 (height + ybump)/YSubsampling); 1239 } else { 1240 skipPass((width + xbump)/XSubsampling, 1241 (height + ybump)/YSubsampling); 1242 } 1243 1244 // If read has been aborted, just return 1245 // processReadAborted will be called later 1246 if (abortRequested()) { 1247 return; 1248 } 1249 } 1250 } 1251 } 1252 1253 private void readImage(ImageReadParam param) throws IIOException { 1254 readMetadata(); 1255 1256 int width = metadata.IHDR_width; 1257 int height = metadata.IHDR_height; 1258 1259 // Init default values 1260 sourceXSubsampling = 1; 1261 sourceYSubsampling = 1; 1262 sourceMinProgressivePass = 0; 1263 sourceMaxProgressivePass = 6; 1264 sourceBands = null; 1265 destinationBands = null; 1266 destinationOffset = new Point(0, 0); 1267 1268 // If an ImageReadParam is available, get values from it 1269 if (param != null) { 1270 sourceXSubsampling = param.getSourceXSubsampling(); 1271 sourceYSubsampling = param.getSourceYSubsampling(); 1272 1273 sourceMinProgressivePass = 1274 Math.max(param.getSourceMinProgressivePass(), 0); 1275 sourceMaxProgressivePass = 1276 Math.min(param.getSourceMaxProgressivePass(), 6); 1277 1278 sourceBands = param.getSourceBands(); 1279 destinationBands = param.getDestinationBands(); 1280 destinationOffset = param.getDestinationOffset(); 1281 } 1282 Inflater inf = null; 1283 try { 1284 stream.seek(imageStartPosition); 1285 1286 Enumeration<InputStream> e = new PNGImageDataEnumeration(stream); 1287 InputStream is = new SequenceInputStream(e); 1288 1289 /* InflaterInputStream uses an Inflater instance which consumes 1290 * native (non-GC visible) resources. This is normally implicitly 1291 * freed when the stream is closed. However since the 1292 * InflaterInputStream wraps a client-supplied input stream, 1293 * we cannot close it. 1294 * But the app may depend on GC finalization to close the stream. 1295 * Therefore to ensure timely freeing of native resources we 1296 * explicitly create the Inflater instance and free its resources 1297 * when we are done with the InflaterInputStream by calling 1298 * inf.end(); 1299 */ 1300 inf = new Inflater(); 1301 is = new InflaterInputStream(is, inf); 1302 is = new BufferedInputStream(is); 1303 this.pixelStream = new DataInputStream(is); 1304 1305 /* 1306 * NB: the PNG spec declares that valid range for width 1307 * and height is [1, 2^31-1], so here we may fail to allocate 1308 * a buffer for destination image due to memory limitation. 1309 * 1310 * However, the recovery strategy for this case should be 1311 * defined on the level of application, so we will not 1312 * try to estimate the required amount of the memory and/or 1313 * handle OOM in any way. 1314 */ 1315 theImage = getDestination(param, 1316 getImageTypes(0), 1317 width, 1318 height); 1319 1320 Rectangle destRegion = new Rectangle(0, 0, 0, 0); 1321 sourceRegion = new Rectangle(0, 0, 0, 0); 1322 computeRegions(param, width, height, 1323 theImage, 1324 sourceRegion, destRegion); 1325 destinationOffset.setLocation(destRegion.getLocation()); 1326 1327 // At this point the header has been read and we know 1328 // how many bands are in the image, so perform checking 1329 // of the read param. 1330 int colorType = metadata.IHDR_colorType; 1331 checkReadParamBandSettings(param, 1332 inputBandsForColorType[colorType], 1333 theImage.getSampleModel().getNumBands()); 1334 1335 processImageStarted(0); 1336 decodeImage(); 1337 if (abortRequested()) { 1338 processReadAborted(); 1339 } else { 1340 processImageComplete(); 1341 } 1342 } catch (IOException e) { 1343 throw new IIOException("Error reading PNG image data", e); 1344 } finally { 1345 if (inf != null) { 1346 inf.end(); 1347 } 1348 } 1349 } 1350 1351 public int getNumImages(boolean allowSearch) throws IIOException { 1352 if (stream == null) { 1353 throw new IllegalStateException("No input source set!"); 1354 } 1355 if (seekForwardOnly && allowSearch) { 1356 throw new IllegalStateException 1357 ("seekForwardOnly and allowSearch can't both be true!"); 1358 } 1359 return 1; 1360 } 1361 1362 public int getWidth(int imageIndex) throws IIOException { 1363 if (imageIndex != 0) { 1364 throw new IndexOutOfBoundsException("imageIndex != 0!"); 1365 } 1366 1367 readHeader(); 1368 1369 return metadata.IHDR_width; 1370 } 1371 1372 public int getHeight(int imageIndex) throws IIOException { 1373 if (imageIndex != 0) { 1374 throw new IndexOutOfBoundsException("imageIndex != 0!"); 1375 } 1376 1377 readHeader(); 1378 1379 return metadata.IHDR_height; 1380 } 1381 1382 public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) 1383 throws IIOException 1384 { 1385 if (imageIndex != 0) { 1386 throw new IndexOutOfBoundsException("imageIndex != 0!"); 1387 } 1388 1389 readHeader(); 1390 1391 ArrayList<ImageTypeSpecifier> l = 1392 new ArrayList<ImageTypeSpecifier>(1); 1393 1394 ColorSpace rgb; 1395 ColorSpace gray; 1396 int[] bandOffsets; 1397 1398 int bitDepth = metadata.IHDR_bitDepth; 1399 int colorType = metadata.IHDR_colorType; 1400 1401 int dataType; 1402 if (bitDepth <= 8) { 1403 dataType = DataBuffer.TYPE_BYTE; 1404 } else { 1405 dataType = DataBuffer.TYPE_USHORT; 1406 } 1407 1408 switch (colorType) { 1409 case PNG_COLOR_GRAY: 1410 // Packed grayscale 1411 l.add(ImageTypeSpecifier.createGrayscale(bitDepth, 1412 dataType, 1413 false)); 1414 break; 1415 1416 case PNG_COLOR_RGB: 1417 if (bitDepth == 8) { 1418 // some standard types of buffered images 1419 // which can be used as destination 1420 l.add(ImageTypeSpecifier.createFromBufferedImageType( 1421 BufferedImage.TYPE_3BYTE_BGR)); 1422 1423 l.add(ImageTypeSpecifier.createFromBufferedImageType( 1424 BufferedImage.TYPE_INT_RGB)); 1425 1426 l.add(ImageTypeSpecifier.createFromBufferedImageType( 1427 BufferedImage.TYPE_INT_BGR)); 1428 1429 } 1430 // Component R, G, B 1431 rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); 1432 bandOffsets = new int[3]; 1433 bandOffsets[0] = 0; 1434 bandOffsets[1] = 1; 1435 bandOffsets[2] = 2; 1436 l.add(ImageTypeSpecifier.createInterleaved(rgb, 1437 bandOffsets, 1438 dataType, 1439 false, 1440 false)); 1441 break; 1442 1443 case PNG_COLOR_PALETTE: 1444 readMetadata(); // Need tRNS chunk 1445 1446 /* 1447 * The PLTE chunk spec says: 1448 * 1449 * The number of palette entries must not exceed the range that 1450 * can be represented in the image bit depth (for example, 2^4 = 16 1451 * for a bit depth of 4). It is permissible to have fewer entries 1452 * than the bit depth would allow. In that case, any out-of-range 1453 * pixel value found in the image data is an error. 1454 * 1455 * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE 1456 * 1457 * Consequently, the case when the palette length is smaller than 1458 * 2^bitDepth is legal in the view of PNG spec. 1459 * 1460 * However the spec of createIndexed() method demands the exact 1461 * equality of the palette lengh and number of possible palette 1462 * entries (2^bitDepth). 1463 * 1464 * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed} 1465 * 1466 * In order to avoid this contradiction we need to extend the 1467 * palette arrays to the limit defined by the bitDepth. 1468 */ 1469 1470 int plength = 1 << bitDepth; 1471 1472 byte[] red = metadata.PLTE_red; 1473 byte[] green = metadata.PLTE_green; 1474 byte[] blue = metadata.PLTE_blue; 1475 1476 if (metadata.PLTE_red.length < plength) { 1477 red = Arrays.copyOf(metadata.PLTE_red, plength); 1478 Arrays.fill(red, metadata.PLTE_red.length, plength, 1479 metadata.PLTE_red[metadata.PLTE_red.length - 1]); 1480 1481 green = Arrays.copyOf(metadata.PLTE_green, plength); 1482 Arrays.fill(green, metadata.PLTE_green.length, plength, 1483 metadata.PLTE_green[metadata.PLTE_green.length - 1]); 1484 1485 blue = Arrays.copyOf(metadata.PLTE_blue, plength); 1486 Arrays.fill(blue, metadata.PLTE_blue.length, plength, 1487 metadata.PLTE_blue[metadata.PLTE_blue.length - 1]); 1488 1489 } 1490 1491 // Alpha from tRNS chunk may have fewer entries than 1492 // the RGB LUTs from the PLTE chunk; if so, pad with 1493 // 255. 1494 byte[] alpha = null; 1495 if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) { 1496 if (metadata.tRNS_alpha.length == red.length) { 1497 alpha = metadata.tRNS_alpha; 1498 } else { 1499 alpha = Arrays.copyOf(metadata.tRNS_alpha, red.length); 1500 Arrays.fill(alpha, 1501 metadata.tRNS_alpha.length, 1502 red.length, (byte)255); 1503 } 1504 } 1505 1506 l.add(ImageTypeSpecifier.createIndexed(red, green, 1507 blue, alpha, 1508 bitDepth, 1509 DataBuffer.TYPE_BYTE)); 1510 break; 1511 1512 case PNG_COLOR_GRAY_ALPHA: 1513 // Component G, A 1514 gray = ColorSpace.getInstance(ColorSpace.CS_GRAY); 1515 bandOffsets = new int[2]; 1516 bandOffsets[0] = 0; 1517 bandOffsets[1] = 1; 1518 l.add(ImageTypeSpecifier.createInterleaved(gray, 1519 bandOffsets, 1520 dataType, 1521 true, 1522 false)); 1523 break; 1524 1525 case PNG_COLOR_RGB_ALPHA: 1526 if (bitDepth == 8) { 1527 // some standard types of buffered images 1528 // wich can be used as destination 1529 l.add(ImageTypeSpecifier.createFromBufferedImageType( 1530 BufferedImage.TYPE_4BYTE_ABGR)); 1531 1532 l.add(ImageTypeSpecifier.createFromBufferedImageType( 1533 BufferedImage.TYPE_INT_ARGB)); 1534 } 1535 1536 // Component R, G, B, A (non-premultiplied) 1537 rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); 1538 bandOffsets = new int[4]; 1539 bandOffsets[0] = 0; 1540 bandOffsets[1] = 1; 1541 bandOffsets[2] = 2; 1542 bandOffsets[3] = 3; 1543 1544 l.add(ImageTypeSpecifier.createInterleaved(rgb, 1545 bandOffsets, 1546 dataType, 1547 true, 1548 false)); 1549 break; 1550 1551 default: 1552 break; 1553 } 1554 1555 return l.iterator(); 1556 } 1557 1558 /* 1559 * Super class implementation uses first element 1560 * of image types list as raw image type. 1561 * 1562 * Also, super implementation uses first element of this list 1563 * as default destination type image read param does not specify 1564 * anything other. 1565 * 1566 * However, in case of RGB and RGBA color types, raw image type 1567 * produces buffered image of custom type. It causes some 1568 * performance degradation of subsequent rendering operations. 1569 * 1570 * To resolve this contradiction we put standard image types 1571 * at the first positions of image types list (to produce standard 1572 * images by default) and put raw image type (which is custom) 1573 * at the last position of this list. 1574 * 1575 * After this changes we should override getRawImageType() 1576 * to return last element of image types list. 1577 */ 1578 public ImageTypeSpecifier getRawImageType(int imageIndex) 1579 throws IOException { 1580 1581 Iterator<ImageTypeSpecifier> types = getImageTypes(imageIndex); 1582 ImageTypeSpecifier raw = null; 1583 do { 1584 raw = types.next(); 1585 } while (types.hasNext()); 1586 return raw; 1587 } 1588 1589 public ImageReadParam getDefaultReadParam() { 1590 return new ImageReadParam(); 1591 } 1592 1593 public IIOMetadata getStreamMetadata() 1594 throws IIOException { 1595 return null; 1596 } 1597 1598 public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { 1599 if (imageIndex != 0) { 1600 throw new IndexOutOfBoundsException("imageIndex != 0!"); 1601 } 1602 readMetadata(); 1603 return metadata; 1604 } 1605 1606 public BufferedImage read(int imageIndex, ImageReadParam param) 1607 throws IIOException { 1608 if (imageIndex != 0) { 1609 throw new IndexOutOfBoundsException("imageIndex != 0!"); 1610 } 1611 1612 readImage(param); 1613 return theImage; 1614 } 1615 1616 public void reset() { 1617 super.reset(); 1618 resetStreamSettings(); 1619 } 1620 1621 private void resetStreamSettings() { 1622 gotHeader = false; 1623 gotMetadata = false; 1624 metadata = null; 1625 pixelStream = null; 1626 } 1627 }