jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java
changeset 34416 68c0d866db5d
child 36448 a07e108d5722
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1630 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageInputStream;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.ExifParentTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFImageMetadata extends IIOMetadata {
+
+    // package scope
+
+    public static final String NATIVE_METADATA_FORMAT_NAME =
+        "javax_imageio_tiff_image_1.0";
+
+    public static final String NATIVE_METADATA_FORMAT_CLASS_NAME =
+        "javax.imageio.plugins.tiff.TIFFImageMetadataFormat";
+
+    private List<TIFFTagSet> tagSets;
+
+    TIFFIFD rootIFD;
+
+    public TIFFImageMetadata(List<TIFFTagSet> tagSets) {
+        super(true,
+              NATIVE_METADATA_FORMAT_NAME,
+              NATIVE_METADATA_FORMAT_CLASS_NAME,
+              null, null);
+
+        this.tagSets = tagSets;
+        this.rootIFD = new TIFFIFD(tagSets);
+    }
+
+    public TIFFImageMetadata(TIFFIFD ifd) {
+        super(true,
+              NATIVE_METADATA_FORMAT_NAME,
+              NATIVE_METADATA_FORMAT_CLASS_NAME,
+              null, null);
+        this.tagSets = ifd.getTagSetList();
+        this.rootIFD = ifd;
+    }
+
+    public void initializeFromStream(ImageInputStream stream,
+                                     boolean ignoreUnknownFields)
+        throws IOException {
+        rootIFD.initialize(stream, true, ignoreUnknownFields);
+    }
+
+    public void addShortOrLongField(int tagNumber, int value) {
+        TIFFField field = new TIFFField(rootIFD.getTag(tagNumber), value);
+        rootIFD.addTIFFField(field);
+    }
+
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    private Node getIFDAsTree(TIFFIFD ifd,
+                              String parentTagName, int parentTagNumber) {
+        IIOMetadataNode IFDRoot = new IIOMetadataNode("TIFFIFD");
+        if (parentTagNumber != 0) {
+            IFDRoot.setAttribute("parentTagNumber",
+                                 Integer.toString(parentTagNumber));
+        }
+        if (parentTagName != null) {
+            IFDRoot.setAttribute("parentTagName", parentTagName);
+        }
+
+        List<TIFFTagSet> tagSets = ifd.getTagSetList();
+        if (tagSets.size() > 0) {
+            Iterator<TIFFTagSet> iter = tagSets.iterator();
+            StringBuilder tagSetNames = new StringBuilder();
+            while (iter.hasNext()) {
+                TIFFTagSet tagSet = iter.next();
+                tagSetNames.append(tagSet.getClass().getName());
+                if (iter.hasNext()) {
+                    tagSetNames.append(",");
+                }
+            }
+
+            IFDRoot.setAttribute("tagSets", tagSetNames.toString());
+        }
+
+        Iterator<TIFFField> iter = ifd.iterator();
+        while (iter.hasNext()) {
+            TIFFField f = iter.next();
+            int tagNumber = f.getTagNumber();
+            TIFFTag tag = TIFFIFD.getTag(tagNumber, tagSets);
+
+            Node node = null;
+            if (tag == null) {
+                node = f.getAsNativeNode();
+            } else if (tag.isIFDPointer() && f.hasDirectory()) {
+                TIFFIFD subIFD = (TIFFIFD)f.getDirectory();
+
+                // Recurse
+                node = getIFDAsTree(subIFD, tag.getName(), tag.getNumber());
+            } else {
+                node = f.getAsNativeNode();
+            }
+
+            if (node != null) {
+                IFDRoot.appendChild(node);
+            }
+        }
+
+        return IFDRoot;
+    }
+
+    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 root = new IIOMetadataNode(nativeMetadataFormatName);
+
+        Node IFDNode = getIFDAsTree(rootIFD, null, 0);
+        root.appendChild(IFDNode);
+
+        return root;
+    }
+
+    private static final String[] colorSpaceNames = {
+        "GRAY", // WhiteIsZero
+        "GRAY", // BlackIsZero
+        "RGB", // RGB
+        "RGB", // PaletteColor
+        "GRAY", // TransparencyMask
+        "CMYK", // CMYK
+        "YCbCr", // YCbCr
+        "Lab", // CIELab
+        "Lab", // ICCLab
+    };
+
+    public IIOMetadataNode getStandardChromaNode() {
+        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        // Set the PhotometricInterpretation and the palette color flag.
+        int photometricInterpretation = -1;
+        boolean isPaletteColor = false;
+        f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f != null) {
+            photometricInterpretation = f.getAsInt(0);
+
+            isPaletteColor =
+                photometricInterpretation ==
+                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+        }
+
+        // Determine the number of channels.
+        int numChannels = -1;
+        if(isPaletteColor) {
+            numChannels = 3;
+        } else {
+            f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+            if (f != null) {
+                numChannels = f.getAsInt(0);
+            } else { // f == null
+                f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                if(f != null) {
+                    numChannels = f.getCount();
+                }
+            }
+        }
+
+        if(photometricInterpretation != -1) {
+            if (photometricInterpretation >= 0 &&
+                photometricInterpretation < colorSpaceNames.length) {
+                node = new IIOMetadataNode("ColorSpaceType");
+                String csName;
+                if(photometricInterpretation ==
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK &&
+                   numChannels == 3) {
+                    csName = "CMY";
+                } else {
+                    csName = colorSpaceNames[photometricInterpretation];
+                }
+                node.setAttribute("name", csName);
+                chroma_node.appendChild(node);
+            }
+
+            node = new IIOMetadataNode("BlackIsZero");
+            node.setAttribute("value",
+                              (photometricInterpretation ==
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO)
+                              ? "FALSE" : "TRUE");
+            chroma_node.appendChild(node);
+        }
+
+        if(numChannels != -1) {
+            node = new IIOMetadataNode("NumChannels");
+            node.setAttribute("value", Integer.toString(numChannels));
+            chroma_node.appendChild(node);
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
+        if (f != null) {
+            // NOTE: The presence of hasAlpha is vestigial: there is
+            // no way in TIFF to represent an alpha component in a palette
+            // color image. See bug 5086341.
+            boolean hasAlpha = false;
+
+            node = new IIOMetadataNode("Palette");
+            int len = f.getCount()/(hasAlpha ? 4 : 3);
+            for (int i = 0; i < len; i++) {
+                IIOMetadataNode entry =
+                    new IIOMetadataNode("PaletteEntry");
+                entry.setAttribute("index", Integer.toString(i));
+
+                int r = (f.getAsInt(i)*255)/65535;
+                int g = (f.getAsInt(len + i)*255)/65535;
+                int b = (f.getAsInt(2*len + i)*255)/65535;
+
+                entry.setAttribute("red", Integer.toString(r));
+                entry.setAttribute("green", Integer.toString(g));
+                entry.setAttribute("blue", Integer.toString(b));
+                if (hasAlpha) {
+                    int alpha = 0;
+                    entry.setAttribute("alpha", Integer.toString(alpha));
+                }
+                node.appendChild(entry);
+            }
+            chroma_node.appendChild(node);
+        }
+
+        return chroma_node;
+    }
+
+    public IIOMetadataNode getStandardCompressionNode() {
+        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        if (f != null) {
+            String compressionTypeName = null;
+            int compression = f.getAsInt(0);
+            boolean isLossless = true; // obligate initialization.
+            if(compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
+                compressionTypeName = "None";
+                isLossless = true;
+            } else {
+                int[] compressionNumbers = TIFFImageWriter.compressionNumbers;
+                for(int i = 0; i < compressionNumbers.length; i++) {
+                    if(compression == compressionNumbers[i]) {
+                        compressionTypeName =
+                            TIFFImageWriter.compressionTypes[i];
+                        isLossless =
+                            TIFFImageWriter.isCompressionLossless[i];
+                        break;
+                    }
+                }
+            }
+
+            if (compressionTypeName != null) {
+                node = new IIOMetadataNode("CompressionTypeName");
+                node.setAttribute("value", compressionTypeName);
+                compression_node.appendChild(node);
+
+                node = new IIOMetadataNode("Lossless");
+                node.setAttribute("value", isLossless ? "TRUE" : "FALSE");
+                compression_node.appendChild(node);
+            }
+        }
+
+        node = new IIOMetadataNode("NumProgressiveScans");
+        node.setAttribute("value", "1");
+        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
+
+        TIFFField f;
+
+        boolean isPaletteColor = false;
+        f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f != null) {
+            isPaletteColor =
+                f.getAsInt(0) ==
+                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+        String planarConfiguration = "PixelInterleaved";
+        if (f != null &&
+            f.getAsInt(0) == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+            planarConfiguration = "PlaneInterleaved";
+        }
+
+        node = new IIOMetadataNode("PlanarConfiguration");
+        node.setAttribute("value", planarConfiguration);
+        data_node.appendChild(node);
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f != null) {
+            int photometricInterpretation = f.getAsInt(0);
+            String sampleFormat = "UnsignedIntegral";
+
+            if (photometricInterpretation ==
+                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
+                sampleFormat = "Index";
+            } else {
+                f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+                if (f != null) {
+                    int format = f.getAsInt(0);
+                    if (format ==
+                        BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
+                        sampleFormat = "SignedIntegral";
+                    } else if (format ==
+                        BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER) {
+                        sampleFormat = "UnsignedIntegral";
+                    } else if (format ==
+                               BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+                        sampleFormat = "Real";
+                    } else {
+                        sampleFormat = null; // don't know
+                    }
+                }
+            }
+            if (sampleFormat != null) {
+                node = new IIOMetadataNode("SampleFormat");
+                node.setAttribute("value", sampleFormat);
+                data_node.appendChild(node);
+            }
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+        int[] bitsPerSample = null;
+        if(f != null) {
+            bitsPerSample = f.getAsInts();
+        } else {
+            f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+            int compression = f != null ?
+                f.getAsInt(0) : BaselineTIFFTagSet.COMPRESSION_NONE;
+            if(getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER) !=
+               null ||
+               compression == BaselineTIFFTagSet.COMPRESSION_JPEG ||
+               compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG ||
+               getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) !=
+               null) {
+                f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+                if(f != null &&
+                   (f.getAsInt(0) ==
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO ||
+                    f.getAsInt(0) ==
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) {
+                    bitsPerSample = new int[] {8};
+                } else {
+                    bitsPerSample = new int[] {8, 8, 8};
+                }
+            } else {
+                bitsPerSample = new int[] {1};
+            }
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            if (i > 0) {
+                sb.append(" ");
+            }
+            sb.append(Integer.toString(bitsPerSample[i]));
+        }
+        node = new IIOMetadataNode("BitsPerSample");
+        if(isPaletteColor) {
+            node.setAttribute("value", repeat(sb.toString(), 3));
+        } else {
+            node.setAttribute("value", sb.toString());
+        }
+        data_node.appendChild(node);
+
+            // SampleMSB
+        f = getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
+        int fillOrder = f != null ?
+            f.getAsInt(0) : BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT;
+        sb = new StringBuffer();
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            if (i > 0) {
+                sb.append(" ");
+            }
+            int maxBitIndex = bitsPerSample[i] == 1 ?
+                7 : bitsPerSample[i] - 1;
+            int msb =
+                fillOrder == BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT ?
+                maxBitIndex : 0;
+            sb.append(Integer.toString(msb));
+        }
+        node = new IIOMetadataNode("SampleMSB");
+        if(isPaletteColor) {
+            node.setAttribute("value", repeat(sb.toString(), 3));
+        } else {
+            node.setAttribute("value", sb.toString());
+        }
+        data_node.appendChild(node);
+
+        return data_node;
+    }
+
+    private static final String[] orientationNames = {
+        null,
+        "Normal",
+        "FlipH",
+        "Rotate180",
+        "FlipV",
+        "FlipHRotate90",
+        "Rotate270",
+        "FlipVRotate90",
+        "Rotate90",
+    };
+
+    public IIOMetadataNode getStandardDimensionNode() {
+        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        long[] xres = null;
+        long[] yres = null;
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
+        if (f != null) {
+            xres = f.getAsRational(0).clone();
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
+        if (f != null) {
+            yres = f.getAsRational(0).clone();
+        }
+
+        if (xres != null && yres != null) {
+            node = new IIOMetadataNode("PixelAspectRatio");
+
+            // Compute (1/xres)/(1/yres)
+            // (xres_denom/xres_num)/(yres_denom/yres_num) =
+            // (xres_denom/xres_num)*(yres_num/yres_denom) =
+            // (xres_denom*yres_num)/(xres_num*yres_denom)
+            float ratio = (float)((double)xres[1]*yres[0])/(xres[0]*yres[1]);
+            node.setAttribute("value", Float.toString(ratio));
+            dimension_node.appendChild(node);
+        }
+
+        if (xres != null || yres != null) {
+            // Get unit field.
+            f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
+
+            // Set resolution unit.
+            int resolutionUnit = f != null ?
+                f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
+
+            // Have size if either centimeters or inches.
+            boolean gotPixelSize =
+                resolutionUnit != BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
+
+            // Convert pixels/inch to pixels/centimeter.
+            if (resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
+                // Divide xres by 2.54
+                if (xres != null) {
+                    xres[0] *= 100;
+                    xres[1] *= 254;
+                }
+
+                // Divide yres by 2.54
+                if (yres != null) {
+                    yres[0] *= 100;
+                    yres[1] *= 254;
+                }
+            }
+
+            if (gotPixelSize) {
+                if (xres != null) {
+                    float horizontalPixelSize = (float)(10.0*xres[1]/xres[0]);
+                    node = new IIOMetadataNode("HorizontalPixelSize");
+                    node.setAttribute("value",
+                                      Float.toString(horizontalPixelSize));
+                    dimension_node.appendChild(node);
+                }
+
+                if (yres != null) {
+                    float verticalPixelSize = (float)(10.0*yres[1]/yres[0]);
+                    node = new IIOMetadataNode("VerticalPixelSize");
+                    node.setAttribute("value",
+                                      Float.toString(verticalPixelSize));
+                    dimension_node.appendChild(node);
+                }
+            }
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
+        int resolutionUnit = f != null ?
+            f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
+        if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH ||
+           resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER) {
+            f = getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION);
+            if(f != null) {
+                long[] xpos = f.getAsRational(0);
+                float xPosition = (float)xpos[0]/(float)xpos[1];
+                // Convert to millimeters.
+                if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
+                    xPosition *= 254F;
+                } else {
+                    xPosition *= 10F;
+                }
+                node = new IIOMetadataNode("HorizontalPosition");
+                node.setAttribute("value",
+                                  Float.toString(xPosition));
+                dimension_node.appendChild(node);
+            }
+
+            f = getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION);
+            if(f != null) {
+                long[] ypos = f.getAsRational(0);
+                float yPosition = (float)ypos[0]/(float)ypos[1];
+                // Convert to millimeters.
+                if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
+                    yPosition *= 254F;
+                } else {
+                    yPosition *= 10F;
+                }
+                node = new IIOMetadataNode("VerticalPosition");
+                node.setAttribute("value",
+                                  Float.toString(yPosition));
+                dimension_node.appendChild(node);
+            }
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_ORIENTATION);
+        if (f != null) {
+            int o = f.getAsInt(0);
+            if (o >= 0 && o < orientationNames.length) {
+                node = new IIOMetadataNode("ImageOrientation");
+                node.setAttribute("value", orientationNames[o]);
+                dimension_node.appendChild(node);
+            }
+        }
+
+        return dimension_node;
+    }
+
+    public IIOMetadataNode getStandardDocumentNode() {
+        IIOMetadataNode document_node = new IIOMetadataNode("Document");
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        node = new IIOMetadataNode("FormatVersion");
+        node.setAttribute("value", "6.0");
+        document_node.appendChild(node);
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
+        if(f != null) {
+            int newSubFileType = f.getAsInt(0);
+            String value = null;
+            if((newSubFileType &
+                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY) != 0) {
+                value = "TransparencyMask";
+            } else if((newSubFileType &
+                       BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) != 0) {
+                value = "ReducedResolution";
+            } else if((newSubFileType &
+                       BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE) != 0) {
+                value = "SinglePage";
+            }
+            if(value != null) {
+                node = new IIOMetadataNode("SubimageInterpretation");
+                node.setAttribute("value", value);
+                document_node.appendChild(node);
+            }
+        }
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_DATE_TIME);
+        if (f != null) {
+            String s = f.getAsString(0);
+
+            // DateTime should be formatted as "YYYY:MM:DD hh:mm:ss".
+            if(s.length() == 19) {
+                node = new IIOMetadataNode("ImageCreationTime");
+
+                // Files with incorrect DateTime format have been
+                // observed so anticipate an exception from substring()
+                // and only add the node if the format is presumably
+                // correct.
+                boolean appendNode;
+                try {
+                    node.setAttribute("year", s.substring(0, 4));
+                    node.setAttribute("month", s.substring(5, 7));
+                    node.setAttribute("day", s.substring(8, 10));
+                    node.setAttribute("hour", s.substring(11, 13));
+                    node.setAttribute("minute", s.substring(14, 16));
+                    node.setAttribute("second", s.substring(17, 19));
+                    appendNode = true;
+                } catch(IndexOutOfBoundsException e) {
+                    appendNode = false;
+                }
+
+                if(appendNode) {
+                    document_node.appendChild(node);
+                }
+            }
+        }
+
+        return document_node;
+    }
+
+    public IIOMetadataNode getStandardTextNode() {
+        IIOMetadataNode text_node = null;
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        int[] textFieldTagNumbers = new int[] {
+            BaselineTIFFTagSet.TAG_DOCUMENT_NAME,
+            BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION,
+            BaselineTIFFTagSet.TAG_MAKE,
+            BaselineTIFFTagSet.TAG_MODEL,
+            BaselineTIFFTagSet.TAG_PAGE_NAME,
+            BaselineTIFFTagSet.TAG_SOFTWARE,
+            BaselineTIFFTagSet.TAG_ARTIST,
+            BaselineTIFFTagSet.TAG_HOST_COMPUTER,
+            BaselineTIFFTagSet.TAG_INK_NAMES,
+            BaselineTIFFTagSet.TAG_COPYRIGHT
+        };
+
+        for(int i = 0; i < textFieldTagNumbers.length; i++) {
+            f = getTIFFField(textFieldTagNumbers[i]);
+            if(f != null) {
+                String value = f.getAsString(0);
+                if(text_node == null) {
+                    text_node = new IIOMetadataNode("Text");
+                }
+                node = new IIOMetadataNode("TextEntry");
+                node.setAttribute("keyword", f.getTag().getName());
+                node.setAttribute("value", value);
+                text_node.appendChild(node);
+            }
+        }
+
+        return text_node;
+    }
+
+    public IIOMetadataNode getStandardTransparencyNode() {
+        IIOMetadataNode transparency_node =
+            new IIOMetadataNode("Transparency");
+        IIOMetadataNode node = null; // scratch node
+
+        TIFFField f;
+
+        node = new IIOMetadataNode("Alpha");
+        String value = "none";
+
+        f = getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
+        if(f != null) {
+            int[] extraSamples = f.getAsInts();
+            for(int i = 0; i < extraSamples.length; i++) {
+                if(extraSamples[i] ==
+                   BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
+                    value = "premultiplied";
+                    break;
+                } else if(extraSamples[i] ==
+                          BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA) {
+                    value = "nonpremultiplied";
+                    break;
+                }
+            }
+        }
+
+        node.setAttribute("value", value);
+        transparency_node.appendChild(node);
+
+        return transparency_node;
+    }
+
+    // Shorthand for throwing an IIOInvalidTreeException
+    private static void fatal(Node node, String reason)
+        throws IIOInvalidTreeException {
+        throw new IIOInvalidTreeException(reason, node);
+    }
+
+    private int[] listToIntArray(String list) {
+        StringTokenizer st = new StringTokenizer(list, " ");
+        ArrayList<Integer> intList = new ArrayList<Integer>();
+        while (st.hasMoreTokens()) {
+            String nextInteger = st.nextToken();
+            Integer nextInt = Integer.valueOf(nextInteger);
+            intList.add(nextInt);
+        }
+
+        int[] intArray = new int[intList.size()];
+        for(int i = 0; i < intArray.length; i++) {
+            intArray[i] = intList.get(i);
+        }
+
+        return intArray;
+    }
+
+    private char[] listToCharArray(String list) {
+        StringTokenizer st = new StringTokenizer(list, " ");
+        ArrayList<Integer> intList = new ArrayList<Integer>();
+        while (st.hasMoreTokens()) {
+            String nextInteger = st.nextToken();
+            Integer nextInt = Integer.valueOf(nextInteger);
+            intList.add(nextInt);
+        }
+
+        char[] charArray = new char[intList.size()];
+        for(int i = 0; i < charArray.length; i++) {
+            charArray[i] = (char)(intList.get(i).intValue());
+        }
+
+        return charArray;
+    }
+
+    private void mergeStandardTree(Node root)
+        throws IIOInvalidTreeException {
+        TIFFField f;
+        TIFFTag tag;
+
+        Node node = root;
+        if (!node.getNodeName()
+            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            fatal(node, "Root must be " +
+                  IIOMetadataFormatImpl.standardMetadataFormatName);
+        }
+
+        // Obtain the sample format and set the palette flag if appropriate.
+        String sampleFormat = null;
+        Node dataNode = getChildNode(root, "Data");
+        boolean isPaletteColor = false;
+        if(dataNode != null) {
+            Node sampleFormatNode = getChildNode(dataNode, "SampleFormat");
+            if(sampleFormatNode != null) {
+                sampleFormat = getAttribute(sampleFormatNode, "value");
+                isPaletteColor = sampleFormat.equals("Index");
+            }
+        }
+
+        // If palette flag not set check for palette.
+        if(!isPaletteColor) {
+            Node chromaNode = getChildNode(root, "Chroma");
+            if(chromaNode != null &&
+               getChildNode(chromaNode, "Palette") != null) {
+                isPaletteColor = true;
+            }
+        }
+
+        node = node.getFirstChild();
+        while (node != null) {
+            String name = node.getNodeName();
+
+            if (name.equals("Chroma")) {
+                String colorSpaceType = null;
+                String blackIsZero = null;
+                boolean gotPalette = false;
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("ColorSpaceType")) {
+                        colorSpaceType = getAttribute(child, "name");
+                    } else if (childName.equals("NumChannels")) {
+                        tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+                        int samplesPerPixel = isPaletteColor ?
+                            1 : Integer.parseInt(getAttribute(child, "value"));
+                        f = new TIFFField(tag, samplesPerPixel);
+                        rootIFD.addTIFFField(f);
+                    } else if (childName.equals("BlackIsZero")) {
+                        blackIsZero = getAttribute(child, "value");
+                    } else if (childName.equals("Palette")) {
+                        Node entry = child.getFirstChild();
+                        HashMap<Integer,char[]> palette = new HashMap<>();
+                        int maxIndex = -1;
+                        while(entry != null) {
+                            String entryName = entry.getNodeName();
+                            if(entryName.equals("PaletteEntry")) {
+                                String idx = getAttribute(entry, "index");
+                                int id = Integer.parseInt(idx);
+                                if(id > maxIndex) {
+                                    maxIndex = id;
+                                }
+                                char red =
+                                    (char)Integer.parseInt(getAttribute(entry,
+                                                                        "red"));
+                                char green =
+                                    (char)Integer.parseInt(getAttribute(entry,
+                                                                        "green"));
+                                char blue =
+                                    (char)Integer.parseInt(getAttribute(entry,
+                                                                        "blue"));
+                                palette.put(Integer.valueOf(id),
+                                            new char[] {red, green, blue});
+
+                                gotPalette = true;
+                            }
+                            entry = entry.getNextSibling();
+                        }
+
+                        if(gotPalette) {
+                            int mapSize = maxIndex + 1;
+                            int paletteLength = 3*mapSize;
+                            char[] paletteEntries = new char[paletteLength];
+                            Iterator<Map.Entry<Integer,char[]>> paletteIter
+                                = palette.entrySet().iterator();
+                            while(paletteIter.hasNext()) {
+                                Map.Entry<Integer,char[]> paletteEntry
+                                    = paletteIter.next();
+                                int index = paletteEntry.getKey();
+                                char[] rgb = paletteEntry.getValue();
+                                paletteEntries[index] =
+                                    (char)((rgb[0]*65535)/255);
+                                paletteEntries[mapSize + index] =
+                                    (char)((rgb[1]*65535)/255);
+                                paletteEntries[2*mapSize + index] =
+                                    (char)((rgb[2]*65535)/255);
+                            }
+
+                            tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP);
+                            f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
+                                              paletteLength, paletteEntries);
+                            rootIFD.addTIFFField(f);
+                        }
+                    }
+
+                    child = child.getNextSibling();
+                }
+
+                int photometricInterpretation = -1;
+                if((colorSpaceType == null || colorSpaceType.equals("GRAY")) &&
+                   blackIsZero != null &&
+                   blackIsZero.equalsIgnoreCase("FALSE")) {
+                    photometricInterpretation =
+                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+                } else if(colorSpaceType != null) {
+                    if(colorSpaceType.equals("GRAY")) {
+                        boolean isTransparency = false;
+                        if(root instanceof IIOMetadataNode) {
+                            IIOMetadataNode iioRoot = (IIOMetadataNode)root;
+                            NodeList siNodeList =
+                                iioRoot.getElementsByTagName("SubimageInterpretation");
+                            if(siNodeList.getLength() == 1) {
+                                Node siNode = siNodeList.item(0);
+                                String value = getAttribute(siNode, "value");
+                                if(value.equals("TransparencyMask")) {
+                                    isTransparency = true;
+                                }
+                            }
+                        }
+                        if(isTransparency) {
+                            photometricInterpretation =
+                                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK;
+                        } else {
+                            photometricInterpretation =
+                                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+                        }
+                    } else if(colorSpaceType.equals("RGB")) {
+                        photometricInterpretation =
+                            gotPalette ?
+                            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR :
+                            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+                    } else if(colorSpaceType.equals("YCbCr")) {
+                        photometricInterpretation =
+                            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
+                    } else if(colorSpaceType.equals("CMYK")) {
+                        photometricInterpretation =
+                            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
+                    } else if(colorSpaceType.equals("Lab")) {
+                        photometricInterpretation =
+                            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
+                    }
+                }
+
+                if(photometricInterpretation != -1) {
+                    tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+                    f = new TIFFField(tag, photometricInterpretation);
+                    rootIFD.addTIFFField(f);
+                }
+            } else if (name.equals("Compression")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("CompressionTypeName")) {
+                        int compression = -1;
+                        String compressionTypeName =
+                            getAttribute(child, "value");
+                        if(compressionTypeName.equalsIgnoreCase("None")) {
+                            compression =
+                                BaselineTIFFTagSet.COMPRESSION_NONE;
+                        } else {
+                            String[] compressionNames =
+                                TIFFImageWriter.compressionTypes;
+                            for(int i = 0; i < compressionNames.length; i++) {
+                                if(compressionNames[i].equalsIgnoreCase(compressionTypeName)) {
+                                    compression =
+                                        TIFFImageWriter.compressionNumbers[i];
+                                    break;
+                                }
+                            }
+                        }
+
+                        if(compression != -1) {
+                            tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COMPRESSION);
+                            f = new TIFFField(tag, compression);
+                            rootIFD.addTIFFField(f);
+
+                            // Lossless is irrelevant.
+                        }
+                    }
+
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Data")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+
+                    if (childName.equals("PlanarConfiguration")) {
+                        String pc = getAttribute(child, "value");
+                        int planarConfiguration = -1;
+                        if(pc.equals("PixelInterleaved")) {
+                            planarConfiguration =
+                                BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                        } else if(pc.equals("PlaneInterleaved")) {
+                            planarConfiguration =
+                                BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR;
+                        }
+                        if(planarConfiguration != -1) {
+                            tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+                            f = new TIFFField(tag, planarConfiguration);
+                            rootIFD.addTIFFField(f);
+                        }
+                    } else if (childName.equals("BitsPerSample")) {
+                        String bps = getAttribute(child, "value");
+                        char[] bitsPerSample = listToCharArray(bps);
+                        tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                        if(isPaletteColor) {
+                            f = new TIFFField(tag, TIFFTag.TIFF_SHORT, 1,
+                                              new char[] {bitsPerSample[0]});
+                        } else {
+                            f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
+                                              bitsPerSample.length,
+                                              bitsPerSample);
+                        }
+                        rootIFD.addTIFFField(f);
+                    } else if (childName.equals("SampleMSB")) {
+                        // Add FillOrder only if lsb-to-msb (right to left)
+                        // for all bands, i.e., SampleMSB is zero for all
+                        // channels.
+                        String sMSB = getAttribute(child, "value");
+                        int[] sampleMSB = listToIntArray(sMSB);
+                        boolean isRightToLeft = true;
+                        for(int i = 0; i < sampleMSB.length; i++) {
+                            if(sampleMSB[i] != 0) {
+                                isRightToLeft = false;
+                                break;
+                            }
+                        }
+                        int fillOrder = isRightToLeft ?
+                            BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT :
+                            BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT;
+                        tag =
+                            rootIFD.getTag(BaselineTIFFTagSet.TAG_FILL_ORDER);
+                        f = new TIFFField(tag, fillOrder);
+                        rootIFD.addTIFFField(f);
+                    }
+
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Dimension")) {
+                float pixelAspectRatio = -1.0f;
+                boolean gotPixelAspectRatio = false;
+
+                float horizontalPixelSize = -1.0f;
+                boolean gotHorizontalPixelSize = false;
+
+                float verticalPixelSize = -1.0f;
+                boolean gotVerticalPixelSize = false;
+
+                boolean sizeIsAbsolute = false;
+
+                float horizontalPosition = -1.0f;
+                boolean gotHorizontalPosition = false;
+
+                float verticalPosition = -1.0f;
+                boolean gotVerticalPosition = false;
+
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if (childName.equals("PixelAspectRatio")) {
+                        String par = getAttribute(child, "value");
+                        pixelAspectRatio = Float.parseFloat(par);
+                        gotPixelAspectRatio = true;
+                    } else if (childName.equals("ImageOrientation")) {
+                        String orientation = getAttribute(child, "value");
+                        for (int i = 0; i < orientationNames.length; i++) {
+                            if (orientation.equals(orientationNames[i])) {
+                                char[] oData = new char[1];
+                                oData[0] = (char)i;
+
+                                f = new TIFFField(
+                            rootIFD.getTag(BaselineTIFFTagSet.TAG_ORIENTATION),
+                            TIFFTag.TIFF_SHORT,
+                            1,
+                            oData);
+
+                                rootIFD.addTIFFField(f);
+                                break;
+                            }
+                        }
+
+                    } else if (childName.equals("HorizontalPixelSize")) {
+                        String hps = getAttribute(child, "value");
+                        horizontalPixelSize = Float.parseFloat(hps);
+                        gotHorizontalPixelSize = true;
+                    } else if (childName.equals("VerticalPixelSize")) {
+                        String vps = getAttribute(child, "value");
+                        verticalPixelSize = Float.parseFloat(vps);
+                        gotVerticalPixelSize = true;
+                    } else if (childName.equals("HorizontalPosition")) {
+                        String hp = getAttribute(child, "value");
+                        horizontalPosition = Float.parseFloat(hp);
+                        gotHorizontalPosition = true;
+                    } else if (childName.equals("VerticalPosition")) {
+                        String vp = getAttribute(child, "value");
+                        verticalPosition = Float.parseFloat(vp);
+                        gotVerticalPosition = true;
+                    }
+
+                    child = child.getNextSibling();
+                }
+
+                sizeIsAbsolute = gotHorizontalPixelSize ||
+                    gotVerticalPixelSize;
+
+                // Fill in pixel size data from aspect ratio
+                if (gotPixelAspectRatio) {
+                    if (gotHorizontalPixelSize && !gotVerticalPixelSize) {
+                        verticalPixelSize =
+                            horizontalPixelSize/pixelAspectRatio;
+                        gotVerticalPixelSize = true;
+                    } else if (gotVerticalPixelSize &&
+                               !gotHorizontalPixelSize) {
+                        horizontalPixelSize =
+                            verticalPixelSize*pixelAspectRatio;
+                        gotHorizontalPixelSize = true;
+                    } else if (!gotHorizontalPixelSize &&
+                               !gotVerticalPixelSize) {
+                        horizontalPixelSize = pixelAspectRatio;
+                        verticalPixelSize = 1.0f;
+                        gotHorizontalPixelSize = true;
+                        gotVerticalPixelSize = true;
+                    }
+                }
+
+                // Compute pixels/centimeter
+                if (gotHorizontalPixelSize) {
+                    float xResolution =
+                        (sizeIsAbsolute ? 10.0f : 1.0f)/horizontalPixelSize;
+                    long[][] hData = new long[1][2];
+                    hData[0] = new long[2];
+                    hData[0][0] = (long)(xResolution*10000.0f);
+                    hData[0][1] = (long)10000;
+
+                    f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           hData);
+                    rootIFD.addTIFFField(f);
+                }
+
+                if (gotVerticalPixelSize) {
+                    float yResolution =
+                        (sizeIsAbsolute ? 10.0f : 1.0f)/verticalPixelSize;
+                    long[][] vData = new long[1][2];
+                    vData[0] = new long[2];
+                    vData[0][0] = (long)(yResolution*10000.0f);
+                    vData[0][1] = (long)10000;
+
+                    f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           vData);
+                    rootIFD.addTIFFField(f);
+                }
+
+                // Emit ResolutionUnit tag
+                char[] res = new char[1];
+                res[0] = (char)(sizeIsAbsolute ?
+                                BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER :
+                                BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
+
+                f = new TIFFField(
+                        rootIFD.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+                        TIFFTag.TIFF_SHORT,
+                        1,
+                        res);
+                rootIFD.addTIFFField(f);
+
+                // Position
+                if(sizeIsAbsolute) {
+                    if(gotHorizontalPosition) {
+                        // Convert from millimeters to centimeters via
+                        // numerator multiplier = denominator/10.
+                        long[][] hData = new long[1][2];
+                        hData[0][0] = (long)(horizontalPosition*10000.0f);
+                        hData[0][1] = (long)100000;
+
+                        f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_X_POSITION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           hData);
+                        rootIFD.addTIFFField(f);
+                    }
+
+                    if(gotVerticalPosition) {
+                        // Convert from millimeters to centimeters via
+                        // numerator multiplier = denominator/10.
+                        long[][] vData = new long[1][2];
+                        vData[0][0] = (long)(verticalPosition*10000.0f);
+                        vData[0][1] = (long)100000;
+
+                        f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_POSITION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           vData);
+                        rootIFD.addTIFFField(f);
+                    }
+                }
+            } else if (name.equals("Document")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+
+                    if (childName.equals("SubimageInterpretation")) {
+                        String si = getAttribute(child, "value");
+                        int newSubFileType = -1;
+                        if(si.equals("TransparencyMask")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY;
+                        } else if(si.equals("ReducedResolution")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION;
+                        } else if(si.equals("SinglePage")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE;
+                        }
+                        if(newSubFileType != -1) {
+                            tag =
+                                rootIFD.getTag(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
+                            f = new TIFFField(tag, newSubFileType);
+                            rootIFD.addTIFFField(f);
+                        }
+                    }
+
+                    if (childName.equals("ImageCreationTime")) {
+                        String year = getAttribute(child, "year");
+                        String month = getAttribute(child, "month");
+                        String day = getAttribute(child, "day");
+                        String hour = getAttribute(child, "hour");
+                        String minute = getAttribute(child, "minute");
+                        String second = getAttribute(child, "second");
+
+                        StringBuffer sb = new StringBuffer();
+                        sb.append(year);
+                        sb.append(":");
+                        if(month.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(month);
+                        sb.append(":");
+                        if(day.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(day);
+                        sb.append(" ");
+                        if(hour.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(hour);
+                        sb.append(":");
+                        if(minute.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(minute);
+                        sb.append(":");
+                        if(second.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(second);
+
+                        String[] dt = new String[1];
+                        dt[0] = sb.toString();
+
+                        f = new TIFFField(
+                              rootIFD.getTag(BaselineTIFFTagSet.TAG_DATE_TIME),
+                              TIFFTag.TIFF_ASCII,
+                              1,
+                              dt);
+                        rootIFD.addTIFFField(f);
+                    }
+
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Text")) {
+                Node child = node.getFirstChild();
+                String theAuthor = null;
+                String theDescription = null;
+                String theTitle = null;
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if(childName.equals("TextEntry")) {
+                        int tagNumber = -1;
+                        NamedNodeMap childAttrs = child.getAttributes();
+                        Node keywordNode = childAttrs.getNamedItem("keyword");
+                        if(keywordNode != null) {
+                            String keyword = keywordNode.getNodeValue();
+                            String value = getAttribute(child, "value");
+                            if(!keyword.equals("") && !value.equals("")) {
+                                if(keyword.equalsIgnoreCase("DocumentName")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_DOCUMENT_NAME;
+                                } else if(keyword.equalsIgnoreCase("ImageDescription")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;
+                                } else if(keyword.equalsIgnoreCase("Make")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_MAKE;
+                                } else if(keyword.equalsIgnoreCase("Model")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_MODEL;
+                                } else if(keyword.equalsIgnoreCase("PageName")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_PAGE_NAME;
+                                } else if(keyword.equalsIgnoreCase("Software")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_SOFTWARE;
+                                } else if(keyword.equalsIgnoreCase("Artist")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_ARTIST;
+                                } else if(keyword.equalsIgnoreCase("HostComputer")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_HOST_COMPUTER;
+                                } else if(keyword.equalsIgnoreCase("InkNames")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_INK_NAMES;
+                                } else if(keyword.equalsIgnoreCase("Copyright")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_COPYRIGHT;
+                                } else if(keyword.equalsIgnoreCase("author")) {
+                                    theAuthor = value;
+                                } else if(keyword.equalsIgnoreCase("description")) {
+                                    theDescription = value;
+                                } else if(keyword.equalsIgnoreCase("title")) {
+                                    theTitle = value;
+                                }
+                                if(tagNumber != -1) {
+                                    f = new TIFFField(rootIFD.getTag(tagNumber),
+                                                      TIFFTag.TIFF_ASCII,
+                                                      1,
+                                                      new String[] {value});
+                                    rootIFD.addTIFFField(f);
+                                }
+                            }
+                        }
+                    }
+                    child = child.getNextSibling();
+                } // child != null
+                if(theAuthor != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_ARTIST) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_ARTIST),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theAuthor});
+                    rootIFD.addTIFFField(f);
+                }
+                if(theDescription != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theDescription});
+                    rootIFD.addTIFFField(f);
+                }
+                if(theTitle != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_DOCUMENT_NAME) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_DOCUMENT_NAME),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theTitle});
+                    rootIFD.addTIFFField(f);
+                }
+            } else if (name.equals("Transparency")) {
+                 Node child = node.getFirstChild();
+                 while (child != null) {
+                     String childName = child.getNodeName();
+
+                     if (childName.equals("Alpha")) {
+                         String alpha = getAttribute(child, "value");
+
+                         f = null;
+                         if (alpha.equals("premultiplied")) {
+                             f = new TIFFField(
+                          rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+                          BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA);
+                         } else if (alpha.equals("nonpremultiplied")) {
+                             f = new TIFFField(
+                          rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+                          BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA);
+                         }
+                         if (f != null) {
+                             rootIFD.addTIFFField(f);
+                         }
+                     }
+
+                    child = child.getNextSibling();
+                 }
+            }
+
+            node = node.getNextSibling();
+        }
+
+        // Set SampleFormat.
+        if(sampleFormat != null) {
+            // Derive the value.
+            int sf = -1;
+            if(sampleFormat.equals("SignedIntegral")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
+            } else if(sampleFormat.equals("UnsignedIntegral")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+            } else if(sampleFormat.equals("Real")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
+            } else if(sampleFormat.equals("Index")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+            }
+
+            if(sf != -1) {
+                // Derive the count.
+                int count = 1;
+
+                // Try SamplesPerPixel first.
+                f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+                if(f != null) {
+                    count = f.getAsInt(0);
+                } else {
+                    // Try BitsPerSample.
+                    f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                    if(f != null) {
+                        count = f.getCount();
+                    }
+                }
+
+                char[] sampleFormatArray = new char[count];
+                Arrays.fill(sampleFormatArray, (char)sf);
+
+                // Add SampleFormat.
+                tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+                f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
+                                  sampleFormatArray.length, sampleFormatArray);
+                rootIFD.addTIFFField(f);
+            }
+        }
+    }
+
+    private static String getAttribute(Node node, String attrName) {
+        NamedNodeMap attrs = node.getAttributes();
+        Node attr = attrs.getNamedItem(attrName);
+        return attr != null ? attr.getNodeValue() : null;
+    }
+
+    private Node getChildNode(Node node, String childName) {
+        Node childNode = null;
+        if(node.hasChildNodes()) {
+            NodeList childNodes = node.getChildNodes();
+            int length = childNodes.getLength();
+            for(int i = 0; i < length; i++) {
+                Node item = childNodes.item(i);
+                if(item.getNodeName().equals(childName)) {
+                    childNode = item;
+                    break;
+                }
+            }
+        }
+        return childNode;
+    }
+
+    public static TIFFIFD parseIFD(Node node) throws IIOInvalidTreeException {
+        if (!node.getNodeName().equals("TIFFIFD")) {
+            fatal(node, "Expected \"TIFFIFD\" node");
+        }
+
+        String tagSetNames = getAttribute(node, "tagSets");
+        List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(5);
+
+        if (tagSetNames != null) {
+            StringTokenizer st = new StringTokenizer(tagSetNames, ",");
+            while (st.hasMoreTokens()) {
+                String className = st.nextToken();
+
+                Object o = null;
+                try {
+                    Class<?> setClass = Class.forName(className);
+                    Method getInstanceMethod =
+                        setClass.getMethod("getInstance", (Class[])null);
+                    o = getInstanceMethod.invoke(null, (Object[])null);
+                } catch (NoSuchMethodException e) {
+                    throw new RuntimeException(e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException(e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e);
+                } catch (ClassNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+
+                if (!(o instanceof TIFFTagSet)) {
+                    fatal(node, "Specified tag set class \"" +
+                          className +
+                          "\" is not an instance of TIFFTagSet");
+                } else {
+                    tagSets.add((TIFFTagSet)o);
+                }
+            }
+        }
+
+        TIFFIFD ifd = new TIFFIFD(tagSets);
+
+        node = node.getFirstChild();
+        while (node != null) {
+            String name = node.getNodeName();
+
+            TIFFField f = null;
+            if (name.equals("TIFFIFD")) {
+                TIFFIFD subIFD = parseIFD(node);
+                String parentTagName = getAttribute(node, "parentTagName");
+                String parentTagNumber = getAttribute(node, "parentTagNumber");
+                TIFFTag tag = null;
+                if(parentTagName != null) {
+                    tag = TIFFIFD.getTag(parentTagName, tagSets);
+                } else if(parentTagNumber != null) {
+                    int tagNumber = Integer.parseUnsignedInt(parentTagNumber);
+                    tag = TIFFIFD.getTag(tagNumber, tagSets);
+                }
+
+                int type;
+                if (tag == null) {
+                    type = TIFFTag.TIFF_LONG;
+                    tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, 0, 1 << type);
+                } else {
+                    if (tag.isDataTypeOK(TIFFTag.TIFF_IFD_POINTER)) {
+                        type = TIFFTag.TIFF_IFD_POINTER;
+                    } else if (tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
+                        type = TIFFTag.TIFF_LONG;
+                    } else {
+                        for (type = TIFFTag.MAX_DATATYPE;
+                            type >= TIFFTag.MIN_DATATYPE;
+                            type--) {
+                            if (tag.isDataTypeOK(type)) {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                f = new TIFFField(tag, type, 1L, subIFD);
+            } else if (name.equals("TIFFField")) {
+                int number = Integer.parseInt(getAttribute(node, "number"));
+
+                TIFFTagSet tagSet = null;
+                Iterator<TIFFTagSet> iter = tagSets.iterator();
+                while (iter.hasNext()) {
+                    TIFFTagSet t = iter.next();
+                    if (t.getTag(number) != null) {
+                        tagSet = t;
+                        break;
+                    }
+                }
+
+                f = TIFFField.createFromMetadataNode(tagSet, node);
+            } else {
+                fatal(node,
+                      "Expected either \"TIFFIFD\" or \"TIFFField\" node, got "
+                      + name);
+            }
+
+            ifd.addTIFFField(f);
+            node = node.getNextSibling();
+        }
+
+        return ifd;
+    }
+
+    private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
+        Node node = root;
+        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
+            fatal(node, "Root must be " + nativeMetadataFormatName);
+        }
+
+        node = node.getFirstChild();
+        if (node == null || !node.getNodeName().equals("TIFFIFD")) {
+            fatal(root, "Root must have \"TIFFIFD\" child");
+        }
+        TIFFIFD ifd = parseIFD(node);
+
+        List<TIFFTagSet> rootIFDTagSets = rootIFD.getTagSetList();
+        Iterator<TIFFTagSet> tagSetIter = ifd.getTagSetList().iterator();
+        while(tagSetIter.hasNext()) {
+            Object o = tagSetIter.next();
+            if(o instanceof TIFFTagSet && !rootIFDTagSets.contains(o)) {
+                rootIFD.addTagSet((TIFFTagSet)o);
+            }
+        }
+
+        Iterator<TIFFField> ifdIter = ifd.iterator();
+        while(ifdIter.hasNext()) {
+            TIFFField field = ifdIter.next();
+            rootIFD.addTIFFField(field);
+        }
+    }
+
+    public void mergeTree(String formatName, Node root)
+        throws IIOInvalidTreeException{
+        if (formatName.equals(nativeMetadataFormatName)) {
+            if (root == null) {
+                throw new NullPointerException("root == null!");
+            }
+            mergeNativeTree(root);
+        } else if (formatName.equals
+                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            if (root == null) {
+                throw new NullPointerException("root == null!");
+            }
+            mergeStandardTree(root);
+        } else {
+            throw new IllegalArgumentException("Not a recognized format!");
+        }
+    }
+
+    public void reset() {
+        rootIFD = new TIFFIFD(tagSets);
+    }
+
+    public TIFFIFD getRootIFD() {
+        return rootIFD;
+    }
+
+    public TIFFField getTIFFField(int tagNumber) {
+        return rootIFD.getTIFFField(tagNumber);
+    }
+
+    public void removeTIFFField(int tagNumber) {
+        rootIFD.removeTIFFField(tagNumber);
+    }
+
+    /**
+     * Returns a <code>TIFFImageMetadata</code> wherein all fields in the
+     * root IFD from the <code>BaselineTIFFTagSet</code> are copied by value
+     * and all other fields copied by reference.
+     */
+    public TIFFImageMetadata getShallowClone() {
+        return new TIFFImageMetadata(rootIFD.getShallowClone());
+    }
+}