c++ctifflibtiff

Adding custom tags to a TIFF file


I’m quite new to libtiff, but I’ve managed to get tiff files saving and opening without much trouble.

Now, I’m being ambitious and trying to add custom tags into my files. I’ve read the documentation (see here) and written some test code which compiles without errors, but fails at runtime with an access violation at the first call to TIFFSetField that features a custom field (calls to TIFFSetField with standard fields are fine).

My test code follows: just under 100 lines, with the only external dependency (besides libtiff) being my code to open a test image from a .pgm file. Can anyone point out what I’m doing wrong? By the way, I'm using libtiff 4.0.3.

#include "stdafx.h"
#include "PGM.h"      // Just for reading in the test image
#include "tiffio.h"

// There are a number of TIFF-related definitions we need to make in order to support the custom tags
// that we want to include in our files. The form of these definitions and subroutines comes straight
// out of the libtiff documentation, and the values of the custom tags themselves come from the 
// range (65000-75535) defined in the TIFF specification as "reusable" and suitable for private use
// within organisations. See http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
#define N(a) (sizeof(a) / sizeof (a[0]))
#define TIFFTAG_INCIDENTPOWER     65000
#define TIFFTAG_REFLECTEDPOWER    65001
#define TIFFTAG_T1                65002
#define TIFFTAG_T2                65003
#define TIFFTAG_HUMIDITY          65004
#define TIFFTAG_EXPOSURE          65005

static const TIFFFieldInfo xtiffFieldInfo[] = {
    { TIFFTAG_INCIDENTPOWER,  -1, -1, TIFF_LONG,  FIELD_CUSTOM, 0, 1, const_cast<char*>("LaserIncidentPower") },
    { TIFFTAG_REFLECTEDPOWER, -1, -1, TIFF_LONG,  FIELD_CUSTOM, 0, 1, const_cast<char*>("LaserReflectedPower") },
    { TIFFTAG_T1,             -1, -1, TIFF_FLOAT, FIELD_CUSTOM, 0, 1, const_cast<char*>("Temperature_1") },
    { TIFFTAG_T2,             -1, -1, TIFF_FLOAT, FIELD_CUSTOM, 0, 1, const_cast<char*>("Temperature_2") },
    { TIFFTAG_HUMIDITY,       -1, -1, TIFF_FLOAT, FIELD_CUSTOM, 0, 1, const_cast<char*>("Humidity") },
    { TIFFTAG_EXPOSURE,       -1, -1, TIFF_FLOAT, FIELD_CUSTOM, 0, 1, const_cast<char*>("ExposureTime(ms)") }
};                                                                        // The casts are necessary because the
                                                                      // string literals are inherently const,
                                                                      // but the definition of TIFFFieldInfo
                                                                      // requires a non-const string pointer.
                                                                      // The Intel and Microsoft compilers
                                                                      // tolerate this, but gcc doesn't.

static void registerCustomTIFFTags(TIFF *tif)
{
    /* Install the extended Tag field info */
    TIFFMergeFieldInfo(tif, xtiffFieldInfo, N(xtiffFieldInfo));
}

void saveAsTiff(int nx, int ny, unsigned short *image, const char* filename,
            int Power1, int Power2, float T1, float T2, float Humidity, float Exposure)
{
    // Create the TIFF directory object:
    TIFF* tif = TIFFOpen(filename, "w");

    // Set the tags: first the standard ones...
    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, nx);
    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, ny);
    TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 16);
    TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
    TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 6); // Takes maximum advantage of Intel ZLIB enhancements
    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
    TIFFSetField(tif, TIFFTAG_ORIENTATION, static_cast<int>(ORIENTATION_TOPLEFT));
    TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, ny);
    // Saving the whole image in a single block makes heavy
    // demands on memory but should enable optimum compression
    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);

    // ... and now our own custom ones; if we comment out this next block then everything
    // works as it should and we successfully save the image:
    registerCustomTIFFTags(tif);
    TIFFSetField(tif, TIFFTAG_INCIDENTPOWER, Power1);   // Access violation here
    TIFFSetField(tif, TIFFTAG_REFLECTEDPOWER, Power2);
    TIFFSetField(tif, TIFFTAG_T1, T1);
    TIFFSetField(tif, TIFFTAG_T2, T2);
    TIFFSetField(tif, TIFFTAG_HUMIDITY, Humidity);
    TIFFSetField(tif, TIFFTAG_EXPOSURE, Exposure);

    TIFFWriteEncodedStrip(tif, 0, static_cast<void *>(image), nx * ny * 2); 
                                                              // The '* 2' is present because each pixel
                                                              // has 16 bits, hence two bytes
    // Write the directory to a file, and close it:
    TIFFWriteDirectory(tif);
    TIFFClose(tif);
}

