001/*
002 * $RCSfile: TIFFIFD.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.7 $
042 * $Date: 2006/09/27 23:56:30 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.tiff;
046
047import java.io.EOFException;
048import java.io.IOException;
049import java.util.ArrayList;
050import java.util.Arrays;
051import java.util.Iterator;
052import java.util.List;
053import java.util.Set;
054import java.util.Vector;
055
056import javax.imageio.stream.ImageInputStream;
057import javax.imageio.stream.ImageOutputStream;
058
059import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
060import com.github.jaiimageio.plugins.tiff.TIFFDirectory;
061import com.github.jaiimageio.plugins.tiff.TIFFField;
062import com.github.jaiimageio.plugins.tiff.TIFFTag;
063import com.github.jaiimageio.plugins.tiff.TIFFTagSet;
064
065public class TIFFIFD extends TIFFDirectory {
066
067    private long stripOrTileByteCountsPosition = -1;
068    private long stripOrTileOffsetsPosition = -1;
069    private long lastPosition = -1;
070
071    public static TIFFTag getTag(int tagNumber, List tagSets) {
072        Iterator iter = tagSets.iterator();
073        while (iter.hasNext()) {
074            TIFFTagSet tagSet = (TIFFTagSet)iter.next();
075            TIFFTag tag = tagSet.getTag(tagNumber);
076            if (tag != null) {
077                return tag;
078            }
079        }
080
081        return null;
082    }
083
084    public static TIFFTag getTag(String tagName, List tagSets) {
085        Iterator iter = tagSets.iterator();
086        while (iter.hasNext()) {
087            TIFFTagSet tagSet = (TIFFTagSet)iter.next();
088            TIFFTag tag = tagSet.getTag(tagName);
089            if (tag != null) {
090                return tag;
091            }
092        }
093
094        return null;
095    }
096
097    private static void writeTIFFFieldToStream(TIFFField field,
098                                               ImageOutputStream stream)
099        throws IOException {
100        int count = field.getCount();
101        Object data = field.getData();
102
103        switch (field.getType()) {
104        case TIFFTag.TIFF_ASCII:
105            for (int i = 0; i < count; i++) {
106                String s = ((String[])data)[i];
107                int length = s.length();
108                for (int j = 0; j < length; j++) {
109                    stream.writeByte(s.charAt(j) & 0xff);
110                }
111                stream.writeByte(0);
112            }
113            break;
114        case TIFFTag.TIFF_UNDEFINED:
115        case TIFFTag.TIFF_BYTE:
116        case TIFFTag.TIFF_SBYTE:
117            stream.write((byte[])data);
118            break;
119        case TIFFTag.TIFF_SHORT:
120            stream.writeChars((char[])data, 0, ((char[])data).length);
121            break;
122        case TIFFTag.TIFF_SSHORT:
123            stream.writeShorts((short[])data, 0, ((short[])data).length);
124            break;
125        case TIFFTag.TIFF_SLONG:
126            stream.writeInts((int[])data, 0, ((int[])data).length);
127            break;
128        case TIFFTag.TIFF_LONG:
129            for (int i = 0; i < count; i++) {
130                stream.writeInt((int)(((long[])data)[i]));
131            }
132            break;
133        case TIFFTag.TIFF_IFD_POINTER:
134            stream.writeInt(0); // will need to be backpatched
135            break;
136        case TIFFTag.TIFF_FLOAT:
137            stream.writeFloats((float[])data, 0, ((float[])data).length);
138            break;
139        case TIFFTag.TIFF_DOUBLE:
140            stream.writeDoubles((double[])data, 0, ((double[])data).length);
141            break;
142        case TIFFTag.TIFF_SRATIONAL:
143            for (int i = 0; i < count; i++) {
144                stream.writeInt(((int[][])data)[i][0]);
145                stream.writeInt(((int[][])data)[i][1]);
146            }
147            break;
148        case TIFFTag.TIFF_RATIONAL:
149            for (int i = 0; i < count; i++) {
150                long num = ((long[][])data)[i][0];
151                long den = ((long[][])data)[i][1];
152                stream.writeInt((int)num);
153                stream.writeInt((int)den);
154            }
155            break;
156        default:
157            // error
158        }
159    }
160
161    public TIFFIFD(List tagSets, TIFFTag parentTag) {
162        super((TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]),
163              parentTag);
164    }
165
166    public TIFFIFD(List tagSets) {
167        this(tagSets, null);
168    }
169
170    public List getTagSetList() {
171        return Arrays.asList(getTagSets());
172    }
173
174    /**
175     * Returns an <code>Iterator</code> over the TIFF fields. The
176     * traversal is in the order of increasing tag number.
177     */
178    // Note: the sort is guaranteed for low fields by the use of an
179    // array wherein the index corresponds to the tag number and for
180    // the high fields by the use of a TreeMap with tag number keys.
181    public Iterator iterator() {
182        return Arrays.asList(getTIFFFields()).iterator();
183    }
184
185    // Stream position initially at beginning, left at end
186    // if ignoreUnknownFields is true, do not load fields for which
187    // a tag cannot be found in an allowed TagSet.
188    public void initialize(ImageInputStream stream,
189                           boolean ignoreUnknownFields) throws IOException {
190        removeTIFFFields();
191
192        List tagSetList = getTagSetList();
193
194        int numEntries = stream.readUnsignedShort();
195        for (int i = 0; i < numEntries; i++) {
196            // Read tag number, value type, and value count.
197            int tag = stream.readUnsignedShort();
198            int type = stream.readUnsignedShort();
199            int count = (int)stream.readUnsignedInt();
200
201            // Get the associated TIFFTag.
202            TIFFTag tiffTag = getTag(tag, tagSetList);
203
204            // Ignore unknown fields.
205            if(ignoreUnknownFields && tiffTag == null) {
206                // Skip the value/offset so as to leave the stream
207                // position at the start of the next IFD entry.
208                stream.skipBytes(4);
209
210                // XXX Warning message ...
211
212                // Continue with the next IFD entry.
213                continue;
214            }
215            
216            long nextTagOffset = stream.getStreamPosition() + 4;
217            
218            int sizeOfType = TIFFTag.getSizeOfType(type);
219            if (count*sizeOfType > 4) {
220                long value = stream.readUnsignedInt();
221                stream.seek(value);
222            }
223            
224            if (tag == BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS ||
225                tag == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS ||
226                tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {
227                this.stripOrTileByteCountsPosition =
228                    stream.getStreamPosition();
229            } else if (tag == BaselineTIFFTagSet.TAG_STRIP_OFFSETS ||
230                       tag == BaselineTIFFTagSet.TAG_TILE_OFFSETS ||
231                       tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) {
232                this.stripOrTileOffsetsPosition =
233                    stream.getStreamPosition();
234            }
235
236            Object obj = null;
237
238            try {
239                switch (type) {
240                case TIFFTag.TIFF_BYTE:
241                case TIFFTag.TIFF_SBYTE:
242                case TIFFTag.TIFF_UNDEFINED:
243                case TIFFTag.TIFF_ASCII:
244                    byte[] bvalues = new byte[count];
245                    stream.readFully(bvalues, 0, count);
246                
247                    if (type == TIFFTag.TIFF_ASCII) {
248                        // Can be multiple strings
249                        Vector v = new Vector();
250                        boolean inString = false;
251                        int prevIndex = 0;
252                        for (int index = 0; index <= count; index++) {
253                            if (index < count && bvalues[index] != 0) {
254                                if (!inString) {
255                                // start of string
256                                    prevIndex = index;
257                                    inString = true;
258                                }
259                            } else { // null or special case at end of string
260                                if (inString) {
261                                // end of string
262                                    String s = new String(bvalues, prevIndex,
263                                                          index - prevIndex);
264                                    v.add(s);
265                                    inString = false;
266                                }
267                            }
268                        }
269
270                        count = v.size();
271                        String[] strings;
272                        if(count != 0) {
273                            strings = new String[count];
274                            for (int c = 0 ; c < count; c++) {
275                                strings[c] = (String)v.elementAt(c);
276                            }
277                        } else {
278                            // This case has been observed when the value of
279                            // 'count' recorded in the field is non-zero but
280                            // the value portion contains all nulls.
281                            count = 1;
282                            strings = new String[] {""};
283                        }
284                    
285                        obj = strings;
286                    } else {
287                        obj = bvalues;
288                    }
289                    break;
290                
291                case TIFFTag.TIFF_SHORT:
292                    char[] cvalues = new char[count];
293                    for (int j = 0; j < count; j++) {
294                        cvalues[j] = (char)(stream.readUnsignedShort());
295                    }
296                    obj = cvalues;
297                    break;
298                
299                case TIFFTag.TIFF_LONG:
300                case TIFFTag.TIFF_IFD_POINTER:
301                    long[] lvalues = new long[count];
302                    for (int j = 0; j < count; j++) {
303                        lvalues[j] = stream.readUnsignedInt();
304                    }
305                    obj = lvalues;
306                    break;
307                
308                case TIFFTag.TIFF_RATIONAL:
309                    long[][] llvalues = new long[count][2];
310                    for (int j = 0; j < count; j++) {
311                        llvalues[j][0] = stream.readUnsignedInt();
312                        llvalues[j][1] = stream.readUnsignedInt();
313                    }
314                    obj = llvalues;
315                    break;
316                
317                case TIFFTag.TIFF_SSHORT:
318                    short[] svalues = new short[count];
319                    for (int j = 0; j < count; j++) {
320                        svalues[j] = stream.readShort();
321                    }
322                    obj = svalues;
323                    break;
324                
325                case TIFFTag.TIFF_SLONG:
326                    int[] ivalues = new int[count];
327                    for (int j = 0; j < count; j++) {
328                        ivalues[j] = stream.readInt();
329                    }
330                    obj = ivalues;
331                    break;
332                
333                case TIFFTag.TIFF_SRATIONAL:
334                    int[][] iivalues = new int[count][2];
335                    for (int j = 0; j < count; j++) {
336                        iivalues[j][0] = stream.readInt();
337                        iivalues[j][1] = stream.readInt();
338                    }
339                    obj = iivalues;
340                    break;
341                
342                case TIFFTag.TIFF_FLOAT:
343                    float[] fvalues = new float[count];
344                    for (int j = 0; j < count; j++) {
345                        fvalues[j] = stream.readFloat();
346                    }
347                    obj = fvalues;
348                    break;
349                
350                case TIFFTag.TIFF_DOUBLE:
351                    double[] dvalues = new double[count];
352                    for (int j = 0; j < count; j++) {
353                        dvalues[j] = stream.readDouble();
354                    }
355                    obj = dvalues;
356                    break;
357                
358                default:
359                    // XXX Warning
360                    break;
361                }
362            } catch(EOFException eofe) {
363                // The TIFF 6.0 fields have tag numbers less than or equal
364                // to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright).
365                // If there is an error reading a baseline tag, then re-throw
366                // the exception and fail; otherwise continue with the next
367                // field.
368                if(BaselineTIFFTagSet.getInstance().getTag(tag) == null) {
369                    throw eofe;
370                }
371            }
372            
373            if (tiffTag == null) {
374                // XXX Warning: unknown tag
375            } else if (!tiffTag.isDataTypeOK(type)) {
376                // XXX Warning: bad data type
377            } else if (tiffTag.isIFDPointer() && obj != null) {
378                stream.mark();
379                stream.seek(((long[])obj)[0]);
380
381                List tagSets = new ArrayList(1);
382                tagSets.add(tiffTag.getTagSet());
383                TIFFIFD subIFD = new TIFFIFD(tagSets);
384
385                // XXX Use same ignore policy for sub-IFD fields?
386                subIFD.initialize(stream, ignoreUnknownFields);
387                obj = subIFD;
388                stream.reset();
389            }
390
391            if (tiffTag == null) {
392                tiffTag = new TIFFTag(null, tag, 1 << type, null);
393            }
394
395            // Add the field if its contents have been initialized which
396            // will not be the case if an EOF was ignored above.
397            if(obj != null) {
398                TIFFField f = new TIFFField(tiffTag, type, count, obj);
399                addTIFFField(f);
400            }
401
402            stream.seek(nextTagOffset);
403        }
404
405        this.lastPosition = stream.getStreamPosition();
406    }
407
408    public void writeToStream(ImageOutputStream stream)
409        throws IOException {
410
411        int numFields = getNumTIFFFields();
412        stream.writeShort(numFields);
413
414        long nextSpace = stream.getStreamPosition() + 12*numFields + 4;
415
416        Iterator iter = iterator();
417        while (iter.hasNext()) {
418            TIFFField f = (TIFFField)iter.next();
419            
420            TIFFTag tag = f.getTag();
421
422            int type = f.getType();
423            int count = f.getCount();
424
425            // Hack to deal with unknown tags
426            if (type == 0) {
427                type = TIFFTag.TIFF_UNDEFINED;
428            }
429            int size = count*TIFFTag.getSizeOfType(type);
430
431            if (type == TIFFTag.TIFF_ASCII) {
432                int chars = 0;
433                for (int i = 0; i < count; i++) {
434                    chars += f.getAsString(i).length() + 1;
435                }
436                count = chars;
437                size = count;
438            }
439
440            int tagNumber = f.getTagNumber();
441            stream.writeShort(tagNumber);
442            stream.writeShort(type);
443            stream.writeInt(count);
444
445            // Write a dummy value to fill space
446            stream.writeInt(0);
447            stream.mark(); // Mark beginning of next field
448            stream.skipBytes(-4);
449
450            long pos;
451
452            if (size > 4 || tag.isIFDPointer()) {
453                // Ensure IFD or value is written on a word boundary
454                nextSpace = (nextSpace + 3) & ~0x3;
455
456                stream.writeInt((int)nextSpace);
457                stream.seek(nextSpace);
458                pos = nextSpace;
459
460                if (tag.isIFDPointer()) {
461                    TIFFIFD subIFD = (TIFFIFD)f.getData();
462                    subIFD.writeToStream(stream);
463                    nextSpace = subIFD.lastPosition;
464                } else {
465                    writeTIFFFieldToStream(f, stream);
466                    nextSpace = stream.getStreamPosition();
467                }
468            } else {
469                pos = stream.getStreamPosition();
470                writeTIFFFieldToStream(f, stream);
471            }
472
473            // If we are writing the data for the
474            // StripByteCounts, TileByteCounts, StripOffsets,
475            // TileOffsets, JPEGInterchangeFormat, or
476            // JPEGInterchangeFormatLength fields, record the current stream
477            // position for backpatching
478            if (tagNumber ==
479                BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS ||
480                tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS ||
481                tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {
482                this.stripOrTileByteCountsPosition = pos;
483            } else if (tagNumber ==
484                       BaselineTIFFTagSet.TAG_STRIP_OFFSETS ||
485                       tagNumber ==
486                       BaselineTIFFTagSet.TAG_TILE_OFFSETS ||
487                       tagNumber ==
488                       BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) {
489                this.stripOrTileOffsetsPosition = pos;
490            }
491
492            stream.reset(); // Go to marked position of next field
493        }
494        
495        this.lastPosition = nextSpace;
496    }
497
498    public long getStripOrTileByteCountsPosition() {
499        return stripOrTileByteCountsPosition;
500    }
501
502    public long getStripOrTileOffsetsPosition() {
503        return stripOrTileOffsetsPosition;
504    }
505
506    public long getLastPosition() {
507        return lastPosition;
508    }
509
510    void setPositions(long stripOrTileOffsetsPosition,
511                      long stripOrTileByteCountsPosition,
512                      long lastPosition) {
513        this.stripOrTileOffsetsPosition = stripOrTileOffsetsPosition;
514        this.stripOrTileByteCountsPosition = stripOrTileByteCountsPosition;
515        this.lastPosition = lastPosition;
516    }
517
518    /**
519     * Returns a <code>TIFFIFD</code> wherein all fields from the
520     * <code>BaselineTIFFTagSet</code> are copied by value and all other
521     * fields copied by reference.
522     */
523    public TIFFIFD getShallowClone() {
524        // Get the baseline TagSet.
525        TIFFTagSet baselineTagSet = BaselineTIFFTagSet.getInstance();
526
527        // If the baseline TagSet is not included just return.
528        List tagSetList = getTagSetList();
529        if(!tagSetList.contains(baselineTagSet)) {
530            return this;
531        }
532
533        // Create a new object.
534        TIFFIFD shallowClone = new TIFFIFD(tagSetList, getParentTag());
535
536        // Get the tag numbers in the baseline set.
537        Set baselineTagNumbers = baselineTagSet.getTagNumbers();
538
539        // Iterate over the fields in this IFD.
540        Iterator fields = iterator();
541        while(fields.hasNext()) {
542            // Get the next field.
543            TIFFField field = (TIFFField)fields.next();
544
545            // Get its tag number.
546            Integer tagNumber = new Integer(field.getTagNumber());
547
548            // Branch based on membership in baseline set.
549            TIFFField fieldClone;
550            if(baselineTagNumbers.contains(tagNumber)) {
551                // Copy by value.
552                Object fieldData = field.getData();
553
554                int fieldType = field.getType();
555
556                try {
557                    switch (fieldType) {
558                    case TIFFTag.TIFF_BYTE:
559                    case TIFFTag.TIFF_SBYTE:
560                    case TIFFTag.TIFF_UNDEFINED:
561                        fieldData = ((byte[])fieldData).clone();
562                        break;
563                    case TIFFTag.TIFF_ASCII:
564                        fieldData = ((String[])fieldData).clone();
565                        break;
566                    case TIFFTag.TIFF_SHORT:
567                        fieldData = ((char[])fieldData).clone();
568                        break;
569                    case TIFFTag.TIFF_LONG:
570                    case TIFFTag.TIFF_IFD_POINTER:
571                        fieldData = ((long[])fieldData).clone();
572                        break;
573                    case TIFFTag.TIFF_RATIONAL:
574                        fieldData = ((long[][])fieldData).clone();
575                        break;
576                    case TIFFTag.TIFF_SSHORT:
577                        fieldData = ((short[])fieldData).clone();
578                        break;
579                    case TIFFTag.TIFF_SLONG:
580                        fieldData = ((int[])fieldData).clone();
581                        break;
582                    case TIFFTag.TIFF_SRATIONAL:
583                        fieldData = ((int[][])fieldData).clone();
584                        break;
585                    case TIFFTag.TIFF_FLOAT:
586                        fieldData = ((float[])fieldData).clone();
587                        break;
588                    case TIFFTag.TIFF_DOUBLE:
589                        fieldData = ((double[])fieldData).clone();
590                        break;
591                    default:
592                        // Shouldn't happen but do nothing ...
593                    }
594                } catch(Exception e) {
595                    // Ignore it and copy by reference ...
596                }
597
598                fieldClone = new TIFFField(field.getTag(), fieldType,
599                                           field.getCount(), fieldData);
600            } else {
601                // Copy by reference.
602                fieldClone = field;
603            }
604
605            // Add the field to the clone.
606            shallowClone.addTIFFField(fieldClone);
607        }
608
609        // Set positions.
610        shallowClone.setPositions(stripOrTileOffsetsPosition,
611                                  stripOrTileByteCountsPosition,
612                                  lastPosition);
613
614        return shallowClone;
615    }
616}