1/*****************************************************************************/
2// tiffinfo
3// Written by Michael Wilber, OBOS Translation Kit Team
4//
5// Version:
6//
7// tiffinfo is a command line program for displaying text information about
8// TIFF images. This information includes a listing of every field (tag) in
9// the TIFF file, for every image in the file.  Also, for some fields,
10// the numerical value for the field is converted to descriptive text.
11//
12// This application and all source files used in its construction, except
13// where noted, are licensed under the MIT License, and have been written
14// and are:
15//
16// Copyright (c) 2003 OpenBeOS Project
17//
18// Permission is hereby granted, free of charge, to any person obtaining a
19// copy of this software and associated documentation files (the "Software"),
20// to deal in the Software without restriction, including without limitation
21// the rights to use, copy, modify, merge, publish, distribute, sublicense,
22// and/or sell copies of the Software, and to permit persons to whom the
23// Software is furnished to do so, subject to the following conditions:
24//
25// The above copyright notice and this permission notice shall be included
26// in all copies or substantial portions of the Software.
27//
28// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
29// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
31// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
33// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
34// DEALINGS IN THE SOFTWARE.
35/*****************************************************************************/
36#include <ByteOrder.h>
37#include <File.h>
38#include <StorageDefs.h>
39#include <iostream.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43
44struct IFDEntry {
45	uint16 tag;
46		// uniquely identifies the field
47	uint16 fieldType;
48		// number, string, float, etc.
49	uint32 count;
50		// length / number of values
51
52	// The actual value or the file offset
53	// where the actual value is located
54	union {
55		float 	floatval;
56		uint32	longval;
57		uint16	shortvals[2];
58		uint8	bytevals[4];
59	};
60};
61
62enum ENTRY_TYPE {
63	TIFF_BYTE = 1,
64	TIFF_ASCII,
65	TIFF_SHORT,
66	TIFF_LONG,
67	TIFF_RATIONAL,
68	TIFF_SBYTE,
69	TIFF_UNDEFINED,
70	TIFF_SSHORT,
71	TIFF_SLONG,
72	TIFF_SRATIONAL,
73	TIFF_FLOAT,
74	TIFF_DOUBLE
75};
76
77const char *
78get_type_string(uint16 type)
79{
80	const char *kstrTypes[] = {
81		"Byte",
82		"ASCII",
83		"Short",
84		"Long",
85		"Rational",
86		"Signed Byte",
87		"Undefined",
88		"Signed Short",
89		"Signed Long",
90		"Signed Rational",
91		"Float",
92		"Double"
93	};
94
95	if (type >= 1 && type <= 12)
96		return kstrTypes[type - 1];
97	else
98		return "?";
99}
100
101const char *
102get_tag_string(uint16 tag)
103{
104	switch (tag) {
105		case 254: return "New Subfile Type";
106		case 255: return "Subfile Type";
107		case 256: return "Image Width";
108		case 257: return "Image Height";
109		case 258: return "Bits Per Sample";
110		case 259: return "Compression";
111		case 262: return "Photometric Interpretation";
112		case 263: return "Thresholding";
113		case 264: return "CellWidth";
114		case 265: return "CellLength";
115		case 266: return "Fill Order";
116		case 269: return "Document Name";
117		case 270: return "Image Description";
118		case 271: return "Make";
119		case 272: return "Model";
120		case 273: return "Strip Offsets";
121		case 274: return "Orientation";
122		case 277: return "Samples Per Pixel";
123		case 278: return "Rows Per Strip";
124		case 279: return "Strip Byte Counts";
125		case 280: return "Min Sample Value";
126		case 281: return "Max Sample Value";
127		case 282: return "X Resolution";
128		case 283: return "Y Resolution";
129		case 284: return "Planar Configuration";
130		case 285: return "Page Name";
131		case 286: return "X Position";
132		case 287: return "Y Position";
133		case 288: return "Free Offsets";
134		case 289: return "Free Byte Counts";
135		case 290: return "Gray Response Unit";
136		case 291: return "Gray Response Curve";
137		case 292: return "T4 Options";
138		case 293: return "T6 Options";
139		case 296: return "Resolution Unit";
140		case 297: return "Page Number";
141		case 305: return "Software";
142		case 306: return "DateTime";
143		case 315: return "Artist";
144		case 316: return "Host Computer";
145		case 320: return "Color Map";
146		case 322: return "Tile Width";
147		case 323: return "Tile Height";
148		case 324: return "Tile Offsets";
149		case 325: return "Tile Byte Counts";
150		case 338: return "Extra Samples";
151		case 339: return "Sample Format";
152		case 529: return "YCbCr Coefficients";
153		case 530: return "YCbCr Subsampling";
154		case 531: return "YCbCr Positioning";
155		case 532: return "Reference Black White";
156		case 32995: return "Matteing";
157		case 32996: return "Data Type"; // obseleted by SampleFormat tag
158		case 32997: return "Image Depth"; // tile / strip calculations
159		case 32998: return "Tile Depth"; // tile / strip calculations
160		case 33432: return "Copyright";
161		case 37439: return "StoNits?";
162
163		default:
164			return "?";
165	}
166}
167
168void
169print_ifd_value(IFDEntry &entry, BFile &file, swap_action swp)
170{
171	switch (entry.tag) {
172		case 254: // NewSubfileType
173			if (entry.count == 1 && entry.fieldType == TIFF_LONG) {
174				if (entry.longval & 1)
175					printf("Low Res (1) ");
176				if (entry.longval & 2)
177					printf("Page (2) ");
178				if (entry.longval & 4)
179					printf("Mask (4) ");
180
181				printf("(0x%.8lx)", entry.longval);
182				return;
183			}
184			break;
185
186		case 256: // ImageWidth
187		case 257: // ImageHeight
188			if (entry.count == 1) {
189				printf("%d",
190					((entry.fieldType == TIFF_SHORT) ?
191						entry.shortvals[0] : static_cast<unsigned int>(entry.longval)));
192				return;
193			}
194			break;
195
196		case 259:
197			if (entry.count == 1 && entry.fieldType == TIFF_SHORT) {
198				switch (entry.shortvals[0]) {
199					case 1:
200						printf("No Compression (1)");
201						return;
202					case 2:
203						printf("CCITT Group 3 1-Dimensional Modified Huffman run-length encoding (2)");
204						return;
205					case 3:
206						printf("Fax Group 3 (3)");
207						return;
208					case 4:
209						printf("Fax Group 4 (4)");
210						return;
211					case 5:
212						printf("LZW (5)");
213						return;
214					case 32773:
215						printf("PackBits (32773)");
216						return;
217				}
218			}
219			break;
220
221		case 262: // PhotometricInterpretation
222			if (entry.count == 1 && entry.fieldType == TIFF_SHORT) {
223				switch (entry.shortvals[0]) {
224					case 0:
225						printf("White is Zero (%d)", entry.shortvals[0]);
226						return;
227					case 1:
228						printf("Black is Zero (%d)", entry.shortvals[0]);
229						return;
230					case 2:
231						printf("RGB (%d)", entry.shortvals[0]);
232						return;
233					case 3:
234						printf("Palette Color (%d)", entry.shortvals[0]);
235						return;
236					case 4:
237						printf("Transparency Mask (%d)", entry.shortvals[0]);
238						return;
239				}
240			}
241			break;
242
243		case 274: // Orientation
244			if (entry.count == 1 && entry.fieldType == TIFF_SHORT) {
245				switch (entry.shortvals[0]) {
246					case 1:
247						printf("top to bottom, left to right (1)");
248						return;
249					case 2:
250						printf("top to bottom, right to left (2)");
251						return;
252					case 3:
253						printf("bottom to top, right to left (3)");
254						return;
255					case 4:
256						printf("bottom to top, left to right (4)");
257						return;
258					case 5:
259						printf("left to right, top to bottom (5)");
260						return;
261					case 6:
262						printf("right to left, top to bottom (6)");
263						return;
264					case 7:
265						printf("right to left, bottom to top (7)");
266						return;
267					case 8:
268						printf("left to right, bottom to top (8)");
269						return;
270				}
271			}
272			break;
273
274		case 278: // RowsPerStrip
275		{
276			const uint32 ksinglestrip = 0xffffffffUL;
277			printf("%u",
278					static_cast<unsigned int>(entry.longval));
279			if (entry.longval == ksinglestrip)
280				printf(" (All rows in first strip)");
281			return;
282		}
283
284		case 284: // PlanarConfiguration
285			if (entry.count == 1 && entry.fieldType == TIFF_SHORT) {
286				if (entry.shortvals[0] == 1) {
287					printf("Chunky (%d)", entry.shortvals[0]);
288					return;
289				}
290				else if (entry.shortvals[0] == 2) {
291					printf("Planar (%d)", entry.shortvals[0]);
292					return;
293				}
294			}
295			break;
296
297		case 296: // ResolutionUnit
298			if (entry.count == 1 && entry.fieldType == TIFF_SHORT) {
299				switch (entry.shortvals[0]) {
300					case 1:
301						printf("None (%d)", entry.shortvals[0]);
302						return;
303					case 2:
304						printf("Inch (%d)", entry.shortvals[0]);
305						return;
306					case 3:
307						printf("Cenimeter (%d)", entry.shortvals[0]);
308						return;
309				}
310			}
311			break;
312
313		default:
314			if (entry.fieldType == TIFF_ASCII) {
315				char ascfield[256] = { 0 };
316
317				if (entry.count <= 4)
318					memcpy(ascfield, &entry.longval, entry.count);
319				else if (entry.count > 4 && entry.count < 256) {
320					ssize_t nread = file.ReadAt(entry.longval, ascfield, entry.count);
321					if (nread != static_cast<ssize_t>(entry.count))
322						ascfield[0] = '\0';
323				}
324
325				if (ascfield[0] != '\0') {
326					printf("%s", ascfield);
327					return;
328				}
329			} else if (entry.fieldType == TIFF_RATIONAL && entry.count == 1) {
330				struct { uint32 numerator; uint32 denominator; } rational;
331
332				ssize_t	nread = file.ReadAt(entry.longval, &rational, 8);
333				if (nread == 8 &&
334					swap_data(B_UINT32_TYPE, &rational, 8, swp) == B_OK) {
335
336					printf("%u / %u (offset: 0x%.8lx)",
337						static_cast<unsigned int>(rational.numerator),
338						static_cast<unsigned int>(rational.denominator),
339						entry.longval);
340					return;
341				}
342			} else if (entry.fieldType == TIFF_SRATIONAL && entry.count == 1) {
343				struct { int32 numerator; int32 denominator; } srational;
344
345				ssize_t	nread = file.ReadAt(entry.longval, &srational, 8);
346				if (nread == 8 &&
347					swap_data(B_INT32_TYPE, &srational, 8, swp) == B_OK) {
348
349					printf("%d / %d (offset: 0x%.8lx)",
350						static_cast<int>(srational.numerator),
351						static_cast<int>(srational.denominator),
352						entry.longval);
353					return;
354				}
355			} else if (entry.fieldType == TIFF_LONG && entry.count == 1) {
356				printf("%u",
357					static_cast<unsigned int>(entry.longval));
358				return;
359			} else if (entry.fieldType == TIFF_SLONG && entry.count == 1) {
360				printf("%d",
361					static_cast<int>(entry.longval));
362				return;
363			} else if (entry.fieldType == TIFF_SHORT && entry.count <= 2) {
364				for (uint32 i = 0; i < entry.count; i++) {
365					if (i > 0)
366						printf(", ");
367					printf("%u", entry.shortvals[i]);
368				}
369				return;
370			} else if (entry.fieldType == TIFF_SSHORT && entry.count <= 2) {
371				for (uint32 i = 0; i < entry.count; i++) {
372					if (i > 0)
373						printf(", ");
374					printf("%d", entry.shortvals[i]);
375				}
376				return;
377			} else if (entry.fieldType == TIFF_BYTE && entry.count <= 4) {
378				for (uint32 i = 0; i < entry.count; i++) {
379					if (i > 0)
380						printf(", ");
381					printf("%u", entry.bytevals[i]);
382				}
383				return;
384			} else if (entry.fieldType == TIFF_SBYTE && entry.count <= 4) {
385				for (uint32 i = 0; i < entry.count; i++) {
386					if (i > 0)
387						printf(", ");
388					printf("%d", entry.bytevals[i]);
389				}
390				return;
391			} else if (entry.fieldType == TIFF_UNDEFINED && entry.count <= 4) {
392				for (uint32 i = 0; i < entry.count; i++) {
393					if (i > 0)
394						printf(", ");
395					printf("0x%.2lx",
396						static_cast<unsigned long>(entry.bytevals[i]));
397				}
398				return;
399			}
400			break;
401	}
402	printf("0x%.8lx", entry.longval);
403}
404
405int swap_value_field(IFDEntry &entry, swap_action swp)
406{
407	switch (entry.fieldType) {
408		case TIFF_BYTE:
409		case TIFF_ASCII:
410		case TIFF_SBYTE:
411		case TIFF_UNDEFINED:
412			if (entry.count > 4) {
413				if (swap_data(B_UINT32_TYPE, &entry.longval, 4, swp) != B_OK)
414					return 0;
415			}
416			return 1;
417
418		case TIFF_LONG:
419		case TIFF_SLONG:
420		case TIFF_RATIONAL:
421		case TIFF_SRATIONAL:
422		case TIFF_DOUBLE:
423			if (swap_data(B_UINT32_TYPE, &entry.longval, 4, swp) != B_OK)
424				return 0;
425			return 1;
426
427		case TIFF_FLOAT:
428			if (swap_data(B_FLOAT_TYPE, &entry.floatval, 4, swp) != B_OK)
429				return 0;
430			return 1;
431
432		case TIFF_SHORT:
433		case TIFF_SSHORT:
434			if (entry.count <= 2) {
435				if (swap_data(B_UINT16_TYPE, &entry.shortvals,
436					entry.count * 2, swp) != B_OK)
437					return 0;
438			} else {
439				if (swap_data(B_UINT32_TYPE, &entry.longval, 4, swp) != B_OK)
440					return 0;
441			}
442
443			return 1;
444	}
445
446	// no error, but unknown type
447	return 2;
448}
449
450int
451report_ifd_entries(BFile &file, uint16 entrycount, swap_action swp)
452{
453	IFDEntry entry;
454
455	if (sizeof(IFDEntry) != 12) {
456		printf("IFDEntry size must be 12\n");
457		return 0;
458	}
459
460	off_t offset = file.Position();
461	for (uint16 i = 0; i < entrycount; offset += 12, i++) {
462		ssize_t nread = file.Read(&entry, 12);
463		if (nread != 12) {
464			printf("unable to read entire ifd entry\n");
465			return 0;
466		}
467		if (swap_data(B_UINT16_TYPE, &entry.tag, 4, swp) != B_OK ||
468			swap_data(B_UINT32_TYPE, &entry.count, 4, swp) != B_OK) {
469			printf("swap_data failed\n");
470			return 0;
471		}
472
473		if (!swap_value_field(entry, swp)) {
474			printf("swap_value_field failed\n");
475			return 0;
476		}
477
478		printf("\nOffset: 0x%.8lx\n", static_cast<unsigned long>(offset));
479		printf(  "   Tag: %s (%d)\n", get_tag_string(entry.tag), entry.tag);
480		printf(  "  Type: %s (%d)\n", get_type_string(entry.fieldType),
481			entry.fieldType);
482		printf(  " Count: %d\n", static_cast<int>(entry.count));
483		printf(  " Value: ");
484		print_ifd_value(entry, file, swp);
485		printf("\n");
486	}
487
488	return 1;
489}
490
491int
492report_ifd(BFile &file, uint32 ifdoffset, swap_action swp)
493{
494	printf("\n<< BEGIN: IFD at 0x%.8lx >>\n\n", ifdoffset);
495
496	if (file.Seek(ifdoffset, SEEK_SET) != ifdoffset) {
497		printf("failed to seek to IFD offset: %d\n",
498			static_cast<unsigned int>(ifdoffset));
499		return 0;
500	}
501
502	uint16 entrycount = 0;
503	ssize_t nread = file.Read(&entrycount, 2);
504	if (nread != 2) {
505		printf("unable to read entry count\n");
506		return 0;
507	}
508	if (swap_data(B_UINT16_TYPE, &entrycount, sizeof(uint16), swp) != B_OK) {
509		printf("failed to swap entrycount\n");
510		return 0;
511	}
512	printf("Entry Count: %d\n", entrycount);
513
514	// Print out entries
515	int ret = report_ifd_entries(file, entrycount, swp);
516
517	if (ret) {
518		uint32 nextIFDOffset = 0;
519
520		nread = file.Read(&nextIFDOffset, 4);
521		if (nread != 4) {
522			printf("unable to read next IFD\n");
523			return 0;
524		}
525		if (swap_data(B_UINT32_TYPE, &nextIFDOffset, sizeof(uint32), swp) != B_OK) {
526			printf("failed to swap next IFD\n");
527			return 0;
528		}
529
530		printf("Next IFD Offset: 0x%.8lx\n", nextIFDOffset);
531		printf("\n<< END: IFD at 0x%.8lx >>\n\n", ifdoffset);
532
533		if (nextIFDOffset != 0)
534			return report_ifd(file, nextIFDOffset, swp);
535		else
536			return 1;
537
538	} else
539		return 0;
540}
541
542int generate_report(const char *filepath)
543{
544	BFile file(filepath, B_READ_ONLY);
545
546	if (file.InitCheck() == B_OK) {
547
548		uint8 buffer[64];
549
550		// Byte Order
551		const uint8 kleSig[] = { 0x49, 0x49, 0x2a, 0x00 };
552		const uint8 kbeSig[] = { 0x4d, 0x4d, 0x00, 0x2a };
553
554		ssize_t nread = file.Read(buffer, 4);
555		if (nread != 4) {
556			printf("Unable to read first 4 bytes\n");
557			return 0;
558		}
559
560		swap_action swp;
561		if (memcmp(buffer, kleSig, 4) == 0) {
562			swp = B_SWAP_LENDIAN_TO_HOST;
563			printf("Byte Order: little endian\n");
564
565		} else if (memcmp(buffer, kbeSig, 4) == 0) {
566			swp = B_SWAP_BENDIAN_TO_HOST;
567			printf("Byte Order: big endian\n");
568
569		} else {
570			printf("Invalid byte order value\n");
571			return 0;
572		}
573
574		// Location of first IFD
575		uint32 firstIFDOffset = 0;
576		nread = file.Read(&firstIFDOffset, 4);
577		if (nread != 4) {
578			printf("Unable to read first IFD offset\n");
579			return 0;
580		}
581		if (swap_data(B_UINT32_TYPE, &firstIFDOffset, sizeof(uint32), swp) != B_OK) {
582			printf("swap_data() error\n");
583			return 0;
584		}
585		printf("First IFD: 0x%.8lx\n", firstIFDOffset);
586
587		// print out first IFD
588		report_ifd(file, firstIFDOffset, swp);
589
590		return 1;
591	}
592
593	return 0;
594}
595
596int main(int argc, char **argv)
597{
598	printf("\n");
599		// put a line break at the beginning of output
600		// to improve readability
601
602	if (argc == 2) {
603
604		printf("TIFF Image: %s\n\n", argv[1]);
605		generate_report(argv[1]);
606
607	} else {
608
609		printf("tiffinfo - reports information about a TIFF image\n");
610		printf("\nUsage:\n");
611		printf("tiffinfo filename.tif\n\n");
612	}
613
614	return 0;
615}
616
617