0001: /*
0002: * $RCSfile: CLibJPEGMetadata.java,v $
0003: *
0004: *
0005: * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * - Redistribution of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * - Redistribution in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * Neither the name of Sun Microsystems, Inc. or the names of
0020: * contributors may be used to endorse or promote products derived
0021: * from this software without specific prior written permission.
0022: *
0023: * This software is provided "AS IS," without a warranty of any
0024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0035: * POSSIBILITY OF SUCH DAMAGES.
0036: *
0037: * You acknowledge that this software is not designed or intended for
0038: * use in the design, construction, operation or maintenance of any
0039: * nuclear facility.
0040: *
0041: * $Revision: 1.7 $
0042: * $Date: 2007/08/28 18:45:53 $
0043: * $State: Exp $
0044: */
0045:
0046: package com.sun.media.imageioimpl.plugins.jpeg;
0047:
0048: import java.awt.Dimension;
0049: import java.awt.Transparency;
0050: import java.awt.color.ColorSpace;
0051: import java.awt.color.ICC_Profile;
0052: import java.awt.image.BufferedImage;
0053: import java.awt.image.ColorModel;
0054: import java.awt.image.ComponentColorModel;
0055: import java.awt.image.DataBuffer;
0056: import java.awt.image.DataBufferByte;
0057: import java.awt.image.IndexColorModel;
0058: import java.awt.image.Raster;
0059: import java.awt.image.RenderedImage;
0060: import java.awt.image.SampleModel;
0061: import java.awt.image.WritableRaster;
0062: import java.io.ByteArrayInputStream;
0063: import java.io.EOFException;
0064: import java.io.IOException;
0065: import java.io.UnsupportedEncodingException;
0066: import java.nio.ByteOrder;
0067: import java.util.ArrayList;
0068: import java.util.Arrays;
0069: import java.util.Iterator;
0070: import java.util.List;
0071: import java.util.Map;
0072: import java.util.SortedMap;
0073: import java.util.TreeMap;
0074: import javax.imageio.IIOException;
0075: import javax.imageio.IIOImage;
0076: import javax.imageio.ImageIO;
0077: import javax.imageio.ImageReader;
0078: import javax.imageio.ImageTypeSpecifier;
0079: import javax.imageio.metadata.IIOMetadata;
0080: import javax.imageio.metadata.IIOMetadataFormatImpl;
0081: import javax.imageio.metadata.IIOMetadataNode;
0082: import javax.imageio.metadata.IIOInvalidTreeException;
0083: import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
0084: import javax.imageio.plugins.jpeg.JPEGQTable;
0085: import javax.imageio.stream.ImageInputStream;
0086: import javax.imageio.stream.MemoryCacheImageInputStream;
0087: import org.w3c.dom.Node;
0088: import org.w3c.dom.NodeList;
0089: import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet;
0090: import com.sun.media.imageio.plugins.tiff.EXIFGPSTagSet;
0091: import com.sun.media.imageio.plugins.tiff.EXIFInteroperabilityTagSet;
0092: import com.sun.media.imageio.plugins.tiff.EXIFParentTIFFTagSet;
0093: import com.sun.media.imageio.plugins.tiff.EXIFTIFFTagSet;
0094: import com.sun.media.imageio.plugins.tiff.TIFFDirectory;
0095: import com.sun.media.imageio.plugins.tiff.TIFFField;
0096: import com.sun.media.imageio.plugins.tiff.TIFFTag;
0097: import com.sun.media.imageio.plugins.tiff.TIFFTagSet;
0098:
0099: public class CLibJPEGMetadata extends IIOMetadata {
0100: // --- Constants ---
0101:
0102: static final String NATIVE_FORMAT = "javax_imageio_jpeg_image_1.0";
0103: // XXX Reference to a non-API J2SE class:
0104: static final String NATIVE_FORMAT_CLASS = "com.sun.imageio.plugins.jpeg.JPEGImageMetadataFormat";
0105:
0106: static final String TIFF_FORMAT = "com_sun_media_imageio_plugins_tiff_image_1.0";
0107: static final String TIFF_FORMAT_CLASS = "com.sun.media.imageioimpl.plugins.tiff.TIFFImageMetadataFormat";
0108:
0109: // Marker codes from J2SE in numerically increasing order.
0110:
0111: /** For temporary use in arithmetic coding */
0112: static final int TEM = 0x01;
0113:
0114: // Codes 0x02 - 0xBF are reserved
0115:
0116: // SOF markers for Nondifferential Huffman coding
0117: /** Baseline DCT */
0118: static final int SOF0 = 0xC0;
0119: /** Extended Sequential DCT */
0120: static final int SOF1 = 0xC1;
0121: /** Progressive DCT */
0122: static final int SOF2 = 0xC2;
0123: /** Lossless Sequential */
0124: static final int SOF3 = 0xC3;
0125:
0126: /** Define Huffman Tables */
0127: static final int DHT = 0xC4;
0128:
0129: // SOF markers for Differential Huffman coding
0130: /** Differential Sequential DCT */
0131: static final int SOF5 = 0xC5;
0132: /** Differential Progressive DCT */
0133: static final int SOF6 = 0xC6;
0134: /** Differential Lossless */
0135: static final int SOF7 = 0xC7;
0136:
0137: /** Reserved for JPEG extensions */
0138: static final int JPG = 0xC8;
0139:
0140: // SOF markers for Nondifferential arithmetic coding
0141: /** Extended Sequential DCT, Arithmetic coding */
0142: static final int SOF9 = 0xC9;
0143: /** Progressive DCT, Arithmetic coding */
0144: static final int SOF10 = 0xCA;
0145: /** Lossless Sequential, Arithmetic coding */
0146: static final int SOF11 = 0xCB;
0147:
0148: /** Define Arithmetic conditioning tables */
0149: static final int DAC = 0xCC;
0150:
0151: // SOF markers for Differential arithmetic coding
0152: /** Differential Sequential DCT, Arithmetic coding */
0153: static final int SOF13 = 0xCD;
0154: /** Differential Progressive DCT, Arithmetic coding */
0155: static final int SOF14 = 0xCE;
0156: /** Differential Lossless, Arithmetic coding */
0157: static final int SOF15 = 0xCF;
0158:
0159: // Restart Markers
0160: static final int RST0 = 0xD0;
0161: static final int RST1 = 0xD1;
0162: static final int RST2 = 0xD2;
0163: static final int RST3 = 0xD3;
0164: static final int RST4 = 0xD4;
0165: static final int RST5 = 0xD5;
0166: static final int RST6 = 0xD6;
0167: static final int RST7 = 0xD7;
0168: /** Number of restart markers */
0169: static final int RESTART_RANGE = 8;
0170:
0171: /** Start of Image */
0172: static final int SOI = 0xD8;
0173: /** End of Image */
0174: static final int EOI = 0xD9;
0175: /** Start of Scan */
0176: static final int SOS = 0xDA;
0177:
0178: /** Define Quantisation Tables */
0179: static final int DQT = 0xDB;
0180:
0181: /** Define Number of lines */
0182: static final int DNL = 0xDC;
0183:
0184: /** Define Restart Interval */
0185: static final int DRI = 0xDD;
0186:
0187: /** Define Heirarchical progression */
0188: static final int DHP = 0xDE;
0189:
0190: /** Expand reference image(s) */
0191: static final int EXP = 0xDF;
0192:
0193: // Application markers
0194: /** APP0 used by JFIF */
0195: static final int APP0 = 0xE0;
0196: static final int APP1 = 0xE1;
0197: static final int APP2 = 0xE2;
0198: static final int APP3 = 0xE3;
0199: static final int APP4 = 0xE4;
0200: static final int APP5 = 0xE5;
0201: static final int APP6 = 0xE6;
0202: static final int APP7 = 0xE7;
0203: static final int APP8 = 0xE8;
0204: static final int APP9 = 0xE9;
0205: static final int APP10 = 0xEA;
0206: static final int APP11 = 0xEB;
0207: static final int APP12 = 0xEC;
0208: static final int APP13 = 0xED;
0209: /** APP14 used by Adobe */
0210: static final int APP14 = 0xEE;
0211: static final int APP15 = 0xEF;
0212:
0213: // codes 0xF0 to 0xFD are reserved
0214:
0215: /** Comment marker */
0216: static final int COM = 0xFE;
0217:
0218: // Marker codes for JPEG-LS
0219:
0220: /** JPEG-LS SOF marker */
0221: // This was SOF48 in an earlier revision of the JPEG-LS specification.
0222: // "55" is the numerical value of SOF55 - SOF0 (= 247 - 192).
0223: static final int SOF55 = 0xF7;
0224:
0225: /** JPEG-LS parameters */
0226: static final int LSE = 0xF2;
0227:
0228: // Min and max APPn codes.
0229: static final int APPN_MIN = APP0;
0230: static final int APPN_MAX = APP15;
0231:
0232: // Min and max contiguous SOFn codes.
0233: static final int SOFN_MIN = SOF0;
0234: static final int SOFN_MAX = SOF15;
0235:
0236: // Min and Max RSTn codes.
0237: static final int RST_MIN = RST0;
0238: static final int RST_MAX = RST7;
0239:
0240: // Specific segment types defined as (code << 8) | X.
0241: static final int APP0_JFIF = (APP0 << 8) | 0;
0242: static final int APP0_JFXX = (APP0 << 8) | 1;
0243: static final int APP1_EXIF = (APP1 << 8) | 0;
0244: static final int APP2_ICC = (APP2 << 8) | 0;
0245: static final int APP14_ADOBE = (APP14 << 8) | 0;
0246: static final int UNKNOWN_MARKER = 0xffff;
0247: static final int SOF_MARKER = (SOF0 << 8) | 0;
0248:
0249: // Resolution unit types.
0250: static final int JFIF_RESUNITS_ASPECT = 0;
0251: static final int JFIF_RESUNITS_DPI = 1;
0252: static final int JFIF_RESUNITS_DPC = 2;
0253:
0254: // Thumbnail types
0255: static final int THUMBNAIL_JPEG = 0x10;
0256: static final int THUMBNAIL_PALETTE = 0x11;
0257: static final int THUMBNAIL_RGB = 0x12;
0258:
0259: // Adobe transform type.
0260: static final int ADOBE_TRANSFORM_UNKNOWN = 0;
0261: static final int ADOBE_TRANSFORM_YCC = 1;
0262: static final int ADOBE_TRANSFORM_YCCK = 2;
0263:
0264: // Zig-zag to natural re-ordering array.
0265: static final int[] zigzag = { 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7,
0266: 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11,
0267: 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20,
0268: 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61,
0269: 35, 36, 48, 49, 57, 58, 62, 63 };
0270:
0271: // --- Static methods ---
0272:
0273: private static IIOImage getThumbnail(ImageInputStream stream,
0274: int len, int thumbnailType, int w, int h)
0275: throws IOException {
0276:
0277: IIOImage result;
0278:
0279: long startPos = stream.getStreamPosition();
0280:
0281: if (thumbnailType == THUMBNAIL_JPEG) {
0282: Iterator readers = ImageIO.getImageReaders(stream);
0283: if (readers == null || !readers.hasNext())
0284: return null;
0285: ImageReader reader = (ImageReader) readers.next();
0286: reader.setInput(stream);
0287: BufferedImage image = reader.read(0, null);
0288: IIOMetadata metadata = null;
0289: try {
0290: metadata = reader.getImageMetadata(0);
0291: } catch (Exception e) {
0292: // Ignore it
0293: }
0294: result = new IIOImage(image, null, metadata);
0295: } else {
0296: int numBands;
0297: ColorModel cm;
0298: if (thumbnailType == THUMBNAIL_PALETTE) {
0299: if (len < 768 + w * h) {
0300: return null;
0301: }
0302:
0303: numBands = 1;
0304:
0305: byte[] palette = new byte[768];
0306: stream.readFully(palette);
0307: byte[] r = new byte[256];
0308: byte[] g = new byte[256];
0309: byte[] b = new byte[256];
0310: for (int i = 0, off = 0; i < 256; i++) {
0311: r[i] = palette[off++];
0312: g[i] = palette[off++];
0313: b[i] = palette[off++];
0314: }
0315:
0316: cm = new IndexColorModel(8, 256, r, g, b);
0317: } else {
0318: if (len < 3 * w * h) {
0319: return null;
0320: }
0321:
0322: numBands = 3;
0323:
0324: ColorSpace cs = ColorSpace
0325: .getInstance(ColorSpace.CS_sRGB);
0326: cm = new ComponentColorModel(cs, false, false,
0327: Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
0328: }
0329:
0330: byte[] data = new byte[w * h * numBands];
0331: stream.readFully(data);
0332: DataBufferByte db = new DataBufferByte(data, data.length);
0333: WritableRaster wr = Raster.createInterleavedRaster(db, w,
0334: h, w * numBands, numBands, new int[] { 0, 1, 2 },
0335: null);
0336: BufferedImage image = new BufferedImage(cm, wr, false, null);
0337: result = new IIOImage(image, null, null);
0338: }
0339:
0340: stream.seek(startPos + len);
0341:
0342: return result;
0343: }
0344:
0345: // --- Instance variables ---
0346:
0347: /** Whether the object may be edited. */
0348: private boolean isReadOnly = true;
0349:
0350: // APP0 JFIF marker segment parameters.
0351: boolean app0JFIFPresent;
0352: int majorVersion = 1;
0353: int minorVersion = 2;
0354: int resUnits; // (0 = aspect ratio; 1 = dots/inch; 2 = dots/cm)
0355: int Xdensity = 1;
0356: int Ydensity = 1;
0357: int thumbWidth = 0;
0358: int thumbHeight = 0;
0359: BufferedImage jfifThumbnail;
0360:
0361: // APP0 JFIF thumbnail(s).
0362: boolean app0JFXXPresent;
0363: List extensionCodes; // Integers 0x10, 0x11, 0x12
0364: List jfxxThumbnails; // IIOImages
0365:
0366: // APP2 ICC_PROFILE marker segment parameters.
0367: boolean app2ICCPresent;
0368: ICC_Profile profile = null;
0369:
0370: // DQT marker segment parameters.
0371: boolean dqtPresent;
0372: List qtables; // Each element is a List of QTables
0373:
0374: // DHT marker segment parameters.
0375: boolean dhtPresent;
0376: List htables; // Each element is a List of HuffmanTables
0377:
0378: // DRI marker segment parameters.
0379: boolean driPresent;
0380: int driInterval;
0381:
0382: // COM marker segment parameters.
0383: boolean comPresent;
0384: List comments; // byte[]s
0385:
0386: // Unknown marker segment parameters.
0387: boolean unknownPresent;
0388: List markerTags; // Integers
0389: List unknownData; // byte[] (NB: 'length' parameter is array length)
0390:
0391: // APP14 Adobe marker segment parameters.
0392: boolean app14AdobePresent;
0393: int version = 100;
0394: int flags0 = 0;
0395: int flags1 = 0;
0396: int transform; // 0 = Unknown, 1 = YCbCr, 2 = YCCK
0397:
0398: // SOF marker segment parameters.
0399: boolean sofPresent;
0400: int sofProcess;
0401: int samplePrecision = 8;
0402: int numLines;
0403: int samplesPerLine;
0404: int numFrameComponents;
0405: int[] componentId;
0406: int[] hSamplingFactor;
0407: int[] vSamplingFactor;
0408: int[] qtableSelector;
0409:
0410: // SOS marker segment parameters.
0411: boolean sosPresent;
0412: int numScanComponents;
0413: int[] componentSelector;
0414: int[] dcHuffTable;
0415: int[] acHuffTable;
0416: int startSpectralSelection;
0417: int endSpectralSelection;
0418: int approxHigh;
0419: int approxLow;
0420:
0421: // Embedded TIFF stream from EXIF segment.
0422: byte[] exifData = null;
0423:
0424: /** Marker codes in the order encountered. */
0425: private List markers = null; // List of Integer
0426:
0427: // Standard metadata variables.
0428: private boolean hasAlpha = false;
0429:
0430: // Agregated list of thumbnails: JFIF > JFXX > EXIF.
0431: private boolean thumbnailsInitialized = false;
0432: private List thumbnails = new ArrayList();
0433:
0434: CLibJPEGMetadata() {
0435: super (true, NATIVE_FORMAT, NATIVE_FORMAT_CLASS,
0436: new String[] { TIFF_FORMAT },
0437: new String[] { TIFF_FORMAT_CLASS });
0438:
0439: this .isReadOnly = isReadOnly;
0440: }
0441:
0442: CLibJPEGMetadata(ImageInputStream stream) throws IIOException {
0443: this ();
0444:
0445: try {
0446: initializeFromStream(stream);
0447: } catch (IOException e) {
0448: throw new IIOException("Cannot initialize JPEG metadata!",
0449: e);
0450: }
0451: }
0452:
0453: private class QTable {
0454: private static final int QTABLE_SIZE = 64;
0455:
0456: int elementPrecision;
0457: int tableID;
0458: JPEGQTable table;
0459:
0460: int length;
0461:
0462: QTable(ImageInputStream stream) throws IOException {
0463: elementPrecision = (int) stream.readBits(4);
0464: tableID = (int) stream.readBits(4);
0465: byte[] tmp = new byte[QTABLE_SIZE];
0466: stream.readFully(tmp);
0467: int[] data = new int[QTABLE_SIZE];
0468: for (int i = 0; i < QTABLE_SIZE; i++) {
0469: data[i] = tmp[zigzag[i]] & 0xff;
0470: }
0471: table = new JPEGQTable(data);
0472: length = data.length + 1;
0473: }
0474: }
0475:
0476: private class HuffmanTable {
0477: private static final int NUM_LENGTHS = 16;
0478:
0479: int tableClass;
0480: int tableID;
0481: JPEGHuffmanTable table;
0482:
0483: int length;
0484:
0485: HuffmanTable(ImageInputStream stream) throws IOException {
0486: tableClass = (int) stream.readBits(4);
0487: tableID = (int) stream.readBits(4);
0488: short[] lengths = new short[NUM_LENGTHS];
0489: for (int i = 0; i < NUM_LENGTHS; i++) {
0490: lengths[i] = (short) stream.read();
0491: }
0492: int numValues = 0;
0493: for (int i = 0; i < NUM_LENGTHS; i++) {
0494: numValues += lengths[i];
0495: }
0496: short[] values = new short[numValues];
0497: for (int i = 0; i < numValues; i++) {
0498: values[i] = (short) stream.read();
0499: }
0500: table = new JPEGHuffmanTable(lengths, values);
0501:
0502: length = 1 + NUM_LENGTHS + values.length;
0503: }
0504: }
0505:
0506: private synchronized void initializeFromStream(ImageInputStream iis)
0507: throws IOException {
0508: iis.mark();
0509: iis.setByteOrder(ByteOrder.BIG_ENDIAN);
0510:
0511: markers = new ArrayList();
0512:
0513: boolean isICCProfileValid = true;
0514: int numICCProfileChunks = 0;
0515: long[] iccProfileChunkOffsets = null;
0516: int[] iccProfileChunkLengths = null;
0517:
0518: while (true) {
0519: try {
0520: // 0xff denotes a potential marker.
0521: if (iis.read() == 0xff) {
0522: // Get next byte.
0523: int code = iis.read();
0524:
0525: // Is a marker if and only if code not in {0x00, 0xff}.
0526: // Continue to next marker if this is not a marker or if
0527: // it is an empty marker.
0528: if (code == 0x00 || code == 0xff || code == SOI
0529: || code == TEM
0530: || (code >= RST_MIN && code <= RST_MAX)) {
0531: continue;
0532: }
0533:
0534: // If at the end, quit.
0535: if (code == EOI) {
0536: break;
0537: }
0538:
0539: // Get the content length.
0540: int dataLength = iis.readUnsignedShort() - 2;
0541:
0542: if (APPN_MIN <= code && code <= APPN_MAX) {
0543: long pos = iis.getStreamPosition();
0544: boolean appnAdded = false;
0545:
0546: switch (code) {
0547: case APP0:
0548: if (dataLength >= 5) {
0549: byte[] b = new byte[5];
0550: iis.readFully(b);
0551: String id = new String(b);
0552: if (id.startsWith("JFIF")
0553: && !app0JFIFPresent) {
0554: app0JFIFPresent = true;
0555: markers.add(new Integer(APP0_JFIF));
0556: majorVersion = iis.read();
0557: minorVersion = iis.read();
0558: resUnits = iis.read();
0559: Xdensity = iis.readUnsignedShort();
0560: Ydensity = iis.readUnsignedShort();
0561: thumbWidth = iis.read();
0562: thumbHeight = iis.read();
0563: if (thumbWidth > 0
0564: && thumbHeight > 0) {
0565: IIOImage imiio = getThumbnail(
0566: iis, dataLength - 14,
0567: THUMBNAIL_RGB,
0568: thumbWidth, thumbHeight);
0569: if (imiio != null) {
0570: jfifThumbnail = (BufferedImage) imiio
0571: .getRenderedImage();
0572: }
0573: }
0574: appnAdded = true;
0575: } else if (id.startsWith("JFXX")) {
0576: if (!app0JFXXPresent) {
0577: extensionCodes = new ArrayList(
0578: 1);
0579: jfxxThumbnails = new ArrayList(
0580: 1);
0581: app0JFXXPresent = true;
0582: }
0583: markers.add(new Integer(APP0_JFXX));
0584: int extCode = iis.read();
0585: extensionCodes.add(new Integer(
0586: extCode));
0587: int w = 0, h = 0, offset = 6;
0588: if (extCode != THUMBNAIL_JPEG) {
0589: w = iis.read();
0590: h = iis.read();
0591: offset += 2;
0592: }
0593: IIOImage imiio = getThumbnail(iis,
0594: dataLength - offset,
0595: extCode, w, h);
0596: if (imiio != null) {
0597: jfxxThumbnails.add(imiio);
0598: }
0599: appnAdded = true;
0600: }
0601: }
0602: break;
0603: case APP1:
0604: if (dataLength >= 6) {
0605: byte[] b = new byte[6];
0606: iis.readFully(b);
0607: if (b[0] == (byte) 'E'
0608: && b[1] == (byte) 'x'
0609: && b[2] == (byte) 'i'
0610: && b[3] == (byte) 'f'
0611: && b[4] == (byte) 0
0612: && b[5] == (byte) 0) {
0613: exifData = new byte[dataLength - 6];
0614: iis.readFully(exifData);
0615: }
0616: }
0617: case APP2:
0618: if (dataLength >= 12) {
0619: byte[] b = new byte[12];
0620: iis.readFully(b);
0621: String id = new String(b);
0622: if (id.startsWith("ICC_PROFILE")) {
0623: if (!isICCProfileValid) {
0624: iis.skipBytes(dataLength - 12);
0625: continue;
0626: }
0627:
0628: int chunkNum = iis.read();
0629: int numChunks = iis.read();
0630: if (numChunks == 0
0631: || chunkNum == 0
0632: || chunkNum > numChunks
0633: || (app2ICCPresent && (numChunks != numICCProfileChunks || iccProfileChunkOffsets[chunkNum] != 0L))) {
0634: isICCProfileValid = false;
0635: iis.skipBytes(dataLength - 14);
0636: continue;
0637: }
0638:
0639: if (!app2ICCPresent) {
0640: app2ICCPresent = true;
0641: // Only flag one marker even though
0642: // multiple may be present.
0643: markers.add(new Integer(
0644: APP2_ICC));
0645:
0646: numICCProfileChunks = numChunks;
0647:
0648: if (numChunks == 1) {
0649: b = new byte[dataLength - 14];
0650: iis.readFully(b);
0651: profile = ICC_Profile
0652: .getInstance(b);
0653: } else {
0654: iccProfileChunkOffsets = new long[numChunks + 1];
0655: iccProfileChunkLengths = new int[numChunks + 1];
0656: iccProfileChunkOffsets[chunkNum] = iis
0657: .getStreamPosition();
0658: iccProfileChunkLengths[chunkNum] = dataLength - 14;
0659: iis
0660: .skipBytes(dataLength - 14);
0661: }
0662: } else {
0663: iccProfileChunkOffsets[chunkNum] = iis
0664: .getStreamPosition();
0665: iccProfileChunkLengths[chunkNum] = dataLength - 14;
0666: iis.skipBytes(dataLength - 14);
0667: }
0668:
0669: appnAdded = true;
0670: }
0671: }
0672: break;
0673: case APP14:
0674: if (dataLength >= 5) {
0675: byte[] b = new byte[5];
0676: iis.readFully(b);
0677: String id = new String(b);
0678: if (id.startsWith("Adobe")
0679: && !app14AdobePresent) { // Adobe segment
0680: app14AdobePresent = true;
0681: markers
0682: .add(new Integer(
0683: APP14_ADOBE));
0684: version = iis.readUnsignedShort();
0685: flags0 = iis.readUnsignedShort();
0686: flags1 = iis.readUnsignedShort();
0687: transform = iis.read();
0688: iis.skipBytes(dataLength - 12);
0689: appnAdded = true;
0690: }
0691: }
0692: break;
0693: default:
0694: appnAdded = false;
0695: break;
0696: }
0697:
0698: if (!appnAdded) {
0699: iis.seek(pos);
0700: addUnknownMarkerSegment(iis, code,
0701: dataLength);
0702: }
0703: } else if (code == DQT) {
0704: if (!dqtPresent) {
0705: dqtPresent = true;
0706: qtables = new ArrayList(1);
0707: }
0708: markers.add(new Integer(DQT));
0709: List l = new ArrayList(1);
0710: do {
0711: QTable t = new QTable(iis);
0712: l.add(t);
0713: dataLength -= t.length;
0714: } while (dataLength > 0);
0715: qtables.add(l);
0716: } else if (code == DHT) {
0717: if (!dhtPresent) {
0718: dhtPresent = true;
0719: htables = new ArrayList(1);
0720: }
0721: markers.add(new Integer(DHT));
0722: List l = new ArrayList(1);
0723: do {
0724: HuffmanTable t = new HuffmanTable(iis);
0725: l.add(t);
0726: dataLength -= t.length;
0727: } while (dataLength > 0);
0728: htables.add(l);
0729: } else if (code == DRI) {
0730: if (!driPresent) {
0731: driPresent = true;
0732: }
0733: markers.add(new Integer(DRI));
0734: driInterval = iis.readUnsignedShort();
0735: } else if (code == COM) {
0736: if (!comPresent) {
0737: comPresent = true;
0738: comments = new ArrayList(1);
0739: }
0740: markers.add(new Integer(COM));
0741: byte[] b = new byte[dataLength];
0742: iis.readFully(b);
0743: comments.add(b);
0744: } else if ((code >= SOFN_MIN && code <= SOFN_MAX)
0745: || code == SOF55) { // SOFn
0746: if (!sofPresent) {
0747: sofPresent = true;
0748: sofProcess = code - SOFN_MIN;
0749: samplePrecision = iis.read();
0750: numLines = iis.readUnsignedShort();
0751: samplesPerLine = iis.readUnsignedShort();
0752: numFrameComponents = iis.read();
0753: componentId = new int[numFrameComponents];
0754: hSamplingFactor = new int[numFrameComponents];
0755: vSamplingFactor = new int[numFrameComponents];
0756: qtableSelector = new int[numFrameComponents];
0757: for (int i = 0; i < numFrameComponents; i++) {
0758: componentId[i] = iis.read();
0759: hSamplingFactor[i] = (int) iis
0760: .readBits(4);
0761: vSamplingFactor[i] = (int) iis
0762: .readBits(4);
0763: qtableSelector[i] = iis.read();
0764: }
0765: markers.add(new Integer(SOF_MARKER));
0766: }
0767: } else if (code == SOS) {
0768: if (!sosPresent) {
0769: sosPresent = true;
0770: numScanComponents = iis.read();
0771: componentSelector = new int[numScanComponents];
0772: dcHuffTable = new int[numScanComponents];
0773: acHuffTable = new int[numScanComponents];
0774: for (int i = 0; i < numScanComponents; i++) {
0775: componentSelector[i] = iis.read();
0776: dcHuffTable[i] = (int) iis.readBits(4);
0777: acHuffTable[i] = (int) iis.readBits(4);
0778: }
0779: startSpectralSelection = iis.read();
0780: endSpectralSelection = iis.read();
0781: approxHigh = (int) iis.readBits(4);
0782: approxLow = (int) iis.readBits(4);
0783: markers.add(new Integer(SOS));
0784: }
0785: break;
0786: } else { // Any other marker
0787: addUnknownMarkerSegment(iis, code, dataLength);
0788: }
0789: }
0790: } catch (EOFException eofe) {
0791: // XXX Should this be caught?
0792: break;
0793: }
0794: }
0795:
0796: if (app2ICCPresent && isICCProfileValid && profile == null) {
0797: int profileDataLength = 0;
0798: for (int i = 1; i <= numICCProfileChunks; i++) {
0799: if (iccProfileChunkOffsets[i] == 0L) {
0800: isICCProfileValid = false;
0801: break;
0802: }
0803: profileDataLength += iccProfileChunkLengths[i];
0804: }
0805:
0806: if (isICCProfileValid) {
0807: byte[] b = new byte[profileDataLength];
0808: int off = 0;
0809: for (int i = 1; i <= numICCProfileChunks; i++) {
0810: iis.seek(iccProfileChunkOffsets[i]);
0811: iis.read(b, off, iccProfileChunkLengths[i]);
0812: off += iccProfileChunkLengths[i];
0813: }
0814:
0815: profile = ICC_Profile.getInstance(b);
0816: }
0817: }
0818:
0819: iis.reset();
0820: }
0821:
0822: private void addUnknownMarkerSegment(ImageInputStream stream,
0823: int code, int len) throws IOException {
0824: if (!unknownPresent) {
0825: unknownPresent = true;
0826: markerTags = new ArrayList(1);
0827: unknownData = new ArrayList(1);
0828: }
0829: markerTags.add(new Integer(code));
0830: byte[] b = new byte[len];
0831: stream.readFully(b);
0832: unknownData.add(b);
0833: markers.add(new Integer(UNKNOWN_MARKER));
0834: }
0835:
0836: public boolean isReadOnly() {
0837: return isReadOnly;
0838: }
0839:
0840: public Node getAsTree(String formatName) {
0841: if (formatName.equals(nativeMetadataFormatName)) {
0842: return getNativeTree();
0843: } else if (formatName
0844: .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
0845: return getStandardTree();
0846: } else if (formatName.equals(TIFF_FORMAT)) {
0847: return getTIFFTree();
0848: } else {
0849: throw new IllegalArgumentException(
0850: "Not a recognized format!");
0851: }
0852: }
0853:
0854: public void mergeTree(String formatName, Node root)
0855: throws IIOInvalidTreeException {
0856: if (isReadOnly) {
0857: throw new IllegalStateException("isReadOnly() == true!");
0858: }
0859: }
0860:
0861: public void reset() {
0862: if (isReadOnly) {
0863: throw new IllegalStateException("isReadOnly() == true!");
0864: }
0865: }
0866:
0867: // Native tree method.
0868:
0869: private Node getNativeTree() {
0870: int jfxxIndex = 0;
0871: int dqtIndex = 0;
0872: int dhtIndex = 0;
0873: int comIndex = 0;
0874: int unknownIndex = 0;
0875:
0876: IIOMetadataNode root = new IIOMetadataNode(
0877: nativeMetadataFormatName);
0878:
0879: IIOMetadataNode JPEGvariety = new IIOMetadataNode("JPEGvariety");
0880: root.appendChild(JPEGvariety);
0881:
0882: IIOMetadataNode markerSequence = new IIOMetadataNode(
0883: "markerSequence");
0884: root.appendChild(markerSequence);
0885:
0886: IIOMetadataNode app0JFIF = null;
0887: if (app0JFIFPresent || app0JFXXPresent || app2ICCPresent) {
0888: app0JFIF = new IIOMetadataNode("app0JFIF");
0889: app0JFIF.setAttribute("majorVersion", Integer
0890: .toString(majorVersion));
0891: app0JFIF.setAttribute("minorVersion", Integer
0892: .toString(minorVersion));
0893: app0JFIF.setAttribute("resUnits", Integer
0894: .toString(resUnits));
0895: app0JFIF.setAttribute("Xdensity", Integer
0896: .toString(Xdensity));
0897: app0JFIF.setAttribute("Ydensity", Integer
0898: .toString(Ydensity));
0899: app0JFIF.setAttribute("thumbWidth", Integer
0900: .toString(thumbWidth));
0901: app0JFIF.setAttribute("thumbHeight", Integer
0902: .toString(thumbHeight));
0903: JPEGvariety.appendChild(app0JFIF);
0904: }
0905:
0906: IIOMetadataNode JFXX = null;
0907: if (app0JFXXPresent) {
0908: JFXX = new IIOMetadataNode("JFXX");
0909: app0JFIF.appendChild(JFXX);
0910: }
0911:
0912: Iterator markerIter = markers.iterator();
0913: while (markerIter.hasNext()) {
0914: int marker = ((Integer) markerIter.next()).intValue();
0915: switch (marker) {
0916: case APP0_JFIF:
0917: // Do nothing: already handled above.
0918: break;
0919: case APP0_JFXX:
0920: IIOMetadataNode app0JFXX = new IIOMetadataNode(
0921: "app0JFXX");
0922: Integer extensionCode = (Integer) extensionCodes
0923: .get(jfxxIndex);
0924: app0JFXX.setAttribute("extensionCode", extensionCode
0925: .toString());
0926: IIOMetadataNode JFIFthumb = null;
0927: switch (extensionCode.intValue()) {
0928: case THUMBNAIL_JPEG:
0929: JFIFthumb = new IIOMetadataNode("JFIFthumbJPEG");
0930: break;
0931: case THUMBNAIL_PALETTE:
0932: JFIFthumb = new IIOMetadataNode("JFIFthumbPalette");
0933: break;
0934: case THUMBNAIL_RGB:
0935: JFIFthumb = new IIOMetadataNode("JFIFthumbRGB");
0936: break;
0937: default:
0938: // No JFIFthumb node will be appended.
0939: }
0940: if (JFIFthumb != null) {
0941: IIOImage img = (IIOImage) jfxxThumbnails
0942: .get(jfxxIndex++);
0943: if (extensionCode.intValue() == THUMBNAIL_JPEG) {
0944: IIOMetadata thumbMetadata = img.getMetadata();
0945: if (thumbMetadata != null) {
0946: Node thumbTree = thumbMetadata
0947: .getAsTree(nativeMetadataFormatName);
0948: if (thumbTree instanceof IIOMetadataNode) {
0949: IIOMetadataNode elt = (IIOMetadataNode) thumbTree;
0950: NodeList elts = elt
0951: .getElementsByTagName("markerSequence");
0952: if (elts.getLength() > 0) {
0953: JFIFthumb.appendChild(elts.item(0));
0954: }
0955: }
0956: }
0957: } else {
0958: BufferedImage thumb = (BufferedImage) img
0959: .getRenderedImage();
0960: JFIFthumb.setAttribute("thumbWidth", Integer
0961: .toString(thumb.getWidth()));
0962: JFIFthumb.setAttribute("thumbHeight", Integer
0963: .toString(thumb.getHeight()));
0964: }
0965: // Add thumbnail as a user object even though not in
0966: // metadata specification.
0967: JFIFthumb.setUserObject(img);
0968: app0JFXX.appendChild(JFIFthumb);
0969: }
0970: JFXX.appendChild(app0JFXX);
0971: break;
0972: case APP2_ICC:
0973: IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
0974: app2ICC.setUserObject(profile);
0975: app0JFIF.appendChild(app2ICC);
0976: break;
0977: case DQT:
0978: IIOMetadataNode dqt = new IIOMetadataNode("dqt");
0979: List tables = (List) qtables.get(dqtIndex++);
0980: int numTables = tables.size();
0981: for (int j = 0; j < numTables; j++) {
0982: IIOMetadataNode dqtable = new IIOMetadataNode(
0983: "dqtable");
0984: QTable t = (QTable) tables.get(j);
0985: dqtable.setAttribute("elementPrecision", Integer
0986: .toString(t.elementPrecision));
0987: dqtable.setAttribute("qtableId", Integer
0988: .toString(t.tableID));
0989: dqtable.setUserObject(t.table);
0990: dqt.appendChild(dqtable);
0991: }
0992: markerSequence.appendChild(dqt);
0993: break;
0994: case DHT:
0995: IIOMetadataNode dht = new IIOMetadataNode("dht");
0996: tables = (List) htables.get(dhtIndex++);
0997: numTables = tables.size();
0998: for (int j = 0; j < numTables; j++) {
0999: IIOMetadataNode dhtable = new IIOMetadataNode(
1000: "dhtable");
1001: HuffmanTable t = (HuffmanTable) tables.get(j);
1002: dhtable.setAttribute("class", Integer
1003: .toString(t.tableClass));
1004: dhtable.setAttribute("htableId", Integer
1005: .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
1014: .toString(driInterval));
1015: markerSequence.appendChild(dri);
1016: break;
1017: case COM:
1018: IIOMetadataNode com = new IIOMetadataNode("com");
1019: com.setUserObject(comments.get(comIndex++));
1020: markerSequence.appendChild(com);
1021: break;
1022: case UNKNOWN_MARKER:
1023: IIOMetadataNode unknown = new IIOMetadataNode("unknown");
1024: Integer markerTag = (Integer) markerTags
1025: .get(unknownIndex);
1026: unknown.setAttribute("MarkerTag", markerTag.toString());
1027: unknown.setUserObject(unknownData.get(unknownIndex++));
1028: markerSequence.appendChild(unknown);
1029: break;
1030: case APP14_ADOBE:
1031: IIOMetadataNode app14Adobe = new IIOMetadataNode(
1032: "app14Adobe");
1033: app14Adobe.setAttribute("version", Integer
1034: .toString(version));
1035: app14Adobe.setAttribute("flags0", Integer
1036: .toString(flags0));
1037: app14Adobe.setAttribute("flags1", Integer
1038: .toString(flags1));
1039: app14Adobe.setAttribute("transform", Integer
1040: .toString(transform));
1041: markerSequence.appendChild(app14Adobe);
1042: break;
1043: case SOF_MARKER:
1044: IIOMetadataNode sof = new IIOMetadataNode("sof");
1045: sof.setAttribute("process", Integer
1046: .toString(sofProcess));
1047: sof.setAttribute("samplePrecision", Integer
1048: .toString(samplePrecision));
1049: sof
1050: .setAttribute("numLines", Integer
1051: .toString(numLines));
1052: sof.setAttribute("samplesPerLine", Integer
1053: .toString(samplesPerLine));
1054: sof.setAttribute("numFrameComponents", Integer
1055: .toString(numFrameComponents));
1056: for (int i = 0; i < numFrameComponents; i++) {
1057: IIOMetadataNode componentSpec = new IIOMetadataNode(
1058: "componentSpec");
1059: componentSpec.setAttribute("componentId", Integer
1060: .toString(componentId[i]));
1061: componentSpec.setAttribute("HsamplingFactor",
1062: Integer.toString(hSamplingFactor[i]));
1063: componentSpec.setAttribute("VsamplingFactor",
1064: Integer.toString(vSamplingFactor[i]));
1065: componentSpec.setAttribute("QtableSelector",
1066: Integer.toString(qtableSelector[i]));
1067: sof.appendChild(componentSpec);
1068: }
1069: markerSequence.appendChild(sof);
1070: break;
1071: case SOS:
1072: IIOMetadataNode sos = new IIOMetadataNode("sos");
1073: sos.setAttribute("numScanComponents", Integer
1074: .toString(numScanComponents));
1075: sos.setAttribute("startSpectralSelection", Integer
1076: .toString(startSpectralSelection));
1077: sos.setAttribute("endSpectralSelection", Integer
1078: .toString(endSpectralSelection));
1079: sos.setAttribute("approxHigh", Integer
1080: .toString(approxHigh));
1081: sos.setAttribute("approxLow", Integer
1082: .toString(approxLow));
1083: for (int i = 0; i < numScanComponents; i++) {
1084: IIOMetadataNode scanComponentSpec = new IIOMetadataNode(
1085: "scanComponentSpec");
1086: scanComponentSpec.setAttribute("componentSelector",
1087: Integer.toString(componentSelector[i]));
1088: scanComponentSpec.setAttribute("dcHuffTable",
1089: Integer.toString(dcHuffTable[i]));
1090: scanComponentSpec.setAttribute("acHuffTable",
1091: Integer.toString(acHuffTable[i]));
1092: sos.appendChild(scanComponentSpec);
1093: }
1094: markerSequence.appendChild(sos);
1095: break;
1096: }
1097: }
1098:
1099: return root;
1100: }
1101:
1102: // Standard tree node methods
1103:
1104: protected IIOMetadataNode getStandardChromaNode() {
1105: if (!sofPresent) {
1106: // No image, so no chroma
1107: return null;
1108: }
1109:
1110: IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
1111: IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
1112: chroma.appendChild(csType);
1113:
1114: IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
1115: chroma.appendChild(numChanNode);
1116: numChanNode.setAttribute("value", Integer
1117: .toString(numFrameComponents));
1118:
1119: // Check JFIF presence.
1120: if (app0JFIFPresent) {
1121: if (numFrameComponents == 1) {
1122: csType.setAttribute("name", "GRAY");
1123: } else {
1124: csType.setAttribute("name", "YCbCr");
1125: }
1126: return chroma;
1127: }
1128:
1129: // How about an Adobe marker segment?
1130: if (app14AdobePresent) {
1131: switch (transform) {
1132: case ADOBE_TRANSFORM_YCCK: // YCCK
1133: csType.setAttribute("name", "YCCK");
1134: break;
1135: case ADOBE_TRANSFORM_YCC: // YCC
1136: csType.setAttribute("name", "YCbCr");
1137: break;
1138: case ADOBE_TRANSFORM_UNKNOWN: // Unknown
1139: if (numFrameComponents == 3) {
1140: csType.setAttribute("name", "RGB");
1141: } else if (numFrameComponents == 4) {
1142: csType.setAttribute("name", "CMYK");
1143: }
1144: break;
1145: }
1146: return chroma;
1147: }
1148:
1149: // Initially assume no opacity.
1150: hasAlpha = false;
1151:
1152: // Neither marker. Check components
1153: if (numFrameComponents < 3) {
1154: csType.setAttribute("name", "GRAY");
1155: if (numFrameComponents == 2) {
1156: hasAlpha = true;
1157: }
1158: return chroma;
1159: }
1160:
1161: boolean idsAreJFIF = true;
1162:
1163: for (int i = 0; i < componentId.length; i++) {
1164: int id = componentId[i];
1165: if ((id < 1) || (id >= componentId.length)) {
1166: idsAreJFIF = false;
1167: }
1168: }
1169:
1170: if (idsAreJFIF) {
1171: csType.setAttribute("name", "YCbCr");
1172: if (numFrameComponents == 4) {
1173: hasAlpha = true;
1174: }
1175: return chroma;
1176: }
1177:
1178: // Check against the letters
1179: if (componentId[0] == 'R' && componentId[1] == 'G'
1180: && componentId[2] == 'B') {
1181: csType.setAttribute("name", "RGB");
1182: if (numFrameComponents == 4 && componentId[3] == 'A') {
1183: hasAlpha = true;
1184: }
1185: return chroma;
1186: }
1187:
1188: if (componentId[0] == 'Y' && componentId[1] == 'C'
1189: && componentId[2] == 'c') {
1190: csType.setAttribute("name", "PhotoYCC");
1191: if (numFrameComponents == 4 && componentId[3] == 'A') {
1192: hasAlpha = true;
1193: }
1194: return chroma;
1195: }
1196:
1197: // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
1198: // 4-channel subsampled are YCbCrA, unsubsampled are CMYK
1199:
1200: boolean subsampled = false;
1201:
1202: int hfactor = hSamplingFactor[0];
1203: int vfactor = vSamplingFactor[0];
1204:
1205: for (int i = 1; i < componentId.length; i++) {
1206: if (hSamplingFactor[i] != hfactor
1207: || vSamplingFactor[i] != vfactor) {
1208: subsampled = true;
1209: break;
1210: }
1211: }
1212:
1213: if (subsampled) {
1214: csType.setAttribute("name", "YCbCr");
1215: if (numFrameComponents == 4) {
1216: hasAlpha = true;
1217: }
1218: return chroma;
1219: }
1220:
1221: // Not subsampled. numFrameComponents < 3 is taken care of above
1222: if (numFrameComponents == 3) {
1223: csType.setAttribute("name", "RGB");
1224: } else {
1225: csType.setAttribute("name", "CMYK");
1226: }
1227:
1228: return chroma;
1229: }
1230:
1231: protected IIOMetadataNode getStandardCompressionNode() {
1232: IIOMetadataNode compression = null;
1233:
1234: if (sofPresent || sosPresent) {
1235: compression = new IIOMetadataNode("Compression");
1236:
1237: if (sofPresent) {
1238: // Process 55 is JPEG-LS, others are lossless JPEG.
1239: boolean isLossless = sofProcess == 3 || sofProcess == 7
1240: || sofProcess == 11 || sofProcess == 15
1241: || sofProcess == 55;
1242:
1243: // CompressionTypeName
1244: IIOMetadataNode name = new IIOMetadataNode(
1245: "CompressionTypeName");
1246: String compressionType = isLossless ? (sofProcess == 55 ? "JPEG-LS"
1247: : "JPEG-LOSSLESS")
1248: : "JPEG";
1249: name.setAttribute("value", compressionType);
1250: compression.appendChild(name);
1251:
1252: // Lossless - false
1253: IIOMetadataNode lossless = new IIOMetadataNode(
1254: "Lossless");
1255: lossless.setAttribute("value", isLossless ? "true"
1256: : "false");
1257: compression.appendChild(lossless);
1258: }
1259:
1260: if (sosPresent) {
1261: IIOMetadataNode prog = new IIOMetadataNode(
1262: "NumProgressiveScans");
1263: prog.setAttribute("value", "1");
1264: compression.appendChild(prog);
1265: }
1266: }
1267:
1268: return compression;
1269: }
1270:
1271: protected IIOMetadataNode getStandardDimensionNode() {
1272: IIOMetadataNode dim = new IIOMetadataNode("Dimension");
1273: IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
1274: orient.setAttribute("value", "normal");
1275: dim.appendChild(orient);
1276:
1277: if (app0JFIFPresent) {
1278: float aspectRatio;
1279: if (resUnits == JFIF_RESUNITS_ASPECT) {
1280: // Aspect ratio.
1281: aspectRatio = (float) Xdensity / (float) Ydensity;
1282: } else {
1283: // Density.
1284: aspectRatio = (float) Ydensity / (float) Xdensity;
1285: }
1286: IIOMetadataNode aspect = new IIOMetadataNode(
1287: "PixelAspectRatio");
1288: aspect.setAttribute("value", Float.toString(aspectRatio));
1289: dim.insertBefore(aspect, orient);
1290:
1291: if (resUnits != JFIF_RESUNITS_ASPECT) {
1292: // 1 == dpi, 2 == dpc
1293: float scale = (resUnits == JFIF_RESUNITS_DPI) ? 25.4F
1294: : 10.0F;
1295:
1296: IIOMetadataNode horiz = new IIOMetadataNode(
1297: "HorizontalPixelSize");
1298: horiz.setAttribute("value", Float.toString(scale
1299: / Xdensity));
1300: dim.appendChild(horiz);
1301:
1302: IIOMetadataNode vert = new IIOMetadataNode(
1303: "VerticalPixelSize");
1304: vert.setAttribute("value", Float.toString(scale
1305: / Ydensity));
1306: dim.appendChild(vert);
1307: }
1308: }
1309: return dim;
1310: }
1311:
1312: protected IIOMetadataNode getStandardTextNode() {
1313: IIOMetadataNode text = null;
1314: if (comPresent) {
1315: text = new IIOMetadataNode("Text");
1316: Iterator iter = comments.iterator();
1317: while (iter.hasNext()) {
1318: IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1319: entry.setAttribute("keyword", "comment");
1320: byte[] data = (byte[]) iter.next();
1321: try {
1322: entry.setAttribute("value", new String(data,
1323: "ISO-8859-1"));
1324: } catch (UnsupportedEncodingException e) {
1325: entry.setAttribute("value", new String(data));
1326: }
1327: text.appendChild(entry);
1328: }
1329: }
1330: return text;
1331: }
1332:
1333: // This method assumes that getStandardChromaNode() has already been
1334: // called to initialize hasAlpha.
1335: protected IIOMetadataNode getStandardTransparencyNode() {
1336: IIOMetadataNode trans = null;
1337: if (hasAlpha == true) {
1338: trans = new IIOMetadataNode("Transparency");
1339: IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1340: alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1341: trans.appendChild(alpha);
1342: }
1343: return trans;
1344: }
1345:
1346: // TIFF tree method
1347:
1348: private Node getTIFFTree() {
1349: String metadataName = TIFF_FORMAT;
1350:
1351: BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
1352:
1353: TIFFDirectory dir = new TIFFDirectory(new TIFFTagSet[] { base,
1354: EXIFParentTIFFTagSet.getInstance() }, null);
1355:
1356: if (sofPresent) {
1357: // sofProcess -> Compression ?
1358: int compression = BaselineTIFFTagSet.COMPRESSION_JPEG;
1359: TIFFField compressionField = new TIFFField(base
1360: .getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
1361: compression);
1362: dir.addTIFFField(compressionField);
1363:
1364: // samplePrecision -> BitsPerSample
1365: char[] bitsPerSample = new char[numFrameComponents];
1366: Arrays.fill(bitsPerSample, (char) (samplePrecision & 0xff));
1367: TIFFField bitsPerSampleField = new TIFFField(base
1368: .getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
1369: TIFFTag.TIFF_SHORT, bitsPerSample.length,
1370: bitsPerSample);
1371: dir.addTIFFField(bitsPerSampleField);
1372:
1373: // numLines -> ImageLength
1374: TIFFField imageLengthField = new TIFFField(base
1375: .getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
1376: numLines);
1377: dir.addTIFFField(imageLengthField);
1378:
1379: // samplesPerLine -> ImageWidth
1380: TIFFField imageWidthField = new TIFFField(base
1381: .getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
1382: samplesPerLine);
1383: dir.addTIFFField(imageWidthField);
1384:
1385: // numFrameComponents -> SamplesPerPixel
1386: TIFFField samplesPerPixelField = new TIFFField(base
1387: .getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
1388: numFrameComponents);
1389: dir.addTIFFField(samplesPerPixelField);
1390:
1391: // componentId -> PhotometricInterpretation + ExtraSamples
1392: IIOMetadataNode chroma = getStandardChromaNode();
1393: if (chroma != null) {
1394: IIOMetadataNode csType = (IIOMetadataNode) chroma
1395: .getElementsByTagName("ColorSpaceType").item(0);
1396: String name = csType.getAttribute("name");
1397: int photometricInterpretation = -1;
1398: if (name.equals("GRAY")) {
1399: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
1400: } else if (name.equals("YCbCr")
1401: || name.equals("PhotoYCC")) {
1402: // NOTE: PhotoYCC -> YCbCr
1403: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
1404: } else if (name.equals("RGB")) {
1405: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
1406: } else if (name.equals("CMYK") || name.equals("YCCK")) {
1407: // NOTE: YCCK -> CMYK
1408: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
1409: }
1410:
1411: if (photometricInterpretation != -1) {
1412: TIFFField photometricInterpretationField = new TIFFField(
1413: base
1414: .getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
1415: photometricInterpretation);
1416: dir.addTIFFField(photometricInterpretationField);
1417: }
1418:
1419: if (hasAlpha) {
1420: char[] extraSamples = new char[] { BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA };
1421: TIFFField extraSamplesField = new TIFFField(
1422: base
1423: .getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1424: TIFFTag.TIFF_SHORT, extraSamples.length,
1425: extraSamples);
1426: dir.addTIFFField(extraSamplesField);
1427: }
1428: } // chroma != null
1429: } // sofPresent
1430:
1431: // JFIF APP0 -> Resolution fields.
1432: if (app0JFIFPresent) {
1433: long[][] xResolution = new long[][] { { Xdensity, 1 } };
1434: TIFFField XResolutionField = new TIFFField(base
1435: .getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1436: TIFFTag.TIFF_RATIONAL, 1, xResolution);
1437: dir.addTIFFField(XResolutionField);
1438:
1439: long[][] yResolution = new long[][] { { Ydensity, 1 } };
1440: TIFFField YResolutionField = new TIFFField(base
1441: .getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1442: TIFFTag.TIFF_RATIONAL, 1, yResolution);
1443: dir.addTIFFField(YResolutionField);
1444:
1445: int resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
1446: switch (resUnits) {
1447: case JFIF_RESUNITS_ASPECT:
1448: resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE;
1449: case JFIF_RESUNITS_DPI:
1450: resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
1451: break;
1452: case JFIF_RESUNITS_DPC:
1453: resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER;
1454: break;
1455: }
1456: TIFFField ResolutionUnitField = new TIFFField(base
1457: .getTag(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++] = (byte) ((qtlength & 0xff00) >> 8);
1507: jpegTablesData[jpoff++] = (byte) (qtlength & 0xff);
1508: jpegTablesData[jpoff++] = (byte) (((qt.elementPrecision & 0xf0) << 4) | (qt.tableID & 0x0f));
1509: int[] table = qt.table.getTable();
1510: int qlen = table.length;
1511: for (int i = 0; i < qlen; i++) {
1512: jpegTablesData[jpoff + zigzag[i]] = (byte) table[i];
1513: }
1514: jpoff += qlen;
1515: }
1516: }
1517: }
1518:
1519: if (dhtPresent) {
1520: Iterator dhts = htables.iterator();
1521: while (dhts.hasNext()) {
1522: Iterator htiter = ((List) dhts.next()).iterator();
1523: while (htiter.hasNext()) {
1524: jpegTablesData[jpoff++] = (byte) 0xff;
1525: jpegTablesData[jpoff++] = (byte) DHT;
1526: HuffmanTable ht = (HuffmanTable) htiter.next();
1527: int htlength = ht.length + 2;
1528: jpegTablesData[jpoff++] = (byte) ((htlength & 0xff00) >> 8);
1529: jpegTablesData[jpoff++] = (byte) (htlength & 0xff);
1530: jpegTablesData[jpoff++] = (byte) (((ht.tableClass & 0x0f) << 4) | (ht.tableID & 0x0f));
1531: short[] lengths = ht.table.getLengths();
1532: int numLengths = lengths.length;
1533: for (int i = 0; i < numLengths; i++) {
1534: jpegTablesData[jpoff++] = (byte) lengths[i];
1535: }
1536: short[] values = ht.table.getValues();
1537: int numValues = values.length;
1538: for (int i = 0; i < numValues; i++) {
1539: jpegTablesData[jpoff++] = (byte) values[i];
1540: }
1541: }
1542: }
1543: }
1544:
1545: jpegTablesData[jpoff++] = (byte) 0xff;
1546: jpegTablesData[jpoff] = (byte) EOI;
1547: }
1548: if (jpegTablesData != null) {
1549: TIFFField JPEGTablesField = new TIFFField(base
1550: .getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
1551: TIFFTag.TIFF_UNDEFINED, jpegTablesData.length,
1552: jpegTablesData);
1553: dir.addTIFFField(JPEGTablesField);
1554: }
1555:
1556: IIOMetadata tiffMetadata = dir.getAsMetadata();
1557:
1558: if (exifData != null) {
1559: try {
1560: Iterator tiffReaders = ImageIO
1561: .getImageReadersByFormatName("TIFF");
1562: if (tiffReaders != null && tiffReaders.hasNext()) {
1563: ImageReader tiffReader = (ImageReader) tiffReaders
1564: .next();
1565: ByteArrayInputStream bais = new ByteArrayInputStream(
1566: exifData);
1567: ImageInputStream exifStream = new MemoryCacheImageInputStream(
1568: bais);
1569: tiffReader.setInput(exifStream);
1570: IIOMetadata exifMetadata = tiffReader
1571: .getImageMetadata(0);
1572: tiffMetadata.mergeTree(metadataName, exifMetadata
1573: .getAsTree(metadataName));
1574: tiffReader.reset();
1575: }
1576: } catch (IOException ioe) {
1577: // Ignore it.
1578: }
1579: }
1580:
1581: return tiffMetadata.getAsTree(metadataName);
1582: }
1583:
1584: // Thumbnail methods
1585:
1586: private void initializeThumbnails() {
1587: synchronized (thumbnails) {
1588: if (!thumbnailsInitialized) {
1589: // JFIF/JFXX are not supposed to coexist in the same
1590: // JPEG stream but in reality sometimes they do.
1591:
1592: // JFIF thumbnail
1593: if (app0JFIFPresent && jfifThumbnail != null) {
1594: thumbnails.add(jfifThumbnail);
1595: }
1596:
1597: // JFXX thumbnail(s)
1598: if (app0JFXXPresent && jfxxThumbnails != null) {
1599: int numJFXX = jfxxThumbnails.size();
1600: for (int i = 0; i < numJFXX; i++) {
1601: IIOImage img = (IIOImage) jfxxThumbnails.get(i);
1602: BufferedImage jfxxThumbnail = (BufferedImage) img
1603: .getRenderedImage();
1604: thumbnails.add(jfxxThumbnail);
1605: }
1606: }
1607:
1608: // EXIF thumbnail
1609: if (exifData != null) {
1610: try {
1611: Iterator tiffReaders = ImageIO
1612: .getImageReadersByFormatName("TIFF");
1613: if (tiffReaders != null
1614: && tiffReaders.hasNext()) {
1615: ImageReader tiffReader = (ImageReader) tiffReaders
1616: .next();
1617: ByteArrayInputStream bais = new ByteArrayInputStream(
1618: exifData);
1619: ImageInputStream exifStream = new MemoryCacheImageInputStream(
1620: bais);
1621: tiffReader.setInput(exifStream);
1622: if (tiffReader.getNumImages(true) > 1) {
1623: BufferedImage exifThumbnail = tiffReader
1624: .read(1, null);
1625: thumbnails.add(exifThumbnail);
1626: }
1627: tiffReader.reset();
1628: }
1629: } catch (IOException ioe) {
1630: // Ignore it.
1631: }
1632: }
1633:
1634: thumbnailsInitialized = true;
1635: } // if(!thumbnailsInitialized)
1636: } // sychronized
1637: }
1638:
1639: int getNumThumbnails() throws IOException {
1640: initializeThumbnails();
1641: return thumbnails.size();
1642: }
1643:
1644: BufferedImage getThumbnail(int thumbnailIndex) throws IOException {
1645: if (thumbnailIndex < 0) {
1646: throw new IndexOutOfBoundsException("thumbnailIndex < 0!");
1647: }
1648:
1649: initializeThumbnails();
1650:
1651: if (thumbnailIndex >= thumbnails.size()) {
1652: throw new IndexOutOfBoundsException(
1653: "thumbnailIndex > getNumThumbnails()");
1654: }
1655:
1656: return (BufferedImage) thumbnails.get(thumbnailIndex);
1657: }
1658: }
|