TinyTIFF
a lightweight C/C++ library for reading and writing TIFF files
Loading...
Searching...
No Matches
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

The library is built with CMake and supports both find_package(TinyTIFF) and CMake's FetchContent to include it into other projects. See https://jkriege2.github.io/TinyTIFF/page_useinstructions.html for details

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();
}
}
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:1042
void TinyTIFFWriter_close(TinyTIFFWriterFile *tiff)
close a given TIFF file
Definition tinytiffwriter.c:1047
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:704
this struct represents a TIFF file
Definition tinytiffwriter.c:103

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();
}
}
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:895
@ TinyTIFFWriter_RGB
Definition tinytiffwriter.h:102
@ TinyTIFF_Interleaved
Definition tinytiff_defs.h:74

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"):

Ryzen 5 3600+, Win10, 32-bit Release-build, writing onto a Harddisk, libTiff 3.8.2 Ryzen 7 5800H, Win11, 64-bit Release-build, writing onto an SSD, libTiff 4.6.0

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 on harddisks. On SSDs the performance of libTIFF does not show an increase with number of images, but is still significantly (1.5-3x, note the logarithmic y-axis) faster than libTIFF.

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)):

Ryzen 5 3600+, Win10, 32-bit Release-build, writing onto a Harddisk, libTiff 3.8.2 Ryzen 7 5800H, Win11, 64-bit Release-build, writing onto an SSD, libTiff 4.6.0

This suggests that for harddisks 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. For SSDs, the libraries are closer together, but still TinyTIFFWriter is faster than libTIFF by a factor of 1.5-5x (again note the logarithmic scale on the y-axis!).

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";
uint32_t frames=TinyTIFFReader_countFrames(tiffr);
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);
const uint16_t samples=TinyTIFFReader_getSamplesPerPixel(tiff);
const uint16_t bitspersample=TinyTIFFReader_getBitsPerSample(tiff, 0);
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);
uint16_t TinyTIFFReader_getBitsPerSample(TinyTIFFReaderFile *tiff, int sample)
return the bits per sample of the current frame
Definition tinytiffreader.c:1062
TinyTIFFReaderFile * TinyTIFFReader_open(const char *filename)
open TIFF file for reading
Definition tinytiffreader.c:945
uint16_t TinyTIFFReader_getSamplesPerPixel(TinyTIFFReaderFile *tiff)
return the samples per pixel of the current frame
Definition tinytiffreader.c:1069
int TinyTIFFReader_wasError(TinyTIFFReaderFile *tiff)
returns TINYTIFF_TRUE (non-zero) when there was an error in the last function call,...
Definition tinytiffreader.c:332
uint32_t TinyTIFFReader_getWidth(TinyTIFFReaderFile *tiff)
return the width of the current frame
Definition tinytiffreader.c:1033
uint32_t TinyTIFFReader_countFrames(TinyTIFFReaderFile *tiff)
return the width of the current frame
Definition tinytiffreader.c:1078
const char * TinyTIFFReader_getLastError(TinyTIFFReaderFile *tiff)
returns a pointer to the last error message
Definition tinytiffreader.c:327
const char * TinyTIFFReader_getImageDescription(TinyTIFFReaderFile *tiff)
return the image description of the current frame
Definition tinytiffreader.c:1047
uint32_t TinyTIFFReader_getHeight(TinyTIFFReaderFile *tiff)
return the height of the current frame
Definition tinytiffreader.c:1040
Definition tinytiffreader.c:154

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);
const uint16_t bitspersample=TinyTIFFReader_getBitsPerSample(tiff, 0);
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);
}
void TinyTIFFReader_close(TinyTIFFReaderFile *tiff)
close a given TIFF file
Definition tinytiffreader.c:1006
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:924