This is a relatively short tutorial on how to read TIFF (Tagged Image File Format).


In order to follow this specific example, you'll need my original tiff file, which is here (78kb) and a hex editor. (I recommend UltraEdit).


In order to follow the explanation any tiff file will do however.


You might also be interested in the official specification from Adobe.



From the TIFF file specification




largest possible file size: 2**32 bytes


A TIFF file begins with an 8-byte image file header that points to an image file
directory (IFD). An image file directory contains information about the image, as
well as pointers to the actual image data.


Bytes 0-1: The byte order used within the file.
Legal values are:

II (4949.H) (Intel)

MM (4D4D.H) (Motorola)


In the II format, byte order is always from the least significant byte to the most
significant byte, for both 16-bit and 32-bit integers This is called little-endian byte
order. In the MM format, byte order is always from most significant to least
significant, for both 16-bit and 32-bit integers. This is called big-endian byte
order.

Bytes 2-3: An arbitrary but carefully chosen number (42) that further identifies the file as a
TIFF file.

The byte order depends on the value of Bytes 0-1.

Bytes 4-7: The offset (in bytes) of the first IFD.
The directory may be at any location in the
file after the header but must begin on a word boundary. In particular, an Image
File Directory may follow the image data it describes. Readers must follow the
pointers wherever they may lead.
The term byte offset is always used in this document to refer to a location with
respect to the beginning of the TIFF file. The first byte of the file has an offset of
0.


Types



The field types and their sizes are:



An Image File Directory (IFD) consists of a 2-byte count of the number of directory
entries (i.e., the number of fields), followed by a sequence of 12-byte field
entries, followed by a 4-byte offset of the next IFD (or 0 if none). (Do not forget to
write the 4 bytes of 0 after the last IFD.)

There must be at least 1 IFD in a TIFF file and each IFD must have at least one
entry.



IFD Entry



Each 12-byte IFD entry has the following format:

Bytes 0-1 The Tag that identifies the field.

Bytes 2-3 The field Type.

Bytes 4-7 The number of values, Count of the indicated Type.

Bytes 8-11 The Value Offset, the file offset (in bytes) of the Value for the field.

The Value is expected to begin on a word boundary; the correspond-ing
Value Offset will thus be an even number. This file offset may
point anywhere in the file, even after the image data.


Taking apart file 000005d.tif



This means that:

49 49 2A 00 08 00 00 00


Means:

49 49 - Intel (Little Endian Encoding)

2A 00 - (decimal: 42 00) magic number, indicating tiff-ness (2x16 + 10 = 42 )

00 08 00 00 - Offset from begin of file to first IFD (in this case, 8 (The next byte))



All the tags in my test file




Start IFD (2 bytes indicating number of fields)
11 00 - number of fields (17)


Start 12 byte IFD Field Entry (FE 00 04 00 01 00 00 00 02 00 00 00) 1


FE 00 - Tag NewSubFileType

04 00 - Type LONG

01 00 00 00 - 1 value

02 00 00 00 - 00000000000000000000000000000010b


NewSubfileType


A general indication of the kind of data contained in this subfile.
Tag = 254 (FE.H)
Type = LONG
N = 1
Replaces the old SubfileType field, due to limitations in the definition of that field.
NewSubfileType is mainly useful when there are multiple subfiles in a single
TIFF file.
This field is made up of a set of 32 flag bits. Unused bits are expected to be 0. Bit 0
is the low-order bit.
Currently defined values are:
Bit 0 is 1 if the image is a reduced-resolution version of another image in this TIFF file;
else the bit is 0.
Bit 1 is 1 if the image is a single page of a multi-page image (see the PageNumber field
description); else the bit is 0.
Bit 2 is 1 if the image defines a transparency mask for another image in this TIFF file.
The PhotometricInterpretation value must be 4, designating a transparency mask.
These values are defined as bit flags because they are independent of each other.
Default is 0.


This thing is a single page or more.



Start 12 byte IFD Field Entry (01 00 04 00 01 00 00 00 F0 09 00 00) 2


