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