001/*
002 * $RCSfile: CLibJPEGMetadata.java,v $
003 *
004 * 
005 * Copyright (c) 2006 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.7 $
042 * $Date: 2007/08/28 18:45:53 $
043 * $State: Exp $
044 */
045
046package com.github.jaiimageio.impl.plugins.jpeg;
047
048import java.awt.Transparency;
049import java.awt.color.ColorSpace;
050import java.awt.color.ICC_Profile;
051import java.awt.image.BufferedImage;
052import java.awt.image.ColorModel;
053import java.awt.image.ComponentColorModel;
054import java.awt.image.DataBuffer;
055import java.awt.image.DataBufferByte;
056import java.awt.image.IndexColorModel;
057import java.awt.image.Raster;
058import java.awt.image.WritableRaster;
059import java.io.ByteArrayInputStream;
060import java.io.EOFException;
061import java.io.IOException;
062import java.io.UnsupportedEncodingException;
063import java.nio.ByteOrder;
064import java.util.ArrayList;
065import java.util.Arrays;
066import java.util.Iterator;
067import java.util.List;
068
069import javax.imageio.IIOException;
070import javax.imageio.IIOImage;
071import javax.imageio.ImageIO;
072import javax.imageio.ImageReader;
073import javax.imageio.metadata.IIOInvalidTreeException;
074import javax.imageio.metadata.IIOMetadata;
075import javax.imageio.metadata.IIOMetadataFormatImpl;
076import javax.imageio.metadata.IIOMetadataNode;
077import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
078import javax.imageio.plugins.jpeg.JPEGQTable;
079import javax.imageio.stream.ImageInputStream;
080import javax.imageio.stream.MemoryCacheImageInputStream;
081
082import org.w3c.dom.Node;
083import org.w3c.dom.NodeList;
084
085import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
086import com.github.jaiimageio.plugins.tiff.EXIFParentTIFFTagSet;
087import com.github.jaiimageio.plugins.tiff.TIFFDirectory;
088import com.github.jaiimageio.plugins.tiff.TIFFField;
089import com.github.jaiimageio.plugins.tiff.TIFFTag;
090import com.github.jaiimageio.plugins.tiff.TIFFTagSet;
091
092public class CLibJPEGMetadata extends IIOMetadata {
093    // --- Constants ---
094
095    static final String NATIVE_FORMAT = "javax_imageio_jpeg_image_1.0";
096    // XXX Reference to a non-API J2SE class:
097    static final String NATIVE_FORMAT_CLASS =
098        "com.sun.imageio.plugins.jpeg.JPEGImageMetadataFormat";
099
100    static final String TIFF_FORMAT =
101        "com_sun_media_imageio_plugins_tiff_image_1.0";
102    static final String TIFF_FORMAT_CLASS =
103        "com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadataFormat";
104
105    // Marker codes from J2SE in numerically increasing order.
106
107    /** For temporary use in arithmetic coding */
108    static final int TEM = 0x01;
109
110    // Codes 0x02 - 0xBF are reserved
111
112    // SOF markers for Nondifferential Huffman coding
113    /** Baseline DCT */
114    static final int SOF0 = 0xC0;
115    /** Extended Sequential DCT */
116    static final int SOF1 = 0xC1;
117    /** Progressive DCT */
118    static final int SOF2 = 0xC2;
119    /** Lossless Sequential */
120    static final int SOF3 = 0xC3;
121
122    /** Define Huffman Tables */
123    static final int DHT = 0xC4;    
124
125    // SOF markers for Differential Huffman coding
126    /** Differential Sequential DCT */
127    static final int SOF5 = 0xC5;
128    /** Differential Progressive DCT */
129    static final int SOF6 = 0xC6;
130    /** Differential Lossless */
131    static final int SOF7 = 0xC7;
132
133    /** Reserved for JPEG extensions */
134    static final int JPG = 0xC8;
135
136    // SOF markers for Nondifferential arithmetic coding
137    /** Extended Sequential DCT, Arithmetic coding */
138    static final int SOF9 = 0xC9;
139    /** Progressive DCT, Arithmetic coding */
140    static final int SOF10 = 0xCA;
141    /** Lossless Sequential, Arithmetic coding */
142    static final int SOF11 = 0xCB;
143
144    /** Define Arithmetic conditioning tables */
145    static final int DAC = 0xCC;
146
147    // SOF markers for Differential arithmetic coding
148    /** Differential Sequential DCT, Arithmetic coding */
149    static final int SOF13 = 0xCD;
150    /** Differential Progressive DCT, Arithmetic coding */
151    static final int SOF14 = 0xCE;
152    /** Differential Lossless, Arithmetic coding */
153    static final int SOF15 = 0xCF;
154
155    // Restart Markers
156    static final int RST0 = 0xD0;
157    static final int RST1 = 0xD1;
158    static final int RST2 = 0xD2;
159    static final int RST3 = 0xD3;
160    static final int RST4 = 0xD4;
161    static final int RST5 = 0xD5;
162    static final int RST6 = 0xD6;
163    static final int RST7 = 0xD7;
164    /** Number of restart markers */
165    static final int RESTART_RANGE = 8;
166
167    /** Start of Image */
168    static final int SOI = 0xD8;
169    /** End of Image */
170    static final int EOI = 0xD9;
171    /** Start of Scan */
172    static final int SOS = 0xDA;
173
174    /** Define Quantisation Tables */
175    static final int DQT = 0xDB;
176
177    /** Define Number of lines */
178    static final int DNL = 0xDC;
179
180    /** Define Restart Interval */
181    static final int DRI = 0xDD;
182
183    /** Define Heirarchical progression */
184    static final int DHP = 0xDE;
185
186    /** Expand reference image(s) */
187    static final int EXP = 0xDF;
188
189    // Application markers
190    /** APP0 used by JFIF */
191    static final int APP0 = 0xE0;
192    static final int APP1 = 0xE1;
193    static final int APP2 = 0xE2;
194    static final int APP3 = 0xE3;
195    static final int APP4 = 0xE4;
196    static final int APP5 = 0xE5;
197    static final int APP6 = 0xE6;
198    static final int APP7 = 0xE7;
199    static final int APP8 = 0xE8;
200    static final int APP9 = 0xE9;
201    static final int APP10 = 0xEA;
202    static final int APP11 = 0xEB;
203    static final int APP12 = 0xEC;
204    static final int APP13 = 0xED;
205    /** APP14 used by Adobe */
206    static final int APP14 = 0xEE;
207    static final int APP15 = 0xEF;
208
209    // codes 0xF0 to 0xFD are reserved
210
211    /** Comment marker */
212    static final int COM = 0xFE;
213
214    // Marker codes for JPEG-LS
215
216    /** JPEG-LS SOF marker */
217    // This was SOF48 in an earlier revision of the JPEG-LS specification.
218    // "55" is the numerical value of SOF55 - SOF0 (= 247 - 192).
219    static final int SOF55 = 0xF7;
220
221    /** JPEG-LS parameters */
222    static final int LSE = 0xF2;
223
224    // Min and max APPn codes.
225    static final int APPN_MIN = APP0;
226    static final int APPN_MAX = APP15;
227
228    // Min and max contiguous SOFn codes.
229    static final int SOFN_MIN = SOF0;
230    static final int SOFN_MAX = SOF15;
231
232    // Min and Max RSTn codes.
233    static final int RST_MIN = RST0;
234    static final int RST_MAX = RST7;
235
236    // Specific segment types defined as (code << 8) | X.
237    static final int APP0_JFIF   = (APP0 << 8) | 0;
238    static final int APP0_JFXX   = (APP0 << 8) | 1;
239    static final int APP1_EXIF   = (APP1 << 8) | 0;
240    static final int APP2_ICC    = (APP2 << 8) | 0;
241    static final int APP14_ADOBE = (APP14 << 8) | 0;
242    static final int UNKNOWN_MARKER = 0xffff;
243    static final int SOF_MARKER = (SOF0 << 8) | 0;
244
245    // Resolution unit types.
246    static final int JFIF_RESUNITS_ASPECT = 0;
247    static final int JFIF_RESUNITS_DPI = 1;
248    static final int JFIF_RESUNITS_DPC = 2;
249
250    // Thumbnail types
251    static final int THUMBNAIL_JPEG = 0x10;
252    static final int THUMBNAIL_PALETTE = 0x11;
253    static final int THUMBNAIL_RGB = 0x12;
254
255    // Adobe transform type.
256    static final int ADOBE_TRANSFORM_UNKNOWN = 0;
257    static final int ADOBE_TRANSFORM_YCC = 1;
258    static final int ADOBE_TRANSFORM_YCCK = 2;
259
260    // Zig-zag to natural re-ordering array.
261    static final int [] zigzag = {
262        0,  1,  5,  6, 14, 15, 27, 28,
263        2,  4,  7, 13, 16, 26, 29, 42,
264        3,  8, 12, 17, 25, 30, 41, 43,
265        9, 11, 18, 24, 31, 40, 44, 53,
266        10, 19, 23, 32, 39, 45, 52, 54,
267        20, 22, 33, 38, 46, 51, 55, 60,
268        21, 34, 37, 47, 50, 56, 59, 61,
269        35, 36, 48, 49, 57, 58, 62, 63
270    };
271
272    // --- Static methods ---
273
274    private static IIOImage getThumbnail(ImageInputStream stream, int len,
275                                         int thumbnailType, int w, int h)
276        throws IOException {
277
278        IIOImage result;
279
280        long startPos = stream.getStreamPosition();
281
282        if(thumbnailType == THUMBNAIL_JPEG) {
283            Iterator readers = ImageIO.getImageReaders(stream);
284            if(readers == null || !readers.hasNext()) return null;
285            ImageReader reader = (ImageReader)readers.next();
286            reader.setInput(stream);
287            BufferedImage image = reader.read(0, null);
288            IIOMetadata metadata = null;
289            try {
290                metadata = reader.getImageMetadata(0);
291            } catch(Exception e) {
292                // Ignore it
293            }
294            result = new IIOImage(image, null, metadata);
295        } else {
296            int numBands;
297            ColorModel cm;
298            if(thumbnailType == THUMBNAIL_PALETTE) {
299                if(len < 768 + w*h) {
300                    return null;
301                }
302
303                numBands = 1;
304
305                byte[] palette = new byte[768];
306                stream.readFully(palette);
307                byte[] r = new byte[256];
308                byte[] g = new byte[256];
309                byte[] b = new byte[256];
310                for(int i = 0, off = 0; i < 256; i++) {
311                    r[i] = palette[off++];
312                    g[i] = palette[off++];
313                    b[i] = palette[off++];
314                }
315
316                cm = new IndexColorModel(8, 256, r, g, b);
317            } else {
318                if(len < 3*w*h) {
319                    return null;
320                }
321
322                numBands = 3;
323
324                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
325                cm = new ComponentColorModel(cs, false, false,
326                                             Transparency.OPAQUE,
327                                             DataBuffer.TYPE_BYTE);
328            }
329
330            byte[] data = new byte[w*h*numBands];
331            stream.readFully(data);
332            DataBufferByte db = new DataBufferByte(data, data.length);
333            WritableRaster wr =
334                Raster.createInterleavedRaster(db, w, h, w*numBands, numBands,
335                                               new int[] {0, 1, 2}, null);
336            BufferedImage image = new BufferedImage(cm, wr, false, null);
337            result = new IIOImage(image, null, null);
338        }
339
340        stream.seek(startPos + len);
341
342        return result;
343    }
344
345    // --- Instance variables ---
346
347    /** Whether the object may be edited. */
348    private boolean isReadOnly = true;
349
350    // APP0 JFIF marker segment parameters.
351    boolean app0JFIFPresent;
352    int majorVersion = 1;
353    int minorVersion = 2;
354    int resUnits; // (0 = aspect ratio; 1 = dots/inch; 2 = dots/cm)
355    int Xdensity = 1;
356    int Ydensity = 1;
357    int thumbWidth = 0;
358    int thumbHeight = 0;
359    BufferedImage jfifThumbnail;
360
361    // APP0 JFIF thumbnail(s).
362    boolean app0JFXXPresent;
363    List extensionCodes; // Integers 0x10, 0x11, 0x12
364    List jfxxThumbnails; // IIOImages
365
366    // APP2 ICC_PROFILE marker segment parameters.
367    boolean app2ICCPresent;
368    ICC_Profile profile = null;
369
370    // DQT marker segment parameters.
371    boolean dqtPresent;
372    List qtables; // Each element is a List of QTables
373
374    // DHT marker segment parameters.
375    boolean dhtPresent;
376    List htables; // Each element is a List of HuffmanTables
377
378    // DRI marker segment parameters.
379    boolean driPresent;
380    int driInterval;
381
382    // COM marker segment parameters.
383    boolean comPresent;
384    List comments; // byte[]s
385
386    // Unknown marker segment parameters.
387    boolean unknownPresent;
388    List markerTags; // Integers
389    List unknownData; // byte[] (NB: 'length' parameter is array length)
390
391    // APP14 Adobe marker segment parameters.
392    boolean app14AdobePresent;
393    int version = 100;
394    int flags0 = 0;
395    int flags1 = 0;
396    int transform; // 0 = Unknown, 1 = YCbCr, 2 = YCCK
397
398    // SOF marker segment parameters.
399    boolean sofPresent;
400    int sofProcess;
401    int samplePrecision = 8;
402    int numLines;
403    int samplesPerLine;
404    int numFrameComponents;
405    int[] componentId;
406    int[] hSamplingFactor;
407    int[] vSamplingFactor;
408    int[] qtableSelector;
409
410    // SOS marker segment parameters.
411    boolean sosPresent;
412    int numScanComponents;
413    int[] componentSelector;
414    int[] dcHuffTable;
415    int[] acHuffTable;
416    int startSpectralSelection;
417    int endSpectralSelection;
418    int approxHigh;
419    int approxLow;
420
421    // Embedded TIFF stream from EXIF segment.
422    byte[] exifData = null;
423
424    /** Marker codes in the order encountered. */
425    private List markers = null; // List of Integer
426
427    // Standard metadata variables.
428    private boolean hasAlpha = false;
429
430    // Agregated list of thumbnails: JFIF > JFXX > EXIF.
431    private boolean thumbnailsInitialized = false;
432    private List thumbnails = new ArrayList();
433
434    CLibJPEGMetadata() {
435        super(true, NATIVE_FORMAT, NATIVE_FORMAT_CLASS,
436              new String[] {TIFF_FORMAT}, new String[] {TIFF_FORMAT_CLASS});
437              
438        this.isReadOnly = isReadOnly;
439    }
440
441    CLibJPEGMetadata(ImageInputStream stream)
442        throws IIOException {
443        this();
444
445        try {
446            initializeFromStream(stream);
447        } catch(IOException e) {
448            throw new IIOException("Cannot initialize JPEG metadata!", e);
449        }
450    }
451
452    private class QTable {
453        private static final int QTABLE_SIZE = 64;
454
455        int elementPrecision;
456        int tableID;
457        JPEGQTable table;
458
459        int length;
460
461        QTable(ImageInputStream stream) throws IOException {
462            elementPrecision = (int)stream.readBits(4);
463            tableID = (int)stream.readBits(4);
464            byte[] tmp = new byte[QTABLE_SIZE];
465            stream.readFully(tmp);
466            int[] data = new int[QTABLE_SIZE];
467            for (int i = 0; i < QTABLE_SIZE; i++) {
468                data[i] = tmp[zigzag[i]] & 0xff;
469            }
470            table = new JPEGQTable(data);
471            length = data.length + 1;
472        }
473    }
474
475    private class HuffmanTable {
476        private static final int NUM_LENGTHS = 16;
477
478        int tableClass;
479        int tableID;
480        JPEGHuffmanTable table;
481
482        int length;
483
484        HuffmanTable(ImageInputStream stream) throws IOException {
485            tableClass = (int)stream.readBits(4);
486            tableID = (int)stream.readBits(4);
487            short[] lengths = new short[NUM_LENGTHS];
488            for (int i = 0; i < NUM_LENGTHS; i++) {
489                lengths[i] = (short)stream.read();
490            }
491            int numValues = 0;
492            for (int i = 0; i < NUM_LENGTHS; i++) {
493                numValues += lengths[i];
494            }
495            short[] values = new short[numValues];
496            for (int i = 0; i < numValues; i++) {
497                values[i] = (short)stream.read();
498            }
499            table = new JPEGHuffmanTable(lengths, values);
500
501            length = 1 + NUM_LENGTHS + values.length;
502        }
503    }
504
505    private synchronized void initializeFromStream(ImageInputStream iis)
506        throws IOException {
507        iis.mark();
508        iis.setByteOrder(ByteOrder.BIG_ENDIAN);
509
510        markers = new ArrayList();
511
512        boolean isICCProfileValid = true;
513        int numICCProfileChunks = 0;
514        long[] iccProfileChunkOffsets = null;
515        int[] iccProfileChunkLengths = null;
516
517        while(true) {
518            try {
519                // 0xff denotes a potential marker.
520                if(iis.read() == 0xff) {
521                    // Get next byte.
522                    int code = iis.read();
523
524                    // Is a marker if and only if code not in {0x00, 0xff}.
525                    // Continue to next marker if this is not a marker or if
526                    // it is an empty marker.
527                    if(code == 0x00 || code == 0xff ||
528                       code == SOI || code == TEM ||
529                       (code >= RST_MIN && code <= RST_MAX)) {
530                        continue;
531                    }
532
533                    // If at the end, quit.
534                    if(code == EOI) {
535                        break;
536                    }
537
538                    // Get the content length.
539                    int dataLength = iis.readUnsignedShort() - 2;
540
541                    if(APPN_MIN <= code && code <= APPN_MAX) {
542                        long pos = iis.getStreamPosition();
543                        boolean appnAdded = false;
544
545                        switch(code) {
546                        case APP0:
547                            if(dataLength >= 5) {
548                                byte[] b = new byte[5];
549                                iis.readFully(b);
550                                String id = new String(b);
551                                if(id.startsWith("JFIF") &&
552                                   !app0JFIFPresent) {
553                                    app0JFIFPresent = true;
554                                    markers.add(new Integer(APP0_JFIF));
555                                    majorVersion = iis.read();
556                                    minorVersion = iis.read();
557                                    resUnits = iis.read();
558                                    Xdensity = iis.readUnsignedShort();
559                                    Ydensity = iis.readUnsignedShort();
560                                    thumbWidth = iis.read();
561                                    thumbHeight = iis.read();
562                                    if(thumbWidth > 0 && thumbHeight > 0) {
563                                        IIOImage imiio =
564                                            getThumbnail(iis, dataLength - 14,
565                                                         THUMBNAIL_RGB,
566                                                         thumbWidth,
567                                                         thumbHeight);
568                                        if(imiio != null) {
569                                            jfifThumbnail = (BufferedImage)
570                                                imiio.getRenderedImage();
571                                        }
572                                    }
573                                    appnAdded = true;
574                                } else if(id.startsWith("JFXX")) {
575                                    if(!app0JFXXPresent) {
576                                        extensionCodes = new ArrayList(1);
577                                        jfxxThumbnails = new ArrayList(1);
578                                        app0JFXXPresent = true;
579                                    }
580                                    markers.add(new Integer(APP0_JFXX));
581                                    int extCode = iis.read();
582                                    extensionCodes.add(new Integer(extCode));
583                                    int w = 0, h = 0, offset = 6;
584                                    if(extCode != THUMBNAIL_JPEG) {
585                                        w = iis.read();
586                                        h = iis.read();
587                                        offset += 2;
588                                    }
589                                    IIOImage imiio =
590                                        getThumbnail(iis, dataLength - offset,
591                                                     extCode, w, h);
592                                    if(imiio != null) {
593                                        jfxxThumbnails.add(imiio);
594                                    }
595                                    appnAdded = true;
596                                }
597                            }
598                            break;
599                        case APP1:
600                            if(dataLength >= 6) {
601                                byte[] b = new byte[6];
602                                iis.readFully(b);
603                                if(b[0] == (byte)'E' &&
604                                   b[1] == (byte)'x' &&
605                                   b[2] == (byte)'i' &&
606                                   b[3] == (byte)'f' &&
607                                   b[4] == (byte)0 &&
608                                   b[5] == (byte)0) {
609                                    exifData = new byte[dataLength - 6];
610                                    iis.readFully(exifData);
611                                }
612                            }
613                        case APP2:
614                            if(dataLength >= 12) {
615                                byte[] b = new byte[12];
616                                iis.readFully(b);
617                                String id = new String(b);
618                                if(id.startsWith("ICC_PROFILE")) {
619                                    if(!isICCProfileValid) {
620                                        iis.skipBytes(dataLength - 12);
621                                        continue;
622                                    }
623
624                                    int chunkNum = iis.read();
625                                    int numChunks = iis.read();
626                                    if(numChunks == 0 ||
627                                       chunkNum == 0 ||
628                                       chunkNum > numChunks ||
629                                       (app2ICCPresent &&
630                                        (numChunks != numICCProfileChunks ||
631                                         iccProfileChunkOffsets[chunkNum]
632                                         != 0L))) {
633                                        isICCProfileValid = false;
634                                        iis.skipBytes(dataLength - 14);
635                                        continue;
636                                    }
637
638                                    if(!app2ICCPresent) {
639                                        app2ICCPresent = true;
640                                        // Only flag one marker even though
641                                        // multiple may be present.
642                                        markers.add(new Integer(APP2_ICC));
643
644                                        numICCProfileChunks = numChunks;
645
646                                        if(numChunks == 1) {
647                                            b = new byte[dataLength - 14];
648                                            iis.readFully(b);
649                                            profile =
650                                                ICC_Profile.getInstance(b);
651                                        } else {
652                                            iccProfileChunkOffsets =
653                                                new long[numChunks + 1];
654                                            iccProfileChunkLengths =
655                                                new int[numChunks + 1];
656                                            iccProfileChunkOffsets[chunkNum] =
657                                                iis.getStreamPosition();
658                                            iccProfileChunkLengths[chunkNum] =
659                                                dataLength - 14;
660                                            iis.skipBytes(dataLength - 14);
661                                        }
662                                    } else {
663                                        iccProfileChunkOffsets[chunkNum] =
664                                            iis.getStreamPosition();
665                                        iccProfileChunkLengths[chunkNum] =
666                                            dataLength - 14;
667                                        iis.skipBytes(dataLength - 14);
668                                    }
669
670                                    appnAdded = true;
671                                }
672                            }
673                            break;
674                        case APP14:
675                            if(dataLength >= 5) {
676                                byte[] b = new byte[5];
677                                iis.readFully(b);
678                                String id = new String(b);
679                                if(id.startsWith("Adobe") &&
680                                   !app14AdobePresent) { // Adobe segment
681                                    app14AdobePresent = true;
682                                    markers.add(new Integer(APP14_ADOBE));
683                                    version = iis.readUnsignedShort();
684                                    flags0 = iis.readUnsignedShort();
685                                    flags1 = iis.readUnsignedShort();
686                                    transform = iis.read();
687                                    iis.skipBytes(dataLength - 12);
688                                    appnAdded = true;
689                                }
690                            }
691                            break;
692                        default:
693                            appnAdded = false;
694                            break;
695                        }
696
697                        if(!appnAdded) {
698                            iis.seek(pos);
699                            addUnknownMarkerSegment(iis, code, dataLength);
700                        }
701                    } else if(code == DQT) {
702                        if(!dqtPresent) {
703                            dqtPresent = true;
704                            qtables = new ArrayList(1);
705                        }
706                        markers.add(new Integer(DQT));
707                        List l = new ArrayList(1);
708                        do {
709                            QTable t = new QTable(iis);
710                            l.add(t);
711                            dataLength -= t.length;
712                        } while(dataLength > 0);
713                        qtables.add(l);
714                    } else if(code == DHT) {
715                        if(!dhtPresent) {
716                            dhtPresent = true;
717                            htables = new ArrayList(1);
718                        }
719                        markers.add(new Integer(DHT));
720                        List l = new ArrayList(1);
721                        do {
722                            HuffmanTable t = new HuffmanTable(iis);
723                            l.add(t);
724                            dataLength -= t.length;
725                        } while(dataLength > 0);
726                        htables.add(l);
727                    } else if(code == DRI) {
728                        if(!driPresent) {
729                            driPresent = true;
730                        }
731                        markers.add(new Integer(DRI));
732                        driInterval = iis.readUnsignedShort();
733                    } else if(code == COM) {
734                        if(!comPresent) {
735                            comPresent = true;
736                            comments = new ArrayList(1);
737                        }
738                        markers.add(new Integer(COM));
739                        byte[] b = new byte[dataLength];
740                        iis.readFully(b);
741                        comments.add(b);
742                    } else if((code >= SOFN_MIN && code <= SOFN_MAX) ||
743                              code == SOF55) { // SOFn
744                        if(!sofPresent) {
745                            sofPresent = true;
746                            sofProcess = code - SOFN_MIN;
747                            samplePrecision = iis.read();
748                            numLines = iis.readUnsignedShort();
749                            samplesPerLine = iis.readUnsignedShort();
750                            numFrameComponents = iis.read();
751                            componentId = new int[numFrameComponents];
752                            hSamplingFactor = new int[numFrameComponents];
753                            vSamplingFactor = new int[numFrameComponents];
754                            qtableSelector = new int[numFrameComponents];
755                            for(int i = 0; i < numFrameComponents; i++) {
756                                componentId[i] = iis.read();
757                                hSamplingFactor[i] = (int)iis.readBits(4);
758                                vSamplingFactor[i] = (int)iis.readBits(4);
759                                qtableSelector[i] = iis.read();
760                            }
761                            markers.add(new Integer(SOF_MARKER));
762                        }
763                    } else if(code == SOS) {
764                        if(!sosPresent) {
765                            sosPresent = true;
766                            numScanComponents = iis.read();
767                            componentSelector = new int[numScanComponents];
768                            dcHuffTable = new int[numScanComponents];
769                            acHuffTable = new int[numScanComponents];
770                            for(int i = 0; i < numScanComponents; i++) {
771                                componentSelector[i] = iis.read();
772                                dcHuffTable[i] = (int)iis.readBits(4);
773                                acHuffTable[i] = (int)iis.readBits(4);
774                            }
775                            startSpectralSelection = iis.read();
776                            endSpectralSelection = iis.read();
777                            approxHigh = (int)iis.readBits(4);
778                            approxLow = (int)iis.readBits(4);
779                            markers.add(new Integer(SOS));
780                        }
781                        break;
782                    } else { // Any other marker
783                        addUnknownMarkerSegment(iis, code, dataLength);
784                    }
785                }
786            } catch(EOFException eofe) {
787                // XXX Should this be caught?
788                break;
789            }
790        }
791
792        if(app2ICCPresent && isICCProfileValid && profile == null) {
793            int profileDataLength = 0;
794            for(int i = 1; i <= numICCProfileChunks; i++) {
795                if(iccProfileChunkOffsets[i] == 0L) {
796                    isICCProfileValid = false;
797                    break;
798                }
799                profileDataLength += iccProfileChunkLengths[i];
800            }
801
802            if(isICCProfileValid) {
803                byte[] b = new byte[profileDataLength];
804                int off = 0;
805                for(int i = 1; i <= numICCProfileChunks; i++) {
806                    iis.seek(iccProfileChunkOffsets[i]);
807                    iis.read(b, off, iccProfileChunkLengths[i]);
808                    off += iccProfileChunkLengths[i];
809                }
810
811                profile = ICC_Profile.getInstance(b);
812            }
813        }
814
815        iis.reset();
816    }
817
818    private void addUnknownMarkerSegment(ImageInputStream stream,
819                                         int code, int len)
820        throws IOException {
821        if(!unknownPresent) {
822            unknownPresent = true;
823            markerTags = new ArrayList(1);
824            unknownData = new ArrayList(1);
825        }
826        markerTags.add(new Integer(code));
827        byte[] b = new byte[len];
828        stream.readFully(b);
829        unknownData.add(b);
830        markers.add(new Integer(UNKNOWN_MARKER));
831    }
832
833    public boolean isReadOnly() {
834        return isReadOnly;
835    }
836
837    public Node getAsTree(String formatName) {
838        if (formatName.equals(nativeMetadataFormatName)) {
839            return getNativeTree();
840        } else if (formatName.equals
841                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
842            return getStandardTree();
843        } else if(formatName.equals(TIFF_FORMAT)) {
844            return getTIFFTree();
845        } else {
846            throw new IllegalArgumentException("Not a recognized format!");
847        }
848    }
849
850    public void mergeTree(String formatName, Node root)
851        throws IIOInvalidTreeException {
852        if(isReadOnly) {
853            throw new IllegalStateException("isReadOnly() == true!");
854        }
855    }
856
857    public void reset() {
858        if(isReadOnly) {
859            throw new IllegalStateException("isReadOnly() == true!");
860        }
861    }
862
863    // Native tree method.
864
865    private Node getNativeTree() {
866        int jfxxIndex = 0;
867        int dqtIndex = 0;
868        int dhtIndex = 0;
869        int comIndex = 0;
870        int unknownIndex = 0;
871
872        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
873
874        IIOMetadataNode JPEGvariety = new IIOMetadataNode("JPEGvariety");
875        root.appendChild(JPEGvariety);
876
877        IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
878        root.appendChild(markerSequence);
879
880        IIOMetadataNode app0JFIF = null;
881        if(app0JFIFPresent || app0JFXXPresent || app2ICCPresent) {
882            app0JFIF = new IIOMetadataNode("app0JFIF");
883            app0JFIF.setAttribute("majorVersion",
884                                  Integer.toString(majorVersion));
885            app0JFIF.setAttribute("minorVersion",
886                                  Integer.toString(minorVersion));
887            app0JFIF.setAttribute("resUnits",
888                                  Integer.toString(resUnits));
889            app0JFIF.setAttribute("Xdensity",
890                                  Integer.toString(Xdensity));
891            app0JFIF.setAttribute("Ydensity",
892                                  Integer.toString(Ydensity));
893            app0JFIF.setAttribute("thumbWidth",
894                                  Integer.toString(thumbWidth));
895            app0JFIF.setAttribute("thumbHeight",
896                                  Integer.toString(thumbHeight));
897            JPEGvariety.appendChild(app0JFIF);
898        }
899
900        IIOMetadataNode JFXX = null;
901        if(app0JFXXPresent) {
902            JFXX = new IIOMetadataNode("JFXX");
903            app0JFIF.appendChild(JFXX);
904        }
905
906        Iterator markerIter = markers.iterator();
907        while(markerIter.hasNext()) {
908            int marker = ((Integer)markerIter.next()).intValue();
909            switch(marker) {
910            case APP0_JFIF:
911                // Do nothing: already handled above.
912                break;
913            case APP0_JFXX:
914                IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
915                Integer extensionCode = (Integer)extensionCodes.get(jfxxIndex);
916                app0JFXX.setAttribute("extensionCode",
917                                      extensionCode.toString());
918                IIOMetadataNode JFIFthumb = null;
919                switch(extensionCode.intValue()) {
920                case THUMBNAIL_JPEG:
921                    JFIFthumb = new IIOMetadataNode("JFIFthumbJPEG");
922                    break;
923                case THUMBNAIL_PALETTE:
924                    JFIFthumb = new IIOMetadataNode("JFIFthumbPalette");
925                    break;
926                case THUMBNAIL_RGB:
927                    JFIFthumb = new IIOMetadataNode("JFIFthumbRGB");
928                    break;
929                default:
930                    // No JFIFthumb node will be appended.
931                }
932                if(JFIFthumb != null) {
933                    IIOImage img = (IIOImage)jfxxThumbnails.get(jfxxIndex++);
934                    if(extensionCode.intValue() == THUMBNAIL_JPEG) {
935                        IIOMetadata thumbMetadata = img.getMetadata();
936                        if(thumbMetadata != null) {
937                            Node thumbTree =
938                                thumbMetadata.getAsTree(nativeMetadataFormatName);
939                            if(thumbTree instanceof IIOMetadataNode) {
940                                IIOMetadataNode elt =
941                                    (IIOMetadataNode)thumbTree;
942                                NodeList elts =
943                                    elt.getElementsByTagName("markerSequence");
944                                if(elts.getLength() > 0) {
945                                    JFIFthumb.appendChild(elts.item(0));
946                                }
947                            }
948                        }
949                    } else {
950                        BufferedImage thumb =
951                            (BufferedImage)img.getRenderedImage();
952                        JFIFthumb.setAttribute("thumbWidth",
953                                               Integer.toString(thumb.getWidth()));
954                        JFIFthumb.setAttribute("thumbHeight",
955                                               Integer.toString(thumb.getHeight()));
956                    }
957                    // Add thumbnail as a user object even though not in
958                    // metadata specification.
959                    JFIFthumb.setUserObject(img);
960                    app0JFXX.appendChild(JFIFthumb);
961                }
962                JFXX.appendChild(app0JFXX);
963                break;
964            case APP2_ICC:
965                IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
966                app2ICC.setUserObject(profile);
967                app0JFIF.appendChild(app2ICC);
968                break;
969            case DQT:
970                IIOMetadataNode dqt = new IIOMetadataNode("dqt");
971                List tables = (List)qtables.get(dqtIndex++);
972                int numTables = tables.size();
973                for(int j = 0; j < numTables; j++) {
974                    IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
975                    QTable t = (QTable)tables.get(j);
976                    dqtable.setAttribute("elementPrecision",
977                                         Integer.toString(t.elementPrecision));
978                    dqtable.setAttribute("qtableId",
979                                         Integer.toString(t.tableID));
980                    dqtable.setUserObject(t.table);
981                    dqt.appendChild(dqtable);                    
982                }
983                markerSequence.appendChild(dqt);
984                break;
985            case DHT:
986                IIOMetadataNode dht = new IIOMetadataNode("dht");
987                tables = (List)htables.get(dhtIndex++);
988                numTables = tables.size();
989                for(int j = 0; j < numTables; j++) {
990                    IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
991                    HuffmanTable t = (HuffmanTable)tables.get(j);
992                    dhtable.setAttribute("class",
993                                         Integer.toString(t.tableClass));
994                    dhtable.setAttribute("htableId",
995                                         Integer.toString(t.tableID));
996                    dhtable.setUserObject(t.table);
997                    dht.appendChild(dhtable);                    
998                }
999                markerSequence.appendChild(dht);
1000                break;
1001            case DRI:
1002                IIOMetadataNode dri = new IIOMetadataNode("dri");
1003                dri.setAttribute("interval", Integer.toString(driInterval));
1004                markerSequence.appendChild(dri);
1005                break;
1006            case COM:
1007                IIOMetadataNode com = new IIOMetadataNode("com");
1008                com.setUserObject(comments.get(comIndex++));
1009                markerSequence.appendChild(com);
1010                break;
1011            case UNKNOWN_MARKER:
1012                IIOMetadataNode unknown = new IIOMetadataNode("unknown");
1013                Integer markerTag = (Integer)markerTags.get(unknownIndex);
1014                unknown.setAttribute("MarkerTag", markerTag.toString());
1015                unknown.setUserObject(unknownData.get(unknownIndex++));
1016                markerSequence.appendChild(unknown);
1017                break;
1018            case APP14_ADOBE:
1019                IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
1020                app14Adobe.setAttribute("version", Integer.toString(version));
1021                app14Adobe.setAttribute("flags0", Integer.toString(flags0));
1022                app14Adobe.setAttribute("flags1", Integer.toString(flags1));
1023                app14Adobe.setAttribute("transform",
1024                                        Integer.toString(transform));
1025                markerSequence.appendChild(app14Adobe);
1026                break;
1027            case SOF_MARKER:
1028                IIOMetadataNode sof = new IIOMetadataNode("sof");
1029                sof.setAttribute("process", Integer.toString(sofProcess));
1030                sof.setAttribute("samplePrecision",
1031                                 Integer.toString(samplePrecision));
1032                sof.setAttribute("numLines", Integer.toString(numLines));
1033                sof.setAttribute("samplesPerLine",
1034                                 Integer.toString(samplesPerLine));
1035                sof.setAttribute("numFrameComponents",
1036                                 Integer.toString(numFrameComponents));
1037                for(int i = 0; i < numFrameComponents; i++) {
1038                    IIOMetadataNode componentSpec =
1039                        new IIOMetadataNode("componentSpec");
1040                    componentSpec.setAttribute("componentId",
1041                                               Integer.toString(componentId[i]));
1042                    componentSpec.setAttribute("HsamplingFactor",
1043                                               Integer.toString(hSamplingFactor[i]));
1044                    componentSpec.setAttribute("VsamplingFactor",
1045                                               Integer.toString(vSamplingFactor[i]));
1046                    componentSpec.setAttribute("QtableSelector",
1047                                               Integer.toString(qtableSelector[i]));
1048                    sof.appendChild(componentSpec);
1049                }
1050                markerSequence.appendChild(sof);
1051                break;
1052            case SOS:
1053                IIOMetadataNode sos = new IIOMetadataNode("sos");
1054                sos.setAttribute("numScanComponents",
1055                                 Integer.toString(numScanComponents));
1056                sos.setAttribute("startSpectralSelection",
1057                                 Integer.toString(startSpectralSelection));
1058                sos.setAttribute("endSpectralSelection",
1059                                 Integer.toString(endSpectralSelection));
1060                sos.setAttribute("approxHigh", Integer.toString(approxHigh));
1061                sos.setAttribute("approxLow", Integer.toString(approxLow));
1062                for(int i = 0; i < numScanComponents; i++) {
1063                    IIOMetadataNode scanComponentSpec =
1064                        new IIOMetadataNode("scanComponentSpec");
1065                    scanComponentSpec.setAttribute("componentSelector",
1066                                                   Integer.toString(componentSelector[i]));
1067                    scanComponentSpec.setAttribute("dcHuffTable",
1068                                                   Integer.toString(dcHuffTable[i]));
1069                    scanComponentSpec.setAttribute("acHuffTable",
1070                                                   Integer.toString(acHuffTable[i]));
1071                    sos.appendChild(scanComponentSpec);
1072                }
1073                markerSequence.appendChild(sos);
1074                break;
1075            }
1076        }
1077
1078        return root;
1079    }
1080
1081    // Standard tree node methods
1082
1083    protected IIOMetadataNode getStandardChromaNode() {
1084        if(!sofPresent) {
1085            // No image, so no chroma
1086            return null;
1087        }
1088
1089        IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
1090        IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
1091        chroma.appendChild(csType);
1092
1093        IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
1094        chroma.appendChild(numChanNode);
1095        numChanNode.setAttribute("value",
1096                                 Integer.toString(numFrameComponents));
1097
1098        // Check JFIF presence.
1099        if(app0JFIFPresent) {
1100            if(numFrameComponents == 1) {
1101                csType.setAttribute("name", "GRAY");
1102            } else {
1103                csType.setAttribute("name", "YCbCr");
1104            }
1105            return chroma;
1106        }
1107
1108        // How about an Adobe marker segment?
1109        if(app14AdobePresent){
1110            switch(transform) {
1111            case ADOBE_TRANSFORM_YCCK: // YCCK
1112                csType.setAttribute("name", "YCCK");
1113                break;
1114            case ADOBE_TRANSFORM_YCC: // YCC
1115                csType.setAttribute("name", "YCbCr");
1116                break;
1117            case ADOBE_TRANSFORM_UNKNOWN: // Unknown
1118                if(numFrameComponents == 3) {
1119                    csType.setAttribute("name", "RGB");
1120                } else if(numFrameComponents == 4) {
1121                    csType.setAttribute("name", "CMYK");
1122                }
1123                break;
1124            }
1125            return chroma;
1126        }
1127
1128        // Initially assume no opacity.
1129        hasAlpha = false;
1130
1131        // Neither marker.  Check components
1132        if(numFrameComponents < 3) {
1133            csType.setAttribute("name", "GRAY");
1134            if(numFrameComponents == 2) {
1135                hasAlpha = true;
1136            }
1137            return chroma;
1138        }
1139
1140        boolean idsAreJFIF = true;
1141
1142        for(int i = 0; i < componentId.length; i++) {
1143            int id = componentId[i];
1144            if((id < 1) || (id >= componentId.length)) {
1145                idsAreJFIF = false;
1146            }
1147        }
1148        
1149        if(idsAreJFIF) {
1150            csType.setAttribute("name", "YCbCr");
1151            if(numFrameComponents == 4) {
1152                hasAlpha = true;
1153            }
1154            return chroma;
1155        }
1156
1157        // Check against the letters
1158        if(componentId[0] == 'R' &&
1159           componentId[1] == 'G' &&
1160           componentId[2] == 'B'){
1161            csType.setAttribute("name", "RGB");
1162            if(numFrameComponents == 4 && componentId[3] == 'A') {
1163                hasAlpha = true;
1164            }
1165            return chroma;
1166        }
1167        
1168        if(componentId[0] == 'Y' &&
1169           componentId[1] == 'C' &&
1170           componentId[2] == 'c'){
1171            csType.setAttribute("name", "PhotoYCC");
1172            if(numFrameComponents == 4 &&
1173               componentId[3] == 'A') {
1174                hasAlpha = true;
1175            }            
1176            return chroma;
1177        }
1178        
1179        // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
1180        // 4-channel subsampled are YCbCrA, unsubsampled are CMYK
1181
1182        boolean subsampled = false;
1183
1184        int hfactor = hSamplingFactor[0];
1185        int vfactor = vSamplingFactor[0];
1186
1187        for(int i = 1; i < componentId.length; i++) {
1188            if(hSamplingFactor[i] != hfactor ||
1189               vSamplingFactor[i] != vfactor){
1190                subsampled = true;
1191                break;
1192            }
1193        }
1194
1195        if(subsampled) {
1196            csType.setAttribute("name", "YCbCr");
1197            if(numFrameComponents == 4) {
1198                hasAlpha = true;
1199            }
1200            return chroma;
1201        }
1202         
1203        // Not subsampled.  numFrameComponents < 3 is taken care of above
1204        if(numFrameComponents == 3) {
1205            csType.setAttribute("name", "RGB");
1206        } else {
1207            csType.setAttribute("name", "CMYK");
1208        }
1209
1210        return chroma;
1211    }
1212
1213    protected IIOMetadataNode getStandardCompressionNode() {
1214        IIOMetadataNode compression = null;
1215
1216        if(sofPresent || sosPresent) {
1217            compression = new IIOMetadataNode("Compression");
1218
1219            if(sofPresent) {
1220                // Process 55 is JPEG-LS, others are lossless JPEG.
1221                boolean isLossless =
1222                    sofProcess == 3 || sofProcess == 7 || sofProcess == 11 ||
1223                    sofProcess == 15 || sofProcess == 55;
1224
1225                // CompressionTypeName
1226                IIOMetadataNode name =
1227                    new IIOMetadataNode("CompressionTypeName");
1228                String compressionType = isLossless ?
1229                    (sofProcess == 55 ? "JPEG-LS" : "JPEG-LOSSLESS") : "JPEG";
1230                name.setAttribute("value", compressionType);
1231                compression.appendChild(name);
1232
1233                // Lossless - false
1234                IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
1235                lossless.setAttribute("value", isLossless ? "true" : "false");
1236                compression.appendChild(lossless);
1237            }
1238
1239            if(sosPresent) {
1240                IIOMetadataNode prog =
1241                    new IIOMetadataNode("NumProgressiveScans");
1242                prog.setAttribute("value", "1");
1243                compression.appendChild(prog);
1244            }
1245        }
1246
1247        return compression;
1248    }
1249
1250    protected IIOMetadataNode getStandardDimensionNode() {
1251        IIOMetadataNode dim = new IIOMetadataNode("Dimension");
1252        IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
1253        orient.setAttribute("value", "normal");
1254        dim.appendChild(orient);
1255
1256        if(app0JFIFPresent) {
1257            float aspectRatio;
1258            if(resUnits == JFIF_RESUNITS_ASPECT) {
1259                // Aspect ratio.
1260                aspectRatio = (float)Xdensity/(float)Ydensity;
1261            } else {
1262                // Density.
1263                aspectRatio = (float)Ydensity/(float)Xdensity;
1264            }
1265            IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
1266            aspect.setAttribute("value", Float.toString(aspectRatio));
1267            dim.insertBefore(aspect, orient);
1268
1269            if(resUnits != JFIF_RESUNITS_ASPECT) {
1270                // 1 == dpi, 2 == dpc
1271                float scale = (resUnits == JFIF_RESUNITS_DPI) ? 25.4F : 10.0F;
1272
1273                IIOMetadataNode horiz = 
1274                    new IIOMetadataNode("HorizontalPixelSize");
1275                horiz.setAttribute("value", 
1276                                   Float.toString(scale/Xdensity));
1277                dim.appendChild(horiz);
1278
1279                IIOMetadataNode vert = 
1280                    new IIOMetadataNode("VerticalPixelSize");
1281                vert.setAttribute("value", 
1282                                  Float.toString(scale/Ydensity));
1283                dim.appendChild(vert);
1284            }
1285        }
1286        return dim;
1287    }
1288
1289    protected IIOMetadataNode getStandardTextNode() {
1290        IIOMetadataNode text = null;
1291        if(comPresent) {
1292            text = new IIOMetadataNode("Text");
1293            Iterator iter = comments.iterator();
1294            while (iter.hasNext()) {
1295                IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1296                entry.setAttribute("keyword", "comment");
1297                byte[] data = (byte[])iter.next();
1298                try {
1299                    entry.setAttribute("value",
1300                                       new String(data, "ISO-8859-1"));
1301                } catch(UnsupportedEncodingException e) {
1302                    entry.setAttribute("value", new String(data));
1303                }
1304                text.appendChild(entry);
1305            }
1306        }
1307        return text;
1308    }
1309
1310    // This method assumes that getStandardChromaNode() has already been
1311    // called to initialize hasAlpha.
1312    protected IIOMetadataNode getStandardTransparencyNode() {
1313        IIOMetadataNode trans = null;
1314        if (hasAlpha == true) {
1315            trans = new IIOMetadataNode("Transparency");
1316            IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1317            alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1318            trans.appendChild(alpha);
1319        }
1320        return trans;
1321    }
1322
1323    // TIFF tree method
1324
1325    private Node getTIFFTree() {
1326        String metadataName = TIFF_FORMAT;
1327
1328        BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
1329
1330        TIFFDirectory dir =
1331            new TIFFDirectory(new TIFFTagSet[] {
1332                base, EXIFParentTIFFTagSet.getInstance()
1333            }, null);
1334
1335        if(sofPresent) {
1336            // sofProcess -> Compression ?
1337            int compression = BaselineTIFFTagSet.COMPRESSION_JPEG;
1338            TIFFField compressionField =
1339                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
1340                              compression);
1341            dir.addTIFFField(compressionField);
1342
1343            // samplePrecision -> BitsPerSample
1344            char[] bitsPerSample = new char[numFrameComponents];
1345            Arrays.fill(bitsPerSample, (char)(samplePrecision & 0xff));
1346            TIFFField bitsPerSampleField =
1347                new TIFFField(
1348                              base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
1349                              TIFFTag.TIFF_SHORT,
1350                              bitsPerSample.length,
1351                              bitsPerSample);
1352            dir.addTIFFField(bitsPerSampleField);
1353
1354            // numLines -> ImageLength
1355            TIFFField imageLengthField =
1356                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
1357                              numLines);
1358            dir.addTIFFField(imageLengthField);
1359
1360            // samplesPerLine -> ImageWidth
1361            TIFFField imageWidthField =
1362                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
1363                              samplesPerLine);
1364            dir.addTIFFField(imageWidthField);
1365
1366            // numFrameComponents -> SamplesPerPixel
1367            TIFFField samplesPerPixelField =
1368                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
1369                              numFrameComponents);
1370            dir.addTIFFField(samplesPerPixelField);
1371
1372            // componentId -> PhotometricInterpretation + ExtraSamples
1373            IIOMetadataNode chroma = getStandardChromaNode();
1374            if(chroma != null) {
1375                IIOMetadataNode csType =
1376                    (IIOMetadataNode)chroma.getElementsByTagName("ColorSpaceType").item(0);
1377                String name = csType.getAttribute("name");
1378                int photometricInterpretation = -1;
1379                if(name.equals("GRAY")) {
1380                    photometricInterpretation =
1381                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
1382                } else if(name.equals("YCbCr") || name.equals("PhotoYCC")) {
1383                    // NOTE: PhotoYCC -> YCbCr
1384                    photometricInterpretation =
1385                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
1386                } else if(name.equals("RGB")) {
1387                    photometricInterpretation =
1388                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
1389                } else if(name.equals("CMYK") || name.equals("YCCK")) {
1390                    // NOTE: YCCK -> CMYK
1391                    photometricInterpretation =
1392                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
1393                }
1394
1395                if(photometricInterpretation != -1) {
1396                    TIFFField photometricInterpretationField =
1397                        new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
1398                                      photometricInterpretation);
1399                    dir.addTIFFField(photometricInterpretationField);
1400                }
1401
1402                if(hasAlpha) {
1403                    char[] extraSamples =
1404                        new char[] {BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA};
1405                    TIFFField extraSamplesField =
1406                        new TIFFField(
1407                                      base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1408                                      TIFFTag.TIFF_SHORT,
1409                                      extraSamples.length,
1410                                      extraSamples);
1411                    dir.addTIFFField(extraSamplesField);
1412                }
1413            } // chroma != null
1414        } // sofPresent
1415
1416        // JFIF APP0 -> Resolution fields.
1417        if(app0JFIFPresent) {
1418            long[][] xResolution = new long[][] {{Xdensity, 1}};
1419            TIFFField XResolutionField =
1420                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1421                              TIFFTag.TIFF_RATIONAL,
1422                              1,
1423                              xResolution);
1424            dir.addTIFFField(XResolutionField);
1425
1426            long[][] yResolution = new long[][] {{Ydensity, 1}};
1427            TIFFField YResolutionField =
1428                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1429                              TIFFTag.TIFF_RATIONAL,
1430                              1,
1431                              yResolution);
1432            dir.addTIFFField(YResolutionField);
1433
1434            int resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
1435            switch(resUnits) {
1436            case JFIF_RESUNITS_ASPECT:
1437                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
1438            case JFIF_RESUNITS_DPI:
1439                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
1440                break;
1441            case JFIF_RESUNITS_DPC:
1442                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER;
1443                break;
1444            }
1445            TIFFField ResolutionUnitField =
1446                new TIFFField(base.getTag
1447                              (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1448                              resolutionUnit);
1449            dir.addTIFFField(ResolutionUnitField);
1450        }
1451
1452        // DQT + DHT -> JPEGTables.
1453        byte[] jpegTablesData = null;
1454        if(dqtPresent || dqtPresent) {
1455            // Determine length of JPEGTables data.
1456            int jpegTablesLength = 2; // SOI
1457            if(dqtPresent) {
1458                Iterator dqts = qtables.iterator();
1459                while(dqts.hasNext()) {
1460                    Iterator qtiter = ((List)dqts.next()).iterator();
1461                    while(qtiter.hasNext()) {
1462                        QTable qt = (QTable)qtiter.next();
1463                        jpegTablesLength += 4 + qt.length;
1464                    }
1465                }
1466            }
1467            if(dhtPresent) {
1468                Iterator dhts = htables.iterator();
1469                while(dhts.hasNext()) {
1470                    Iterator htiter = ((List)dhts.next()).iterator();
1471                    while(htiter.hasNext()) {
1472                        HuffmanTable ht = (HuffmanTable)htiter.next();
1473                        jpegTablesLength += 4 + ht.length;
1474                    }
1475                }
1476            }
1477            jpegTablesLength += 2; // EOI
1478
1479            // Allocate space.
1480            jpegTablesData = new byte[jpegTablesLength];
1481
1482            // SOI
1483            jpegTablesData[0] = (byte)0xff;
1484            jpegTablesData[1] = (byte)SOI;
1485            int jpoff = 2;
1486
1487            if(dqtPresent) {
1488                Iterator dqts = qtables.iterator();
1489                while(dqts.hasNext()) {
1490                    Iterator qtiter = ((List)dqts.next()).iterator();
1491                    while(qtiter.hasNext()) {
1492                        jpegTablesData[jpoff++] = (byte)0xff;
1493                        jpegTablesData[jpoff++] = (byte)DQT;
1494                        QTable qt = (QTable)qtiter.next();
1495                        int qtlength = qt.length + 2;
1496                        jpegTablesData[jpoff++] =
1497                            (byte)((qtlength & 0xff00) >> 8);
1498                        jpegTablesData[jpoff++] = (byte)(qtlength & 0xff);
1499                        jpegTablesData[jpoff++] =
1500                            (byte)(((qt.elementPrecision & 0xf0) << 4) |
1501                                   (qt.tableID & 0x0f));
1502                        int[] table = qt.table.getTable();
1503                        int qlen = table.length;
1504                        for(int i = 0; i < qlen; i++) {
1505                            jpegTablesData[jpoff + zigzag[i]] = (byte)table[i];
1506                        }
1507                        jpoff += qlen;
1508                    }
1509                }
1510            }
1511
1512            if(dhtPresent) {
1513                Iterator dhts = htables.iterator();
1514                while(dhts.hasNext()) {
1515                    Iterator htiter = ((List)dhts.next()).iterator();
1516                    while(htiter.hasNext()) {
1517                        jpegTablesData[jpoff++] = (byte)0xff;
1518                        jpegTablesData[jpoff++] = (byte)DHT;
1519                        HuffmanTable ht = (HuffmanTable)htiter.next();
1520                        int htlength = ht.length + 2;
1521                        jpegTablesData[jpoff++] =
1522                            (byte)((htlength & 0xff00) >> 8);
1523                        jpegTablesData[jpoff++] = (byte)(htlength & 0xff);
1524                        jpegTablesData[jpoff++] =
1525                            (byte)(((ht.tableClass & 0x0f) << 4) |
1526                                   (ht.tableID & 0x0f));
1527                        short[] lengths = ht.table.getLengths();
1528                        int numLengths = lengths.length;
1529                        for(int i = 0; i < numLengths; i++) {
1530                            jpegTablesData[jpoff++] = (byte)lengths[i];
1531                        }
1532                        short[] values = ht.table.getValues();
1533                        int numValues = values.length;
1534                        for(int i = 0; i < numValues; i++) {
1535                            jpegTablesData[jpoff++] = (byte)values[i];
1536                        }
1537                    }
1538                }
1539            }
1540
1541            jpegTablesData[jpoff++] = (byte)0xff;
1542            jpegTablesData[jpoff] = (byte)EOI;            
1543        }
1544        if(jpegTablesData != null) {
1545            TIFFField JPEGTablesField =
1546                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
1547                              TIFFTag.TIFF_UNDEFINED,
1548                              jpegTablesData.length,
1549                              jpegTablesData);
1550            dir.addTIFFField(JPEGTablesField);
1551        }
1552
1553        IIOMetadata tiffMetadata = dir.getAsMetadata();
1554
1555        if(exifData != null) {
1556            try {
1557                Iterator tiffReaders =
1558                    ImageIO.getImageReadersByFormatName("TIFF");
1559                if(tiffReaders != null && tiffReaders.hasNext()) {
1560                    ImageReader tiffReader = (ImageReader)tiffReaders.next();
1561                    ByteArrayInputStream bais =
1562                        new ByteArrayInputStream(exifData);
1563                    ImageInputStream exifStream =
1564                        new MemoryCacheImageInputStream(bais);
1565                    tiffReader.setInput(exifStream);
1566                    IIOMetadata exifMetadata = tiffReader.getImageMetadata(0);
1567                    tiffMetadata.mergeTree(metadataName,
1568                                           exifMetadata.getAsTree(metadataName));
1569                    tiffReader.reset();
1570                }
1571            } catch(IOException ioe) {
1572                // Ignore it.
1573            }
1574        }
1575
1576        return tiffMetadata.getAsTree(metadataName);
1577    }
1578
1579    // Thumbnail methods
1580
1581    private void initializeThumbnails() {
1582        synchronized(thumbnails) {
1583            if(!thumbnailsInitialized) {
1584                // JFIF/JFXX are not supposed to coexist in the same
1585                // JPEG stream but in reality sometimes they do.
1586
1587                // JFIF thumbnail
1588                if(app0JFIFPresent && jfifThumbnail != null) {
1589                    thumbnails.add(jfifThumbnail);
1590                }
1591
1592                // JFXX thumbnail(s)
1593                if(app0JFXXPresent && jfxxThumbnails != null) {
1594                    int numJFXX = jfxxThumbnails.size();
1595                    for(int i = 0; i < numJFXX; i++) {
1596                        IIOImage img = (IIOImage)jfxxThumbnails.get(i);
1597                        BufferedImage jfxxThumbnail =
1598                            (BufferedImage)img.getRenderedImage();
1599                        thumbnails.add(jfxxThumbnail);
1600                    }
1601                }
1602
1603                // EXIF thumbnail
1604                if(exifData != null) {
1605                    try {
1606                        Iterator tiffReaders =
1607                            ImageIO.getImageReadersByFormatName("TIFF");
1608                        if(tiffReaders != null && tiffReaders.hasNext()) {
1609                            ImageReader tiffReader =
1610                                (ImageReader)tiffReaders.next();
1611                            ByteArrayInputStream bais =
1612                                new ByteArrayInputStream(exifData);
1613                            ImageInputStream exifStream =
1614                                new MemoryCacheImageInputStream(bais);
1615                            tiffReader.setInput(exifStream);
1616                            if(tiffReader.getNumImages(true) > 1) {
1617                                BufferedImage exifThumbnail =
1618                                    tiffReader.read(1, null);
1619                                thumbnails.add(exifThumbnail);
1620                            }
1621                            tiffReader.reset();
1622                        }
1623                    } catch(IOException ioe) {
1624                        // Ignore it.
1625                    }
1626                }
1627
1628                thumbnailsInitialized = true;
1629            } // if(!thumbnailsInitialized)
1630        } // sychronized
1631    }
1632
1633    int getNumThumbnails() throws IOException {
1634        initializeThumbnails();
1635        return thumbnails.size();
1636    }
1637
1638    BufferedImage getThumbnail(int thumbnailIndex) throws IOException {
1639        if(thumbnailIndex < 0) {
1640            throw new IndexOutOfBoundsException("thumbnailIndex < 0!");
1641        }
1642
1643        initializeThumbnails();
1644
1645        if(thumbnailIndex >= thumbnails.size()) {
1646            throw new IndexOutOfBoundsException
1647                ("thumbnailIndex > getNumThumbnails()");
1648        }
1649
1650        return (BufferedImage)thumbnails.get(thumbnailIndex);
1651    }
1652}