00 01 - Tag (this case: ImageWidth

04 00 - Type of tag (in this case: 4 = LONG 32-bit (4-byte) unsigned integer.)

01 00 00 00 - number of values (in this case: 1)

F0 09 00 00 - Value offset (in this case, direct value, since it fits in one 4 byte sequence)

- Also, since it's little endian, it means F0 09 => 09 F0 = 2544 pixels wide


Start 12 byte IFD Field Entry (01 01 04 00 01 00 00 00 E4 0C 00 00) 3



01 01 - Tag ImageHeight

04 00 - Type LONG

01 00 00 00 - 1 value

E4 0C 00 00 - Direct Value: 3300 (0x0CE4 = 3300)


Start 12 byte IFD Field Entry (02 01 03 00 01 00 00 00 01 00 00 00) 4


02 01 - tag BitsPerSample

03 00 - SHORT

01 00 00 00 - 1 value

01 00 - 1 bit per sample

00 00 - padding


Start 12 byte IFD Field Entry (03 01 03 00 01 00 00 00 04 00 00 00) 5


03 01 - tag Compression

03 00 - SHORT

01 00 00 00 - 1 value

04 00 - Group 4 Fax

00 00 padding





Compression values:

1 Uncompressed

2 CCITT 1D

3 Group 3 Fax

4 Group 4 Fax

5 LZW

6 JPEG

32773 PackBits



Start 12 byte IFD Field Entry (06 01 03 00 01 00 00 00 00 00 00 00) 6





06 01 - tag PhotometricInterpretation

03 00 - SHORT

01 00 00 00 - 1 val

00 00 - WhiteIsZero

00 00 - padding



PhotometricInterpretation values:

0 WhiteIsZero

1 BlackIsZero

2 RGB

3 RGB Palette

4 Transparency mask

5 CMYK

6 YCbCr

7 CIELab



Start 12 byte IFD Field Entry (07 01 03 00 01 00 00 00 01 00 00 00) 7





07 01 - tag Threshholding

03 00 - SHORT

01 00 00 00 - 1 val

01 00 - value of 1

00 00 - padding



Threshholding

For black and white TIFF files that represent shades of gray, the technique used to

convert from gray to black and white pixels.

Tag = 263 (107.H)

Type = SHORT

N = 1

1 = No dithering or halftoning has been applied to the image data.

2 = An ordered dither or halftone technique has been applied to the image data.

3 = A randomized process such as error diffusion has been applied to the image data.

Default is Threshholding = 1. See also CellWidth, CellLength.



Start 12 byte IFD Field Entry (11 01 04 00 01 00 00 00 00 03 00 00) 8



11 01 - tag StripOffsets

04 00 - LONG

01 00 00 00 - 1 val

00 03 00 00 - 0x300 = 768





StripsPerImage = floor ((ImageLength + RowsPerStrip - 1) / RowsPerStrip).

StripsPerImage is not a field. It is merely a value that a TIFF reader will want to

compute because it specifies the number of StripOffsets and StripByteCounts for the

image.

Note that either SHORT or LONG values can be used to specify RowsPerStrip.

SHORT values may be used for small TIFF files. It should be noted, however, that

earlier TIFF specification revisions required LONG values and that some software

may not accept SHORT values.

The default is 2**32 - 1, which is effectively infinity. That is, the entire image is

one strip.

Use of a single strip is not recommended. Choose RowsPerStrip such that each strip is

about 8K bytes, even if the data is not compressed, since it makes buffering simpler

for readers. The 8K value is fairly arbitrary, but seems to work well.



Start 12 byte IFD Field Entry (12 01 03 00 01 00 00 00 01 00 00 00) 9



12 01 - tag Orientation

03 00 - SHORT

01 00 00 00 - 1 val

01 00 - 1

00 00 - padding





Orientation Values

The orientation of the image with respect to the rows and columns.

Tag = 274 (112.H)

Type = SHORT

N = 1

1 = The 0th row represents the visual top of the image, and the 0th column represents

the visual left-hand side.

2 = The 0th row represents the visual top of the image, and the 0th column represents

the visual right-hand side.

3 = The 0th row represents the visual bottom of the image, and the 0th column represents

the visual right-hand side.

4 = The 0th row represents the visual bottom of the image, and the 0th column represents

the visual left-hand side.

5 = The 0th row represents the visual left-hand side of the image, and the 0th column

represents the visual top.

6 = The 0th row represents the visual right-hand side of the image, and the 0th column

represents the visual top.

7 = The 0th row represents the visual right-hand side of the image, and the 0th column

represents the visual bottom.

8 = The 0th row represents the visual left-hand side of the image, and the 0th column

represents the visual bottom.

Default is 1.



Start 12 byte IFD Field Entry (15 01 03 00 01 00 00 00 01 00 00 00) 10



15 01 - tag SamplesPerPixel

03 00 - SHORT

01 00 00 00 - 1 val

01 00 - 1

00 00 - padding



SamplesPerPixel values

The number of components per pixel.

Tag = 277 (115.H)

Type = SHORT

N = 1

SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.

SamplesPerPixel is usually 3 for RGB images.



Start 12 byte IFD Field Entry (16 01 04 00 01 00 00 00 E4 0C 00 00) 11



16 01 - tag RowsPerStrip

04 00 - LONG

01 00 00 00 - 1 val

E4 0C 00 00 - 0x0CE4 = 3300 (this is equal to the ImageHeight, could be coincidence)



RowsPerStrip

Tag = 278 (116.H)

Type = SHORT or LONG

The number of rows in each strip (except possibly the last strip.)

For example, if ImageLength is 24, and RowsPerStrip is 10, then there are 3

strips, with 10 rows in the first strip, 10 rows in the second strip, and 4 rows in the

third strip. (The data in the last strip is not padded with 6 extra rows of dummy

data.)



Start 12 byte IFD Field Entry (17 01 04 00 01 00 00 00 28 69 00 00) 12



17 01 - tag StripByteCount

04 00 - LONG

01 00 00 00 - 1 val

28 69 00 00 - 0x6928 = 26920 (= ~26K)



(Since ImageHeight = 3300, RowsPerStrip = 3300, StripOffSets= single value, there is 1 strip,\

with all the imagedata)



Start 12 byte IFD Field Entry (1A 01 05 00 01 00 00 00 F8 02 00 00) 13



1A 01 - tag XResolution

05 00 - RATIONAL (2 LONG)

01 00 00 00 - 1 val (must be an offset, 2 LONG = 8 bytes and does not fit)

F8 02 00 00 - offset = 0x02F8 = 760 from BOF (we're at 218 now (give or take a few bytes)



Start 12 byte IFD Field Entry (1B 01 05 00 01 00 00 00 F0 02 00 00) 14



1B 01 - tag YResolution

05 00 - RATIONAL

01 00 00 00 - 1 val (will not fit, this is an offset)

F0 02 00 00 - offset = 0x02F0 = 752 from BOF



This means that the 2 LONGs for the Xres start 8 bytes after the 2 LONGs for the Yres, which is ok



Start 12 byte IFD Field Entry (28 01 03 00 01 00 00 00 02 00 00 00) 15



28 01 - tag ResolutionUnit

03 00 - SHORT

01 00 00 00 - 1 val

02 00 - 2 (Inch)

00 00 - pading



ResolutionUnit

Tag = 296 (128.H)

Type = SHORT

Values:

1 = No absolute unit of measurement. Used for images that may have a non-square

aspect ratio but no meaningful absolute dimensions.

2 = Inch.

3 = Centimeter.

Default = 2 (inch).



Start 12 byte IFD Field Entry (31 01 02 00 38 00 00 00 B8 02 00 00) 16



31 01 - tag Software

02 00 - ASCII (bytes)

38 00 00 00 - 0x38 = 56 values of byte (must be an offset)

B8 02 00 00 - offset = 0x02B8 = 696



This means the Software name is from 696 to 752, which is where the YRes starts, this is Good.



Start 12 byte IFD Field Entry (32 01 02 00 14 00 00 00 A4 02 00 00) 17



32 01 - tag DateTime

02 00 - ASCII (bytes)

14 00 00 00 - 0x14 = 20 values of byte ( must be an offset)

A2 02 00 00 - offset = 0x02A2 = 674



This means the DateTim data are from 674 to 694, which means were missing 2 bytes.

(offsets must align on word boundaries (4 bytes)



End of field entries.

4 byte offset for next IFD (or 00 00 00 00 for this-was-the-last)

28 6C 00 00 (0x6C28 = 27688 from the start of file)



We're now at 8header +2IFDheader + (17x12)=204 fields + 4offset = 218 bytes in the file.



Offsets for extra data:

bytes: 20 2 56 8 8

start= 674 |DateTime|padding|Software|YResolution|XResolution| += 94 bytes = 768

And 768, surprise, surprise, is the StripOffsets :)





I'm not sure why those (674-218)= 456 extra bytes are there... (they're all empty anyway)


The actual data starts at 768, and is StripByteCount*Number_of_strips long
Number_of_Strips = (ImageHeight/RowsPerStrip) (=1 in this case)
so the data ends at: 768+26920 = 27688, This his where the next IFD-block starts. This is Good.



In order to break this mulipage tiff into its constituent parts we have to do the following:



  1. open the tiff
  2. read the header for little/big endian format
  3. read the first IFD


    1. read all fields, and remember them.
    2. dump all the offset fields. (except the datapart)

  4. create new file
  5. write tiff header
  6. determine header+fields length


I used to have some working code for this, but I seem to have misplaced it...