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