2012.11.24 00:09 "[Tiff] help writing thumbnails to TIFF file?", by Paul Heckbert

2012.12.13 02:57 "[Tiff] IFD offset tags written with wrong DataType in libtiff 4.0.3", by Paul Heckbert

I have a test program that uses libtiff to create an EXIF IFD, and I found that with libtiff version 4.0.3, the TIFFTAG_EXIFIFD tag was being written with TIFFDataType TIFF_IFD into Classic TIFF files (TIFF files that are not BigTIFF). Unfortunately, TIFF_IFD=13 is a new type and is not yet standard. Much existing software expects the EXIFIFD tag to have type TIFF_LONG=4 and will not read the EXIF tags if the type is written as TIFF_IFD. This means that writing of EXIF tags is not working as broadly as possible with libtiff version 4.0.3.

The attached program demonstrates the problem. If I use this program, wexif, and then try to read the generated TIFF file into Apple iPhoto 7.1.5, although the TIFF file has an EXIF IFD, the entire IFD is apparently ignored by iPhoto because the EXIFIFD tag has this unexpected value and so iPhoto doesn't read the DateTimeOriginal, ExposureTime, FNumber, etc from the file. Other software, e.g. exiftool 9.03 or Photoshop CS 5, is more lenient or more up-to-date, and it accepts either type.

The same is probably true of tags GPSIFD and INTEROPERABILITYIFD.

For documentation on the EXIF specification as of 2010 (version 2.3), see page 26 of http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf

I was able to get the program below to write "portable" TIFF files after making the following change to tiff-4.0.3/libtiff/tif_dirwrite.c

@@ -1688,7 +1688,10 @@ TIFFWriteDirectoryTagIfdIfd8Array(TIFF* tif, uint32* ndir, TIFFDirEntry* dir,

-    o=TIFFWriteDirectoryTagCheckedIfdArray(tif,ndir,dir,tag,count,p);
+    /* Write 4 byte offsets with TIFFDataType of TIFF_LONG, not  

TIFF_IFD.

+     * Note that EXIF specification version 2.3 says that EXIFIFD,  

GPSIFD, and INTEROPERABILITYIFD

+     * have type LONG, not type IFD. */
+    o=TIFFWriteDirectoryTagCheckedLongArray(tif,ndir,dir,tag,count,p);

The above may not be the best way to fix it. Perhaps it's also possible by changing the EXIFIFD line in tif_dirinfo.c?

Below is a C++ test program that's pretty good about exercising a number of things:

//-------------------------------------------------------- wexif.cpp

// write a test picture to a TIFF file using libtiff, setting some optional EXIF tags

#include <assert.h>
#include <string>

#include <tiffio.h>

typedef unsigned char uint8;

// write a picture that's a horizontal ramp in red, vertical ramp in green, and specified blue

static void write_image(TIFF *tif, int nx, int ny, int blue) {

  uint8 *buf = (uint8 *)_TIFFmalloc(nx*3);

assert(buf);

  for (int y=0; y<ny; y++) {

  uint8 *p = buf;

    for (int x=0; x<nx; x++) {
      *p++ = x*255/(nx-1);  // r
      *p++ = y*255/(ny-1);  // g

       *p++ = blue;          // b

  }
  assert(TIFFWriteScanline(tif, buf, y, 0) != -1);
}
_TIFFfree(buf);

}

static void print_dir(TIFF *tif, const std::string &label) {
   printf("\n%s\n", label.c_str());
   TIFFPrintDirectory(tif, stdout, 0);

}

static void set_tags(TIFF *tif, int nx, int ny, int compression) {

  assert(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, nx) != 0);
  assert(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, ny) != 0);
  assert(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8) != 0);
  assert(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) != 0);
  assert(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, ny/2) != 0); // two big
strips

   assert(TIFFSetField(tif, TIFFTAG_COMPRESSION, compression) != 0);
   if (compression == COMPRESSION_LZW)

  assert(TIFFSetField(tif, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL) != 0);

   assert(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)!
= 0);

  assert(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) != 0);
  assert(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3) != 0);

}

