001/* 002 * $RCSfile: CLibPNGMetadata.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.3 $ 042 * $Date: 2006/02/27 17:25:04 $ 043 * $State: Exp $ 044 */ 045 046package com.github.jaiimageio.impl.plugins.png; 047 048import java.awt.image.ColorModel; 049import java.awt.image.IndexColorModel; 050import java.awt.image.SampleModel; 051import java.io.ByteArrayInputStream; 052import java.io.InputStream; 053import java.io.IOException; 054import java.io.UnsupportedEncodingException; 055import java.util.ArrayList; 056import java.util.Arrays; 057import java.util.Calendar; 058import java.util.GregorianCalendar; 059import java.util.Iterator; 060import java.util.List; 061import java.util.StringTokenizer; 062import java.util.zip.DataFormatException; 063import java.util.zip.Deflater; 064import java.util.zip.Inflater; 065import javax.imageio.IIOException; 066import javax.imageio.ImageTypeSpecifier; 067import javax.imageio.ImageWriteParam; 068import javax.imageio.metadata.IIOInvalidTreeException; 069import javax.imageio.metadata.IIOMetadata; 070import javax.imageio.metadata.IIOMetadataFormat; 071import javax.imageio.metadata.IIOMetadataFormatImpl; 072import javax.imageio.metadata.IIOMetadataNode; 073import javax.imageio.stream.ImageInputStream; 074import javax.imageio.stream.MemoryCacheImageInputStream; 075import org.w3c.dom.Node; 076 077// 078// Core J2SE problems fixed in this package: 079// 5109146: 080// PNG: Background color initialization from standard metadata is incomplete 081// 5109114: 082// PNG: Cannot set IHDR_bitDepth from standard metadata /Data/BitsPerSample 083// 5106305: 084// PNG standard to native image metadata conversion incorrect for pixel size 085// 5106550: 086// PNG writer merge standard metadata fails for TextEntry sans #IMPLIED 087// attributes 088// 5082756: 089// Image I/O plug-ins set metadata boolean attributes to "true" or "false" 090// 5105068: 091// PNGImageWriter.convertImageMetadata() broken for non-PNGMetadata 092// 093 094/** 095 */ 096public class CLibPNGMetadata extends IIOMetadata implements Cloneable { 097 098 // package scope 099 public static final String 100 nativeMetadataFormatName = "javax_imageio_png_1.0"; 101 102 protected static final String nativeMetadataFormatClassName 103 = "com.github.jaiimageio.impl.plugins.png.CLibPNGMetadataFormat"; 104 105 // Color types for IHDR chunk 106 public static final String[] IHDR_colorTypeNames = { 107 "Grayscale", null, "RGB", "Palette", 108 "GrayAlpha", null, "RGBAlpha" 109 }; 110 111 public static final int[] IHDR_numChannels = { 112 1, 0, 3, 3, 2, 0, 4 113 }; 114 115 // Bit depths for IHDR chunk 116 public static final String[] IHDR_bitDepths = { 117 "1", "2", "4", "8", "16" 118 }; 119 120 // Compression methods for IHDR chunk 121 public static final String[] IHDR_compressionMethodNames = { 122 "deflate" 123 }; 124 125 // Filter methods for IHDR chunk 126 public static final String[] IHDR_filterMethodNames = { 127 "adaptive" 128 }; 129 130 // Interlace methods for IHDR chunk 131 public static final String[] IHDR_interlaceMethodNames = { 132 "none", "adam7" 133 }; 134 135 // Compression methods for iCCP chunk 136 public static final String[] iCCP_compressionMethodNames = { 137 "deflate" 138 }; 139 140 // Compression methods for zTXt chunk 141 public static final String[] zTXt_compressionMethodNames = { 142 "deflate" 143 }; 144 145 // "Unknown" unit for pHYs chunk 146 public static final int PHYS_UNIT_UNKNOWN = 0; 147 148 // "Meter" unit for pHYs chunk 149 public static final int PHYS_UNIT_METER = 1; 150 151 // Unit specifiers for pHYs chunk 152 public static final String[] unitSpecifierNames = { 153 "unknown", "meter" 154 }; 155 156 // Rendering intents for sRGB chunk 157 public static final String[] renderingIntentNames = { 158 "Perceptual", // 0 159 "Relative colorimetric", // 1 160 "Saturation", // 2 161 "Absolute colorimetric" // 3 162 163 }; 164 165 // Color space types for Chroma->ColorSpaceType node 166 public static final String[] colorSpaceTypeNames = { 167 "GRAY", null, "RGB", "RGB", 168 "GRAY", null, "RGB" 169 }; 170 171 // BEGIN Definitions required for reading. 172 173 // Critical chunks 174 static final int IHDR_TYPE = chunkType("IHDR"); 175 static final int PLTE_TYPE = chunkType("PLTE"); 176 static final int IDAT_TYPE = chunkType("IDAT"); 177 static final int IEND_TYPE = chunkType("IEND"); 178 179 // Ancillary chunks 180 static final int bKGD_TYPE = chunkType("bKGD"); 181 static final int cHRM_TYPE = chunkType("cHRM"); 182 static final int gAMA_TYPE = chunkType("gAMA"); 183 static final int hIST_TYPE = chunkType("hIST"); 184 static final int iCCP_TYPE = chunkType("iCCP"); 185 static final int iTXt_TYPE = chunkType("iTXt"); 186 static final int pHYs_TYPE = chunkType("pHYs"); 187 static final int sBIT_TYPE = chunkType("sBIT"); 188 static final int sPLT_TYPE = chunkType("sPLT"); 189 static final int sRGB_TYPE = chunkType("sRGB"); 190 static final int tEXt_TYPE = chunkType("tEXt"); 191 static final int tIME_TYPE = chunkType("tIME"); 192 static final int tRNS_TYPE = chunkType("tRNS"); 193 static final int zTXt_TYPE = chunkType("zTXt"); 194 195 static final int PNG_COLOR_GRAY = 0; 196 static final int PNG_COLOR_RGB = 2; 197 static final int PNG_COLOR_PALETTE = 3; 198 static final int PNG_COLOR_GRAY_ALPHA = 4; 199 static final int PNG_COLOR_RGB_ALPHA = 6; 200 201 // END Definitions required for reading. 202 203 // IHDR chunk 204 public boolean IHDR_present; 205 public int IHDR_width; 206 public int IHDR_height; 207 public int IHDR_bitDepth; 208 public int IHDR_colorType; 209 public int IHDR_compressionMethod; 210 public int IHDR_filterMethod; 211 public int IHDR_interlaceMethod; // 0 == none, 1 == adam7 212 213 // PLTE chunk 214 public boolean PLTE_present; 215 public byte[] PLTE_red; 216 public byte[] PLTE_green; 217 public byte[] PLTE_blue; 218 219 // bKGD chunk 220 // If external (non-PNG sourced) data has red = green = blue, 221 // always store it as gray and promote when writing 222 public boolean bKGD_present; 223 public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE 224 public int bKGD_index; 225 public int bKGD_gray; 226 public int bKGD_red; 227 public int bKGD_green; 228 public int bKGD_blue; 229 230 // cHRM chunk 231 public boolean cHRM_present; 232 public int cHRM_whitePointX; 233 public int cHRM_whitePointY; 234 public int cHRM_redX; 235 public int cHRM_redY; 236 public int cHRM_greenX; 237 public int cHRM_greenY; 238 public int cHRM_blueX; 239 public int cHRM_blueY; 240 241 // gAMA chunk 242 public boolean gAMA_present; 243 public int gAMA_gamma; 244 245 // hIST chunk 246 public boolean hIST_present; 247 public char[] hIST_histogram; 248 249 // iCCP chunk 250 public boolean iCCP_present; 251 public String iCCP_profileName; 252 public int iCCP_compressionMethod; 253 public byte[] iCCP_compressedProfile; 254 255 // iTXt chunk 256 public ArrayList iTXt_keyword = new ArrayList(); // Strings 257 public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers 258 public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers 259 public ArrayList iTXt_languageTag = new ArrayList(); // Strings 260 public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings 261 public ArrayList iTXt_text = new ArrayList(); // Strings 262 263 // pHYs chunk 264 public boolean pHYs_present; 265 public int pHYs_pixelsPerUnitXAxis; 266 public int pHYs_pixelsPerUnitYAxis; 267 public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter 268 269 // sBIT chunk 270 public boolean sBIT_present; 271 public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA 272 public int sBIT_grayBits; 273 public int sBIT_redBits; 274 public int sBIT_greenBits; 275 public int sBIT_blueBits; 276 public int sBIT_alphaBits; 277 278 // sPLT chunk 279 public boolean sPLT_present; 280 public String sPLT_paletteName; // 1-79 characters 281 public int sPLT_sampleDepth; // 8 or 16 282 public int[] sPLT_red; 283 public int[] sPLT_green; 284 public int[] sPLT_blue; 285 public int[] sPLT_alpha; 286 public int[] sPLT_frequency; 287 288 // sRGB chunk 289 public boolean sRGB_present; 290 public int sRGB_renderingIntent; 291 292 // tEXt chunk 293 public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings 294 public ArrayList tEXt_text = new ArrayList(); // Strings 295 296 // tIME chunk 297 public boolean tIME_present; 298 public int tIME_year; 299 public int tIME_month; 300 public int tIME_day; 301 public int tIME_hour; 302 public int tIME_minute; 303 public int tIME_second; 304 305 // tRNS chunk 306 // If external (non-PNG sourced) data has red = green = blue, 307 // always store it as gray and promote when writing 308 public boolean tRNS_present; 309 public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE 310 public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc. 311 public int tRNS_gray; 312 public int tRNS_red; 313 public int tRNS_green; 314 public int tRNS_blue; 315 316 // zTXt chunk 317 public ArrayList zTXt_keyword = new ArrayList(); // Strings 318 public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers 319 public ArrayList zTXt_text = new ArrayList(); // Strings 320 321 // Unknown chunks 322 public ArrayList unknownChunkType = new ArrayList(); // Strings 323 public ArrayList unknownChunkData = new ArrayList(); // byte arrays 324 325 /** 326 * Converts its parameter to another <code>String</code> which contains 327 * only printable Latin-1 characters but not leading, trailing, or 328 * consecutive spaces. 329 * 330 * @param s the <code>String</code> to convert. 331 * @return a printable Latin-1 <code>String</code> sans superfluous spaces. 332 */ 333 static String toPrintableLatin1(String s) { 334 // Pass a null right back. 335 if(s == null) return null; 336 337 // Get Latin-1 characters. 338 byte[] data = null; 339 try { 340 data = s.getBytes("ISO-8859-1"); 341 } catch(UnsupportedEncodingException e) { 342 // In theory this should not happen (assert). 343 data = s.getBytes(); 344 } 345 346 // Copy printable characters omitting leading spaces and 347 // all but first trailing space. 348 int len = 0; 349 int prev = 0; 350 for (int i = 0; i < data.length; i++) { 351 int d = data[i] & 0xFF; 352 if (prev == 32 && d == 32) 353 continue; 354 if ((d > 32 && d <=126) || (d >= 161 && d <=255) || 355 (d == 32 && len != 0)) 356 data[len++] = (byte)d; 357 prev = d; 358 } 359 360 // Return an empty string if no acceptable characters. 361 if(len == 0) return ""; 362 363 // Omit trailing space, if any. 364 if(data[len - 1] == 32) len--; 365 366 return new String(data, 0, len); 367 } 368 369 public CLibPNGMetadata() { 370 super(true, 371 nativeMetadataFormatName, 372 nativeMetadataFormatClassName, 373 null, null); 374 } 375 376 public CLibPNGMetadata(IIOMetadata metadata) 377 throws IIOInvalidTreeException { 378 379 this(); 380 381 if(metadata != null) { 382 List formats = Arrays.asList(metadata.getMetadataFormatNames()); 383 384 if(formats.contains(nativeMetadataFormatName)) { 385 // Initialize from native image metadata format. 386 String format = nativeMetadataFormatName; 387 setFromTree(format, metadata.getAsTree(format)); 388 } else if(metadata.isStandardMetadataFormatSupported()) { 389 // Initialize from standard metadata form of the input tree. 390 String format = 391 IIOMetadataFormatImpl.standardMetadataFormatName; 392 setFromTree(format, metadata.getAsTree(format)); 393 } 394 } 395 } 396 397 /** 398 * Sets the instance variables of the IHDR and if necessary PLTE and 399 * tRNS chunks. The <code>numBands</code> parameter is necessary since 400 * we may only be writing a subset of the image bands. 401 */ 402 public void initialize(ImageTypeSpecifier imageType, 403 int numBands, 404 ImageWriteParam param, 405 int interlaceMethod) { 406 ColorModel colorModel = imageType.getColorModel(); 407 SampleModel sampleModel = imageType.getSampleModel(); 408 409 // Intialize IHDR_width and IHDR_height 410 IHDR_width = sampleModel.getWidth(); 411 IHDR_height = sampleModel.getHeight(); 412 413 // Initialize IHDR_bitDepth 414 int[] sampleSize = sampleModel.getSampleSize(); 415 int bitDepth = sampleSize[0]; 416 // Choose max bit depth over all channels 417 // Fixes bug 4413109 418 for (int i = 1; i < sampleSize.length; i++) { 419 if (sampleSize[i] > bitDepth) { 420 bitDepth = sampleSize[i]; 421 } 422 } 423 // Multi-channel images must have a bit depth of 8 or 16 424 if (sampleSize.length > 1 && bitDepth < 8) { 425 bitDepth = 8; 426 } 427 428 // Round bit depth up to a power of 2 429 if (bitDepth > 2 && bitDepth < 4) { 430 bitDepth = 4; 431 } else if (bitDepth > 4 && bitDepth < 8) { 432 bitDepth = 8; 433 } else if (bitDepth > 8 && bitDepth < 16) { 434 bitDepth = 16; 435 } else if (bitDepth > 16) { 436 throw new RuntimeException("bitDepth > 16!"); 437 } 438 IHDR_bitDepth = bitDepth; 439 440 // Initialize IHDR_colorType 441 if (colorModel instanceof IndexColorModel) { 442 IndexColorModel icm = (IndexColorModel)colorModel; 443 int size = icm.getMapSize(); 444 445 byte[] reds = new byte[size]; 446 icm.getReds(reds); 447 byte[] greens = new byte[size]; 448 icm.getGreens(greens); 449 byte[] blues = new byte[size]; 450 icm.getBlues(blues); 451 452 // Determine whether the color tables are actually a gray ramp 453 // if the color type has not been set previously 454 boolean isGray = false; 455 if (!IHDR_present || 456 (IHDR_colorType != PNG_COLOR_PALETTE)) { 457 isGray = true; 458 int scale = 255/((1 << IHDR_bitDepth) - 1); 459 for (int i = 0; i < size; i++) { 460 byte red = reds[i]; 461 if ((red != (byte)(i*scale)) || 462 (red != greens[i]) || 463 (red != blues[i])) { 464 isGray = false; 465 break; 466 } 467 } 468 } 469 470 // Determine whether transparency exists 471 boolean hasAlpha = colorModel.hasAlpha(); 472 473 byte[] alpha = null; 474 if (hasAlpha) { 475 alpha = new byte[size]; 476 icm.getAlphas(alpha); 477 } 478 479 if (isGray && hasAlpha) { 480 IHDR_colorType = PNG_COLOR_GRAY_ALPHA; 481 } else if (isGray) { 482 IHDR_colorType = PNG_COLOR_GRAY; 483 } else { 484 IHDR_colorType = PNG_COLOR_PALETTE; 485 486 // Initialize PLTE chunk 487 PLTE_present = true; 488 PLTE_red = (byte[])reds.clone(); 489 PLTE_green = (byte[])greens.clone(); 490 PLTE_blue = (byte[])blues.clone(); 491 492 if (hasAlpha) { 493 // Initialize tRNS chunk 494 tRNS_present = true; 495 tRNS_colorType = PNG_COLOR_PALETTE; 496 tRNS_alpha = (byte[])alpha.clone(); 497 } 498 } 499 } else { 500 if (numBands == 1) { 501 IHDR_colorType = PNG_COLOR_GRAY; 502 } else if (numBands == 2) { 503 IHDR_colorType = PNG_COLOR_GRAY_ALPHA; 504 } else if (numBands == 3) { 505 IHDR_colorType = PNG_COLOR_RGB; 506 } else if (numBands == 4) { 507 IHDR_colorType = PNG_COLOR_RGB_ALPHA; 508 } else { 509 throw new RuntimeException("Number of bands not 1-4!"); 510 } 511 } 512 513 // Initialize IHDR_compressionMethod and IHDR_filterMethod 514 IHDR_compressionMethod = IHDR_filterMethod = 0; // Only supported value 515 516 // Initialize IHDR_interlaceMethod 517 if(param != null && 518 param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) { 519 IHDR_interlaceMethod = 0; // No interlacing. 520 } else if(param != null && 521 param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) { 522 IHDR_interlaceMethod = 1; // Adam7 523 } else { 524 // param == null || 525 // param.getProgressiveMode() == 526 // ImageWriteParam.MODE_COPY_FROM_METADATA 527 IHDR_interlaceMethod = interlaceMethod; 528 } 529 530 IHDR_present = true; 531 } 532 533 public boolean isReadOnly() { 534 return false; 535 } 536 537 private ArrayList cloneBytesArrayList(ArrayList in) { 538 if (in == null) { 539 return null; 540 } else { 541 ArrayList list = new ArrayList(in.size()); 542 Iterator iter = in.iterator(); 543 while (iter.hasNext()) { 544 Object o = iter.next(); 545 if (o == null) { 546 list.add(null); 547 } else { 548 list.add(((byte[])o).clone()); 549 } 550 } 551 552 return list; 553 } 554 } 555 556 // Deep clone 557 public Object clone() { 558 CLibPNGMetadata metadata; 559 try { 560 metadata = (CLibPNGMetadata)super.clone(); 561 } catch (CloneNotSupportedException e) { 562 return null; 563 } 564 565 // unknownChunkData needs deep clone 566 metadata.unknownChunkData = 567 cloneBytesArrayList(this.unknownChunkData); 568 569 return metadata; 570 } 571 572 public Node getAsTree(String formatName) { 573 if (formatName.equals(nativeMetadataFormatName)) { 574 return getNativeTree(); 575 } else if (formatName.equals 576 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 577 return getStandardTree(); 578 } else { 579 throw new IllegalArgumentException("Not a recognized format!"); 580 } 581 } 582 583 private Node getNativeTree() { 584 IIOMetadataNode node = null; // scratch node 585 IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); 586 587 // IHDR 588 if (IHDR_present) { 589 IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR"); 590 IHDR_node.setAttribute("width", Integer.toString(IHDR_width)); 591 IHDR_node.setAttribute("height", Integer.toString(IHDR_height)); 592 IHDR_node.setAttribute("bitDepth", 593 Integer.toString(IHDR_bitDepth)); 594 IHDR_node.setAttribute("colorType", 595 IHDR_colorTypeNames[IHDR_colorType]); 596 // IHDR_compressionMethod must be 0 in PNG 1.1 597 IHDR_node.setAttribute("compressionMethod", 598 IHDR_compressionMethodNames[IHDR_compressionMethod]); 599 // IHDR_filterMethod must be 0 in PNG 1.1 600 IHDR_node.setAttribute("filterMethod", 601 IHDR_filterMethodNames[IHDR_filterMethod]); 602 IHDR_node.setAttribute("interlaceMethod", 603 IHDR_interlaceMethodNames[IHDR_interlaceMethod]); 604 root.appendChild(IHDR_node); 605 } 606 607 // PLTE 608 if (PLTE_present) { 609 IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE"); 610 int numEntries = PLTE_red.length; 611 for (int i = 0; i < numEntries; i++) { 612 IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry"); 613 entry.setAttribute("index", Integer.toString(i)); 614 entry.setAttribute("red", 615 Integer.toString(PLTE_red[i] & 0xff)); 616 entry.setAttribute("green", 617 Integer.toString(PLTE_green[i] & 0xff)); 618 entry.setAttribute("blue", 619 Integer.toString(PLTE_blue[i] & 0xff)); 620 PLTE_node.appendChild(entry); 621 } 622 623 root.appendChild(PLTE_node); 624 } 625 626 // bKGD 627 if (bKGD_present) { 628 IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD"); 629 630 if (bKGD_colorType == PNG_COLOR_PALETTE) { 631 node = new IIOMetadataNode("bKGD_Palette"); 632 node.setAttribute("index", Integer.toString(bKGD_index)); 633 } else if (bKGD_colorType == PNG_COLOR_GRAY) { 634 node = new IIOMetadataNode("bKGD_Grayscale"); 635 node.setAttribute("gray", Integer.toString(bKGD_gray)); 636 } else if (bKGD_colorType == PNG_COLOR_RGB) { 637 node = new IIOMetadataNode("bKGD_RGB"); 638 node.setAttribute("red", Integer.toString(bKGD_red)); 639 node.setAttribute("green", Integer.toString(bKGD_green)); 640 node.setAttribute("blue", Integer.toString(bKGD_blue)); 641 } 642 bKGD_node.appendChild(node); 643 644 root.appendChild(bKGD_node); 645 } 646 647 // cHRM 648 if (cHRM_present) { 649 IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM"); 650 cHRM_node.setAttribute("whitePointX", 651 Integer.toString(cHRM_whitePointX)); 652 cHRM_node.setAttribute("whitePointY", 653 Integer.toString(cHRM_whitePointY)); 654 cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX)); 655 cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY)); 656 cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX)); 657 cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY)); 658 cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX)); 659 cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY)); 660 661 root.appendChild(cHRM_node); 662 } 663 664 // gAMA 665 if (gAMA_present) { 666 IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA"); 667 gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma)); 668 669 root.appendChild(gAMA_node); 670 } 671 672 // hIST 673 if (hIST_present) { 674 IIOMetadataNode hIST_node = new IIOMetadataNode("hIST"); 675 676 for (int i = 0; i < hIST_histogram.length; i++) { 677 IIOMetadataNode hist = 678 new IIOMetadataNode("hISTEntry"); 679 hist.setAttribute("index", Integer.toString(i)); 680 hist.setAttribute("value", 681 Integer.toString(hIST_histogram[i])); 682 hIST_node.appendChild(hist); 683 } 684 685 root.appendChild(hIST_node); 686 } 687 688 // iCCP 689 if (iCCP_present) { 690 IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP"); 691 iCCP_node.setAttribute("profileName", iCCP_profileName); 692 iCCP_node.setAttribute("compressionMethod", 693 iCCP_compressionMethodNames[iCCP_compressionMethod]); 694 695 Object profile = iCCP_compressedProfile; 696 if (profile != null) { 697 profile = ((byte[])profile).clone(); 698 } 699 iCCP_node.setUserObject(profile); 700 701 root.appendChild(iCCP_node); 702 } 703 704 // iTXt 705 if (iTXt_keyword.size() > 0) { 706 IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt"); 707 for (int i = 0; i < iTXt_keyword.size(); i++) { 708 Integer val; 709 710 IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry"); 711 iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i)); 712 val = (Integer)iTXt_compressionFlag.get(i); 713 iTXt_node.setAttribute("compressionFlag", val.toString()); 714 val = (Integer)iTXt_compressionMethod.get(i); 715 iTXt_node.setAttribute("compressionMethod", val.toString()); 716 iTXt_node.setAttribute("languageTag", 717 (String)iTXt_languageTag.get(i)); 718 iTXt_node.setAttribute("translatedKeyword", 719 (String)iTXt_translatedKeyword.get(i)); 720 iTXt_node.setAttribute("text", (String)iTXt_text.get(i)); 721 722 iTXt_parent.appendChild(iTXt_node); 723 } 724 725 root.appendChild(iTXt_parent); 726 } 727 728 // pHYs 729 if (pHYs_present) { 730 IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs"); 731 pHYs_node.setAttribute("pixelsPerUnitXAxis", 732 Integer.toString(pHYs_pixelsPerUnitXAxis)); 733 pHYs_node.setAttribute("pixelsPerUnitYAxis", 734 Integer.toString(pHYs_pixelsPerUnitYAxis)); 735 pHYs_node.setAttribute("unitSpecifier", 736 unitSpecifierNames[pHYs_unitSpecifier]); 737 738 root.appendChild(pHYs_node); 739 } 740 741 // sBIT 742 if (sBIT_present) { 743 IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT"); 744 745 if (sBIT_colorType == PNG_COLOR_GRAY) { 746 node = new IIOMetadataNode("sBIT_Grayscale"); 747 node.setAttribute("gray", 748 Integer.toString(sBIT_grayBits)); 749 } else if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA) { 750 node = new IIOMetadataNode("sBIT_GrayAlpha"); 751 node.setAttribute("gray", 752 Integer.toString(sBIT_grayBits)); 753 node.setAttribute("alpha", 754 Integer.toString(sBIT_alphaBits)); 755 } else if (sBIT_colorType == PNG_COLOR_RGB) { 756 node = new IIOMetadataNode("sBIT_RGB"); 757 node.setAttribute("red", 758 Integer.toString(sBIT_redBits)); 759 node.setAttribute("green", 760 Integer.toString(sBIT_greenBits)); 761 node.setAttribute("blue", 762 Integer.toString(sBIT_blueBits)); 763 } else if (sBIT_colorType == PNG_COLOR_RGB_ALPHA) { 764 node = new IIOMetadataNode("sBIT_RGBAlpha"); 765 node.setAttribute("red", 766 Integer.toString(sBIT_redBits)); 767 node.setAttribute("green", 768 Integer.toString(sBIT_greenBits)); 769 node.setAttribute("blue", 770 Integer.toString(sBIT_blueBits)); 771 node.setAttribute("alpha", 772 Integer.toString(sBIT_alphaBits)); 773 } else if (sBIT_colorType == PNG_COLOR_PALETTE) { 774 node = new IIOMetadataNode("sBIT_Palette"); 775 node.setAttribute("red", 776 Integer.toString(sBIT_redBits)); 777 node.setAttribute("green", 778 Integer.toString(sBIT_greenBits)); 779 node.setAttribute("blue", 780 Integer.toString(sBIT_blueBits)); 781 } 782 sBIT_node.appendChild(node); 783 784 root.appendChild(sBIT_node); 785 } 786 787 // sPLT 788 if (sPLT_present) { 789 IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT"); 790 791 sPLT_node.setAttribute("name", sPLT_paletteName); 792 sPLT_node.setAttribute("sampleDepth", 793 Integer.toString(sPLT_sampleDepth)); 794 795 int numEntries = sPLT_red.length; 796 for (int i = 0; i < numEntries; i++) { 797 IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry"); 798 entry.setAttribute("index", Integer.toString(i)); 799 entry.setAttribute("red", Integer.toString(sPLT_red[i])); 800 entry.setAttribute("green", Integer.toString(sPLT_green[i])); 801 entry.setAttribute("blue", Integer.toString(sPLT_blue[i])); 802 entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i])); 803 entry.setAttribute("frequency", 804 Integer.toString(sPLT_frequency[i])); 805 sPLT_node.appendChild(entry); 806 } 807 808 root.appendChild(sPLT_node); 809 } 810 811 // sRGB 812 if (sRGB_present) { 813 IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB"); 814 sRGB_node.setAttribute("renderingIntent", 815 renderingIntentNames[sRGB_renderingIntent]); 816 817 root.appendChild(sRGB_node); 818 } 819 820 // tEXt 821 if (tEXt_keyword.size() > 0) { 822 IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt"); 823 for (int i = 0; i < tEXt_keyword.size(); i++) { 824 IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry"); 825 tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i)); 826 tEXt_node.setAttribute("value" , (String)tEXt_text.get(i)); 827 828 tEXt_parent.appendChild(tEXt_node); 829 } 830 831 root.appendChild(tEXt_parent); 832 } 833 834 // tIME 835 if (tIME_present) { 836 IIOMetadataNode tIME_node = new IIOMetadataNode("tIME"); 837 tIME_node.setAttribute("year", Integer.toString(tIME_year)); 838 tIME_node.setAttribute("month", Integer.toString(tIME_month)); 839 tIME_node.setAttribute("day", Integer.toString(tIME_day)); 840 tIME_node.setAttribute("hour", Integer.toString(tIME_hour)); 841 tIME_node.setAttribute("minute", Integer.toString(tIME_minute)); 842 tIME_node.setAttribute("second", Integer.toString(tIME_second)); 843 844 root.appendChild(tIME_node); 845 } 846 847 // tRNS 848 if (tRNS_present) { 849 IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS"); 850 851 if (tRNS_colorType == PNG_COLOR_PALETTE) { 852 node = new IIOMetadataNode("tRNS_Palette"); 853 854 for (int i = 0; i < tRNS_alpha.length; i++) { 855 IIOMetadataNode entry = 856 new IIOMetadataNode("tRNS_PaletteEntry"); 857 entry.setAttribute("index", Integer.toString(i)); 858 entry.setAttribute("alpha", 859 Integer.toString(tRNS_alpha[i] & 0xff)); 860 node.appendChild(entry); 861 } 862 } else if (tRNS_colorType == PNG_COLOR_GRAY) { 863 node = new IIOMetadataNode("tRNS_Grayscale"); 864 node.setAttribute("gray", Integer.toString(tRNS_gray)); 865 } else if (tRNS_colorType == PNG_COLOR_RGB) { 866 node = new IIOMetadataNode("tRNS_RGB"); 867 node.setAttribute("red", Integer.toString(tRNS_red)); 868 node.setAttribute("green", Integer.toString(tRNS_green)); 869 node.setAttribute("blue", Integer.toString(tRNS_blue)); 870 } 871 tRNS_node.appendChild(node); 872 873 root.appendChild(tRNS_node); 874 } 875 876 // zTXt 877 if (zTXt_keyword.size() > 0) { 878 IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt"); 879 for (int i = 0; i < zTXt_keyword.size(); i++) { 880 IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry"); 881 zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i)); 882 883 int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue(); 884 zTXt_node.setAttribute("compressionMethod", 885 zTXt_compressionMethodNames[cm]); 886 887 zTXt_node.setAttribute("text", (String)zTXt_text.get(i)); 888 889 zTXt_parent.appendChild(zTXt_node); 890 } 891 892 root.appendChild(zTXt_parent); 893 } 894 895 // Unknown chunks 896 if (unknownChunkType.size() > 0) { 897 IIOMetadataNode unknown_parent = 898 new IIOMetadataNode("UnknownChunks"); 899 for (int i = 0; i < unknownChunkType.size(); i++) { 900 IIOMetadataNode unknown_node = 901 new IIOMetadataNode("UnknownChunk"); 902 unknown_node.setAttribute("type", 903 (String)unknownChunkType.get(i)); 904 unknown_node.setUserObject((byte[])unknownChunkData.get(i)); 905 906 unknown_parent.appendChild(unknown_node); 907 } 908 909 root.appendChild(unknown_parent); 910 } 911 912 return root; 913 } 914 915 private int getNumChannels() { 916 // Determine number of channels 917 // Be careful about palette color with transparency 918 int numChannels = IHDR_numChannels[IHDR_colorType]; 919 if (IHDR_colorType == PNG_COLOR_PALETTE && 920 tRNS_present && tRNS_colorType == IHDR_colorType) { 921 numChannels = 4; 922 } 923 return numChannels; 924 } 925 926 public IIOMetadataNode getStandardChromaNode() { 927 IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); 928 IIOMetadataNode node = null; // scratch node 929 930 node = new IIOMetadataNode("ColorSpaceType"); 931 node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]); 932 chroma_node.appendChild(node); 933 934 node = new IIOMetadataNode("NumChannels"); 935 node.setAttribute("value", Integer.toString(getNumChannels())); 936 chroma_node.appendChild(node); 937 938 if (gAMA_present) { 939 node = new IIOMetadataNode("Gamma"); 940 node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); 941 chroma_node.appendChild(node); 942 } 943 944 node = new IIOMetadataNode("BlackIsZero"); 945 node.setAttribute("value", "TRUE"); 946 chroma_node.appendChild(node); 947 948 if (PLTE_present) { 949 boolean hasAlpha = tRNS_present && 950 (tRNS_colorType == PNG_COLOR_PALETTE); 951 952 node = new IIOMetadataNode("Palette"); 953 for (int i = 0; i < PLTE_red.length; i++) { 954 IIOMetadataNode entry = 955 new IIOMetadataNode("PaletteEntry"); 956 entry.setAttribute("index", Integer.toString(i)); 957 entry.setAttribute("red", 958 Integer.toString(PLTE_red[i] & 0xff)); 959 entry.setAttribute("green", 960 Integer.toString(PLTE_green[i] & 0xff)); 961 entry.setAttribute("blue", 962 Integer.toString(PLTE_blue[i] & 0xff)); 963 if (hasAlpha) { 964 int alpha = (i < tRNS_alpha.length) ? 965 (tRNS_alpha[i] & 0xff) : 255; 966 entry.setAttribute("alpha", Integer.toString(alpha)); 967 } 968 node.appendChild(entry); 969 } 970 chroma_node.appendChild(node); 971 } 972 973 if (bKGD_present) { 974 if (bKGD_colorType == PNG_COLOR_PALETTE) { 975 node = new IIOMetadataNode("BackgroundIndex"); 976 node.setAttribute("value", Integer.toString(bKGD_index)); 977 } else { 978 node = new IIOMetadataNode("BackgroundColor"); 979 int r, g, b; 980 981 if (bKGD_colorType == PNG_COLOR_GRAY) { 982 r = g = b = bKGD_gray; 983 } else { 984 r = bKGD_red; 985 g = bKGD_green; 986 b = bKGD_blue; 987 } 988 node.setAttribute("red", Integer.toString(r)); 989 node.setAttribute("green", Integer.toString(g)); 990 node.setAttribute("blue", Integer.toString(b)); 991 } 992 chroma_node.appendChild(node); 993 } 994 995 return chroma_node; 996 } 997 998 public IIOMetadataNode getStandardCompressionNode() { 999 IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); 1000 IIOMetadataNode node = null; // scratch node 1001 1002 node = new IIOMetadataNode("CompressionTypeName"); 1003 node.setAttribute("value", "deflate"); 1004 compression_node.appendChild(node); 1005 1006 node = new IIOMetadataNode("Lossless"); 1007 node.setAttribute("value", "TRUE"); 1008 compression_node.appendChild(node); 1009 1010 node = new IIOMetadataNode("NumProgressiveScans"); 1011 node.setAttribute("value", 1012 (IHDR_interlaceMethod == 0) ? "1" : "7"); 1013 compression_node.appendChild(node); 1014 1015 return compression_node; 1016 } 1017 1018 private String repeat(String s, int times) { 1019 if (times == 1) { 1020 return s; 1021 } 1022 StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1); 1023 sb.append(s); 1024 for (int i = 1; i < times; i++) { 1025 sb.append(" "); 1026 sb.append(s); 1027 } 1028 return sb.toString(); 1029 } 1030 1031 public IIOMetadataNode getStandardDataNode() { 1032 IIOMetadataNode data_node = new IIOMetadataNode("Data"); 1033 IIOMetadataNode node = null; // scratch node 1034 1035 node = new IIOMetadataNode("PlanarConfiguration"); 1036 node.setAttribute("value", "PixelInterleaved"); 1037 data_node.appendChild(node); 1038 1039 node = new IIOMetadataNode("SampleFormat"); 1040 node.setAttribute("value", 1041 IHDR_colorType == PNG_COLOR_PALETTE ? 1042 "Index" : "UnsignedIntegral"); 1043 data_node.appendChild(node); 1044 1045 String bitDepth = Integer.toString(IHDR_bitDepth); 1046 node = new IIOMetadataNode("BitsPerSample"); 1047 node.setAttribute("value", repeat(bitDepth, getNumChannels())); 1048 data_node.appendChild(node); 1049 1050 if (sBIT_present) { 1051 node = new IIOMetadataNode("SignificantBitsPerSample"); 1052 String sbits; 1053 if (sBIT_colorType == PNG_COLOR_GRAY || 1054 sBIT_colorType == PNG_COLOR_GRAY_ALPHA) { 1055 sbits = Integer.toString(sBIT_grayBits); 1056 } else { // sBIT_colorType == PNG_COLOR_RGB || 1057 // sBIT_colorType == PNG_COLOR_RGB_ALPHA 1058 sbits = Integer.toString(sBIT_redBits) + " " + 1059 Integer.toString(sBIT_greenBits) + " " + 1060 Integer.toString(sBIT_blueBits); 1061 } 1062 1063 if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA || 1064 sBIT_colorType == PNG_COLOR_RGB_ALPHA) { 1065 sbits += " " + Integer.toString(sBIT_alphaBits); 1066 } 1067 1068 node.setAttribute("value", sbits); 1069 data_node.appendChild(node); 1070 } 1071 1072 // SampleMSB 1073 1074 return data_node; 1075 } 1076 1077 public IIOMetadataNode getStandardDimensionNode() { 1078 IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); 1079 IIOMetadataNode node = null; // scratch node 1080 1081 node = new IIOMetadataNode("PixelAspectRatio"); 1082 // aspect ratio is pixel width/height which is the ratio of the 1083 // inverses of pixels per unit length. 1084 float ratio = pHYs_present ? 1085 (float)pHYs_pixelsPerUnitYAxis/pHYs_pixelsPerUnitXAxis : 1.0F; 1086 node.setAttribute("value", Float.toString(ratio)); 1087 dimension_node.appendChild(node); 1088 1089 node = new IIOMetadataNode("ImageOrientation"); 1090 node.setAttribute("value", "Normal"); 1091 dimension_node.appendChild(node); 1092 1093 if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) { 1094 node = new IIOMetadataNode("HorizontalPixelSize"); 1095 node.setAttribute("value", 1096 Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis)); 1097 dimension_node.appendChild(node); 1098 1099 node = new IIOMetadataNode("VerticalPixelSize"); 1100 node.setAttribute("value", 1101 Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis)); 1102 dimension_node.appendChild(node); 1103 } 1104 1105 return dimension_node; 1106 } 1107 1108 public IIOMetadataNode getStandardDocumentNode() { 1109 if (!tIME_present) { 1110 return null; 1111 } 1112 1113 IIOMetadataNode document_node = new IIOMetadataNode("Document"); 1114 IIOMetadataNode node = null; // scratch node 1115 1116 node = new IIOMetadataNode("ImageModificationTime"); 1117 node.setAttribute("year", Integer.toString(tIME_year)); 1118 node.setAttribute("month", Integer.toString(tIME_month)); 1119 node.setAttribute("day", Integer.toString(tIME_day)); 1120 node.setAttribute("hour", Integer.toString(tIME_hour)); 1121 node.setAttribute("minute", Integer.toString(tIME_minute)); 1122 node.setAttribute("second", Integer.toString(tIME_second)); 1123 document_node.appendChild(node); 1124 1125 return document_node; 1126 } 1127 1128 public IIOMetadataNode getStandardTextNode() { 1129 int numEntries = tEXt_keyword.size() + 1130 iTXt_keyword.size() + zTXt_keyword.size(); 1131 if (numEntries == 0) { 1132 return null; 1133 } 1134 1135 IIOMetadataNode text_node = new IIOMetadataNode("Text"); 1136 IIOMetadataNode node = null; // scratch node 1137 1138 for (int i = 0; i < tEXt_keyword.size(); i++) { 1139 node = new IIOMetadataNode("TextEntry"); 1140 node.setAttribute("keyword", (String)tEXt_keyword.get(i)); 1141 node.setAttribute("value", (String)tEXt_text.get(i)); 1142 node.setAttribute("encoding", "ISO-8859-1"); 1143 node.setAttribute("compression", "none"); 1144 1145 text_node.appendChild(node); 1146 } 1147 1148 for (int i = 0; i < iTXt_keyword.size(); i++) { 1149 node = new IIOMetadataNode("TextEntry"); 1150 node.setAttribute("keyword", (String)iTXt_keyword.get(i)); 1151 node.setAttribute("value", (String)iTXt_text.get(i)); 1152 node.setAttribute("language", 1153 (String)iTXt_languageTag.get(i)); 1154 if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) { 1155 node.setAttribute("compression", "deflate"); 1156 } else { 1157 node.setAttribute("compression", "none"); 1158 } 1159 1160 text_node.appendChild(node); 1161 } 1162 1163 for (int i = 0; i < zTXt_keyword.size(); i++) { 1164 node = new IIOMetadataNode("TextEntry"); 1165 node.setAttribute("keyword", (String)zTXt_keyword.get(i)); 1166 node.setAttribute("value", (String)zTXt_text.get(i)); 1167 node.setAttribute("compression", "deflate"); 1168 1169 text_node.appendChild(node); 1170 } 1171 1172 return text_node; 1173 } 1174 1175 public IIOMetadataNode getStandardTransparencyNode() { 1176 IIOMetadataNode transparency_node = 1177 new IIOMetadataNode("Transparency"); 1178 IIOMetadataNode node = null; // scratch node 1179 1180 node = new IIOMetadataNode("Alpha"); 1181 boolean hasAlpha = 1182 (IHDR_colorType == PNG_COLOR_RGB_ALPHA) || 1183 (IHDR_colorType == PNG_COLOR_GRAY_ALPHA) || 1184 (IHDR_colorType == PNG_COLOR_PALETTE && 1185 tRNS_present && 1186 (tRNS_colorType == IHDR_colorType) && 1187 (tRNS_alpha != null)); 1188 node.setAttribute("value", hasAlpha ? "nonpremultiplied" : "none"); 1189 transparency_node.appendChild(node); 1190 1191 if (tRNS_present) { 1192 if(tRNS_colorType == PNG_COLOR_RGB || 1193 tRNS_colorType == PNG_COLOR_GRAY) { 1194 node = new IIOMetadataNode("TransparentColor"); 1195 if (tRNS_colorType == PNG_COLOR_RGB) { 1196 node.setAttribute("value", 1197 Integer.toString(tRNS_red) + " " + 1198 Integer.toString(tRNS_green) + " " + 1199 Integer.toString(tRNS_blue)); 1200 } else if (tRNS_colorType == PNG_COLOR_GRAY) { 1201 node.setAttribute("value", Integer.toString(tRNS_gray)); 1202 } 1203 transparency_node.appendChild(node); 1204 } 1205 } 1206 1207 return transparency_node; 1208 } 1209 1210 // Shorthand for throwing an IIOInvalidTreeException 1211 private void fatal(Node node, String reason) 1212 throws IIOInvalidTreeException { 1213 throw new IIOInvalidTreeException(reason, node); 1214 } 1215 1216 // Get an integer-valued attribute 1217 private int getIntAttribute(Node node, String name, 1218 int defaultValue, boolean required) 1219 throws IIOInvalidTreeException { 1220 String value = getAttribute(node, name, null, required); 1221 if (value == null) { 1222 return defaultValue; 1223 } 1224 return Integer.parseInt(value); 1225 } 1226 1227 // Get a float-valued attribute 1228 private float getFloatAttribute(Node node, String name, 1229 float defaultValue, boolean required) 1230 throws IIOInvalidTreeException { 1231 String value = getAttribute(node, name, null, required); 1232 if (value == null) { 1233 return defaultValue; 1234 } 1235 return Float.parseFloat(value); 1236 } 1237 1238 // Get a required integer-valued attribute 1239 private int getIntAttribute(Node node, String name) 1240 throws IIOInvalidTreeException { 1241 return getIntAttribute(node, name, -1, true); 1242 } 1243 1244 // Get a required float-valued attribute 1245 private float getFloatAttribute(Node node, String name) 1246 throws IIOInvalidTreeException { 1247 return getFloatAttribute(node, name, -1.0F, true); 1248 } 1249 1250 // Get a boolean-valued attribute 1251 private boolean getBooleanAttribute(Node node, String name, 1252 boolean defaultValue, 1253 boolean required) 1254 throws IIOInvalidTreeException { 1255 Node attr = node.getAttributes().getNamedItem(name); 1256 if (attr == null) { 1257 if (!required) { 1258 return defaultValue; 1259 } else { 1260 fatal(node, "Required attribute " + name + " not present!"); 1261 } 1262 } 1263 1264 String value = attr.getNodeValue(); 1265 1266 if (value.equalsIgnoreCase("true")) { 1267 return true; 1268 } else if (value.equalsIgnoreCase("false")) { 1269 return false; 1270 } else { 1271 fatal(node, "Attribute " + name + " must be 'true' or 'false'!"); 1272 return false; 1273 } 1274 } 1275 1276 // Get a required boolean-valued attribute 1277 private boolean getBooleanAttribute(Node node, String name) 1278 throws IIOInvalidTreeException { 1279 return getBooleanAttribute(node, name, false, true); 1280 } 1281 1282 // Get an enumerated attribute as an index into a String array 1283 private int getEnumeratedAttribute(Node node, 1284 String name, String[] legalNames, 1285 int defaultValue, boolean required) 1286 throws IIOInvalidTreeException { 1287 Node attr = node.getAttributes().getNamedItem(name); 1288 if (attr == null) { 1289 if (!required) { 1290 return defaultValue; 1291 } else { 1292 fatal(node, "Required attribute " + name + " not present!"); 1293 } 1294 } 1295 1296 String value = attr.getNodeValue(); 1297 1298 for (int i = 0; i < legalNames.length; i++) { 1299 if (value.equals(legalNames[i])) { 1300 return i; 1301 } 1302 } 1303 1304 fatal(node, "Illegal value for attribute " + name + "!"); 1305 return -1; 1306 } 1307 1308 // Get a required enumerated attribute as an index into a String array 1309 private int getEnumeratedAttribute(Node node, 1310 String name, String[] legalNames) 1311 throws IIOInvalidTreeException { 1312 return getEnumeratedAttribute(node, name, legalNames, -1, true); 1313 } 1314 1315 // Get a String-valued attribute 1316 private String getAttribute(Node node, String name, 1317 String defaultValue, boolean required) 1318 throws IIOInvalidTreeException { 1319 Node attr = node.getAttributes().getNamedItem(name); 1320 if (attr == null) { 1321 if (!required) { 1322 return defaultValue; 1323 } else { 1324 fatal(node, "Required attribute " + name + " not present!"); 1325 } 1326 } 1327 return attr.getNodeValue(); 1328 } 1329 1330 // Get a required String-valued attribute 1331 private String getAttribute(Node node, String name) 1332 throws IIOInvalidTreeException { 1333 return getAttribute(node, name, null, true); 1334 } 1335 1336 public void mergeTree(String formatName, Node root) 1337 throws IIOInvalidTreeException { 1338 if (formatName.equals(nativeMetadataFormatName)) { 1339 if (root == null) { 1340 throw new IllegalArgumentException("root == null!"); 1341 } 1342 mergeNativeTree(root); 1343 } else if (formatName.equals 1344 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 1345 if (root == null) { 1346 throw new IllegalArgumentException("root == null!"); 1347 } 1348 mergeStandardTree(root); 1349 } else { 1350 throw new IllegalArgumentException("Not a recognized format!"); 1351 } 1352 } 1353 1354 private void mergeNativeTree(Node root) 1355 throws IIOInvalidTreeException { 1356 Node node = root; 1357 if (!node.getNodeName().equals(nativeMetadataFormatName)) { 1358 fatal(node, "Root must be " + nativeMetadataFormatName); 1359 } 1360 1361 node = node.getFirstChild(); 1362 while (node != null) { 1363 String name = node.getNodeName(); 1364 1365 if (name.equals("IHDR")) { 1366 IHDR_width = getIntAttribute(node, "width"); 1367 IHDR_height = getIntAttribute(node, "height"); 1368 IHDR_bitDepth = getEnumeratedAttribute(node, "bitDepth", 1369 IHDR_bitDepths); 1370 IHDR_colorType = getEnumeratedAttribute(node, "colorType", 1371 IHDR_colorTypeNames); 1372 IHDR_compressionMethod = 1373 getEnumeratedAttribute(node, "compressionMethod", 1374 IHDR_compressionMethodNames); 1375 IHDR_filterMethod = 1376 getEnumeratedAttribute(node, 1377 "filterMethod", 1378 IHDR_filterMethodNames); 1379 IHDR_interlaceMethod = 1380 getEnumeratedAttribute(node, "interlaceMethod", 1381 IHDR_interlaceMethodNames); 1382 IHDR_present = true; 1383 } else if (name.equals("PLTE")) { 1384 byte[] red = new byte[256]; 1385 byte[] green = new byte[256]; 1386 byte[] blue = new byte[256]; 1387 int maxindex = -1; 1388 1389 Node PLTE_entry = node.getFirstChild(); 1390 if (PLTE_entry == null) { 1391 fatal(node, "Palette has no entries!"); 1392 } 1393 1394 while (PLTE_entry != null) { 1395 if (!PLTE_entry.getNodeName().equals("PLTEEntry")) { 1396 fatal(node, 1397 "Only a PLTEEntry may be a child of a PLTE!"); 1398 } 1399 1400 int index = getIntAttribute(PLTE_entry, "index"); 1401 if (index < 0 || index > 255) { 1402 fatal(node, 1403 "Bad value for PLTEEntry attribute index!"); 1404 } 1405 if (index > maxindex) { 1406 maxindex = index; 1407 } 1408 red[index] = 1409 (byte)getIntAttribute(PLTE_entry, "red"); 1410 green[index] = 1411 (byte)getIntAttribute(PLTE_entry, "green"); 1412 blue[index] = 1413 (byte)getIntAttribute(PLTE_entry, "blue"); 1414 1415 PLTE_entry = PLTE_entry.getNextSibling(); 1416 } 1417 1418 int numEntries = maxindex + 1; 1419 PLTE_red = new byte[numEntries]; 1420 PLTE_green = new byte[numEntries]; 1421 PLTE_blue = new byte[numEntries]; 1422 System.arraycopy(red, 0, PLTE_red, 0, numEntries); 1423 System.arraycopy(green, 0, PLTE_green, 0, numEntries); 1424 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); 1425 PLTE_present = true; 1426 } else if (name.equals("bKGD")) { 1427 bKGD_present = false; // Guard against partial overwrite 1428 Node bKGD_node = node.getFirstChild(); 1429 if (bKGD_node == null) { 1430 fatal(node, "bKGD node has no children!"); 1431 } 1432 String bKGD_name = bKGD_node.getNodeName(); 1433 if (bKGD_name.equals("bKGD_Palette")) { 1434 bKGD_index = getIntAttribute(bKGD_node, "index"); 1435 bKGD_colorType = PNG_COLOR_PALETTE; 1436 } else if (bKGD_name.equals("bKGD_Grayscale")) { 1437 bKGD_gray = getIntAttribute(bKGD_node, "gray"); 1438 bKGD_colorType = PNG_COLOR_GRAY; 1439 } else if (bKGD_name.equals("bKGD_RGB")) { 1440 bKGD_red = getIntAttribute(bKGD_node, "red"); 1441 bKGD_green = getIntAttribute(bKGD_node, "green"); 1442 bKGD_blue = getIntAttribute(bKGD_node, "blue"); 1443 bKGD_colorType = PNG_COLOR_RGB; 1444 } else { 1445 fatal(node, "Bad child of a bKGD node!"); 1446 } 1447 if (bKGD_node.getNextSibling() != null) { 1448 fatal(node, "bKGD node has more than one child!"); 1449 } 1450 1451 bKGD_present = true; 1452 } else if (name.equals("cHRM")) { 1453 cHRM_whitePointX = getIntAttribute(node, "whitePointX"); 1454 cHRM_whitePointY = getIntAttribute(node, "whitePointY"); 1455 cHRM_redX = getIntAttribute(node, "redX"); 1456 cHRM_redY = getIntAttribute(node, "redY"); 1457 cHRM_greenX = getIntAttribute(node, "greenX"); 1458 cHRM_greenY = getIntAttribute(node, "greenY"); 1459 cHRM_blueX = getIntAttribute(node, "blueX"); 1460 cHRM_blueY = getIntAttribute(node, "blueY"); 1461 1462 cHRM_present = true; 1463 } else if (name.equals("gAMA")) { 1464 gAMA_gamma = getIntAttribute(node, "value"); 1465 gAMA_present = true; 1466 } else if (name.equals("hIST")) { 1467 char[] hist = new char[256]; 1468 int maxindex = -1; 1469 1470 Node hIST_entry = node.getFirstChild(); 1471 if (hIST_entry == null) { 1472 fatal(node, "hIST node has no children!"); 1473 } 1474 1475 while (hIST_entry != null) { 1476 if (!hIST_entry.getNodeName().equals("hISTEntry")) { 1477 fatal(node, 1478 "Only a hISTEntry may be a child of a hIST!"); 1479 } 1480 1481 int index = getIntAttribute(hIST_entry, "index"); 1482 if (index < 0 || index > 255) { 1483 fatal(node, 1484 "Bad value for histEntry attribute index!"); 1485 } 1486 if (index > maxindex) { 1487 maxindex = index; 1488 } 1489 hist[index] = 1490 (char)getIntAttribute(hIST_entry, "value"); 1491 1492 hIST_entry = hIST_entry.getNextSibling(); 1493 } 1494 1495 int numEntries = maxindex + 1; 1496 hIST_histogram = new char[numEntries]; 1497 System.arraycopy(hist, 0, hIST_histogram, 0, numEntries); 1498 1499 hIST_present = true; 1500 } else if (name.equals("iCCP")) { 1501 iCCP_profileName = 1502 toPrintableLatin1(getAttribute(node, "profileName")); 1503 iCCP_compressionMethod = 1504 getEnumeratedAttribute(node, "compressionMethod", 1505 iCCP_compressionMethodNames); 1506 Object compressedProfile = 1507 ((IIOMetadataNode)node).getUserObject(); 1508 if (compressedProfile == null) { 1509 fatal(node, "No ICCP profile present in user object!"); 1510 } 1511 if (!(compressedProfile instanceof byte[])) { 1512 fatal(node, "User object not a byte array!"); 1513 } 1514 1515 iCCP_compressedProfile = 1516 (byte[])((byte[])compressedProfile).clone(); 1517 1518 iCCP_present = true; 1519 } else if (name.equals("iTXt")) { 1520 Node iTXt_node = node.getFirstChild(); 1521 while (iTXt_node != null) { 1522 if (!iTXt_node.getNodeName().equals("iTXtEntry")) { 1523 fatal(node, 1524 "Only an iTXtEntry may be a child of an iTXt!"); 1525 } 1526 1527 String keyword = 1528 toPrintableLatin1(getAttribute(iTXt_node, "keyword")); 1529 iTXt_keyword.add(keyword); 1530 1531 boolean compressionFlag = 1532 getBooleanAttribute(iTXt_node, "compressionFlag"); 1533 iTXt_compressionFlag.add(new Boolean(compressionFlag)); 1534 1535 String compressionMethod = 1536 getAttribute(iTXt_node, "compressionMethod"); 1537 iTXt_compressionMethod.add(compressionMethod); 1538 1539 String languageTag = 1540 getAttribute(iTXt_node, "languageTag"); 1541 iTXt_languageTag.add(languageTag); 1542 1543 String translatedKeyword = 1544 getAttribute(iTXt_node, "translatedKeyword"); 1545 iTXt_translatedKeyword.add(translatedKeyword); 1546 1547 String text = getAttribute(iTXt_node, "text"); 1548 iTXt_text.add(text); 1549 1550 iTXt_node = iTXt_node.getNextSibling(); 1551 } 1552 } else if (name.equals("pHYs")) { 1553 pHYs_pixelsPerUnitXAxis = 1554 getIntAttribute(node, "pixelsPerUnitXAxis"); 1555 pHYs_pixelsPerUnitYAxis = 1556 getIntAttribute(node, "pixelsPerUnitYAxis"); 1557 pHYs_unitSpecifier = 1558 getEnumeratedAttribute(node, "unitSpecifier", 1559 unitSpecifierNames); 1560 1561 pHYs_present = true; 1562 } else if (name.equals("sBIT")) { 1563 sBIT_present = false; // Guard against partial overwrite 1564 Node sBIT_node = node.getFirstChild(); 1565 if (sBIT_node == null) { 1566 fatal(node, "sBIT node has no children!"); 1567 } 1568 String sBIT_name = sBIT_node.getNodeName(); 1569 if (sBIT_name.equals("sBIT_Grayscale")) { 1570 sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); 1571 sBIT_colorType = PNG_COLOR_GRAY; 1572 } else if (sBIT_name.equals("sBIT_GrayAlpha")) { 1573 sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); 1574 sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); 1575 sBIT_colorType = PNG_COLOR_GRAY_ALPHA; 1576 } else if (sBIT_name.equals("sBIT_RGB")) { 1577 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1578 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1579 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1580 sBIT_colorType = PNG_COLOR_RGB; 1581 } else if (sBIT_name.equals("sBIT_RGBAlpha")) { 1582 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1583 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1584 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1585 sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); 1586 sBIT_colorType = PNG_COLOR_RGB_ALPHA; 1587 } else if (sBIT_name.equals("sBIT_Palette")) { 1588 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1589 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1590 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1591 sBIT_colorType = PNG_COLOR_PALETTE; 1592 } else { 1593 fatal(node, "Bad child of an sBIT node!"); 1594 } 1595 if (sBIT_node.getNextSibling() != null) { 1596 fatal(node, "sBIT node has more than one child!"); 1597 } 1598 1599 sBIT_present = true; 1600 } else if (name.equals("sPLT")) { 1601 sPLT_paletteName = 1602 toPrintableLatin1(getAttribute(node, "name")); 1603 sPLT_sampleDepth = getIntAttribute(node, "sampleDepth"); 1604 1605 int[] red = new int[256]; 1606 int[] green = new int[256]; 1607 int[] blue = new int[256]; 1608 int[] alpha = new int[256]; 1609 int[] frequency = new int[256]; 1610 int maxindex = -1; 1611 1612 Node sPLT_entry = node.getFirstChild(); 1613 if (sPLT_entry == null) { 1614 fatal(node, "sPLT node has no children!"); 1615 } 1616 1617 while (sPLT_entry != null) { 1618 if (!sPLT_entry.getNodeName().equals("sPLTEntry")) { 1619 fatal(node, 1620 "Only an sPLTEntry may be a child of an sPLT!"); 1621 } 1622 1623 int index = getIntAttribute(sPLT_entry, "index"); 1624 if (index < 0 || index > 255) { 1625 fatal(node, 1626 "Bad value for PLTEEntry attribute index!"); 1627 } 1628 if (index > maxindex) { 1629 maxindex = index; 1630 } 1631 red[index] = getIntAttribute(sPLT_entry, "red"); 1632 green[index] = getIntAttribute(sPLT_entry, "green"); 1633 blue[index] = getIntAttribute(sPLT_entry, "blue"); 1634 alpha[index] = getIntAttribute(sPLT_entry, "alpha"); 1635 frequency[index] = 1636 getIntAttribute(sPLT_entry, "frequency"); 1637 1638 sPLT_entry = sPLT_entry.getNextSibling(); 1639 } 1640 1641 int numEntries = maxindex + 1; 1642 sPLT_red = new int[numEntries]; 1643 sPLT_green = new int[numEntries]; 1644 sPLT_blue = new int[numEntries]; 1645 sPLT_alpha = new int[numEntries]; 1646 sPLT_frequency = new int[numEntries]; 1647 System.arraycopy(red, 0, sPLT_red, 0, numEntries); 1648 System.arraycopy(green, 0, sPLT_green, 0, numEntries); 1649 System.arraycopy(blue, 0, sPLT_blue, 0, numEntries); 1650 System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries); 1651 System.arraycopy(frequency, 0, 1652 sPLT_frequency, 0, numEntries); 1653 1654 sPLT_present = true; 1655 } else if (name.equals("sRGB")) { 1656 sRGB_renderingIntent = 1657 getEnumeratedAttribute(node, "renderingIntent", 1658 renderingIntentNames); 1659 1660 sRGB_present = true; 1661 } else if (name.equals("tEXt")) { 1662 Node tEXt_node = node.getFirstChild(); 1663 while (tEXt_node != null) { 1664 if (!tEXt_node.getNodeName().equals("tEXtEntry")) { 1665 fatal(node, 1666 "Only an tEXtEntry may be a child of an tEXt!"); 1667 } 1668 1669 String keyword = 1670 toPrintableLatin1(getAttribute(tEXt_node, "keyword")); 1671 tEXt_keyword.add(keyword); 1672 1673 String text = getAttribute(tEXt_node, "value"); 1674 tEXt_text.add(text); 1675 1676 tEXt_node = tEXt_node.getNextSibling(); 1677 } 1678 } else if (name.equals("tIME")) { 1679 tIME_year = getIntAttribute(node, "year"); 1680 tIME_month = getIntAttribute(node, "month"); 1681 tIME_day = getIntAttribute(node, "day"); 1682 tIME_hour = getIntAttribute(node, "hour"); 1683 tIME_minute = getIntAttribute(node, "minute"); 1684 tIME_second = getIntAttribute(node, "second"); 1685 1686 tIME_present = true; 1687 } else if (name.equals("tRNS")) { 1688 tRNS_present = false; // Guard against partial overwrite 1689 Node tRNS_node = node.getFirstChild(); 1690 if (tRNS_node == null) { 1691 fatal(node, "tRNS node has no children!"); 1692 } 1693 String tRNS_name = tRNS_node.getNodeName(); 1694 if (tRNS_name.equals("tRNS_Palette")) { 1695 byte[] alpha = new byte[256]; 1696 int maxindex = -1; 1697 1698 Node tRNS_paletteEntry = tRNS_node.getFirstChild(); 1699 if (tRNS_paletteEntry == null) { 1700 fatal(node, "tRNS_Palette node has no children!"); 1701 } 1702 while (tRNS_paletteEntry != null) { 1703 if (!tRNS_paletteEntry.getNodeName().equals( 1704 "tRNS_PaletteEntry")) { 1705 fatal(node, 1706 "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!"); 1707 } 1708 int index = 1709 getIntAttribute(tRNS_paletteEntry, "index"); 1710 if (index < 0 || index > 255) { 1711 fatal(node, 1712 "Bad value for tRNS_PaletteEntry attribute index!"); 1713 } 1714 if (index > maxindex) { 1715 maxindex = index; 1716 } 1717 alpha[index] = 1718 (byte)getIntAttribute(tRNS_paletteEntry, 1719 "alpha"); 1720 1721 tRNS_paletteEntry = 1722 tRNS_paletteEntry.getNextSibling(); 1723 } 1724 1725 int numEntries = maxindex + 1; 1726 tRNS_alpha = new byte[numEntries]; 1727 tRNS_colorType = PNG_COLOR_PALETTE; 1728 System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries); 1729 } else if (tRNS_name.equals("tRNS_Grayscale")) { 1730 tRNS_gray = getIntAttribute(tRNS_node, "gray"); 1731 tRNS_colorType = PNG_COLOR_GRAY; 1732 } else if (tRNS_name.equals("tRNS_RGB")) { 1733 tRNS_red = getIntAttribute(tRNS_node, "red"); 1734 tRNS_green = getIntAttribute(tRNS_node, "green"); 1735 tRNS_blue = getIntAttribute(tRNS_node, "blue"); 1736 tRNS_colorType = PNG_COLOR_RGB; 1737 } else { 1738 fatal(node, "Bad child of a tRNS node!"); 1739 } 1740 if (tRNS_node.getNextSibling() != null) { 1741 fatal(node, "tRNS node has more than one child!"); 1742 } 1743 1744 tRNS_present = true; 1745 } else if (name.equals("zTXt")) { 1746 Node zTXt_node = node.getFirstChild(); 1747 while (zTXt_node != null) { 1748 if (!zTXt_node.getNodeName().equals("zTXtEntry")) { 1749 fatal(node, 1750 "Only an zTXtEntry may be a child of an zTXt!"); 1751 } 1752 1753 String keyword = 1754 toPrintableLatin1(getAttribute(zTXt_node, "keyword")); 1755 zTXt_keyword.add(keyword); 1756 1757 int compressionMethod = 1758 getEnumeratedAttribute(zTXt_node, "compressionMethod", 1759 zTXt_compressionMethodNames); 1760 zTXt_compressionMethod.add(new Integer(compressionMethod)); 1761 1762 String text = getAttribute(zTXt_node, "text"); 1763 zTXt_text.add(text); 1764 1765 zTXt_node = zTXt_node.getNextSibling(); 1766 } 1767 } else if (name.equals("UnknownChunks")) { 1768 Node unknown_node = node.getFirstChild(); 1769 while (unknown_node != null) { 1770 if (!unknown_node.getNodeName().equals("UnknownChunk")) { 1771 fatal(node, 1772 "Only an UnknownChunk may be a child of an UnknownChunks!"); 1773 } 1774 String chunkType = getAttribute(unknown_node, "type"); 1775 Object chunkData = 1776 ((IIOMetadataNode)unknown_node).getUserObject(); 1777 1778 if (chunkType.length() != 4) { 1779 fatal(unknown_node, 1780 "Chunk type must be 4 characters!"); 1781 } 1782 if (chunkData == null) { 1783 fatal(unknown_node, 1784 "No chunk data present in user object!"); 1785 } 1786 if (!(chunkData instanceof byte[])) { 1787 fatal(unknown_node, 1788 "User object not a byte array!"); 1789 } 1790 unknownChunkType.add(chunkType); 1791 unknownChunkData.add(((byte[])chunkData).clone()); 1792 1793 unknown_node = unknown_node.getNextSibling(); 1794 } 1795 } else { 1796 fatal(node, "Unknown child of root node!"); 1797 } 1798 1799 node = node.getNextSibling(); 1800 } 1801 } 1802 1803 private boolean isISOLatin(String s) { 1804 int len = s.length(); 1805 for (int i = 0; i < len; i++) { 1806 if (s.charAt(i) > 255) { 1807 return false; 1808 } 1809 } 1810 return true; 1811 } 1812 1813 private void mergeStandardTree(Node root) 1814 throws IIOInvalidTreeException { 1815 Node node = root; 1816 if (!node.getNodeName() 1817 .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { 1818 fatal(node, "Root must be " + 1819 IIOMetadataFormatImpl.standardMetadataFormatName); 1820 } 1821 1822 node = node.getFirstChild(); 1823 while(node != null) { 1824 String name = node.getNodeName(); 1825 1826 if (name.equals("Chroma")) { 1827 Node child = node.getFirstChild(); 1828 while (child != null) { 1829 String childName = child.getNodeName(); 1830 if (childName.equals("Gamma")) { 1831 float gamma = getFloatAttribute(child, "value"); 1832 gAMA_present = true; 1833 gAMA_gamma = (int)(gamma*100000 + 0.5); 1834 } else if (childName.equals("Palette")) { 1835 byte[] red = new byte[256]; 1836 byte[] green = new byte[256]; 1837 byte[] blue = new byte[256]; 1838 int maxindex = -1; 1839 1840 Node entry = child.getFirstChild(); 1841 while (entry != null) { 1842 String entryName = entry.getNodeName(); 1843 if(entryName.equals("PaletteEntry")) { 1844 int index = getIntAttribute(entry, "index"); 1845 if (index >= 0 && index <= 255) { 1846 red[index] = 1847 (byte)getIntAttribute(entry, "red"); 1848 green[index] = 1849 (byte)getIntAttribute(entry, "green"); 1850 blue[index] = 1851 (byte)getIntAttribute(entry, "blue"); 1852 if (index > maxindex) { 1853 maxindex = index; 1854 } 1855 } 1856 } 1857 entry = entry.getNextSibling(); 1858 } 1859 1860 int numEntries = maxindex + 1; 1861 PLTE_red = new byte[numEntries]; 1862 PLTE_green = new byte[numEntries]; 1863 PLTE_blue = new byte[numEntries]; 1864 System.arraycopy(red, 0, PLTE_red, 0, numEntries); 1865 System.arraycopy(green, 0, PLTE_green, 0, numEntries); 1866 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); 1867 PLTE_present = true; 1868 } else if (childName.equals("BackgroundIndex")) { 1869 bKGD_present = true; 1870 bKGD_colorType = PNG_COLOR_PALETTE; 1871 bKGD_index = getIntAttribute(child, "value"); 1872 } else if (childName.equals("BackgroundColor")) { 1873 int red = getIntAttribute(child, "red"); 1874 int green = getIntAttribute(child, "green"); 1875 int blue = getIntAttribute(child, "blue"); 1876 if (red == green && red == blue) { 1877 bKGD_colorType = PNG_COLOR_GRAY; 1878 bKGD_gray = red; 1879 } else { 1880 bKGD_colorType = PNG_COLOR_RGB; 1881 bKGD_red = red; 1882 bKGD_green = green; 1883 bKGD_blue = blue; 1884 } 1885 bKGD_present = true; 1886 } 1887 // } else if (childName.equals("ColorSpaceType")) { 1888 // } else if (childName.equals("NumChannels")) { 1889 1890 child = child.getNextSibling(); 1891 } 1892 } else if (name.equals("Compression")) { 1893 Node child = node.getFirstChild(); 1894 while (child != null) { 1895 String childName = child.getNodeName(); 1896 if (childName.equals("NumProgressiveScans")) { 1897 // Use Adam7 if NumProgressiveScans > 1 1898 int scans = getIntAttribute(child, "value"); 1899 IHDR_interlaceMethod = (scans > 1) ? 1 : 0; 1900 // } else if (childName.equals("CompressionTypeName")) { 1901 // } else if (childName.equals("Lossless")) { 1902 // } else if (childName.equals("BitRate")) { 1903 } 1904 child = child.getNextSibling(); 1905 } 1906 } else if (name.equals("Data")) { 1907 Node child = node.getFirstChild(); 1908 while (child != null) { 1909 String childName = child.getNodeName(); 1910 if (childName.equals("BitsPerSample")) { 1911 String s = getAttribute(child, "value"); 1912 StringTokenizer t = new StringTokenizer(s); 1913 int maxBits = -1; 1914 while (t.hasMoreTokens()) { 1915 int bits = Integer.parseInt(t.nextToken()); 1916 if (bits > maxBits) { 1917 maxBits = bits; 1918 } 1919 } 1920 if (maxBits < 1) { 1921 maxBits = 1; 1922 } else if (maxBits == 3) { 1923 maxBits = 4; 1924 } else if (maxBits > 4 && maxBits < 8) { 1925 maxBits = 8; 1926 } else if (maxBits > 8) { 1927 maxBits = 16; 1928 } 1929 IHDR_bitDepth = maxBits; 1930 } else if (childName.equals("SignificantBitsPerSample")) { 1931 String s = getAttribute(child, "value"); 1932 StringTokenizer t = new StringTokenizer(s); 1933 int numTokens = t.countTokens(); 1934 if (numTokens == 1) { 1935 sBIT_colorType = PNG_COLOR_GRAY; 1936 sBIT_grayBits = Integer.parseInt(t.nextToken()); 1937 } else if (numTokens == 2) { 1938 sBIT_colorType = 1939 PNG_COLOR_GRAY_ALPHA; 1940 sBIT_grayBits = Integer.parseInt(t.nextToken()); 1941 sBIT_alphaBits = Integer.parseInt(t.nextToken()); 1942 } else if (numTokens == 3) { 1943 sBIT_colorType = PNG_COLOR_RGB; 1944 sBIT_redBits = Integer.parseInt(t.nextToken()); 1945 sBIT_greenBits = Integer.parseInt(t.nextToken()); 1946 sBIT_blueBits = Integer.parseInt(t.nextToken()); 1947 } else if (numTokens == 4) { 1948 sBIT_colorType = 1949 PNG_COLOR_RGB_ALPHA; 1950 sBIT_redBits = Integer.parseInt(t.nextToken()); 1951 sBIT_greenBits = Integer.parseInt(t.nextToken()); 1952 sBIT_blueBits = Integer.parseInt(t.nextToken()); 1953 sBIT_alphaBits = Integer.parseInt(t.nextToken()); 1954 } 1955 if (numTokens >= 1 && numTokens <= 4) { 1956 sBIT_present = true; 1957 } 1958 // } else if (childName.equals("PlanarConfiguration")) { 1959 // } else if (childName.equals("SampleFormat")) { 1960 // } else if (childName.equals("SampleMSB")) { 1961 } 1962 child = child.getNextSibling(); 1963 } 1964 } else if (name.equals("Dimension")) { 1965 boolean gotWidth = false; 1966 boolean gotHeight = false; 1967 boolean gotAspectRatio = false; 1968 1969 float width = -1.0F; 1970 float height = -1.0F; 1971 float aspectRatio = -1.0F; 1972 1973 Node child = node.getFirstChild(); 1974 while (child != null) { 1975 String childName = child.getNodeName(); 1976 if (childName.equals("PixelAspectRatio")) { 1977 aspectRatio = getFloatAttribute(child, "value"); 1978 gotAspectRatio = true; 1979 } else if (childName.equals("HorizontalPixelSize")) { 1980 width = getFloatAttribute(child, "value"); 1981 gotWidth = true; 1982 } else if (childName.equals("VerticalPixelSize")) { 1983 height = getFloatAttribute(child, "value"); 1984 gotHeight = true; 1985 // } else if (childName.equals("ImageOrientation")) { 1986 // } else if 1987 // (childName.equals("HorizontalPhysicalPixelSpacing")) { 1988 // } else if 1989 // (childName.equals("VerticalPhysicalPixelSpacing")) { 1990 // } else if (childName.equals("HorizontalPosition")) { 1991 // } else if (childName.equals("VerticalPosition")) { 1992 // } else if (childName.equals("HorizontalPixelOffset")) { 1993 // } else if (childName.equals("VerticalPixelOffset")) { 1994 } 1995 child = child.getNextSibling(); 1996 } 1997 1998 if (gotWidth && gotHeight) { 1999 pHYs_present = true; 2000 pHYs_unitSpecifier = 1; 2001 pHYs_pixelsPerUnitXAxis = (int)(1000.0F/width + 0.5F); 2002 pHYs_pixelsPerUnitYAxis = (int)(1000.0F/height + 0.5F); 2003 } else if (gotAspectRatio) { 2004 pHYs_present = true; 2005 pHYs_unitSpecifier = 0; 2006 2007 // Find a reasonable rational approximation 2008 int denom = 1; 2009 for (; denom < 100; denom++) { 2010 int num = (int)(aspectRatio*denom); 2011 if (Math.abs(num/denom - aspectRatio) < 0.001) { 2012 break; 2013 } 2014 } 2015 pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom); 2016 pHYs_pixelsPerUnitYAxis = denom; 2017 } 2018 } else if (name.equals("Document")) { 2019 Node child = node.getFirstChild(); 2020 while (child != null) { 2021 String childName = child.getNodeName(); 2022 if (childName.equals("ImageModificationTime")) { 2023 tIME_present = true; 2024 tIME_year = getIntAttribute(child, "year"); 2025 tIME_month = getIntAttribute(child, "month"); 2026 tIME_day = getIntAttribute(child, "day"); 2027 tIME_hour = 2028 getIntAttribute(child, "hour", 0, false); 2029 tIME_minute = 2030 getIntAttribute(child, "minute", 0, false); 2031 tIME_second = 2032 getIntAttribute(child, "second", 0, false); 2033 // } else if (childName.equals("SubimageInterpretation")) { 2034 // } else if (childName.equals("ImageCreationTime")) { 2035 } 2036 child = child.getNextSibling(); 2037 } 2038 } else if (name.equals("Text")) { 2039 Node child = node.getFirstChild(); 2040 while (child != null) { 2041 String childName = child.getNodeName(); 2042 if (childName.equals("TextEntry")) { 2043 String keyword = getAttribute(child, "keyword", 2044 "text", false); 2045 String value = getAttribute(child, "value"); 2046 String encoding = getAttribute(child, "encoding", 2047 "unknown", false); 2048 String language = getAttribute(child, "language", 2049 "unknown", false); 2050 String compression = 2051 getAttribute(child, "compression", 2052 "other", false); 2053 2054 if (isISOLatin(value)) { 2055 if (compression.equals("zip")) { 2056 // Use a zTXt node 2057 zTXt_keyword.add(toPrintableLatin1(keyword)); 2058 zTXt_text.add(value); 2059 zTXt_compressionMethod.add(new Integer(0)); 2060 } else { 2061 // Use a tEXt node 2062 tEXt_keyword.add(toPrintableLatin1(keyword)); 2063 tEXt_text.add(value); 2064 } 2065 } else { 2066 int flag = compression.equals("zip") ? 2067 1 : 0; 2068 2069 // Use an iTXt node 2070 iTXt_keyword.add(toPrintableLatin1(keyword)); 2071 iTXt_compressionFlag.add(new Integer(flag)); 2072 iTXt_compressionMethod.add(new Integer(0)); 2073 iTXt_languageTag.add(language); 2074 iTXt_translatedKeyword.add(keyword); // fake it 2075 iTXt_text.add(value); 2076 } 2077 } 2078 child = child.getNextSibling(); 2079 } 2080 // } else if (name.equals("Transparency")) { 2081 // Node child = node.getFirstChild(); 2082 // while (child != null) { 2083 // String childName = child.getNodeName(); 2084 // if (childName.equals("Alpha")) { 2085 // } else if (childName.equals("TransparentIndex")) { 2086 // } else if (childName.equals("TransparentColor")) { 2087 // } else if (childName.equals("TileTransparencies")) { 2088 // } else if (childName.equals("TileOpacities")) { 2089 // } 2090 // child = child.getNextSibling(); 2091 // } 2092 // } else { 2093 // // fatal(node, "Unknown child of root node!"); 2094 } 2095 2096 node = node.getNextSibling(); 2097 } 2098 } 2099 2100 // Reset all instance variables to their initial state 2101 public void reset() { 2102 IHDR_present = false; 2103 PLTE_present = false; 2104 bKGD_present = false; 2105 cHRM_present = false; 2106 gAMA_present = false; 2107 hIST_present = false; 2108 iCCP_present = false; 2109 iTXt_keyword = new ArrayList(); 2110 iTXt_compressionFlag = new ArrayList(); 2111 iTXt_compressionMethod = new ArrayList(); 2112 iTXt_languageTag = new ArrayList(); 2113 iTXt_translatedKeyword = new ArrayList(); 2114 iTXt_text = new ArrayList(); 2115 pHYs_present = false; 2116 sBIT_present = false; 2117 sPLT_present = false; 2118 sRGB_present = false; 2119 tEXt_keyword = new ArrayList(); 2120 tEXt_text = new ArrayList(); 2121 tIME_present = false; 2122 tRNS_present = false; 2123 zTXt_keyword = new ArrayList(); 2124 zTXt_compressionMethod = new ArrayList(); 2125 zTXt_text = new ArrayList(); 2126 unknownChunkType = new ArrayList(); 2127 unknownChunkData = new ArrayList(); 2128 } 2129 2130 // BEGIN metadata reading section. 2131 2132 private boolean gotHeader = false; 2133 private boolean gotMetadata = false; 2134 2135 private static int chunkType(String typeString) { 2136 char c0 = typeString.charAt(0); 2137 char c1 = typeString.charAt(1); 2138 char c2 = typeString.charAt(2); 2139 char c3 = typeString.charAt(3); 2140 2141 int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; 2142 return type; 2143 } 2144 2145 private String readNullTerminatedString(ImageInputStream stream) 2146 throws IOException { 2147 StringBuffer b = new StringBuffer(); 2148 int c; 2149 2150 while ((c = stream.read()) != 0) { 2151 b.append((char)c); 2152 } 2153 return b.toString(); 2154 } 2155 2156 // END metadata writing methods. 2157}