001    /*
002     * TIFFCodec
003     * 
004     * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.codecs.tiff;
009    
010    import java.io.IOException;
011    import java.io.RandomAccessFile;
012    import java.util.Hashtable;
013    import java.util.Vector;
014    import net.sourceforge.jiu.codecs.CodecMode;
015    import net.sourceforge.jiu.codecs.ImageCodec;
016    import net.sourceforge.jiu.codecs.InvalidFileStructureException;
017    import net.sourceforge.jiu.codecs.UnsupportedCodecModeException;
018    import net.sourceforge.jiu.codecs.UnsupportedTypeException;
019    import net.sourceforge.jiu.codecs.WrongFileFormatException;
020    import net.sourceforge.jiu.data.MemoryBilevelImage;
021    import net.sourceforge.jiu.data.MemoryGray16Image;
022    import net.sourceforge.jiu.data.MemoryGray8Image;
023    import net.sourceforge.jiu.data.MemoryPaletted8Image;
024    import net.sourceforge.jiu.data.MemoryRGB24Image;
025    import net.sourceforge.jiu.data.MemoryRGB48Image;
026    import net.sourceforge.jiu.data.PixelImage;
027    import net.sourceforge.jiu.ops.MissingParameterException;
028    import net.sourceforge.jiu.ops.OperationFailedException;
029    import net.sourceforge.jiu.ops.WrongParameterException;
030    
031    /**
032     * A codec to read Tagged Image File Format (TIFF) image files.
033     *
034     * <h3>Usage example</h3>
035     * Load an image from a TIFF file.
036     * <pre>
037     * TIFFCodec codec = new TIFFCodec();
038     * codec.setFile("image.tif", CodecMode.LOAD);
039     * codec.process();
040     * PixelImage loadedImage = codec.getImage();
041     * </pre>
042     * Saving images is not supported by this codec.
043     *
044     * <h3>Compression types</h3>
045     * <h4>Reading</h4>
046     * The TIFF package supports the following compression types when reading:
047     * <ul>
048     * <li><em>Uncompressed</em>. Compression method number 1. Works with all types of image data. See {@link TIFFDecoderUncompressed}.</li>
049     * <li><em>Packbits</em>. Compression method number 32773. Works with all types of image data. See {@link TIFFDecoderPackbits}.</li>
050     * <li><em>CCITT Group 3 1-Dimensional Modified Huffman runlength encoding</em>. Compression method number 2. 
051     *  Works with bilevel image data only. See {@link TIFFDecoderModifiedHuffman}.</li>
052     * <li><em>Deflated</em>. Compression method number 8 or 32946. Works with all types of image data. See {@link TIFFDecoderDeflated}.</li>
053     * <li><em>LogLuv RLE</em> and <em>LogLuv 24</em>. Compression method numbers 34676 and 34677. Works only with LogLuv color data. See {@link TIFFDecoderLogLuv}.</li>
054     * </ul>
055     * <p>
056     * Note that you can write your own decoder (extending {@link TIFFDecoder}) for any compression type
057     * you want.
058     * </p>
059     *
060     * <h3>Image types</h3>
061     * <h4>Reading</h4>
062     * The TIFF package supports the following image / color types when reading:
063     * <ul>
064     * <li><em>Black & white</em>. JIU image data type {@link net.sourceforge.jiu.data.BilevelImage}.</li>
065     * <li><em>Grayscale, 4 and 8 bits per pixel</em>. JIU image data type {@link net.sourceforge.jiu.data.Gray8Image}.</li>
066     * <li>TODO add other image types</li>
067     * </ul>
068     * <p>
069     * Note that you can write your own decoder (extending {@link TIFFDecoder}) for any compression type
070     * you want.
071     * </p>
072     *
073     * <h4>Writing</h4>
074     * <p>Writing TIFFs is not supported. 
075     * I don't know if or when it will be supported.</p>
076     *
077     * <h3>Strips and tiles</h3>
078     * The early versions of TIFF considered an image to be a sequence of <em>strips</em>.
079     * Each strip was a rectangular part of the image, as wide as the complete image, 
080     * and with a certain height defined by the <em>rows per strip</em> tag.
081     * So with a number of rows per strip of 10, and an image height of 200, you would
082     * have to store 20 strips.
083     * It was recommended that a strip should not be larger than 8 KB (RAM was tighter
084     * in those days).
085     * The rule of thumb to define the number of rows per strip was to see how many rows
086     * would fit into 8 KB.
087     * <p>
088     * Later, the concept of <em>tiles</em> was added to the TIFF specs.
089     * Tiled TIFFs are separated into rectangles that not only had a defineable
090     * height but also a defineable width (tile width and tile height are also stored in
091     * corresponding tags).
092     * <p>
093     * Obviously, strips are just a special case of tiles, with the tile width being equal
094     * to image width.
095     * That is why JIU internally only deals with tiles.
096     * The only difference: No row padding takes place for strips.
097     * In a tiled image with a tile height of 10 and an image height of 14, 
098     * the image is two tiles high.
099     * 
100     * <h3>Number of images</h3>
101     * TIFF allows for multiple images in a single file.
102     * This codec regards the image index, queries {@link #getImageIndex} and skips to the
103     * correct image.
104     *
105     * <h3>Bounds</h3>
106     * The bounds concept of JIU is supported by this codec.
107     * So you can specify bounds of a rectangular part of an image that you want to load
108     * instead of loading the complete image.
109     *
110     * <h3>Color spaces</h3>
111     * The following color spaces are understood when reading truecolor TIFF files.
112     * <ul>
113     * <li><em>RGB</em> - should cover most truecolor files.</li>
114     * <li><em>CMYK</em> - is supported, but colors may not be exactly right.
115     *  CMYK data is converted to RGB on the fly, so the codec user never accesses CMYK data.</li>
116     * <li><em>LogLuv</em> - is supported, but not all flavors yet.</li>
117     * </ul>
118     * <h3>Physical resolution</h3>
119     * DPI information can be stored in TIFF files.
120     * If that information is available, this codec retrieves it so that it
121     * can be queried using {@link #getDpiX} and {@link #getDpiY}.
122     *
123     * <h3>Background information on TIFF</h3>
124     * TIFF is an important image file format for DTP (desktop publishing).
125     * The advantages of TIFF include its flexibility, availability of libraries to read
126     * and write TIFF files and its good support in existing software.
127     * The major disadvantage of TIFF is its complexity, which makes it hard for software
128     * to support all possible valid TIFF files.
129     * <p>
130     * TIFF was created by Aldus and now <em>belongs</em> to Adobe, who offer a specification document: 
131     * <a target="_top" href="http://partners.adobe.com/asn/developer/PDFS/TN/TIFF6.pdf">TIFF
132     * (Tagged Image File Format) 6.0 Specification</a> (updated on Web September, 20 1995, 
133     * document dated June, 3 1992) (PDF: 385 KB / 121 pages).
134     * <p>
135     * Other good references include the <a target="_top" href="http://www.libtiff.org">homepage 
136     * of libtiff</a>, a free C library to read and write TIFF files and
137     * <a target="_top" href="http://home.earthlink.net/~ritter/tiff/">The Unofficial TIFF
138     * homepage</a> by <a href="mailto:ritter@earthlink.net">Niles Ritter</a>.
139     * Also see <a target="_top" href="http://dmoz.org/Computers/Data_Formats/Graphics/Pixmap/TIFF/">the TIFF section</a>
140     * of the <a target="_top" href="http://www.dmoz.org">Open Directory</a>.
141     * <p>
142     * TIFF is used for various specialized tasks.
143     * As an example, see <a target="_top" href="http://www.remotesensing.org/geotiff/geotiff.html">GeoTIFF</a> (geographical
144     * data) or <a target="_top" href="http://www.ba.wakwak.com/~tsuruzoh/index-e.html">EXIF</a>
145     * (digital camera metadata; this is actually a TIFF directory embedded in a JPEG header).
146     * <p>
147     * Here's a list of features that make TIFF quite complex:
148     * <ul>
149     * <li>More than one image can be stored in a TIFF file.</li>
150     * <li>Integer values that are larger than one byte can be in either little or big endian byte order.</li>
151     * <li>Various color types are supported (bilevel, gray, paletted, all kinds of color spaces (RGB / YCbCr / CMYK).
152     *  It's easy to add new color types, so this list can grow.</li>
153     * <li>The meta data (information that describes the image and how it is stored) can be distributed all over 
154     *  the file.</li>
155     * <li>Image data is stored as packed bytes, 4-bit-nibbles, bytes and 16-bit-integers. Other types are possible
156     *  as well.</li>
157     * <li>Various compression types are supported; not all types can be used on all color types.</li>
158     * <li>Image data can be stored in strips or tiles.</li>
159     * <li>An arbitrary number of non-image-data samples can stored within the image data.</li>
160     * <li>Color types with more than one sample per pixel can store data in an interleaved (<em>chunky</em>)
161     *  way or in planes.</li>
162     * <li>Different ways of defining black and white are possible with bilevel and grayscale images.</li>
163     * </ul>
164     * 
165     * @author Marco Schmidt
166     */
167    public class TIFFCodec extends ImageCodec implements TIFFConstants
168    {
169            private static final boolean DEBUG = true;
170            public static final int BYTE_ORDER_MOTOROLA = 0;
171            public static final int BYTE_ORDER_INTEL = 1;
172            private static final int MAGIC_INTEL = 0x49492a00;
173            private static final int MAGIC_MOTOROLA = 0x4d4d002a;
174            private static final int MAX_PRINT_ELEMENTS = 20;
175    
176            private int byteOrder;
177            private int nextIfdOffset;
178    
179            private static Hashtable decoders;
180            static
181            {
182                    decoders = new Hashtable();
183                    registerDecoder(TIFFDecoderDeflated.class);
184                    registerDecoder(TIFFDecoderModifiedHuffman.class);
185                    registerDecoder(TIFFDecoderPackbits.class);
186                    registerDecoder(TIFFDecoderUncompressed.class);
187                    registerDecoder(TIFFDecoderLogLuv.class);
188            }
189    
190            /**
191             * If the current byte order is {@link BYTE_ORDER_MOTOROLA} and the type 
192             * argument is {@link TiffConstants.TAG_TYPE_BYTE} or
193             * {@link TiffConstants.TAG_TYPE_SHORT}, the value parameter must
194             * be adjusted by some bitshifting.
195             * If the above mentioned criteria are not met, the value argument is 
196             * returned without any modifications.
197             * <p>
198             * <em>Why this is necessary remains a mystery to me. Marco</em>
199             *
200             * @param value the int value which may have to be adjusted
201             * @return the value parameter which may have been modified
202             */
203            private int adjustInt(int value, int type)
204            {
205                    if (getByteOrder() == BYTE_ORDER_MOTOROLA)
206                    {
207                            if (type == TAG_TYPE_BYTE)
208                            {
209                                    return ((value >> 24) & 0xff);
210                            }
211                            else
212                            if (type == TAG_TYPE_SHORT)
213                            {
214                                    return ((value >> 16) & 0xff) | (((value >> 24) & 0xff) << 8);
215                            }
216                            else
217                            {
218                                    return value;
219                            }
220                    }
221                    else
222                    {
223                            return value;
224                    }
225            }
226    
227            private static TIFFDecoder createDecoder(TIFFCodec codec, TIFFImageFileDirectory ifd, int tileIndex) throws 
228                    IOException, 
229                    UnsupportedTypeException
230            {
231                    Integer compression = new Integer(ifd.getCompression());
232                    Class decoderClass = (Class)decoders.get(compression);
233                    if (decoderClass == null)
234                    {
235                            throw new UnsupportedTypeException("Could not create decoder for this compression type: " + 
236                                    compression.intValue());
237                    }
238                    Object instance;
239                    try
240                    {
241                            instance = decoderClass.newInstance();
242                    }
243                    catch (Exception e)
244                    {
245                            throw new UnsupportedTypeException("Could not create decoder for this compression type.");
246                    }
247                    if (instance instanceof TIFFDecoder)
248                    {
249                            TIFFDecoder decoder = (TIFFDecoder)instance;
250                            decoder.setCodec(codec);
251                            decoder.setTileIndex(tileIndex);
252                            decoder.setImageFileDirectory(ifd);
253                            try
254                            {
255                                    decoder.initialize();
256                            }
257                            catch (MissingParameterException mpe)
258                            {
259                                    throw new UnsupportedTypeException("Unable to initialize decoder: " + mpe.toString());
260                            }
261                            return decoder;
262                    }
263                    else
264                    {
265                            throw new UnsupportedTypeException("Could not create decoder for this compression type.");
266                    }
267            }
268    
269            /**
270             * Returns the current byte order, either 
271             * {@link #BYTE_ORDER_INTEL} or
272             * {@link #BYTE_ORDER_MOTOROLA}.
273             * @return current byte order
274             */
275            public int getByteOrder()
276            {
277                    return byteOrder;
278            }
279    
280            public String getFormatName()
281            {
282                    return "Tagged Image File Format (TIFF)";
283            }
284    
285            public String[] getMimeTypes()
286            {
287                    return new String[] {"image/tiff", "image/tif"};
288            }
289    
290            /**
291             * Returns the name of a tag in English.
292             * @param id of the tag for which a name is to be returned
293             * @return tag name as String or a question mark <code>?</code>
294             */
295            public static String getTagName(int id)
296            {
297                    switch(id)
298                    {
299                            case(TAG_ARTIST): return "Artist";
300                            case(TAG_BAD_FAX_LINES): return "Bad fax lines";
301                            case(TAG_BITS_PER_SAMPLE): return "Bits per sample";
302                            case(TAG_CELL_LENGTH): return "Cell length";
303                            case(TAG_CELL_WIDTH): return "Cell width";
304                            case(TAG_CLEAN_FAX_DATA): return "Clean fax data";
305                            case(TAG_COLOR_MAP): return "Color map";
306                            case(TAG_COMPRESSION): return "Compression";
307                            case(TAG_CONSECUTIVE_BAD_FAX_LINES): return "Consecutive bad fax lines";
308                            case(TAG_COPYRIGHT): return "Copyright";
309                            case(TAG_DATE_TIME): return "Date and time";
310                            case(TAG_DOCUMENT_NAME): return "Document name";
311                            case(TAG_EXTRA_SAMPLES): return "Extra samples";
312                            case(TAG_FILL_ORDER): return "Fill order";
313                            case(TAG_FREE_BYTE_COUNTS): return "Free byte counts";
314                            case(TAG_FREE_OFFSETS): return "Free offsets";
315                            case(TAG_GRAY_RESPONSE_CURVE): return "Gray response curve";
316                            case(TAG_GRAY_RESPONSE_UNIT): return "Gray response unit";
317                            case(TAG_HOST_COMPUTER): return "Host computer";
318                            case(TAG_IMAGE_DESCRIPTION): return "Image description";
319                            case(TAG_IMAGE_LENGTH): return "Image length";
320                            case(TAG_IMAGE_WIDTH): return "Image width";
321                            case(TAG_MAKE): return "Make";
322                            case(TAG_MAX_SAMPLE_VALUE): return "Maximum sample value";
323                            case(TAG_MIN_SAMPLE_VALUE): return "Minimum sample value";
324                            case(TAG_MODEL): return "Model";
325                            case(TAG_NEW_SUBFILE_TYPE): return "New subfile type";
326                            case(TAG_ORIENTATION): return "Orientation";
327                            case(TAG_PHOTOMETRIC_INTERPRETATION): return "Photometric interpretation";
328                            case(TAG_PLANAR_CONFIGURATION): return "Planar configuration";
329                            case(TAG_PREDICTOR): return "Predictor";
330                            case(TAG_RESOLUTION_UNIT): return "Resolution unit";
331                            case(TAG_RESOLUTION_X): return "Resolution X";
332                            case(TAG_RESOLUTION_Y): return "Resolution Y";
333                            case(TAG_ROWS_PER_STRIP): return "Rows per strip";
334                            case(TAG_SAMPLES_PER_PIXEL): return "Samples per pixel";
335                            case(TAG_SOFTWARE): return "Software";
336                            case(TAG_STRIP_BYTE_COUNTS): return "Strip byte counts";
337                            case(TAG_STRIP_OFFSETS): return "Strip offsets";
338                            case(TAG_TILE_BYTE_COUNTS): return "Byte counts";
339                            case(TAG_TILE_HEIGHT): return "Tile height";
340                            case(TAG_TILE_OFFSETS): return "Tile offsets";
341                            case(TAG_TILE_WIDTH): return "Tile widths";
342                            default: return "?";
343                    }
344            }
345    
346            public boolean isLoadingSupported()
347            {
348                    return true;
349            }
350    
351            public boolean isSavingSupported()
352            {
353                    return false;
354            }
355    
356            /**
357             * Attempts to load an image from a file in the TIFF format.
358             * Some options can be given to this codec before the call
359             * to this load method.
360             * <ul>
361             * <li>You must provide a {@link java.io.RandomAccessFile} using
362             *   {@link #setInput(java.io.RandomAccessFile)}.</li>
363             * <li>If there is more than one image in the input file, you
364             *   can make the codec load it by calling {@link #setImageIndex(int)}.
365             *   The argument is the index of the image, the first being <code>0</code>,
366             *   the second <code>1</code> and so on. The default is <code>0</code>.</li>
367             * </ul>
368             *
369             * @return the image if everything was successful
370             * @throws InvalidFileStructureException if the TIFF file was corrupt in some way
371             * @throws IOException if there were errors reading from the input file
372             * @throws UnsupportedTypeException if the flavour of TIFF encountered in the input
373             *  file is not supported yet
374             * @throws WrongFileFormatException
375             */
376            private void load() throws 
377                    InvalidFileStructureException, 
378                    IOException, 
379                    UnsupportedTypeException, 
380                    WrongFileFormatException,
381                    WrongParameterException
382            {
383                    readHeader();
384                    skipImageFileDirectories(getImageIndex());
385                    TIFFImageFileDirectory ifd = readImageFileDirectory();
386                    ifd.initFromTags(true);
387                    println("IFD OK");
388                    int dpiX = ifd.getDpiX();
389                    int dpiY = ifd.getDpiY();
390                    if (dpiX > 0 && dpiY > 0)
391                    {
392                            println("DPI: " + dpiX + " x " + dpiY);
393                            setDpi(dpiX, dpiY);
394                    }
395                    //ifd.dump();
396                    load(ifd);
397            }
398    
399            private void load(TIFFImageFileDirectory ifd) throws
400                    InvalidFileStructureException,
401                    IOException,
402                    UnsupportedTypeException,
403                    WrongFileFormatException,
404                    WrongParameterException
405            {
406                    setBoundsIfNecessary(ifd.getWidth(), ifd.getHeight());
407                    checkImageResolution();
408                    int width = getBoundsWidth();
409                    int height = getBoundsHeight();
410                    // create image if necessary
411                    PixelImage image = getImage();
412                    if (image == null)
413                    {
414                            int imageType = ifd.getImageType();
415                            switch (imageType)
416                            {
417                                    case(TIFFImageFileDirectory.TYPE_BILEVEL_BYTE):
418                                    case(TIFFImageFileDirectory.TYPE_BILEVEL_PACKED):
419                                    {
420                                            image = new MemoryBilevelImage(width, height);
421                                            break;
422                                    }
423                                    case(TIFFImageFileDirectory.TYPE_GRAY4):
424                                    case(TIFFImageFileDirectory.TYPE_GRAY8):
425                                    case(TIFFImageFileDirectory.TYPE_LOGL):
426                                    {
427                                            image = new MemoryGray8Image(width, height);
428                                            break;
429                                    }
430                                    case(TIFFImageFileDirectory.TYPE_GRAY16):
431                                    {
432                                            image = new MemoryGray16Image(width, height);
433                                            break;
434                                    }
435                                    case(TIFFImageFileDirectory.TYPE_PALETTED4):
436                                    case(TIFFImageFileDirectory.TYPE_PALETTED8):
437                                    {
438                                            image = new MemoryPaletted8Image(width, height, ifd.getPalette());
439                                            break;
440                                    }
441                                    case(TIFFImageFileDirectory.TYPE_CMYK32_INTERLEAVED):
442                                    case(TIFFImageFileDirectory.TYPE_CMYK32_PLANAR):
443                                    case(TIFFImageFileDirectory.TYPE_RGB24_INTERLEAVED):
444                                    case(TIFFImageFileDirectory.TYPE_LOGLUV32_INTERLEAVED):
445                                    {
446                                            image = new MemoryRGB24Image(width, height);
447                                            break;
448                                    }
449                                    case(TIFFImageFileDirectory.TYPE_RGB48_INTERLEAVED):
450                                    {
451                                            image = new MemoryRGB48Image(width, height);
452                                            break;
453                                    }
454                                    default:
455                                    {
456                                            throw new UnsupportedTypeException("Unsupported image type.");
457                                    }
458                            }
459                            setImage(image);
460                    }
461                    int tileIndex = 0;
462                    int numTiles = ifd.getNumTiles();
463                    while (tileIndex < numTiles && !getAbort())
464                    {
465                            println("Decode tile / strip index #" + (tileIndex + 1) + "/" + numTiles);
466                            int x1 = ifd.getTileX1(tileIndex);
467                            int y1 = ifd.getTileY1(tileIndex);
468                            int x2 = ifd.getTileX2(tileIndex);
469                            int y2 = ifd.getTileY2(tileIndex);
470                            if (isTileRequired(x1, y1, x2, y2))
471                            {
472                                    println("About to create decoder.");
473                                    TIFFDecoder decoder = createDecoder(this, ifd, tileIndex);
474                                    decoder.decode();
475                            }
476                            tileIndex++;
477                    }
478            }
479    
480            private void print(String s)
481            {
482                    if (DEBUG)
483                    {
484                            System.out.print(s);
485                    }
486            }
487    
488            private void println(String s)
489            {
490                    if (DEBUG)
491                    {
492                            System.out.println(s);
493                    }
494            }
495    
496            public void process() throws
497                    MissingParameterException,
498                    OperationFailedException
499            {
500                    initModeFromIOObjects();
501                    try
502                    {
503                            if (getMode() == CodecMode.LOAD && getRandomAccessFile() != null)
504                            {
505                                    load();
506                            }
507                            else
508                            {
509                                    throw new MissingParameterException("TIFF codec must have RandomAccessFile object opened for reading.");
510                            }
511                    }
512                    catch (IOException ioe)
513                    {
514                            close();
515                            throw new OperationFailedException("I/O error occurred: " + ioe.toString());
516                    }
517            }
518    
519            /**
520             * Reads the first eight bytes from the input file, checks if this is a 
521             * valid TIFF file and stores byte order and offset of the first image
522             * file directory.
523             * @throws IOException if there were reading errors
524             * @throws WrongFileFormatException if this is not a valid TIFF file
525             */
526            private void readHeader() throws
527                    IOException, 
528                    WrongFileFormatException
529            {
530                    RandomAccessFile in = getRandomAccessFile();
531                    // the argument to in.seek must be changed to a variable in the future for
532                    // this codec to be used to read EXIF information from JPEGs;
533                    // for some reason, TIFF was chosen for that
534                    in.seek(0);
535                    // note: this is the only place where we use in.readInt()
536                    // directly; afterwards, the detected byte order
537                    // is regarded via this class' methods readInt() and readShort()
538                    // methods
539                    int magic = in.readInt();
540                    if (magic == MAGIC_INTEL)
541                    {
542                            setByteOrder(BYTE_ORDER_INTEL);
543                    }
544                    else
545                    if (magic == MAGIC_MOTOROLA)
546                    {
547                            setByteOrder(BYTE_ORDER_MOTOROLA);
548                    }
549                    else
550                    {
551                            throw new WrongFileFormatException("Not a TIFF file (does not " +
552                                    "begin with II or MM followed by 42).");
553                    }
554                    nextIfdOffset = readInt();
555                    println("\nifd=" + nextIfdOffset);
556            }
557    
558            /**
559             * Reads a complete TIFF image file directory including all data that is
560             * pointed to using the offset components and returns it.
561             * 
562             * @return the image file directory data or <code>null</code> on failure
563             */
564            private TIFFImageFileDirectory readImageFileDirectory() throws 
565                    InvalidFileStructureException, 
566                    IOException
567            {
568                    TIFFImageFileDirectory result = new TIFFImageFileDirectory();
569                    RandomAccessFile in = getRandomAccessFile();
570                    in.seek(nextIfdOffset);
571                    short numTags = readShort();
572                    if (numTags < 0)
573                    {
574                            throw new InvalidFileStructureException("Number of tags in IFD " +
575                                    "smaller than 1 @" + nextIfdOffset + ": " + numTags);
576                    }
577                    for (int i = 0; i < numTags; i++)
578                    {
579                            TIFFTag tag = readTag();
580                            if (tag != null)
581                            {
582                                    result.append(tag);
583                            }
584                    }
585                    nextIfdOffset = in.readInt();
586                    println("Next IFD " + nextIfdOffset);
587                    return result;
588            }
589    
590            /**
591             * Reads a 32 bit signed integer value, regarding the current byte order.
592             * @return the loaded value
593             * @see #getByteOrder
594             */
595            private int readInt() throws IOException
596            {
597                    RandomAccessFile in = getRandomAccessFile();
598                    int result = in.readInt();
599                    if (getByteOrder() == BYTE_ORDER_INTEL)
600                    {
601                            int r1 = (result >> 24) & 0xff;
602                            int r2 = (result >> 16) & 0xff;
603                            int r3 = (result >> 8) & 0xff;
604                            int r4 = result & 0xff;
605                            return r1 | (r2 << 8) | (r3 << 16) | (r4 << 24);
606                    }
607                    else
608                    {
609                            return result;
610                    }
611            }
612    
613            /**
614             * Reads a 16 bit signed integer value, regarding the current byte order.
615             * @return the loaded value
616             */
617            private short readShort() throws IOException
618            {
619                    RandomAccessFile in = getRandomAccessFile();
620                    short result = in.readShort();
621                    if (getByteOrder() == BYTE_ORDER_INTEL)
622                    {
623                            int r1 = (result >> 8) & 0xff;
624                            int r2 = result & 0xff;
625                            return (short)((r2 << 8) | r1);
626                    }
627                    else
628                    {
629                            return result;
630                    }
631            }
632    
633            /**
634             * Loads a String of a given length from current position of input file.
635             * Characters are one-byte ASCII.
636             * Non-text characters are dropped.
637             * @param length number of characters in a row to be loaded
638             * @return loaded String
639             * @throws IOException if there were reading errors or an unexpected 
640             *  end of file
641             */
642            private String readString(int length) throws IOException
643            {
644                    RandomAccessFile in = getRandomAccessFile();
645                    StringBuffer sb = new StringBuffer(length - 1);
646                    while (length-- > 0)
647                    {
648                            int value = in.read();
649                            if (value >= 32 && value < 256)
650                            {
651                                    sb.append((char)value);
652                            }
653                    }
654                    return sb.toString();
655            }
656    
657            /**
658             * Reads a TIFF tag and all data belonging to it and returns a 
659             * TIFFTag object.
660             * The additional data is somewhere in the TIFF file.
661             * The current position will be stored, the method will seek to the offset
662             * position and load the data.
663             *
664             * @return TIFFTag containing information on the tag
665             */
666            private TIFFTag readTag() throws
667                    InvalidFileStructureException,
668                    IOException
669            {
670                    RandomAccessFile in = getRandomAccessFile();
671                    int id = readShort() & 0xffff;
672                    int type = readShort() & 0xffff;
673                    int count = readInt();
674                    print(getTagName(id) + " (" + id + "), type " + type + ", count " + count + ", ");
675                    int offset = readInt();
676                    if (count < 1)
677                    {
678                            //throw new InvalidFileStructureException("Invalid count value for tag " + id + " (" + count + ").");
679                            return null;
680                    }
681                    Vector vector = null;
682                    // perform weird bitshifting magic if necessary
683                    if (count == 1 &&
684                        (type == TAG_TYPE_BYTE || type == TAG_TYPE_SHORT || type == TAG_TYPE_LONG))
685                    {
686                            offset = adjustInt(offset, type);
687                            println("value " + offset);
688                    }
689                    else
690                    if (count <= 4 && type == TAG_TYPE_BYTE)
691                    {
692                            vector = new Vector();
693                            for (int i = 0; i < count; i++)
694                            {
695                                    byte b = (byte)((offset << (i * 8)) & 0xff);
696                                    vector.addElement(new Byte(b));
697                            }
698                    }
699                    else
700                    if (count >= 1)
701                    {
702                            long oldOffset = in.getFilePointer();
703                            in.seek(offset);
704                            vector = new Vector();
705                            if (type == TAG_TYPE_ASCII)
706                            {
707                                    vector.addElement(readString(count));
708                                    println("string " + vector.elementAt(0));
709                            }
710                            else
711                            if (type == TAG_TYPE_BYTE)
712                            {
713                                    for (int i = 0; i < count; i++)
714                                    {
715                                            byte b = in.readByte();
716                                            println("byte #" + i + "=" + (b & 0xff));
717                                            vector.addElement(new Byte(b));
718                                    }
719                            }
720                            else
721                            if (type == TAG_TYPE_SHORT)
722                            {
723                                    for (int i = 0; i < count; i++)
724                                    {
725                                            int s = readShort();
726                                            println("short #" + i + "=" + (s & 0xffff));
727                                            vector.addElement(new Short((short)s));
728                                    }
729                            }
730                            else
731                            if (type == TAG_TYPE_LONG)
732                            {
733                                    for (int i = 0; i < count; i++)
734                                    {
735                                            int v = adjustInt(readInt(), type);
736                                            println("long #" + i + "=" + v);
737                                            vector.addElement(new Integer(v));
738                                    }
739                            }
740                            else
741                            if (type == TAG_TYPE_RATIONAL)
742                            {
743                                    for (int i = 0; i < count; i++)
744                                    {
745                                            int v1 = adjustInt(readInt(), TAG_TYPE_LONG);
746                                            int v2 = adjustInt(readInt(), TAG_TYPE_LONG);
747                                            println("rational #" + i + "=" + v1 + "/" + v2);
748                                            vector.addElement(new TIFFRational(v1, v2));
749                                    }
750                            }
751                            in.seek(oldOffset);
752                    }
753                    TIFFTag result = new TIFFTag(id, type, count, offset);
754                    result.setVector(vector);
755                    return result;
756            }
757    
758            /**
759             * Register a {@link TIFFDecoder} class.
760             * TIFF knows many compression types, and JIU only supports some of them.
761             * To register an external TIFFDecoder class with TIFFCodec, call this method
762             * with the class field of your decoder.
763             * As an example, for your TIFFDecoderMyCompression class,
764             * call <code>TIFFCodec.registerDecoder(TIFFDecoderMyCompression.class)</code>.
765             * It will be checked if 
766             * <code>decoderClass.newInstance() instanceof TIFFDecoder</code>
767             * is true and, if so, the class will be added to an internal list.
768             * Whenever a TIFF file is to be decoded, the correct decoder is determined
769             * (each decoder knows about the compression types it supports via the getCompressionTypes method)
770             * and for each tile or strip such a decoder object will be created.
771             */
772            public static void registerDecoder(Class decoderClass)
773            {
774                    if (decoderClass == null)
775                    {
776                            return;
777                    }
778                    Object instance;
779                    try
780                    {
781                            instance = decoderClass.newInstance();
782                    }
783                    catch (Exception e)
784                    {
785                            return;
786                    }
787                    if (instance instanceof TIFFDecoder)
788                    {
789                            TIFFDecoder decoder = (TIFFDecoder)instance;
790                            Integer[] compressionTypes = decoder.getCompressionTypes();
791                            if (compressionTypes == null)
792                            {
793                                    return;
794                            }
795                            int index = 0;
796                            while (index < compressionTypes.length)
797                            {
798                                    Integer type = compressionTypes[index++];
799                                    if (type != null)
800                                    {
801                                            decoders.put(type, decoderClass);
802                                    }
803                            }
804                    }
805            }
806    
807            /**
808             * Sets the byte order to the argument.
809             * The byte order in a TIFF file is either {@link #BYTE_ORDER_INTEL} or
810             * {@link #BYTE_ORDER_MOTOROLA}.
811             * @param newByteOrder the new byte order to be set
812             * @throws IllegalArgumentException if the argument is not one of the above
813             *  mentioned constants
814             */
815            private void setByteOrder(int newByteOrder)
816            {
817                    if (newByteOrder == BYTE_ORDER_INTEL ||
818                        newByteOrder == BYTE_ORDER_MOTOROLA)
819                    {
820                            byteOrder = newByteOrder;
821                    }
822                    else
823                    {
824                            throw new IllegalArgumentException("Byte order must be either " +
825                                    "BYTE_ORDER_INTEL or BYTE_ORDER_MOTOROLA.");
826                    }
827            }
828    
829            public void setFile(String fileName, CodecMode codecMode) throws 
830                    IOException, 
831                    UnsupportedCodecModeException
832            {
833                    if (codecMode == CodecMode.LOAD)
834                    {
835                            setRandomAccessFile(new RandomAccessFile(fileName, "r"), CodecMode.LOAD);
836                    }
837                    else
838                    {
839                            throw new UnsupportedCodecModeException("This TIFF codec can only load images.");
840                    }
841            }
842    
843            /**
844             * Skips a given number of image file directories in this TIFF files.
845             * Throws an exception if there were errors or not enough image file
846             * directories.
847             * @param numDirectories the number of directories to be skipped,
848             *  should be non-negative
849             * @throws IllegalArgumentException if argument is negative
850             * @throws InvalidFileStructureException if there aren't enough image
851             *  file directories
852             * @throws IOExceptions if there were errors reading or skipping data
853             */
854            private void skipImageFileDirectories(int numDirectories) throws
855                    InvalidFileStructureException,
856                    IOException
857            {
858                    RandomAccessFile in = getRandomAccessFile();
859                    if (numDirectories < 0)
860                    {
861                            throw new IllegalArgumentException("Cannot skip negative number " +
862                                    "of image file directories: " + numDirectories);
863                    }
864                    int skipped = 0;
865                    while (numDirectories-- > 0)
866                    {
867                            in.seek(nextIfdOffset);
868                            short numTags = readShort();
869                            in.skipBytes(numTags * 12);
870                            nextIfdOffset = readInt();
871                            if (nextIfdOffset == 0)
872                            {
873                                    throw new InvalidFileStructureException("Could only skip " + 
874                                            skipped + " image file directories, no more images in file.");
875                            }
876                            skipped++;
877                    }
878            }
879    }