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