Bug 2417 - TIFFCheckpointDirectory not working when writing two IFDs
: TIFFCheckpointDirectory not working when writing two IFDs
Status: RESOLVED LATER
: libtiff
default
: unspecified
: Macintosh MacOS X
: P2 major
: ---
Assigned To:
:
:
: migrated_to_gitlab
:
:
  Show dependency treegraph
 
Reported: 2012-11-16 00:41 by
Modified: 2019-10-01 14:19 (History)


Attachments


Note

You need to log in before you can comment on or make changes to this bug.


Description From 2012-11-16 00:41:11
I am trying to use libtiff to create a TIFF file that contains two IFDs, one
for the primary image and one for a small thumbnail image.  I'd like the order
of data in the TIFF file to be IFD0, IFD1, thumbnail image, primary image --
that would be most consistent with other image creation software, e.g.
Photoshop.  Surely this should be possible with libtiff.

The attached C code is a pretty minimal demonstration of the problem.  If run
with no arguments, it results in two warnings
_TIFFVGetField: thumb.tif: Invalid tag "Predictor" (not supported by codec).
_TIFFVGetField: thumb.tif: Invalid tag "Predictor" (not supported by codec).
and then crashes with
thumb.c:68: failed assertion `TIFFSetDirectory(tif, 1)'
Abort trap

and if I examine the partially-written output file with exiftool, it shows that
IFD1 was written directly on top of IFD0 - this is not what I wanted and
probably explains the Predictor messages.

The man page for TIFFCheckpointDirectory is sketchy, as is much of libtiff's
documentation.  There is no discussion of how TIFFWriteDirectory or
TIFFCheckpointDirectory increment the current directory index, although
examination of the source code makes that clear, making it a challenge for
anyone trying to write multiple IFDs with any control over the order of data in
the file.  And I don't find any example uses of TIFFCheckpointDirectory in the
tools/ or contrib/ dirs, so consequently I am only able to guess that I'm using
TIFFCheckpointDirectory and TIFFSetDirectory properly.

Either there's a bug in TIFFCheckpointDirectory or I am using that function
incorrectly.  Which is it?

I'm on Mac OS 10.5.8 and linking with libtiff 4.0.3.

thumb.c:
---
/* Write a test picture to a TIFF file, with thumbnail and EXIF, using libtiff.
 *
 * Run "thumb -nocheckpoint" to write data in the order:
 *   primary image, IFD0, thumbnail image, IFD1.
 *   and most software (e.g. Mac Finder, exiftool) does not find IFD1, it
seems.
 * With no args, this program uses TIFFCheckpointDirectory to write in the
order I'd prefer:
 *   IFD0, IFD1, thumbnail image, primary image.
 *   But running it this way causes IFD1 to overwrite IFD0
 *   and gives an assert on the first call to TIFFSetDirectory.
 *
 * Paul Heckbert, 2012/11/15
 */

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

static void write_image(TIFF *tif, int nx, int ny) {
  uint8 *buf = _TIFFmalloc(nx*3);
  int x, y;

  assert(buf);
  /* picture has horizontal ramp in red, vertical ramp in green */
  for (y=0; y<ny; y++) {
    uint8 *p = buf;
    for (x=0; x<nx; x++) {
      *p++ = x*255/(nx-1);  /* r */
      *p++ = y*255/(ny-1);  /* g */
      *p++ = 0;             /* b */
    }
    assert(TIFFWriteScanline(tif, buf, y, 0) != -1);
  }
  _TIFFfree(buf);
}

static void set_tags(TIFF *tif, int nx, int ny, int compression) {
  assert(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, nx));
  assert(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, ny));
  assert(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8));
  assert(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB));
  assert(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, ny));  /* one strip */
  printf("set_tags %d %d %d\n", nx, ny, compression);
  assert(TIFFSetField(tif, TIFFTAG_COMPRESSION, compression));
  if (compression == COMPRESSION_LZW)
    assert(TIFFSetField(tif, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL));
  assert(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT));
  assert(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG));
  assert(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3));
}

int main(int argc, char **argv) {
  int px = 640, py = 480;  /* primary image size */
  int tx = 160, ty = 120;  /* thumbnail image size */
  char *filename = "thumb.tif";
  int checkpoint = argc!=2 || strcmp(argv[1], "-nocheckpoint");

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

  if (checkpoint) {
    set_tags(tif, px, py, COMPRESSION_LZW);
    /* write IFD0 (preliminary version) and create an empty IFD1 */
    assert(TIFFCheckpointDirectory(tif));
    set_tags(tif, tx, ty, COMPRESSION_NONE);
    /* write IFD1 (preliminary version) and create an empty IFD2 */
    assert(TIFFCheckpointDirectory(tif));
    /* set current dir to IFD1 */
    assert(TIFFSetDirectory(tif, 1));
    /* write pixels of thumbnail */
    write_image(tif, tx, ty);
    /* write IFD1 */
    assert(TIFFWriteDirectory(tif));
    /* set current dir to IFD0 */
    assert(TIFFSetDirectory(tif, 0));
    /* write pixels of primary image */
    write_image(tif, px, py);
  } else {
    set_tags(tif, px, py, COMPRESSION_LZW);
    /* write pixels of primary image */
    write_image(tif, px, py);
    /* write IFD0 */
    assert(TIFFWriteDirectory(tif));
    set_tags(tif, tx, ty, COMPRESSION_NONE);
    /* write pixels of thumbnail */
    write_image(tif, tx, ty);
  }

  /* causes TIFFWriteDirectory to be called */
  TIFFClose(tif);
  return 0;
}
---
Makefile
---
DEP = ../../../dependencies
TIFF = $(DEP)/tiff-4.0.3/build
COPTS = -g -Wall -Werror

TIFFDYLIB = $(TIFF)/lib/libtiff.dylib

thumb: thumb.o $(TIFFDYLIB)
  gcc $(COPTS) -o $@ $^ $(TIFFDYLIB)

.c.o:
  gcc -c $(COPTS) -I$(TIFF)/include $<
---
------- Comment #1 From 2012-11-19 13:44:26 -------
I see that bug 2135, comment 5 also finds problems with
TIFFCheckpointDirectory, but that code is not trying to create two IFDs, so
it's not quite the same situation.
------- Comment #2 From 2019-10-01 14:19:38 -------
Bugzilla is no longer used for tracking libtiff issues. Remaining open tickets,
such as this one, have been migrated to the libtiff GitLab instance at
https://gitlab.com/libtiff/libtiff/issues .

The migrated tickets have their summary prefixed with [BZ#XXXX] where XXXX is
the initial Bugzilla issue number.