| 1 18 package org.apache.batik.ext.awt.image.codec; 19 20 import java.awt.Color ; 21 import java.awt.Point ; 22 import java.awt.Transparency ; 23 import java.awt.color.ColorSpace ; 24 import java.awt.image.ColorModel ; 25 import java.awt.image.ComponentColorModel ; 26 import java.awt.image.DataBuffer ; 27 import java.awt.image.DataBufferByte ; 28 import java.awt.image.DataBufferUShort ; 29 import java.awt.image.IndexColorModel ; 30 import java.awt.image.Raster ; 31 import java.awt.image.RenderedImage ; 32 import java.awt.image.SampleModel ; 33 import java.awt.image.WritableRaster ; 34 import java.io.BufferedInputStream ; 35 import java.io.ByteArrayInputStream ; 36 import java.io.DataInputStream ; 37 import java.io.IOException ; 38 import java.io.InputStream ; 39 import java.io.SequenceInputStream ; 40 import java.util.Date ; 41 import java.util.GregorianCalendar ; 42 import java.util.TimeZone ; 43 import java.util.Vector ; 44 import java.util.zip.Inflater ; 45 import java.util.zip.InflaterInputStream ; 46 47 49 public class PNGImageDecoder extends ImageDecoderImpl { 50 51 public PNGImageDecoder(InputStream input, 52 PNGDecodeParam param) { 53 super(input, param); 54 } 55 56 public RenderedImage decodeAsRenderedImage(int page) throws IOException { 57 if (page != 0) { 58 throw new IOException (PropertyUtil.getString("PNGImageDecoder19")); 59 } 60 return new PNGImage(input, (PNGDecodeParam)param); 61 } 62 } 63 64 class PNGChunk { 65 int length; 66 int type; 67 byte[] data; 68 int crc; 69 70 String typeString; 71 72 public PNGChunk(int length, int type, byte[] data, int crc) { 73 this.length = length; 74 this.type = type; 75 this.data = data; 76 this.crc = crc; 77 78 typeString = new String (); 79 typeString += (char)(type >> 24); 80 typeString += (char)((type >> 16) & 0xff); 81 typeString += (char)((type >> 8) & 0xff); 82 typeString += (char)(type & 0xff); 83 } 84 85 public int getLength() { 86 return length; 87 } 88 89 public int getType() { 90 return type; 91 } 92 93 public String getTypeString() { 94 return typeString; 95 } 96 97 public byte[] getData() { 98 return data; 99 } 100 101 public byte getByte(int offset) { 102 return data[offset]; 103 } 104 105 public int getInt1(int offset) { 106 return data[offset] & 0xff; 107 } 108 109 public int getInt2(int offset) { 110 return ((data[offset] & 0xff) << 8) | 111 (data[offset + 1] & 0xff); 112 } 113 114 public int getInt4(int offset) { 115 return ((data[offset] & 0xff) << 24) | 116 ((data[offset + 1] & 0xff) << 16) | 117 ((data[offset + 2] & 0xff) << 8) | 118 (data[offset + 3] & 0xff); 119 } 120 121 public String getString4(int offset) { 122 String s = new String (); 123 s += (char)data[offset]; 124 s += (char)data[offset + 1]; 125 s += (char)data[offset + 2]; 126 s += (char)data[offset + 3]; 127 return s; 128 } 129 130 public boolean isType(String typeName) { 131 return typeString.equals(typeName); 132 } 133 } 134 135 141 class PNGImage extends SimpleRenderedImage { 142 143 public static final int PNG_COLOR_GRAY = 0; 144 public static final int PNG_COLOR_RGB = 2; 145 public static final int PNG_COLOR_PALETTE = 3; 146 public static final int PNG_COLOR_GRAY_ALPHA = 4; 147 public static final int PNG_COLOR_RGB_ALPHA = 6; 148 149 private static final String [] colorTypeNames = { 150 "Grayscale", "Error", "Truecolor", "Index", 151 "Grayscale with alpha", "Error", "Truecolor with alpha" 152 }; 153 154 public static final int PNG_FILTER_NONE = 0; 155 public static final int PNG_FILTER_SUB = 1; 156 public static final int PNG_FILTER_UP = 2; 157 public static final int PNG_FILTER_AVERAGE = 3; 158 public static final int PNG_FILTER_PAETH = 4; 159 160 private static final int RED_OFFSET = 2; 161 private static final int GREEN_OFFSET = 1; 162 private static final int BLUE_OFFSET = 0; 163 164 private int[][] bandOffsets = { 165 null, 166 { 0 }, { 0, 1 }, { 0, 1, 2 }, { 0, 1, 2, 3 } }; 171 172 private int bitDepth; 173 private int colorType; 174 175 private int compressionMethod; 176 private int filterMethod; 177 private int interlaceMethod; 178 179 private int paletteEntries; 180 private byte[] redPalette; 181 private byte[] greenPalette; 182 private byte[] bluePalette; 183 private byte[] alphaPalette; 184 185 private int bkgdRed; 186 private int bkgdGreen; 187 private int bkgdBlue; 188 189 private int grayTransparentAlpha; 190 private int redTransparentAlpha; 191 private int greenTransparentAlpha; 192 private int blueTransparentAlpha; 193 194 private int maxOpacity; 195 196 private int[] significantBits = null; 197 198 private boolean hasBackground = false; 199 200 202 private boolean suppressAlpha = false; 204 205 private boolean expandPalette = false; 207 208 private boolean output8BitGray = false; 210 211 private boolean outputHasAlphaPalette = false; 213 214 private boolean performGammaCorrection = false; 216 217 private boolean expandGrayAlpha = false; 219 220 private boolean generateEncodeParam = false; 222 223 private PNGDecodeParam decodeParam = null; 225 226 private PNGEncodeParam encodeParam = null; 228 229 private boolean emitProperties = true; 230 231 private float fileGamma = 45455/100000.0F; 232 233 private float userExponent = 1.0F; 234 235 private float displayExponent = 2.2F; 236 237 private float[] chromaticity = null; 238 239 private int sRGBRenderingIntent = -1; 240 241 private int postProcess = POST_NONE; 243 244 246 private static final int POST_NONE = 0; 248 249 private static final int POST_GAMMA = 1; 251 252 private static final int POST_GRAY_LUT = 2; 254 255 private static final int POST_GRAY_LUT_ADD_TRANS = 3; 257 258 private static final int POST_PALETTE_TO_RGB = 4; 260 261 private static final int POST_PALETTE_TO_RGBA = 5; 263 264 private static final int POST_ADD_GRAY_TRANS = 6; 266 267 private static final int POST_ADD_RGB_TRANS = 7; 269 270 private static final int POST_REMOVE_GRAY_TRANS = 8; 272 273 private static final int POST_REMOVE_RGB_TRANS = 9; 275 276 private static final int POST_EXP_MASK = 16; 278 279 private static final int POST_GRAY_ALPHA_EXP = 281 POST_NONE | POST_EXP_MASK; 282 283 private static final int POST_GAMMA_EXP = 285 POST_GAMMA | POST_EXP_MASK; 286 287 private static final int POST_GRAY_LUT_ADD_TRANS_EXP = 289 POST_GRAY_LUT_ADD_TRANS | POST_EXP_MASK; 290 291 private static final int POST_ADD_GRAY_TRANS_EXP = 293 POST_ADD_GRAY_TRANS | POST_EXP_MASK; 294 295 private Vector streamVec = new Vector (); 296 private DataInputStream dataStream; 297 298 private int bytesPerPixel; private int inputBands; 300 private int outputBands; 301 302 private int chunkIndex = 0; 304 305 private Vector textKeys = new Vector (); 306 private Vector textStrings = new Vector (); 307 308 private Vector ztextKeys = new Vector (); 309 private Vector ztextStrings = new Vector (); 310 311 private WritableRaster theTile; 312 313 private int[] gammaLut = null; 314 315 private void initGammaLut(int bits) { 316 double exp = (double)userExponent/(fileGamma*displayExponent); 317 int numSamples = 1 << bits; 318 int maxOutSample = (bits == 16) ? 65535 : 255; 319 320 gammaLut = new int[numSamples]; 321 for (int i = 0; i < numSamples; i++) { 322 double gbright = (double)i/(numSamples - 1); 323 double gamma = Math.pow(gbright, exp); 324 int igamma = (int)(gamma*maxOutSample + 0.5); 325 if (igamma > maxOutSample) { 326 igamma = maxOutSample; 327 } 328 gammaLut[i] = igamma; 329 } 330 } 331 332 private final byte[][] expandBits = { 333 null, 334 { (byte)0x00, (byte)0xff }, 335 { (byte)0x00, (byte)0x55, (byte)0xaa, (byte)0xff }, 336 null, 337 { (byte)0x00, (byte)0x11, (byte)0x22, (byte)0x33, 338 (byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77, 339 (byte)0x88, (byte)0x99, (byte)0xaa, (byte)0xbb, 340 (byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff } 341 }; 342 343 private int[] grayLut = null; 344 345 private void initGrayLut(int bits) { 346 int len = 1 << bits; 347 grayLut = new int[len]; 348 349 if (performGammaCorrection) { 350 for (int i = 0; i < len; i++) { 351 grayLut[i] = gammaLut[i]; 352 } 353 } else { 354 for (int i = 0; i < len; i++) { 355 grayLut[i] = expandBits[bits][i]; 356 } 357 } 358 } 359 360 public PNGImage(InputStream stream, PNGDecodeParam decodeParam) 361 throws IOException { 362 363 if (!stream.markSupported()) { 364 stream = new BufferedInputStream (stream); 365 } 366 DataInputStream distream = new DataInputStream (stream); 367 368 if (decodeParam == null) { 369 decodeParam = new PNGDecodeParam(); 370 } 371 this.decodeParam = decodeParam; 372 373 this.suppressAlpha = decodeParam.getSuppressAlpha(); 375 this.expandPalette = decodeParam.getExpandPalette(); 376 this.output8BitGray = decodeParam.getOutput8BitGray(); 377 this.expandGrayAlpha = decodeParam.getExpandGrayAlpha(); 378 if (decodeParam.getPerformGammaCorrection()) { 379 this.userExponent = decodeParam.getUserExponent(); 380 this.displayExponent = decodeParam.getDisplayExponent(); 381 performGammaCorrection = true; 382 output8BitGray = true; 383 } 384 this.generateEncodeParam = decodeParam.getGenerateEncodeParam(); 385 386 if (emitProperties) { 387 properties.put("file_type", "PNG v. 1.0"); 388 } 389 390 try { 391 long magic = distream.readLong(); 392 if (magic != 0x89504e470d0a1a0aL) { 393 String msg = PropertyUtil.getString("PNGImageDecoder0"); 394 throw new RuntimeException (msg); 395 } 396 } catch (Exception e) { 397 e.printStackTrace(); 398 String msg = PropertyUtil.getString("PNGImageDecoder1"); 399 throw new RuntimeException (msg); 400 } 401 402 do { 403 try { 404 PNGChunk chunk; 405 406 String chunkType = getChunkType(distream); 407 if (chunkType.equals("IHDR")) { 408 chunk = readChunk(distream); 409 parse_IHDR_chunk(chunk); 410 } else if (chunkType.equals("PLTE")) { 411 chunk = readChunk(distream); 412 parse_PLTE_chunk(chunk); 413 } else if (chunkType.equals("IDAT")) { 414 chunk = readChunk(distream); 415 streamVec.add(new ByteArrayInputStream (chunk.getData())); 416 } else if (chunkType.equals("IEND")) { 417 chunk = readChunk(distream); 418 parse_IEND_chunk(chunk); 419 break; } else if (chunkType.equals("bKGD")) { 421 chunk = readChunk(distream); 422 parse_bKGD_chunk(chunk); 423 } else if (chunkType.equals("cHRM")) { 424 chunk = readChunk(distream); 425 parse_cHRM_chunk(chunk); 426 } else if (chunkType.equals("gAMA")) { 427 chunk = readChunk(distream); 428 parse_gAMA_chunk(chunk); 429 } else if (chunkType.equals("hIST")) { 430 chunk = readChunk(distream); 431 parse_hIST_chunk(chunk); 432 } else if (chunkType.equals("iCCP")) { 433 chunk = readChunk(distream); 434 parse_iCCP_chunk(chunk); 435 } else if (chunkType.equals("pHYs")) { 436 chunk = readChunk(distream); 437 parse_pHYs_chunk(chunk); 438 } else if (chunkType.equals("sBIT")) { 439 chunk = readChunk(distream); 440 parse_sBIT_chunk(chunk); 441 } else if (chunkType.equals("sRGB")) { 442 chunk = readChunk(distream); 443 parse_sRGB_chunk(chunk); 444 } else if (chunkType.equals("tEXt")) { 445 chunk = readChunk(distream); 446 parse_tEXt_chunk(chunk); 447 } else if (chunkType.equals("tIME")) { 448 chunk = readChunk(distream); 449 parse_tIME_chunk(chunk); 450 } else if (chunkType.equals("tRNS")) { 451 chunk = readChunk(distream); 452 parse_tRNS_chunk(chunk); 453 } else if (chunkType.equals("zTXt")) { 454 chunk = readChunk(distream); 455 parse_zTXt_chunk(chunk); 456 } else { 457 chunk = readChunk(distream); 458 460 String type = chunk.getTypeString(); 461 byte[] data = chunk.getData(); 462 if (encodeParam != null) { 463 encodeParam.addPrivateChunk(type, data); 464 } 465 if (emitProperties) { 466 String key = "chunk_" + chunkIndex++ + ":" + type; 467 properties.put(key.toLowerCase(), data); 468 } 469 } 470 } catch (Exception e) { 471 e.printStackTrace(); 472 String msg = PropertyUtil.getString("PNGImageDecoder2"); 473 throw new RuntimeException (msg); 474 } 475 } while (true); 476 477 479 if (significantBits == null) { 480 significantBits = new int[inputBands]; 481 for (int i = 0; i < inputBands; i++) { 482 significantBits[i] = bitDepth; 483 } 484 485 if (emitProperties) { 486 properties.put("significant_bits", significantBits); 487 } 488 } 489 } 490 491 private static String getChunkType(DataInputStream distream) { 492 try { 493 distream.mark(8); 494 distream.readInt(); 495 int type = distream.readInt(); 496 distream.reset(); 497 498 String typeString = new String (); 499 typeString += (char)(type >> 24); 500 typeString += (char)((type >> 16) & 0xff); 501 typeString += (char)((type >> 8) & 0xff); 502 typeString += (char)(type & 0xff); 503 return typeString; 504 } catch (Exception e) { 505 e.printStackTrace(); 506 return null; 507 } 508 } 509 510 private static PNGChunk readChunk(DataInputStream distream) { 511 try { 512 int length = distream.readInt(); 513 int type = distream.readInt(); 514 byte[] data = new byte[length]; 515 distream.readFully(data); 516 int crc = distream.readInt(); 517 518 return new PNGChunk(length, type, data, crc); 519 } catch (Exception e) { 520 e.printStackTrace(); 521 return null; 522 } 523 } 524 525 private void parse_IHDR_chunk(PNGChunk chunk) { 526 tileWidth = width = chunk.getInt4(0); 527 tileHeight = height = chunk.getInt4(4); 528 529 bitDepth = chunk.getInt1(8); 530 531 if ((bitDepth != 1) && (bitDepth != 2) && (bitDepth != 4) && 532 (bitDepth != 8) && (bitDepth != 16)) { 533 String msg = PropertyUtil.getString("PNGImageDecoder3"); 535 throw new RuntimeException (msg); 536 } 537 maxOpacity = (1 << bitDepth) - 1; 538 539 colorType = chunk.getInt1(9); 540 if ((colorType != PNG_COLOR_GRAY) && 541 (colorType != PNG_COLOR_RGB) && 542 (colorType != PNG_COLOR_PALETTE) && 543 (colorType != PNG_COLOR_GRAY_ALPHA) && 544 (colorType != PNG_COLOR_RGB_ALPHA)) { 545 System.out.println(PropertyUtil.getString("PNGImageDecoder4")); 546 } 547 548 if ((colorType == PNG_COLOR_RGB) && (bitDepth < 8)) { 549 String msg = PropertyUtil.getString("PNGImageDecoder5"); 551 throw new RuntimeException (msg); 552 } 553 554 if ((colorType == PNG_COLOR_PALETTE) && (bitDepth == 16)) { 555 String msg = PropertyUtil.getString("PNGImageDecoder6"); 557 throw new RuntimeException (msg); 558 } 559 560 if ((colorType == PNG_COLOR_GRAY_ALPHA) && (bitDepth < 8)) { 561 String msg = PropertyUtil.getString("PNGImageDecoder7"); 563 throw new RuntimeException (msg); 564 } 565 566 if ((colorType == PNG_COLOR_RGB_ALPHA) && (bitDepth < 8)) { 567 String msg = PropertyUtil.getString("PNGImageDecoder8"); 569 throw new RuntimeException (msg); 570 } 571 572 if (emitProperties) { 573 properties.put("color_type", colorTypeNames[colorType]); 574 } 575 576 if (generateEncodeParam) { 577 if (colorType == PNG_COLOR_PALETTE) { 578 encodeParam = new PNGEncodeParam.Palette(); 579 } else if (colorType == PNG_COLOR_GRAY || 580 colorType == PNG_COLOR_GRAY_ALPHA) { 581 encodeParam = new PNGEncodeParam.Gray(); 582 } else { 583 encodeParam = new PNGEncodeParam.RGB(); 584 } 585 decodeParam.setEncodeParam(encodeParam); 586 } 587 588 if (encodeParam != null) { 589 encodeParam.setBitDepth(bitDepth); 590 } 591 if (emitProperties) { 592 properties.put("bit_depth", new Integer (bitDepth)); 593 } 594 595 if (performGammaCorrection) { 596 float gamma = (1.0F/2.2F)*(displayExponent/userExponent); 598 if (encodeParam != null) { 599 encodeParam.setGamma(gamma); 600 } 601 if (emitProperties) { 602 properties.put("gamma", new Float (gamma)); 603 } 604 } 605 606 compressionMethod = chunk.getInt1(10); 607 if (compressionMethod != 0) { 608 String msg = PropertyUtil.getString("PNGImageDecoder9"); 610 throw new RuntimeException (msg); 611 } 612 613 filterMethod = chunk.getInt1(11); 614 if (filterMethod != 0) { 615 String msg = PropertyUtil.getString("PNGImageDecoder10"); 617 throw new RuntimeException (msg); 618 } 619 620 interlaceMethod = chunk.getInt1(12); 621 if (interlaceMethod == 0) { 622 if (encodeParam != null) { 623 encodeParam.setInterlacing(false); 624 } 625 if (emitProperties) { 626 properties.put("interlace_method", "None"); 627 } 628 } else if (interlaceMethod == 1) { 629 if (encodeParam != null) { 630 encodeParam.setInterlacing(true); 631 } 632 if (emitProperties) { 633 properties.put("interlace_method", "Adam7"); 634 } 635 } else { 636 String msg = PropertyUtil.getString("PNGImageDecoder11"); 638 throw new RuntimeException (msg); 639 } 640 641 bytesPerPixel = (bitDepth == 16) ? 2 : 1; 642 643 switch (colorType) { 644 case PNG_COLOR_GRAY: 645 inputBands = 1; 646 outputBands = 1; 647 648 if (output8BitGray && (bitDepth < 8)) { 649 postProcess = POST_GRAY_LUT; 650 } else if (performGammaCorrection) { 651 postProcess = POST_GAMMA; 652 } else { 653 postProcess = POST_NONE; 654 } 655 break; 656 657 case PNG_COLOR_RGB: 658 inputBands = 3; 659 bytesPerPixel *= 3; 660 outputBands = 3; 661 662 if (performGammaCorrection) { 663 postProcess = POST_GAMMA; 664 } else { 665 postProcess = POST_NONE; 666 } 667 break; 668 669 case PNG_COLOR_PALETTE: 670 inputBands = 1; 671 bytesPerPixel = 1; 672 outputBands = expandPalette ? 3 : 1; 673 674 if (expandPalette) { 675 postProcess = POST_PALETTE_TO_RGB; 676 } else { 677 postProcess = POST_NONE; 678 } 679 break; 680 681 case PNG_COLOR_GRAY_ALPHA: 682 inputBands = 2; 683 bytesPerPixel *= 2; 684 685 if (suppressAlpha) { 686 outputBands = 1; 687 postProcess = POST_REMOVE_GRAY_TRANS; 688 } else { 689 if (performGammaCorrection) { 690 postProcess = POST_GAMMA; 691 } else { 692 postProcess = POST_NONE; 693 } 694 if (expandGrayAlpha) { 695 postProcess |= POST_EXP_MASK; 696 outputBands = 4; 697 } else { 698 outputBands = 2; 699 } 700 } 701 break; 702 703 case PNG_COLOR_RGB_ALPHA: 704 inputBands = 4; 705 bytesPerPixel *= 4; 706 outputBands = (!suppressAlpha) ? 4 : 3; 707 708 if (suppressAlpha) { 709 postProcess = POST_REMOVE_RGB_TRANS; 710 } else if (performGammaCorrection) { 711 postProcess = POST_GAMMA; 712 } else { 713 postProcess = POST_NONE; 714 } 715 break; 716 } 717 } 718 719 private void parse_IEND_chunk(PNGChunk chunk) throws Exception { 720 int textLen = textKeys.size(); 722 String [] textArray = new String [2*textLen]; 723 for (int i = 0; i < textLen; i++) { 724 String key = (String )textKeys.elementAt(i); 725 String val = (String )textStrings.elementAt(i); 726 textArray[2*i] = key; 727 textArray[2*i + 1] = val; 728 if (emitProperties) { 729 String uniqueKey = "text_" + i + ":" + key; 730 properties.put(uniqueKey.toLowerCase(), val); 731 } 732 } 733 if (encodeParam != null) { 734 encodeParam.setText(textArray); 735 } 736 737 int ztextLen = ztextKeys.size(); 739 String [] ztextArray = new String [2*ztextLen]; 740 <
|