All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jaiimageio.impl.plugins.jpeg.CLibJPEGMetadata Maven / Gradle / Ivy

Go to download

Java Advanced Imaging Image I/O Tools API core, but without the classes involved with javax.media.jai dependencies, JPEG2000 or codecLibJIIO, meaning that this library can be distributed under the modified BSD license and should be GPL compatible.

The newest version!
/*
 * $RCSfile: CLibJPEGMetadata.java,v $
 *
 * 
 * Copyright (c) 2006 Sun Microsystems, Inc. All  Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 * 
 * - Redistribution of source code must retain the above copyright 
 *   notice, this  list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any 
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES. 
 * 
 * You acknowledge that this software is not designed or intended for 
 * use in the design, construction, operation or maintenance of any 
 * nuclear facility. 
 *
 * $Revision: 1.7 $
 * $Date: 2007/08/28 18:45:53 $
 * $State: Exp $
 */

package com.github.jaiimageio.impl.plugins.jpeg;

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
import com.github.jaiimageio.plugins.tiff.EXIFParentTIFFTagSet;
import com.github.jaiimageio.plugins.tiff.TIFFDirectory;
import com.github.jaiimageio.plugins.tiff.TIFFField;
import com.github.jaiimageio.plugins.tiff.TIFFTag;
import com.github.jaiimageio.plugins.tiff.TIFFTagSet;

public class CLibJPEGMetadata extends IIOMetadata {
    // --- Constants ---

    static final String NATIVE_FORMAT = "javax_imageio_jpeg_image_1.0";
    // XXX Reference to a non-API J2SE class:
    static final String NATIVE_FORMAT_CLASS =
        "com.sun.imageio.plugins.jpeg.JPEGImageMetadataFormat";

    static final String TIFF_FORMAT =
        "com_sun_media_imageio_plugins_tiff_image_1.0";
    static final String TIFF_FORMAT_CLASS =
        "com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadataFormat";

    // Marker codes from J2SE in numerically increasing order.

    /** For temporary use in arithmetic coding */
    static final int TEM = 0x01;

    // Codes 0x02 - 0xBF are reserved

    // SOF markers for Nondifferential Huffman coding
    /** Baseline DCT */
    static final int SOF0 = 0xC0;
    /** Extended Sequential DCT */
    static final int SOF1 = 0xC1;
    /** Progressive DCT */
    static final int SOF2 = 0xC2;
    /** Lossless Sequential */
    static final int SOF3 = 0xC3;

    /** Define Huffman Tables */
    static final int DHT = 0xC4;    

    // SOF markers for Differential Huffman coding
    /** Differential Sequential DCT */
    static final int SOF5 = 0xC5;
    /** Differential Progressive DCT */
    static final int SOF6 = 0xC6;
    /** Differential Lossless */
    static final int SOF7 = 0xC7;

    /** Reserved for JPEG extensions */
    static final int JPG = 0xC8;

    // SOF markers for Nondifferential arithmetic coding
    /** Extended Sequential DCT, Arithmetic coding */
    static final int SOF9 = 0xC9;
    /** Progressive DCT, Arithmetic coding */
    static final int SOF10 = 0xCA;
    /** Lossless Sequential, Arithmetic coding */
    static final int SOF11 = 0xCB;

    /** Define Arithmetic conditioning tables */
    static final int DAC = 0xCC;

    // SOF markers for Differential arithmetic coding
    /** Differential Sequential DCT, Arithmetic coding */
    static final int SOF13 = 0xCD;
    /** Differential Progressive DCT, Arithmetic coding */
    static final int SOF14 = 0xCE;
    /** Differential Lossless, Arithmetic coding */
    static final int SOF15 = 0xCF;

    // Restart Markers
    static final int RST0 = 0xD0;
    static final int RST1 = 0xD1;
    static final int RST2 = 0xD2;
    static final int RST3 = 0xD3;
    static final int RST4 = 0xD4;
    static final int RST5 = 0xD5;
    static final int RST6 = 0xD6;
    static final int RST7 = 0xD7;
    /** Number of restart markers */
    static final int RESTART_RANGE = 8;

    /** Start of Image */
    static final int SOI = 0xD8;
    /** End of Image */
    static final int EOI = 0xD9;
    /** Start of Scan */
    static final int SOS = 0xDA;

    /** Define Quantisation Tables */
    static final int DQT = 0xDB;

    /** Define Number of lines */
    static final int DNL = 0xDC;

    /** Define Restart Interval */
    static final int DRI = 0xDD;

    /** Define Heirarchical progression */
    static final int DHP = 0xDE;

    /** Expand reference image(s) */
    static final int EXP = 0xDF;

    // Application markers
    /** APP0 used by JFIF */
    static final int APP0 = 0xE0;
    static final int APP1 = 0xE1;
    static final int APP2 = 0xE2;
    static final int APP3 = 0xE3;
    static final int APP4 = 0xE4;
    static final int APP5 = 0xE5;
    static final int APP6 = 0xE6;
    static final int APP7 = 0xE7;
    static final int APP8 = 0xE8;
    static final int APP9 = 0xE9;
    static final int APP10 = 0xEA;
    static final int APP11 = 0xEB;
    static final int APP12 = 0xEC;
    static final int APP13 = 0xED;
    /** APP14 used by Adobe */
    static final int APP14 = 0xEE;
    static final int APP15 = 0xEF;

    // codes 0xF0 to 0xFD are reserved

    /** Comment marker */
    static final int COM = 0xFE;

    // Marker codes for JPEG-LS

    /** JPEG-LS SOF marker */
    // This was SOF48 in an earlier revision of the JPEG-LS specification.
    // "55" is the numerical value of SOF55 - SOF0 (= 247 - 192).
    static final int SOF55 = 0xF7;

    /** JPEG-LS parameters */
    static final int LSE = 0xF2;

    // Min and max APPn codes.
    static final int APPN_MIN = APP0;
    static final int APPN_MAX = APP15;

    // Min and max contiguous SOFn codes.
    static final int SOFN_MIN = SOF0;
    static final int SOFN_MAX = SOF15;

    // Min and Max RSTn codes.
    static final int RST_MIN = RST0;
    static final int RST_MAX = RST7;

    // Specific segment types defined as (code << 8) | X.
    static final int APP0_JFIF   = (APP0 << 8) | 0;
    static final int APP0_JFXX   = (APP0 << 8) | 1;
    static final int APP1_EXIF   = (APP1 << 8) | 0;
    static final int APP2_ICC    = (APP2 << 8) | 0;
    static final int APP14_ADOBE = (APP14 << 8) | 0;
    static final int UNKNOWN_MARKER = 0xffff;
    static final int SOF_MARKER = (SOF0 << 8) | 0;

