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