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