    // Resolution unit types.
    static final int JFIF_RESUNITS_ASPECT = 0;
    static final int JFIF_RESUNITS_DPI = 1;
    static final int JFIF_RESUNITS_DPC = 2;

    // Thumbnail types
    static final int THUMBNAIL_JPEG = 0x10;
    static final int THUMBNAIL_PALETTE = 0x11;
    static final int THUMBNAIL_RGB = 0x12;

    // Adobe transform type.
    static final int ADOBE_TRANSFORM_UNKNOWN = 0;
    static final int ADOBE_TRANSFORM_YCC = 1;
    static final int ADOBE_TRANSFORM_YCCK = 2;

    // Zig-zag to natural re-ordering array.
    static final int [] zigzag = {
        0,  1,  5,  6, 14, 15, 27, 28,
        2,  4,  7, 13, 16, 26, 29, 42,
        3,  8, 12, 17, 25, 30, 41, 43,
        9, 11, 18, 24, 31, 40, 44, 53,
        10, 19, 23, 32, 39, 45, 52, 54,
        20, 22, 33, 38, 46, 51, 55, 60,
        21, 34, 37, 47, 50, 56, 59, 61,
        35, 36, 48, 49, 57, 58, 62, 63
    };

    // --- Static methods ---

    private static IIOImage getThumbnail(ImageInputStream stream, int len,
                                         int thumbnailType, int w, int h)
        throws IOException {

        IIOImage result;

        long startPos = stream.getStreamPosition();

        if(thumbnailType == THUMBNAIL_JPEG) {
            Iterator readers = ImageIO.getImageReaders(stream);
            if(readers == null || !readers.hasNext()) return null;
            ImageReader reader = (ImageReader)readers.next();
            reader.setInput(stream);
            BufferedImage image = reader.read(0, null);
            IIOMetadata metadata = null;
            try {
                metadata = reader.getImageMetadata(0);
            } catch(Exception e) {
                // Ignore it
            }
            result = new IIOImage(image, null, metadata);
        } else {
            int numBands;
            ColorModel cm;
            if(thumbnailType == THUMBNAIL_PALETTE) {
                if(len < 768 + w*h) {
                    return null;
                }

                numBands = 1;

                byte[] palette = new byte[768];
                stream.readFully(palette);
                byte[] r = new byte[256];
                byte[] g = new byte[256];
                byte[] b = new byte[256];
                for(int i = 0, off = 0; i < 256; i++) {
                    r[i] = palette[off++];
                    g[i] = palette[off++];
                    b[i] = palette[off++];
                }

                cm = new IndexColorModel(8, 256, r, g, b);
            } else {
                if(len < 3*w*h) {
                    return null;
                }

                numBands = 3;

                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                cm = new ComponentColorModel(cs, false, false,
                                             Transparency.OPAQUE,
                                             DataBuffer.TYPE_BYTE);
            }

            byte[] data = new byte[w*h*numBands];
            stream.readFully(data);
            DataBufferByte db = new DataBufferByte(data, data.length);
            WritableRaster wr =
                Raster.createInterleavedRaster(db, w, h, w*numBands, numBands,
                                               new int[] {0, 1, 2}, null);
            BufferedImage image = new BufferedImage(cm, wr, false, null);
            result = new IIOImage(image, null, null);
        }

        stream.seek(startPos + len);

        return result;
    }

    // --- Instance variables ---

    /** Whether the object may be edited. */
    private boolean isReadOnly = true;

    // APP0 JFIF marker segment parameters.
    boolean app0JFIFPresent;
    int majorVersion = 1;
    int minorVersion = 2;
    int resUnits; // (0 = aspect ratio; 1 = dots/inch; 2 = dots/cm)
    int Xdensity = 1;
    int Ydensity = 1;
    int thumbWidth = 0;
    int thumbHeight = 0;
    BufferedImage jfifThumbnail;

    // APP0 JFIF thumbnail(s).
    boolean app0JFXXPresent;
    List extensionCodes; // Integers 0x10, 0x11, 0x12
    List jfxxThumbnails; // IIOImages

    // APP2 ICC_PROFILE marker segment parameters.
    boolean app2ICCPresent;
    ICC_Profile profile = null;

    // DQT marker segment parameters.
    boolean dqtPresent;
    List qtables; // Each element is a List of QTables

    // DHT marker segment parameters.
    boolean dhtPresent;
    List htables; // Each element is a List of HuffmanTables

    // DRI marker segment parameters.
    boolean driPresent;
    int driInterval;

    // COM marker segment parameters.
    boolean comPresent;
    List comments; // byte[]s

    // Unknown marker segment parameters.
    boolean unknownPresent;
    List markerTags; // Integers
    List unknownData; // byte[] (NB: 'length' parameter is array length)

    // APP14 Adobe marker segment parameters.
    boolean app14AdobePresent;
    int version = 100;
    int flags0 = 0;
    int flags1 = 0;
    int transform; // 0 = Unknown, 1 = YCbCr, 2 = YCCK

    // SOF marker segment parameters.
    boolean sofPresent;
    int sofProcess;
    int samplePrecision = 8;
    int numLines;
    int samplesPerLine;
    int numFrameComponents;
    int[] componentId;
    int[] hSamplingFactor;
    int[] vSamplingFactor;
    int[] qtableSelector;

    // SOS marker segment parameters.
    boolean sosPresent;
    int numScanComponents;
    int[] componentSelector;
    int[] dcHuffTable;
    int[] acHuffTable;
    int startSpectralSelection;
    int endSpectralSelection;
    int approxHigh;
    int approxLow;

    // Embedded TIFF stream from EXIF segment.
    byte[] exifData = null;

    /** Marker codes in the order encountered. */
    private List markers = null; // List of Integer

    // Standard metadata variables.
    private boolean hasAlpha = false;

    // Agregated list of thumbnails: JFIF > JFXX > EXIF.
    private boolean thumbnailsInitialized = false;
    private List thumbnails = new ArrayList();

    CLibJPEGMetadata() {
        super(true, NATIVE_FORMAT, NATIVE_FORMAT_CLASS,
              new String[] {TIFF_FORMAT}, new String[] {TIFF_FORMAT_CLASS});
              
        this.isReadOnly = isReadOnly;
    }

    CLibJPEGMetadata(ImageInputStream stream)
        throws IIOException {
        this();

        try {
            initializeFromStream(stream);
        } catch(IOException e) {
            throw new IIOException("Cannot initialize JPEG metadata!", e);
        }
    }

    private class QTable {
        private static final int QTABLE_SIZE = 64;

        int elementPrecision;
        int tableID;
        JPEGQTable table;

        int length;