int main() {

   int px = 640, py = 480;  // primary image size
   int tx = 160, ty = 120;  // thumbnail image size

   char *filename = "e.tif";

TIFF *tif = TIFFOpen(filename, "w");
assert(tif);

   //--------------------------------------------- IFD0
   set_tags(tif, px, py, COMPRESSION_LZW);

   assert(TIFFSetField(tif, TIFFTAG_XRESOLUTION, 250.) != 0);
   assert(TIFFSetField(tif, TIFFTAG_YRESOLUTION, 250.) != 0);
   assert(TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH) != 0);
   assert(TIFFSetField(tif, TIFFTAG_MAKE, "Canon") != 0);
   assert(TIFFSetField(tif, TIFFTAG_MODEL, "Cybershot 11") != 0);

   assert(TIFFSetField(tif, TIFFTAG_SOFTWARE, "GigaPan Stitch
2.0.0000") != 0);
   assert(TIFFSetField(tif, TIFFTAG_DATETIME, "2011:08:01 18:28:48")!
= 0);

   uint64 exif_dir_offset = 0; // temporary value
   // set EXIFIFD to temporary value so directory will be full size
when we checkpoint it
   assert(TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_dir_offset) != 0);

   // write out IFD0 before we switch to EXIF IFD, so SetDirectory
after WriteCustomDir will work

  assert(TIFFCheckpointDirectory(tif) != 0);

   assert(TIFFSetDirectory(tif, 0) != 0);

   //--------------------------------------------- EXIF IFD
   print_dir(tif, "after Checkpoint,SetDirectory");
   assert(TIFFCreateEXIFDirectory(tif) == 0);

   // DateTimeOriginal tag belongs in EXIF IFD, not IFD0

  assert(TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, "2011:08:01 18:28:47")
!= 0);

   assert(TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, "2011:08:01
18:28:46") != 0);

   assert(TIFFSetField(tif, EXIFTAG_EXPOSURETIME, .25) != 0);
   assert(TIFFSetField(tif, EXIFTAG_FNUMBER, 11.) != 0);
   short iso[] = {200};
   assert(TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, iso) !=
0); // pass array length & array ptr
   assert(TIFFSetField(tif, EXIFTAG_EXPOSUREBIASVALUE, -1.) != 0);
   assert(TIFFSetField(tif, EXIFTAG_FLASH, 0) != 0);
   assert(TIFFSetField(tif, EXIFTAG_FOCALLENGTH, 100.) != 0);

   assert(TIFFWriteCustomDirectory(tif, &exif_dir_offset) != 0); //
sets exif_dir_offset vbl
   printf("exif_dir_offset=0x%llx\n", exif_dir_offset);
   print_dir(tif, "after WriteCustomDirectory");

   //--------------------------------------------- patch IFD0
   assert(TIFFSetDirectory(tif, 0) != 0);
   // TODO: the following line causes new directory to be created and
old to be orphaned!
   assert(TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_dir_offset) != 0);

   // write IFD0 (second preliminary version) and create an empty IFD1.
   // Checkpoint causes IFD to be written at beg. of file.
   // otherwise it would be written at end of file (after the pixels).

  assert(TIFFCheckpointDirectory(tif) != 0);

   assert(TIFFWriteDirectory(tif) != 0);

   bool thumbnail = true;
   if (thumbnail) {
     //--------------------------------------------- IFD1
     set_tags(tif, tx, ty, COMPRESSION_NONE);
     // write IFD1 (preliminary version) and create an empty IFD2

  assert(TIFFCheckpointDirectory(tif) != 0);

     assert(TIFFWriteDirectory(tif) != 0);
     // set current dir to IFD1
     assert(TIFFSetDirectory(tif, 1) != 0);
     // write pixels of thumbnail (has blue in upper left)
     write_image(tif, tx, ty, 255);
     // rewrite IFD1
     assert(TIFFWriteDirectory(tif) != 0);
   }

   // set current dir to IFD0
   assert(TIFFSetDirectory(tif, 0) != 0);

   //--------------------------------------------- add pixels to IFD0
   // write pixels of primary image (has black in upper left)
   write_image(tif, px, py, 0);

 TIFFClose(tif);

}

a Makefile that works on Mac OS

DEP = ../../../dependencies
JPEG = $(DEP)/jpeg-6bx102b/build
TIFF = $(DEP)/tiff-4.0.3/build
ZLIB = $(DEP)/zlib-1.2.3/build
COPTS = -g -Wall -Werror

JTZLIBS = $(JPEG)/lib/libjpeg.dylib \
   $(TIFF)/lib/libtiff.a \
   $(ZLIB)/lib/libz.a

wexif: wexif.o $(TIFF)/lib/libtiff.a
        g++ $(COPTS) -o $@ $^ $(JTZLIBS)

.cpp.o:
        g++ -c $(COPTS) -I$(TIFF)/include $<