int _tmain(int argc, _TCHAR* argv[])
{
    // Variables to hold the image
    int nx, ny;
    unsigned short *image = nullptr;

    // Information we'd like to put into our custom tags:
    int Power1 = 1000000;
    int Power2 = 8;
    float T1 = 23.5f;
    float T2 = -18.7f;
    float Humidity = 0.98f;
    float Exposure = 0.001f;

    // Read the test image in from a PGM file:
    readFromPGM(&nx, &ny, &image, "TestImage.pgm");

    // Save the image as a TIF file:
    saveAsTiff(nx, ny, image, "TestImage.tif", Power1, Power2, T1, T2, Humidity, Exposure);
    return 0;
}

Solution

  • Following some guidance from a kind gentleman called Paul Heckbert on the TIFF users' mailing list, here is the code that finally worked for me. There were two keys to it:

    (1) Defining the TIFFFieldInfo fields correctly, in a way which was different from the example given in the documentation. (2) Using the "extender" pattern for encapsulating the call to TIFFMergeFieldInfo, but doing this before opening the file. If I left the call until after opening the file, then when reading a file with custom tags a number of "Warning, unknown field" messages would be issued immediately on opening the file.

    Here is the code:

    #include "stdafx.h"
    #include "PGM.h"
    #include "tiffio.h"
    
    // There are a number of TIFF-related definitions we need to make in order to support the custom tags
    // that we want to include in our files. The form of these definitions and subroutines comes straight
    // out of the libtiff documentation, and the values of the custom tags themselves come from the 
    // range (65000-75535) defined in the TIFF specification as "reusable" and suitable for private use
    // within organisations. See http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
    #define N(a) (sizeof(a) / sizeof (a[0]))
    #define TIFFTAG_INCIDENTPOWER     65000
    #define TIFFTAG_REFLECTEDPOWER    65001
    #define TIFFTAG_T1                65002
    #define TIFFTAG_T2                65003
    #define TIFFTAG_HUMIDITY          65004
    #define TIFFTAG_EXPOSURE          65005
    
    static const TIFFFieldInfo xtiffFieldInfo[] = {
            { TIFFTAG_INCIDENTPOWER,  1, 1, TIFF_LONG,  FIELD_CUSTOM, 0, 0, const_cast<char*>("LaserIncidentPower") },
            { TIFFTAG_REFLECTEDPOWER, 1, 1, TIFF_LONG,  FIELD_CUSTOM, 0, 0, const_cast<char*>("LaserReflectedPower") },
            { TIFFTAG_T1,             1, 1, TIFF_FLOAT, FIELD_CUSTOM, 0, 0, const_cast<char*>("Temperature_1") },
            { TIFFTAG_T2,             1, 1, TIFF_FLOAT, FIELD_CUSTOM, 0, 0, const_cast<char*>("Temperature_2") },
            { TIFFTAG_HUMIDITY,       1, 1, TIFF_FLOAT, FIELD_CUSTOM, 0, 0, const_cast<char*>("Humidity") },
            { TIFFTAG_EXPOSURE,       1, 1, TIFF_FLOAT, FIELD_CUSTOM, 0, 0, const_cast<char*>("ExposureTime(ms)") }
    };                                                                    // The casts are necessary because the
                                                                          // string literals are inherently const,
                                                                          // but the definition of TIFFFieldInfo
                                                                          // requires a non-const string pointer.
                                                                          // The Intel and Microsoft compilers
                                                                          // tolerate this, but gcc doesn't.
    
    static TIFFExtendProc parent_extender = NULL;  // In case we want a chain of extensions
    
    static void registerCustomTIFFTags(TIFF *tif)
    {
        /* Install the extended Tag field info */
        int error = TIFFMergeFieldInfo(tif, xtiffFieldInfo, N(xtiffFieldInfo));
    
        if (parent_extender)
            (*parent_extender)(tif);
    }
    
    static void augment_libtiff_with_custom_tags() {
        static bool first_time = true;
        if (!first_time) return;
        first_time = false;
        parent_extender = TIFFSetTagExtender(registerCustomTIFFTags);
    }
    
    void saveAsTiff(int nx, int ny, unsigned short *image, const char* filename,
                    int Power1, int Power2, float T1, float T2, float Humidity, float Exposure)
    {
        // Create the TIFF directory object:
        augment_libtiff_with_custom_tags();
        TIFF* tif = TIFFOpen(filename, "w");
    
        // Set the tags: first the standard ones...
        TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, nx);
        TIFFSetField(tif, TIFFTAG_IMAGELENGTH, ny);
        TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 16);
        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
        TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 6); // Takes maximum advantage of Intel ZLIB enhancements
        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
        TIFFSetField(tif, TIFFTAG_ORIENTATION, static_cast<int>(ORIENTATION_TOPLEFT));
        TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
        TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, ny);
        // Saving the whole image in a single block makes heavy
        // demands on memory but should enable optimum compression
        TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    
        // ... and now our own custom ones:
        TIFFSetField(tif, TIFFTAG_INCIDENTPOWER, Power1);
        TIFFSetField(tif, TIFFTAG_REFLECTEDPOWER, Power2);
        TIFFSetField(tif, TIFFTAG_T1, T1);
        TIFFSetField(tif, TIFFTAG_T2, T2);
        TIFFSetField(tif, TIFFTAG_HUMIDITY, Humidity);
        TIFFSetField(tif, TIFFTAG_EXPOSURE, Exposure);
    
        TIFFWriteEncodedStrip(tif, 0, static_cast<void *>(image), nx * ny * 2); 
                                                                  // The '* 2' is present because each pixel
                                                                  // has 16 bits, hence two bytes
        // Write the directory to a file, and close it:
        TIFFWriteDirectory(tif);
        TIFFClose(tif);
    }
    
    void readFromTiff(int *nx, int *ny, unsigned short **image, const char* filename,
                      int *Power1, int *Power2, float *T1, float *T2, float *Humidity, float *Exposure)
    {
        // Create the TIFF directory object:
        augment_libtiff_with_custom_tags();
        TIFF* tif = TIFFOpen(filename, "r");
        if (NULL == tif)
        {
            *nx = -1;
            *ny = -1;
            return;
        }
    
        // Read in the image size and metadata:
        TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, nx);
        TIFFGetField(tif, TIFFTAG_IMAGELENGTH, ny);
        TIFFGetField(tif, TIFFTAG_INCIDENTPOWER, Power1);
        TIFFGetField(tif, TIFFTAG_REFLECTEDPOWER, Power2);
        TIFFGetField(tif, TIFFTAG_T1, T1);
        TIFFGetField(tif, TIFFTAG_T2, T2);
        TIFFGetField(tif, TIFFTAG_HUMIDITY, Humidity);
        TIFFGetField(tif, TIFFTAG_EXPOSURE, Exposure);
    
        // Dimension the buffer, and read in the image data itself:
        *image = new unsigned short[(*nx) * (*ny)];
        TIFFReadEncodedStrip(tif, static_cast<tstrip_t>(0), static_cast<void *>(*image), static_cast<tsize_t>(-1));
        TIFFClose(tif);
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        // Variables to hold the image
        int nx, ny;
        unsigned short *image = nullptr;
    
        // Information we'd like to put into our custom tags:
        int Power1 = 1000000;
        int Power2 = 8;
        float T1 = 23.5f;
        float T2 = -18.7f;
        float Humidity = 0.98f;
        float Exposure = 0.001f;
    
        // Read the test image in from a PGM file:
        readFromPGM(&nx, &ny, &image, "TestImage.pgm");
    
        // Save the image as a TIF file:
        saveAsTiff(nx, ny, image, "TestImage.tif", Power1, Power2, T1, T2, Humidity, Exposure);
    
        // Prepare to read back in: first clear the various fields
        Power1 = 0;
        Power2 = 0;
        T1 = 0.0f;
        T2 = 0.0f;
        Humidity = 0.0f;
        Exposure = 0.0f;
        nx = 0;
        ny = 0;
        delete [] image;
    
        readFromTiff(&nx, &ny, &image, "TestImage.tif", &Power1, &Power2, &T1, &T2, &Humidity, &Exposure);
    
        printf("Image size = %d, %d\r\n", ny, ny);
        printf("Power1 = %d\r\n", Power1);
        printf("Power2 = %d\r\n", Power2);
        printf("T1 = %6.3f\r\n", T1);
        printf("T2 = %6.3f\r\n", T2);
        printf("Humidity = %6.3f\r\n", Humidity);
        printf("Exposure = %6.3f\r\n", Exposure);
        return 0;
    }