        QTable(ImageInputStream stream) throws IOException {
            elementPrecision = (int)stream.readBits(4);
            tableID = (int)stream.readBits(4);
            byte[] tmp = new byte[QTABLE_SIZE];
            stream.readFully(tmp);
            int[] data = new int[QTABLE_SIZE];
            for (int i = 0; i < QTABLE_SIZE; i++) {
                data[i] = tmp[zigzag[i]] & 0xff;
            }
            table = new JPEGQTable(data);
            length = data.length + 1;
        }
    }

    private class HuffmanTable {
        private static final int NUM_LENGTHS = 16;

        int tableClass;
        int tableID;
        JPEGHuffmanTable table;

        int length;

        HuffmanTable(ImageInputStream stream) throws IOException {
            tableClass = (int)stream.readBits(4);
            tableID = (int)stream.readBits(4);
            short[] lengths = new short[NUM_LENGTHS];
            for (int i = 0; i < NUM_LENGTHS; i++) {
                lengths[i] = (short)stream.read();
            }
            int numValues = 0;
            for (int i = 0; i < NUM_LENGTHS; i++) {
                numValues += lengths[i];
            }
            short[] values = new short[numValues];
            for (int i = 0; i < numValues; i++) {
                values[i] = (short)stream.read();
            }
            table = new JPEGHuffmanTable(lengths, values);

            length = 1 + NUM_LENGTHS + values.length;
        }
    }

    private synchronized void initializeFromStream(ImageInputStream iis)
        throws IOException {
        iis.mark();
        iis.setByteOrder(ByteOrder.BIG_ENDIAN);

        markers = new ArrayList();

        boolean isICCProfileValid = true;
        int numICCProfileChunks = 0;
        long[] iccProfileChunkOffsets = null;
        int[] iccProfileChunkLengths = null;

        while(true) {
            try {
                // 0xff denotes a potential marker.
                if(iis.read() == 0xff) {
                    // Get next byte.
                    int code = iis.read();

                    // Is a marker if and only if code not in {0x00, 0xff}.
                    // Continue to next marker if this is not a marker or if
                    // it is an empty marker.
                    if(code == 0x00 || code == 0xff ||
                       code == SOI || code == TEM ||
                       (code >= RST_MIN && code <= RST_MAX)) {
                        continue;
                    }

                    // If at the end, quit.
                    if(code == EOI) {
                        break;
                    }

                    // Get the content length.
                    int dataLength = iis.readUnsignedShort() - 2;

                    if(APPN_MIN <= code && code <= APPN_MAX) {
                        long pos = iis.getStreamPosition();
                        boolean appnAdded = false;

                        switch(code) {
                        case APP0:
                            if(dataLength >= 5) {
                                byte[] b = new byte[5];
                                iis.readFully(b);
                                String id = new String(b);
                                if(id.startsWith("JFIF") &&
                                   !app0JFIFPresent) {
                                    app0JFIFPresent = true;
                                    markers.add(new Integer(APP0_JFIF));
                                    majorVersion = iis.read();
                                    minorVersion = iis.read();
                                    resUnits = iis.read();
                                    Xdensity = iis.readUnsignedShort();
                                    Ydensity = iis.readUnsignedShort();
                                    thumbWidth = iis.read();
                                    thumbHeight = iis.read();
                                    if(thumbWidth > 0 && thumbHeight > 0) {
                                        IIOImage imiio =
                                            getThumbnail(iis, dataLength - 14,
                                                         THUMBNAIL_RGB,
                                                         thumbWidth,
                                                         thumbHeight);
                                        if(imiio != null) {
                                            jfifThumbnail = (BufferedImage)
                                                imiio.getRenderedImage();
                                        }
                                    }
                                    appnAdded = true;
                                } else if(id.startsWith("JFXX")) {
                                    if(!app0JFXXPresent) {
                                        extensionCodes = new ArrayList(1);
                                        jfxxThumbnails = new ArrayList(1);
                                        app0JFXXPresent = true;
                                    }
                                    markers.add(new Integer(APP0_JFXX));
                                    int extCode = iis.read();
                                    extensionCodes.add(new Integer(extCode));
                                    int w = 0, h = 0, offset = 6;
                                    if(extCode != THUMBNAIL_JPEG) {
                                        w = iis.read();
                                        h = iis.read();
                                        offset += 2;
                                    }
                                    IIOImage imiio =
                                        getThumbnail(iis, dataLength - offset,
                                                     extCode, w, h);
                                    if(imiio != null) {
                                        jfxxThumbnails.add(imiio);
                                    }
                                    appnAdded = true;
                                }
                            }
                            break;
                        case APP1:
                            if(dataLength >= 6) {
                                byte[] b = new byte[6];
                                iis.readFully(b);
                                if(b[0] == (byte)'E' &&
                                   b[1] == (byte)'x' &&
                                   b[2] == (byte)'i' &&
                                   b[3] == (byte)'f' &&
                                   b[4] == (byte)0 &&
                                   b[5] == (byte)0) {
                                    exifData = new byte[dataLength - 6];
                                    iis.readFully(exifData);
                                }
                            }
                        case APP2:
                            if(dataLength >= 12) {
                                byte[] b = new byte[12];
                                iis.readFully(b);
                                String id = new String(b);
                                if(id.startsWith("ICC_PROFILE")) {
                                    if(!isICCProfileValid) {
                                        iis.skipBytes(dataLength - 12);
                                        continue;
                                    }

                                    int chunkNum = iis.read();
                                    int numChunks = iis.read();
                                    if(numChunks == 0 ||
                                       chunkNum == 0 ||
                                       chunkNum > numChunks ||
                                       (app2ICCPresent &&
                                        (numChunks != numICCProfileChunks ||
                                         iccProfileChunkOffsets[chunkNum]
                                         != 0L))) {
                                        isICCProfileValid = false;
                                        iis.skipBytes(dataLength - 14);
                                        continue;
                                    }

                                    if(!app2ICCPresent) {
                                        app2ICCPresent = true;
                                        // Only flag one marker even though
                                        // multiple may be present.
                                        markers.add(new Integer(APP2_ICC));

                                        numICCProfileChunks = numChunks;

                                        if(numChunks == 1) {
                                            b = new byte[dataLength - 14];
                                            iis.readFully(b);
                                            profile =
                                                ICC_Profile.getInstance(b);
                                        } else {
                                            iccProfileChunkOffsets =
                                                new long[numChunks + 1];
                                            iccProfileChunkLengths =
                                                new int[numChunks + 1];
                                            iccProfileChunkOffsets[chunkNum] =
                                                iis.getStreamPosition();
                                            iccProfileChunkLengths[chunkNum] =
                                                dataLength - 14;
                                            iis.skipBytes(dataLength - 14);
                                        }
                                    } else {
                                        iccProfileChunkOffsets[chunkNum] =
                                            iis.getStreamPosition();
                                        iccProfileChunkLengths[chunkNum] =
                                            dataLength - 14;
                                        iis.skipBytes(dataLength - 14);
                                    }

                                    appnAdded = true;
                                }
                            }
                            break;
                        case APP14:
                            if(dataLength >= 5) {
                                byte[] b = new byte[5];
                                iis.readFully(b);
                                String id = new String(b);
                                if(id.startsWith("Adobe") &&
                                   !app14AdobePresent) { // Adobe segment
                                    app14AdobePresent = true;
                                    markers.add(new Integer(APP14_ADOBE));
                                    version = iis.readUnsignedShort();
                                    flags0 = iis.readUnsignedShort();
                                    flags1 = iis.readUnsignedShort();
                                    transform = iis.read();
                                    iis.skipBytes(dataLength - 12);
                                    appnAdded = true;
                                }
                            }
                            break;
                        default:
                            appnAdded = false;
                            break;
                        }

                        if(!appnAdded) {
                            iis.seek(pos);
                            addUnknownMarkerSegment(iis, code, dataLength);
                        }
                    } else if(code == DQT) {
                        if(!dqtPresent) {
                            dqtPresent = true;
                            qtables = new ArrayList(1);
                        }
                        markers.add(new Integer(DQT));
                        List l = new ArrayList(1);
                        do {
                            QTable t = new QTable(iis);
                            l.add(t);
                            dataLength -= t.length;
                        } while(dataLength > 0);
                        qtables.add(l);
                    } else if(code == DHT) {
                        if(!dhtPresent) {
                            dhtPresent = true;
                            htables = new ArrayList(1);
                        }
                        markers.add(new Integer(DHT));
                        List l = new ArrayList(1);
                        do {
                            HuffmanTable t = new HuffmanTable(iis);
                            l.add(t);
                            dataLength -= t.length;
                        } while(dataLength > 0);
                        htables.add(l);
                    } else if(code == DRI) {
                        if(!driPresent) {
                            driPresent = true;
                        }
                        markers.add(new Integer(DRI));
                        driInterval = iis.readUnsignedShort();
                    } else if(code == COM) {
                        if(!comPresent) {
                            comPresent = true;
                            comments = new ArrayList(1);
                        }
                        markers.add(new Integer(COM));
                        byte[] b = new byte[dataLength];
                        iis.readFully(b);
                        comments.add(b);
                    } else if((code >= SOFN_MIN && code <= SOFN_MAX) ||
                              code == SOF55) { // SOFn
                        if(!sofPresent) {
                            sofPresent = true;
                            sofProcess = code - SOFN_MIN;
                            samplePrecision = iis.read();
                            numLines = iis.readUnsignedShort();
                            samplesPerLine = iis.readUnsignedShort();
                            numFrameComponents = iis.read();
                            componentId = new int[numFrameComponents];
                            hSamplingFactor = new int[numFrameComponents];
                            vSamplingFactor = new int[numFrameComponents];
                            qtableSelector = new int[numFrameComponents];
                            for(int i = 0; i < numFrameComponents; i++) {
                                componentId[i] = iis.read();
                                hSamplingFactor[i] = (int)iis.readBits(4);
                                vSamplingFactor[i] = (int)iis.readBits(4);
                                qtableSelector[i] = iis.read();
                            }
                            markers.add(new Integer(SOF_MARKER));
                        }
                    } else if(code == SOS) {
                        if(!sosPresent) {
                            sosPresent = true;
                            numScanComponents = iis.read();
                            componentSelector = new int[numScanComponents];
                            dcHuffTable = new int[numScanComponents];
                            acHuffTable = new int[numScanComponents];
                            for(int i = 0; i < numScanComponents; i++) {
                                componentSelector[i] = iis.read();
                                dcHuffTable[i] = (int)iis.readBits(4);
                                acHuffTable[i] = (int)iis.readBits(4);
                            }
                            startSpectralSelection = iis.read();
                            endSpectralSelection = iis.read();
                            approxHigh = (int)iis.readBits(4);
                            approxLow = (int)iis.readBits(4);
                            markers.add(new Integer(SOS));
                        }
                        break;
                    } else { // Any other marker
                        addUnknownMarkerSegment(iis, code, dataLength);
                    }
                }
            } catch(EOFException eofe) {
                // XXX Should this be caught?
                break;
            }
        }

        if(app2ICCPresent && isICCProfileValid && profile == null) {
            int profileDataLength = 0;
            for(int i = 1; i <= numICCProfileChunks; i++) {
                if(iccProfileChunkOffsets[i] == 0L) {
                    isICCProfileValid = false;
                    break;
                }
                profileDataLength += iccProfileChunkLengths[i];
            }

            if(isICCProfileValid) {
                byte[] b = new byte[profileDataLength];
                int off = 0;
                for(int i = 1; i <= numICCProfileChunks; i++) {
                    iis.seek(iccProfileChunkOffsets[i]);
                    iis.read(b, off, iccProfileChunkLengths[i]);
                    off += iccProfileChunkLengths[i];
                }

                profile = ICC_Profile.getInstance(b);
            }
        }

        iis.reset();
    }

