001    /*
002     * TIFFImageFileDirectory
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.text.ParseException;
011    import java.text.SimpleDateFormat;
012    import java.util.Date;
013    import java.util.GregorianCalendar;
014    import java.util.TimeZone;
015    import java.util.Vector;
016    import net.sourceforge.jiu.codecs.InvalidFileStructureException;
017    import net.sourceforge.jiu.codecs.UnsupportedTypeException;
018    import net.sourceforge.jiu.codecs.tiff.TIFFConstants;
019    import net.sourceforge.jiu.codecs.tiff.TIFFTag;
020    import net.sourceforge.jiu.data.Palette;
021    import net.sourceforge.jiu.data.RGBIndex;
022    
023    /**
024     * This class encapsulates all data of a TIFF image file directory (IFD).
025     * @author Marco Schmidt
026     */
027    public class TIFFImageFileDirectory implements TIFFConstants
028    {
029            public static final int TYPE_BILEVEL_PACKED = 0;
030            public static final int TYPE_GRAY4 = 1;
031            public static final int TYPE_GRAY8 = 2;
032            public static final int TYPE_GRAY16 = 3;
033            public static final int TYPE_PALETTED4 = 4;
034            public static final int TYPE_PALETTED8 = 5;
035            public static final int TYPE_RGB24_INTERLEAVED = 6;
036            public static final int TYPE_RGB48_INTERLEAVED = 7;
037            public static final int TYPE_BILEVEL_BYTE = 8;
038            public static final int TYPE_CMYK32_INTERLEAVED = 9;
039            public static final int TYPE_CMYK32_PLANAR = 10;
040            public static final int TYPE_LOGLUV32_INTERLEAVED = 11;
041            public static final int TYPE_LOGL = 12;
042    
043            private String artist;
044            private int[] bitsPerSample;
045            private int bitsPerPixel;
046            private int bitsPerRow;
047            private int bytesBetweenSamples;
048            private int[] bytesPerSample;
049            private int bytesPerRow;
050            private int compression;
051            private String copyright;
052            private Date date;
053            private String dateTime;
054            private int dpiX;
055            private int dpiY;
056            private int[] extraSamples;
057            private int height;
058            private int horizontalTiles;
059            private String hostComputer;
060            private String imageDescription;
061            private int imageType;
062            private boolean invertGraySamples;
063            private String make;
064            private String model;
065            private int numStrips;
066            private int numTiles;
067            private int orientation;
068            private Palette palette;
069            private int pixelsPerRow;
070            private int planarConfiguration;
071            private int photometricInterpretation;
072            private int predictor;
073            private int[] sampleTypes;
074            private int resolutionUnit;
075            private double resolutionX;
076            private double resolutionY;
077            private int rowsPerStrip;
078            private int samplesPerPixel;
079            private String software;
080            private Vector stripByteCounts;
081            private Vector stripOffsets;
082            private int t4Options;
083            private int t6Options;
084            private Vector tags;
085            private Vector tileByteCounts;
086            private Vector tileOffsets;
087            private TimeZone timeZone;
088            private int tileWidth;
089            private int tileHeight;
090            private int totalTiles;
091            private int verticalTiles;
092            private int width;
093    
094            /**
095             * Initializes all members to null or -1 and creates an internal list for
096             * the tags that will be make up this directory.
097             */
098            public TIFFImageFileDirectory()
099            {
100                    initMembers();
101                    tags = new Vector();
102            }
103    
104            /**
105             * Adds a tag to the end of the internal list of tags.
106             * @param tag the TIFFTag instance to be appended
107             */
108            public void append(TIFFTag tag)
109            {
110                    tags.addElement(tag);
111            }
112    
113            private void checkContent() throws
114                    InvalidFileStructureException,
115                    UnsupportedTypeException
116            {
117                    if (width < 1)
118                    {
119                            throw new InvalidFileStructureException("No valid width available.");
120                    }
121                    if (height < 1)
122                    {
123                            throw new InvalidFileStructureException("No valid width available.");
124                    }
125                    if (stripOffsets != null)
126                    {
127                            pixelsPerRow = width;
128                    }
129                    else
130                    if (tileOffsets != null)
131                    {
132                            pixelsPerRow = tileWidth;
133                    }
134                    if (rowsPerStrip == -1 && stripOffsets != null && stripOffsets.size() == 1)
135                    {
136                            rowsPerStrip = height;
137                    }
138                    // do more checks based on color type
139                    switch (photometricInterpretation)
140                    {
141                            case(PHOTOMETRIC_BLACK_IS_ZERO):
142                            case(PHOTOMETRIC_WHITE_IS_ZERO):
143                            {
144                                    if (bitsPerSample[0] == 1)
145                                    {
146                                            imageType = TYPE_BILEVEL_PACKED;
147                                    }
148                                    else
149                                    {
150                                            if (bitsPerSample[0] == 4)
151                                            {
152                                                    imageType = TYPE_GRAY4;
153                                            }
154                                            else
155                                            if (bitsPerSample[0] == 8)
156                                            {
157                                                    imageType = TYPE_GRAY8;
158                                            }
159                                            else
160                                            {
161                                                    throw new UnsupportedTypeException("Only bit depths 1, 4 and 8 are supported for bilevel and grayscale images.");
162                                            }
163                                    }
164                                    break;
165                            }
166                            case(PHOTOMETRIC_PALETTED):
167                            {
168                                    if (getPalette() == null)
169                                    {
170                                            throw new InvalidFileStructureException("No palette found in paletted image.");
171                                    }
172                                    break;
173                            }
174                            case(PHOTOMETRIC_TRUECOLOR_RGB):
175                            {
176                                    if (planarConfiguration != PLANAR_CONFIGURATION_CHUNKY)
177                                    {
178                                            throw new UnsupportedTypeException("Cannot handle planar configuration other than chunky for RGB images.");
179                                    }
180                                    if (bitsPerSample.length != 3)
181                                    {
182                                            throw new UnsupportedTypeException("Found RGB truecolor image, but instead of three " + bitsPerSample.length + " component(s).");
183                                    }
184                                    if (bitsPerPixel == 24)
185                                    {
186                                            imageType = TYPE_RGB24_INTERLEAVED;
187                                    }
188                                    else
189                                    if (bitsPerPixel == 48)
190                                    {
191                                            imageType = TYPE_RGB48_INTERLEAVED;
192                                    }
193                                    else
194                                    {
195                                            throw new UnsupportedTypeException("Unsupported RGB truecolor image color depth: " + bitsPerPixel + ".");
196                                    }
197                                    break;
198                            }
199                            case(PHOTOMETRIC_TRUECOLOR_LOGLUV):
200                            {
201                                    if (planarConfiguration == PLANAR_CONFIGURATION_CHUNKY)
202                                    {
203                                            imageType = TYPE_LOGLUV32_INTERLEAVED;
204                                    }
205                                    else
206                                    {
207                                            throw new UnsupportedTypeException("Cannot handle planar configuration other than chunky for RGB images.");
208                                    }
209                                    break;
210                            }
211                            case(PHOTOMETRIC_LOGL):
212                            {
213                                    imageType = TYPE_LOGL;
214                                    break;
215                            }
216                            case(PHOTOMETRIC_TRUECOLOR_CMYK):
217                            {
218                                    if (planarConfiguration == PLANAR_CONFIGURATION_CHUNKY)
219                                    {
220                                            imageType = TYPE_CMYK32_INTERLEAVED;
221                                    }
222                                    /*else
223                                    if (planarConfiguration == PLANAR_CONFIGURATION_PLANAR)
224                                    {
225                                            imageType = TYPE_CMYK32_PLANAR;
226                                    }*/
227                                    else
228                                    {
229                                            throw new UnsupportedTypeException("Cannot handle planar configuration other than chunky for CMYK images.");
230                                    }
231                                    break;
232                            }
233                            default:
234                            {
235                                    throw new UnsupportedTypeException("Unsupported color type: "  + photometricInterpretation + ".");
236                            }
237                    }
238                    if (compression == COMPRESSION_CCITT_GROUP3_1D_MODIFIED_HUFFMAN)
239                    {
240                            if (bitsPerPixel != 1)
241                            {
242                                    throw new UnsupportedTypeException("Number of bits per pixel must be 1 for " +
243                                            "compression type: " + getCompressionName(compression) + ".");
244                            }
245                            imageType = TYPE_BILEVEL_BYTE;
246                    }
247                    // TODO more validity checks
248            }
249    
250            /**
251             * TODO: regard extra samples
252             */
253            public int computeNumBytes(int numPixels)
254            {
255                    if (bitsPerPixel == 1)
256                    {
257                            if (imageType == TYPE_BILEVEL_BYTE)
258                            {
259                                    return numPixels;
260                            }
261                            else
262                            {
263                                    return (numPixels + 7) / 8;
264                            }
265                    }
266                    else
267                    if (bitsPerPixel <= 4)
268                    {
269                            return (numPixels + 1) / 2;
270                    }
271                    else
272                    if (bitsPerPixel <= 8)
273                    {
274                            return numPixels;
275                    }
276                    else
277                    if (bitsPerPixel == 16)
278                    {
279                            return numPixels * 2;
280                    }
281                    else
282                    if (bitsPerPixel == 24)
283                    {
284                            return numPixels * 3;
285                    }
286                    else
287                    if (bitsPerPixel == 32)
288                    {
289                            return numPixels * 4;
290                    }
291                    else
292                    if (bitsPerPixel == 48)
293                    {
294                            return numPixels * 6;
295                    }
296                    else
297                    {
298                            return -1;
299                    }
300            }
301    
302            /**
303             * Returns information on the person who created the image
304             * (as stored in tag {@link TIFFConstants#TAG_ARTIST}).
305             */
306            public String getArtist()
307            {
308                    return artist;
309            }
310    
311            /**
312             * Returns the number of bits per pixel (not including transparency information).
313             */
314            public int getBitsPerPixel()
315            {
316                    return bitsPerPixel;
317            }
318    
319            /**
320             * Returns the number of compressed byte for a given tile.
321             * Tile index must not be negative and must be smaller than the number of tiles.
322             * @param tileIndex zero-based index of tile or strip for which the number of compressed bytes is to be returned
323             */
324            public int getByteCount(int tileIndex)
325            {
326                    if (stripByteCounts != null)
327                    {
328                            return ((Number)stripByteCounts.elementAt(tileIndex)).intValue();
329                    }
330                    else
331                    if (tileByteCounts != null)
332                    {
333                            return ((Number)tileByteCounts.elementAt(tileIndex)).intValue();
334                    }
335                    else
336                    {
337                            return 0;
338                    }
339            }
340    
341            public int getBytesPerRow()
342            {
343                    return computeNumBytes(getTileWidth());
344            }
345    
346            /**
347             * Returns the compression method, encoded as a number as found in
348             * {@link TIFFConstants} (more specifically, the COMPRESSION_xyz constants).
349             * Use {@link #getCompressionName(int)} to get the English name
350             * of this compression method.
351             * @return compression method 
352             */
353            public int getCompression()
354            {
355                    return compression;
356            }
357    
358            /**
359             * Returns the name of a TIFF compression method.
360             * If the name is unknown, <em>Unknown method</em> plus
361             * the method number is returned.
362             * This static method can be used in combination with the value from
363             * {@link #getCompression}.
364             * @param method the compression method number
365             * @return the compression method name
366             */
367            public static String getCompressionName(int method)
368            {
369                    switch(method)
370                    {
371                            case(COMPRESSION_CCITT_GROUP3_1D_MODIFIED_HUFFMAN): return "CCITT Group 3 1D Modified Huffman";
372                            case(COMPRESSION_CCITT_T4): return "CCITT T.4";
373                            case(COMPRESSION_CCITT_T6): return "CCITT T.6";
374                            case(COMPRESSION_DEFLATED_INOFFICIAL): return "Deflated (inofficial, 32496)";
375                            case(COMPRESSION_DEFLATED_OFFICIAL): return "Deflated (official, 8)";
376                            case(COMPRESSION_LZW): return "LZW";
377                            case(COMPRESSION_NONE): return "Uncompressed";
378                            case(COMPRESSION_PACKBITS): return "Packbits";
379                            case(6): return "JPEG (old style)";
380                            case(7):return "JPEG (new style)";
381                            case(103): return "Pegasus IMJ";
382                            case(32766): return "NeXT 2-bit RLE";
383                            case(32771): return "Uncompressed, word-aligned";
384                            case(32809): return "Thunderscan RLE";
385                            case(32895): return "IT8 CT with padding";
386                            case(32896): return "IT8 Linework RLE";
387                            case(32897): return "IT8 Monochrome picture";
388                            case(32898): return "IT8 Binary line art";
389                            case(32908): return "Pixar 10 bit LZW";
390                            case(32909): return "Pixar 11 bit ZIP";
391                            case(32947): return "Kodak DCS";
392                            case(34661): return "ISO JBIG";
393                            case(34676): return "SGI Log Luminance RLE";
394                            case(34677): return "SGI Log 24-bit packed";
395                            default: return "Unknown method (" + method + ")";
396                    }
397            }
398    
399            public String getCopyright()
400            {
401                    return copyright;
402            }
403    
404            /**
405             * If a date / time tag was found in this image file directory and
406             * {@link #initFromTags} was called already, it was attempted to
407             * create a {@link java.util.Date} object from it.
408             * This object (or <code>null</code>) is returned.
409             * Use {@link #setTimeZone} to provide a time zone before the date 
410             * parsing is done.
411             * @see #getDateTimeString
412             */
413            public Date getDateTime()
414            {
415                    return date;
416            }
417    
418            /**
419             * If there was a date / time tag in this IFD, its String value
420             * is returned.
421             * @see #getDateTime
422             */
423            public String getDateTimeString()
424            {
425                    return dateTime;
426            }
427    
428            public int getDpiX()
429            {
430                    return dpiX;
431            }
432    
433            public int getDpiY()
434            {
435                    return dpiY;
436            }
437    
438            public int getHeight()
439            {
440                    return height;
441            }
442    
443            public String getHostComputer()
444            {
445                    return hostComputer;
446            }
447    
448            public String getImageDescription()
449            {
450                    return imageDescription;
451            }
452    
453            public int getImageType()
454            {
455                    return imageType;
456            }
457    
458            public String getModel()
459            {
460                    return model;
461            }
462    
463            public int getNumHorizontalTiles()
464            {
465                    return horizontalTiles;
466            }
467    
468            public int getNumStrips()
469            {
470                    return numStrips;
471            }
472    
473            public int getNumTiles()
474            {
475                    return numTiles;
476            }
477    
478            public int getNumVerticalTiles()
479            {
480                    return verticalTiles;
481            }
482    
483            public Palette getPalette()
484            {
485                    return palette;
486            }
487    
488            public int getPhotometricInterpretation()
489            {
490                    return photometricInterpretation;
491            }
492    
493            public int getPredictor()
494            {
495                    return predictor;
496            }
497    
498            public int getRowsPerStrip()
499            {
500                    return rowsPerStrip;
501            }
502    
503            public int getSamplesPerPixel()
504            {
505                    return samplesPerPixel;
506            }
507    
508            public String getSoftware()
509            {
510                    return software;
511            }
512    
513            public Vector getStripOffsets()
514            {
515                    return stripOffsets;
516            }
517    
518            public int getT4Options()
519            {
520                    return t4Options;
521            }
522    
523            public int getT6Options()
524            {
525                    return t6Options;
526            }
527    
528            public int getTileHeight()
529            {
530                    return tileHeight;
531            }
532    
533            public long getTileOffset(int tileIndex)
534            {
535                    if (stripOffsets != null)
536                    {
537                            Number number = (Number)stripOffsets.elementAt(tileIndex);
538                            return number.longValue();
539                    }
540                    else
541                    if (tileOffsets != null)
542                    {
543                            Number number = (Number)tileOffsets.elementAt(tileIndex);
544                            return number.longValue();
545                    }
546                    else
547                    {
548                            throw new IllegalArgumentException("Tile index invalid: " + tileIndex);
549                    }
550            }
551    
552            public int getTileWidth()
553            {
554                    return tileWidth;
555            }
556    
557            public int getTileX1(int tileIndex)
558            {
559                    if (tileIndex < 0 || tileIndex >= getNumTiles())
560                    {
561                            throw new IllegalArgumentException("Not a valid tile index: "  + tileIndex);
562                    }
563                    else
564                    {
565                            return (tileIndex % getNumHorizontalTiles()) * getTileWidth();
566                    }
567            }
568    
569            public int getTileX2(int tileIndex)
570            {
571                    if (tileIndex < 0 || tileIndex >= getNumTiles())
572                    {
573                            throw new IllegalArgumentException("Not a valid tile index: "  + tileIndex);
574                    }
575                    else
576                    {
577                            return ((tileIndex % getNumHorizontalTiles()) + 1) * getTileWidth() - 1;
578                    }
579            }
580    
581            public int getTileY1(int tileIndex)
582            {
583                    if (tileIndex < 0 || tileIndex >= getNumTiles())
584                    {
585                            throw new IllegalArgumentException("Not a valid tile index: "  + tileIndex);
586                    }
587                    else
588                    {
589                            return (tileIndex % getNumVerticalTiles()) * getTileHeight();
590                    }
591            }
592    
593            public int getTileY2(int tileIndex)
594            {
595                    if (tileIndex < 0 || tileIndex >= getNumTiles())
596                    {
597                            throw new IllegalArgumentException("Not a valid tile index: "  + tileIndex);
598                    }
599                    else
600                    {
601                            int result = ((tileIndex % getNumVerticalTiles()) + 1) * getTileHeight() - 1;
602                            if (result >= height)
603                            {
604                                    result = height - 1;
605                            }
606                            return result;
607                    }
608            }
609    
610            public int getWidth()
611            {
612                    return width;
613            }
614    
615            public void initMembers()
616            {
617                    bitsPerPixel = -1;
618                    bitsPerSample = null;
619                    compression = -1;
620                    height = -1;
621                    horizontalTiles = -1;
622                    invertGraySamples = false;
623                    numStrips = -1;
624                    numTiles = -1;
625                    orientation = 1;
626                    photometricInterpretation = -1;
627                    planarConfiguration = -1;
628                    resolutionUnit = 2;
629                    resolutionX = -1.0;
630                    resolutionY = -1.0;
631                    rowsPerStrip = -1;
632                    stripOffsets = null;
633                    tags = null;
634                    tileOffsets = null;
635                    tileWidth = -1;
636                    tileHeight = -1;
637                    totalTiles = -1;
638                    verticalTiles = -1;
639                    width = -1;
640            }
641    
642            public void initFromTags(boolean check) throws
643                    InvalidFileStructureException,
644                    UnsupportedTypeException
645            {
646                    int index = 0;
647                    while (index < tags.size())
648                    {
649                            TIFFTag tag = (TIFFTag)tags.elementAt(index++);
650                            int id = tag.getId();
651                            int count = tag.getCount();
652                            int type = tag.getType();
653                            boolean isNotInt = !tag.isInt();
654                            switch(id)
655                            {
656                                    case(TAG_ARTIST):
657                                    {
658                                            artist = tag.getString();
659                                            break;
660                                    }
661                                    case(TAG_BITS_PER_SAMPLE):
662                                    {
663                                            if (isNotInt)
664                                            {
665                                                    throw new InvalidFileStructureException("Bits per " +
666                                                            "sample value(s) must be byte/short/long; type=" +
667                                                            type);
668                                            }
669                                            if (count == 1)
670                                            {
671                                                    bitsPerSample = new int[1];
672                                                    bitsPerSample[0] = tag.getOffset();
673                                                    bitsPerPixel = bitsPerSample[0];
674                                            }
675                                            else
676                                            {
677                                                    bitsPerPixel = 0;
678                                                    bitsPerSample = new int[count];
679                                                    for (int i = 0; i < count; i++)
680                                                    {
681                                                            bitsPerSample[i] = tag.getElementAsInt(i);
682                                                            if (bitsPerSample[i] < 1)
683                                                            {
684                                                                    throw new InvalidFileStructureException("Bits per " +
685                                                                            "sample value #" + i + " is smaller than 1.");
686                                                            }
687                                                            bitsPerPixel += bitsPerSample[i];
688                                                    }
689                                            }
690                                            break;
691                                    }
692                                    case(TAG_COLOR_MAP):
693                                    {
694                                            if ((count % 3) != 0)
695                                            {
696                                                    throw new InvalidFileStructureException("Number of palette entries must be divideable by three without rest; " + count);
697                                            }
698                                            if (count < 3 || count > 768)
699                                            {
700                                                    throw new UnsupportedTypeException("Unsupported number of palette entries: " + count + ".");
701                                            }
702                                            if (type != TAG_TYPE_SHORT)
703                                            {
704                                                    throw new UnsupportedTypeException("Unsupported number type for palette entries: " + type);
705                                            }
706                                            int numEntries = count / 3;
707                                            palette = new Palette(numEntries, 255);
708                                            int vectorIndex = 0;
709                                            for (int paletteIndex = 0; paletteIndex < numEntries; paletteIndex++)
710                                            {
711                                                    palette.putSample(RGBIndex.INDEX_RED, paletteIndex, tag.getElementAsInt(vectorIndex++) >> 8);
712                                            }
713                                            for (int paletteIndex = 0; paletteIndex < numEntries; paletteIndex++)
714                                            {
715                                                    palette.putSample(RGBIndex.INDEX_GREEN, paletteIndex, tag.getElementAsInt(vectorIndex++) >> 8);
716                                            }
717                                            for (int paletteIndex = 0; paletteIndex < numEntries; paletteIndex++)
718                                            {
719                                                    palette.putSample(RGBIndex.INDEX_BLUE, paletteIndex, tag.getElementAsInt(vectorIndex++) >> 8);
720                                            }
721                                            break;
722                                    }
723                                    case(TAG_COMPRESSION):
724                                    {
725                                            if (count != 1 || isNotInt)
726                                            {
727                                                    throw new InvalidFileStructureException("Expected " +
728                                                            " single byte/short/long value for compression " +
729                                                            "(count=" + count + ", type=" + type + ").");
730                                            }
731                                            compression = tag.getOffset();
732                                            break;
733                                    }
734                                    case(TAG_DATE_TIME):
735                                    {
736                                            dateTime = tag.getString();
737                                            if (dateTime != null)
738                                            {
739                                                    dateTime = dateTime.trim();
740                                            }
741                                            if (dateTime != null)
742                                            {
743                                                    SimpleDateFormat format = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
744                                                    if (timeZone != null)
745                                                    {
746                                                            format.setCalendar(new GregorianCalendar(timeZone));
747                                                    }
748                                                    try
749                                                    {
750                                                            date = format.parse(dateTime);
751                                                    }
752                                                    catch (ParseException pe)
753                                                    {
754                                                            date = null;
755                                                    }
756                                            }
757                                            break;
758                                    }
759                                    case(TAG_HOST_COMPUTER):
760                                    {
761                                            hostComputer = tag.getString();
762                                            break;
763                                    }
764                                    case(TAG_IMAGE_DESCRIPTION):
765                                    {
766                                            imageDescription = tag.getString();
767                                            break;
768                                    }
769                                    case(TAG_IMAGE_WIDTH):
770                                    {
771                                            if (count != 1 || isNotInt)
772                                            {
773                                                    throw new InvalidFileStructureException("Expected " +
774                                                            "single byte/short/long value for image width " +
775                                                            "(count=" + count + ", type=" + type + ").");
776                                            }
777                                            width = tag.getOffset();
778                                            break;
779                                    }
780                                    case(TAG_IMAGE_LENGTH):
781                                    {
782                                            if (count != 1 || isNotInt)
783                                            {
784                                                    throw new InvalidFileStructureException("Expected " +
785                                                            "single byte/short/long value for image height " +
786                                                            "(count=" + count + ", type=" + type + ").");
787                                            }
788                                            height = tag.getOffset();
789                                            break;
790                                    }
791                                    case(TAG_MAKE):
792                                    {
793                                            make = tag.getString();
794                                            break;
795                                    }
796                                    case(TAG_MODEL):
797                                    {
798                                            model = tag.getString();
799                                            break;
800                                    }
801                                    case(TAG_ORIENTATION):
802                                    {
803                                            if (count != 1 || isNotInt)
804                                            {
805                                                    throw new InvalidFileStructureException("Expected " +
806                                                            "single byte/short/long value for image height " +
807                                                            "(count=" + count + ", type=" + type + ").");
808                                            }
809                                            orientation = tag.getOffset();
810                                            break;
811                                    }
812                                    case(TAG_PHOTOMETRIC_INTERPRETATION):
813                                    {
814                                            if (count != 1 || isNotInt)
815                                            {
816                                                    throw new InvalidFileStructureException("Expected " +
817                                                            "single byte/short/long value for photometric interpretation.");
818                                            }
819                                            photometricInterpretation = tag.getOffset();
820                                            break;
821                                    }
822                                    case(TAG_RESOLUTION_UNIT):
823                                    {
824                                            if (count != 1 || isNotInt)
825                                            {
826                                                    throw new InvalidFileStructureException("Expected " +
827                                                            "single byte/short/long value for planar configuration.");
828                                            }
829                                            resolutionUnit = tag.getOffset();
830                                            break;
831                                    }
832                                    case(TAG_RESOLUTION_X):
833                                    {
834                                            if (count != 1 || type != TAG_TYPE_RATIONAL)
835                                            {
836                                                    throw new InvalidFileStructureException("Expected " +
837                                                            "single byte/short/long value for planar configuration.");
838                                            }
839                                            Object o = tag.getObject(0);
840                                            if (o != null && o instanceof TIFFRational)
841                                            {
842                                                    TIFFRational rational = (TIFFRational)o;
843                                                    resolutionX = rational.getAsDouble();
844                                            }
845                                            break;
846                                    }
847                                    case(TAG_RESOLUTION_Y):
848                                    {
849                                            if (count != 1 || type != TAG_TYPE_RATIONAL)
850                                            {
851                                                    throw new InvalidFileStructureException("Expected " +
852                                                            "single byte/short/long value for planar configuration.");
853                                            }
854                                            Object o = tag.getObject(0);
855                                            if (o != null && o instanceof TIFFRational)
856                                            {
857                                                    TIFFRational rational = (TIFFRational)o;
858                                                    resolutionY = rational.getAsDouble();
859                                            }
860                                            break;
861                                    }
862                                    case(TAG_PLANAR_CONFIGURATION):
863                                    {
864                                            if (count != 1 || isNotInt)
865                                            {
866                                                    throw new InvalidFileStructureException("Expected " +
867                                                            "single byte/short/long value for planar configuration.");
868                                            }
869                                            planarConfiguration = tag.getOffset();
870                                            break;
871                                    }
872                                    case(TAG_ROWS_PER_STRIP):
873                                    {
874                                            if (count != 1 || isNotInt)
875                                            {
876                                                    throw new InvalidFileStructureException("Expected " +
877                                                            "single byte/short/long value for image height.");
878                                            }
879                                            rowsPerStrip = tag.getOffset();
880                                            break;
881                                    }
882                                    case(TAG_SAMPLES_PER_PIXEL):
883                                    {
884                                            if (count != 1 || isNotInt)
885                                            {
886                                                    throw new InvalidFileStructureException("Expected " +
887                                                            "single byte/short/long value for samples per pixel.");
888                                            }
889                                            samplesPerPixel = tag.getOffset();
890                                            break;
891                                    }
892                                    case(TAG_SOFTWARE):
893                                    {
894                                            software = tag.getString();
895                                            break;
896                                    }
897                                    case(TAG_STRIP_BYTE_COUNTS):
898                                    {
899                                            if (count < 1)
900                                            {
901                                                    throw new InvalidFileStructureException("Need at least one strip offset.");
902                                            }
903                                            if (count == 1)
904                                            {
905                                                    if (isNotInt)
906                                                    {
907                                                            throw new InvalidFileStructureException("There is " +
908                                                                    "only one strip offset, but its type is not integer.");
909                                                    }
910                                                    stripByteCounts = new Vector();
911                                                    stripByteCounts.addElement(new Long(tag.getOffset()));
912                                            }
913                                            else
914                                            {
915                                                    stripByteCounts = tag.getVector();
916                                            }
917                                            break;
918                                    }
919                                    case(TAG_STRIP_OFFSETS):
920                                    {
921                                            if (count < 1)
922                                            {
923                                                    throw new InvalidFileStructureException("Need at least one strip offset.");
924                                            }
925                                            if (count == 1)
926                                            {
927                                                    if (isNotInt)
928                                                    {
929                                                            throw new InvalidFileStructureException("There is " +
930                                                                    "only one strip offset, but its type is not integer.");
931                                                    }
932                                                    stripOffsets = new Vector();
933                                                    stripOffsets.addElement(new Long(tag.getOffset()));
934                                            }
935                                            else
936                                            {
937                                                    stripOffsets = tag.getVector();
938                                            }
939                                            numStrips = count;
940                                            numTiles = count;
941                                            horizontalTiles = 1;
942                                            verticalTiles = count;
943                                            //println("strip offsets: " + stripOffsets.size());
944                                            break;
945                                    }
946                                    case(TAG_T4_OPTIONS):
947                                    {
948                                            if (count != 1 || isNotInt)
949                                            {
950                                                    throw new InvalidFileStructureException("Expected " +
951                                                            "single byte/short/long value for T4 Options.");
952                                            }
953                                            t4Options = tag.getOffset();
954                                            break;
955                                    }
956                                    case(TAG_T6_OPTIONS):
957                                    {
958                                            if (count != 1 || isNotInt)
959                                            {
960                                                    throw new InvalidFileStructureException("Expected " +
961                                                            "single byte/short/long value for T6 Options.");
962                                            }
963                                            t6Options = tag.getOffset();
964                                            break;
965                                    }
966                                    case(TAG_TILE_HEIGHT):
967                                    {
968                                            if (count != 1 || isNotInt)
969                                            {
970                                                    throw new InvalidFileStructureException("Expected " +
971                                                            "single byte/short/long value for image height " +
972                                                            "(count=" + count + ", type=" + type + ").");
973                                            }
974                                            tileHeight = tag.getOffset();
975                                            if (tileHeight < 1)
976                                            {
977                                                    throw new InvalidFileStructureException("Tile height must be one or larger.");
978                                            }
979                                            verticalTiles = height / tileHeight;
980                                            if ((height % tileHeight) != 0)
981                                            {
982                                                    verticalTiles++;
983                                            }
984                                            break;
985                                    }
986                                    case(TAG_TILE_OFFSETS):
987                                    {
988                                            if (count < 1)
989                                            {
990                                                    throw new InvalidFileStructureException("Need at least one tile offset.");
991                                            }
992                                            if (count == 1)
993                                            {
994                                                    if (isNotInt)
995                                                    {
996                                                            throw new InvalidFileStructureException("There is " +
997                                                                    "only one tile offset, but its type is not integer.");
998                                                    }
999                                                    tileOffsets = new Vector();
1000                                                    tileOffsets.addElement(new Long(tag.getOffset()));
1001                                            }
1002                                            else
1003                                            {
1004                                                    tileOffsets = tag.getVector();
1005                                            }
1006                                            numStrips = count;
1007                                            numTiles = count;
1008                                            horizontalTiles = 1;
1009                                            verticalTiles = count;
1010                                            //println("strip offsets: " + stripOffsets.size());
1011                                            break;
1012                                    }
1013                                    case(TAG_TILE_WIDTH):
1014                                    {
1015                                            if (count != 1 || isNotInt)
1016                                            {
1017                                                    throw new InvalidFileStructureException("Expected " +
1018                                                            "single byte/short/long value for image height " +
1019                                                            "(count=" + count + ", type=" + type + ").");
1020                                            }
1021                                            tileWidth = tag.getOffset();
1022                                            if (tileWidth < 1)
1023                                            {
1024                                                    throw new InvalidFileStructureException("Tile width must be one or larger.");
1025                                            }
1026                                            horizontalTiles = width / tileWidth;
1027                                            if ((width % tileWidth) != 0)
1028                                            {
1029                                                    horizontalTiles++;
1030                                            }
1031                                            break;
1032                                    }
1033                            }
1034                    }
1035                    if (planarConfiguration == -1)
1036                    {
1037                            planarConfiguration = PLANAR_CONFIGURATION_CHUNKY;
1038                    }
1039                    if (photometricInterpretation == TIFFConstants.PHOTOMETRIC_PALETTED)
1040                    {
1041                            if (bitsPerPixel == 4)
1042                            {
1043                                    imageType = TYPE_PALETTED4;
1044                            }
1045                            else
1046                            if (bitsPerPixel == 8)
1047                            {
1048                                    imageType = TYPE_PALETTED8;
1049                            }
1050                            else
1051                            {
1052                                    throw new UnsupportedTypeException("Only paletted images with 4 or 8 bits per sample are supported.");
1053                            }
1054                    }
1055                    if (resolutionUnit == 2 && resolutionX > 0.0 && resolutionY > 0.0)
1056                    {
1057                            dpiX = (int)resolutionX;
1058                            dpiY = (int)resolutionY;
1059                    }
1060                    if (isStriped())
1061                    {
1062                            tileWidth = width;
1063                            if (numStrips == 1 && rowsPerStrip == -1)
1064                            {
1065                                    rowsPerStrip = height;
1066                            }
1067                            tileHeight = rowsPerStrip;
1068                    }
1069                    if (check)
1070                    {
1071                            checkContent();
1072                    }
1073            }
1074    
1075            public boolean isGrayscale()
1076            {
1077                    return getBitsPerPixel() > 1 && 
1078                            (photometricInterpretation == PHOTOMETRIC_BLACK_IS_ZERO || 
1079                             photometricInterpretation == PHOTOMETRIC_WHITE_IS_ZERO);
1080            }
1081    
1082            public boolean isPaletted()
1083            {
1084                    return (photometricInterpretation == PHOTOMETRIC_PALETTED);
1085            }
1086    
1087            /**
1088             * Returns <code>true</code> if the image belonging to this IFD
1089             * is stored as strips, <code>false</code> otherwise.
1090             * @see #isTiled
1091             */
1092            public boolean isStriped()
1093            {
1094                    return (stripOffsets != null);
1095            }
1096    
1097            /**
1098             * Returns <code>true</code> if the image belonging to this IFD
1099             * is stored as tiles, <code>false</code> otherwise.
1100             * @see #isStriped
1101             */
1102            public boolean isTiled()
1103            {
1104                    return (tileOffsets != null);
1105            }
1106    
1107            /**
1108             * Sets the time zone to be used when trying to interpret dates
1109             * found in a {@link #TAG_DATE_TIME} tag.
1110             * Example call: 
1111             * <code>setTimeZone(TimeZone.getTimeZone("America/New_York");</code>.
1112             * @param tz TimeZone object
1113             */
1114            public void setTimeZone(TimeZone tz)
1115            {
1116                    timeZone = tz;
1117            }
1118    }