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