    private void addUnknownMarkerSegment(ImageInputStream stream,
                                         int code, int len)
        throws IOException {
        if(!unknownPresent) {
            unknownPresent = true;
            markerTags = new ArrayList(1);
            unknownData = new ArrayList(1);
        }
        markerTags.add(new Integer(code));
        byte[] b = new byte[len];
        stream.readFully(b);
        unknownData.add(b);
        markers.add(new Integer(UNKNOWN_MARKER));
    }

    public boolean isReadOnly() {
        return isReadOnly;
    }

    public Node getAsTree(String formatName) {
        if (formatName.equals(nativeMetadataFormatName)) {
            return getNativeTree();
        } else if (formatName.equals
                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
            return getStandardTree();
        } else if(formatName.equals(TIFF_FORMAT)) {
            return getTIFFTree();
        } else {
            throw new IllegalArgumentException("Not a recognized format!");
        }
    }

    public void mergeTree(String formatName, Node root)
        throws IIOInvalidTreeException {
        if(isReadOnly) {
            throw new IllegalStateException("isReadOnly() == true!");
        }
    }

    public void reset() {
        if(isReadOnly) {
            throw new IllegalStateException("isReadOnly() == true!");
        }
    }

    // Native tree method.

    private Node getNativeTree() {
        int jfxxIndex = 0;
        int dqtIndex = 0;
        int dhtIndex = 0;
        int comIndex = 0;
        int unknownIndex = 0;

        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);

