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