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