        IIOMetadataNode JPEGvariety = new IIOMetadataNode("JPEGvariety");
        root.appendChild(JPEGvariety);

        IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
        root.appendChild(markerSequence);

        IIOMetadataNode app0JFIF = null;
        if(app0JFIFPresent || app0JFXXPresent || app2ICCPresent) {
            app0JFIF = new IIOMetadataNode("app0JFIF");
            app0JFIF.setAttribute("majorVersion",
                                  Integer.toString(majorVersion));
            app0JFIF.setAttribute("minorVersion",
                                  Integer.toString(minorVersion));
            app0JFIF.setAttribute("resUnits",
                                  Integer.toString(resUnits));
            app0JFIF.setAttribute("Xdensity",
                                  Integer.toString(Xdensity));
            app0JFIF.setAttribute("Ydensity",
                                  Integer.toString(Ydensity));
            app0JFIF.setAttribute("thumbWidth",
                                  Integer.toString(thumbWidth));
            app0JFIF.setAttribute("thumbHeight",
                                  Integer.toString(thumbHeight));
            JPEGvariety.appendChild(app0JFIF);
        }

        IIOMetadataNode JFXX = null;
        if(app0JFXXPresent) {
            JFXX = new IIOMetadataNode("JFXX");
            app0JFIF.appendChild(JFXX);
        }

        Iterator markerIter = markers.iterator();
        while(markerIter.hasNext()) {
            int marker = ((Integer)markerIter.next()).intValue();
            switch(marker) {
            case APP0_JFIF:
                // Do nothing: already handled above.
                break;
            case APP0_JFXX:
                IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
                Integer extensionCode = (Integer)extensionCodes.get(jfxxIndex);
                app0JFXX.setAttribute("extensionCode",
                                      extensionCode.toString());
                IIOMetadataNode JFIFthumb = null;
                switch(extensionCode.intValue()) {
                case THUMBNAIL_JPEG:
                    JFIFthumb = new IIOMetadataNode("JFIFthumbJPEG");
                    break;
                case THUMBNAIL_PALETTE:
                    JFIFthumb = new IIOMetadataNode("JFIFthumbPalette");
                    break;
                case THUMBNAIL_RGB:
                    JFIFthumb = new IIOMetadataNode("JFIFthumbRGB");
                    break;
                default:
                    // No JFIFthumb node will be appended.
                }
                if(JFIFthumb != null) {
                    IIOImage img = (IIOImage)jfxxThumbnails.get(jfxxIndex++);
                    if(extensionCode.intValue() == THUMBNAIL_JPEG) {
                        IIOMetadata thumbMetadata = img.getMetadata();
                        if(thumbMetadata != null) {
                            Node thumbTree =
                                thumbMetadata.getAsTree(nativeMetadataFormatName);
                            if(thumbTree instanceof IIOMetadataNode) {
                                IIOMetadataNode elt =
                                    (IIOMetadataNode)thumbTree;
                                NodeList elts =
                                    elt.getElementsByTagName("markerSequence");
                                if(elts.getLength() > 0) {
                                    JFIFthumb.appendChild(elts.item(0));
                                }
                            }
                        }
                    } else {
                        BufferedImage thumb =
                            (BufferedImage)img.getRenderedImage();
                        JFIFthumb.setAttribute("thumbWidth",
                                               Integer.toString(thumb.getWidth()));
                        JFIFthumb.setAttribute("thumbHeight",
                                               Integer.toString(thumb.getHeight()));
                    }
                    // Add thumbnail as a user object even though not in
                    // metadata specification.
                    JFIFthumb.setUserObject(img);
                    app0JFXX.appendChild(JFIFthumb);
                }
                JFXX.appendChild(app0JFXX);
                break;
            case APP2_ICC:
                IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
                app2ICC.setUserObject(profile);
                app0JFIF.appendChild(app2ICC);
                break;
            case DQT:
                IIOMetadataNode dqt = new IIOMetadataNode("dqt");
                List tables = (List)qtables.get(dqtIndex++);
                int numTables = tables.size();
                for(int j = 0; j < numTables; j++) {
                    IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
                    QTable t = (QTable)tables.get(j);
                    dqtable.setAttribute("elementPrecision",
                                         Integer.toString(t.elementPrecision));
                    dqtable.setAttribute("qtableId",
                                         Integer.toString(t.tableID));
                    dqtable.setUserObject(t.table);
                    dqt.appendChild(dqtable);                    
                }
                markerSequence.appendChild(dqt);
                break;
            case DHT:
                IIOMetadataNode dht = new IIOMetadataNode("dht");
                tables = (List)htables.get(dhtIndex++);
                numTables = tables.size();
                for(int j = 0; j < numTables; j++) {
                    IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
                    HuffmanTable t = (HuffmanTable)tables.get(j);
                    dhtable.setAttribute("class",
                                         Integer.toString(t.tableClass));
                    dhtable.setAttribute("htableId",
                                         Integer.toString(t.tableID));
                    dhtable.setUserObject(t.table);
                    dht.appendChild(dhtable);                    
                }
                markerSequence.appendChild(dht);
                break;
            case DRI:
                IIOMetadataNode dri = new IIOMetadataNode("dri");
                dri.setAttribute("interval", Integer.toString(driInterval));
                markerSequence.appendChild(dri);
                break;
            case COM:
                IIOMetadataNode com = new IIOMetadataNode("com");
                com.setUserObject(comments.get(comIndex++));
                markerSequence.appendChild(com);
                break;
            case UNKNOWN_MARKER:
                IIOMetadataNode unknown = new IIOMetadataNode("unknown");
                Integer markerTag = (Integer)markerTags.get(unknownIndex);
                unknown.setAttribute("MarkerTag", markerTag.toString());
                unknown.setUserObject(unknownData.get(unknownIndex++));
                markerSequence.appendChild(unknown);
                break;
            case APP14_ADOBE:
                IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
                app14Adobe.setAttribute("version", Integer.toString(version));
                app14Adobe.setAttribute("flags0", Integer.toString(flags0));
                app14Adobe.setAttribute("flags1", Integer.toString(flags1));
                app14Adobe.setAttribute("transform",
                                        Integer.toString(transform));
                markerSequence.appendChild(app14Adobe);
                break;
            case SOF_MARKER:
                IIOMetadataNode sof = new IIOMetadataNode("sof");
                sof.setAttribute("process", Integer.toString(sofProcess));
                sof.setAttribute("samplePrecision",
                                 Integer.toString(samplePrecision));
                sof.setAttribute("numLines", Integer.toString(numLines));
                sof.setAttribute("samplesPerLine",
                                 Integer.toString(samplesPerLine));
                sof.setAttribute("numFrameComponents",
                                 Integer.toString(numFrameComponents));
                for(int i = 0; i < numFrameComponents; i++) {
                    IIOMetadataNode componentSpec =
                        new IIOMetadataNode("componentSpec");
                    componentSpec.setAttribute("componentId",
                                               Integer.toString(componentId[i]));
                    componentSpec.setAttribute("HsamplingFactor",
                                               Integer.toString(hSamplingFactor[i]));
                    componentSpec.setAttribute("VsamplingFactor",
                                               Integer.toString(vSamplingFactor[i]));
                    componentSpec.setAttribute("QtableSelector",
                                               Integer.toString(qtableSelector[i]));
                    sof.appendChild(componentSpec);
                }
                markerSequence.appendChild(sof);
                break;
            case SOS:
                IIOMetadataNode sos = new IIOMetadataNode("sos");
                sos.setAttribute("numScanComponents",
                                 Integer.toString(numScanComponents));
                sos.setAttribute("startSpectralSelection",
                                 Integer.toString(startSpectralSelection));
                sos.setAttribute("endSpectralSelection",
                                 Integer.toString(endSpectralSelection));
                sos.setAttribute("approxHigh", Integer.toString(approxHigh));
                sos.setAttribute("approxLow", Integer.toString(approxLow));
                for(int i = 0; i < numScanComponents; i++) {
                    IIOMetadataNode scanComponentSpec =
                        new IIOMetadataNode("scanComponentSpec");
                    scanComponentSpec.setAttribute("componentSelector",
                                                   Integer.toString(componentSelector[i]));
                    scanComponentSpec.setAttribute("dcHuffTable",
                                                   Integer.toString(dcHuffTable[i]));
                    scanComponentSpec.setAttribute("acHuffTable",
                                                   Integer.toString(acHuffTable[i]));
                    sos.appendChild(scanComponentSpec);
                }
                markerSequence.appendChild(sos);
                break;
            }
        }

