001: /*
002: * $RCSfile: TIFFDirectory.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.3 $
009: * $Date: 2005/09/19 21:52:04 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codec;
013:
014: import java.io.IOException;
015: import java.io.EOFException;
016: import java.io.Serializable;
017: import java.util.Enumeration;
018: import java.util.Hashtable;
019: import java.util.Vector;
020:
021: /**
022: * A class representing an Image File Directory (IFD) from a TIFF 6.0
023: * stream. The TIFF file format is described in more detail in the
024: * comments for the TIFFDescriptor class.
025: *
026: * <p> A TIFF IFD consists of a set of TIFFField tags. Methods are
027: * provided to query the set of tags and to obtain the raw field
028: * array. In addition, convenience methods are provided for acquiring
029: * the values of tags that contain a single value that fits into a
030: * byte, int, long, float, or double.
031: *
032: * <p> Every TIFF file is made up of one or more public IFDs that are
033: * joined in a linked list, rooted in the file header. A file may
034: * also contain so-called private IFDs that are referenced from
035: * tag data and do not appear in the main list.
036: *
037: * <p><b> This class is not a committed part of the JAI API. It may
038: * be removed or changed in future releases of JAI.</b>
039: *
040: * @see javax.media.jai.operator.TIFFDescriptor
041: * @see TIFFField
042: */
043: public class TIFFDirectory extends Object implements Serializable {
044:
045: /** A boolean storing the endianness of the stream. */
046: boolean isBigEndian;
047:
048: /** The number of entries in the IFD. */
049: int numEntries;
050:
051: /** An array of TIFFFields. */
052: TIFFField[] fields;
053:
054: /** A Hashtable indexing the fields by tag number. */
055: Hashtable fieldIndex = new Hashtable();
056:
057: /** The offset of this IFD. */
058: long IFDOffset = 8;
059:
060: /** The offset of the next IFD. */
061: long nextIFDOffset = 0;
062:
063: /** The default constructor. */
064: TIFFDirectory() {
065: }
066:
067: private static boolean isValidEndianTag(int endian) {
068: return ((endian == 0x4949) || (endian == 0x4d4d));
069: }
070:
071: /**
072: * Constructs a TIFFDirectory from a SeekableStream.
073: * The directory parameter specifies which directory to read from
074: * the linked list present in the stream; directory 0 is normally
075: * read but it is possible to store multiple images in a single
076: * TIFF file by maintaing multiple directories.
077: *
078: * @param stream a SeekableStream to read from.
079: * @param directory the index of the directory to read.
080: */
081: public TIFFDirectory(SeekableStream stream, int directory)
082: throws IOException {
083:
084: long global_save_offset = stream.getFilePointer();
085: long ifd_offset;
086:
087: // Read the TIFF header
088: stream.seek(0L);
089: int endian = stream.readUnsignedShort();
090: if (!isValidEndianTag(endian)) {
091: throw new IllegalArgumentException(JaiI18N
092: .getString("TIFFDirectory1"));
093: }
094: isBigEndian = (endian == 0x4d4d);
095:
096: int magic = readUnsignedShort(stream);
097: if (magic != 42) {
098: throw new IllegalArgumentException(JaiI18N
099: .getString("TIFFDirectory2"));
100: }
101:
102: // Get the initial ifd offset as an unsigned int (using a long)
103: ifd_offset = readUnsignedInt(stream);
104:
105: for (int i = 0; i < directory; i++) {
106: if (ifd_offset == 0L) {
107: throw new IllegalArgumentException(JaiI18N
108: .getString("TIFFDirectory3"));
109: }
110:
111: stream.seek(ifd_offset);
112: int entries = readUnsignedShort(stream);
113: stream.skip(12 * entries);
114:
115: ifd_offset = readUnsignedInt(stream);
116: }
117:
118: stream.seek(ifd_offset);
119: initialize(stream);
120: stream.seek(global_save_offset);
121: }
122:
123: /**
124: * Constructs a TIFFDirectory by reading a SeekableStream.
125: * The ifd_offset parameter specifies the stream offset from which
126: * to begin reading; this mechanism is sometimes used to store
127: * private IFDs within a TIFF file that are not part of the normal
128: * sequence of IFDs.
129: *
130: * @param stream a SeekableStream to read from.
131: * @param ifd_offset the long byte offset of the directory.
132: * @param directory the index of the directory to read beyond the
133: * one at the current stream offset; zero indicates the IFD
134: * at the current offset.
135: */
136: public TIFFDirectory(SeekableStream stream, long ifd_offset,
137: int directory) throws IOException {
138:
139: long global_save_offset = stream.getFilePointer();
140: stream.seek(0L);
141: int endian = stream.readUnsignedShort();
142: if (!isValidEndianTag(endian)) {
143: throw new IllegalArgumentException(JaiI18N
144: .getString("TIFFDirectory1"));
145: }
146: isBigEndian = (endian == 0x4d4d);
147:
148: // Seek to the first IFD.
149: stream.seek(ifd_offset);
150:
151: // Seek to desired IFD if necessary.
152: int dirNum = 0;
153: while (dirNum < directory) {
154: // Get the number of fields in the current IFD.
155: int numEntries = readUnsignedShort(stream);
156:
157: // Skip to the next IFD offset value field.
158: stream.seek(ifd_offset + 12 * numEntries);
159:
160: // Read the offset to the next IFD beyond this one.
161: ifd_offset = readUnsignedInt(stream);
162:
163: // Seek to the next IFD.
164: stream.seek(ifd_offset);
165:
166: // Increment the directory.
167: dirNum++;
168: }
169:
170: initialize(stream);
171: stream.seek(global_save_offset);
172: }
173:
174: private static final int[] sizeOfType = { 0, // 0 = n/a
175: 1, // 1 = byte
176: 1, // 2 = ascii
177: 2, // 3 = short
178: 4, // 4 = long
179: 8, // 5 = rational
180: 1, // 6 = sbyte
181: 1, // 7 = undefined
182: 2, // 8 = sshort
183: 4, // 9 = slong
184: 8, // 10 = srational
185: 4, // 11 = float
186: 8 // 12 = double
187: };
188:
189: private void initialize(SeekableStream stream) throws IOException {
190: long nextTagOffset;
191: int i, j;
192:
193: IFDOffset = stream.getFilePointer();
194:
195: numEntries = readUnsignedShort(stream);
196: fields = new TIFFField[numEntries];
197:
198: for (i = 0; i < numEntries; i++) {
199: int tag = readUnsignedShort(stream);
200: int type = readUnsignedShort(stream);
201: int count = (int) (readUnsignedInt(stream));
202: int value = 0;
203:
204: // The place to return to to read the next tag
205: nextTagOffset = stream.getFilePointer() + 4;
206:
207: try {
208: // If the tag data can't fit in 4 bytes, the next 4 bytes
209: // contain the starting offset of the data
210: if (count * sizeOfType[type] > 4) {
211: value = (int) (readUnsignedInt(stream));
212: stream.seek(value);
213: }
214: } catch (ArrayIndexOutOfBoundsException ae) {
215:
216: System.err.println(tag + " "
217: + JaiI18N.getString("TIFFDirectory4"));
218: // if the data type is unknown we should skip this TIFF Field
219: stream.seek(nextTagOffset);
220: continue;
221: }
222:
223: fieldIndex.put(new Integer(tag), new Integer(i));
224: Object obj = null;
225:
226: try {
227: switch (type) {
228: case TIFFField.TIFF_BYTE:
229: case TIFFField.TIFF_SBYTE:
230: case TIFFField.TIFF_UNDEFINED:
231: case TIFFField.TIFF_ASCII:
232: byte[] bvalues = new byte[count];
233: stream.readFully(bvalues, 0, count);
234:
235: if (type == TIFFField.TIFF_ASCII) {
236:
237: // Can be multiple strings
238: int index = 0, prevIndex = 0;
239: Vector v = new Vector();
240:
241: while (index < count) {
242:
243: while ((index < count)
244: && (bvalues[index++] != 0))
245: ;
246:
247: // When we encountered zero, means one string has ended
248: v.add(new String(bvalues, prevIndex,
249: (index - prevIndex)));
250: prevIndex = index;
251: }
252:
253: count = v.size();
254: String strings[] = new String[count];
255: for (int c = 0; c < count; c++) {
256: strings[c] = (String) v.elementAt(c);
257: }
258:
259: obj = strings;
260: } else {
261: obj = bvalues;
262: }
263:
264: break;
265:
266: case TIFFField.TIFF_SHORT:
267: char[] cvalues = new char[count];
268: for (j = 0; j < count; j++) {
269: cvalues[j] = (char) (readUnsignedShort(stream));
270: }
271: obj = cvalues;
272: break;
273:
274: case TIFFField.TIFF_LONG:
275: long[] lvalues = new long[count];
276: for (j = 0; j < count; j++) {
277: lvalues[j] = readUnsignedInt(stream);
278: }
279: obj = lvalues;
280: break;
281:
282: case TIFFField.TIFF_RATIONAL:
283: long[][] llvalues = new long[count][2];
284: for (j = 0; j < count; j++) {
285: llvalues[j][0] = readUnsignedInt(stream);
286: llvalues[j][1] = readUnsignedInt(stream);
287: }
288: obj = llvalues;
289: break;
290:
291: case TIFFField.TIFF_SSHORT:
292: short[] svalues = new short[count];
293: for (j = 0; j < count; j++) {
294: svalues[j] = readShort(stream);
295: }
296: obj = svalues;
297: break;
298:
299: case TIFFField.TIFF_SLONG:
300: int[] ivalues = new int[count];
301: for (j = 0; j < count; j++) {
302: ivalues[j] = readInt(stream);
303: }
304: obj = ivalues;
305: break;
306:
307: case TIFFField.TIFF_SRATIONAL:
308: int[][] iivalues = new int[count][2];
309: for (j = 0; j < count; j++) {
310: iivalues[j][0] = readInt(stream);
311: iivalues[j][1] = readInt(stream);
312: }
313: obj = iivalues;
314: break;
315:
316: case TIFFField.TIFF_FLOAT:
317: float[] fvalues = new float[count];
318: for (j = 0; j < count; j++) {
319: fvalues[j] = readFloat(stream);
320: }
321: obj = fvalues;
322: break;
323:
324: case TIFFField.TIFF_DOUBLE:
325: double[] dvalues = new double[count];
326: for (j = 0; j < count; j++) {
327: dvalues[j] = readDouble(stream);
328: }
329: obj = dvalues;
330: break;
331:
332: default:
333: System.err.println(JaiI18N
334: .getString("TIFFDirectory0"));
335: break;
336: }
337:
338: fields[i] = new TIFFField(tag, type, count, obj);
339: } catch (EOFException eofe) {
340: // The TIFF 6.0 fields have tag numbers less than or equal
341: // to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright).
342: // If there is an error reading a baseline tag, then re-throw
343: // the exception and fail; otherwise continue with the next
344: // field.
345: if (tag <= 532 || tag == 33432) {
346: throw eofe;
347: }
348:
349: fieldIndex.remove(new Integer(tag));
350: // XXX Warning message
351: }
352:
353: stream.seek(nextTagOffset);
354: }
355:
356: // Read the offset of the next IFD.
357: nextIFDOffset = readUnsignedInt(stream);
358: }
359:
360: /** Returns the number of directory entries. */
361: public int getNumEntries() {
362: return numEntries;
363: }
364:
365: /**
366: * Returns the value of a given tag as a <code>TIFFField</code>,
367: * or <code>null</code> if the tag is not present.
368: *
369: * <p>Note that the value (data) of the <code>TIFFField</code>
370: * will always be the actual field value regardless of the number of
371: * bytes required for that value. This is the case despite the fact
372: * that the TIFF <i>IFD Entry</i> corresponding to the field may
373: * actually contain the offset to the field's value rather than
374: * the value itself (the latter occurring if and only if the
375: * value fits into 4 bytes). In other words, the value of the
376: * field will already have been read from the TIFF stream.</p>
377: */
378: public TIFFField getField(int tag) {
379: Integer i = (Integer) fieldIndex.get(new Integer(tag));
380: if (i == null) {
381: return null;
382: } else {
383: return fields[i.intValue()];
384: }
385: }
386:
387: /**
388: * Returns true if a tag appears in the directory.
389: */
390: public boolean isTagPresent(int tag) {
391: return fieldIndex.containsKey(new Integer(tag));
392: }
393:
394: /**
395: * Returns an ordered array of ints indicating the tag
396: * values.
397: */
398: public int[] getTags() {
399: int[] tags = new int[fieldIndex.size()];
400: Enumeration enumeration = fieldIndex.keys();
401: int i = 0;
402:
403: while (enumeration.hasMoreElements()) {
404: tags[i++] = ((Integer) enumeration.nextElement())
405: .intValue();
406: }
407:
408: return tags;
409: }
410:
411: /**
412: * Returns an array of TIFFFields containing all the fields
413: * in this directory.
414: */
415: public TIFFField[] getFields() {
416: return fields;
417: }
418:
419: /**
420: * Returns the value of a particular index of a given tag as a
421: * byte. The caller is responsible for ensuring that the tag is
422: * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
423: * TIFF_UNDEFINED.
424: */
425: public byte getFieldAsByte(int tag, int index) {
426: Integer i = (Integer) fieldIndex.get(new Integer(tag));
427: byte[] b = (fields[i.intValue()]).getAsBytes();
428: return b[index];
429: }
430:
431: /**
432: * Returns the value of index 0 of a given tag as a
433: * byte. The caller is responsible for ensuring that the tag is
434: * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
435: * TIFF_UNDEFINED.
436: */
437: public byte getFieldAsByte(int tag) {
438: return getFieldAsByte(tag, 0);
439: }
440:
441: /**
442: * Returns the value of a particular index of a given tag as a
443: * long. The caller is responsible for ensuring that the tag is
444: * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
445: * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
446: */
447: public long getFieldAsLong(int tag, int index) {
448: Integer i = (Integer) fieldIndex.get(new Integer(tag));
449: return (fields[i.intValue()]).getAsLong(index);
450: }
451:
452: /**
453: * Returns the value of index 0 of a given tag as a
454: * long. The caller is responsible for ensuring that the tag is
455: * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
456: * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
457: */
458: public long getFieldAsLong(int tag) {
459: return getFieldAsLong(tag, 0);
460: }
461:
462: /**
463: * Returns the value of a particular index of a given tag as a
464: * float. The caller is responsible for ensuring that the tag is
465: * present and has numeric type (all but TIFF_UNDEFINED and
466: * TIFF_ASCII).
467: */
468: public float getFieldAsFloat(int tag, int index) {
469: Integer i = (Integer) fieldIndex.get(new Integer(tag));
470: return fields[i.intValue()].getAsFloat(index);
471: }
472:
473: /**
474: * Returns the value of index 0 of a given tag as a float. The
475: * caller is responsible for ensuring that the tag is present and
476: * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
477: */
478: public float getFieldAsFloat(int tag) {
479: return getFieldAsFloat(tag, 0);
480: }
481:
482: /**
483: * Returns the value of a particular index of a given tag as a
484: * double. The caller is responsible for ensuring that the tag is
485: * present and has numeric type (all but TIFF_UNDEFINED and
486: * TIFF_ASCII).
487: */
488: public double getFieldAsDouble(int tag, int index) {
489: Integer i = (Integer) fieldIndex.get(new Integer(tag));
490: return fields[i.intValue()].getAsDouble(index);
491: }
492:
493: /**
494: * Returns the value of index 0 of a given tag as a double. The
495: * caller is responsible for ensuring that the tag is present and
496: * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
497: */
498: public double getFieldAsDouble(int tag) {
499: return getFieldAsDouble(tag, 0);
500: }
501:
502: // Methods to read primitive data types from the stream
503:
504: private short readShort(SeekableStream stream) throws IOException {
505: if (isBigEndian) {
506: return stream.readShort();
507: } else {
508: return stream.readShortLE();
509: }
510: }
511:
512: private int readUnsignedShort(SeekableStream stream)
513: throws IOException {
514: if (isBigEndian) {
515: return stream.readUnsignedShort();
516: } else {
517: return stream.readUnsignedShortLE();
518: }
519: }
520:
521: private int readInt(SeekableStream stream) throws IOException {
522: if (isBigEndian) {
523: return stream.readInt();
524: } else {
525: return stream.readIntLE();
526: }
527: }
528:
529: private long readUnsignedInt(SeekableStream stream)
530: throws IOException {
531: if (isBigEndian) {
532: return stream.readUnsignedInt();
533: } else {
534: return stream.readUnsignedIntLE();
535: }
536: }
537:
538: private long readLong(SeekableStream stream) throws IOException {
539: if (isBigEndian) {
540: return stream.readLong();
541: } else {
542: return stream.readLongLE();
543: }
544: }
545:
546: private float readFloat(SeekableStream stream) throws IOException {
547: if (isBigEndian) {
548: return stream.readFloat();
549: } else {
550: return stream.readFloatLE();
551: }
552: }
553:
554: private double readDouble(SeekableStream stream) throws IOException {
555: if (isBigEndian) {
556: return stream.readDouble();
557: } else {
558: return stream.readDoubleLE();
559: }
560: }
561:
562: private static int readUnsignedShort(SeekableStream stream,
563: boolean isBigEndian) throws IOException {
564: if (isBigEndian) {
565: return stream.readUnsignedShort();
566: } else {
567: return stream.readUnsignedShortLE();
568: }
569: }
570:
571: private static long readUnsignedInt(SeekableStream stream,
572: boolean isBigEndian) throws IOException {
573: if (isBigEndian) {
574: return stream.readUnsignedInt();
575: } else {
576: return stream.readUnsignedIntLE();
577: }
578: }
579:
580: // Utilities
581:
582: /**
583: * Returns the number of image directories (subimages) stored in a
584: * given TIFF file, represented by a <code>SeekableStream</code>.
585: */
586: public static int getNumDirectories(SeekableStream stream)
587: throws IOException {
588: long pointer = stream.getFilePointer(); // Save stream pointer
589:
590: stream.seek(0L);
591: int endian = stream.readUnsignedShort();
592: if (!isValidEndianTag(endian)) {
593: throw new IllegalArgumentException(JaiI18N
594: .getString("TIFFDirectory1"));
595: }
596: boolean isBigEndian = (endian == 0x4d4d);
597: int magic = readUnsignedShort(stream, isBigEndian);
598: if (magic != 42) {
599: throw new IllegalArgumentException(JaiI18N
600: .getString("TIFFDirectory2"));
601: }
602:
603: stream.seek(4L);
604: long offset = readUnsignedInt(stream, isBigEndian);
605:
606: int numDirectories = 0;
607: while (offset != 0L) {
608: ++numDirectories;
609:
610: // EOFException means IFD was probably not properly terminated.
611: try {
612: stream.seek(offset);
613: int entries = readUnsignedShort(stream, isBigEndian);
614: stream.skip(12 * entries);
615: offset = readUnsignedInt(stream, isBigEndian);
616: } catch (EOFException eof) {
617: numDirectories--;
618: break;
619: }
620: }
621:
622: stream.seek(pointer); // Reset stream pointer
623: return numDirectories;
624: }
625:
626: /**
627: * Returns a boolean indicating whether the byte order used in the
628: * the TIFF file is big-endian (i.e. whether the byte order is from
629: * the most significant to the least significant)
630: */
631: public boolean isBigEndian() {
632: return isBigEndian;
633: }
634:
635: /**
636: * Returns the offset of the IFD corresponding to this
637: * <code>TIFFDirectory</code>.
638: */
639: public long getIFDOffset() {
640: return IFDOffset;
641: }
642:
643: /**
644: * Returns the offset of the next IFD after the IFD corresponding to this
645: * <code>TIFFDirectory</code>.
646: */
647: public long getNextIFDOffset() {
648: return nextIFDOffset;
649: }
650: }
|