/*---------------------------------------------------------------------------- Dumps TIFF files Chris Giese http://SisAndHappy.com/ChrisGiese/ This code is public domain (no copyright). You can do whatever you want with it. 19 Feb. 2013: - Code reworked based on an actual TIFF file loader I wrote - Interesting information from the TIFF file is loaded into a tiff_t struct - Now dumping strip byte counts & file offsets 9 Sep. 2012: - Now displaying tag name before checking if type is correct - Tag 0x125 (Group4Options/T6Options) was not displayed correctly - Added a bunch of tag names from the spec - Code to check TIFF magic value at start of file might've been wrong for big endian ("MM") files 31 Jul. 2012: - Initial release ----------------------------------------------------------------------------*/ #include /* free(), malloc() */ #include /* memset() */ /* FILE, EOF, fopen(), fseek(), fread(), fgetc(), fclose() */ #include /* putchar(), printf() */ #if 0 #include #else typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; #endif typedef struct { uint16_t (*read_16)(const void *buf); uint32_t (*read_32)(const void *buf); unsigned long wd, ht, fax_flags, num_strips; unsigned compr, phot_interp, fill_order, samp_per_pixel, planar; uint32_t *strip_offsets, *strip_byte_counts; uint16_t *bits_per_sample; } tiff_t; /****************************************************************************/ static void destroy_tiff(tiff_t *s) { if(s->strip_offsets != NULL) free(s->strip_offsets); if(s->strip_byte_counts != NULL) free(s->strip_byte_counts); memset(s, 0, sizeof(tiff_t)); } /****************************************************************************/ static uint16_t read_le16(const void *buf0) { const uint8_t *buf = buf0; uint16_t rv; rv = buf[1]; rv <<= 8; rv |= buf[0]; return rv; } /****************************************************************************/ static uint32_t read_le32(const void *buf0) { const uint8_t *buf = buf0; uint32_t rv; rv = buf[3]; rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[0]; return rv; } /****************************************************************************/ static uint16_t read_be16(const void *buf0) { const uint8_t *buf = buf0; uint16_t rv; rv = buf[0]; rv <<= 8; rv |= buf[1]; return rv; } /****************************************************************************/ static uint32_t read_be32(const void *buf0) { const uint8_t *buf = buf0; uint32_t rv; rv = buf[0]; rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[3]; return rv; } /****************************************************************************/ static int read_shorts_to_shorts(const tiff_t *s, uint16_t *dst, unsigned long count, const uint8_t *val_or_off, FILE *f) { unsigned long off, i; uint8_t buf[2]; if(count * sizeof(uint16_t) <= 4) { for(i = 0; i < count; i++) { *dst = s->read_16(val_or_off); dst++; val_or_off += 2; } } else { off = s->read_32(val_or_off); fseek(f, off, SEEK_SET); for(i = 0; i < count; i++) { if(fread(buf, 1, 2, f) != 2) return -1; *dst = s->read_16(buf); dst++; } } return 0; } /****************************************************************************/ static int read_shorts_to_longs(const tiff_t *s, uint32_t *dst, unsigned long count, const uint8_t *val_or_off, FILE *f) { unsigned long off, i; uint8_t buf[2]; if(count * sizeof(uint16_t) <= 4) { for(i = 0; i < count; i++) { *dst = s->read_16(val_or_off); dst++; val_or_off += 2; } } else { off = s->read_32(val_or_off); fseek(f, off, SEEK_SET); for(i = 0; i < count; i++) { if(fread(buf, 1, 2, f) != 2) return -1; *dst = s->read_16(buf); dst++; } } return 0; } /****************************************************************************/ static int read_longs_to_longs(const tiff_t *s, uint32_t *dst, unsigned long count, const uint8_t *val_or_off, FILE *f) { unsigned long off, i; uint8_t buf[4]; if(count * sizeof(uint32_t) <= 4) { *dst = s->read_32(val_or_off); } else { off = s->read_32(val_or_off); fseek(f, off, SEEK_SET); for(i = 0; i < count; i++) { if(fread(buf, 1, 4, f) != 4) return -1; *dst = s->read_32(buf); dst++; } } return 0; } /***************************************************************************** 12-byte TIFF tag: Bytes 0-1 =16-bit Tag Bytes 2-3 =16-bit Type of Value(s) Bytes 4-7 =32-bit Count of Value(s) Bytes 8-11 =32-bit Value (if <= 32 bits) or file offset of Value *****************************************************************************/ static int process_tag(tiff_t *s, const uint8_t *buf, FILE *f) { static const char *type_name[] = { "?", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "SBYTE", "undefined", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE" }; static const unsigned type_size[] = { -1u, 1, 1, 2, 4, 8, 1, -1u, 2, 4, 8, 4, 8 }; /**/ unsigned long count, arg_size; unsigned tag, type; int i; tag = s->read_16(&buf[0]); type = s->read_16(&buf[2]); count = s->read_32(&buf[4]); printf("0x%3X=", tag); if(tag == 0xFE) { printf("NewSubfileType "); if(type != 4) BAD_TYPE: { printf("\nError in process_tag: tag has " "invalid type %u\n", type); return -1; } } else if(tag == 0xFF) { printf("SubfileType "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x100) { printf("ImageWidth "); if(type == 3) s->wd = s->read_16(&buf[8]); else if(type == 4) s->wd = s->read_32(&buf[8]); else goto BAD_TYPE; } else if(tag == 0x101) { printf("ImageLength "); if(type == 3) s->ht = s->read_16(&buf[8]); else if(type == 4) s->ht = s->read_32(&buf[8]); else goto BAD_TYPE; } else if(tag == 0x102) { printf("BitsPerSample[] "); if(type != 3) goto BAD_TYPE; if(s->samp_per_pixel != 0 && s->samp_per_pixel != count) { printf("Error: number of samples per pixel from " "BitsPerSample (%lu) != value from " "SamplesPerPixel (%u)\n", count, s->samp_per_pixel); return -1; } s->samp_per_pixel = (unsigned)count; s->bits_per_sample = malloc((unsigned)count * sizeof(uint16_t)); if(s->bits_per_sample == NULL) { printf("Error: out of memory\n"); return -1; } /* read and store the BitsPerSample vector */ if((i = read_shorts_to_shorts(s, s->bits_per_sample, count, &buf[8], f)) != 0) { printf("Error reading values from file\n"); return i; } } else if(tag == 0x103) { printf("Compression "); if(type != 3) goto BAD_TYPE; s->compr = s->read_16(&buf[8]); } else if(tag == 0x106) { printf("PhotometricInterpretation "); if(type != 3) goto BAD_TYPE; s->phot_interp = s->read_16(&buf[8]); } else if(tag == 0x107) { printf("Thresholding "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x108) { printf("CellWidth "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x109) { printf("CellLength "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x10A) { printf("FillOrder "); if(type != 3) goto BAD_TYPE; s->fill_order = s->read_16(&buf[8]); } else if(tag == 0x10D) { printf("DocumentName "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x10E) { printf("ImageDescription "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x10F) { printf("Make "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x110) { printf("Model "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x111) { printf("StripOffsets[] "); if(type != 3 && type != 4) goto BAD_TYPE; if(s->num_strips != 0 && s->num_strips != count) { printf("Error: number of strips from StripOffsets " "(%lu) != number of strips from StripByte" "Counts (%lu)\n", count, s->num_strips); return -1; } s->strip_offsets = malloc(count * sizeof(unsigned long)); if(s->strip_offsets == NULL) { printf("Error: out of memory\n"); return -1; } s->num_strips = count; i = (type == 3) ? read_shorts_to_longs(s, s->strip_offsets, count, &buf[8], f) : read_longs_to_longs(s, s->strip_offsets, count, &buf[8], f); if(i != 0) { printf("Error reading values from file\n"); return i; } } else if(tag == 0x112) { printf("Orientation "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x115) { printf("SamplesPerPixel "); if(type != 3) goto BAD_TYPE; s->samp_per_pixel = s->read_16(&buf[8]); } else if(tag == 0x116) { printf("RowsPerStrip "); if(type != 3 && type != 4) goto BAD_TYPE; } else if(tag == 0x117) { printf("StripByteCounts[] "); if(type != 3 && type != 4) goto BAD_TYPE; if(s->num_strips != 0 && s->num_strips != count) { printf("Error: number of strips from StripByteCounts " "(%lu) != number of strips from StripOffsets" " (%lu)\n", count, s->num_strips); return -1; } s->strip_byte_counts = malloc(count * sizeof(unsigned long)); if(s->strip_byte_counts == NULL) { printf("Error: out of memory\n"); return -1; } s->num_strips = count; i = (type == 3) ? read_shorts_to_longs(s, s->strip_byte_counts, count, &buf[8], f) : read_longs_to_longs(s, s->strip_byte_counts, count, &buf[8], f); if(i != 0) { printf("Error reading values from file\n"); return i; } } else if(tag == 0x118) { printf("MinSampleValue "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x119) { printf("MaxSampleValue "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x11A) { printf("XResolution "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x11B) { printf("YResolution "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x11C) { printf("PlanarConfiguration "); if(type != 3) goto BAD_TYPE; s->planar = s->read_16(&buf[8]); } else if(tag == 0x11D) { printf("PageName "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x11E) { printf("XPosition "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x11F) { printf("YPosition "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x120) { printf("FreeOffsets[] "); if(type != 4) goto BAD_TYPE; } else if(tag == 0x121) { printf("FreeByteCounts[] "); if(type != 4) goto BAD_TYPE; } else if(tag == 0x122) { printf("GrayResponseUnit "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x123) { printf("GrayResponseCurve "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x124) { printf("Group3Options/T4Options "); if(type != 4) goto BAD_TYPE; s->fax_flags = s->read_32(&buf[8]); } else if(tag == 0x125) { printf("Group4Options/T6Options "); if(type != 4) goto BAD_TYPE; s->fax_flags = s->read_32(&buf[8]); } else if(tag == 0x128) { printf("ResolutionUnit "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x129) { printf("PageNumber "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x12D) { printf("TransferFunction "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x131) { printf("Software "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x132) { printf("DateTime "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x13B) { printf("Artist "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x13C) { printf("HostComputer "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x13D) { printf("Predictor "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x13E) { printf("WhitePoint "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x13F) { printf("PrimaryChromaticities "); if(type != 5) goto BAD_TYPE; } else if(tag == 0x140) { printf("ColorMap "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x141) { printf("HalftoneHints "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x142) { printf("TileWidth "); if(type != 3 && type != 4) goto BAD_TYPE; } else if(tag == 0x143) { printf("TileLength "); if(type != 3 && type != 4) goto BAD_TYPE; } else if(tag == 0x144) { printf("TileOffsets[] "); if(type != 4) goto BAD_TYPE; } else if(tag == 0x145) { printf("TileByteCounts[] "); if(type != 3 && type != 4) goto BAD_TYPE; } else if(tag == 0x14C) { printf("InkSet "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x14D) { printf("InkNames "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x14E) { printf("NumberOfInks "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x150) { printf("DotRange "); if(type != 1 && type != 3) goto BAD_TYPE; } else if(tag == 0x151) { printf("TargetPrinter "); if(type != 2) goto BAD_TYPE; } else if(tag == 0x152) { printf("ExtraSamples "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x153) { printf("SampleFormat "); if(type != 3) goto BAD_TYPE; } else if(tag == 0x154) printf("SMinSampleValue "); else if(tag == 0x155) printf("SMaxSampleValue "); else if(tag == 0x156) { printf("TransferRange "); if(type != 3) goto BAD_TYPE; } else printf("??? "); if(type >= sizeof(type_name) / sizeof(type_name[0])) printf("=?? "); else printf("=%-9s", type_name[type]); printf(" %3lu ", count); if(type >= sizeof(type_name) / sizeof(type_name[0])) arg_size = -1u; else arg_size = type_size[type] * count; printf(" %4lu ", arg_size); if(count == 1 || type == 2) { if(type == 1) /* BYTE */ printf(" %u", buf[8]); else if(type == 2) /* ASCII */ { int i; fseek(f, s->read_32(&buf[8]), SEEK_SET); for(; count != 0; count--) { i = fgetc(f); if(i == EOF || i == 0) break; putchar(i); } } else if(type == 3) /* SHORT */ printf("%u", s->read_16(&buf[8])); else if(type == 4) /* LONG */ printf("%lu", s->read_32(&buf[8])); else if(type == 5) /* RATIONAL */ { unsigned long n, d; fseek(f, s->read_32(&buf[8]), SEEK_SET); if(fread(buf, 1, 8, f) == 8) { n = s->read_32(buf); d = s->read_32(&buf[4]); printf("%lu/%lu=%-5.1f", n, d, (double)n / d); } } else if(type == 6) /* SBYTE */ printf("%d", (signed char)(buf[8])); else if(type == 8) /* SSHORT */ printf("%d", s->read_16(&buf[8])); else if(type == 9) /* SLONG */ printf("%ld", s->read_32(&buf[8])); else if(type == 10) /* SRATIONAL */ { long n, d; fseek(f, s->read_32(&buf[8]), SEEK_SET); if(fread(buf, 1, 8, f) == 8) { n = s->read_32(buf); d = s->read_32(&buf[4]); printf("%ld/%ld=%-5.1f", n, d, (double)n / d); } } else if(type == 11) /* FLOAT */ printf("%f", (float)s->read_32(&buf[8])); /* else if(type == 12) DOUBLE */ else printf("??"); } printf("\n"); return 0; } /****************************************************************************/ static int dump_tiff(tiff_t *s, FILE *f) { unsigned long dir_pos; unsigned num_tags, i; uint8_t buf[12]; int j; /* scan tags */ while(1) { if(fread(buf, 1, 4, f) != 4) ERR: { printf("Error reading from file\n"); return -1; } if((dir_pos = s->read_32(buf)) == 0) break; /* seek to image file directory */ fseek(f, dir_pos, SEEK_SET); printf("Image file directory at file offset %lu:\n", dir_pos); /* read the number of entries */ if(fread(buf, 1, 2, f) != 2) goto ERR; num_tags = s->read_16(buf); printf( " arg val\n" "## tag arg type cnt size value\n" "-- -------------------------------- ----------- --- ---- ---------------\n"); for(i = 0; i < num_tags; i++) { /* seek to 12-byte tag and read it */ fseek(f, dir_pos + 2 + 12 * i, SEEK_SET); if(fread(buf, 1, 12, f) != 12) return -1; /* dump it */ printf("%2u ", i); if((j = process_tag(s, buf, f)) != 0) return j; } if(s->num_strips != 0) { printf("Strip file offsets:\n"); for(i = 0; i < s->num_strips; i++) printf("%6lu ", s->strip_offsets[i]); printf("\nStrip byte counts:\n"); for(i = 0; i < s->num_strips; i++) printf("%6lu ", s->strip_byte_counts[i]); printf("\n"); } /* read file offset of next directory (=0 if end) */ fseek(f, dir_pos + 2 + 12 * num_tags, SEEK_SET); } return 0; } /****************************************************************************/ int main(int arg_c, char *arg_v[]) { uint8_t buf[4]; tiff_t s; FILE *f; int i; for(i = 1; i < arg_c; i++) { if((f = fopen(arg_v[i], "rb")) == NULL) { printf("Error: can't open file '%s'\n", arg_v[i]); continue; } if(fread(buf, 1, 4, f) != 4) NOT: { fclose(f); printf("Error: file '%s' is not a TIFF file\n", arg_v[i]); continue; } memset(&s, 0, sizeof(tiff_t)); /* little-endian TIFF ('I' for Intel) */ if(buf[0] == 'I' && buf[1] == 'I' && read_le16(&buf[2]) == 42) { s.read_16 = read_le16; s.read_32 = read_le32; } /* big-endian TIFF ('M' for Motorola) */ else if(buf[0] == 'M' && buf[1] == 'M' && read_be16(&buf[2]) == 42) { s.read_16 = read_be16; s.read_32 = read_be32; } else goto NOT; printf("TIFF file '%s':\n", arg_v[i]); (void)dump_tiff(&s, f); destroy_tiff(&s); fclose(f); } return 0; }