        return root;
    }

    // Standard tree node methods

    protected IIOMetadataNode getStandardChromaNode() {
        if(!sofPresent) {
            // No image, so no chroma
            return null;
        }

        IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
        IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
        chroma.appendChild(csType);

        IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
        chroma.appendChild(numChanNode);
        numChanNode.setAttribute("value",
                                 Integer.toString(numFrameComponents));

        // Check JFIF presence.
        if(app0JFIFPresent) {
            if(numFrameComponents == 1) {
                csType.setAttribute("name", "GRAY");
            } else {
                csType.setAttribute("name", "YCbCr");
            }
            return chroma;
        }

        // How about an Adobe marker segment?
        if(app14AdobePresent){
            switch(transform) {
            case ADOBE_TRANSFORM_YCCK: // YCCK
                csType.setAttribute("name", "YCCK");
                break;
            case ADOBE_TRANSFORM_YCC: // YCC
                csType.setAttribute("name", "YCbCr");
                break;
            case ADOBE_TRANSFORM_UNKNOWN: // Unknown
                if(numFrameComponents == 3) {
                    csType.setAttribute("name", "RGB");
                } else if(numFrameComponents == 4) {
                    csType.setAttribute("name", "CMYK");
                }
                break;
            }
            return chroma;
        }

        // Initially assume no opacity.
        hasAlpha = false;

        // Neither marker.  Check components
        if(numFrameComponents < 3) {
            csType.setAttribute("name", "GRAY");
            if(numFrameComponents == 2) {
                hasAlpha = true;
            }
            return chroma;
        }

        boolean idsAreJFIF = true;

        for(int i = 0; i < componentId.length; i++) {
            int id = componentId[i];
            if((id < 1) || (id >= componentId.length)) {
                idsAreJFIF = false;
            }
        }
        
        if(idsAreJFIF) {
            csType.setAttribute("name", "YCbCr");
            if(numFrameComponents == 4) {
                hasAlpha = true;
            }
            return chroma;
        }

        // Check against the letters
        if(componentId[0] == 'R' &&
           componentId[1] == 'G' &&
           componentId[2] == 'B'){
            csType.setAttribute("name", "RGB");
            if(numFrameComponents == 4 && componentId[3] == 'A') {
                hasAlpha = true;
            }
            return chroma;
        }
        
        if(componentId[0] == 'Y' &&
           componentId[1] == 'C' &&
           componentId[2] == 'c'){
            csType.setAttribute("name", "PhotoYCC");
            if(numFrameComponents == 4 &&
               componentId[3] == 'A') {
                hasAlpha = true;
            }            
            return chroma;
        }
        
        // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
        // 4-channel subsampled are YCbCrA, unsubsampled are CMYK

        boolean subsampled = false;

        int hfactor = hSamplingFactor[0];
        int vfactor = vSamplingFactor[0];

        for(int i = 1; i < componentId.length; i++) {
            if(hSamplingFactor[i] != hfactor ||
               vSamplingFactor[i] != vfactor){
                subsampled = true;
                break;
            }
        }

        if(subsampled) {
            csType.setAttribute("name", "YCbCr");
            if(numFrameComponents == 4) {
                hasAlpha = true;
            }
            return chroma;
        }
         
        // Not subsampled.  numFrameComponents < 3 is taken care of above
        if(numFrameComponents == 3) {
            csType.setAttribute("name", "RGB");
        } else {
            csType.setAttribute("name", "CMYK");
        }

        return chroma;
    }

    protected IIOMetadataNode getStandardCompressionNode() {
        IIOMetadataNode compression = null;

        if(sofPresent || sosPresent) {
            compression = new IIOMetadataNode("Compression");

            if(sofPresent) {
                // Process 55 is JPEG-LS, others are lossless JPEG.
                boolean isLossless =
                    sofProcess == 3 || sofProcess == 7 || sofProcess == 11 ||
                    sofProcess == 15 || sofProcess == 55;

                // CompressionTypeName
                IIOMetadataNode name =
                    new IIOMetadataNode("CompressionTypeName");
                String compressionType = isLossless ?
                    (sofProcess == 55 ? "JPEG-LS" : "JPEG-LOSSLESS") : "JPEG";
                name.setAttribute("value", compressionType);
                compression.appendChild(name);

                // Lossless - false
                IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
                lossless.setAttribute("value", isLossless ? "true" : "false");
                compression.appendChild(lossless);
            }

            if(sosPresent) {
                IIOMetadataNode prog =
                    new IIOMetadataNode("NumProgressiveScans");
                prog.setAttribute("value", "1");
                compression.appendChild(prog);
            }
        }

        return compression;
    }

    protected IIOMetadataNode getStandardDimensionNode() {
        IIOMetadataNode dim = new IIOMetadataNode("Dimension");
        IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
        orient.setAttribute("value", "normal");
        dim.appendChild(orient);

        if(app0JFIFPresent) {
            float aspectRatio;
            if(resUnits == JFIF_RESUNITS_ASPECT) {
                // Aspect ratio.
                aspectRatio = (float)Xdensity/(float)Ydensity;
            } else {
                // Density.
                aspectRatio = (float)Ydensity/(float)Xdensity;
            }
            IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
            aspect.setAttribute("value", Float.toString(aspectRatio));
            dim.insertBefore(aspect, orient);

            if(resUnits != JFIF_RESUNITS_ASPECT) {
                // 1 == dpi, 2 == dpc
                float scale = (resUnits == JFIF_RESUNITS_DPI) ? 25.4F : 10.0F;

                IIOMetadataNode horiz = 
                    new IIOMetadataNode("HorizontalPixelSize");
                horiz.setAttribute("value", 
                                   Float.toString(scale/Xdensity));
                dim.appendChild(horiz);

                IIOMetadataNode vert = 
                    new IIOMetadataNode("VerticalPixelSize");
                vert.setAttribute("value", 
                                  Float.toString(scale/Ydensity));
                dim.appendChild(vert);
            }
        }
        return dim;
    }

    protected IIOMetadataNode getStandardTextNode() {
        IIOMetadataNode text = null;
        if(comPresent) {
            text = new IIOMetadataNode("Text");
            Iterator iter = comments.iterator();
            while (iter.hasNext()) {
                IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
                entry.setAttribute("keyword", "comment");
                byte[] data = (byte[])iter.next();
                try {
                    entry.setAttribute("value",
                                       new String(data, "ISO-8859-1"));
                } catch(UnsupportedEncodingException e) {
                    entry.setAttribute("value", new String(data));
                }
                text.appendChild(entry);
            }
        }
        return text;
    }

    // This method assumes that getStandardChromaNode() has already been
    // called to initialize hasAlpha.
    protected IIOMetadataNode getStandardTransparencyNode() {
        IIOMetadataNode trans = null;
        if (hasAlpha == true) {
            trans = new IIOMetadataNode("Transparency");
            IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
            alpha.setAttribute("value", "nonpremultiplied"); // Always assume
            trans.appendChild(alpha);
        }
        return trans;
    }

    // TIFF tree method

    private Node getTIFFTree() {
        String metadataName = TIFF_FORMAT;

        BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();

        TIFFDirectory dir =
            new TIFFDirectory(new TIFFTagSet[] {
                base, EXIFParentTIFFTagSet.getInstance()
            }, null);

        if(sofPresent) {
            // sofProcess -> Compression ?
            int compression = BaselineTIFFTagSet.COMPRESSION_JPEG;
            TIFFField compressionField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
                              compression);
            dir.addTIFFField(compressionField);

            // samplePrecision -> BitsPerSample
            char[] bitsPerSample = new char[numFrameComponents];
            Arrays.fill(bitsPerSample, (char)(samplePrecision & 0xff));
            TIFFField bitsPerSampleField =
                new TIFFField(
                              base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
                              TIFFTag.TIFF_SHORT,
                              bitsPerSample.length,
                              bitsPerSample);
            dir.addTIFFField(bitsPerSampleField);

            // numLines -> ImageLength
            TIFFField imageLengthField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
                              numLines);
            dir.addTIFFField(imageLengthField);

            // samplesPerLine -> ImageWidth
            TIFFField imageWidthField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
                              samplesPerLine);
            dir.addTIFFField(imageWidthField);

            // numFrameComponents -> SamplesPerPixel
            TIFFField samplesPerPixelField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
                              numFrameComponents);
            dir.addTIFFField(samplesPerPixelField);

            // componentId -> PhotometricInterpretation + ExtraSamples
            IIOMetadataNode chroma = getStandardChromaNode();
            if(chroma != null) {
                IIOMetadataNode csType =
                    (IIOMetadataNode)chroma.getElementsByTagName("ColorSpaceType").item(0);
                String name = csType.getAttribute("name");
                int photometricInterpretation = -1;
                if(name.equals("GRAY")) {
                    photometricInterpretation =
                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
                } else if(name.equals("YCbCr") || name.equals("PhotoYCC")) {
                    // NOTE: PhotoYCC -> YCbCr
                    photometricInterpretation =
                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
                } else if(name.equals("RGB")) {
                    photometricInterpretation =
                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
                } else if(name.equals("CMYK") || name.equals("YCCK")) {
                    // NOTE: YCCK -> CMYK
                    photometricInterpretation =
                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
                }

                if(photometricInterpretation != -1) {
                    TIFFField photometricInterpretationField =
                        new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
                                      photometricInterpretation);
                    dir.addTIFFField(photometricInterpretationField);
                }

                if(hasAlpha) {
                    char[] extraSamples =
                        new char[] {BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA};
                    TIFFField extraSamplesField =
                        new TIFFField(
                                      base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
                                      TIFFTag.TIFF_SHORT,
                                      extraSamples.length,
                                      extraSamples);
                    dir.addTIFFField(extraSamplesField);
                }
            } // chroma != null
        } // sofPresent

        // JFIF APP0 -> Resolution fields.
        if(app0JFIFPresent) {
            long[][] xResolution = new long[][] {{Xdensity, 1}};
            TIFFField XResolutionField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
                              TIFFTag.TIFF_RATIONAL,
                              1,
                              xResolution);
            dir.addTIFFField(XResolutionField);

            long[][] yResolution = new long[][] {{Ydensity, 1}};
            TIFFField YResolutionField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
                              TIFFTag.TIFF_RATIONAL,
                              1,
                              yResolution);
            dir.addTIFFField(YResolutionField);

            int resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
            switch(resUnits) {
            case JFIF_RESUNITS_ASPECT:
                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
            case JFIF_RESUNITS_DPI:
                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
                break;
            case JFIF_RESUNITS_DPC:
                resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER;
                break;
            }
            TIFFField ResolutionUnitField =
                new TIFFField(base.getTag
                              (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
                              resolutionUnit);
            dir.addTIFFField(ResolutionUnitField);
        }

        // DQT + DHT -> JPEGTables.
        byte[] jpegTablesData = null;
        if(dqtPresent || dqtPresent) {
            // Determine length of JPEGTables data.
            int jpegTablesLength = 2; // SOI
            if(dqtPresent) {
                Iterator dqts = qtables.iterator();
                while(dqts.hasNext()) {
                    Iterator qtiter = ((List)dqts.next()).iterator();
                    while(qtiter.hasNext()) {
                        QTable qt = (QTable)qtiter.next();
                        jpegTablesLength += 4 + qt.length;
                    }
                }
            }
            if(dhtPresent) {
                Iterator dhts = htables.iterator();
                while(dhts.hasNext()) {
                    Iterator htiter = ((List)dhts.next()).iterator();
                    while(htiter.hasNext()) {
                        HuffmanTable ht = (HuffmanTable)htiter.next();
                        jpegTablesLength += 4 + ht.length;
                    }
                }
            }
            jpegTablesLength += 2; // EOI

            // Allocate space.
            jpegTablesData = new byte[jpegTablesLength];

            // SOI
            jpegTablesData[0] = (byte)0xff;
            jpegTablesData[1] = (byte)SOI;
            int jpoff = 2;

            if(dqtPresent) {
                Iterator dqts = qtables.iterator();
                while(dqts.hasNext()) {
                    Iterator qtiter = ((List)dqts.next()).iterator();
                    while(qtiter.hasNext()) {
                        jpegTablesData[jpoff++] = (byte)0xff;
                        jpegTablesData[jpoff++] = (byte)DQT;
                        QTable qt = (QTable)qtiter.next();
                        int qtlength = qt.length + 2;
                        jpegTablesData[jpoff++] =
                            (byte)((qtlength & 0xff00) >> 8);
                        jpegTablesData[jpoff++] = (byte)(qtlength & 0xff);
                        jpegTablesData[jpoff++] =
                            (byte)(((qt.elementPrecision & 0xf0) << 4) |
                                   (qt.tableID & 0x0f));
                        int[] table = qt.table.getTable();
                        int qlen = table.length;
                        for(int i = 0; i < qlen; i++) {
                            jpegTablesData[jpoff + zigzag[i]] = (byte)table[i];
                        }
                        jpoff += qlen;
                    }
                }
            }

            if(dhtPresent) {
                Iterator dhts = htables.iterator();
                while(dhts.hasNext()) {
                    Iterator htiter = ((List)dhts.next()).iterator();
                    while(htiter.hasNext()) {
                        jpegTablesData[jpoff++] = (byte)0xff;
                        jpegTablesData[jpoff++] = (byte)DHT;
                        HuffmanTable ht = (HuffmanTable)htiter.next();
                        int htlength = ht.length + 2;
                        jpegTablesData[jpoff++] =
                            (byte)((htlength & 0xff00) >> 8);
                        jpegTablesData[jpoff++] = (byte)(htlength & 0xff);
                        jpegTablesData[jpoff++] =
                            (byte)(((ht.tableClass & 0x0f) << 4) |
                                   (ht.tableID & 0x0f));
                        short[] lengths = ht.table.getLengths();
                        int numLengths = lengths.length;
                        for(int i = 0; i < numLengths; i++) {
                            jpegTablesData[jpoff++] = (byte)lengths[i];
                        }
                        short[] values = ht.table.getValues();
                        int numValues = values.length;
                        for(int i = 0; i < numValues; i++) {
                            jpegTablesData[jpoff++] = (byte)values[i];
                        }
                    }
                }
            }

            jpegTablesData[jpoff++] = (byte)0xff;
            jpegTablesData[jpoff] = (byte)EOI;            
        }
        if(jpegTablesData != null) {
            TIFFField JPEGTablesField =
                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
                              TIFFTag.TIFF_UNDEFINED,
                              jpegTablesData.length,
                              jpegTablesData);
            dir.addTIFFField(JPEGTablesField);
        }

        IIOMetadata tiffMetadata = dir.getAsMetadata();

        if(exifData != null) {
            try {
                Iterator tiffReaders =
                    ImageIO.getImageReadersByFormatName("TIFF");
                if(tiffReaders != null && tiffReaders.hasNext()) {
                    ImageReader tiffReader = (ImageReader)tiffReaders.next();
                    ByteArrayInputStream bais =
                        new ByteArrayInputStream(exifData);
                    ImageInputStream exifStream =
                        new MemoryCacheImageInputStream(bais);
                    tiffReader.setInput(exifStream);
                    IIOMetadata exifMetadata = tiffReader.getImageMetadata(0);
                    tiffMetadata.mergeTree(metadataName,
                                           exifMetadata.getAsTree(metadataName));
                    tiffReader.reset();
                }
            } catch(IOException ioe) {
                // Ignore it.
            }
        }

        return tiffMetadata.getAsTree(metadataName);
    }

    // Thumbnail methods

    private void initializeThumbnails() {
        synchronized(thumbnails) {
            if(!thumbnailsInitialized) {
                // JFIF/JFXX are not supposed to coexist in the same
                // JPEG stream but in reality sometimes they do.

                // JFIF thumbnail
                if(app0JFIFPresent && jfifThumbnail != null) {
                    thumbnails.add(jfifThumbnail);
                }

                // JFXX thumbnail(s)
                if(app0JFXXPresent && jfxxThumbnails != null) {
                    int numJFXX = jfxxThumbnails.size();
                    for(int i = 0; i < numJFXX; i++) {
                        IIOImage img = (IIOImage)jfxxThumbnails.get(i);
                        BufferedImage jfxxThumbnail =
                            (BufferedImage)img.getRenderedImage();
                        thumbnails.add(jfxxThumbnail);
                    }
                }

                // EXIF thumbnail
                if(exifData != null) {
                    try {
                        Iterator tiffReaders =
                            ImageIO.getImageReadersByFormatName("TIFF");
                        if(tiffReaders != null && tiffReaders.hasNext()) {
                            ImageReader tiffReader =
                                (ImageReader)tiffReaders.next();
                            ByteArrayInputStream bais =
                                new ByteArrayInputStream(exifData);
                            ImageInputStream exifStream =
                                new MemoryCacheImageInputStream(bais);
                            tiffReader.setInput(exifStream);
                            if(tiffReader.getNumImages(true) > 1) {
                                BufferedImage exifThumbnail =
                                    tiffReader.read(1, null);
                                thumbnails.add(exifThumbnail);
                            }
                            tiffReader.reset();
                        }
                    } catch(IOException ioe) {
                        // Ignore it.
                    }
                }

                thumbnailsInitialized = true;
            } // if(!thumbnailsInitialized)
        } // sychronized
    }

    int getNumThumbnails() throws IOException {
        initializeThumbnails();
        return thumbnails.size();
    }

    BufferedImage getThumbnail(int thumbnailIndex) throws IOException {
        if(thumbnailIndex < 0) {
            throw new IndexOutOfBoundsException("thumbnailIndex < 0!");
        }

        initializeThumbnails();

        if(thumbnailIndex >= thumbnails.size()) {
            throw new IndexOutOfBoundsException
                ("thumbnailIndex > getNumThumbnails()");
        }

        return (BufferedImage)thumbnails.get(thumbnailIndex);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy