TinyTIFF
a lightweight C/C++ library for reading and writing TIFF files
TinyTIFF - A lightweight C++ library for Writing and Reading TIFF-files

This is a lightweight C++ library that allows to read and write TIFF files. It only implements limited support for the features of TIFF. It was developed as a replacement for libTIFF for some very specific cases, where performance in writing (and reading) is much more important than full feature support. TinyTIFF e.g. does not support compression. Also the multi-frame TIFF-support is taylored (actually that is one of the main reasons for this library) to writing sequence of equally dimensioned images fast. The currently supported features are:

  • for WRITING (TinyTIFFWriter):
    • TIFF-only (no BigTIFF), i.e. max. 4GB
    • multiple frames per file (actually this is the scope of this lib, to write such multi-page files fast), but with the limitation that all frames have the same dimension, data-type and number of samples. The latter allows for the desired speed optimizations!
    • uncompressed frames
    • one, or more samples per frame
    • data types: UINT, INT, FLOAT, 8-64-bit
    • photometric interpretations: Greyscale, RGB, including ALPHA information
    • planar (R1R2R3...G1G2G3...B1B2B3...) or chunky (R1G1B1R2G2B2R3G3B3...) data organization for multi-sample data
    • writes stripped TIFFs only, no tiled TIFFs
  • for READING (TinyTIFFReader):
    • TIFF-only (no BigTIFF), i.e. max. 4GB
    • uncompressed frames
    • one, or more samples per frame
    • data types: UINT, INT, FLOAT, 8-64bit
    • planar and chunky data organization, for multi-sample data
    • no suppoer for palleted images
    • stripped TIFFs only, tiling is not supported

This software is licensed under the term of the GNU Lesser General Public License 3.0 (LGPL 3.0) or above. See TinyTIFF: LGPL >= 3.0 for details.

Some helpful links:

TinyTIFFWriter

TinyTIFFWriter: Usage

The methods in this file allow to write TIFF files with limited capabilites, but very fast. Usually writing TIFF files with a library like libTIFF is relatively slow, when multiple images are written into a single file. The methods in this files overcome this problem by implementing a tiny writer lib that allows to write a TIFF file where all images have the same properties (size, bit depth, ...). This is a situation thet occurs e.g. in cases where a camera acquires a video that should be saved as TIFF file. The library works like this (write 50 32x32 pixel 8-bit images:

TinyTIFFWriterFile* tif=TinyTIFFWriter_open("myfil.tif", 8, 1, 32, 32);
if (tif) {
for (uint16_t frame=0; frame<50; frame++) {
const uint8_t* data=readImage();
}
}

TinyTIFFWriter: Multi-Sample Images (e.g. RGB)

You can also store multi-sample images. Their interpretation has to be specified in the call to TinyTIFFWriter_open(), e.g. for an RGB-image, use:

if (tif) {
for (uint16_t frame=0; frame<50; frame++) {
const uint8_t* data=readImage();
}
}

Note, that TinyTIFFWriter_writeImage() expects the image data in the given buff data is given in interleaved order, i.e. R1G1B1R2G2B2R3G3B3... and writes it out in the same order. If you specify more or less samples, the same rules apply. You can use the extended method TinyTIFFWriter_writeImageMultiSample() if your data is not in chunky format, or you want to write the data out in planar form, instead of chunky (e.g. to make the data readable by TinyTIFFReader.

Also it is possibly to specify that one of the extra channels ahall be used as ALPHA information. then you need to call TinyTIFFWriter_open(..., TinyTIFFWriter_RGBA) or TinyTIFFWriter_open(..., TinyTIFFWriter_GreyscaleAndAlpha). All samples additional to the ones covered by the interpretation flag are treated as extraSamples with unspecified type (see TIFF specification for details).

TinyTIFFWriter: How it Works

The images are written in big- or little-endian according to your system. The TIFF header is set accordingly, so we do not need to shuffle around bytes when writing, but the created TIFF file may differ from hardware system to hardware system, although the same data is written (once in little-endian, once in big-endian). Currently this library saves all images as unsigned int, but with given bit-depth (8, 16, 32 or 64). Also this library explicitly writes a resolution of 1 in both directions.

Internally this library works like this: TinyTIFFWriter_open() will basically only initialize the internal datastructures and write the TIFF header. It also determines the byte order used by the system and sets the TIFF header acordingly. As the image size is known, the size of every image in the file can be predetermined (we assume a maximum number of TIFF directory entries). The size will be:

      MAX_HEADER_ENTRIES*12 + SOME_FREE_SPACE + WIDTH*HEIGHT*SAMPLES(BITS_PER_SAMPLES/8)
      ---------------------------------------   ----------------------------------------
          directory/image description data                 image data

The free space, indicated as SOME_FREE_SPACE is used to store contents of extended fields, like RATIONAL or ARRAY fields. Every image in the file will have this size and unused bytes are set to 0x00. TinyTIFFWriter_writeImage() then works like this: The image description data is first assembled in memory, then the complete image description data and the complete image data is written to the file all together. This reduces the number of file access operations and writes the data in two reltively large chunks which allows the operating system to properly optimize file access. Finally this method will save the position of the NEXT_IFD_OFFSET field in the image header. The NEXT_IFD_OFFSET field is filled with the adress of the next potential image. Finally the method TinyTIFFWriter_close() will write 0x00000000 into the NEXT_IFD_OFFSET of the last image (as saved above) which ends the list of images in the file. This ansatz for writing TIFF files is only about a factor of 2 slower than directly writing binary data into a file. In addition the time needed to write an image stays equal also when writing many images, which is NOT the case for libtiff.

TinyTIFFWriter: Benchmarks

The library was developed due to a problem with libTIFF, when a lot (>1000) frames are written into a TIFF-file. LibTIFF does not need constant time per frame (i.e. the time to write a multi-frame TIFF grows linearly with the number of frames), but the time to write a frame increases with the number of frames. The following performance measurement shows this. It was acquired using tinytiffwriter_speedtest from this repository and shows the average time required to write one frame (64x64x pixels, 16-bit integer) out of a number (10, 100, 1000, ...) of frames. It compares the performance of libTIFF, TinyTIFFWriter and simply writing the dtaa using fwrite() ("RAW"). It was acquired on an Ryzen 5 3600+, Win10, 32-bit Release-build, writing onto a Harddisk (not a SSD)

For a microscope developed during my PhD thesis, it was necessary to write 100000 frames and more with acceptable duration. Therefore libTIFF was unusable and TinyTIFFWriter was developed. As can be seen in the graph above. The performance of TinyTIFFWriter and fwrite()/RAW is comparable, whereas the performance of LibTIFF falls off towards large files.

The following image shows another performance measurement, this time for different frame sizes (64x64-4096x4096, acquired on an Ryzen 5 3600+, Win10, 32-bit Release-build, writing onto a Harddisk (not a SSD)):

This suggests that the performance of TinyTIFFWriter and fwrite() are comparable for all image sizes. For larger images, also the performance of libTIFF is in the same range, whereas for small images, libTIFF falls off somewhat.

TinyTIFFReader

TinyTIFFReader: Usage

The following example code reads all frames in a TIFF:

TinyTIFFReaderFile* tiffr=NULL;
tiffr=TinyTIFFReader_open(filename);
if (!tiffr) {
std::cout<<" ERROR reading (not existent, not accessible or no TIFF file)\n";
} else {
std::cout<<" ERROR:"<<TinyTIFFReader_getLastError(tiffr)<<"\n";
} else {
std::cout<<" ImageDescription:\n"<< TinyTIFFReader_getImageDescription(tiffr) <<"\n";
std::cout<<" frames: "<<frames<<"\n";
uint32_t frame=0;
std::cout<<" ERROR:"<<TinyTIFFReader_getLastError(tiffr)<<"\n";
} else {
do {
const uint32_t width=TinyTIFFReader_getWidth(tiffr);
const uint32_t height=TinyTIFFReader_getHeight(tiffr);
bool ok=true;
std::cout<<" size of frame "<<frame<<": "<<width<<"x"<<height<<"\n";
std::cout<<" each pixel has "<<samples<<" samples with "<<bitspersample<<" bits each"\n";
if (ok) {
frame++;
// allocate memory for 1 sample from the image
uint8_t* image=(uint8_t*)calloc(width*height, bitspersample/8);
for (uint16_t sample=0; sample<samples; sample++) {
// read the sample
TinyTIFFReader_getSampleData(tiffr, image, sample);
if (TinyTIFFReader_wasError(tiffr)) { ok=false; std::cout<<" ERROR:"<<TinyTIFFReader_getLastError(tiffr)<<"\n"; break; }
// HERE WE CAN DO SOMETHING WITH THE SAMPLE FROM THE IMAGE
// IN image (ROW-MAJOR!)
// Note: That you may have to typecast the array image to the
// datatype used in the TIFF-file. You can get the size of each
// sample in bits by calling TinyTIFFReader_getBitsPerSample() and
// the datatype by calling TinyTIFFReader_getSampleFormat().
}
free(image);
}
} while (TinyTIFFReader_readNext(tiffr)); // iterate over all frames
std::cout<<" read "<<frame<<" frames\n";
}
}
}
TinyTIFFReader_close(tiffr);

This simplified example reads the first sample from the first frame in a TIFF file:

TinyTIFFReaderFile* tiffr=NULL;
tiffr=TinyTIFFReader_open(filename);
if (!tiffr) {
std::cout<<" ERROR reading (not existent, not accessible or no TIFF file)\n";
} else {
const uint32_t width=TinyTIFFReader_getWidth(tiffr);
const uint32_t height=TinyTIFFReader_getHeight(tiffr);
uint8_t* image=(uint8_t*)calloc(width*height, bitspersample/8);
TinyTIFFReader_getSampleData(tiffr, image, 0);
// HERE WE CAN DO SOMETHING WITH THE SAMPLE FROM THE IMAGE
// IN image (ROW-MAJOR!)
// Note: That you may have to typecast the array image to the
// datatype used in the TIFF-file. You can get the size of each
// sample in bits by calling TinyTIFFReader_getBitsPerSample() and
// the datatype by calling TinyTIFFReader_getSampleFormat().
free(image);
}
TinyTIFFReader_getWidth
uint32_t TinyTIFFReader_getWidth(TinyTIFFReaderFile *tiff)
return the width of the current frame
Definition: tinytiffreader.c:944
TinyTIFFWriterFile::width
uint32_t width
width of the frames
Definition: tinytiffwriter.c:111
TinyTIFFReaderFile
Definition: tinytiffreader.c:139
TinyTIFFReader_open
TinyTIFFReaderFile * TinyTIFFReader_open(const char *filename)
open TIFF file for reading
Definition: tinytiffreader.c:865
TinyTIFFReader_countFrames
uint32_t TinyTIFFReader_countFrames(TinyTIFFReaderFile *tiff)
return the width of the current frame
Definition: tinytiffreader.c:988
TinyTIFFReader_getSampleData
int TinyTIFFReader_getSampleData(TinyTIFFReaderFile *tiff, void *buffer, uint16_t sample)
read the given sample from the current frame into the given buffer, the byteorder is transformed to t...
Definition: tinytiffreader.c:637
TinyTIFFWriter_RGB
@ TinyTIFFWriter_RGB
Definition: tinytiffwriter.h:105
TinyTIFFWriter_close
void TinyTIFFWriter_close(TinyTIFFWriterFile *tiff)
close a given TIFF file
Definition: tinytiffwriter.c:1077
TinyTIFFReader_getHeight
uint32_t TinyTIFFReader_getHeight(TinyTIFFReaderFile *tiff)
return the height of the current frame
Definition: tinytiffreader.c:951
TinyTIFFWriterFile::height
uint32_t height
height of the frames
Definition: tinytiffwriter.c:113
TinyTIFF_Interleaved
@ TinyTIFF_Interleaved
Definition: tinytiff_defs.h:74
TinyTIFFWriterFile::samples
uint16_t samples
number of samples of the frames
Definition: tinytiffwriter.c:119
TinyTIFFWriterFile::frames
uint64_t frames
counter for the frames, written into the file
Definition: tinytiffwriter.c:123
TinyTIFFWriterFile
this struct represents a TIFF file
Definition: tinytiffwriter.c:89
TinyTIFFReader_close
void TinyTIFFReader_close(TinyTIFFReaderFile *tiff)
close a given TIFF file
Definition: tinytiffreader.c:918
TinyTIFFReader_getBitsPerSample
uint16_t TinyTIFFReader_getBitsPerSample(TinyTIFFReaderFile *tiff, int sample)
return the bits per sample of the current frame
Definition: tinytiffreader.c:973
TinyTIFFReader_wasError
int TinyTIFFReader_wasError(TinyTIFFReaderFile *tiff)
returns TINYTIFF_TRUE (non-zero) when there was an error in the last function call,...
Definition: tinytiffreader.c:324
TinyTIFFWriter_writeImageMultiSample
int TinyTIFFWriter_writeImageMultiSample(TinyTIFFWriterFile *tiff, const void *data, enum TinyTIFFSampleLayout inputOrganisation, enum TinyTIFFSampleLayout outputOrganization)
write a new image to the give TIFF file. the image ist stored in separate planes or planar configurat...
Definition: tinytiffwriter.c:945
TinyTIFFReader_getSamplesPerPixel
uint16_t TinyTIFFReader_getSamplesPerPixel(TinyTIFFReaderFile *tiff)
return the samples per pixel of the current frame
Definition: tinytiffreader.c:980
TinyTIFFReader_getImageDescription
const char * TinyTIFFReader_getImageDescription(TinyTIFFReaderFile *tiff)
return the image description of the current frame
Definition: tinytiffreader.c:958
TinyTIFFReader_getLastError
const char * TinyTIFFReader_getLastError(TinyTIFFReaderFile *tiff)
returns a pointer to the last error message
Definition: tinytiffreader.c:319
TinyTIFFWriterFile::bitspersample
uint16_t bitspersample
bits per sample of the frames
Definition: tinytiffwriter.c:115
TinyTIFFWriter_open
TinyTIFFWriterFile * TinyTIFFWriter_open(const char *filename, uint16_t bitsPerSample, enum TinyTIFFWriterSampleFormat sampleFormat, uint16_t samples, uint32_t width, uint32_t height, enum TinyTIFFWriterSampleInterpretation sampleInterpretation)
create a new TIFF file
Definition: tinytiffwriter.c:687
TinyTIFFWriter_writeImage
int TinyTIFFWriter_writeImage(TinyTIFFWriterFile *tiff, const void *data)
Write a new image to the give TIFF file, in chunky configuration, expects the data to be chunky too....
Definition: tinytiffwriter.c:1072