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