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