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