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