Source code: com/flexstor/flexdbserver/services/asset/iptc/TiffFile.java
1 /*
2 * TiffFile.java
3 *
4 * Copyright $Date: 2003/08/11 02:22:34 $ FLEXSTOR.net Inc.
5 *
6 * This work is licensed for use and distribution under license terms found at
7 * http://www.flexstor.org/license.html
8 *
9 */
10
11 package com.flexstor.flexdbserver.services.asset.iptc;
12
13 import java.io.IOException;
14 import java.util.Hashtable;
15
16 import com.flexstor.common.io.xfile.FlexXFile;
17 import com.flexstor.common.io.xfile.FlexXRandomAccessFile;
18
19
20 /**
21 *
22 *
23 */
24 public class TiffFile
25 {
26 protected FlexXRandomAccessFile refXRandomFile = null;
27 protected Hashtable htTagData = null;
28 protected Hashtable htExcludeTagIds = null; // User can set the Ids of tags which will be excluded
29 protected Hashtable htIncludeTagIds = null; // User can set the Ids of tags which will be included
30 protected boolean bIntelFormat = true;
31 protected long nSavedPointer = 0;
32
33 // ***** DEBUG switch *****
34 protected boolean bDebug = false;
35
36 /**
37 *
38 *
39 */
40 public TiffFile(String sFilename)
41 {
42
43 htTagData = new Hashtable();
44 htExcludeTagIds = new Hashtable();
45 htIncludeTagIds = new Hashtable();
46
47 FlexXFile refXFile = new FlexXFile(sFilename);
48 openFile(refXFile);
49 } // constructor 1
50
51
52 /**
53 *
54 *
55 */
56 public TiffFile(FlexXFile refXFile)
57 {
58
59 htTagData = new Hashtable();
60 htExcludeTagIds = new Hashtable();
61 htIncludeTagIds = new Hashtable();
62
63 openFile(refXFile);
64 } // constructor 2
65
66
67 /**
68 *
69 *
70 */
71 public boolean loadTagDirectory()
72 {
73
74 // Is the file open?
75 if (refXRandomFile == null)
76 {
77 ABNError("File not accessable");
78 return false;
79 }
80
81 // Read the first 8 bytes of the file. This is the image file header
82 // bytes 0-1 byte order (0x49, 0x49 = Intel; 0x4d, 0x4d = Motorola)
83 // bytes 2-3 tiff file type identifier (ignore)
84 // bytes 4-7 The offset from file start (0) of the first IFD
85 byte[] anHeader = readBytes(8);
86 if (anHeader == null)
87 {
88 ABNError("Could not read image file header");
89 closeFile();
90 return false;
91 }
92
93 // The first 2 bytes identify the byte order. Get and save it
94 if ((anHeader[0] == 0x49) && (anHeader[1] == 0x49))
95 {
96 bIntelFormat = true; // It's Intel format
97 ABNDebug("Intel Format");
98 }
99
100 else if ((anHeader[0] == 0x4d) && (anHeader[1] == 0x4d))
101 {
102 bIntelFormat = false; // It's Motorola format
103 ABNDebug("Motorola Format");
104 }
105
106 else
107 {
108 ABNError("Error: Not TIFF file header");
109 closeFile();
110 return false;
111 }
112
113 // The next two bytes further identify the TIFF file
114 // Ignore these because the value is not always consistent with the spec
115
116 // The next 4 byte value is the position from the start of the file of the
117 // first IFD which may be ANYWHERE in the file
118 long nIfdOffset = (long)binaryToInt(anHeader, 4, 4);
119 if (nIfdOffset == -1)
120 {
121 ABNError("Error: Bad IFD offset");
122 closeFile();
123 return false;
124 }
125
126 // Process the first IFD and any that may follow. This method may be
127 // recursive if more than one IFD exists. The end result should be that
128 // all of the data for the tags found will be put into the hashtable, htTagData,
129 // which can be accessed thru the public method getTagData(nTagId)
130 if (processIFD(nIfdOffset) == false)
131 {
132 closeFile();
133 return false;
134 }
135
136 closeFile();
137 return true;
138 } // loadTagDirectory
139
140
141 /**
142 *
143 *
144 */
145 public byte[] getTagData(int nTagID)
146 {
147 return (byte[])htTagData.get(new Integer(nTagID));
148 } // getTagData
149
150
151 /**
152 *
153 *
154 */
155 public boolean isIntel()
156 {
157 return bIntelFormat;
158 }
159
160
161 /**
162 *
163 *
164 */
165 public FlexXRandomAccessFile getFileReference()
166 {
167 return refXRandomFile;
168 }
169
170
171 /**
172 * The Tad Id exclude table can contain the id of those tags that will NOT be parsed
173 *
174 */
175 public void setExcludeTagIdTable(Hashtable htTable)
176 {
177 htExcludeTagIds = htTable;
178 }
179
180
181 /**
182 *
183 *
184 */
185 public Hashtable getExcludeTagIdTable()
186 {
187 return htExcludeTagIds;
188 }
189
190
191 /**
192 *
193 *
194 */
195 public boolean addExcludeTagId(int nTagId)
196 {
197 if (htExcludeTagIds == null)
198 {
199 return false;
200 }
201
202 htExcludeTagIds.put(new Integer(nTagId), new Integer(nTagId));
203 return true;
204 }
205
206
207 /**
208 *
209 *
210 */
211 public boolean removeExcludeTagId(int nTagId)
212 {
213 if (htExcludeTagIds == null)
214 {
215 return false;
216 }
217
218 htExcludeTagIds.remove(new Integer(nTagId));
219 return true;
220 }
221
222
223 /**
224 * The Tad Id include table can contain the id of those tags that will NOT be parsed
225 *
226 */
227 public void setIncludeTagIdTable(Hashtable htTable)
228 {
229 htIncludeTagIds = htTable;
230 }
231
232
233 /**
234 *
235 *
236 */
237 public Hashtable getIncludeTagIdTable()
238 {
239 return htIncludeTagIds;
240 }
241
242
243 /**
244 *
245 *
246 */
247 public boolean addIncludeTagId(int nTagId)
248 {
249 if (htIncludeTagIds == null)
250 {
251 return false;
252 }
253
254 htIncludeTagIds.put(new Integer(nTagId), new Integer(nTagId));
255 return true;
256 }
257
258
259 /**
260 *
261 *
262 */
263 public boolean removeIncludeTagId(int nTagId)
264 {
265 if (htIncludeTagIds == null)
266 {
267 return false;
268 }
269
270 htIncludeTagIds.remove(new Integer(nTagId));
271 return true;
272 }
273
274 /**
275 *
276 *
277 */
278 protected boolean isTagExcluded(int nTagId)
279 {
280 if (htExcludeTagIds == null)
281 {
282 return false;
283 }
284
285 return htExcludeTagIds.containsKey(new Integer(nTagId));
286 }
287
288
289 /**
290 *
291 *
292 */
293 protected boolean isTagIncluded(int nTagId)
294 {
295 if (htIncludeTagIds == null)
296 {
297 return true;
298 }
299
300 if (htIncludeTagIds.isEmpty() == true)
301 {
302 // If it is empty then assume all are included
303 return true;
304 }
305
306 return htIncludeTagIds.containsKey(new Integer(nTagId));
307 }
308
309
310 /**
311 *
312 *
313 */
314 protected boolean processIFD(long nIfdOffset)
315 {
316 boolean bResult = true;
317
318 // The IFD (Image File Directory) contains 1 or more tags followed by
319 // an optional 0-based offset to the next IFD
320 // bytes n+0, n+1 number of tags (2 bytes)
321 // bytes n+2 - n+14 first tag (12 bytes)
322 // bytes n+15- n+27 second tag (12 bytes)
323 // ...
324 // bytes n+2+number-of-tags*12 Offset to next IFD (4 bytes)
325 // Note: Offset = 0000 if no further IFDs
326
327 // Seek to the IFD
328 if (goToOffset(nIfdOffset) == false)
329 {
330 ABNError("Could not seek to IFD offset: " + nIfdOffset);
331 return false;
332 }
333
334 // Read the first 2-bytes of the IFD. This contains the number of
335 // tags in this IFD
336 byte[] anIFDBuffer = readBytes(2);
337 if (anIFDBuffer == null)
338 {
339 ABNError("Could not read tag count");
340 return false;
341 }
342
343 int nTagCount = binaryToInt(anIFDBuffer, 0, 2);
344 if (nTagCount < 0)
345 {
346 ABNError("Invalid tag count");
347 return false;
348 }
349
350 // Process the tags for this IFD
351 if (processTags(nTagCount) == false)
352 {
353 ABNError("Process tags error");
354 }
355
356 // Check for another IFD
357 // Restore the pointer to the end of the just processed IFD
358 if (restorePointer(nSavedPointer) == false)
359 {
360 ABNError("Could not restore file pointer position");
361 return false;
362 }
363
364 anIFDBuffer = readBytes(4);
365 if (anIFDBuffer == null)
366 {
367 ABNError("Could not read next IFD offset");
368 return false;
369 }
370
371 int nNextIfdOffset = binaryToInt(anIFDBuffer, 0, 4);
372 if (nNextIfdOffset == -1)
373 {
374 ABNError("Next IFD offset is invalid");
375 return false;
376 }
377
378 // If the offset is zero, there are no further IFDs
379 if (nNextIfdOffset != 0)
380 {
381 // Recursively process the next IFD
382 bResult = processIFD(nNextIfdOffset);
383 }
384
385 return bResult;
386 } // processIFD
387
388
389 /**
390 *
391 *
392 */
393 protected boolean processTags(int nTagCount)
394 {
395 boolean bResult = true;
396
397 // Read all of the raw tags for this IFD
398 // There are nTagCount 12-bytes tags in the IFD
399 // Read them from the file
400 byte[] anTags = readBytes(12*nTagCount);
401 if (anTags == null)
402 {
403 ABNError("Could not read tags");
404 return false;
405 }
406
407 // Save the current file pointer to be restored later after processing this IFD
408 nSavedPointer = savePointer();
409
410 // Read and parse each tag
411 byte[] anRawTag = new byte[12];
412 for (int i=0; i<12*nTagCount; i+=12)
413 {
414 // Get each 12-byte chunk from the tag buffer
415 System.arraycopy(anTags, i, anRawTag, 0, 12);
416
417 // Parse the tag
418 if (parseTag(anRawTag) == false)
419 {
420 ABNError("Error parsing tag");
421 bResult = false;
422 break;
423 }
424 } // for i
425
426 return bResult;
427 } // processTags
428
429
430
431 /**
432 *
433 *
434 */
435 protected boolean parseTag(byte[] anTagBuffer)
436 {
437 boolean bResult = true;
438
439 // Each tag consists of the following 12 bytes:
440 // bytes 0-1 Tag id (2 bytes)
441 // bytes 2-3 Field type (2 bytes)
442 // bytes 4-7 Count of type values (4 bytes)
443 // bytes 8-11 The value offset of (4 bytes)
444 // the value itself if it
445 // fits in 4 bytes (offset is from start of file)
446 //
447
448 // Get the tag id (bytes 0,1)
449 int nTagId = binaryToInt(anTagBuffer, 0, 2);
450 if (nTagId == -1)
451 {
452 ABNError("Could not read tag ID");
453 return false;
454 }
455
456 // The user can specify a specific tag or group of tags to parse
457 // If the include hash table is not empty (i.e. at least one include tag
458 // was specified, then if this tag is not in that table, skip it.
459 if (isTagIncluded(nTagId) == false)
460 {
461 ABNDebug("Tag " + nTagId + " is not in exclude set and user specified at least one id for include.");
462 return true;
463 }
464
465 // The user can specify any tag id as excludable (see public methods)
466 // If this tag is in the exclude hash table, then skip it.
467 if (isTagExcluded(nTagId) == true)
468 {
469 ABNDebug("Tag " + nTagId + " is excluded");
470 return true;
471 }
472
473 // Get the field type
474 int nFieldType = binaryToInt(anTagBuffer, 2, 2);
475 if (nFieldType == -1)
476 {
477 ABNError("Could not read tag field type");
478 return false;
479 }
480
481 if (nFieldType > 8)
482 {
483 // Field type is not in our list, exit but go on to next tag
484 ABNError("Unknown tag field type: " + nFieldType);
485 return true;
486 }
487
488
489 // Based on the field type determine the number of bytes per value.
490 // Types:
491 // 1 = BYTE 1 - byte
492 // 2 = ASCII 1 - byte
493 // 3 = SHORT 2 - bytes
494 // 4 = LONG 4 - bytes
495 // 5 = RATIONAL 8 - bytes (2 LONGS, num/den)
496 // 7 = UNDEFINED 1 - byte (NOTE: in the following table, we are setting the value size to zero
497 // because the sample files have large many bytes at the offset
498 // and, at this point, we aren't interested in it)
499 byte[] anBytesPerValueTable = {0, 1, 1, 2, 4, 8, 0, 0};
500 int nBytesPerValue = anBytesPerValueTable[nFieldType];
501 if (nBytesPerValue == 0)
502 {
503 // Field type is not in our list, exit but go on to next tag
504 ABNError("Unknown tag field type");
505 return true;
506 }
507
508 // The next 4-bytes contain the number of values of the indicated type
509 int nValueCount = binaryToInt(anTagBuffer, 4, 4);
510 if (nValueCount == -1)
511 {
512 ABNError("Could not read value count");
513 return false;
514 }
515
516 // The next 4-bytes are the value offset. If the total value fits in the value offset
517 // (4 bytes or less) then it is placed left-justified in the value offset. Otherwise,
518 // the value-offset represents an offset in the file to the location that
519 // the value is stored.
520 int nTotalByteCount = nValueCount * nBytesPerValue;
521 ABNDebug("Tag ID: " + nTagId + ", Byte count: " + nTotalByteCount);
522 byte[] anTagValue = new byte[nTotalByteCount];
523 if (nTotalByteCount <= 4)
524 {
525 // The value offset is the value itself
526 // Copy it to the tag value array
527 System.arraycopy(anTagBuffer, 8, anTagValue, 0, nTotalByteCount);
528 }
529
530 else
531 {
532 // The value offset is indeed the offset to the actual data
533 // Get the offset
534 int nValueOffset = binaryToInt(anTagBuffer, 8, 4);
535 if (nValueOffset == -1)
536 {
537 ABNError("Could not read tag value offset");
538 return false;
539 }
540
541 // Go to the file offset and read the value to the tag value array
542 if (goToOffset(nValueOffset) == false)
543 {
544 ABNError("Could not seek to tag value offset: " + nValueOffset);
545 return false;
546 }
547
548 // Read the bytes in the file at the offset specified in the tag
549 anTagValue = readBytes(nTotalByteCount);
550 if (anTagValue == null)
551 {
552 ABNError("Could not read value at offset " + nValueOffset + " for tag " + nTagId);
553 return false;
554 }
555
556 } // nTotalByteCount else
557
558 // We have all the info from this tag we need so put it in the hashtable
559 // Key = tag id, value = byte array containing the tag value
560 htTagData.put(new Integer(nTagId), anTagValue);
561
562 return bResult;
563 } // parseTag
564
565
566 /**
567 *
568 *
569 */
570 protected int binaryToInt(byte[] anBuffer, int nStartIndex, int nLength)
571 {
572 int nResult = -1;
573 if ((nStartIndex + nLength) > anBuffer.length)
574 {
575 ABNError("Array out of bounds in binaryToInt()");
576 return nResult;
577 }
578
579 byte[] anTemp = new byte[nLength];
580 if (isIntel() == true)
581 {
582 // Reverse the byte order for Intel (little-endian)
583 for (int i=0; i<nLength; i++)
584 {
585 anTemp[i] = anBuffer[nStartIndex + nLength - i - 1];
586 }
587 }
588
589 else
590 {
591 for (int i=0; i<nLength; i++)
592 {
593 anTemp[i] = anBuffer[nStartIndex + i];
594 }
595 }
596
597 nResult = hexBytesToInt(anTemp, 0, nLength);
598 return nResult;
599 } // binaryToInt
600
601
602 /** This method converts part of an array of bytes into an integer value.
603 * (For our purposes, we will not exceed four bytes.)
604 * @param abHexBytes The array of bytes. Some of which we will convert to an int.
605 * @param nStartPos The start of our byte value that will be converted.
606 * @param nLength The number of bytes that will be converted
607 * @return Returns an int value of the specifiec portion of our byte array.
608 * (i.e. {0x1C, 0x2B} would be converted to 7211)
609 */
610 protected int hexBytesToInt (byte abHexBytes[], int nStartPos, int nLength) {
611
612 // The soon-to-be-computed value of the byte array.
613 int nBytesValue = 0;
614
615 // The number of bits that need to get shifted is equal to one less than the
616 // number of bytes times eight. (i.e. 0x01, 0xC2 : the first byte 0x01 needs
617 // to be shifted 8 bits to the left before it is added to the second byte 0xC2.
618 int nBitShifter = ( (nLength - 1) * 8 );
619
620 // process each of the bytes in turn.
621 for ( int i=0; i < nLength; i++ ) {
622 //ABNDebug( "byte " + i + " is " + abHexBytes[ nStartPos+i ] +
623 // ", and nBitShifter is " + nBitShifter);
624 // arithmatic is always performed at least at 32-bit precision. This has the
625 // side effect of un-signing our signed byte before we bitshift.
626 nBytesValue |= (( abHexBytes[ nStartPos+i ] & 0x000000ff ) << nBitShifter );
627 // Each byte we process, we need to adjust the distance the bits are shifted.
628 nBitShifter = nBitShifter - 8;
629 }
630 return nBytesValue;
631 } // hexBytesToInt
632
633
634
635
636 /**
637 *
638 *
639 */
640 protected void openFile(FlexXFile refXFile)
641 {
642
643 try
644 {
645 refXRandomFile = new FlexXRandomAccessFile(refXFile, "r");
646 }
647
648 catch(IOException ioe)
649 {
650 ABNError("Could not open random access file." + ioe.toString());
651 }
652 } // openFile
653
654
655 /**
656 *
657 *
658 */
659 protected void closeFile()
660 {
661 if (refXRandomFile == null)
662 {
663 ABNError("Could not close random access TIFF file.");
664 return;
665 }
666
667 try
668 {
669 refXRandomFile.close();
670 }
671
672 catch(IOException ioe)
673 {
674 ABNError("Could not close random access file." + ioe.toString());
675 }
676 } // closeFile
677
678
679 /**
680 *
681 *
682 */
683 protected byte[] readBytes(int nCount)
684 {
685 byte[] anBytes = new byte[nCount];
686
687 if (refXRandomFile == null)
688 {
689 ABNError("File reference is null pointer - readBytes");
690 return null;
691 }
692
693 try
694 {
695 int nStatus = refXRandomFile.read(anBytes);
696 if (nStatus == -1)
697 {
698 ABNError("Could not read. End of file");
699 return null;
700 }
701 }
702
703 catch (IOException ioe)
704 {
705 ABNError("Error reading file: " + ioe.toString());
706 return null;
707 }
708
709 return anBytes;
710 } // readBytes
711
712
713 /**
714 *
715 *
716 */
717 protected boolean goToOffset(long nOffset)
718 {
719 if (refXRandomFile == null)
720 {
721 ABNError("File reference is a null pointer - goToOffset");
722 return false;
723 }
724
725 try
726 {
727 refXRandomFile.seek(nOffset);
728 }
729
730 catch (IOException ioe)
731 {
732 ABNError("File error in goToOffset: " + ioe.toString());
733 return false;
734 }
735
736 return true;
737 } // goToOffset
738
739
740 /**
741 *
742 *
743 */
744 protected boolean restorePointer(long nFilePosition)
745 {
746 return goToOffset(nFilePosition);
747 } // restorePointer
748
749 /**
750 *
751 *
752 */
753 protected long savePointer()
754 {
755 long nPos = -1;
756 if (refXRandomFile == null)
757 {
758 return -1;
759 }
760
761 try
762 {
763 nPos = refXRandomFile.getFilePointer();
764 }
765
766 catch (IOException ioe)
767 {
768 ABNError("File error getting position: " + ioe.toString());
769 nPos = -1;
770 }
771
772 return nPos;
773 } // savePointer
774
775
776 /**
777 * Debug code
778 *
779 */
780 protected void printTagData(byte[] anTagData)
781 {
782 if (bDebug == false)
783 {
784 return;
785 }
786
787
788 // Print out the contents of the tag data buffer
789 // Use the hard way since printing as a string does'nt work well due
790 // to the various wierd characters that it may contain.
791 // Replace all chars < 0x02 (except for 0x0d) with an asterisk
792 // Note that the replaced characters are important for this application.
793 // They are the tags.
794 String sBuffer = "";
795 for (int i=0; i<anTagData.length; i++)
796 {
797 byte nChar = anTagData[i];
798 if ((nChar < 0x20) && (nChar != 0x0d))
799 {
800 nChar = 0x2a;
801 }
802
803 sBuffer += (char)nChar;
804 } // for i
805
806 ABNDebug(sBuffer);
807 } // printTagData
808
809
810 /**
811 *
812 *
813 */
814 protected void ABNDebug(String sMsg)
815 {
816 if (bDebug)
817 {
818 System.out.println("TiffFile DEBUG: " + sMsg);
819 }
820 }
821
822
823 /**
824 *
825 *
826 */
827 protected void ABNError(String sMsg)
828 {
829 System.out.println("TiffFile: " + sMsg);
830 }
831
832
833
834 } // class TiffFile