Source code: org/apache/batik/ext/awt/image/codec/tiff/TIFFDirectory.java
1 /*
2
3 Copyright 2001,2003 The Apache Software Foundation
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 */
18 package org.apache.batik.ext.awt.image.codec.tiff;
19
20 import java.io.IOException;
21 import java.io.Serializable;
22 import java.util.Iterator;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Vector;
26
27 import org.apache.batik.ext.awt.image.codec.SeekableStream;
28
29 /**
30 * A class representing an Image File Directory (IFD) from a TIFF 6.0
31 * stream. The TIFF file format is described in more detail in the
32 * comments for the TIFFDescriptor class.
33 *
34 * <p> A TIFF IFD consists of a set of TIFFField tags. Methods are
35 * provided to query the set of tags and to obtain the raw field
36 * array. In addition, convenience methods are provided for acquiring
37 * the values of tags that contain a single value that fits into a
38 * byte, int, long, float, or double.
39 *
40 * <p> Every TIFF file is made up of one or more public IFDs that are
41 * joined in a linked list, rooted in the file header. A file may
42 * also contain so-called private IFDs that are referenced from
43 * tag data and do not appear in the main list.
44 *
45 * <p><b> This class is not a committed part of the JAI API. It may
46 * be removed or changed in future releases of JAI.</b>
47 *
48 * @see TIFFField
49 */
50 public class TIFFDirectory extends Object implements Serializable {
51
52 /** A boolean storing the endianness of the stream. */
53 boolean isBigEndian;
54
55 /** The number of entries in the IFD. */
56 int numEntries;
57
58 /** An array of TIFFFields. */
59 TIFFField[] fields;
60
61 /** A Hashtable indexing the fields by tag number. */
62 Map fieldIndex = new HashMap();
63
64 /** The offset of this IFD. */
65 long IFDOffset = 8;
66
67 /** The offset of the next IFD. */
68 long nextIFDOffset = 0;
69
70 /** The default constructor. */
71 TIFFDirectory() {}
72
73 private static boolean isValidEndianTag(int endian) {
74 return ((endian == 0x4949) || (endian == 0x4d4d));
75 }
76
77 /**
78 * Constructs a TIFFDirectory from a SeekableStream.
79 * The directory parameter specifies which directory to read from
80 * the linked list present in the stream; directory 0 is normally
81 * read but it is possible to store multiple images in a single
82 * TIFF file by maintaing multiple directories.
83 *
84 * @param stream a SeekableStream to read from.
85 * @param directory the index of the directory to read.
86 */
87 public TIFFDirectory(SeekableStream stream, int directory)
88 throws IOException {
89
90 long global_save_offset = stream.getFilePointer();
91 long ifd_offset;
92
93 // Read the TIFF header
94 stream.seek(0L);
95 int endian = stream.readUnsignedShort();
96 if (!isValidEndianTag(endian)) {
97 throw new
98 IllegalArgumentException("TIFFDirectory1");
99 }
100 isBigEndian = (endian == 0x4d4d);
101
102 int magic = readUnsignedShort(stream);
103 if (magic != 42) {
104 throw new
105 IllegalArgumentException("TIFFDirectory2");
106 }
107
108 // Get the initial ifd offset as an unsigned int (using a long)
109 ifd_offset = readUnsignedInt(stream);
110
111 for (int i = 0; i < directory; i++) {
112 if (ifd_offset == 0L) {
113 throw new
114 IllegalArgumentException("TIFFDirectory3");
115 }
116
117 stream.seek(ifd_offset);
118 int entries = readUnsignedShort(stream);
119 stream.skip(12*entries);
120
121 ifd_offset = readUnsignedInt(stream);
122 }
123
124 stream.seek(ifd_offset);
125 initialize(stream);
126 stream.seek(global_save_offset);
127 }
128
129 /**
130 * Constructs a TIFFDirectory by reading a SeekableStream.
131 * The ifd_offset parameter specifies the stream offset from which
132 * to begin reading; this mechanism is sometimes used to store
133 * private IFDs within a TIFF file that are not part of the normal
134 * sequence of IFDs.
135 *
136 * @param stream a SeekableStream to read from.
137 * @param ifd_offset the long byte offset of the directory.
138 * @param directory the index of the directory to read beyond the
139 * one at the current stream offset; zero indicates the IFD
140 * at the current offset.
141 */
142 public TIFFDirectory(SeekableStream stream, long ifd_offset, int directory)
143 throws IOException {
144
145 long global_save_offset = stream.getFilePointer();
146 stream.seek(0L);
147 int endian = stream.readUnsignedShort();
148 if (!isValidEndianTag(endian)) {
149 throw new
150 IllegalArgumentException("TIFFDirectory1");
151 }
152 isBigEndian = (endian == 0x4d4d);
153
154 // Seek to the first IFD.
155 stream.seek(ifd_offset);
156
157 // Seek to desired IFD if necessary.
158 int dirNum = 0;
159 while(dirNum < directory) {
160 // Get the number of fields in the current IFD.
161 int numEntries = readUnsignedShort(stream);
162
163 // Skip to the next IFD offset value field.
164 stream.seek(ifd_offset + 12*numEntries);
165
166 // Read the offset to the next IFD beyond this one.
167 ifd_offset = readUnsignedInt(stream);
168
169 // Seek to the next IFD.
170 stream.seek(ifd_offset);
171
172 // Increment the directory.
173 dirNum++;
174 }
175
176 initialize(stream);
177 stream.seek(global_save_offset);
178 }
179
180 private static final int[] sizeOfType = {
181 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 Vector v = new Vector();
245
246 while (index < count) {
247
248 while ((index < count) && (bvalues[index++] != 0));
249
250 // When we encountered zero, means one string has ended
251 v.add(new String(bvalues, prevIndex,
252 (index - prevIndex)) );
253 prevIndex = index;
254 }
255
256 count = v.size();
257 String strings[] = new String[count];
258 for (int c = 0 ; c < count; c++) {
259 strings[c] = (String)v.elementAt(c);
260 }
261
262 obj = strings;
263 } else {
264 obj = bvalues;
265 }
266
267 break;
268
269 case TIFFField.TIFF_SHORT:
270 char[] cvalues = new char[count];
271 for (j = 0; j < count; j++) {
272 cvalues[j] = (char)(readUnsignedShort(stream));
273 }
274 obj = cvalues;
275 break;
276
277 case TIFFField.TIFF_LONG:
278 long[] lvalues = new long[count];
279 for (j = 0; j < count; j++) {
280 lvalues[j] = readUnsignedInt(stream);
281 }
282 obj = lvalues;
283 break;
284
285 case TIFFField.TIFF_RATIONAL:
286 long[][] llvalues = new long[count][2];
287 for (j = 0; j < count; j++) {
288 llvalues[j][0] = readUnsignedInt(stream);
289 llvalues[j][1] = readUnsignedInt(stream);
290 }
291 obj = llvalues;
292 break;
293
294 case TIFFField.TIFF_SSHORT:
295 short[] svalues = new short[count];
296 for (j = 0; j < count; j++) {
297 svalues[j] = readShort(stream);
298 }
299 obj = svalues;
300 break;
301
302 case TIFFField.TIFF_SLONG:
303 int[] ivalues = new int[count];
304 for (j = 0; j < count; j++) {
305 ivalues[j] = readInt(stream);
306 }
307 obj = ivalues;
308 break;
309
310 case TIFFField.TIFF_SRATIONAL:
311 int[][] iivalues = new int[count][2];
312 for (j = 0; j < count; j++) {
313 iivalues[j][0] = readInt(stream);
314 iivalues[j][1] = readInt(stream);
315 }
316 obj = iivalues;
317 break;
318
319 case TIFFField.TIFF_FLOAT:
320 float[] fvalues = new float[count];
321 for (j = 0; j < count; j++) {
322 fvalues[j] = readFloat(stream);
323 }
324 obj = fvalues;
325 break;
326
327 case TIFFField.TIFF_DOUBLE:
328 double[] dvalues = new double[count];
329 for (j = 0; j < count; j++) {
330 dvalues[j] = readDouble(stream);
331 }
332 obj = dvalues;
333 break;
334
335 default:
336 System.err.println("TIFFDirectory0");
337 break;
338 }
339
340 fields[i] = new TIFFField(tag, type, count, obj);
341 stream.seek(nextTagOffset);
342 }
343
344 // Read the offset of the next IFD.
345 nextIFDOffset = readUnsignedInt(stream);
346 }
347
348 /** Returns the number of directory entries. */
349 public int getNumEntries() {
350 return numEntries;
351 }
352
353 /**
354 * Returns the value of a given tag as a TIFFField,
355 * or null if the tag is not present.
356 */
357 public TIFFField getField(int tag) {
358 Integer i = (Integer)fieldIndex.get(new Integer(tag));
359 if (i == null) {
360 return null;
361 } else {
362 return fields[i.intValue()];
363 }
364 }
365
366 /**
367 * Returns true if a tag appears in the directory.
368 */
369 public boolean isTagPresent(int tag) {
370 return fieldIndex.containsKey(new Integer(tag));
371 }
372
373 /**
374 * Returns an ordered array of ints indicating the tag
375 * values.
376 */
377 public int[] getTags() {
378 int[] tags = new int[fieldIndex.size()];
379 Iterator iter = fieldIndex.keySet().iterator();
380 int i = 0;
381
382 while (iter.hasNext()) {
383 tags[i++] = ((Integer)iter.next()).intValue();
384 }
385
386 return tags;
387 }
388
389 /**
390 * Returns an array of TIFFFields containing all the fields
391 * in this directory.
392 */
393 public TIFFField[] getFields() {
394 return fields;
395 }
396
397 /**
398 * Returns the value of a particular index of a given tag as a
399 * byte. The caller is responsible for ensuring that the tag is
400 * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
401 * TIFF_UNDEFINED.
402 */
403 public byte getFieldAsByte(int tag, int index) {
404 Integer i = (Integer)fieldIndex.get(new Integer(tag));
405 byte [] b = (fields[i.intValue()]).getAsBytes();
406 return b[index];
407 }
408
409 /**
410 * Returns the value of index 0 of a given tag as a
411 * byte. The caller is responsible for ensuring that the tag is
412 * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
413 * TIFF_UNDEFINED.
414 */
415 public byte getFieldAsByte(int tag) {
416 return getFieldAsByte(tag, 0);
417 }
418
419 /**
420 * Returns the value of a particular index of a given tag as a
421 * long. The caller is responsible for ensuring that the tag is
422 * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
423 * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
424 */
425 public long getFieldAsLong(int tag, int index) {
426 Integer i = (Integer)fieldIndex.get(new Integer(tag));
427 return (fields[i.intValue()]).getAsLong(index);
428 }
429
430 /**
431 * Returns the value of index 0 of a given tag as a
432 * long. The caller is responsible for ensuring that the tag is
433 * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
434 * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
435 */
436 public long getFieldAsLong(int tag) {
437 return getFieldAsLong(tag, 0);
438 }
439
440 /**
441 * Returns the value of a particular index of a given tag as a
442 * float. The caller is responsible for ensuring that the tag is
443 * present and has numeric type (all but TIFF_UNDEFINED and
444 * TIFF_ASCII).
445 */
446 public float getFieldAsFloat(int tag, int index) {
447 Integer i = (Integer)fieldIndex.get(new Integer(tag));
448 return fields[i.intValue()].getAsFloat(index);
449 }
450
451 /**
452 * Returns the value of index 0 of a given tag as a float. The
453 * caller is responsible for ensuring that the tag is present and
454 * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
455 */
456 public float getFieldAsFloat(int tag) {
457 return getFieldAsFloat(tag, 0);
458 }
459
460 /**
461 * Returns the value of a particular index of a given tag as a
462 * double. The caller is responsible for ensuring that the tag is
463 * present and has numeric type (all but TIFF_UNDEFINED and
464 * TIFF_ASCII).
465 */
466 public double getFieldAsDouble(int tag, int index) {
467 Integer i = (Integer)fieldIndex.get(new Integer(tag));
468 return fields[i.intValue()].getAsDouble(index);
469 }
470
471 /**
472 * Returns the value of index 0 of a given tag as a double. The
473 * caller is responsible for ensuring that the tag is present and
474 * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
475 */
476 public double getFieldAsDouble(int tag) {
477 return getFieldAsDouble(tag, 0);
478 }
479
480 // Methods to read primitive data types from the stream
481
482 private short readShort(SeekableStream stream)
483 throws IOException {
484 if (isBigEndian) {
485 return stream.readShort();
486 } else {
487 return stream.readShortLE();
488 }
489 }
490
491 private int readUnsignedShort(SeekableStream stream)
492 throws IOException {
493 if (isBigEndian) {
494 return stream.readUnsignedShort();
495 } else {
496 return stream.readUnsignedShortLE();
497 }
498 }
499
500 private int readInt(SeekableStream stream)
501 throws IOException {
502 if (isBigEndian) {
503 return stream.readInt();
504 } else {
505 return stream.readIntLE();
506 }
507 }
508
509 private long readUnsignedInt(SeekableStream stream)
510 throws IOException {
511 if (isBigEndian) {
512 return stream.readUnsignedInt();
513 } else {
514 return stream.readUnsignedIntLE();
515 }
516 }
517
518 private long readLong(SeekableStream stream)
519 throws IOException {
520 if (isBigEndian) {
521 return stream.readLong();
522 } else {
523 return stream.readLongLE();
524 }
525 }
526
527 private float readFloat(SeekableStream stream)
528 throws IOException {
529 if (isBigEndian) {
530 return stream.readFloat();
531 } else {
532 return stream.readFloatLE();
533 }
534 }
535
536 private double readDouble(SeekableStream stream)
537 throws IOException {
538 if (isBigEndian) {
539 return stream.readDouble();
540 } else {
541 return stream.readDoubleLE();
542 }
543 }
544
545 private static int readUnsignedShort(SeekableStream stream,
546 boolean isBigEndian)
547 throws IOException {
548 if (isBigEndian) {
549 return stream.readUnsignedShort();
550 } else {
551 return stream.readUnsignedShortLE();
552 }
553 }
554
555 private static long readUnsignedInt(SeekableStream stream,
556 boolean isBigEndian)
557 throws IOException {
558 if (isBigEndian) {
559 return stream.readUnsignedInt();
560 } else {
561 return stream.readUnsignedIntLE();
562 }
563 }
564
565 // Utilities
566
567 /**
568 * Returns the number of image directories (subimages) stored in a
569 * given TIFF file, represented by a <code>SeekableStream</code>.
570 */
571 public static int getNumDirectories(SeekableStream stream)
572 throws IOException{
573 long pointer = stream.getFilePointer(); // Save stream pointer
574
575 stream.seek(0L);
576 int endian = stream.readUnsignedShort();
577 if (!isValidEndianTag(endian)) {
578 throw new
579 IllegalArgumentException("TIFFDirectory1");
580 }
581 boolean isBigEndian = (endian == 0x4d4d);
582 int magic = readUnsignedShort(stream, isBigEndian);
583 if (magic != 42) {
584 throw new
585 IllegalArgumentException("TIFFDirectory2");
586 }
587
588 stream.seek(4L);
589 long offset = readUnsignedInt(stream, isBigEndian);
590
591 int numDirectories = 0;
592 while (offset != 0L) {
593 ++numDirectories;
594
595 stream.seek(offset);
596 int entries = readUnsignedShort(stream, isBigEndian);
597 stream.skip(12*entries);
598 offset = readUnsignedInt(stream, isBigEndian);
599 }
600
601 stream.seek(pointer); // Reset stream pointer
602 return numDirectories;
603 }
604
605 /**
606 * Returns a boolean indicating whether the byte order used in the
607 * the TIFF file is big-endian. That is, whether the byte order is from
608 * the most significant to the least significant.
609 */
610 public boolean isBigEndian() {
611 return isBigEndian;
612 }
613
614 /**
615 * Returns the offset of the IFD corresponding to this
616 * <code>TIFFDirectory</code>.
617 */
618 public long getIFDOffset() {
619 return IFDOffset;
620 }
621
622 /**
623 * Returns the offset of the next IFD after the IFD corresponding to this
624 * <code>TIFFDirectory</code>.
625 */
626 public long getNextIFDOffset() {
627 return nextIFDOffset;
628 }
629 }