1 /*
   2  * Copyright (c) 2000, 2014, 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 = reds.clone();
 348                 PLTE_green = greens.clone();
 349                 PLTE_blue = 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 : 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" , tEXt_keyword.get(i));
 707                 tEXt_node.setAttribute("value" , 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", zTXt_keyword.get(i));
 763 
 764                 int cm = (zTXt_compressionMethod.get(i)).intValue();
 765                 zTXt_node.setAttribute("compressionMethod",
 766                                        zTXt_compressionMethodNames[cm]);
 767 
 768                 zTXt_node.setAttribute("text", 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                                           unknownChunkType.get(i));
 785                 unknown_node.setUserObject(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         StringBuilder sb = new StringBuilder((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", tEXt_keyword.get(i));
1020             node.setAttribute("value", 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", zTXt_keyword.get(i));
1045             node.setAttribute("value", 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 =
1258                         Integer.valueOf(IHDR_bitDepths[
1259                                 getEnumeratedAttribute(node,
1260                                                     "bitDepth",
1261                                                     IHDR_bitDepths)]);
1262                 IHDR_colorType = getEnumeratedAttribute(node, "colorType",
1263                                                         IHDR_colorTypeNames);
1264                 IHDR_compressionMethod =
1265                     getEnumeratedAttribute(node, "compressionMethod",
1266                                            IHDR_compressionMethodNames);
1267                 IHDR_filterMethod =
1268                     getEnumeratedAttribute(node,
1269                                            "filterMethod",
1270                                            IHDR_filterMethodNames);
1271                 IHDR_interlaceMethod =
1272                     getEnumeratedAttribute(node, "interlaceMethod",
1273                                            IHDR_interlaceMethodNames);
1274                 IHDR_present = true;
1275             } else if (name.equals("PLTE")) {
1276                 byte[] red = new byte[256];
1277                 byte[] green  = new byte[256];
1278                 byte[] blue = new byte[256];
1279                 int maxindex = -1;
1280 
1281                 Node PLTE_entry = node.getFirstChild();
1282                 if (PLTE_entry == null) {
1283                     fatal(node, "Palette has no entries!");
1284                 }
1285 
1286                 while (PLTE_entry != null) {
1287                     if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
1288                         fatal(node,
1289                               "Only a PLTEEntry may be a child of a PLTE!");
1290                     }
1291 
1292                     int index = getIntAttribute(PLTE_entry, "index");
1293                     if (index < 0 || index > 255) {
1294                         fatal(node,
1295                               "Bad value for PLTEEntry attribute index!");
1296                     }
1297                     if (index > maxindex) {
1298                         maxindex = index;
1299                     }
1300                     red[index] =
1301                         (byte)getIntAttribute(PLTE_entry, "red");
1302                     green[index] =
1303                         (byte)getIntAttribute(PLTE_entry, "green");
1304                     blue[index] =
1305                         (byte)getIntAttribute(PLTE_entry, "blue");
1306 
1307                     PLTE_entry = PLTE_entry.getNextSibling();
1308                 }
1309 
1310                 int numEntries = maxindex + 1;
1311                 PLTE_red = new byte[numEntries];
1312                 PLTE_green = new byte[numEntries];
1313                 PLTE_blue = new byte[numEntries];
1314                 System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1315                 System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1316                 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1317                 PLTE_present = true;
1318             } else if (name.equals("bKGD")) {
1319                 bKGD_present = false; // Guard against partial overwrite
1320                 Node bKGD_node = node.getFirstChild();
1321                 if (bKGD_node == null) {
1322                     fatal(node, "bKGD node has no children!");
1323                 }
1324                 String bKGD_name = bKGD_node.getNodeName();
1325                 if (bKGD_name.equals("bKGD_Palette")) {
1326                     bKGD_index = getIntAttribute(bKGD_node, "index");
1327                     bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1328                 } else if (bKGD_name.equals("bKGD_Grayscale")) {
1329                     bKGD_gray = getIntAttribute(bKGD_node, "gray");
1330                     bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1331                 } else if (bKGD_name.equals("bKGD_RGB")) {
1332                     bKGD_red = getIntAttribute(bKGD_node, "red");
1333                     bKGD_green = getIntAttribute(bKGD_node, "green");
1334                     bKGD_blue = getIntAttribute(bKGD_node, "blue");
1335                     bKGD_colorType = PNGImageReader.PNG_COLOR_RGB;
1336                 } else {
1337                     fatal(node, "Bad child of a bKGD node!");
1338                 }
1339                 if (bKGD_node.getNextSibling() != null) {
1340                     fatal(node, "bKGD node has more than one child!");
1341                 }
1342 
1343                 bKGD_present = true;
1344             } else if (name.equals("cHRM")) {
1345                 cHRM_whitePointX = getIntAttribute(node, "whitePointX");
1346                 cHRM_whitePointY = getIntAttribute(node, "whitePointY");
1347                 cHRM_redX = getIntAttribute(node, "redX");
1348                 cHRM_redY = getIntAttribute(node, "redY");
1349                 cHRM_greenX = getIntAttribute(node, "greenX");
1350                 cHRM_greenY = getIntAttribute(node, "greenY");
1351                 cHRM_blueX = getIntAttribute(node, "blueX");
1352                 cHRM_blueY = getIntAttribute(node, "blueY");
1353 
1354                 cHRM_present = true;
1355             } else if (name.equals("gAMA")) {
1356                 gAMA_gamma = getIntAttribute(node, "value");
1357                 gAMA_present = true;
1358             } else if (name.equals("hIST")) {
1359                 char[] hist = new char[256];
1360                 int maxindex = -1;
1361 
1362                 Node hIST_entry = node.getFirstChild();
1363                 if (hIST_entry == null) {
1364                     fatal(node, "hIST node has no children!");
1365                 }
1366 
1367                 while (hIST_entry != null) {
1368                     if (!hIST_entry.getNodeName().equals("hISTEntry")) {
1369                         fatal(node,
1370                               "Only a hISTEntry may be a child of a hIST!");
1371                     }
1372 
1373                     int index = getIntAttribute(hIST_entry, "index");
1374                     if (index < 0 || index > 255) {
1375                         fatal(node,
1376                               "Bad value for histEntry attribute index!");
1377                     }
1378                     if (index > maxindex) {
1379                         maxindex = index;
1380                     }
1381                     hist[index] =
1382                         (char)getIntAttribute(hIST_entry, "value");
1383 
1384                     hIST_entry = hIST_entry.getNextSibling();
1385                 }
1386 
1387                 int numEntries = maxindex + 1;
1388                 hIST_histogram = new char[numEntries];
1389                 System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
1390 
1391                 hIST_present = true;
1392             } else if (name.equals("iCCP")) {
1393                 iCCP_profileName = getAttribute(node, "profileName");
1394                 iCCP_compressionMethod =
1395                     getEnumeratedAttribute(node, "compressionMethod",
1396                                            iCCP_compressionMethodNames);
1397                 Object compressedProfile =
1398                     ((IIOMetadataNode)node).getUserObject();
1399                 if (compressedProfile == null) {
1400                     fatal(node, "No ICCP profile present in user object!");
1401                 }
1402                 if (!(compressedProfile instanceof byte[])) {
1403                     fatal(node, "User object not a byte array!");
1404                 }
1405 
1406                 iCCP_compressedProfile = ((byte[])compressedProfile).clone();
1407 
1408                 iCCP_present = true;
1409             } else if (name.equals("iTXt")) {
1410                 Node iTXt_node = node.getFirstChild();
1411                 while (iTXt_node != null) {
1412                     if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
1413                         fatal(node,
1414                               "Only an iTXtEntry may be a child of an iTXt!");
1415                     }
1416 
1417                     String keyword = getAttribute(iTXt_node, "keyword");
1418                     if (isValidKeyword(keyword)) {
1419                         iTXt_keyword.add(keyword);
1420 
1421                         boolean compressionFlag =
1422                             getBooleanAttribute(iTXt_node, "compressionFlag");
1423                         iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag));
1424 
1425                         String compressionMethod =
1426                             getAttribute(iTXt_node, "compressionMethod");
1427                         iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
1428 
1429                         String languageTag =
1430                             getAttribute(iTXt_node, "languageTag");
1431                         iTXt_languageTag.add(languageTag);
1432 
1433                         String translatedKeyword =
1434                             getAttribute(iTXt_node, "translatedKeyword");
1435                         iTXt_translatedKeyword.add(translatedKeyword);
1436 
1437                         String text = getAttribute(iTXt_node, "text");
1438                         iTXt_text.add(text);
1439 
1440                     }
1441                     // silently skip invalid text entry
1442 
1443                     iTXt_node = iTXt_node.getNextSibling();
1444                 }
1445             } else if (name.equals("pHYs")) {
1446                 pHYs_pixelsPerUnitXAxis =
1447                     getIntAttribute(node, "pixelsPerUnitXAxis");
1448                 pHYs_pixelsPerUnitYAxis =
1449                     getIntAttribute(node, "pixelsPerUnitYAxis");
1450                 pHYs_unitSpecifier =
1451                     getEnumeratedAttribute(node, "unitSpecifier",
1452                                            unitSpecifierNames);
1453 
1454                 pHYs_present = true;
1455             } else if (name.equals("sBIT")) {
1456                 sBIT_present = false; // Guard against partial overwrite
1457                 Node sBIT_node = node.getFirstChild();
1458                 if (sBIT_node == null) {
1459                     fatal(node, "sBIT node has no children!");
1460                 }
1461                 String sBIT_name = sBIT_node.getNodeName();
1462                 if (sBIT_name.equals("sBIT_Grayscale")) {
1463                     sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1464                     sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1465                 } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
1466                     sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1467                     sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1468                     sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1469                 } else if (sBIT_name.equals("sBIT_RGB")) {
1470                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1471                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1472                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1473                     sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1474                 } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
1475                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1476                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1477                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1478                     sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1479                     sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
1480                 } else if (sBIT_name.equals("sBIT_Palette")) {
1481                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1482                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1483                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1484                     sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1485                 } else {
1486                     fatal(node, "Bad child of an sBIT node!");
1487                 }
1488                 if (sBIT_node.getNextSibling() != null) {
1489                     fatal(node, "sBIT node has more than one child!");
1490                 }
1491 
1492                 sBIT_present = true;
1493             } else if (name.equals("sPLT")) {
1494                 sPLT_paletteName = getAttribute(node, "name");
1495                 sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
1496 
1497                 int[] red = new int[256];
1498                 int[] green  = new int[256];
1499                 int[] blue = new int[256];
1500                 int[] alpha = new int[256];
1501                 int[] frequency = new int[256];
1502                 int maxindex = -1;
1503 
1504                 Node sPLT_entry = node.getFirstChild();
1505                 if (sPLT_entry == null) {
1506                     fatal(node, "sPLT node has no children!");
1507                 }
1508 
1509                 while (sPLT_entry != null) {
1510                     if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
1511                         fatal(node,
1512                               "Only an sPLTEntry may be a child of an sPLT!");
1513                     }
1514 
1515                     int index = getIntAttribute(sPLT_entry, "index");
1516                     if (index < 0 || index > 255) {
1517                         fatal(node,
1518                               "Bad value for PLTEEntry attribute index!");
1519                     }
1520                     if (index > maxindex) {
1521                         maxindex = index;
1522                     }
1523                     red[index] = getIntAttribute(sPLT_entry, "red");
1524                     green[index] = getIntAttribute(sPLT_entry, "green");
1525                     blue[index] = getIntAttribute(sPLT_entry, "blue");
1526                     alpha[index] = getIntAttribute(sPLT_entry, "alpha");
1527                     frequency[index] =
1528                         getIntAttribute(sPLT_entry, "frequency");
1529 
1530                     sPLT_entry = sPLT_entry.getNextSibling();
1531                 }
1532 
1533                 int numEntries = maxindex + 1;
1534                 sPLT_red = new int[numEntries];
1535                 sPLT_green = new int[numEntries];
1536                 sPLT_blue = new int[numEntries];
1537                 sPLT_alpha = new int[numEntries];
1538                 sPLT_frequency = new int[numEntries];
1539                 System.arraycopy(red, 0, sPLT_red, 0, numEntries);
1540                 System.arraycopy(green, 0, sPLT_green, 0, numEntries);
1541                 System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
1542                 System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
1543                 System.arraycopy(frequency, 0,
1544                                  sPLT_frequency, 0, numEntries);
1545 
1546                 sPLT_present = true;
1547             } else if (name.equals("sRGB")) {
1548                 sRGB_renderingIntent =
1549                     getEnumeratedAttribute(node, "renderingIntent",
1550                                            renderingIntentNames);
1551 
1552                 sRGB_present = true;
1553             } else if (name.equals("tEXt")) {
1554                 Node tEXt_node = node.getFirstChild();
1555                 while (tEXt_node != null) {
1556                     if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
1557                         fatal(node,
1558                               "Only an tEXtEntry may be a child of an tEXt!");
1559                     }
1560 
1561                     String keyword = getAttribute(tEXt_node, "keyword");
1562                     tEXt_keyword.add(keyword);
1563 
1564                     String text = getAttribute(tEXt_node, "value");
1565                     tEXt_text.add(text);
1566 
1567                     tEXt_node = tEXt_node.getNextSibling();
1568                 }
1569             } else if (name.equals("tIME")) {
1570                 tIME_year = getIntAttribute(node, "year");
1571                 tIME_month = getIntAttribute(node, "month");
1572                 tIME_day = getIntAttribute(node, "day");
1573                 tIME_hour = getIntAttribute(node, "hour");
1574                 tIME_minute = getIntAttribute(node, "minute");
1575                 tIME_second = getIntAttribute(node, "second");
1576 
1577                 tIME_present = true;
1578             } else if (name.equals("tRNS")) {
1579                 tRNS_present = false; // Guard against partial overwrite
1580                 Node tRNS_node = node.getFirstChild();
1581                 if (tRNS_node == null) {
1582                     fatal(node, "tRNS node has no children!");
1583                 }
1584                 String tRNS_name = tRNS_node.getNodeName();
1585                 if (tRNS_name.equals("tRNS_Palette")) {
1586                     byte[] alpha = new byte[256];
1587                     int maxindex = -1;
1588 
1589                     Node tRNS_paletteEntry = tRNS_node.getFirstChild();
1590                     if (tRNS_paletteEntry == null) {
1591                         fatal(node, "tRNS_Palette node has no children!");
1592                     }
1593                     while (tRNS_paletteEntry != null) {
1594                         if (!tRNS_paletteEntry.getNodeName().equals(
1595                                                         "tRNS_PaletteEntry")) {
1596                             fatal(node,
1597                  "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
1598                         }
1599                         int index =
1600                             getIntAttribute(tRNS_paletteEntry, "index");
1601                         if (index < 0 || index > 255) {
1602                             fatal(node,
1603                            "Bad value for tRNS_PaletteEntry attribute index!");
1604                         }
1605                         if (index > maxindex) {
1606                             maxindex = index;
1607                         }
1608                         alpha[index] =
1609                             (byte)getIntAttribute(tRNS_paletteEntry,
1610                                                   "alpha");
1611 
1612                         tRNS_paletteEntry =
1613                             tRNS_paletteEntry.getNextSibling();
1614                     }
1615 
1616                     int numEntries = maxindex + 1;
1617                     tRNS_alpha = new byte[numEntries];
1618                     tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1619                     System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
1620                 } else if (tRNS_name.equals("tRNS_Grayscale")) {
1621                     tRNS_gray = getIntAttribute(tRNS_node, "gray");
1622                     tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY;
1623                 } else if (tRNS_name.equals("tRNS_RGB")) {
1624                     tRNS_red = getIntAttribute(tRNS_node, "red");
1625                     tRNS_green = getIntAttribute(tRNS_node, "green");
1626                     tRNS_blue = getIntAttribute(tRNS_node, "blue");
1627                     tRNS_colorType = PNGImageReader.PNG_COLOR_RGB;
1628                 } else {
1629                     fatal(node, "Bad child of a tRNS node!");
1630                 }
1631                 if (tRNS_node.getNextSibling() != null) {
1632                     fatal(node, "tRNS node has more than one child!");
1633                 }
1634 
1635                 tRNS_present = true;
1636             } else if (name.equals("zTXt")) {
1637                 Node zTXt_node = node.getFirstChild();
1638                 while (zTXt_node != null) {
1639                     if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
1640                         fatal(node,
1641                               "Only an zTXtEntry may be a child of an zTXt!");
1642                     }
1643 
1644                     String keyword = getAttribute(zTXt_node, "keyword");
1645                     zTXt_keyword.add(keyword);
1646 
1647                     int compressionMethod =
1648                         getEnumeratedAttribute(zTXt_node, "compressionMethod",
1649                                                zTXt_compressionMethodNames);
1650                     zTXt_compressionMethod.add(new Integer(compressionMethod));
1651 
1652                     String text = getAttribute(zTXt_node, "text");
1653                     zTXt_text.add(text);
1654 
1655                     zTXt_node = zTXt_node.getNextSibling();
1656                 }
1657             } else if (name.equals("UnknownChunks")) {
1658                 Node unknown_node = node.getFirstChild();
1659                 while (unknown_node != null) {
1660                     if (!unknown_node.getNodeName().equals("UnknownChunk")) {
1661                         fatal(node,
1662                    "Only an UnknownChunk may be a child of an UnknownChunks!");
1663                     }
1664                     String chunkType = getAttribute(unknown_node, "type");
1665                     Object chunkData =
1666                         ((IIOMetadataNode)unknown_node).getUserObject();
1667 
1668                     if (chunkType.length() != 4) {
1669                         fatal(unknown_node,
1670                               "Chunk type must be 4 characters!");
1671                     }
1672                     if (chunkData == null) {
1673                         fatal(unknown_node,
1674                               "No chunk data present in user object!");
1675                     }
1676                     if (!(chunkData instanceof byte[])) {
1677                         fatal(unknown_node,
1678                               "User object not a byte array!");
1679                     }
1680                     unknownChunkType.add(chunkType);
1681                     unknownChunkData.add(((byte[])chunkData).clone());
1682 
1683                     unknown_node = unknown_node.getNextSibling();
1684                 }
1685             } else {
1686                 fatal(node, "Unknown child of root node!");
1687             }
1688 
1689             node = node.getNextSibling();
1690         }
1691     }
1692 
1693     /*
1694      * Accrding to PNG spec, keywords are restricted to 1 to 79 bytes
1695      * in length. Keywords shall contain only printable Latin-1 characters
1696      * and spaces; To reduce the chances for human misreading of a keyword,
1697      * leading spaces, trailing spaces, and consecutive spaces are not
1698      * permitted in keywords.
1699      *
1700      * See: http://www.w3.org/TR/PNG/#11keywords
1701      */
1702     private boolean isValidKeyword(String s) {
1703         int len = s.length();
1704         if (len < 1 || len >= 80) {
1705             return false;
1706         }
1707         if (s.startsWith(" ") || s.endsWith(" ") || s.contains("  ")) {
1708             return false;
1709         }
1710         return isISOLatin(s, false);
1711     }
1712 
1713     /*
1714      * According to PNG spec, keyword shall contain only printable
1715      * Latin-1 [ISO-8859-1] characters and spaces; that is, only
1716      * character codes 32-126 and 161-255 decimal are allowed.
1717      * For Latin-1 value fields the 0x10 (linefeed) control
1718      * character is aloowed too.
1719      *
1720      * See: http://www.w3.org/TR/PNG/#11keywords
1721      */
1722     private boolean isISOLatin(String s, boolean isLineFeedAllowed) {
1723         int len = s.length();
1724         for (int i = 0; i < len; i++) {
1725             char c = s.charAt(i);
1726             if (c < 32 || c > 255 || (c > 126 && c < 161)) {
1727                 // not printable. Check whether this is an allowed
1728                 // control char
1729                 if (!isLineFeedAllowed || c != 0x10) {
1730                     return false;
1731                 }
1732             }
1733         }
1734         return true;
1735     }
1736 
1737     private void mergeStandardTree(Node root)
1738         throws IIOInvalidTreeException {
1739         Node node = root;
1740         if (!node.getNodeName()
1741             .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
1742             fatal(node, "Root must be " +
1743                   IIOMetadataFormatImpl.standardMetadataFormatName);
1744         }
1745 
1746         node = node.getFirstChild();
1747         while (node != null) {
1748             String name = node.getNodeName();
1749 
1750             if (name.equals("Chroma")) {
1751                 Node child = node.getFirstChild();
1752                 while (child != null) {
1753                     String childName = child.getNodeName();
1754                     if (childName.equals("Gamma")) {
1755                         float gamma = getFloatAttribute(child, "value");
1756                         gAMA_present = true;
1757                         gAMA_gamma = (int)(gamma*100000 + 0.5);
1758                     } else if (childName.equals("Palette")) {
1759                         byte[] red = new byte[256];
1760                         byte[] green = new byte[256];
1761                         byte[] blue = new byte[256];
1762                         int maxindex = -1;
1763 
1764                         Node entry = child.getFirstChild();
1765                         while (entry != null) {
1766                             int index = getIntAttribute(entry, "index");
1767                             if (index >= 0 && index <= 255) {
1768                                 red[index] =
1769                                     (byte)getIntAttribute(entry, "red");
1770                                 green[index] =
1771                                     (byte)getIntAttribute(entry, "green");
1772                                 blue[index] =
1773                                     (byte)getIntAttribute(entry, "blue");
1774                                 if (index > maxindex) {
1775                                     maxindex = index;
1776                                 }
1777                             }
1778                             entry = entry.getNextSibling();
1779                         }
1780 
1781                         int numEntries = maxindex + 1;
1782                         PLTE_red = new byte[numEntries];
1783                         PLTE_green = new byte[numEntries];
1784                         PLTE_blue = new byte[numEntries];
1785                         System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1786                         System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1787                         System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1788                         PLTE_present = true;
1789                     } else if (childName.equals("BackgroundIndex")) {
1790                         bKGD_present = true;
1791                         bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1792                         bKGD_index = getIntAttribute(child, "value");
1793                     } else if (childName.equals("BackgroundColor")) {
1794                         int red = getIntAttribute(child, "red");
1795                         int green = getIntAttribute(child, "green");
1796                         int blue = getIntAttribute(child, "blue");
1797                         if (red == green && red == blue) {
1798                             bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1799                             bKGD_gray = red;
1800                         } else {
1801                             bKGD_red = red;
1802                             bKGD_green = green;
1803                             bKGD_blue = blue;
1804                         }
1805                         bKGD_present = true;
1806                     }
1807 //                  } else if (childName.equals("ColorSpaceType")) {
1808 //                  } else if (childName.equals("NumChannels")) {
1809 
1810                     child = child.getNextSibling();
1811                 }
1812             } else if (name.equals("Compression")) {
1813                 Node child = node.getFirstChild();
1814                 while (child != null) {
1815                     String childName = child.getNodeName();
1816                     if (childName.equals("NumProgressiveScans")) {
1817                         // Use Adam7 if NumProgressiveScans > 1
1818                         int scans = getIntAttribute(child, "value");
1819                         IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
1820 //                  } else if (childName.equals("CompressionTypeName")) {
1821 //                  } else if (childName.equals("Lossless")) {
1822 //                  } else if (childName.equals("BitRate")) {
1823                     }
1824                     child = child.getNextSibling();
1825                 }
1826             } else if (name.equals("Data")) {
1827                 Node child = node.getFirstChild();
1828                 while (child != null) {
1829                     String childName = child.getNodeName();
1830                     if (childName.equals("BitsPerSample")) {
1831                         String s = getAttribute(child, "value");
1832                         StringTokenizer t = new StringTokenizer(s);
1833                         int maxBits = -1;
1834                         while (t.hasMoreTokens()) {
1835                             int bits = Integer.parseInt(t.nextToken());
1836                             if (bits > maxBits) {
1837                                 maxBits = bits;
1838                             }
1839                         }
1840                         if (maxBits < 1) {
1841                             maxBits = 1;
1842                         }
1843                         if (maxBits == 3) maxBits = 4;
1844                         if (maxBits > 4 || maxBits < 8) {
1845                             maxBits = 8;
1846                         }
1847                         if (maxBits > 8) {
1848                             maxBits = 16;
1849                         }
1850                         IHDR_bitDepth = maxBits;
1851                     } else if (childName.equals("SignificantBitsPerSample")) {
1852                         String s = getAttribute(child, "value");
1853                         StringTokenizer t = new StringTokenizer(s);
1854                         int numTokens = t.countTokens();
1855                         if (numTokens == 1) {
1856                             sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1857                             sBIT_grayBits = Integer.parseInt(t.nextToken());
1858                         } else if (numTokens == 2) {
1859                             sBIT_colorType =
1860                               PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1861                             sBIT_grayBits = Integer.parseInt(t.nextToken());
1862                             sBIT_alphaBits = Integer.parseInt(t.nextToken());
1863                         } else if (numTokens == 3) {
1864                             sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1865                             sBIT_redBits = Integer.parseInt(t.nextToken());
1866                             sBIT_greenBits = Integer.parseInt(t.nextToken());
1867                             sBIT_blueBits = Integer.parseInt(t.nextToken());
1868                         } else if (numTokens == 4) {
1869                             sBIT_colorType =
1870                               PNGImageReader.PNG_COLOR_RGB_ALPHA;
1871                             sBIT_redBits = Integer.parseInt(t.nextToken());
1872                             sBIT_greenBits = Integer.parseInt(t.nextToken());
1873                             sBIT_blueBits = Integer.parseInt(t.nextToken());
1874                             sBIT_alphaBits = Integer.parseInt(t.nextToken());
1875                         }
1876                         if (numTokens >= 1 && numTokens <= 4) {
1877                             sBIT_present = true;
1878                         }
1879 //                      } else if (childName.equals("PlanarConfiguration")) {
1880 //                      } else if (childName.equals("SampleFormat")) {
1881 //                      } else if (childName.equals("SampleMSB")) {
1882                     }
1883                     child = child.getNextSibling();
1884                 }
1885             } else if (name.equals("Dimension")) {
1886                 boolean gotWidth = false;
1887                 boolean gotHeight = false;
1888                 boolean gotAspectRatio = false;
1889 
1890                 float width = -1.0F;
1891                 float height = -1.0F;
1892                 float aspectRatio = -1.0F;
1893 
1894                 Node child = node.getFirstChild();
1895                 while (child != null) {
1896                     String childName = child.getNodeName();
1897                     if (childName.equals("PixelAspectRatio")) {
1898                         aspectRatio = getFloatAttribute(child, "value");
1899                         gotAspectRatio = true;
1900                     } else if (childName.equals("HorizontalPixelSize")) {
1901                         width = getFloatAttribute(child, "value");
1902                         gotWidth = true;
1903                     } else if (childName.equals("VerticalPixelSize")) {
1904                         height = getFloatAttribute(child, "value");
1905                         gotHeight = true;
1906 //                  } else if (childName.equals("ImageOrientation")) {
1907 //                  } else if
1908 //                      (childName.equals("HorizontalPhysicalPixelSpacing")) {
1909 //                  } else if
1910 //                      (childName.equals("VerticalPhysicalPixelSpacing")) {
1911 //                  } else if (childName.equals("HorizontalPosition")) {
1912 //                  } else if (childName.equals("VerticalPosition")) {
1913 //                  } else if (childName.equals("HorizontalPixelOffset")) {
1914 //                  } else if (childName.equals("VerticalPixelOffset")) {
1915                     }
1916                     child = child.getNextSibling();
1917                 }
1918 
1919                 if (gotWidth && gotHeight) {
1920                     pHYs_present = true;
1921                     pHYs_unitSpecifier = 1;
1922                     pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F);
1923                     pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F);
1924                 } else if (gotAspectRatio) {
1925                     pHYs_present = true;
1926                     pHYs_unitSpecifier = 0;
1927 
1928                     // Find a reasonable rational approximation
1929                     int denom = 1;
1930                     for (; denom < 100; denom++) {
1931                         int num = (int)(aspectRatio*denom);
1932                         if (Math.abs(num/denom - aspectRatio) < 0.001) {
1933                             break;
1934                         }
1935                     }
1936                     pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
1937                     pHYs_pixelsPerUnitYAxis = denom;
1938                 }
1939             } else if (name.equals("Document")) {
1940                 Node child = node.getFirstChild();
1941                 while (child != null) {
1942                     String childName = child.getNodeName();
1943                     if (childName.equals("ImageModificationTime")) {
1944                         tIME_present = true;
1945                         tIME_year = getIntAttribute(child, "year");
1946                         tIME_month = getIntAttribute(child, "month");
1947                         tIME_day = getIntAttribute(child, "day");
1948                         tIME_hour =
1949                             getIntAttribute(child, "hour", 0, false);
1950                         tIME_minute =
1951                             getIntAttribute(child, "minute", 0, false);
1952                         tIME_second =
1953                             getIntAttribute(child, "second", 0, false);
1954 //                  } else if (childName.equals("SubimageInterpretation")) {
1955 //                  } else if (childName.equals("ImageCreationTime")) {
1956                     }
1957                     child = child.getNextSibling();
1958                 }
1959             } else if (name.equals("Text")) {
1960                 Node child = node.getFirstChild();
1961                 while (child != null) {
1962                     String childName = child.getNodeName();
1963                     if (childName.equals("TextEntry")) {
1964                         String keyword =
1965                             getAttribute(child, "keyword", "", false);
1966                         String value = getAttribute(child, "value");
1967                         String language =
1968                             getAttribute(child, "language", "", false);
1969                         String compression =
1970                             getAttribute(child, "compression", "none", false);
1971 
1972                         if (!isValidKeyword(keyword)) {
1973                             // Just ignore this node, PNG requires keywords
1974                         } else if (isISOLatin(value, true)) {
1975                             if (compression.equals("zip")) {
1976                                 // Use a zTXt node
1977                                 zTXt_keyword.add(keyword);
1978                                 zTXt_text.add(value);
1979                                 zTXt_compressionMethod.add(Integer.valueOf(0));
1980                             } else {
1981                                 // Use a tEXt node
1982                                 tEXt_keyword.add(keyword);
1983                                 tEXt_text.add(value);
1984                             }
1985                         } else {
1986                             // Use an iTXt node
1987                             iTXt_keyword.add(keyword);
1988                             iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip")));
1989                             iTXt_compressionMethod.add(Integer.valueOf(0));
1990                             iTXt_languageTag.add(language);
1991                             iTXt_translatedKeyword.add(keyword); // fake it
1992                             iTXt_text.add(value);
1993                         }
1994                     }
1995                     child = child.getNextSibling();
1996                 }
1997 //          } else if (name.equals("Transparency")) {
1998 //              Node child = node.getFirstChild();
1999 //              while (child != null) {
2000 //                  String childName = child.getNodeName();
2001 //                  if (childName.equals("Alpha")) {
2002 //                  } else if (childName.equals("TransparentIndex")) {
2003 //                  } else if (childName.equals("TransparentColor")) {
2004 //                  } else if (childName.equals("TileTransparencies")) {
2005 //                  } else if (childName.equals("TileOpacities")) {
2006 //                  }
2007 //                  child = child.getNextSibling();
2008 //              }
2009 //          } else {
2010 //              // fatal(node, "Unknown child of root node!");
2011             }
2012 
2013             node = node.getNextSibling();
2014         }
2015     }
2016 
2017     // Reset all instance variables to their initial state
2018     public void reset() {
2019         IHDR_present = false;
2020         PLTE_present = false;
2021         bKGD_present = false;
2022         cHRM_present = false;
2023         gAMA_present = false;
2024         hIST_present = false;
2025         iCCP_present = false;
2026         iTXt_keyword = new ArrayList<String>();
2027         iTXt_compressionFlag = new ArrayList<Boolean>();
2028         iTXt_compressionMethod = new ArrayList<Integer>();
2029         iTXt_languageTag = new ArrayList<String>();
2030         iTXt_translatedKeyword = new ArrayList<String>();
2031         iTXt_text = new ArrayList<String>();
2032         pHYs_present = false;
2033         sBIT_present = false;
2034         sPLT_present = false;
2035         sRGB_present = false;
2036         tEXt_keyword = new ArrayList<String>();
2037         tEXt_text = new ArrayList<String>();
2038         tIME_present = false;
2039         tRNS_present = false;
2040         zTXt_keyword = new ArrayList<String>();
2041         zTXt_compressionMethod = new ArrayList<Integer>();
2042         zTXt_text = new ArrayList<String>();
2043         unknownChunkType = new ArrayList<String>();
2044         unknownChunkData = new ArrayList<byte[]>();
2045     }
2046 }