1 /*
   2  * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.imageio.plugins.tiff;
  26 
  27 import java.io.IOException;
  28 import java.lang.reflect.InvocationTargetException;
  29 import java.lang.reflect.Method;
  30 import java.util.ArrayList;
  31 import java.util.Arrays;
  32 import java.util.HashMap;
  33 import java.util.Iterator;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.StringTokenizer;
  37 import javax.imageio.metadata.IIOMetadata;
  38 import javax.imageio.metadata.IIOInvalidTreeException;
  39 import javax.imageio.metadata.IIOMetadataFormatImpl;
  40 import javax.imageio.metadata.IIOMetadataNode;
  41 import javax.imageio.stream.ImageInputStream;
  42 import org.w3c.dom.NamedNodeMap;
  43 import org.w3c.dom.Node;
  44 import org.w3c.dom.NodeList;
  45 import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
  46 import javax.imageio.plugins.tiff.ExifParentTIFFTagSet;
  47 import javax.imageio.plugins.tiff.TIFFField;
  48 import javax.imageio.plugins.tiff.TIFFTag;
  49 import javax.imageio.plugins.tiff.TIFFTagSet;
  50 
  51 public class TIFFImageMetadata extends IIOMetadata {
  52 
  53     // package scope
  54 
  55     public static final String NATIVE_METADATA_FORMAT_NAME =
  56         "javax_imageio_tiff_image_1.0";
  57 
  58     public static final String NATIVE_METADATA_FORMAT_CLASS_NAME =
  59         "javax.imageio.plugins.tiff.TIFFImageMetadataFormat";
  60 
  61     private List<TIFFTagSet> tagSets;
  62 
  63     TIFFIFD rootIFD;
  64 
  65     public TIFFImageMetadata(List<TIFFTagSet> tagSets) {
  66         super(true,
  67               NATIVE_METADATA_FORMAT_NAME,
  68               NATIVE_METADATA_FORMAT_CLASS_NAME,
  69               null, null);
  70 
  71         this.tagSets = tagSets;
  72         this.rootIFD = new TIFFIFD(tagSets);
  73     }
  74 
  75     public TIFFImageMetadata(TIFFIFD ifd) {
  76         super(true,
  77               NATIVE_METADATA_FORMAT_NAME,
  78               NATIVE_METADATA_FORMAT_CLASS_NAME,
  79               null, null);
  80         this.tagSets = ifd.getTagSetList();
  81         this.rootIFD = ifd;
  82     }
  83 
  84     public void initializeFromStream(ImageInputStream stream,
  85                                      boolean ignoreUnknownFields)
  86         throws IOException {
  87         rootIFD.initialize(stream, true, ignoreUnknownFields);
  88     }
  89 
  90     public void addShortOrLongField(int tagNumber, int value) {
  91         TIFFField field = new TIFFField(rootIFD.getTag(tagNumber), value);
  92         rootIFD.addTIFFField(field);
  93     }
  94 
  95     public boolean isReadOnly() {
  96         return false;
  97     }
  98 
  99     private Node getIFDAsTree(TIFFIFD ifd,
 100                               String parentTagName, int parentTagNumber) {
 101         IIOMetadataNode IFDRoot = new IIOMetadataNode("TIFFIFD");
 102         if (parentTagNumber != 0) {
 103             IFDRoot.setAttribute("parentTagNumber",
 104                                  Integer.toString(parentTagNumber));
 105         }
 106         if (parentTagName != null) {
 107             IFDRoot.setAttribute("parentTagName", parentTagName);
 108         }
 109 
 110         List<TIFFTagSet> tagSets = ifd.getTagSetList();
 111         if (tagSets.size() > 0) {
 112             Iterator<TIFFTagSet> iter = tagSets.iterator();
 113             StringBuilder tagSetNames = new StringBuilder();
 114             while (iter.hasNext()) {
 115                 TIFFTagSet tagSet = iter.next();
 116                 tagSetNames.append(tagSet.getClass().getName());
 117                 if (iter.hasNext()) {
 118                     tagSetNames.append(",");
 119                 }
 120             }
 121 
 122             IFDRoot.setAttribute("tagSets", tagSetNames.toString());
 123         }
 124 
 125         Iterator<TIFFField> iter = ifd.iterator();
 126         while (iter.hasNext()) {
 127             TIFFField f = iter.next();
 128             int tagNumber = f.getTagNumber();
 129             TIFFTag tag = TIFFIFD.getTag(tagNumber, tagSets);
 130 
 131             Node node = null;
 132             if (tag == null) {
 133                 node = f.getAsNativeNode();
 134             } else if (tag.isIFDPointer() && f.hasDirectory()) {
 135                 TIFFIFD subIFD = TIFFIFD.getDirectoryAsIFD(f.getDirectory());
 136 
 137                 // Recurse
 138                 node = getIFDAsTree(subIFD, tag.getName(), tag.getNumber());
 139             } else {
 140                 node = f.getAsNativeNode();
 141             }
 142 
 143             if (node != null) {
 144                 IFDRoot.appendChild(node);
 145             }
 146         }
 147 
 148         return IFDRoot;
 149     }
 150 
 151     public Node getAsTree(String formatName) {
 152         if (formatName.equals(nativeMetadataFormatName)) {
 153             return getNativeTree();
 154         } else if (formatName.equals
 155                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
 156             return getStandardTree();
 157         } else {
 158             throw new IllegalArgumentException("Not a recognized format!");
 159         }
 160     }
 161 
 162     private Node getNativeTree() {
 163         IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
 164 
 165         Node IFDNode = getIFDAsTree(rootIFD, null, 0);
 166         root.appendChild(IFDNode);
 167 
 168         return root;
 169     }
 170 
 171     private static final String[] colorSpaceNames = {
 172         "GRAY", // WhiteIsZero
 173         "GRAY", // BlackIsZero
 174         "RGB", // RGB
 175         "RGB", // PaletteColor
 176         "GRAY", // TransparencyMask
 177         "CMYK", // CMYK
 178         "YCbCr", // YCbCr
 179         "Lab", // CIELab
 180         "Lab", // ICCLab
 181     };
 182 
 183     public IIOMetadataNode getStandardChromaNode() {
 184         IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
 185         IIOMetadataNode node = null; // scratch node
 186 
 187         TIFFField f;
 188 
 189         // Set the PhotometricInterpretation and the palette color flag.
 190         int photometricInterpretation = -1;
 191         boolean isPaletteColor = false;
 192         f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
 193         if (f != null) {
 194             photometricInterpretation = f.getAsInt(0);
 195 
 196             isPaletteColor =
 197                 photometricInterpretation ==
 198                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
 199         }
 200 
 201         // Determine the number of channels.
 202         int numChannels = -1;
 203         if(isPaletteColor) {
 204             numChannels = 3;
 205         } else {
 206             f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
 207             if (f != null) {
 208                 numChannels = f.getAsInt(0);
 209             } else { // f == null
 210                 f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
 211                 if(f != null) {
 212                     numChannels = f.getCount();
 213                 }
 214             }
 215         }
 216 
 217         if(photometricInterpretation != -1) {
 218             if (photometricInterpretation >= 0 &&
 219                 photometricInterpretation < colorSpaceNames.length) {
 220                 node = new IIOMetadataNode("ColorSpaceType");
 221                 String csName;
 222                 if(photometricInterpretation ==
 223                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK &&
 224                    numChannels == 3) {
 225                     csName = "CMY";
 226                 } else {
 227                     csName = colorSpaceNames[photometricInterpretation];
 228                 }
 229                 node.setAttribute("name", csName);
 230                 chroma_node.appendChild(node);
 231             }
 232 
 233             node = new IIOMetadataNode("BlackIsZero");
 234             node.setAttribute("value",
 235                               (photometricInterpretation ==
 236                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO)
 237                               ? "FALSE" : "TRUE");
 238             chroma_node.appendChild(node);
 239         }
 240 
 241         if(numChannels != -1) {
 242             node = new IIOMetadataNode("NumChannels");
 243             node.setAttribute("value", Integer.toString(numChannels));
 244             chroma_node.appendChild(node);
 245         }
 246 
 247         f = getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
 248         if (f != null) {
 249             // NOTE: The presence of hasAlpha is vestigial: there is
 250             // no way in TIFF to represent an alpha component in a palette
 251             // color image. See bug 5086341.
 252             boolean hasAlpha = false;
 253 
 254             node = new IIOMetadataNode("Palette");
 255             int len = f.getCount()/(hasAlpha ? 4 : 3);
 256             for (int i = 0; i < len; i++) {
 257                 IIOMetadataNode entry =
 258                     new IIOMetadataNode("PaletteEntry");
 259                 entry.setAttribute("index", Integer.toString(i));
 260 
 261                 int r = (f.getAsInt(i)*255)/65535;
 262                 int g = (f.getAsInt(len + i)*255)/65535;
 263                 int b = (f.getAsInt(2*len + i)*255)/65535;
 264 
 265                 entry.setAttribute("red", Integer.toString(r));
 266                 entry.setAttribute("green", Integer.toString(g));
 267                 entry.setAttribute("blue", Integer.toString(b));
 268                 if (hasAlpha) {
 269                     int alpha = 0;
 270                     entry.setAttribute("alpha", Integer.toString(alpha));
 271                 }
 272                 node.appendChild(entry);
 273             }
 274             chroma_node.appendChild(node);
 275         }
 276 
 277         return chroma_node;
 278     }
 279 
 280     public IIOMetadataNode getStandardCompressionNode() {
 281         IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
 282         IIOMetadataNode node = null; // scratch node
 283 
 284         TIFFField f;
 285 
 286         f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
 287         if (f != null) {
 288             String compressionTypeName = null;
 289             int compression = f.getAsInt(0);
 290             boolean isLossless = true; // obligate initialization.
 291             if(compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
 292                 compressionTypeName = "None";
 293                 isLossless = true;
 294             } else {
 295                 int[] compressionNumbers = TIFFImageWriter.compressionNumbers;
 296                 for(int i = 0; i < compressionNumbers.length; i++) {
 297                     if(compression == compressionNumbers[i]) {
 298                         compressionTypeName =
 299                             TIFFImageWriter.compressionTypes[i];
 300                         isLossless =
 301                             TIFFImageWriter.isCompressionLossless[i];
 302                         break;
 303                     }
 304                 }
 305             }
 306 
 307             if (compressionTypeName != null) {
 308                 node = new IIOMetadataNode("CompressionTypeName");
 309                 node.setAttribute("value", compressionTypeName);
 310                 compression_node.appendChild(node);
 311 
 312                 node = new IIOMetadataNode("Lossless");
 313                 node.setAttribute("value", isLossless ? "TRUE" : "FALSE");
 314                 compression_node.appendChild(node);
 315             }
 316         }
 317 
 318         node = new IIOMetadataNode("NumProgressiveScans");
 319         node.setAttribute("value", "1");
 320         compression_node.appendChild(node);
 321 
 322         return compression_node;
 323     }
 324 
 325     private String repeat(String s, int times) {
 326         if (times == 1) {
 327             return s;
 328         }
 329         StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
 330         sb.append(s);
 331         for (int i = 1; i < times; i++) {
 332             sb.append(" ");
 333             sb.append(s);
 334         }
 335         return sb.toString();
 336     }
 337 
 338     public IIOMetadataNode getStandardDataNode() {
 339         IIOMetadataNode data_node = new IIOMetadataNode("Data");
 340         IIOMetadataNode node = null; // scratch node
 341 
 342         TIFFField f;
 343 
 344         boolean isPaletteColor = false;
 345         f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
 346         if (f != null) {
 347             isPaletteColor =
 348                 f.getAsInt(0) ==
 349                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
 350         }
 351 
 352         f = getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
 353         String planarConfiguration = "PixelInterleaved";
 354         if (f != null &&
 355             f.getAsInt(0) == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
 356             planarConfiguration = "PlaneInterleaved";
 357         }
 358 
 359         node = new IIOMetadataNode("PlanarConfiguration");
 360         node.setAttribute("value", planarConfiguration);
 361         data_node.appendChild(node);
 362 
 363         f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
 364         if (f != null) {
 365             int photometricInterpretation = f.getAsInt(0);
 366             String sampleFormat = "UnsignedIntegral";
 367 
 368             if (photometricInterpretation ==
 369                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
 370                 sampleFormat = "Index";
 371             } else {
 372                 f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
 373                 if (f != null) {
 374                     int format = f.getAsInt(0);
 375                     if (format ==
 376                         BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
 377                         sampleFormat = "SignedIntegral";
 378                     } else if (format ==
 379                         BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER) {
 380                         sampleFormat = "UnsignedIntegral";
 381                     } else if (format ==
 382                                BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
 383                         sampleFormat = "Real";
 384                     } else {
 385                         sampleFormat = null; // don't know
 386                     }
 387                 }
 388             }
 389             if (sampleFormat != null) {
 390                 node = new IIOMetadataNode("SampleFormat");
 391                 node.setAttribute("value", sampleFormat);
 392                 data_node.appendChild(node);
 393             }
 394         }
 395 
 396         f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
 397         int[] bitsPerSample = null;
 398         if(f != null) {
 399             bitsPerSample = f.getAsInts();
 400         } else {
 401             f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
 402             int compression = f != null ?
 403                 f.getAsInt(0) : BaselineTIFFTagSet.COMPRESSION_NONE;
 404             if(getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER) !=
 405                null ||
 406                compression == BaselineTIFFTagSet.COMPRESSION_JPEG ||
 407                compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG ||
 408                getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) !=
 409                null) {
 410                 f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
 411                 if(f != null &&
 412                    (f.getAsInt(0) ==
 413                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO ||
 414                     f.getAsInt(0) ==
 415                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) {
 416                     bitsPerSample = new int[] {8};
 417                 } else {
 418                     bitsPerSample = new int[] {8, 8, 8};
 419                 }
 420             } else {
 421                 bitsPerSample = new int[] {1};
 422             }
 423         }
 424         StringBuffer sb = new StringBuffer();
 425         for (int i = 0; i < bitsPerSample.length; i++) {
 426             if (i > 0) {
 427                 sb.append(" ");
 428             }
 429             sb.append(Integer.toString(bitsPerSample[i]));
 430         }
 431         node = new IIOMetadataNode("BitsPerSample");
 432         if(isPaletteColor) {
 433             node.setAttribute("value", repeat(sb.toString(), 3));
 434         } else {
 435             node.setAttribute("value", sb.toString());
 436         }
 437         data_node.appendChild(node);
 438 
 439             // SampleMSB
 440         f = getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
 441         int fillOrder = f != null ?
 442             f.getAsInt(0) : BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT;
 443         sb = new StringBuffer();
 444         for (int i = 0; i < bitsPerSample.length; i++) {
 445             if (i > 0) {
 446                 sb.append(" ");
 447             }
 448             int maxBitIndex = bitsPerSample[i] == 1 ?
 449                 7 : bitsPerSample[i] - 1;
 450             int msb =
 451                 fillOrder == BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT ?
 452                 maxBitIndex : 0;
 453             sb.append(Integer.toString(msb));
 454         }
 455         node = new IIOMetadataNode("SampleMSB");
 456         if(isPaletteColor) {
 457             node.setAttribute("value", repeat(sb.toString(), 3));
 458         } else {
 459             node.setAttribute("value", sb.toString());
 460         }
 461         data_node.appendChild(node);
 462 
 463         return data_node;
 464     }
 465 
 466     private static final String[] orientationNames = {
 467         null,
 468         "Normal",
 469         "FlipH",
 470         "Rotate180",
 471         "FlipV",
 472         "FlipHRotate90",
 473         "Rotate270",
 474         "FlipVRotate90",
 475         "Rotate90",
 476     };
 477 
 478     public IIOMetadataNode getStandardDimensionNode() {
 479         IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
 480         IIOMetadataNode node = null; // scratch node
 481 
 482         TIFFField f;
 483 
 484         long[] xres = null;
 485         long[] yres = null;
 486 
 487         f = getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
 488         if (f != null) {
 489             xres = f.getAsRational(0).clone();
 490         }
 491 
 492         f = getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
 493         if (f != null) {
 494             yres = f.getAsRational(0).clone();
 495         }
 496 
 497         if (xres != null && yres != null) {
 498             node = new IIOMetadataNode("PixelAspectRatio");
 499 
 500             // Compute (1/xres)/(1/yres)
 501             // (xres_denom/xres_num)/(yres_denom/yres_num) =
 502             // (xres_denom/xres_num)*(yres_num/yres_denom) =
 503             // (xres_denom*yres_num)/(xres_num*yres_denom)
 504             float ratio = (float)((double)xres[1]*yres[0])/(xres[0]*yres[1]);
 505             node.setAttribute("value", Float.toString(ratio));
 506             dimension_node.appendChild(node);
 507         }
 508 
 509         if (xres != null || yres != null) {
 510             // Get unit field.
 511             f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
 512 
 513             // Set resolution unit.
 514             int resolutionUnit = f != null ?
 515                 f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
 516 
 517             // Have size if either centimeters or inches.
 518             boolean gotPixelSize =
 519                 resolutionUnit != BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
 520 
 521             // Convert pixels/inch to pixels/centimeter.
 522             if (resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
 523                 // Divide xres by 2.54
 524                 if (xres != null) {
 525                     xres[0] *= 100;
 526                     xres[1] *= 254;
 527                 }
 528 
 529                 // Divide yres by 2.54
 530                 if (yres != null) {
 531                     yres[0] *= 100;
 532                     yres[1] *= 254;
 533                 }
 534             }
 535 
 536             if (gotPixelSize) {
 537                 if (xres != null) {
 538                     float horizontalPixelSize = (float)(10.0*xres[1]/xres[0]);
 539                     node = new IIOMetadataNode("HorizontalPixelSize");
 540                     node.setAttribute("value",
 541                                       Float.toString(horizontalPixelSize));
 542                     dimension_node.appendChild(node);
 543                 }
 544 
 545                 if (yres != null) {
 546                     float verticalPixelSize = (float)(10.0*yres[1]/yres[0]);
 547                     node = new IIOMetadataNode("VerticalPixelSize");
 548                     node.setAttribute("value",
 549                                       Float.toString(verticalPixelSize));
 550                     dimension_node.appendChild(node);
 551                 }
 552             }
 553         }
 554 
 555         f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
 556         int resolutionUnit = f != null ?
 557             f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
 558         if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH ||
 559            resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER) {
 560             f = getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION);
 561             if(f != null) {
 562                 long[] xpos = f.getAsRational(0);
 563                 float xPosition = (float)xpos[0]/(float)xpos[1];
 564                 // Convert to millimeters.
 565                 if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
 566                     xPosition *= 254F;
 567                 } else {
 568                     xPosition *= 10F;
 569                 }
 570                 node = new IIOMetadataNode("HorizontalPosition");
 571                 node.setAttribute("value",
 572                                   Float.toString(xPosition));
 573                 dimension_node.appendChild(node);
 574             }
 575 
 576             f = getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION);
 577             if(f != null) {
 578                 long[] ypos = f.getAsRational(0);
 579                 float yPosition = (float)ypos[0]/(float)ypos[1];
 580                 // Convert to millimeters.
 581                 if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) {
 582                     yPosition *= 254F;
 583                 } else {
 584                     yPosition *= 10F;
 585                 }
 586                 node = new IIOMetadataNode("VerticalPosition");
 587                 node.setAttribute("value",
 588                                   Float.toString(yPosition));
 589                 dimension_node.appendChild(node);
 590             }
 591         }
 592 
 593         f = getTIFFField(BaselineTIFFTagSet.TAG_ORIENTATION);
 594         if (f != null) {
 595             int o = f.getAsInt(0);
 596             if (o >= 0 && o < orientationNames.length) {
 597                 node = new IIOMetadataNode("ImageOrientation");
 598                 node.setAttribute("value", orientationNames[o]);
 599                 dimension_node.appendChild(node);
 600             }
 601         }
 602 
 603         return dimension_node;
 604     }
 605 
 606     public IIOMetadataNode getStandardDocumentNode() {
 607         IIOMetadataNode document_node = new IIOMetadataNode("Document");
 608         IIOMetadataNode node = null; // scratch node
 609 
 610         TIFFField f;
 611 
 612         node = new IIOMetadataNode("FormatVersion");
 613         node.setAttribute("value", "6.0");
 614         document_node.appendChild(node);
 615 
 616         f = getTIFFField(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
 617         if(f != null) {
 618             int newSubFileType = f.getAsInt(0);
 619             String value = null;
 620             if((newSubFileType &
 621                 BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY) != 0) {
 622                 value = "TransparencyMask";
 623             } else if((newSubFileType &
 624                        BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) != 0) {
 625                 value = "ReducedResolution";
 626             } else if((newSubFileType &
 627                        BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE) != 0) {
 628                 value = "SinglePage";
 629             }
 630             if(value != null) {
 631                 node = new IIOMetadataNode("SubimageInterpretation");
 632                 node.setAttribute("value", value);
 633                 document_node.appendChild(node);
 634             }
 635         }
 636 
 637         f = getTIFFField(BaselineTIFFTagSet.TAG_DATE_TIME);
 638         if (f != null) {
 639             String s = f.getAsString(0);
 640 
 641             // DateTime should be formatted as "YYYY:MM:DD hh:mm:ss".
 642             if(s.length() == 19) {
 643                 node = new IIOMetadataNode("ImageCreationTime");
 644 
 645                 // Files with incorrect DateTime format have been
 646                 // observed so anticipate an exception from substring()
 647                 // and only add the node if the format is presumably
 648                 // correct.
 649                 boolean appendNode;
 650                 try {
 651                     node.setAttribute("year", s.substring(0, 4));
 652                     node.setAttribute("month", s.substring(5, 7));
 653                     node.setAttribute("day", s.substring(8, 10));
 654                     node.setAttribute("hour", s.substring(11, 13));
 655                     node.setAttribute("minute", s.substring(14, 16));
 656                     node.setAttribute("second", s.substring(17, 19));
 657                     appendNode = true;
 658                 } catch(IndexOutOfBoundsException e) {
 659                     appendNode = false;
 660                 }
 661 
 662                 if(appendNode) {
 663                     document_node.appendChild(node);
 664                 }
 665             }
 666         }
 667 
 668         return document_node;
 669     }
 670 
 671     public IIOMetadataNode getStandardTextNode() {
 672         IIOMetadataNode text_node = null;
 673         IIOMetadataNode node = null; // scratch node
 674 
 675         TIFFField f;
 676 
 677         int[] textFieldTagNumbers = new int[] {
 678             BaselineTIFFTagSet.TAG_DOCUMENT_NAME,
 679             BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION,
 680             BaselineTIFFTagSet.TAG_MAKE,
 681             BaselineTIFFTagSet.TAG_MODEL,
 682             BaselineTIFFTagSet.TAG_PAGE_NAME,
 683             BaselineTIFFTagSet.TAG_SOFTWARE,
 684             BaselineTIFFTagSet.TAG_ARTIST,
 685             BaselineTIFFTagSet.TAG_HOST_COMPUTER,
 686             BaselineTIFFTagSet.TAG_INK_NAMES,
 687             BaselineTIFFTagSet.TAG_COPYRIGHT
 688         };
 689 
 690         for(int i = 0; i < textFieldTagNumbers.length; i++) {
 691             f = getTIFFField(textFieldTagNumbers[i]);
 692             if(f != null) {
 693                 String value = f.getAsString(0);
 694                 if(text_node == null) {
 695                     text_node = new IIOMetadataNode("Text");
 696                 }
 697                 node = new IIOMetadataNode("TextEntry");
 698                 node.setAttribute("keyword", f.getTag().getName());
 699                 node.setAttribute("value", value);
 700                 text_node.appendChild(node);
 701             }
 702         }
 703 
 704         return text_node;
 705     }
 706 
 707     public IIOMetadataNode getStandardTransparencyNode() {
 708         IIOMetadataNode transparency_node =
 709             new IIOMetadataNode("Transparency");
 710         IIOMetadataNode node = null; // scratch node
 711 
 712         TIFFField f;
 713 
 714         node = new IIOMetadataNode("Alpha");
 715         String value = "none";
 716 
 717         f = getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
 718         if(f != null) {
 719             int[] extraSamples = f.getAsInts();
 720             for(int i = 0; i < extraSamples.length; i++) {
 721                 if(extraSamples[i] ==
 722                    BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
 723                     value = "premultiplied";
 724                     break;
 725                 } else if(extraSamples[i] ==
 726                           BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA) {
 727                     value = "nonpremultiplied";
 728                     break;
 729                 }
 730             }
 731         }
 732 
 733         node.setAttribute("value", value);
 734         transparency_node.appendChild(node);
 735 
 736         return transparency_node;
 737     }
 738 
 739     // Shorthand for throwing an IIOInvalidTreeException
 740     private static void fatal(Node node, String reason)
 741         throws IIOInvalidTreeException {
 742         throw new IIOInvalidTreeException(reason, node);
 743     }
 744 
 745     private int[] listToIntArray(String list) {
 746         StringTokenizer st = new StringTokenizer(list, " ");
 747         ArrayList<Integer> intList = new ArrayList<Integer>();
 748         while (st.hasMoreTokens()) {
 749             String nextInteger = st.nextToken();
 750             Integer nextInt = Integer.valueOf(nextInteger);
 751             intList.add(nextInt);
 752         }
 753 
 754         int[] intArray = new int[intList.size()];
 755         for(int i = 0; i < intArray.length; i++) {
 756             intArray[i] = intList.get(i);
 757         }
 758 
 759         return intArray;
 760     }
 761 
 762     private char[] listToCharArray(String list) {
 763         StringTokenizer st = new StringTokenizer(list, " ");
 764         ArrayList<Integer> intList = new ArrayList<Integer>();
 765         while (st.hasMoreTokens()) {
 766             String nextInteger = st.nextToken();
 767             Integer nextInt = Integer.valueOf(nextInteger);
 768             intList.add(nextInt);
 769         }
 770 
 771         char[] charArray = new char[intList.size()];
 772         for(int i = 0; i < charArray.length; i++) {
 773             charArray[i] = (char)(intList.get(i).intValue());
 774         }
 775 
 776         return charArray;
 777     }
 778 
 779     private void mergeStandardTree(Node root)
 780         throws IIOInvalidTreeException {
 781         TIFFField f;
 782         TIFFTag tag;
 783 
 784         Node node = root;
 785         if (!node.getNodeName()
 786             .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
 787             fatal(node, "Root must be " +
 788                   IIOMetadataFormatImpl.standardMetadataFormatName);
 789         }
 790 
 791         // Obtain the sample format and set the palette flag if appropriate.
 792         String sampleFormat = null;
 793         Node dataNode = getChildNode(root, "Data");
 794         boolean isPaletteColor = false;
 795         if(dataNode != null) {
 796             Node sampleFormatNode = getChildNode(dataNode, "SampleFormat");
 797             if(sampleFormatNode != null) {
 798                 sampleFormat = getAttribute(sampleFormatNode, "value");
 799                 isPaletteColor = sampleFormat.equals("Index");
 800             }
 801         }
 802 
 803         // If palette flag not set check for palette.
 804         if(!isPaletteColor) {
 805             Node chromaNode = getChildNode(root, "Chroma");
 806             if(chromaNode != null &&
 807                getChildNode(chromaNode, "Palette") != null) {
 808                 isPaletteColor = true;
 809             }
 810         }
 811 
 812         node = node.getFirstChild();
 813         while (node != null) {
 814             String name = node.getNodeName();
 815 
 816             if (name.equals("Chroma")) {
 817                 String colorSpaceType = null;
 818                 String blackIsZero = null;
 819                 boolean gotPalette = false;
 820                 Node child = node.getFirstChild();
 821                 while (child != null) {
 822                     String childName = child.getNodeName();
 823                     if (childName.equals("ColorSpaceType")) {
 824                         colorSpaceType = getAttribute(child, "name");
 825                     } else if (childName.equals("NumChannels")) {
 826                         tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
 827                         int samplesPerPixel = isPaletteColor ?
 828                             1 : Integer.parseInt(getAttribute(child, "value"));
 829                         f = new TIFFField(tag, samplesPerPixel);
 830                         rootIFD.addTIFFField(f);
 831                     } else if (childName.equals("BlackIsZero")) {
 832                         blackIsZero = getAttribute(child, "value");
 833                     } else if (childName.equals("Palette")) {
 834                         Node entry = child.getFirstChild();
 835                         HashMap<Integer,char[]> palette = new HashMap<>();
 836                         int maxIndex = -1;
 837                         while(entry != null) {
 838                             String entryName = entry.getNodeName();
 839                             if(entryName.equals("PaletteEntry")) {
 840                                 String idx = getAttribute(entry, "index");
 841                                 int id = Integer.parseInt(idx);
 842                                 if(id > maxIndex) {
 843                                     maxIndex = id;
 844                                 }
 845                                 char red =
 846                                     (char)Integer.parseInt(getAttribute(entry,
 847                                                                         "red"));
 848                                 char green =
 849                                     (char)Integer.parseInt(getAttribute(entry,
 850                                                                         "green"));
 851                                 char blue =
 852                                     (char)Integer.parseInt(getAttribute(entry,
 853                                                                         "blue"));
 854                                 palette.put(Integer.valueOf(id),
 855                                             new char[] {red, green, blue});
 856 
 857                                 gotPalette = true;
 858                             }
 859                             entry = entry.getNextSibling();
 860                         }
 861 
 862                         if(gotPalette) {
 863                             int mapSize = maxIndex + 1;
 864                             int paletteLength = 3*mapSize;
 865                             char[] paletteEntries = new char[paletteLength];
 866                             Iterator<Map.Entry<Integer,char[]>> paletteIter
 867                                 = palette.entrySet().iterator();
 868                             while(paletteIter.hasNext()) {
 869                                 Map.Entry<Integer,char[]> paletteEntry
 870                                     = paletteIter.next();
 871                                 int index = paletteEntry.getKey();
 872                                 char[] rgb = paletteEntry.getValue();
 873                                 paletteEntries[index] =
 874                                     (char)((rgb[0]*65535)/255);
 875                                 paletteEntries[mapSize + index] =
 876                                     (char)((rgb[1]*65535)/255);
 877                                 paletteEntries[2*mapSize + index] =
 878                                     (char)((rgb[2]*65535)/255);
 879                             }
 880 
 881                             tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP);
 882                             f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
 883                                               paletteLength, paletteEntries);
 884                             rootIFD.addTIFFField(f);
 885                         }
 886                     }
 887 
 888                     child = child.getNextSibling();
 889                 }
 890 
 891                 int photometricInterpretation = -1;
 892                 if((colorSpaceType == null || colorSpaceType.equals("GRAY")) &&
 893                    blackIsZero != null &&
 894                    blackIsZero.equalsIgnoreCase("FALSE")) {
 895                     photometricInterpretation =
 896                         BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
 897                 } else if(colorSpaceType != null) {
 898                     if(colorSpaceType.equals("GRAY")) {
 899                         boolean isTransparency = false;
 900                         if(root instanceof IIOMetadataNode) {
 901                             IIOMetadataNode iioRoot = (IIOMetadataNode)root;
 902                             NodeList siNodeList =
 903                                 iioRoot.getElementsByTagName("SubimageInterpretation");
 904                             if(siNodeList.getLength() == 1) {
 905                                 Node siNode = siNodeList.item(0);
 906                                 String value = getAttribute(siNode, "value");
 907                                 if(value.equals("TransparencyMask")) {
 908                                     isTransparency = true;
 909                                 }
 910                             }
 911                         }
 912                         if(isTransparency) {
 913                             photometricInterpretation =
 914                                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK;
 915                         } else {
 916                             photometricInterpretation =
 917                                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
 918                         }
 919                     } else if(colorSpaceType.equals("RGB")) {
 920                         photometricInterpretation =
 921                             gotPalette ?
 922                             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR :
 923                             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
 924                     } else if(colorSpaceType.equals("YCbCr")) {
 925                         photometricInterpretation =
 926                             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
 927                     } else if(colorSpaceType.equals("CMYK")) {
 928                         photometricInterpretation =
 929                             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
 930                     } else if(colorSpaceType.equals("Lab")) {
 931                         photometricInterpretation =
 932                             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
 933                     }
 934                 }
 935 
 936                 if(photometricInterpretation != -1) {
 937                     tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
 938                     f = new TIFFField(tag, photometricInterpretation);
 939                     rootIFD.addTIFFField(f);
 940                 }
 941             } else if (name.equals("Compression")) {
 942                 Node child = node.getFirstChild();
 943                 while (child != null) {
 944                     String childName = child.getNodeName();
 945                     if (childName.equals("CompressionTypeName")) {
 946                         int compression = -1;
 947                         String compressionTypeName =
 948                             getAttribute(child, "value");
 949                         if(compressionTypeName.equalsIgnoreCase("None")) {
 950                             compression =
 951                                 BaselineTIFFTagSet.COMPRESSION_NONE;
 952                         } else {
 953                             String[] compressionNames =
 954                                 TIFFImageWriter.compressionTypes;
 955                             for(int i = 0; i < compressionNames.length; i++) {
 956                                 if(compressionNames[i].equalsIgnoreCase(compressionTypeName)) {
 957                                     compression =
 958                                         TIFFImageWriter.compressionNumbers[i];
 959                                     break;
 960                                 }
 961                             }
 962                         }
 963 
 964                         if(compression != -1) {
 965                             tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COMPRESSION);
 966                             f = new TIFFField(tag, compression);
 967                             rootIFD.addTIFFField(f);
 968 
 969                             // Lossless is irrelevant.
 970                         }
 971                     }
 972 
 973                     child = child.getNextSibling();
 974                 }
 975             } else if (name.equals("Data")) {
 976                 Node child = node.getFirstChild();
 977                 while (child != null) {
 978                     String childName = child.getNodeName();
 979 
 980                     if (childName.equals("PlanarConfiguration")) {
 981                         String pc = getAttribute(child, "value");
 982                         int planarConfiguration = -1;
 983                         if(pc.equals("PixelInterleaved")) {
 984                             planarConfiguration =
 985                                 BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
 986                         } else if(pc.equals("PlaneInterleaved")) {
 987                             planarConfiguration =
 988                                 BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR;
 989                         }
 990                         if(planarConfiguration != -1) {
 991                             tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
 992                             f = new TIFFField(tag, planarConfiguration);
 993                             rootIFD.addTIFFField(f);
 994                         }
 995                     } else if (childName.equals("BitsPerSample")) {
 996                         String bps = getAttribute(child, "value");
 997                         char[] bitsPerSample = listToCharArray(bps);
 998                         tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
 999                         if(isPaletteColor) {
1000                             f = new TIFFField(tag, TIFFTag.TIFF_SHORT, 1,
1001                                               new char[] {bitsPerSample[0]});
1002                         } else {
1003                             f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
1004                                               bitsPerSample.length,
1005                                               bitsPerSample);
1006                         }
1007                         rootIFD.addTIFFField(f);
1008                     } else if (childName.equals("SampleMSB")) {
1009                         // Add FillOrder only if lsb-to-msb (right to left)
1010                         // for all bands, i.e., SampleMSB is zero for all
1011                         // channels.
1012                         String sMSB = getAttribute(child, "value");
1013                         int[] sampleMSB = listToIntArray(sMSB);
1014                         boolean isRightToLeft = true;
1015                         for(int i = 0; i < sampleMSB.length; i++) {
1016                             if(sampleMSB[i] != 0) {
1017                                 isRightToLeft = false;
1018                                 break;
1019                             }
1020                         }
1021                         int fillOrder = isRightToLeft ?
1022                             BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT :
1023                             BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT;
1024                         tag =
1025                             rootIFD.getTag(BaselineTIFFTagSet.TAG_FILL_ORDER);
1026                         f = new TIFFField(tag, fillOrder);
1027                         rootIFD.addTIFFField(f);
1028                     }
1029 
1030                     child = child.getNextSibling();
1031                 }
1032             } else if (name.equals("Dimension")) {
1033                 float pixelAspectRatio = -1.0f;
1034                 boolean gotPixelAspectRatio = false;
1035 
1036                 float horizontalPixelSize = -1.0f;
1037                 boolean gotHorizontalPixelSize = false;
1038 
1039                 float verticalPixelSize = -1.0f;
1040                 boolean gotVerticalPixelSize = false;
1041 
1042                 boolean sizeIsAbsolute = false;
1043 
1044                 float horizontalPosition = -1.0f;
1045                 boolean gotHorizontalPosition = false;
1046 
1047                 float verticalPosition = -1.0f;
1048                 boolean gotVerticalPosition = false;
1049 
1050                 Node child = node.getFirstChild();
1051                 while (child != null) {
1052                     String childName = child.getNodeName();
1053                     if (childName.equals("PixelAspectRatio")) {
1054                         String par = getAttribute(child, "value");
1055                         pixelAspectRatio = Float.parseFloat(par);
1056                         gotPixelAspectRatio = true;
1057                     } else if (childName.equals("ImageOrientation")) {
1058                         String orientation = getAttribute(child, "value");
1059                         for (int i = 0; i < orientationNames.length; i++) {
1060                             if (orientation.equals(orientationNames[i])) {
1061                                 char[] oData = new char[1];
1062                                 oData[0] = (char)i;
1063 
1064                                 f = new TIFFField(
1065                             rootIFD.getTag(BaselineTIFFTagSet.TAG_ORIENTATION),
1066                             TIFFTag.TIFF_SHORT,
1067                             1,
1068                             oData);
1069 
1070                                 rootIFD.addTIFFField(f);
1071                                 break;
1072                             }
1073                         }
1074 
1075                     } else if (childName.equals("HorizontalPixelSize")) {
1076                         String hps = getAttribute(child, "value");
1077                         horizontalPixelSize = Float.parseFloat(hps);
1078                         gotHorizontalPixelSize = true;
1079                     } else if (childName.equals("VerticalPixelSize")) {
1080                         String vps = getAttribute(child, "value");
1081                         verticalPixelSize = Float.parseFloat(vps);
1082                         gotVerticalPixelSize = true;
1083                     } else if (childName.equals("HorizontalPosition")) {
1084                         String hp = getAttribute(child, "value");
1085                         horizontalPosition = Float.parseFloat(hp);
1086                         gotHorizontalPosition = true;
1087                     } else if (childName.equals("VerticalPosition")) {
1088                         String vp = getAttribute(child, "value");
1089                         verticalPosition = Float.parseFloat(vp);
1090                         gotVerticalPosition = true;
1091                     }
1092 
1093                     child = child.getNextSibling();
1094                 }
1095 
1096                 sizeIsAbsolute = gotHorizontalPixelSize ||
1097                     gotVerticalPixelSize;
1098 
1099                 // Fill in pixel size data from aspect ratio
1100                 if (gotPixelAspectRatio) {
1101                     if (gotHorizontalPixelSize && !gotVerticalPixelSize) {
1102                         verticalPixelSize =
1103                             horizontalPixelSize/pixelAspectRatio;
1104                         gotVerticalPixelSize = true;
1105                     } else if (gotVerticalPixelSize &&
1106                                !gotHorizontalPixelSize) {
1107                         horizontalPixelSize =
1108                             verticalPixelSize*pixelAspectRatio;
1109                         gotHorizontalPixelSize = true;
1110                     } else if (!gotHorizontalPixelSize &&
1111                                !gotVerticalPixelSize) {
1112                         horizontalPixelSize = pixelAspectRatio;
1113                         verticalPixelSize = 1.0f;
1114                         gotHorizontalPixelSize = true;
1115                         gotVerticalPixelSize = true;
1116                     }
1117                 }
1118 
1119                 // Compute pixels/centimeter
1120                 if (gotHorizontalPixelSize) {
1121                     float xResolution =
1122                         (sizeIsAbsolute ? 10.0f : 1.0f)/horizontalPixelSize;
1123                     long[][] hData = new long[1][2];
1124                     hData[0] = new long[2];
1125                     hData[0][0] = (long)(xResolution*10000.0f);
1126                     hData[0][1] = (long)10000;
1127 
1128                     f = new TIFFField(
1129                            rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1130                            TIFFTag.TIFF_RATIONAL,
1131                            1,
1132                            hData);
1133                     rootIFD.addTIFFField(f);
1134                 }
1135 
1136                 if (gotVerticalPixelSize) {
1137                     float yResolution =
1138                         (sizeIsAbsolute ? 10.0f : 1.0f)/verticalPixelSize;
1139                     long[][] vData = new long[1][2];
1140                     vData[0] = new long[2];
1141                     vData[0][0] = (long)(yResolution*10000.0f);
1142                     vData[0][1] = (long)10000;
1143 
1144                     f = new TIFFField(
1145                            rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1146                            TIFFTag.TIFF_RATIONAL,
1147                            1,
1148                            vData);
1149                     rootIFD.addTIFFField(f);
1150                 }
1151 
1152                 // Emit ResolutionUnit tag
1153                 char[] res = new char[1];
1154                 res[0] = (char)(sizeIsAbsolute ?
1155                                 BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER :
1156                                 BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
1157 
1158                 f = new TIFFField(
1159                         rootIFD.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1160                         TIFFTag.TIFF_SHORT,
1161                         1,
1162                         res);
1163                 rootIFD.addTIFFField(f);
1164 
1165                 // Position
1166                 if(sizeIsAbsolute) {
1167                     if(gotHorizontalPosition) {
1168                         // Convert from millimeters to centimeters via
1169                         // numerator multiplier = denominator/10.
1170                         long[][] hData = new long[1][2];
1171                         hData[0][0] = (long)(horizontalPosition*10000.0f);
1172                         hData[0][1] = (long)100000;
1173 
1174                         f = new TIFFField(
1175                            rootIFD.getTag(BaselineTIFFTagSet.TAG_X_POSITION),
1176                            TIFFTag.TIFF_RATIONAL,
1177                            1,
1178                            hData);
1179                         rootIFD.addTIFFField(f);
1180                     }
1181 
1182                     if(gotVerticalPosition) {
1183                         // Convert from millimeters to centimeters via
1184                         // numerator multiplier = denominator/10.
1185                         long[][] vData = new long[1][2];
1186                         vData[0][0] = (long)(verticalPosition*10000.0f);
1187                         vData[0][1] = (long)100000;
1188 
1189                         f = new TIFFField(
1190                            rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_POSITION),
1191                            TIFFTag.TIFF_RATIONAL,
1192                            1,
1193                            vData);
1194                         rootIFD.addTIFFField(f);
1195                     }
1196                 }
1197             } else if (name.equals("Document")) {
1198                 Node child = node.getFirstChild();
1199                 while (child != null) {
1200                     String childName = child.getNodeName();
1201 
1202                     if (childName.equals("SubimageInterpretation")) {
1203                         String si = getAttribute(child, "value");
1204                         int newSubFileType = -1;
1205                         if(si.equals("TransparencyMask")) {
1206                             newSubFileType =
1207                                 BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY;
1208                         } else if(si.equals("ReducedResolution")) {
1209                             newSubFileType =
1210                                 BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION;
1211                         } else if(si.equals("SinglePage")) {
1212                             newSubFileType =
1213                                 BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE;
1214                         }
1215                         if(newSubFileType != -1) {
1216                             tag =
1217                                 rootIFD.getTag(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
1218                             f = new TIFFField(tag, newSubFileType);
1219                             rootIFD.addTIFFField(f);
1220                         }
1221                     }
1222 
1223                     if (childName.equals("ImageCreationTime")) {
1224                         String year = getAttribute(child, "year");
1225                         String month = getAttribute(child, "month");
1226                         String day = getAttribute(child, "day");
1227                         String hour = getAttribute(child, "hour");
1228                         String minute = getAttribute(child, "minute");
1229                         String second = getAttribute(child, "second");
1230 
1231                         StringBuffer sb = new StringBuffer();
1232                         sb.append(year);
1233                         sb.append(":");
1234                         if(month.length() == 1) {
1235                             sb.append("0");
1236                         }
1237                         sb.append(month);
1238                         sb.append(":");
1239                         if(day.length() == 1) {
1240                             sb.append("0");
1241                         }
1242                         sb.append(day);
1243                         sb.append(" ");
1244                         if(hour.length() == 1) {
1245                             sb.append("0");
1246                         }
1247                         sb.append(hour);
1248                         sb.append(":");
1249                         if(minute.length() == 1) {
1250                             sb.append("0");
1251                         }
1252                         sb.append(minute);
1253                         sb.append(":");
1254                         if(second.length() == 1) {
1255                             sb.append("0");
1256                         }
1257                         sb.append(second);
1258 
1259                         String[] dt = new String[1];
1260                         dt[0] = sb.toString();
1261 
1262                         f = new TIFFField(
1263                               rootIFD.getTag(BaselineTIFFTagSet.TAG_DATE_TIME),
1264                               TIFFTag.TIFF_ASCII,
1265                               1,
1266                               dt);
1267                         rootIFD.addTIFFField(f);
1268                     }
1269 
1270                     child = child.getNextSibling();
1271                 }
1272             } else if (name.equals("Text")) {
1273                 Node child = node.getFirstChild();
1274                 String theAuthor = null;
1275                 String theDescription = null;
1276                 String theTitle = null;
1277                 while (child != null) {
1278                     String childName = child.getNodeName();
1279                     if(childName.equals("TextEntry")) {
1280                         int tagNumber = -1;
1281                         NamedNodeMap childAttrs = child.getAttributes();
1282                         Node keywordNode = childAttrs.getNamedItem("keyword");
1283                         if(keywordNode != null) {
1284                             String keyword = keywordNode.getNodeValue();
1285                             String value = getAttribute(child, "value");
1286                             if(!keyword.equals("") && !value.equals("")) {
1287                                 if(keyword.equalsIgnoreCase("DocumentName")) {
1288                                     tagNumber =
1289                                         BaselineTIFFTagSet.TAG_DOCUMENT_NAME;
1290                                 } else if(keyword.equalsIgnoreCase("ImageDescription")) {
1291                                     tagNumber =
1292                                         BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;
1293                                 } else if(keyword.equalsIgnoreCase("Make")) {
1294                                     tagNumber =
1295                                         BaselineTIFFTagSet.TAG_MAKE;
1296                                 } else if(keyword.equalsIgnoreCase("Model")) {
1297                                     tagNumber =
1298                                         BaselineTIFFTagSet.TAG_MODEL;
1299                                 } else if(keyword.equalsIgnoreCase("PageName")) {
1300                                     tagNumber =
1301                                         BaselineTIFFTagSet.TAG_PAGE_NAME;
1302                                 } else if(keyword.equalsIgnoreCase("Software")) {
1303                                     tagNumber =
1304                                         BaselineTIFFTagSet.TAG_SOFTWARE;
1305                                 } else if(keyword.equalsIgnoreCase("Artist")) {
1306                                     tagNumber =
1307                                         BaselineTIFFTagSet.TAG_ARTIST;
1308                                 } else if(keyword.equalsIgnoreCase("HostComputer")) {
1309                                     tagNumber =
1310                                         BaselineTIFFTagSet.TAG_HOST_COMPUTER;
1311                                 } else if(keyword.equalsIgnoreCase("InkNames")) {
1312                                     tagNumber =
1313                                         BaselineTIFFTagSet.TAG_INK_NAMES;
1314                                 } else if(keyword.equalsIgnoreCase("Copyright")) {
1315                                     tagNumber =
1316                                         BaselineTIFFTagSet.TAG_COPYRIGHT;
1317                                 } else if(keyword.equalsIgnoreCase("author")) {
1318                                     theAuthor = value;
1319                                 } else if(keyword.equalsIgnoreCase("description")) {
1320                                     theDescription = value;
1321                                 } else if(keyword.equalsIgnoreCase("title")) {
1322                                     theTitle = value;
1323                                 }
1324                                 if(tagNumber != -1) {
1325                                     f = new TIFFField(rootIFD.getTag(tagNumber),
1326                                                       TIFFTag.TIFF_ASCII,
1327                                                       1,
1328                                                       new String[] {value});
1329                                     rootIFD.addTIFFField(f);
1330                                 }
1331                             }
1332                         }
1333                     }
1334                     child = child.getNextSibling();
1335                 } // child != null
1336                 if(theAuthor != null &&
1337                    getTIFFField(BaselineTIFFTagSet.TAG_ARTIST) == null) {
1338                     f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_ARTIST),
1339                                       TIFFTag.TIFF_ASCII,
1340                                       1,
1341                                       new String[] {theAuthor});
1342                     rootIFD.addTIFFField(f);
1343                 }
1344                 if(theDescription != null &&
1345                    getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION) == null) {
1346                     f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION),
1347                                       TIFFTag.TIFF_ASCII,
1348                                       1,
1349                                       new String[] {theDescription});
1350                     rootIFD.addTIFFField(f);
1351                 }
1352                 if(theTitle != null &&
1353                    getTIFFField(BaselineTIFFTagSet.TAG_DOCUMENT_NAME) == null) {
1354                     f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_DOCUMENT_NAME),
1355                                       TIFFTag.TIFF_ASCII,
1356                                       1,
1357                                       new String[] {theTitle});
1358                     rootIFD.addTIFFField(f);
1359                 }
1360             } else if (name.equals("Transparency")) {
1361                  Node child = node.getFirstChild();
1362                  while (child != null) {
1363                      String childName = child.getNodeName();
1364 
1365                      if (childName.equals("Alpha")) {
1366                          String alpha = getAttribute(child, "value");
1367 
1368                          f = null;
1369                          if (alpha.equals("premultiplied")) {
1370                              f = new TIFFField(
1371                           rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1372                           BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA);
1373                          } else if (alpha.equals("nonpremultiplied")) {
1374                              f = new TIFFField(
1375                           rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1376                           BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA);
1377                          }
1378                          if (f != null) {
1379                              rootIFD.addTIFFField(f);
1380                          }
1381                      }
1382 
1383                     child = child.getNextSibling();
1384                  }
1385             }
1386 
1387             node = node.getNextSibling();
1388         }
1389 
1390         // Set SampleFormat.
1391         if(sampleFormat != null) {
1392             // Derive the value.
1393             int sf = -1;
1394             if(sampleFormat.equals("SignedIntegral")) {
1395                 sf = BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
1396             } else if(sampleFormat.equals("UnsignedIntegral")) {
1397                 sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
1398             } else if(sampleFormat.equals("Real")) {
1399                 sf = BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
1400             } else if(sampleFormat.equals("Index")) {
1401                 sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
1402             }
1403 
1404             if(sf != -1) {
1405                 // Derive the count.
1406                 int count = 1;
1407 
1408                 // Try SamplesPerPixel first.
1409                 f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
1410                 if(f != null) {
1411                     count = f.getAsInt(0);
1412                 } else {
1413                     // Try BitsPerSample.
1414                     f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1415                     if(f != null) {
1416                         count = f.getCount();
1417                     }
1418                 }
1419 
1420                 char[] sampleFormatArray = new char[count];
1421                 Arrays.fill(sampleFormatArray, (char)sf);
1422 
1423                 // Add SampleFormat.
1424                 tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
1425                 f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
1426                                   sampleFormatArray.length, sampleFormatArray);
1427                 rootIFD.addTIFFField(f);
1428             }
1429         }
1430     }
1431 
1432     private static String getAttribute(Node node, String attrName) {
1433         NamedNodeMap attrs = node.getAttributes();
1434         Node attr = attrs.getNamedItem(attrName);
1435         return attr != null ? attr.getNodeValue() : null;
1436     }
1437 
1438     private Node getChildNode(Node node, String childName) {
1439         Node childNode = null;
1440         if(node.hasChildNodes()) {
1441             NodeList childNodes = node.getChildNodes();
1442             int length = childNodes.getLength();
1443             for(int i = 0; i < length; i++) {
1444                 Node item = childNodes.item(i);
1445                 if(item.getNodeName().equals(childName)) {
1446                     childNode = item;
1447                     break;
1448                 }
1449             }
1450         }
1451         return childNode;
1452     }
1453 
1454     public static TIFFIFD parseIFD(Node node) throws IIOInvalidTreeException {
1455         if (!node.getNodeName().equals("TIFFIFD")) {
1456             fatal(node, "Expected \"TIFFIFD\" node");
1457         }
1458 
1459         String tagSetNames = getAttribute(node, "tagSets");
1460         List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(5);
1461 
1462         if (tagSetNames != null) {
1463             StringTokenizer st = new StringTokenizer(tagSetNames, ",");
1464             while (st.hasMoreTokens()) {
1465                 String className = st.nextToken();
1466 
1467                 Object o = null;
1468                 Class<?> setClass = null;
1469                 try {
1470                     ClassLoader cl = TIFFImageMetadata.class.getClassLoader();
1471                     setClass = Class.forName(className, false, cl);
1472                     if (!TIFFTagSet.class.isAssignableFrom(setClass)) {
1473                         fatal(node, "TagSets in IFD must be subset of"
1474                                 + " TIFFTagSet class");
1475                     }
1476                     Method getInstanceMethod =
1477                         setClass.getMethod("getInstance", (Class[])null);
1478                     o = getInstanceMethod.invoke(null, (Object[])null);
1479                 } catch (NoSuchMethodException e) {
1480                     throw new RuntimeException(e);
1481                 } catch (IllegalAccessException e) {
1482                     throw new RuntimeException(e);
1483                 } catch (InvocationTargetException e) {
1484                     throw new RuntimeException(e);
1485                 } catch (ClassNotFoundException e) {
1486                     throw new RuntimeException(e);
1487                 }
1488 
1489                 if (!(o instanceof TIFFTagSet)) {
1490                     fatal(node, "Specified tag set class \"" +
1491                           className +
1492                           "\" is not an instance of TIFFTagSet");
1493                 } else {
1494                     tagSets.add((TIFFTagSet)o);
1495                 }
1496             }
1497         }
1498 
1499         TIFFIFD ifd = new TIFFIFD(tagSets);
1500 
1501         node = node.getFirstChild();
1502         while (node != null) {
1503             String name = node.getNodeName();
1504 
1505             TIFFField f = null;
1506             if (name.equals("TIFFIFD")) {
1507                 TIFFIFD subIFD = parseIFD(node);
1508                 String parentTagName = getAttribute(node, "parentTagName");
1509                 String parentTagNumber = getAttribute(node, "parentTagNumber");
1510                 TIFFTag tag = null;
1511                 if(parentTagName != null) {
1512                     tag = TIFFIFD.getTag(parentTagName, tagSets);
1513                 } else if(parentTagNumber != null) {
1514                     int tagNumber = Integer.parseUnsignedInt(parentTagNumber);
1515                     tag = TIFFIFD.getTag(tagNumber, tagSets);
1516                 }
1517 
1518                 int type;
1519                 if (tag == null) {
1520                     type = TIFFTag.TIFF_LONG;
1521                     tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, 0, 1 << type);
1522                 } else {
1523                     if (tag.isDataTypeOK(TIFFTag.TIFF_IFD_POINTER)) {
1524                         type = TIFFTag.TIFF_IFD_POINTER;
1525                     } else if (tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
1526                         type = TIFFTag.TIFF_LONG;
1527                     } else {
1528                         for (type = TIFFTag.MAX_DATATYPE;
1529                             type >= TIFFTag.MIN_DATATYPE;
1530                             type--) {
1531                             if (tag.isDataTypeOK(type)) {
1532                                 break;
1533                             }
1534                         }
1535                     }
1536                 }
1537 
1538                 f = new TIFFField(tag, type, 1L, subIFD);
1539             } else if (name.equals("TIFFField")) {
1540                 int number = Integer.parseInt(getAttribute(node, "number"));
1541 
1542                 TIFFTagSet tagSet = null;
1543                 Iterator<TIFFTagSet> iter = tagSets.iterator();
1544                 while (iter.hasNext()) {
1545                     TIFFTagSet t = iter.next();
1546                     if (t.getTag(number) != null) {
1547                         tagSet = t;
1548                         break;
1549                     }
1550                 }
1551 
1552                 f = TIFFField.createFromMetadataNode(tagSet, node);
1553             } else {
1554                 fatal(node,
1555                       "Expected either \"TIFFIFD\" or \"TIFFField\" node, got "
1556                       + name);
1557             }
1558 
1559             ifd.addTIFFField(f);
1560             node = node.getNextSibling();
1561         }
1562 
1563         return ifd;
1564     }
1565 
1566     private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
1567         Node node = root;
1568         if (!node.getNodeName().equals(nativeMetadataFormatName)) {
1569             fatal(node, "Root must be " + nativeMetadataFormatName);
1570         }
1571 
1572         node = node.getFirstChild();
1573         if (node == null || !node.getNodeName().equals("TIFFIFD")) {
1574             fatal(root, "Root must have \"TIFFIFD\" child");
1575         }
1576         TIFFIFD ifd = parseIFD(node);
1577 
1578         List<TIFFTagSet> rootIFDTagSets = rootIFD.getTagSetList();
1579         Iterator<TIFFTagSet> tagSetIter = ifd.getTagSetList().iterator();
1580         while(tagSetIter.hasNext()) {
1581             Object o = tagSetIter.next();
1582             if(o instanceof TIFFTagSet && !rootIFDTagSets.contains(o)) {
1583                 rootIFD.addTagSet((TIFFTagSet)o);
1584             }
1585         }
1586 
1587         Iterator<TIFFField> ifdIter = ifd.iterator();
1588         while(ifdIter.hasNext()) {
1589             TIFFField field = ifdIter.next();
1590             rootIFD.addTIFFField(field);
1591         }
1592     }
1593 
1594     public void mergeTree(String formatName, Node root)
1595         throws IIOInvalidTreeException{
1596         if (formatName.equals(nativeMetadataFormatName)) {
1597             if (root == null) {
1598                 throw new NullPointerException("root == null!");
1599             }
1600             mergeNativeTree(root);
1601         } else if (formatName.equals
1602                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
1603             if (root == null) {
1604                 throw new NullPointerException("root == null!");
1605             }
1606             mergeStandardTree(root);
1607         } else {
1608             throw new IllegalArgumentException("Not a recognized format!");
1609         }
1610     }
1611 
1612     public void reset() {
1613         rootIFD = new TIFFIFD(tagSets);
1614     }
1615 
1616     public TIFFIFD getRootIFD() {
1617         return rootIFD;
1618     }
1619 
1620     public TIFFField getTIFFField(int tagNumber) {
1621         return rootIFD.getTIFFField(tagNumber);
1622     }
1623 
1624     public void removeTIFFField(int tagNumber) {
1625         rootIFD.removeTIFFField(tagNumber);
1626     }
1627 
1628     /**
1629      * Returns a {@code TIFFImageMetadata} wherein all fields in the
1630      * root IFD from the {@code BaselineTIFFTagSet} are copied by value
1631      * and all other fields copied by reference.
1632      */
1633     public TIFFImageMetadata getShallowClone() {
1634         return new TIFFImageMetadata(rootIFD.getShallowClone());
1635     }
1636 }