jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java
changeset 2 90ce3da70b43
child 1734 861400729115
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,2016 @@
+/*
+ * Copyright 2000-2001 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.imageio.plugins.png;
+
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.SampleModel;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormat;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+import org.w3c.dom.Node;
+
+public class PNGMetadata extends IIOMetadata implements Cloneable {
+
+    // package scope
+    public static final String
+        nativeMetadataFormatName = "javax_imageio_png_1.0";
+
+    protected static final String nativeMetadataFormatClassName
+        = "com.sun.imageio.plugins.png.PNGMetadataFormat";
+
+    // Color types for IHDR chunk
+    public static final String[] IHDR_colorTypeNames = {
+        "Grayscale", null, "RGB", "Palette",
+        "GrayAlpha", null, "RGBAlpha"
+    };
+
+    public static final int[] IHDR_numChannels = {
+        1, 0, 3, 3, 2, 0, 4
+    };
+
+    // Bit depths for IHDR chunk
+    public static final String[] IHDR_bitDepths = {
+        "1", "2", "4", "8", "16"
+    };
+
+    // Compression methods for IHDR chunk
+    public static final String[] IHDR_compressionMethodNames = {
+        "deflate"
+    };
+
+    // Filter methods for IHDR chunk
+    public static final String[] IHDR_filterMethodNames = {
+        "adaptive"
+    };
+
+    // Interlace methods for IHDR chunk
+    public static final String[] IHDR_interlaceMethodNames = {
+        "none", "adam7"
+    };
+
+    // Compression methods for iCCP chunk
+    public static final String[] iCCP_compressionMethodNames = {
+        "deflate"
+    };
+
+    // Compression methods for zTXt chunk
+    public static final String[] zTXt_compressionMethodNames = {
+        "deflate"
+    };
+
+    // "Unknown" unit for pHYs chunk
+    public static final int PHYS_UNIT_UNKNOWN = 0;
+
+    // "Meter" unit for pHYs chunk
+    public static final int PHYS_UNIT_METER = 1;
+
+    // Unit specifiers for pHYs chunk
+    public static final String[] unitSpecifierNames = {
+        "unknown", "meter"
+    };
+
+    // Rendering intents for sRGB chunk
+    public static final String[] renderingIntentNames = {
+        "Perceptual", // 0
+        "Relative colorimetric", // 1
+        "Saturation", // 2
+        "Absolute colorimetric" // 3
+
+    };
+
+    // Color space types for Chroma->ColorSpaceType node
+    public static final String[] colorSpaceTypeNames = {
+        "GRAY", null, "RGB", "RGB",
+        "GRAY", null, "RGB"
+    };
+
+    // IHDR chunk
+    public boolean IHDR_present;
+    public int IHDR_width;
+    public int IHDR_height;
+    public int IHDR_bitDepth;
+    public int IHDR_colorType;
+    public int IHDR_compressionMethod;
+    public int IHDR_filterMethod;
+    public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
+
+    // PLTE chunk
+    public boolean PLTE_present;
+    public byte[] PLTE_red;
+    public byte[] PLTE_green;
+    public byte[] PLTE_blue;
+
+    // If non-null, used to reorder palette entries during encoding in
+    // order to minimize the size of the tRNS chunk.  Thus an index of
+    // 'i' in the source should be encoded as index 'PLTE_order[i]'.
+    // PLTE_order will be null unless 'initialize' is called with an
+    // IndexColorModel image type.
+    public int[] PLTE_order = null;
+
+    // bKGD chunk
+    // If external (non-PNG sourced) data has red = green = blue,
+    // always store it as gray and promote when writing
+    public boolean bKGD_present;
+    public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
+    public int bKGD_index;
+    public int bKGD_gray;
+    public int bKGD_red;
+    public int bKGD_green;
+    public int bKGD_blue;
+
+    // cHRM chunk
+    public boolean cHRM_present;
+    public int cHRM_whitePointX;
+    public int cHRM_whitePointY;
+    public int cHRM_redX;
+    public int cHRM_redY;
+    public int cHRM_greenX;
+    public int cHRM_greenY;
+    public int cHRM_blueX;
+    public int cHRM_blueY;
+
+    // gAMA chunk
+    public boolean gAMA_present;
+    public int gAMA_gamma;
+
+    // hIST chunk
+    public boolean hIST_present;
+    public char[] hIST_histogram;
+
+    // iCCP chunk
+    public boolean iCCP_present;
+    public String iCCP_profileName;
+    public int iCCP_compressionMethod;
+    public byte[] iCCP_compressedProfile;
+
+    // iTXt chunk
+    public ArrayList iTXt_keyword = new ArrayList(); // Strings
+    public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers
+    public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers
+    public ArrayList iTXt_languageTag = new ArrayList(); // Strings
+    public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings
+    public ArrayList iTXt_text = new ArrayList(); // Strings
+
+    // pHYs chunk
+    public boolean pHYs_present;
+    public int pHYs_pixelsPerUnitXAxis;
+    public int pHYs_pixelsPerUnitYAxis;
+    public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
+
+    // sBIT chunk
+    public boolean sBIT_present;
+    public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
+    public int sBIT_grayBits;
+    public int sBIT_redBits;
+    public int sBIT_greenBits;
+    public int sBIT_blueBits;
+    public int sBIT_alphaBits;
+
+    // sPLT chunk
+    public boolean sPLT_present;
+    public String sPLT_paletteName; // 1-79 characters
+    public int sPLT_sampleDepth; // 8 or 16
+    public int[] sPLT_red;
+    public int[] sPLT_green;
+    public int[] sPLT_blue;
+    public int[] sPLT_alpha;
+    public int[] sPLT_frequency;
+
+    // sRGB chunk
+    public boolean sRGB_present;
+    public int sRGB_renderingIntent;
+
+    // tEXt chunk
+    public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings
+    public ArrayList tEXt_text = new ArrayList(); // Strings
+
+    // tIME chunk
+    public boolean tIME_present;
+    public int tIME_year;
+    public int tIME_month;
+    public int tIME_day;
+    public int tIME_hour;
+    public int tIME_minute;
+    public int tIME_second;
+
+    // tRNS chunk
+    // If external (non-PNG sourced) data has red = green = blue,
+    // always store it as gray and promote when writing
+    public boolean tRNS_present;
+    public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
+    public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
+    public int tRNS_gray;
+    public int tRNS_red;
+    public int tRNS_green;
+    public int tRNS_blue;
+
+    // zTXt chunk
+    public ArrayList zTXt_keyword = new ArrayList(); // Strings
+    public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers
+    public ArrayList zTXt_text = new ArrayList(); // Strings
+
+    // Unknown chunks
+    public ArrayList unknownChunkType = new ArrayList(); // Strings
+    public ArrayList unknownChunkData = new ArrayList(); // byte arrays
+
+    public PNGMetadata() {
+        super(true,
+              nativeMetadataFormatName,
+              nativeMetadataFormatClassName,
+              null, null);
+    }
+
+    public PNGMetadata(IIOMetadata metadata) {
+        // TODO -- implement
+    }
+
+    /**
+     * Sets the IHDR_bitDepth and IHDR_colorType variables.
+     * The <code>numBands</code> parameter is necessary since
+     * we may only be writing a subset of the image bands.
+     */
+    public void initialize(ImageTypeSpecifier imageType, int numBands) {
+        ColorModel colorModel = imageType.getColorModel();
+        SampleModel sampleModel = imageType.getSampleModel();
+
+        // Initialize IHDR_bitDepth
+        int[] sampleSize = sampleModel.getSampleSize();
+        int bitDepth = sampleSize[0];
+        // Choose max bit depth over all channels
+        // Fixes bug 4413109
+        for (int i = 1; i < sampleSize.length; i++) {
+            if (sampleSize[i] > bitDepth) {
+                bitDepth = sampleSize[i];
+            }
+        }
+        // Multi-channel images must have a bit depth of 8 or 16
+        if (sampleSize.length > 1 && bitDepth < 8) {
+            bitDepth = 8;
+        }
+
+        // Round bit depth up to a power of 2
+        if (bitDepth > 2 && bitDepth < 4) {
+            bitDepth = 4;
+        } else if (bitDepth > 4 && bitDepth < 8) {
+            bitDepth = 8;
+        } else if (bitDepth > 8 && bitDepth < 16) {
+            bitDepth = 16;
+        } else if (bitDepth > 16) {
+            throw new RuntimeException("bitDepth > 16!");
+        }
+        IHDR_bitDepth = bitDepth;
+
+        // Initialize IHDR_colorType
+        if (colorModel instanceof IndexColorModel) {
+            IndexColorModel icm = (IndexColorModel)colorModel;
+            int size = icm.getMapSize();
+
+            byte[] reds = new byte[size];
+            icm.getReds(reds);
+            byte[] greens = new byte[size];
+            icm.getGreens(greens);
+            byte[] blues = new byte[size];
+            icm.getBlues(blues);
+
+            // Determine whether the color tables are actually a gray ramp
+            // if the color type has not been set previously
+            boolean isGray = false;
+            if (!IHDR_present ||
+                (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) {
+                isGray = true;
+                int scale = 255/((1 << IHDR_bitDepth) - 1);
+                for (int i = 0; i < size; i++) {
+                    byte red = reds[i];
+                    if ((red != (byte)(i*scale)) ||
+                        (red != greens[i]) ||
+                        (red != blues[i])) {
+                        isGray = false;
+                        break;
+                    }
+                }
+            }
+
+            // Determine whether transparency exists
+            boolean hasAlpha = colorModel.hasAlpha();
+
+            byte[] alpha = null;
+            if (hasAlpha) {
+                alpha = new byte[size];
+                icm.getAlphas(alpha);
+            }
+
+            /*
+             * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images
+             * contained more than 1024 pixels (or even than 768 pixels in case of
+             * single transparent pixel in palette).
+             * For such images alpha samples in raster will occupy more space than
+             * it is required to store palette so it could be reasonable to
+             * use PNG_COLOR_PALETTE color type for large images.
+             */
+
+            if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
+            } else if (isGray && !hasAlpha) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
+            } else {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+                PLTE_present = true;
+                PLTE_order = null;
+                PLTE_red = (byte[])reds.clone();
+                PLTE_green = (byte[])greens.clone();
+                PLTE_blue = (byte[])blues.clone();
+
+                if (hasAlpha) {
+                    tRNS_present = true;
+                    tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+
+                    PLTE_order = new int[alpha.length];
+
+                    // Reorder the palette so that non-opaque entries
+                    // come first.  Since the tRNS chunk does not have
+                    // to store trailing 255's, this can save a
+                    // considerable amount of space when encoding
+                    // images with only one transparent pixel value,
+                    // e.g., images from GIF sources.
+
+                    byte[] newAlpha = new byte[alpha.length];
+
+                    // Scan for non-opaque entries and assign them
+                    // positions starting at 0.
+                    int newIndex = 0;
+                    for (int i = 0; i < alpha.length; i++) {
+                        if (alpha[i] != (byte)255) {
+                            PLTE_order[i] = newIndex;
+                            newAlpha[newIndex] = alpha[i];
+                            ++newIndex;
+                        }
+                    }
+                    int numTransparent = newIndex;
+
+                    // Scan for opaque entries and assign them
+                    // positions following the non-opaque entries.
+                    for (int i = 0; i < alpha.length; i++) {
+                        if (alpha[i] == (byte)255) {
+                            PLTE_order[i] = newIndex++;
+                        }
+                    }
+
+                    // Reorder the palettes
+                    byte[] oldRed = PLTE_red;
+                    byte[] oldGreen = PLTE_green;
+                    byte[] oldBlue = PLTE_blue;
+                    int len = oldRed.length; // All have the same length
+                    PLTE_red = new byte[len];
+                    PLTE_green = new byte[len];
+                    PLTE_blue = new byte[len];
+                    for (int i = 0; i < len; i++) {
+                        PLTE_red[PLTE_order[i]] = oldRed[i];
+                        PLTE_green[PLTE_order[i]] = oldGreen[i];
+                        PLTE_blue[PLTE_order[i]] = oldBlue[i];
+                    }
+
+                    // Copy only the transparent entries into tRNS_alpha
+                    tRNS_alpha = new byte[numTransparent];
+                    System.arraycopy(newAlpha, 0,
+                                     tRNS_alpha, 0, numTransparent);
+                }
+            }
+        } else {
+            if (numBands == 1) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
+            } else if (numBands == 2) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
+            } else if (numBands == 3) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_RGB;
+            } else if (numBands == 4) {
+                IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
+            } else {
+                throw new RuntimeException("Number of bands not 1-4!");
+            }
+        }
+
+        IHDR_present = true;
+    }
+
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    private ArrayList cloneBytesArrayList(ArrayList in) {
+        if (in == null) {
+            return null;
+        } else {
+            ArrayList list = new ArrayList(in.size());
+            Iterator iter = in.iterator();
+            while (iter.hasNext()) {
+                Object o = iter.next();
+                if (o == null) {
+                    list.add(null);
+                } else {
+                    list.add(((byte[])o).clone());
+                }
+            }
+
+            return list;
+        }
+    }
+
+    // Deep clone
+    public Object clone() {
+        PNGMetadata metadata;
+        try {
+            metadata = (PNGMetadata)super.clone();
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+
+        // unknownChunkData needs deep clone
+        metadata.unknownChunkData =
+            cloneBytesArrayList(this.unknownChunkData);
+
+        return metadata;
+    }
+
+    public Node getAsTree(String formatName) {
+        if (formatName.equals(nativeMetadataFormatName)) {
+            return getNativeTree();
+        } else if (formatName.equals
+                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            return getStandardTree();
+        } else {
+            throw new IllegalArgumentException("Not a recognized format!");
+        }
+    }
+
+    private Node getNativeTree() {
+        IIOMetadataNode node = null; // scratch node
+        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
+
+        // IHDR
+        if (IHDR_present) {
+            IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
+            IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
+            IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
+            IHDR_node.setAttribute("bitDepth",
+                                   Integer.toString(IHDR_bitDepth));
+            IHDR_node.setAttribute("colorType",
+                                   IHDR_colorTypeNames[IHDR_colorType]);
+            // IHDR_compressionMethod must be 0 in PNG 1.1
+            IHDR_node.setAttribute("compressionMethod",
+                          IHDR_compressionMethodNames[IHDR_compressionMethod]);
+            // IHDR_filterMethod must be 0 in PNG 1.1
+            IHDR_node.setAttribute("filterMethod",
+                                    IHDR_filterMethodNames[IHDR_filterMethod]);
+            IHDR_node.setAttribute("interlaceMethod",
+                              IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
+            root.appendChild(IHDR_node);
+        }
+
+        // PLTE
+        if (PLTE_present) {
+            IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
+            int numEntries = PLTE_red.length;
+            for (int i = 0; i < numEntries; i++) {
+                IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
+                entry.setAttribute("index", Integer.toString(i));
+                entry.setAttribute("red",
+                                   Integer.toString(PLTE_red[i] & 0xff));
+                entry.setAttribute("green",
+                                   Integer.toString(PLTE_green[i] & 0xff));
+                entry.setAttribute("blue",
+                                   Integer.toString(PLTE_blue[i] & 0xff));
+                PLTE_node.appendChild(entry);
+            }
+
+            root.appendChild(PLTE_node);
+        }
+
+        // bKGD
+        if (bKGD_present) {
+            IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
+
+            if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
+                node = new IIOMetadataNode("bKGD_Palette");
+                node.setAttribute("index", Integer.toString(bKGD_index));
+            } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
+                node = new IIOMetadataNode("bKGD_Grayscale");
+                node.setAttribute("gray", Integer.toString(bKGD_gray));
+            } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) {
+                node = new IIOMetadataNode("bKGD_RGB");
+                node.setAttribute("red", Integer.toString(bKGD_red));
+                node.setAttribute("green", Integer.toString(bKGD_green));
+                node.setAttribute("blue", Integer.toString(bKGD_blue));
+            }
+            bKGD_node.appendChild(node);
+
+            root.appendChild(bKGD_node);
+        }
+
+        // cHRM
+        if (cHRM_present) {
+            IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
+            cHRM_node.setAttribute("whitePointX",
+                              Integer.toString(cHRM_whitePointX));
+            cHRM_node.setAttribute("whitePointY",
+                              Integer.toString(cHRM_whitePointY));
+            cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
+            cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
+            cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
+            cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
+            cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
+            cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
+
+            root.appendChild(cHRM_node);
+        }
+
+        // gAMA
+        if (gAMA_present) {
+            IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
+            gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
+
+            root.appendChild(gAMA_node);
+        }
+
+        // hIST
+        if (hIST_present) {
+            IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
+
+            for (int i = 0; i < hIST_histogram.length; i++) {
+                IIOMetadataNode hist =
+                    new IIOMetadataNode("hISTEntry");
+                hist.setAttribute("index", Integer.toString(i));
+                hist.setAttribute("value",
+                                  Integer.toString(hIST_histogram[i]));
+                hIST_node.appendChild(hist);
+            }
+
+            root.appendChild(hIST_node);
+        }
+
+        // iCCP
+        if (iCCP_present) {
+            IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
+            iCCP_node.setAttribute("profileName", iCCP_profileName);
+            iCCP_node.setAttribute("compressionMethod",
+                          iCCP_compressionMethodNames[iCCP_compressionMethod]);
+
+            Object profile = iCCP_compressedProfile;
+            if (profile != null) {
+                profile = ((byte[])profile).clone();
+            }
+            iCCP_node.setUserObject(profile);
+
+            root.appendChild(iCCP_node);
+        }
+
+        // iTXt
+        if (iTXt_keyword.size() > 0) {
+            IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
+            for (int i = 0; i < iTXt_keyword.size(); i++) {
+                Integer val;
+
+                IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
+                iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i));
+                val = (Integer)iTXt_compressionFlag.get(i);
+                iTXt_node.setAttribute("compressionFlag", val.toString());
+                val = (Integer)iTXt_compressionMethod.get(i);
+                iTXt_node.setAttribute("compressionMethod", val.toString());
+                iTXt_node.setAttribute("languageTag",
+                                       (String)iTXt_languageTag.get(i));
+                iTXt_node.setAttribute("translatedKeyword",
+                                       (String)iTXt_translatedKeyword.get(i));
+                iTXt_node.setAttribute("text", (String)iTXt_text.get(i));
+
+                iTXt_parent.appendChild(iTXt_node);
+            }
+
+            root.appendChild(iTXt_parent);
+        }
+
+        // pHYs
+        if (pHYs_present) {
+            IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
+            pHYs_node.setAttribute("pixelsPerUnitXAxis",
+                              Integer.toString(pHYs_pixelsPerUnitXAxis));
+            pHYs_node.setAttribute("pixelsPerUnitYAxis",
+                                   Integer.toString(pHYs_pixelsPerUnitYAxis));
+            pHYs_node.setAttribute("unitSpecifier",
+                                   unitSpecifierNames[pHYs_unitSpecifier]);
+
+            root.appendChild(pHYs_node);
+        }
+
+        // sBIT
+        if (sBIT_present) {
+            IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
+
+            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) {
+                node = new IIOMetadataNode("sBIT_Grayscale");
+                node.setAttribute("gray",
+                                  Integer.toString(sBIT_grayBits));
+            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
+                node = new IIOMetadataNode("sBIT_GrayAlpha");
+                node.setAttribute("gray",
+                                  Integer.toString(sBIT_grayBits));
+                node.setAttribute("alpha",
+                                  Integer.toString(sBIT_alphaBits));
+            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) {
+                node = new IIOMetadataNode("sBIT_RGB");
+                node.setAttribute("red",
+                                  Integer.toString(sBIT_redBits));
+                node.setAttribute("green",
+                                  Integer.toString(sBIT_greenBits));
+                node.setAttribute("blue",
+                                  Integer.toString(sBIT_blueBits));
+            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
+                node = new IIOMetadataNode("sBIT_RGBAlpha");
+                node.setAttribute("red",
+                                  Integer.toString(sBIT_redBits));
+                node.setAttribute("green",
+                                  Integer.toString(sBIT_greenBits));
+                node.setAttribute("blue",
+                                  Integer.toString(sBIT_blueBits));
+                node.setAttribute("alpha",
+                                  Integer.toString(sBIT_alphaBits));
+            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
+                node = new IIOMetadataNode("sBIT_Palette");
+                node.setAttribute("red",
+                                  Integer.toString(sBIT_redBits));
+                node.setAttribute("green",
+                                  Integer.toString(sBIT_greenBits));
+                node.setAttribute("blue",
+                                  Integer.toString(sBIT_blueBits));
+            }
+            sBIT_node.appendChild(node);
+
+            root.appendChild(sBIT_node);
+        }
+
+        // sPLT
+        if (sPLT_present) {
+            IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
+
+            sPLT_node.setAttribute("name", sPLT_paletteName);
+            sPLT_node.setAttribute("sampleDepth",
+                                   Integer.toString(sPLT_sampleDepth));
+
+            int numEntries = sPLT_red.length;
+            for (int i = 0; i < numEntries; i++) {
+                IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
+                entry.setAttribute("index", Integer.toString(i));
+                entry.setAttribute("red", Integer.toString(sPLT_red[i]));
+                entry.setAttribute("green", Integer.toString(sPLT_green[i]));
+                entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
+                entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
+                entry.setAttribute("frequency",
+                                  Integer.toString(sPLT_frequency[i]));
+                sPLT_node.appendChild(entry);
+            }
+
+            root.appendChild(sPLT_node);
+        }
+
+        // sRGB
+        if (sRGB_present) {
+            IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
+            sRGB_node.setAttribute("renderingIntent",
+                                   renderingIntentNames[sRGB_renderingIntent]);
+
+            root.appendChild(sRGB_node);
+        }
+
+        // tEXt
+        if (tEXt_keyword.size() > 0) {
+            IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
+            for (int i = 0; i < tEXt_keyword.size(); i++) {
+                IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
+                tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i));
+                tEXt_node.setAttribute("value" , (String)tEXt_text.get(i));
+
+                tEXt_parent.appendChild(tEXt_node);
+            }
+
+            root.appendChild(tEXt_parent);
+        }
+
+        // tIME
+        if (tIME_present) {
+            IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
+            tIME_node.setAttribute("year", Integer.toString(tIME_year));
+            tIME_node.setAttribute("month", Integer.toString(tIME_month));
+            tIME_node.setAttribute("day", Integer.toString(tIME_day));
+            tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
+            tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
+            tIME_node.setAttribute("second", Integer.toString(tIME_second));
+
+            root.appendChild(tIME_node);
+        }
+
+        // tRNS
+        if (tRNS_present) {
+            IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
+
+            if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
+                node = new IIOMetadataNode("tRNS_Palette");
+
+                for (int i = 0; i < tRNS_alpha.length; i++) {
+                    IIOMetadataNode entry =
+                        new IIOMetadataNode("tRNS_PaletteEntry");
+                    entry.setAttribute("index", Integer.toString(i));
+                    entry.setAttribute("alpha",
+                                       Integer.toString(tRNS_alpha[i] & 0xff));
+                    node.appendChild(entry);
+                }
+            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
+                node = new IIOMetadataNode("tRNS_Grayscale");
+                node.setAttribute("gray", Integer.toString(tRNS_gray));
+            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
+                node = new IIOMetadataNode("tRNS_RGB");
+                node.setAttribute("red", Integer.toString(tRNS_red));
+                node.setAttribute("green", Integer.toString(tRNS_green));
+                node.setAttribute("blue", Integer.toString(tRNS_blue));
+            }
+            tRNS_node.appendChild(node);
+
+            root.appendChild(tRNS_node);
+        }
+
+        // zTXt
+        if (zTXt_keyword.size() > 0) {
+            IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
+            for (int i = 0; i < zTXt_keyword.size(); i++) {
+                IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
+                zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i));
+
+                int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue();
+                zTXt_node.setAttribute("compressionMethod",
+                                       zTXt_compressionMethodNames[cm]);
+
+                zTXt_node.setAttribute("text", (String)zTXt_text.get(i));
+
+                zTXt_parent.appendChild(zTXt_node);
+            }
+
+            root.appendChild(zTXt_parent);
+        }
+
+        // Unknown chunks
+        if (unknownChunkType.size() > 0) {
+            IIOMetadataNode unknown_parent =
+                new IIOMetadataNode("UnknownChunks");
+            for (int i = 0; i < unknownChunkType.size(); i++) {
+                IIOMetadataNode unknown_node =
+                    new IIOMetadataNode("UnknownChunk");
+                unknown_node.setAttribute("type",
+                                          (String)unknownChunkType.get(i));
+                unknown_node.setUserObject((byte[])unknownChunkData.get(i));
+
+                unknown_parent.appendChild(unknown_node);
+            }
+
+            root.appendChild(unknown_parent);
+        }
+
+        return root;
+    }
+
+    private int getNumChannels() {
+        // Determine number of channels
+        // Be careful about palette color with transparency
+        int numChannels = IHDR_numChannels[IHDR_colorType];
+        if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
+            tRNS_present && tRNS_colorType == IHDR_colorType) {
+            numChannels = 4;
+        }
+        return numChannels;
+    }
+
+    public IIOMetadataNode getStandardChromaNode() {
+        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("ColorSpaceType");
+        node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
+        chroma_node.appendChild(node);
+
+        node = new IIOMetadataNode("NumChannels");
+        node.setAttribute("value", Integer.toString(getNumChannels()));
+        chroma_node.appendChild(node);
+
+        if (gAMA_present) {
+            node = new IIOMetadataNode("Gamma");
+            node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
+            chroma_node.appendChild(node);
+        }
+
+        node = new IIOMetadataNode("BlackIsZero");
+        node.setAttribute("value", "true");
+        chroma_node.appendChild(node);
+
+        if (PLTE_present) {
+            boolean hasAlpha = tRNS_present &&
+                (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE);
+
+            node = new IIOMetadataNode("Palette");
+            for (int i = 0; i < PLTE_red.length; i++) {
+                IIOMetadataNode entry =
+                    new IIOMetadataNode("PaletteEntry");
+                entry.setAttribute("index", Integer.toString(i));
+                entry.setAttribute("red",
+                                   Integer.toString(PLTE_red[i] & 0xff));
+                entry.setAttribute("green",
+                                   Integer.toString(PLTE_green[i] & 0xff));
+                entry.setAttribute("blue",
+                                   Integer.toString(PLTE_blue[i] & 0xff));
+                if (hasAlpha) {
+                    int alpha = (i < tRNS_alpha.length) ?
+                        (tRNS_alpha[i] & 0xff) : 255;
+                    entry.setAttribute("alpha", Integer.toString(alpha));
+                }
+                node.appendChild(entry);
+            }
+            chroma_node.appendChild(node);
+        }
+
+        if (bKGD_present) {
+            if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
+                node = new IIOMetadataNode("BackgroundIndex");
+                node.setAttribute("value", Integer.toString(bKGD_index));
+            } else {
+                node = new IIOMetadataNode("BackgroundColor");
+                int r, g, b;
+
+                if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
+                    r = g = b = bKGD_gray;
+                } else {
+                    r = bKGD_red;
+                    g = bKGD_green;
+                    b = bKGD_blue;
+                }
+                node.setAttribute("red", Integer.toString(r));
+                node.setAttribute("green", Integer.toString(g));
+                node.setAttribute("blue", Integer.toString(b));
+            }
+            chroma_node.appendChild(node);
+        }
+
+        return chroma_node;
+    }
+
+    public IIOMetadataNode getStandardCompressionNode() {
+        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("CompressionTypeName");
+        node.setAttribute("value", "deflate");
+        compression_node.appendChild(node);
+
+        node = new IIOMetadataNode("Lossless");
+        node.setAttribute("value", "true");
+        compression_node.appendChild(node);
+
+        node = new IIOMetadataNode("NumProgressiveScans");
+        node.setAttribute("value",
+                          (IHDR_interlaceMethod == 0) ? "1" : "7");
+        compression_node.appendChild(node);
+
+        return compression_node;
+    }
+
+    private String repeat(String s, int times) {
+        if (times == 1) {
+            return s;
+        }
+        StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
+        sb.append(s);
+        for (int i = 1; i < times; i++) {
+            sb.append(" ");
+            sb.append(s);
+        }
+        return sb.toString();
+    }
+
+    public IIOMetadataNode getStandardDataNode() {
+        IIOMetadataNode data_node = new IIOMetadataNode("Data");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("PlanarConfiguration");
+        node.setAttribute("value", "PixelInterleaved");
+        data_node.appendChild(node);
+
+        node = new IIOMetadataNode("SampleFormat");
+        node.setAttribute("value",
+                          IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ?
+                          "Index" : "UnsignedIntegral");
+        data_node.appendChild(node);
+
+        String bitDepth = Integer.toString(IHDR_bitDepth);
+        node = new IIOMetadataNode("BitsPerSample");
+        node.setAttribute("value", repeat(bitDepth, getNumChannels()));
+        data_node.appendChild(node);
+
+        if (sBIT_present) {
+            node = new IIOMetadataNode("SignificantBitsPerSample");
+            String sbits;
+            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY ||
+                sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
+                sbits = Integer.toString(sBIT_grayBits);
+            } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB ||
+                     // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
+                sbits = Integer.toString(sBIT_redBits) + " " +
+                    Integer.toString(sBIT_greenBits) + " " +
+                    Integer.toString(sBIT_blueBits);
+            }
+
+            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
+                sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
+                sbits += " " + Integer.toString(sBIT_alphaBits);
+            }
+
+            node.setAttribute("value", sbits);
+            data_node.appendChild(node);
+        }
+
+        // SampleMSB
+
+        return data_node;
+    }
+
+    public IIOMetadataNode getStandardDimensionNode() {
+        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("PixelAspectRatio");
+        float ratio = pHYs_present ?
+            (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F;
+        node.setAttribute("value", Float.toString(ratio));
+        dimension_node.appendChild(node);
+
+        node = new IIOMetadataNode("ImageOrientation");
+        node.setAttribute("value", "Normal");
+        dimension_node.appendChild(node);
+
+        if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
+            node = new IIOMetadataNode("HorizontalPixelSize");
+            node.setAttribute("value",
+                              Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis));
+            dimension_node.appendChild(node);
+
+            node = new IIOMetadataNode("VerticalPixelSize");
+            node.setAttribute("value",
+                              Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis));
+            dimension_node.appendChild(node);
+        }
+
+        return dimension_node;
+    }
+
+    public IIOMetadataNode getStandardDocumentNode() {
+        if (!tIME_present) {
+            return null;
+        }
+
+        IIOMetadataNode document_node = new IIOMetadataNode("Document");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("ImageModificationTime");
+        node.setAttribute("year", Integer.toString(tIME_year));
+        node.setAttribute("month", Integer.toString(tIME_month));
+        node.setAttribute("day", Integer.toString(tIME_day));
+        node.setAttribute("hour", Integer.toString(tIME_hour));
+        node.setAttribute("minute", Integer.toString(tIME_minute));
+        node.setAttribute("second", Integer.toString(tIME_second));
+        document_node.appendChild(node);
+
+        return document_node;
+    }
+
+    public IIOMetadataNode getStandardTextNode() {
+        int numEntries = tEXt_keyword.size() +
+            iTXt_keyword.size() + zTXt_keyword.size();
+        if (numEntries == 0) {
+            return null;
+        }
+
+        IIOMetadataNode text_node = new IIOMetadataNode("Text");
+        IIOMetadataNode node = null; // scratch node
+
+        for (int i = 0; i < tEXt_keyword.size(); i++) {
+            node = new IIOMetadataNode("TextEntry");
+            node.setAttribute("keyword", (String)tEXt_keyword.get(i));
+            node.setAttribute("value", (String)tEXt_text.get(i));
+            node.setAttribute("encoding", "ISO-8859-1");
+            node.setAttribute("compression", "none");
+
+            text_node.appendChild(node);
+        }
+
+        for (int i = 0; i < iTXt_keyword.size(); i++) {
+            node = new IIOMetadataNode("TextEntry");
+            node.setAttribute("keyword", (String)iTXt_keyword.get(i));
+            node.setAttribute("value", (String)iTXt_text.get(i));
+            node.setAttribute("language",
+                              (String)iTXt_languageTag.get(i));
+            if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) {
+                node.setAttribute("compression", "deflate");
+            } else {
+                node.setAttribute("compression", "none");
+            }
+
+            text_node.appendChild(node);
+        }
+
+        for (int i = 0; i < zTXt_keyword.size(); i++) {
+            node = new IIOMetadataNode("TextEntry");
+            node.setAttribute("keyword", (String)zTXt_keyword.get(i));
+            node.setAttribute("value", (String)zTXt_text.get(i));
+            node.setAttribute("compression", "deflate");
+
+            text_node.appendChild(node);
+        }
+
+        return text_node;
+    }
+
+    public IIOMetadataNode getStandardTransparencyNode() {
+        IIOMetadataNode transparency_node =
+            new IIOMetadataNode("Transparency");
+        IIOMetadataNode node = null; // scratch node
+
+        node = new IIOMetadataNode("Alpha");
+        boolean hasAlpha =
+            (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) ||
+            (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) ||
+            (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
+             tRNS_present &&
+             (tRNS_colorType == IHDR_colorType) &&
+             (tRNS_alpha != null));
+        node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none");
+        transparency_node.appendChild(node);
+
+        if (tRNS_present) {
+            node = new IIOMetadataNode("TransparentColor");
+            if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
+                node.setAttribute("value",
+                                  Integer.toString(tRNS_red) + " " +
+                                  Integer.toString(tRNS_green) + " " +
+                                  Integer.toString(tRNS_blue));
+            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
+                node.setAttribute("value", Integer.toString(tRNS_gray));
+            }
+            transparency_node.appendChild(node);
+        }
+
+        return transparency_node;
+    }
+
+    // Shorthand for throwing an IIOInvalidTreeException
+    private void fatal(Node node, String reason)
+        throws IIOInvalidTreeException {
+        throw new IIOInvalidTreeException(reason, node);
+    }
+
+    // Get an integer-valued attribute
+    private String getStringAttribute(Node node, String name,
+                                      String defaultValue, boolean required)
+        throws IIOInvalidTreeException {
+        Node attr = node.getAttributes().getNamedItem(name);
+        if (attr == null) {
+            if (!required) {
+                return defaultValue;
+            } else {
+                fatal(node, "Required attribute " + name + " not present!");
+            }
+        }
+        return attr.getNodeValue();
+    }
+
+
+    // Get an integer-valued attribute
+    private int getIntAttribute(Node node, String name,
+                                int defaultValue, boolean required)
+        throws IIOInvalidTreeException {
+        String value = getStringAttribute(node, name, null, required);
+        if (value == null) {
+            return defaultValue;
+        }
+        return Integer.parseInt(value);
+    }
+
+    // Get a float-valued attribute
+    private float getFloatAttribute(Node node, String name,
+                                    float defaultValue, boolean required)
+        throws IIOInvalidTreeException {
+        String value = getStringAttribute(node, name, null, required);
+        if (value == null) {
+            return defaultValue;
+        }
+        return Float.parseFloat(value);
+    }
+
+    // Get a required integer-valued attribute
+    private int getIntAttribute(Node node, String name)
+        throws IIOInvalidTreeException {
+        return getIntAttribute(node, name, -1, true);
+    }
+
+    // Get a required float-valued attribute
+    private float getFloatAttribute(Node node, String name)
+        throws IIOInvalidTreeException {
+        return getFloatAttribute(node, name, -1.0F, true);
+    }
+
+    // Get a boolean-valued attribute
+    private boolean getBooleanAttribute(Node node, String name,
+                                        boolean defaultValue,
+                                        boolean required)
+        throws IIOInvalidTreeException {
+        Node attr = node.getAttributes().getNamedItem(name);
+        if (attr == null) {
+            if (!required) {
+                return defaultValue;
+            } else {
+                fatal(node, "Required attribute " + name + " not present!");
+            }
+        }
+        String value = attr.getNodeValue();
+        if (value.equals("true")) {
+            return true;
+        } else if (value.equals("false")) {
+            return false;
+        } else {
+            fatal(node, "Attribute " + name + " must be 'true' or 'false'!");
+            return false;
+        }
+    }
+
+    // Get a required boolean-valued attribute
+    private boolean getBooleanAttribute(Node node, String name)
+        throws IIOInvalidTreeException {
+        return getBooleanAttribute(node, name, false, true);
+    }
+
+    // Get an enumerated attribute as an index into a String array
+    private int getEnumeratedAttribute(Node node,
+                                       String name, String[] legalNames,
+                                       int defaultValue, boolean required)
+        throws IIOInvalidTreeException {
+        Node attr = node.getAttributes().getNamedItem(name);
+        if (attr == null) {
+            if (!required) {
+                return defaultValue;
+            } else {
+                fatal(node, "Required attribute " + name + " not present!");
+            }
+        }
+        String value = attr.getNodeValue();
+        for (int i = 0; i < legalNames.length; i++) {
+            if (value.equals(legalNames[i])) {
+                return i;
+            }
+        }
+
+        fatal(node, "Illegal value for attribute " + name + "!");
+        return -1;
+    }
+
+    // Get a required enumerated attribute as an index into a String array
+    private int getEnumeratedAttribute(Node node,
+                                       String name, String[] legalNames)
+        throws IIOInvalidTreeException {
+        return getEnumeratedAttribute(node, name, legalNames, -1, true);
+    }
+
+    // Get a String-valued attribute
+    private String getAttribute(Node node, String name,
+                                String defaultValue, boolean required)
+        throws IIOInvalidTreeException {
+        Node attr = node.getAttributes().getNamedItem(name);
+        if (attr == null) {
+            if (!required) {
+                return defaultValue;
+            } else {
+                fatal(node, "Required attribute " + name + " not present!");
+            }
+        }
+        return attr.getNodeValue();
+    }
+
+    // Get a required String-valued attribute
+    private String getAttribute(Node node, String name)
+        throws IIOInvalidTreeException {
+            return getAttribute(node, name, null, true);
+    }
+
+    public void mergeTree(String formatName, Node root)
+        throws IIOInvalidTreeException {
+        if (formatName.equals(nativeMetadataFormatName)) {
+            if (root == null) {
+                throw new IllegalArgumentException("root == null!");
+            }
+            mergeNativeTree(root);
+        } else if (formatName.equals
+                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            if (root == null) {
+                throw new IllegalArgumentException("root == null!");
+            }
+            mergeStandardTree(root);
+        } else {
+            throw new IllegalArgumentException("Not a recognized format!");
+        }
+    }
+
+    private void mergeNativeTree(Node root)
+        throws IIOInvalidTreeException {
+        Node node = root;
+        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
+            fatal(node, "Root must be " + nativeMetadataFormatName);
+        }
+
+        node = node.getFirstChild();
+        while (node != null) {
+            String name = node.getNodeName();
+
+            if (name.equals("IHDR")) {
+                IHDR_width = getIntAttribute(node, "width");
+                IHDR_height = getIntAttribute(node, "height");
+                IHDR_bitDepth = getEnumeratedAttribute(node, "bitDepth",
+                                                       IHDR_bitDepths);
+                IHDR_colorType = getEnumeratedAttribute(node, "colorType",
+                                                        IHDR_colorTypeNames);
+                IHDR_compressionMethod =
+                    getEnumeratedAttribute(node, "compressionMethod",
+                                           IHDR_compressionMethodNames);
+                IHDR_filterMethod =
+                    getEnumeratedAttribute(node,
+                                           "filterMethod",
+                                           IHDR_filterMethodNames);
+                IHDR_interlaceMethod =
+                    getEnumeratedAttribute(node, "interlaceMethod",
+                                           IHDR_interlaceMethodNames);
+                IHDR_present = true;
+            } else if (name.equals("PLTE")) {
+                byte[] red = new byte[256];
+                byte[] green  = new byte[256];
+                byte[] blue = new byte[256];
+                int maxindex = -1;
+
+                Node PLTE_entry = node.getFirstChild();
+                if (PLTE_entry == null) {
+                    fatal(node, "Palette has no entries!");
+                }
+
+                while (PLTE_entry != null) {
+                    if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
+                        fatal(node,
+                              "Only a PLTEEntry may be a child of a PLTE!");
+                    }
+
+                    int index = getIntAttribute(PLTE_entry, "index");
+                    if (index < 0 || index > 255) {
+                        fatal(node,
+                              "Bad value for PLTEEntry attribute index!");
+                    }
+                    if (index > maxindex) {
+                        maxindex = index;
+                    }
+                    red[index] =
+                        (byte)getIntAttribute(PLTE_entry, "red");
+                    green[index] =
+                        (byte)getIntAttribute(PLTE_entry, "green");
+                    blue[index] =
+                        (byte)getIntAttribute(PLTE_entry, "blue");
+
+                    PLTE_entry = PLTE_entry.getNextSibling();
+                }
+
+                int numEntries = maxindex + 1;
+                PLTE_red = new byte[numEntries];
+                PLTE_green = new byte[numEntries];
+                PLTE_blue = new byte[numEntries];
+                System.arraycopy(red, 0, PLTE_red, 0, numEntries);
+                System.arraycopy(green, 0, PLTE_green, 0, numEntries);
+                System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
+                PLTE_present = true;
+            } else if (name.equals("bKGD")) {
+                bKGD_present = false; // Guard against partial overwrite
+                Node bKGD_node = node.getFirstChild();
+                if (bKGD_node == null) {
+                    fatal(node, "bKGD node has no children!");
+                }
+                String bKGD_name = bKGD_node.getNodeName();
+                if (bKGD_name.equals("bKGD_Palette")) {
+                    bKGD_index = getIntAttribute(bKGD_node, "index");
+                    bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+                } else if (bKGD_name.equals("bKGD_Grayscale")) {
+                    bKGD_gray = getIntAttribute(bKGD_node, "gray");
+                    bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
+                } else if (bKGD_name.equals("bKGD_RGB")) {
+                    bKGD_red = getIntAttribute(bKGD_node, "red");
+                    bKGD_green = getIntAttribute(bKGD_node, "green");
+                    bKGD_blue = getIntAttribute(bKGD_node, "blue");
+                    bKGD_colorType = PNGImageReader.PNG_COLOR_RGB;
+                } else {
+                    fatal(node, "Bad child of a bKGD node!");
+                }
+                if (bKGD_node.getNextSibling() != null) {
+                    fatal(node, "bKGD node has more than one child!");
+                }
+
+                bKGD_present = true;
+            } else if (name.equals("cHRM")) {
+                cHRM_whitePointX = getIntAttribute(node, "whitePointX");
+                cHRM_whitePointY = getIntAttribute(node, "whitePointY");
+                cHRM_redX = getIntAttribute(node, "redX");
+                cHRM_redY = getIntAttribute(node, "redY");
+                cHRM_greenX = getIntAttribute(node, "greenX");
+                cHRM_greenY = getIntAttribute(node, "greenY");
+                cHRM_blueX = getIntAttribute(node, "blueX");
+                cHRM_blueY = getIntAttribute(node, "blueY");
+
+                cHRM_present = true;
+            } else if (name.equals("gAMA")) {
+                gAMA_gamma = getIntAttribute(node, "value");
+                gAMA_present = true;
+            } else if (name.equals("hIST")) {
+                char[] hist = new char[256];
+                int maxindex = -1;
+
+                Node hIST_entry = node.getFirstChild();
+                if (hIST_entry == null) {
+                    fatal(node, "hIST node has no children!");
+                }
+
+                while (hIST_entry != null) {
+                    if (!hIST_entry.getNodeName().equals("hISTEntry")) {
+                        fatal(node,
+                              "Only a hISTEntry may be a child of a hIST!");
+                    }
+
+                    int index = getIntAttribute(hIST_entry, "index");
+                    if (index < 0 || index > 255) {
+                        fatal(node,
+                              "Bad value for histEntry attribute index!");
+                    }
+                    if (index > maxindex) {
+                        maxindex = index;
+                    }
+                    hist[index] =
+                        (char)getIntAttribute(hIST_entry, "value");
+
+                    hIST_entry = hIST_entry.getNextSibling();
+                }
+
+                int numEntries = maxindex + 1;
+                hIST_histogram = new char[numEntries];
+                System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
+
+                hIST_present = true;
+            } else if (name.equals("iCCP")) {
+                iCCP_profileName = getAttribute(node, "profileName");
+                iCCP_compressionMethod =
+                    getEnumeratedAttribute(node, "compressionMethod",
+                                           iCCP_compressionMethodNames);
+                Object compressedProfile =
+                    ((IIOMetadataNode)node).getUserObject();
+                if (compressedProfile == null) {
+                    fatal(node, "No ICCP profile present in user object!");
+                }
+                if (!(compressedProfile instanceof byte[])) {
+                    fatal(node, "User object not a byte array!");
+                }
+
+                iCCP_compressedProfile =
+                    (byte[])((byte[])compressedProfile).clone();
+
+                iCCP_present = true;
+            } else if (name.equals("iTXt")) {
+                Node iTXt_node = node.getFirstChild();
+                while (iTXt_node != null) {
+                    if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
+                        fatal(node,
+                              "Only an iTXtEntry may be a child of an iTXt!");
+                    }
+
+                    String keyword = getAttribute(iTXt_node, "keyword");
+                    iTXt_keyword.add(keyword);
+
+                    boolean compressionFlag =
+                        getBooleanAttribute(iTXt_node, "compressionFlag");
+                    iTXt_compressionFlag.add(new Boolean(compressionFlag));
+
+                    String compressionMethod =
+                        getAttribute(iTXt_node, "compressionMethod");
+                    iTXt_compressionMethod.add(compressionMethod);
+
+                    String languageTag =
+                        getAttribute(iTXt_node, "languageTag");
+                    iTXt_languageTag.add(languageTag);
+
+                    String translatedKeyword =
+                        getAttribute(iTXt_node, "translatedKeyword");
+                    iTXt_translatedKeyword.add(translatedKeyword);
+
+                    String text = getAttribute(iTXt_node, "text");
+                    iTXt_text.add(text);
+
+                    iTXt_node = iTXt_node.getNextSibling();
+                }
+            } else if (name.equals("pHYs")) {
+                pHYs_pixelsPerUnitXAxis =
+                    getIntAttribute(node, "pixelsPerUnitXAxis");
+                pHYs_pixelsPerUnitYAxis =
+                    getIntAttribute(node, "pixelsPerUnitYAxis");
+                pHYs_unitSpecifier =
+                    getEnumeratedAttribute(node, "unitSpecifier",
+                                           unitSpecifierNames);
+
+                pHYs_present = true;
+            } else if (name.equals("sBIT")) {
+                sBIT_present = false; // Guard against partial overwrite
+                Node sBIT_node = node.getFirstChild();
+                if (sBIT_node == null) {
+                    fatal(node, "sBIT node has no children!");
+                }
+                String sBIT_name = sBIT_node.getNodeName();
+                if (sBIT_name.equals("sBIT_Grayscale")) {
+                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
+                    sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
+                } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
+                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
+                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
+                    sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
+                } else if (sBIT_name.equals("sBIT_RGB")) {
+                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
+                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
+                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
+                    sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
+                } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
+                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
+                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
+                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
+                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
+                    sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
+                } else if (sBIT_name.equals("sBIT_Palette")) {
+                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
+                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
+                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
+                    sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+                } else {
+                    fatal(node, "Bad child of an sBIT node!");
+                }
+                if (sBIT_node.getNextSibling() != null) {
+                    fatal(node, "sBIT node has more than one child!");
+                }
+
+                sBIT_present = true;
+            } else if (name.equals("sPLT")) {
+                sPLT_paletteName = getAttribute(node, "name");
+                sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
+
+                int[] red = new int[256];
+                int[] green  = new int[256];
+                int[] blue = new int[256];
+                int[] alpha = new int[256];
+                int[] frequency = new int[256];
+                int maxindex = -1;
+
+                Node sPLT_entry = node.getFirstChild();
+                if (sPLT_entry == null) {
+                    fatal(node, "sPLT node has no children!");
+                }
+
+                while (sPLT_entry != null) {
+                    if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
+                        fatal(node,
+                              "Only an sPLTEntry may be a child of an sPLT!");
+                    }
+
+                    int index = getIntAttribute(sPLT_entry, "index");
+                    if (index < 0 || index > 255) {
+                        fatal(node,
+                              "Bad value for PLTEEntry attribute index!");
+                    }
+                    if (index > maxindex) {
+                        maxindex = index;
+                    }
+                    red[index] = getIntAttribute(sPLT_entry, "red");
+                    green[index] = getIntAttribute(sPLT_entry, "green");
+                    blue[index] = getIntAttribute(sPLT_entry, "blue");
+                    alpha[index] = getIntAttribute(sPLT_entry, "alpha");
+                    frequency[index] =
+                        getIntAttribute(sPLT_entry, "frequency");
+
+                    sPLT_entry = sPLT_entry.getNextSibling();
+                }
+
+                int numEntries = maxindex + 1;
+                sPLT_red = new int[numEntries];
+                sPLT_green = new int[numEntries];
+                sPLT_blue = new int[numEntries];
+                sPLT_alpha = new int[numEntries];
+                sPLT_frequency = new int[numEntries];
+                System.arraycopy(red, 0, sPLT_red, 0, numEntries);
+                System.arraycopy(green, 0, sPLT_green, 0, numEntries);
+                System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
+                System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
+                System.arraycopy(frequency, 0,
+                                 sPLT_frequency, 0, numEntries);
+
+                sPLT_present = true;
+            } else if (name.equals("sRGB")) {
+                sRGB_renderingIntent =
+                    getEnumeratedAttribute(node, "renderingIntent",
+                                           renderingIntentNames);
+
+                sRGB_present = true;
+            } else if (name.equals("tEXt")) {
+                Node tEXt_node = node.getFirstChild();
+                while (tEXt_node != null) {
+                    if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
+                        fatal(node,
+                              "Only an tEXtEntry may be a child of an tEXt!");
+                    }
+
+                    String keyword = getAttribute(tEXt_node, "keyword");
+                    tEXt_keyword.add(keyword);
+
+                    String text = getAttribute(tEXt_node, "value");
+                    tEXt_text.add(text);
+
+                    tEXt_node = tEXt_node.getNextSibling();
+                }
+            } else if (name.equals("tIME")) {
+                tIME_year = getIntAttribute(node, "year");
+                tIME_month = getIntAttribute(node, "month");
+                tIME_day = getIntAttribute(node, "day");
+                tIME_hour = getIntAttribute(node, "hour");
+                tIME_minute = getIntAttribute(node, "minute");
+                tIME_second = getIntAttribute(node, "second");
+
+                tIME_present = true;
+            } else if (name.equals("tRNS")) {
+                tRNS_present = false; // Guard against partial overwrite
+                Node tRNS_node = node.getFirstChild();
+                if (tRNS_node == null) {
+                    fatal(node, "tRNS node has no children!");
+                }
+                String tRNS_name = tRNS_node.getNodeName();
+                if (tRNS_name.equals("tRNS_Palette")) {
+                    byte[] alpha = new byte[256];
+                    int maxindex = -1;
+
+                    Node tRNS_paletteEntry = tRNS_node.getFirstChild();
+                    if (tRNS_paletteEntry == null) {
+                        fatal(node, "tRNS_Palette node has no children!");
+                    }
+                    while (tRNS_paletteEntry != null) {
+                        if (!tRNS_paletteEntry.getNodeName().equals(
+                                                        "tRNS_PaletteEntry")) {
+                            fatal(node,
+                 "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
+                        }
+                        int index =
+                            getIntAttribute(tRNS_paletteEntry, "index");
+                        if (index < 0 || index > 255) {
+                            fatal(node,
+                           "Bad value for tRNS_PaletteEntry attribute index!");
+                        }
+                        if (index > maxindex) {
+                            maxindex = index;
+                        }
+                        alpha[index] =
+                            (byte)getIntAttribute(tRNS_paletteEntry,
+                                                  "alpha");
+
+                        tRNS_paletteEntry =
+                            tRNS_paletteEntry.getNextSibling();
+                    }
+
+                    int numEntries = maxindex + 1;
+                    tRNS_alpha = new byte[numEntries];
+                    tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+                    System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
+                } else if (tRNS_name.equals("tRNS_Grayscale")) {
+                    tRNS_gray = getIntAttribute(tRNS_node, "gray");
+                    tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY;
+                } else if (tRNS_name.equals("tRNS_RGB")) {
+                    tRNS_red = getIntAttribute(tRNS_node, "red");
+                    tRNS_green = getIntAttribute(tRNS_node, "green");
+                    tRNS_blue = getIntAttribute(tRNS_node, "blue");
+                    tRNS_colorType = PNGImageReader.PNG_COLOR_RGB;
+                } else {
+                    fatal(node, "Bad child of a tRNS node!");
+                }
+                if (tRNS_node.getNextSibling() != null) {
+                    fatal(node, "tRNS node has more than one child!");
+                }
+
+                tRNS_present = true;
+            } else if (name.equals("zTXt")) {
+                Node zTXt_node = node.getFirstChild();
+                while (zTXt_node != null) {
+                    if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
+                        fatal(node,
+                              "Only an zTXtEntry may be a child of an zTXt!");
+                    }
+
+                    String keyword = getAttribute(zTXt_node, "keyword");
+                    zTXt_keyword.add(keyword);
+
+                    int compressionMethod =
+                        getEnumeratedAttribute(zTXt_node, "compressionMethod",
+                                               zTXt_compressionMethodNames);
+                    zTXt_compressionMethod.add(new Integer(compressionMethod));
+
+                    String text = getAttribute(zTXt_node, "text");
+                    zTXt_text.add(text);
+
+                    zTXt_node = zTXt_node.getNextSibling();
+                }
+            } else if (name.equals("UnknownChunks")) {
+                Node unknown_node = node.getFirstChild();
+                while (unknown_node != null) {
+                    if (!unknown_node.getNodeName().equals("UnknownChunk")) {
+                        fatal(node,
+                   "Only an UnknownChunk may be a child of an UnknownChunks!");
+                    }
+                    String chunkType = getAttribute(unknown_node, "type");
+                    Object chunkData =
+                        ((IIOMetadataNode)unknown_node).getUserObject();
+
+                    if (chunkType.length() != 4) {
+                        fatal(unknown_node,
+                              "Chunk type must be 4 characters!");
+                    }
+                    if (chunkData == null) {
+                        fatal(unknown_node,
+                              "No chunk data present in user object!");
+                    }
+                    if (!(chunkData instanceof byte[])) {
+                        fatal(unknown_node,
+                              "User object not a byte array!");
+                    }
+                    unknownChunkType.add(chunkType);
+                    unknownChunkData.add(((byte[])chunkData).clone());
+
+                    unknown_node = unknown_node.getNextSibling();
+                }
+            } else {
+                fatal(node, "Unknown child of root node!");
+            }
+
+            node = node.getNextSibling();
+        }
+    }
+
+    private boolean isISOLatin(String s) {
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            if (s.charAt(i) > 255) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void mergeStandardTree(Node root)
+        throws IIOInvalidTreeException {
+        Node node = root;
+        if (!node.getNodeName()
+            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            fatal(node, "Root must be " +
+                  IIOMetadataFormatImpl.standardMetadataFormatName);
+        }
+
+        node = node.getFirstChild();
+        while (node != null) {
+            String name = node.getNodeName();
+
+            if (name.equals("Chroma")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("Gamma")) {
+                        float gamma = getFloatAttribute(child, "value");
+                        gAMA_present = true;
+                        gAMA_gamma = (int)(gamma*100000 + 0.5);
+                    } else if (childName.equals("Palette")) {
+                        byte[] red = new byte[256];
+                        byte[] green = new byte[256];
+                        byte[] blue = new byte[256];
+                        int maxindex = -1;
+
+                        Node entry = child.getFirstChild();
+                        while (entry != null) {
+                            int index = getIntAttribute(entry, "index");
+                            if (index >= 0 && index <= 255) {
+                                red[index] =
+                                    (byte)getIntAttribute(entry, "red");
+                                green[index] =
+                                    (byte)getIntAttribute(entry, "green");
+                                blue[index] =
+                                    (byte)getIntAttribute(entry, "blue");
+                                if (index > maxindex) {
+                                    maxindex = index;
+                                }
+                            }
+                            entry = entry.getNextSibling();
+                        }
+
+                        int numEntries = maxindex + 1;
+                        PLTE_red = new byte[numEntries];
+                        PLTE_green = new byte[numEntries];
+                        PLTE_blue = new byte[numEntries];
+                        System.arraycopy(red, 0, PLTE_red, 0, numEntries);
+                        System.arraycopy(green, 0, PLTE_green, 0, numEntries);
+                        System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
+                        PLTE_present = true;
+                    } else if (childName.equals("BackgroundIndex")) {
+                        bKGD_present = true;
+                        bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
+                        bKGD_index = getIntAttribute(child, "value");
+                    } else if (childName.equals("BackgroundColor")) {
+                        int red = getIntAttribute(child, "red");
+                        int green = getIntAttribute(child, "green");
+                        int blue = getIntAttribute(child, "blue");
+                        if (red == green && red == blue) {
+                            bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
+                            bKGD_gray = red;
+                        } else {
+                            bKGD_red = red;
+                            bKGD_green = green;
+                            bKGD_blue = blue;
+                        }
+                        bKGD_present = true;
+                    }
+//                  } else if (childName.equals("ColorSpaceType")) {
+//                  } else if (childName.equals("NumChannels")) {
+
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Compression")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("NumProgressiveScans")) {
+                        // Use Adam7 if NumProgressiveScans > 1
+                        int scans = getIntAttribute(child, "value");
+                        IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
+//                  } else if (childName.equals("CompressionTypeName")) {
+//                  } else if (childName.equals("Lossless")) {
+//                  } else if (childName.equals("BitRate")) {
+                    }
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Data")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("BitsPerSample")) {
+                        String s = getAttribute(child, "value");
+                        StringTokenizer t = new StringTokenizer(s);
+                        int maxBits = -1;
+                        while (t.hasMoreTokens()) {
+                            int bits = Integer.parseInt(t.nextToken());
+                            if (bits > maxBits) {
+                                maxBits = bits;
+                            }
+                        }
+                        if (maxBits < 1) {
+                            maxBits = 1;
+                        }
+                        if (maxBits == 3) maxBits = 4;
+                        if (maxBits > 4 || maxBits < 8) {
+                            maxBits = 8;
+                        }
+                        if (maxBits > 8) {
+                            maxBits = 16;
+                        }
+                        IHDR_bitDepth = maxBits;
+                    } else if (childName.equals("SignificantBitsPerSample")) {
+                        String s = getAttribute(child, "value");
+                        StringTokenizer t = new StringTokenizer(s);
+                        int numTokens = t.countTokens();
+                        if (numTokens == 1) {
+                            sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
+                            sBIT_grayBits = Integer.parseInt(t.nextToken());
+                        } else if (numTokens == 2) {
+                            sBIT_colorType =
+                              PNGImageReader.PNG_COLOR_GRAY_ALPHA;
+                            sBIT_grayBits = Integer.parseInt(t.nextToken());
+                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
+                        } else if (numTokens == 3) {
+                            sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
+                            sBIT_redBits = Integer.parseInt(t.nextToken());
+                            sBIT_greenBits = Integer.parseInt(t.nextToken());
+                            sBIT_blueBits = Integer.parseInt(t.nextToken());
+                        } else if (numTokens == 4) {
+                            sBIT_colorType =
+                              PNGImageReader.PNG_COLOR_RGB_ALPHA;
+                            sBIT_redBits = Integer.parseInt(t.nextToken());
+                            sBIT_greenBits = Integer.parseInt(t.nextToken());
+                            sBIT_blueBits = Integer.parseInt(t.nextToken());
+                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
+                        }
+                        if (numTokens >= 1 && numTokens <= 4) {
+                            sBIT_present = true;
+                        }
+//                      } else if (childName.equals("PlanarConfiguration")) {
+//                      } else if (childName.equals("SampleFormat")) {
+//                      } else if (childName.equals("SampleMSB")) {
+                    }
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Dimension")) {
+                boolean gotWidth = false;
+                boolean gotHeight = false;
+                boolean gotAspectRatio = false;
+
+                float width = -1.0F;
+                float height = -1.0F;
+                float aspectRatio = -1.0F;
+
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("PixelAspectRatio")) {
+                        aspectRatio = getFloatAttribute(child, "value");
+                        gotAspectRatio = true;
+                    } else if (childName.equals("HorizontalPixelSize")) {
+                        width = getFloatAttribute(child, "value");
+                        gotWidth = true;
+                    } else if (childName.equals("VerticalPixelSize")) {
+                        height = getFloatAttribute(child, "value");
+                        gotHeight = true;
+//                  } else if (childName.equals("ImageOrientation")) {
+//                  } else if
+//                      (childName.equals("HorizontalPhysicalPixelSpacing")) {
+//                  } else if
+//                      (childName.equals("VerticalPhysicalPixelSpacing")) {
+//                  } else if (childName.equals("HorizontalPosition")) {
+//                  } else if (childName.equals("VerticalPosition")) {
+//                  } else if (childName.equals("HorizontalPixelOffset")) {
+//                  } else if (childName.equals("VerticalPixelOffset")) {
+                    }
+                    child = child.getNextSibling();
+                }
+
+                if (gotWidth && gotHeight) {
+                    pHYs_present = true;
+                    pHYs_unitSpecifier = 1;
+                    pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F);
+                    pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F);
+                } else if (gotAspectRatio) {
+                    pHYs_present = true;
+                    pHYs_unitSpecifier = 0;
+
+                    // Find a reasonable rational approximation
+                    int denom = 1;
+                    for (; denom < 100; denom++) {
+                        int num = (int)(aspectRatio*denom);
+                        if (Math.abs(num/denom - aspectRatio) < 0.001) {
+                            break;
+                        }
+                    }
+                    pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
+                    pHYs_pixelsPerUnitYAxis = denom;
+                }
+            } else if (name.equals("Document")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("ImageModificationTime")) {
+                        tIME_present = true;
+                        tIME_year = getIntAttribute(child, "year");
+                        tIME_month = getIntAttribute(child, "month");
+                        tIME_day = getIntAttribute(child, "day");
+                        tIME_hour =
+                            getIntAttribute(child, "hour", 0, false);
+                        tIME_minute =
+                            getIntAttribute(child, "minute", 0, false);
+                        tIME_second =
+                            getIntAttribute(child, "second", 0, false);
+//                  } else if (childName.equals("SubimageInterpretation")) {
+//                  } else if (childName.equals("ImageCreationTime")) {
+                    }
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Text")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("TextEntry")) {
+                        String keyword = getAttribute(child, "keyword");
+                        String value = getAttribute(child, "value");
+                        String encoding = getAttribute(child, "encoding");
+                        String language = getAttribute(child, "language");
+                        String compression =
+                            getAttribute(child, "compression");
+
+                        if (isISOLatin(value)) {
+                            if (compression.equals("zip")) {
+                                // Use a zTXt node
+                                zTXt_keyword.add(keyword);
+                                zTXt_text.add(value);
+                                zTXt_compressionMethod.add(new Integer(0));
+                            } else {
+                                // Use a tEXt node
+                                tEXt_keyword.add(keyword);
+                                tEXt_text.add(value);
+                            }
+                        } else {
+                            int flag = compression.equals("zip") ?
+                                1 : 0;
+
+                            // Use an iTXt node
+                            iTXt_keyword.add(keyword);
+                            iTXt_compressionFlag.add(new Integer(flag));
+                            iTXt_compressionMethod.add(new Integer(0));
+                            iTXt_languageTag.add(language);
+                            iTXt_translatedKeyword.add(keyword); // fake it
+                            iTXt_text.add(value);
+                        }
+                    }
+                    child = child.getNextSibling();
+                }
+//          } else if (name.equals("Transparency")) {
+//              Node child = node.getFirstChild();
+//              while (child != null) {
+//                  String childName = child.getNodeName();
+//                  if (childName.equals("Alpha")) {
+//                  } else if (childName.equals("TransparentIndex")) {
+//                  } else if (childName.equals("TransparentColor")) {
+//                  } else if (childName.equals("TileTransparencies")) {
+//                  } else if (childName.equals("TileOpacities")) {
+//                  }
+//                  child = child.getNextSibling();
+//              }
+//          } else {
+//              // fatal(node, "Unknown child of root node!");
+            }
+
+            node = node.getNextSibling();
+        }
+    }
+
+    // Reset all instance variables to their initial state
+    public void reset() {
+        IHDR_present = false;
+        PLTE_present = false;
+        bKGD_present = false;
+        cHRM_present = false;
+        gAMA_present = false;
+        hIST_present = false;
+        iCCP_present = false;
+        iTXt_keyword = new ArrayList();
+        iTXt_compressionFlag = new ArrayList();
+        iTXt_compressionMethod = new ArrayList();
+        iTXt_languageTag = new ArrayList();
+        iTXt_translatedKeyword = new ArrayList();
+        iTXt_text = new ArrayList();
+        pHYs_present = false;
+        sBIT_present = false;
+        sPLT_present = false;
+        sRGB_present = false;
+        tEXt_keyword = new ArrayList();
+        tEXt_text = new ArrayList();
+        tIME_present = false;
+        tRNS_present = false;
+        zTXt_keyword = new ArrayList();
+        zTXt_compressionMethod = new ArrayList();
+        zTXt_text = new ArrayList();
+        unknownChunkType = new ArrayList();
+        unknownChunkData = new ArrayList();
+    }
+}