The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <tiffio.h>
#include <string.h>
#include "imtiff.h"
#include "imext.h"

/* needed to implement our substitute TIFFIsCODECConfigured */
#if TIFFLIB_VERSION < 20031121
static int TIFFIsCODECConfigured(uint16 scheme);
#endif

/*
=head1 NAME

tiff.c - implements reading and writing tiff files, uses io layer.

=head1 SYNOPSIS

   io_glue *ig = io_new_fd( fd );
   i_img *im   = i_readtiff_wiol(ig, -1); // no limit on how much is read
   // or 
   io_glue *ig = io_new_fd( fd );
   return_code = i_writetiff_wiol(im, ig); 

=head1 DESCRIPTION

tiff.c implements the basic functions to read and write tiff files.
It uses the iolayer and needs either a seekable source or an entire
memory mapped buffer.

=head1 FUNCTION REFERENCE

Some of these functions are internal.

=over

=cut
*/

#define byteswap_macro(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |     \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#define CLAMP8(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))
#define CLAMP16(x) ((x) < 0 ? 0 : (x) > 65535 ? 65535 : (x))

#define Sample16To8(num) ((num) / 257)

struct tag_name {
  char *name;
  uint32 tag;
};

static i_img *read_one_rgb_tiled(TIFF *tif, i_img_dim width, i_img_dim height, int allow_incomplete);
static i_img *read_one_rgb_lines(TIFF *tif, i_img_dim width, i_img_dim height, int allow_incomplete);

static struct tag_name text_tag_names[] =
{
  { "tiff_documentname", TIFFTAG_DOCUMENTNAME, },
  { "tiff_imagedescription", TIFFTAG_IMAGEDESCRIPTION, },
  { "tiff_make", TIFFTAG_MAKE, },
  { "tiff_model", TIFFTAG_MODEL, },
  { "tiff_pagename", TIFFTAG_PAGENAME, },
  { "tiff_software", TIFFTAG_SOFTWARE, },
  { "tiff_datetime", TIFFTAG_DATETIME, },
  { "tiff_artist", TIFFTAG_ARTIST, },
  { "tiff_hostcomputer", TIFFTAG_HOSTCOMPUTER, },
};

static struct tag_name 
compress_values[] =
  {
    { "none",     COMPRESSION_NONE },
    { "ccittrle", COMPRESSION_CCITTRLE },
    { "fax3",     COMPRESSION_CCITTFAX3 },
    { "t4",       COMPRESSION_CCITTFAX3 },
    { "fax4",     COMPRESSION_CCITTFAX4 },
    { "t6",       COMPRESSION_CCITTFAX4 },
    { "lzw",      COMPRESSION_LZW },
    { "jpeg",     COMPRESSION_JPEG },
    { "packbits", COMPRESSION_PACKBITS },
    { "deflate",  COMPRESSION_ADOBE_DEFLATE },
    { "zip",      COMPRESSION_ADOBE_DEFLATE },
    { "oldzip",   COMPRESSION_DEFLATE },
    { "ccittrlew", COMPRESSION_CCITTRLEW },
  };

static const int compress_value_count = 
  sizeof(compress_values) / sizeof(*compress_values);

static struct tag_name
sample_format_values[] =
  {
    { "uint",      SAMPLEFORMAT_UINT },
    { "int",       SAMPLEFORMAT_INT },
    { "ieeefp",    SAMPLEFORMAT_IEEEFP },
    { "undefined", SAMPLEFORMAT_VOID },
  };

static const int sample_format_value_count = 
  sizeof(sample_format_values) / sizeof(*sample_format_values);

static int 
myTIFFIsCODECConfigured(uint16 scheme);

typedef struct read_state_tag read_state_t;
/* the setup function creates the image object, allocates the line buffer */
typedef int (*read_setup_t)(read_state_t *state);

/* the putter writes the image data provided by the getter to the
   image, x, y, width, height describe the target area of the image,
   extras is the extra number of pixels stored for each scanline in
   the raster buffer, (for tiles against the right side of the
   image) */

typedef int (*read_putter_t)(read_state_t *state, i_img_dim x, i_img_dim y,
			     i_img_dim width, i_img_dim height, int extras);

/* reads from a tiled or strip image and calls the putter.
   This may need a second type for handling non-contiguous images
   at some point */
typedef int (*read_getter_t)(read_state_t *state, read_putter_t putter);

struct read_state_tag {
  TIFF *tif;
  i_img *img;
  void *raster;
  i_img_dim pixels_read;
  int allow_incomplete;
  void *line_buf;
  uint32 width, height;
  uint16 bits_per_sample;
  uint16 photometric;

  /* the total number of channels (samples per pixel) */
  int samples_per_pixel;

  /* if non-zero, which channel is the alpha channel, typically 3 for rgb */
  int alpha_chan;

  /* whether or not to scale the color channels based on the alpha
     channel.  TIFF has 2 types of alpha channel, if the alpha channel
     we use is EXTRASAMPLE_ASSOCALPHA then the color data will need to
     be scaled to match Imager's conventions */
  int scale_alpha;

  /* number of color samples (not including alpha) */
  int color_channels;

  /* SampleFormat is 2 */
  int sample_signed;

  int sample_format;
};

static int tile_contig_getter(read_state_t *state, read_putter_t putter);
static int strip_contig_getter(read_state_t *state, read_putter_t putter);

static int setup_paletted(read_state_t *state);
static int paletted_putter8(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);
static int paletted_putter4(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_16_rgb(read_state_t *state);
static int setup_16_grey(read_state_t *state);
static int putter_16(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_8_rgb(read_state_t *state);
static int setup_8_grey(read_state_t *state);
static int putter_8(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_32_rgb(read_state_t *state);
static int setup_32_grey(read_state_t *state);
static int putter_32(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_bilevel(read_state_t *state);
static int putter_bilevel(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_cmyk8(read_state_t *state);
static int putter_cmyk8(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);

static int setup_cmyk16(read_state_t *state);
static int putter_cmyk16(read_state_t *, i_img_dim, i_img_dim, i_img_dim, i_img_dim, int);
static void
rgb_channels(read_state_t *state, int *out_channels);
static void
grey_channels(read_state_t *state, int *out_channels);
static void
cmyk_channels(read_state_t *state, int *out_channels);
static void
fallback_rgb_channels(TIFF *tif, i_img_dim width, i_img_dim height, int *channels, int *alpha_chan);

static const int text_tag_count = 
  sizeof(text_tag_names) / sizeof(*text_tag_names);

#if TIFFLIB_VERSION >= 20051230
#define USE_EXT_WARN_HANDLER
#endif

#define TIFFIO_MAGIC 0xC6A340CC

static void error_handler(char const *module, char const *fmt, va_list ap) {
  mm_log((1, "tiff error fmt %s\n", fmt));
  i_push_errorvf(0, fmt, ap);
}

typedef struct {
  unsigned magic;
  io_glue *ig;
#ifdef USE_EXT_WARN_HANDLER
  char *warn_buffer;
  size_t warn_size;
#endif
} tiffio_context_t;

static void
tiffio_context_init(tiffio_context_t *c, io_glue *ig);
static void
tiffio_context_final(tiffio_context_t *c);

#define WARN_BUFFER_LIMIT 10000

#ifdef USE_EXT_WARN_HANDLER

static void
warn_handler_ex(thandle_t h, const char *module, const char *fmt, va_list ap) {
  tiffio_context_t *c = (tiffio_context_t *)h;
  char buf[200];

  if (c->magic != TIFFIO_MAGIC)
    return;

  buf[0] = '\0';
#ifdef IMAGER_VSNPRINTF
  vsnprintf(buf, sizeof(buf), fmt, ap);
#else
  vsprintf(buf, fmt, ap);
#endif
  mm_log((1, "tiff warning %s\n", buf));

  if (!c->warn_buffer || strlen(c->warn_buffer)+strlen(buf)+2 > c->warn_size) {
    size_t new_size = c->warn_size + strlen(buf) + 2;
    char *old_buffer = c->warn_buffer;
    if (new_size > WARN_BUFFER_LIMIT) {
      new_size = WARN_BUFFER_LIMIT;
    }
    c->warn_buffer = myrealloc(c->warn_buffer, new_size);
    if (!old_buffer) c->warn_buffer[0] = '\0';
    c->warn_size = new_size;
  }
  if (strlen(c->warn_buffer)+strlen(buf)+2 <= c->warn_size) {
    strcat(c->warn_buffer, buf);
    strcat(c->warn_buffer, "\n");
  }
}

#else

static char *warn_buffer = NULL;
static int warn_buffer_size = 0;

static void warn_handler(char const *module, char const *fmt, va_list ap) {
  char buf[1000];

  buf[0] = '\0';
#ifdef IMAGER_VSNPRINTF
  vsnprintf(buf, sizeof(buf), fmt, ap);
#else
  vsprintf(buf, fmt, ap);
#endif
  mm_log((1, "tiff warning %s\n", buf));

  if (!warn_buffer || strlen(warn_buffer)+strlen(buf)+2 > warn_buffer_size) {
    int new_size = warn_buffer_size + strlen(buf) + 2;
    char *old_buffer = warn_buffer;
    if (new_size > WARN_BUFFER_LIMIT) {
      new_size = WARN_BUFFER_LIMIT;
    }
    warn_buffer = myrealloc(warn_buffer, new_size);
    if (!old_buffer) *warn_buffer = '\0';
    warn_buffer_size = new_size;
  }
  if (strlen(warn_buffer)+strlen(buf)+2 <= warn_buffer_size) {
    strcat(warn_buffer, buf);
    strcat(warn_buffer, "\n");
  }
}

#endif

static i_mutex_t mutex;

void
i_tiff_init(void) {
  mutex = i_mutex_new();
}

static int save_tiff_tags(TIFF *tif, i_img *im);

static void 
pack_4bit_to(unsigned char *dest, const unsigned char *src, i_img_dim count);


static toff_t sizeproc(thandle_t x) {
	return 0;
}


/*
=item comp_seek(h, o, w)

Compatability for 64 bit systems like latest freebsd (internal)

   h - tiff handle, cast an io_glue object
   o - offset
   w - whence

=cut
*/

static
toff_t
comp_seek(thandle_t h, toff_t o, int w) {
  io_glue *ig = ((tiffio_context_t *)h)->ig;
  return (toff_t) i_io_seek(ig, o, w);
}

/*
=item comp_mmap(thandle_t, tdata_t*, toff_t*)

Dummy mmap stub.

This shouldn't ever be called but newer tifflibs want it anyway.

=cut
*/

static 
int
comp_mmap(thandle_t h, tdata_t*p, toff_t*off) {
  return -1;
}

/*
=item comp_munmap(thandle_t h, tdata_t p, toff_t off)

Dummy munmap stub.

This shouldn't ever be called but newer tifflibs want it anyway.

=cut
*/

static void
comp_munmap(thandle_t h, tdata_t p, toff_t off) {
  /* do nothing */
}

static tsize_t
comp_read(thandle_t h, tdata_t p, tsize_t size) {
  return i_io_read(((tiffio_context_t *)h)->ig, p, size);
}

static tsize_t
comp_write(thandle_t h, tdata_t p, tsize_t size) {
  return i_io_write(((tiffio_context_t *)h)->ig, p, size);
}

static int
comp_close(thandle_t h) {
  return i_io_close(((tiffio_context_t *)h)->ig);
}

static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
  i_img *im;
  uint32 width, height;
  uint16 samples_per_pixel;
  int tiled;
  float xres, yres;
  uint16 resunit;
  int gotXres, gotYres;
  uint16 photometric;
  uint16 bits_per_sample;
  uint16 planar_config;
  uint16 inkset;
  uint16 compress;
  uint16 sample_format;
  int i;
  read_state_t state;
  read_setup_t setupf = NULL;
  read_getter_t getterf = NULL;
  read_putter_t putterf = NULL;
  int channels = MAXCHANNELS;
  size_t sample_size = ~0; /* force failure if some code doesn't set it */
  i_img_dim total_pixels;
  int samples_integral;

  TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
  TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
  TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
  tiled = TIFFIsTiled(tif);
  TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
  TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
  TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config);
  TIFFGetFieldDefaulted(tif, TIFFTAG_INKSET, &inkset);

  if (samples_per_pixel == 0) {
    i_push_error(0, "invalid image: SamplesPerPixel is 0");
    return NULL;
  }

  TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format);

  mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, samples_per_pixel));
  mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
  mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));

  total_pixels = width * height;
  memset(&state, 0, sizeof(state));
  state.tif = tif;
  state.allow_incomplete = allow_incomplete;
  state.width = width;
  state.height = height;
  state.bits_per_sample = bits_per_sample;
  state.samples_per_pixel = samples_per_pixel;
  state.photometric = photometric;
  state.sample_signed = sample_format == SAMPLEFORMAT_INT;
  state.sample_format = sample_format;

  samples_integral = sample_format == SAMPLEFORMAT_UINT
    || sample_format == SAMPLEFORMAT_INT 
    || sample_format == SAMPLEFORMAT_VOID;  /* sample as UINT */

  /* yes, this if() is horrible */
  if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8
      && samples_integral) {
    setupf = setup_paletted;
    if (bits_per_sample == 8)
      putterf = paletted_putter8;
    else if (bits_per_sample == 4)
      putterf = paletted_putter4;
    else
      mm_log((1, "unsupported paletted bits_per_sample %d\n", bits_per_sample));

    sample_size = sizeof(i_sample_t);
    channels = 1;
  }
  else if (bits_per_sample == 16 
	   && photometric == PHOTOMETRIC_RGB
	   && samples_per_pixel >= 3
	   && samples_integral) {
    setupf = setup_16_rgb;
    putterf = putter_16;
    sample_size = 2;
    rgb_channels(&state, &channels);
  }
  else if (bits_per_sample == 16
	   && photometric == PHOTOMETRIC_MINISBLACK
	   && samples_integral) {
    setupf = setup_16_grey;
    putterf = putter_16;
    sample_size = 2;
    grey_channels(&state, &channels);
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_MINISBLACK
	   && samples_integral) {
    setupf = setup_8_grey;
    putterf = putter_8;
    sample_size = 1;
    grey_channels(&state, &channels);
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_RGB
	   && samples_integral) {
    setupf = setup_8_rgb;
    putterf = putter_8;
    sample_size = 1;
    rgb_channels(&state, &channels);
  }
  else if (bits_per_sample == 32 
	   && photometric == PHOTOMETRIC_RGB
	   && samples_per_pixel >= 3) {
    setupf = setup_32_rgb;
    putterf = putter_32;
    sample_size = sizeof(i_fsample_t);
    rgb_channels(&state, &channels);
  }
  else if (bits_per_sample == 32
	   && photometric == PHOTOMETRIC_MINISBLACK) {
    setupf = setup_32_grey;
    putterf = putter_32;
    sample_size = sizeof(i_fsample_t);
    grey_channels(&state, &channels);
  }
  else if (bits_per_sample == 1
	   && (photometric == PHOTOMETRIC_MINISBLACK
	       || photometric == PHOTOMETRIC_MINISWHITE)
	   && samples_per_pixel == 1) {
    setupf = setup_bilevel;
    putterf = putter_bilevel;
    sample_size = sizeof(i_palidx);
    channels = 1;
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_SEPARATED
	   && inkset == INKSET_CMYK
	   && samples_per_pixel >= 4
	   && samples_integral) {
    setupf = setup_cmyk8;
    putterf = putter_cmyk8;
    sample_size = 1;
    cmyk_channels(&state, &channels);
  }
  else if (bits_per_sample == 16
	   && photometric == PHOTOMETRIC_SEPARATED
	   && inkset == INKSET_CMYK
	   && samples_per_pixel >= 4
	   && samples_integral) {
    setupf = setup_cmyk16;
    putterf = putter_cmyk16;
    sample_size = 2;
    cmyk_channels(&state, &channels);
  }
  else {
    int alpha;
    fallback_rgb_channels(tif, width, height, &channels, &alpha);
    sample_size = 1;
  }

  if (!i_int_check_image_file_limits(width, height, channels, sample_size)) {
    return NULL;
  }

  if (tiled) {
    if (planar_config == PLANARCONFIG_CONTIG)
      getterf = tile_contig_getter;
  }
  else {
    if (planar_config == PLANARCONFIG_CONTIG)
      getterf = strip_contig_getter;
  }
  if (setupf && getterf && putterf) {

    if (!setupf(&state))
      return NULL;
    if (!getterf(&state, putterf) || !state.pixels_read) {
      if (state.img)
	i_img_destroy(state.img);
      if (state.raster)
	_TIFFfree(state.raster);
      if (state.line_buf)
	myfree(state.line_buf);
      
      return NULL;
    }

    if (allow_incomplete && state.pixels_read < total_pixels) {
      i_tags_setn(&(state.img->tags), "i_incomplete", 1);
      i_tags_setn(&(state.img->tags), "i_lines_read", 
		  state.pixels_read / width);
    }
    im = state.img;
    
    if (state.raster)
      _TIFFfree(state.raster);
    if (state.line_buf)
      myfree(state.line_buf);
  }
  else {
    if (tiled) {
      im = read_one_rgb_tiled(tif, width, height, allow_incomplete);
    }
    else {
      im = read_one_rgb_lines(tif, width, height, allow_incomplete);
    }
  }

  if (!im)
    return NULL;

  /* general metadata */
  i_tags_setn(&im->tags, "tiff_bitspersample", bits_per_sample);
  i_tags_setn(&im->tags, "tiff_photometric", photometric);
  TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &compress);
    
  /* resolution tags */
  TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
  gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
  gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
  if (gotXres || gotYres) {
    if (!gotXres)
      xres = yres;
    else if (!gotYres)
      yres = xres;
    i_tags_setn(&im->tags, "tiff_resolutionunit", resunit);
    if (resunit == RESUNIT_CENTIMETER) {
      /* from dots per cm to dpi */
      xres *= 2.54;
      yres *= 2.54;
      i_tags_set(&im->tags, "tiff_resolutionunit_name", "centimeter", -1);
    }
    else if (resunit == RESUNIT_NONE) {
      i_tags_setn(&im->tags, "i_aspect_only", 1);
      i_tags_set(&im->tags, "tiff_resolutionunit_name", "none", -1);
    }
    else if (resunit == RESUNIT_INCH) {
      i_tags_set(&im->tags, "tiff_resolutionunit_name", "inch", -1);
    }
    else {
      i_tags_set(&im->tags, "tiff_resolutionunit_name", "unknown", -1);
    }
    /* tifflib doesn't seem to provide a way to get to the original rational
       value of these, which would let me provide a more reasonable
       precision. So make up a number. */
    i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
    i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
  }

  /* Text tags */
  for (i = 0; i < text_tag_count; ++i) {
    char *data;
    if (TIFFGetField(tif, text_tag_names[i].tag, &data)) {
      mm_log((1, "i_readtiff_wiol: tag %d has value %s\n", 
	      text_tag_names[i].tag, data));
      i_tags_set(&im->tags, text_tag_names[i].name, data, -1);
    }
  }

  i_tags_set(&im->tags, "i_format", "tiff", 4);
#ifdef USE_EXT_WARN_HANDLER
  {
    tiffio_context_t *ctx = TIFFClientdata(tif);
    if (ctx->warn_buffer && ctx->warn_buffer[0]) {
      i_tags_set(&im->tags, "i_warning", ctx->warn_buffer, -1);
      ctx->warn_buffer[0] = '\0';
    }
  }
#else
  if (warn_buffer && *warn_buffer) {
    i_tags_set(&im->tags, "i_warning", warn_buffer, -1);
    *warn_buffer = '\0';
  }
#endif

  for (i = 0; i < compress_value_count; ++i) {
    if (compress_values[i].tag == compress) {
      i_tags_set(&im->tags, "tiff_compression", compress_values[i].name, -1);
      break;
    }
  }

  if (TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) {
    /* only set the tag if the the TIFF tag is present */
    i_tags_setn(&im->tags, "tiff_sample_format", sample_format);

    for (i = 0; i < sample_format_value_count; ++i) {
      if (sample_format_values[i].tag == sample_format) {
	i_tags_set(&im->tags, "tiff_sample_format_name",
		   sample_format_values[i].name, -1);
	break;
      }
    }
  }

  return im;
}

/*
=item i_readtiff_wiol(ig, allow_incomplete, page)

=cut
*/
i_img*
i_readtiff_wiol(io_glue *ig, int allow_incomplete, int page) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  TIFFErrorHandler old_warn_handler;
#ifdef USE_EXT_WARN_HANDLER
  TIFFErrorHandlerExt old_ext_warn_handler;
#endif
  i_img *im;
  int current_page;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  i_clear_error();
  old_handler = TIFFSetErrorHandler(error_handler);
#ifdef USE_EXT_WARN_HANDLER
  old_warn_handler = TIFFSetWarningHandler(NULL);
  old_ext_warn_handler = TIFFSetWarningHandlerExt(warn_handler_ex);
#else
  old_warn_handler = TIFFSetWarningHandler(warn_handler);
  if (warn_buffer)
    *warn_buffer = '\0';
#endif

  /* Add code to get the filename info from the iolayer */
  /* Also add code to check for mmapped code */

  mm_log((1, "i_readtiff_wiol(ig %p, allow_incomplete %d, page %d)\n", ig, allow_incomplete, page));
  
  tiffio_context_init(&ctx, ig);
  tif = TIFFClientOpen("(Iolayer)", 
		       "rm", 
		       (thandle_t) &ctx,
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close,
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  
  if (!tif) {
    mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
    i_push_error(0, "Error opening file");
    TIFFSetErrorHandler(old_handler);
    TIFFSetWarningHandler(old_warn_handler);
#ifdef USE_EXT_WARN_HANDLER
    TIFFSetWarningHandlerExt(old_ext_warn_handler);
#endif
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return NULL;
  }

  for (current_page = 0; current_page < page; ++current_page) {
    if (!TIFFReadDirectory(tif)) {
      mm_log((1, "i_readtiff_wiol: Unable to switch to directory %d\n", page));
      i_push_errorf(0, "could not switch to page %d", page);
      TIFFSetErrorHandler(old_handler);
      TIFFSetWarningHandler(old_warn_handler);
#ifdef USE_EXT_WARN_HANDLER
    TIFFSetWarningHandlerExt(old_ext_warn_handler);
#endif
      TIFFClose(tif);
      tiffio_context_final(&ctx);
      i_mutex_unlock(mutex);
      return NULL;
    }
  }

  im = read_one_tiff(tif, allow_incomplete);

  if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
  TIFFSetErrorHandler(old_handler);
  TIFFSetWarningHandler(old_warn_handler);
#ifdef USE_EXT_WARN_HANDLER
    TIFFSetWarningHandlerExt(old_ext_warn_handler);
#endif
  TIFFClose(tif);
  tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);

  return im;
}

/*
=item i_readtiff_multi_wiol(ig, *count)

Reads multiple images from a TIFF.

=cut
*/
i_img**
i_readtiff_multi_wiol(io_glue *ig, int *count) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  TIFFErrorHandler old_warn_handler;
#ifdef USE_EXT_WARN_HANDLER
  TIFFErrorHandlerExt old_ext_warn_handler;
#endif
  i_img **results = NULL;
  int result_alloc = 0;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  i_clear_error();
  old_handler = TIFFSetErrorHandler(error_handler);
#ifdef USE_EXT_WARN_HANDLER
  old_warn_handler = TIFFSetWarningHandler(NULL);
  old_ext_warn_handler = TIFFSetWarningHandlerExt(warn_handler_ex);
#else
  old_warn_handler = TIFFSetWarningHandler(warn_handler);
  if (warn_buffer)
    *warn_buffer = '\0';
#endif

  tiffio_context_init(&ctx, ig);

  /* Add code to get the filename info from the iolayer */
  /* Also add code to check for mmapped code */

  mm_log((1, "i_readtiff_wiol(ig %p)\n", ig));
  
  tif = TIFFClientOpen("(Iolayer)", 
		       "rm", 
		       (thandle_t) &ctx,
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close,
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  
  if (!tif) {
    mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
    i_push_error(0, "Error opening file");
    TIFFSetErrorHandler(old_handler);
    TIFFSetWarningHandler(old_warn_handler);
#ifdef USE_EXT_WARN_HANDLER
    TIFFSetWarningHandlerExt(old_ext_warn_handler);
#endif
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return NULL;
  }

  *count = 0;
  do {
    i_img *im = read_one_tiff(tif, 0);
    if (!im)
      break;
    if (++*count > result_alloc) {
      if (result_alloc == 0) {
        result_alloc = 5;
        results = mymalloc(result_alloc * sizeof(i_img *));
      }
      else {
        i_img **newresults;
        result_alloc *= 2;
        newresults = myrealloc(results, result_alloc * sizeof(i_img *));
	if (!newresults) {
	  i_img_destroy(im); /* don't leak it */
	  break;
	}
	results = newresults;
      }
    }
    results[*count-1] = im;
  } while (TIFFReadDirectory(tif));

  TIFFSetWarningHandler(old_warn_handler);
  TIFFSetErrorHandler(old_handler);
#ifdef USE_EXT_WARN_HANDLER
    TIFFSetWarningHandlerExt(old_ext_warn_handler);
#endif
  TIFFClose(tif);
  tiffio_context_final(&ctx);
  i_mutex_unlock(mutex);

  return results;
}

undef_int
i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) {
  uint32 width, height;
  unsigned char *linebuf = NULL;
  uint32 y;
  int rc;
  uint32 x;
  uint32 rowsperstrip;
  float vres = fine ? 196 : 98;
  int luma_chan;

  width    = im->xsize;
  height   = im->ysize;

  if (width != im->xsize || height != im->ysize) {
    i_push_error(0, "image too large for TIFF");
    return 0;
  }

  switch (im->channels) {
  case 1:
  case 2:
    luma_chan = 0;
    break;
  case 3:
  case 4:
    luma_chan = 1;
    break;
  default:
    /* This means a colorspace we don't handle yet */
    mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
    return 0;
  }

  /* Add code to get the filename info from the iolayer */
  /* Also add code to check for mmapped code */


  mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
  
  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      width)   )
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  )
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,   1)        )
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }

  linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
  
  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }

  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);

  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%lu\n",
	  (unsigned long)TIFFScanlineSize(tif) ));
  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));

  if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
  if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0; 
  }

  if (!save_tiff_tags(tif, im)) {
    return 0;
  }

  for (y=0; y<height; y++) {
    int linebufpos=0;
    for(x=0; x<width; x+=8) { 
      int bits;
      int bitpos;
      i_sample_t luma[8];
      uint8 bitval = 128;
      linebuf[linebufpos]=0;
      bits = width-x; if(bits>8) bits=8;
      i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
      for(bitpos=0;bitpos<bits;bitpos++) {
	linebuf[linebufpos] |= ((luma[bitpos] < 128) ? bitval : 0);
	bitval >>= 1;
      }
      linebufpos++;
    }
    if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
      mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
      break;
    }
  }
  if (linebuf) _TIFFfree(linebuf);

  return 1;
}

static uint16
find_compression(char const *name, uint16 *compress) {
  int i;

  for (i = 0; i < compress_value_count; ++i) {
    if (strcmp(compress_values[i].name, name) == 0) {
      *compress = (uint16)compress_values[i].tag;
      return 1;
    }
  }
  *compress = COMPRESSION_NONE;

  return 0;
}

static uint16
get_compression(i_img *im, uint16 def_compress) {
  int entry;
  int value;

  if (i_tags_find(&im->tags, "tiff_compression", 0, &entry)
      && im->tags.tags[entry].data) {
    uint16 compress;
    if (find_compression(im->tags.tags[entry].data, &compress)
	&& myTIFFIsCODECConfigured(compress))
      return compress;
  }
  if (i_tags_get_int(&im->tags, "tiff_compression", 0, &value)) {
    if ((uint16)value == value
	&& myTIFFIsCODECConfigured((uint16)value))
      return (uint16)value;
  }

  return def_compress;
}

int
i_tiff_has_compression(const char *name) {
  uint16 compress;

  if (!find_compression(name, &compress))
    return 0;

  return myTIFFIsCODECConfigured(compress);
}

static int
set_base_tags(TIFF *tif, i_img *im, uint16 compress, uint16 photometric, 
	      uint16 bits_per_sample, uint16 samples_per_pixel) {
  double xres, yres;
  int resunit;
  int got_xres, got_yres;
  int aspect_only;

  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, im->xsize)) {
    i_push_error(0, "write TIFF: setting width tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, im->ysize)) {
    i_push_error(0, "write TIFF: setting length tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) {
    i_push_error(0, "write TIFF: setting orientation tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
    i_push_error(0, "write TIFF: setting planar configuration tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) {
    i_push_error(0, "write TIFF: setting photometric tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compress)) {
    i_push_error(0, "write TIFF: setting compression tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) {
    i_push_error(0, "write TIFF: setting bits per sample tag");
    return 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel)) {
    i_push_error(0, "write TIFF: setting samples per pixel tag");
    return 0;
  }

  got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
  got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
  if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
    aspect_only = 0;
  if (!i_tags_get_int(&im->tags, "tiff_resolutionunit", 0, &resunit))
    resunit = RESUNIT_INCH;
  if (got_xres || got_yres) {
    if (!got_xres)
      xres = yres;
    else if (!got_yres)
      yres = xres;
    if (aspect_only) {
      resunit = RESUNIT_NONE;
    }
    else {
      if (resunit == RESUNIT_CENTIMETER) {
	xres /= 2.54;
	yres /= 2.54;
      }
      else {
	resunit  = RESUNIT_INCH;
      }
    }
    if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
      i_push_error(0, "write TIFF: setting xresolution tag");
      return 0;
    }
    if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
      i_push_error(0, "write TIFF: setting yresolution tag");
      return 0;
    }
    if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
      i_push_error(0, "write TIFF: setting resolutionunit tag");
      return 0;
    }
  }

  return 1;
}

static int 
write_one_bilevel(TIFF *tif, i_img *im, int zero_is_white) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  uint16 photometric;
  unsigned char *in_row;
  unsigned char *out_row;
  unsigned out_size;
  i_img_dim x, y;
  int invert;

  mm_log((1, "tiff - write_one_bilevel(tif %p, im %p, zero_is_white %d)\n", 
	  tif, im, zero_is_white));

  /* ignore a silly choice */
  if (compress == COMPRESSION_JPEG)
    compress = COMPRESSION_PACKBITS;

  switch (compress) {
  case COMPRESSION_CCITTRLE:
  case COMPRESSION_CCITTFAX3:
  case COMPRESSION_CCITTFAX4:
    /* natural fax photometric */
    photometric = PHOTOMETRIC_MINISWHITE;
    break;

  default:
    /* natural for most computer images */
    photometric = PHOTOMETRIC_MINISBLACK;
    break;
  }

  if (!set_base_tags(tif, im, compress, photometric, 1, 1))
    return 0;

  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
    i_push_error(0, "write TIFF: setting rows per strip tag");
    return 0; 
  }

  out_size = TIFFScanlineSize(tif);
  out_row = (unsigned char *)_TIFFmalloc( out_size );
  in_row = mymalloc(im->xsize);

  invert = (photometric == PHOTOMETRIC_MINISWHITE) != (zero_is_white != 0);

  for (y = 0; y < im->ysize; ++y) {
    int mask = 0x80;
    unsigned char *outp = out_row;
    memset(out_row, 0, out_size);
    i_gpal(im, 0, im->xsize, y, in_row);
    for (x = 0; x < im->xsize; ++x) {
      if (invert ? !in_row[x] : in_row[x]) {
	*outp |= mask;
      }
      mask >>= 1;
      if (!mask) {
	++outp;
	mask = 0x80;
      }
    }
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      _TIFFfree(out_row);
      myfree(in_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }

  _TIFFfree(out_row);
  myfree(in_row);

  return 1;
}

static int
set_palette(TIFF *tif, i_img *im, int size) {
  int count;
  uint16 *colors;
  uint16 *out[3];
  i_color c;
  int i, ch;
  
  colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size);
  out[0] = colors;
  out[1] = colors + size;
  out[2] = colors + 2 * size;
    
  count = i_colorcount(im);
  for (i = 0; i < count; ++i) {
    i_getcolors(im, i, &c, 1);
    for (ch = 0; ch < 3; ++ch)
      out[ch][i] = c.channel[ch] * 257;
  }
  for (; i < size; ++i) {
    for (ch = 0; ch < 3; ++ch)
      out[ch][i] = 0;
  }
  if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) {
    _TIFFfree(colors);
    i_push_error(0, "write TIFF: setting color map");
    return 0;
  }
  _TIFFfree(colors);
  
  return 1;
}

static int
write_one_paletted8(TIFF *tif, i_img *im) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  unsigned char *out_row;
  unsigned out_size;
  i_img_dim y;

  mm_log((1, "tiff - write_one_paletted8(tif %p, im %p)\n", tif, im));

  /* ignore a silly choice */
  if (compress == COMPRESSION_JPEG ||
      compress == COMPRESSION_CCITTRLE ||
      compress == COMPRESSION_CCITTFAX3 ||
      compress == COMPRESSION_CCITTFAX4)
    compress = COMPRESSION_PACKBITS;

  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
    i_push_error(0, "write TIFF: setting rows per strip tag");
    return 0; 
  }

  if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 8, 1))
    return 0;

  if (!set_palette(tif, im, 256))
    return 0;

  out_size = TIFFScanlineSize(tif);
  out_row = (unsigned char *)_TIFFmalloc( out_size );

  for (y = 0; y < im->ysize; ++y) {
    i_gpal(im, 0, im->xsize, y, out_row);
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      _TIFFfree(out_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }

  _TIFFfree(out_row);

  return 1;
}

static int
write_one_paletted4(TIFF *tif, i_img *im) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  unsigned char *in_row;
  unsigned char *out_row;
  size_t out_size;
  i_img_dim y;

  mm_log((1, "tiff - write_one_paletted4(tif %p, im %p)\n", tif, im));

  /* ignore a silly choice */
  if (compress == COMPRESSION_JPEG ||
      compress == COMPRESSION_CCITTRLE ||
      compress == COMPRESSION_CCITTFAX3 ||
      compress == COMPRESSION_CCITTFAX4)
    compress = COMPRESSION_PACKBITS;

  if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 4, 1))
    return 0;

  if (!set_palette(tif, im, 16))
    return 0;

  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
    i_push_error(0, "write TIFF: setting rows per strip tag");
    return 0; 
  }

  in_row = mymalloc(im->xsize);
  out_size = TIFFScanlineSize(tif);
  out_row = (unsigned char *)_TIFFmalloc( out_size );

  for (y = 0; y < im->ysize; ++y) {
    i_gpal(im, 0, im->xsize, y, in_row);
    memset(out_row, 0, out_size);
    pack_4bit_to(out_row, in_row, im->xsize);
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      _TIFFfree(out_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }

  myfree(in_row);
  _TIFFfree(out_row);

  return 1;
}

static int
set_direct_tags(TIFF *tif, i_img *im, uint16 compress, 
		uint16 bits_per_sample) {
  uint16 extras = EXTRASAMPLE_ASSOCALPHA;
  uint16 extra_count = im->channels == 2 || im->channels == 4;
  uint16 photometric = im->channels >= 3 ? 
    PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK;

  if (!set_base_tags(tif, im, compress, photometric, bits_per_sample, 
		     im->channels)) {
    return 0;
  }
  
  if (extra_count) {
    if (!TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, extra_count, &extras)) {
      i_push_error(0, "write TIFF: setting extra samples tag");
      return 0;
    }
  }

  if (compress == COMPRESSION_JPEG) {
    int jpeg_quality;
    if (i_tags_get_int(&im->tags, "tiff_jpegquality", 0, &jpeg_quality)
	&& jpeg_quality >= 0 && jpeg_quality <= 100) {
      if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, jpeg_quality)) {
	i_push_error(0, "write TIFF: setting jpeg quality pseudo-tag");
	return 0;
      }
    }
  }

  return 1;
}

static int 
write_one_32(TIFF *tif, i_img *im) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  unsigned *in_row;
  size_t out_size;
  uint32 *out_row;
  i_img_dim y;
  size_t sample_count = im->xsize * im->channels;
  size_t sample_index;
    
  mm_log((1, "tiff - write_one_32(tif %p, im %p)\n", tif, im));

  /* only 8 and 12 bit samples are supported by jpeg compression */
  if (compress == COMPRESSION_JPEG)
    compress = COMPRESSION_PACKBITS;

  if (!set_direct_tags(tif, im, compress, 32))
    return 0;

  in_row = mymalloc(sample_count * sizeof(unsigned));
  out_size = TIFFScanlineSize(tif);
  out_row = (uint32 *)_TIFFmalloc( out_size );

  for (y = 0; y < im->ysize; ++y) {
    if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 32) <= 0) {
      i_push_error(0, "Cannot read 32-bit samples");
      return 0;
    }
    for (sample_index = 0; sample_index < sample_count; ++sample_index)
      out_row[sample_index] = in_row[sample_index];
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      myfree(in_row);
      _TIFFfree(out_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }

  myfree(in_row);
  _TIFFfree(out_row);
  
  return 1;
}

static int 
write_one_16(TIFF *tif, i_img *im) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  unsigned *in_row;
  size_t out_size;
  uint16 *out_row;
  i_img_dim y;
  size_t sample_count = im->xsize * im->channels;
  size_t sample_index;
    
  mm_log((1, "tiff - write_one_16(tif %p, im %p)\n", tif, im));

  /* only 8 and 12 bit samples are supported by jpeg compression */
  if (compress == COMPRESSION_JPEG)
    compress = COMPRESSION_PACKBITS;

  if (!set_direct_tags(tif, im, compress, 16))
    return 0;

  in_row = mymalloc(sample_count * sizeof(unsigned));
  out_size = TIFFScanlineSize(tif);
  out_row = (uint16 *)_TIFFmalloc( out_size );

  for (y = 0; y < im->ysize; ++y) {
    if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 16) <= 0) {
      i_push_error(0, "Cannot read 16-bit samples");
      return 0;
    }
    for (sample_index = 0; sample_index < sample_count; ++sample_index)
      out_row[sample_index] = in_row[sample_index];
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      myfree(in_row);
      _TIFFfree(out_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }

  myfree(in_row);
  _TIFFfree(out_row);
  
  return 1;
}

static int 
write_one_8(TIFF *tif, i_img *im) {
  uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
  size_t out_size;
  unsigned char *out_row;
  i_img_dim y;
  size_t sample_count = im->xsize * im->channels;
    
  mm_log((1, "tiff - write_one_8(tif %p, im %p)\n", tif, im));

  if (!set_direct_tags(tif, im, compress, 8))
    return 0;

  out_size = TIFFScanlineSize(tif);
  if (out_size < sample_count)
    out_size = sample_count;
  out_row = (unsigned char *)_TIFFmalloc( out_size );

  for (y = 0; y < im->ysize; ++y) {
    if (i_gsamp(im, 0, im->xsize, y, out_row, NULL, im->channels) <= 0) {
      i_push_error(0, "Cannot read 8-bit samples");
      return 0;
    }
    if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
      _TIFFfree(out_row);
      i_push_error(0, "write TIFF: write scan line failed");
      return 0;
    }
  }
  _TIFFfree(out_row);
  
  return 1;
}

static int
i_writetiff_low(TIFF *tif, i_img *im) {
  uint32 width, height;
  uint16 channels;
  int zero_is_white;

  width    = im->xsize;
  height   = im->ysize;
  channels = im->channels;

  if (width != im->xsize || height != im->ysize) {
    i_push_error(0, "image too large for TIFF");
    return 0;
  }

  mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d, bits=%d\n", width, height, channels, im->bits));
  if (im->type == i_palette_type) {
    mm_log((1, "i_writetiff_low: paletted, colors=%d\n", i_colorcount(im)));
  }
  
  if (i_img_is_monochrome(im, &zero_is_white)) {
    if (!write_one_bilevel(tif, im, zero_is_white))
      return 0;
  }
  else if (im->type == i_palette_type) {
    if (i_colorcount(im) <= 16) {
      if (!write_one_paletted4(tif, im))
	return 0;
    }
    else {
      if (!write_one_paletted8(tif, im))
	return 0;
    }
  }
  else if (im->bits > 16) {
    if (!write_one_32(tif, im))
      return 0;
  }
  else if (im->bits > 8) {
    if (!write_one_16(tif, im))
      return 0;
  }
  else {
    if (!write_one_8(tif, im))
      return 0;
  }

  if (!save_tiff_tags(tif, im))
    return 0;

  return 1;
}

/*
=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode)

Stores an image in the iolayer object.

   ig - io_object that defines source to write to 
   imgs,count - the images to write

=cut 
*/

undef_int
i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  int i;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  old_handler = TIFFSetErrorHandler(error_handler);

  i_clear_error();
  mm_log((1, "i_writetiff_multi_wiol(ig %p, imgs %p, count %d)\n", 
          ig, imgs, count));

  tiffio_context_init(&ctx, ig);
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) &ctx, 
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close, 
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  


  if (!tif) {
    mm_log((1, "i_writetiff_multi_wiol: Unable to open tif file for writing\n"));
    i_push_error(0, "Could not create TIFF object");
    TIFFSetErrorHandler(old_handler);
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return 0;
  }

  for (i = 0; i < count; ++i) {
    if (!i_writetiff_low(tif, imgs[i])) {
      TIFFClose(tif);
      TIFFSetErrorHandler(old_handler);
      tiffio_context_final(&ctx);
      i_mutex_unlock(mutex);
      return 0;
    }

    if (!TIFFWriteDirectory(tif)) {
      i_push_error(0, "Cannot write TIFF directory");
      TIFFClose(tif);
      TIFFSetErrorHandler(old_handler);
      tiffio_context_final(&ctx);
      i_mutex_unlock(mutex);
      return 0;
    }
  }

  TIFFSetErrorHandler(old_handler);
  (void) TIFFClose(tif);
  tiffio_context_final(&ctx);

  i_mutex_unlock(mutex);

  if (i_io_close(ig))
    return 0;

  return 1;
}

/*
=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode)

Stores an image in the iolayer object.

   ig - io_object that defines source to write to 
   imgs,count - the images to write
   fine_mode - select fine or normal mode fax images

=cut 
*/


undef_int
i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
  TIFF* tif;
  int i;
  TIFFErrorHandler old_handler;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  old_handler = TIFFSetErrorHandler(error_handler);

  i_clear_error();
  mm_log((1, "i_writetiff_multi_wiol(ig %p, imgs %p, count %d)\n", 
          ig, imgs, count));

  tiffio_context_init(&ctx, ig);
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) &ctx, 
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close, 
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  


  if (!tif) {
    mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
    i_push_error(0, "Could not create TIFF object");
    TIFFSetErrorHandler(old_handler);
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return 0;
  }

  for (i = 0; i < count; ++i) {
    if (!i_writetiff_low_faxable(tif, imgs[i], fine)) {
      TIFFClose(tif);
      TIFFSetErrorHandler(old_handler);
      tiffio_context_final(&ctx);
      i_mutex_unlock(mutex);
      return 0;
    }

    if (!TIFFWriteDirectory(tif)) {
      i_push_error(0, "Cannot write TIFF directory");
      TIFFClose(tif);
      TIFFSetErrorHandler(old_handler);
      tiffio_context_final(&ctx);
      i_mutex_unlock(mutex);
      return 0;
    }
  }

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);
  tiffio_context_final(&ctx);

  i_mutex_unlock(mutex);

  if (i_io_close(ig))
    return 0;

  return 1;
}

/*
=item i_writetiff_wiol(im, ig)

Stores an image in the iolayer object.

   im - image object to write out
   ig - io_object that defines source to write to 

=cut 
*/
undef_int
i_writetiff_wiol(i_img *img, io_glue *ig) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  old_handler = TIFFSetErrorHandler(error_handler);

  i_clear_error();
  mm_log((1, "i_writetiff_wiol(img %p, ig %p)\n", img, ig));

  tiffio_context_init(&ctx, ig);

  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) &ctx, 
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close, 
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  


  if (!tif) {
    mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
    i_push_error(0, "Could not create TIFF object");
    tiffio_context_final(&ctx);
    TIFFSetErrorHandler(old_handler);
    i_mutex_unlock(mutex);
    return 0;
  }

  if (!i_writetiff_low(tif, img)) {
    TIFFClose(tif);
    tiffio_context_final(&ctx);
    TIFFSetErrorHandler(old_handler);
    i_mutex_unlock(mutex);
    return 0;
  }

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);
  tiffio_context_final(&ctx);
  i_mutex_unlock(mutex);

  if (i_io_close(ig))
    return 0;

  return 1;
}



/*
=item i_writetiff_wiol_faxable(i_img *, io_glue *)

Stores an image in the iolayer object in faxable tiff format.

   im - image object to write out
   ig - io_object that defines source to write to 

Note, this may be rewritten to use to simply be a call to a
lower-level function that gives more options for writing tiff at some
point.

=cut
*/

undef_int
i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  tiffio_context_t ctx;

  i_mutex_lock(mutex);

  old_handler = TIFFSetErrorHandler(error_handler);

  i_clear_error();
  mm_log((1, "i_writetiff_wiol(img %p, ig %p)\n", im, ig));

  tiffio_context_init(&ctx, ig);
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) &ctx, 
		       comp_read,
		       comp_write,
		       comp_seek,
		       comp_close, 
		       sizeproc,
		       comp_mmap,
		       comp_munmap);
  


  if (!tif) {
    mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
    i_push_error(0, "Could not create TIFF object");
    TIFFSetErrorHandler(old_handler);
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return 0;
  }

  if (!i_writetiff_low_faxable(tif, im, fine)) {
    TIFFClose(tif);
    TIFFSetErrorHandler(old_handler);
    tiffio_context_final(&ctx);
    i_mutex_unlock(mutex);
    return 0;
  }

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);
  tiffio_context_final(&ctx);
  i_mutex_unlock(mutex);

  if (i_io_close(ig))
    return 0;

  return 1;
}

static int save_tiff_tags(TIFF *tif, i_img *im) {
  int i;
 
  for (i = 0; i < text_tag_count; ++i) {
    int entry;
    if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) {
      if (!TIFFSetField(tif, text_tag_names[i].tag, 
                       im->tags.tags[entry].data)) {
       i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
       return 0;
      }
    }
  }

  return 1;
}


static void
unpack_4bit_to(unsigned char *dest, const unsigned char *src, 
	       size_t src_byte_count) {
  while (src_byte_count > 0) {
    *dest++ = *src >> 4;
    *dest++ = *src++ & 0xf;
    --src_byte_count;
  }
}

static void pack_4bit_to(unsigned char *dest, const unsigned char *src, 
			 i_img_dim pixel_count) {
  int i = 0;
  while (i < pixel_count) {
    if ((i & 1) == 0) {
      *dest = *src++ << 4;
    }
    else {
      *dest++ |= *src++;
    }
    ++i;
  }
}

/*
=item fallback_rgb_channels

Calculate the number of output channels when we fallback to the RGBA
family of functions.

=cut
*/

static void
fallback_rgb_channels(TIFF *tif, i_img_dim width, i_img_dim height, int *channels, int *alpha_chan) {
  uint16 photometric;
  uint16 in_channels;
  uint16 extra_count;
  uint16 *extras;

  TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &in_channels);
  TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);

  switch (photometric) {
  case PHOTOMETRIC_SEPARATED:
    *channels = 3;
    break;
  
  case PHOTOMETRIC_MINISWHITE:
  case PHOTOMETRIC_MINISBLACK:
    /* the TIFF RGBA functions expand single channel grey into RGB,
       so reduce it, we move the alpha channel into the right place 
       if needed */
    *channels = 1;
    break;

  default:
    *channels = 3;
    break;
  }
  /* TIFF images can have more than one alpha channel, but Imager can't
     this ignores the possibility of 2 channel images with 2 alpha,
     but there's not much I can do about that */
  *alpha_chan = 0;
  if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)
      && extra_count) {
    *alpha_chan = (*channels)++;
  }
}

static i_img *
make_rgb(TIFF *tif, i_img_dim width, i_img_dim height, int *alpha_chan) {
  int channels = 0;

  fallback_rgb_channels(tif, width, height, &channels, alpha_chan);

  return i_img_8_new(width, height, channels);
}

static i_img *
read_one_rgb_lines(TIFF *tif, i_img_dim width, i_img_dim height, int allow_incomplete) {
  i_img *im;
  uint32* raster = NULL;
  uint32 rowsperstrip, row;
  i_color *line_buf;
  int alpha_chan;
  int rc;

  im = make_rgb(tif, width, height, &alpha_chan);
  if (!im)
    return NULL;

  rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
  mm_log((1, "i_readtiff_wiol: rowsperstrip=%d rc = %d\n", rowsperstrip, rc));
  
  if (rc != 1 || rowsperstrip==-1) {
    rowsperstrip = height;
  }
  
  raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
  if (!raster) {
    i_img_destroy(im);
    i_push_error(0, "No space for raster buffer");
    return NULL;
  }

  line_buf = mymalloc(sizeof(i_color) * width);
  
  for( row = 0; row < height; row += rowsperstrip ) {
    uint32 newrows, i_row;
    
    if (!TIFFReadRGBAStrip(tif, row, raster)) {
      if (allow_incomplete) {
	i_tags_setn(&im->tags, "i_lines_read", row);
	i_tags_setn(&im->tags, "i_incomplete", 1);
	break;
      }
      else {
	i_push_error(0, "could not read TIFF image strip");
	_TIFFfree(raster);
	i_img_destroy(im);
	return NULL;
      }
    }
    
    newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
    mm_log((1, "newrows=%d\n", newrows));
    
    for( i_row = 0; i_row < newrows; i_row++ ) { 
      uint32 x;
      i_color *outp = line_buf;

      for(x = 0; x<width; x++) {
	uint32 temp = raster[x+width*(newrows-i_row-1)];
	outp->rgba.r = TIFFGetR(temp);
	outp->rgba.g = TIFFGetG(temp);
	outp->rgba.b = TIFFGetB(temp);

	if (alpha_chan) {
	  /* the libtiff RGBA code expands greyscale into RGBA, so put the
	     alpha in the right place and scale it */
	  int ch;
	  outp->channel[alpha_chan] = TIFFGetA(temp);
	  if (outp->channel[alpha_chan]) {
	    for (ch = 0; ch < alpha_chan; ++ch) {
	      outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan];
	    }
	  }
	}

	outp++;
      }
      i_plin(im, 0, width, i_row+row, line_buf);
    }
  }

  myfree(line_buf);
  _TIFFfree(raster);
  
  return im;
}

/* adapted from libtiff 

  libtiff's TIFFReadRGBATile succeeds even when asked to read an
  invalid tile, which means we have no way of knowing whether the data
  we received from it is valid or not.

  So the caller here has set stoponerror to 1 so that
  TIFFRGBAImageGet() will fail.

  read_one_rgb_tiled() then takes that into account for i_incomplete
  or failure.
 */
static int
myTIFFReadRGBATile(TIFFRGBAImage *img, uint32 col, uint32 row, uint32 * raster)

{
    int 	ok;
    uint32	tile_xsize, tile_ysize;
    uint32	read_xsize, read_ysize;
    uint32	i_row;

    /*
     * Verify that our request is legal - on a tile file, and on a
     * tile boundary.
     */
    
    TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILEWIDTH, &tile_xsize);
    TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILELENGTH, &tile_ysize);
    if( (col % tile_xsize) != 0 || (row % tile_ysize) != 0 )
    {
      i_push_errorf(0, "Row/col passed to myTIFFReadRGBATile() must be top"
		    "left corner of a tile.");
      return 0;
    }

    /*
     * The TIFFRGBAImageGet() function doesn't allow us to get off the
     * edge of the image, even to fill an otherwise valid tile.  So we
     * figure out how much we can read, and fix up the tile buffer to
     * a full tile configuration afterwards.
     */

    if( row + tile_ysize > img->height )
        read_ysize = img->height - row;
    else
        read_ysize = tile_ysize;
    
    if( col + tile_xsize > img->width )
        read_xsize = img->width - col;
    else
        read_xsize = tile_xsize;

    /*
     * Read the chunk of imagery.
     */
    
    img->row_offset = row;
    img->col_offset = col;

    ok = TIFFRGBAImageGet(img, raster, read_xsize, read_ysize );
        
    /*
     * If our read was incomplete we will need to fix up the tile by
     * shifting the data around as if a full tile of data is being returned.
     *
     * This is all the more complicated because the image is organized in
     * bottom to top format. 
     */

    if( read_xsize == tile_xsize && read_ysize == tile_ysize )
        return( ok );

    for( i_row = 0; i_row < read_ysize; i_row++ ) {
        memmove( raster + (tile_ysize - i_row - 1) * tile_xsize,
                 raster + (read_ysize - i_row - 1) * read_xsize,
                 read_xsize * sizeof(uint32) );
        _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize+read_xsize,
                     0, sizeof(uint32) * (tile_xsize - read_xsize) );
    }

    for( i_row = read_ysize; i_row < tile_ysize; i_row++ ) {
        _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize,
                     0, sizeof(uint32) * tile_xsize );
    }

    return (ok);
}

static i_img *
read_one_rgb_tiled(TIFF *tif, i_img_dim width, i_img_dim height, int allow_incomplete) {
  i_img *im;
  uint32* raster = NULL;
  int ok = 1;
  uint32 row, col;
  uint32 tile_width, tile_height;
  unsigned long pixels = 0;
  char 	emsg[1024] = "";
  TIFFRGBAImage img;
  i_color *line;
  int alpha_chan;
  
  im = make_rgb(tif, width, height, &alpha_chan);
  if (!im)
    return NULL;
  
  if (!TIFFRGBAImageOK(tif, emsg) 
      || !TIFFRGBAImageBegin(&img, tif, 1, emsg)) {
    i_push_error(0, emsg);
    i_img_destroy(im);
    return( 0 );
  }

  TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
  TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
  mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
  
  raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
  if (!raster) {
    i_img_destroy(im);
    i_push_error(0, "No space for raster buffer");
    TIFFRGBAImageEnd(&img);
    return NULL;
  }
  line = mymalloc(tile_width * sizeof(i_color));
  
  for( row = 0; row < height; row += tile_height ) {
    for( col = 0; col < width; col += tile_width ) {
      
      /* Read the tile into an RGBA array */
      if (myTIFFReadRGBATile(&img, col, row, raster)) {
	uint32 i_row, x;
	uint32 newrows = (row+tile_height > height) ? height-row : tile_height;
	uint32 newcols = (col+tile_width  > width ) ? width-col  : tile_width;

	mm_log((1, "i_readtiff_wiol: tile(%d, %d) newcols=%d newrows=%d\n", col, row, newcols, newrows));
	for( i_row = 0; i_row < newrows; i_row++ ) {
	  i_color *outp = line;
	  for(x = 0; x < newcols; x++) {
	    uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
	    outp->rgba.r = TIFFGetR(temp);
	    outp->rgba.g = TIFFGetG(temp);
	    outp->rgba.b = TIFFGetB(temp);
	    outp->rgba.a = TIFFGetA(temp);

	    if (alpha_chan) {
	      /* the libtiff RGBA code expands greyscale into RGBA, so put the
		 alpha in the right place and scale it */
	      int ch;
	      outp->channel[alpha_chan] = TIFFGetA(temp);
	      
	      if (outp->channel[alpha_chan]) {
		for (ch = 0; ch < alpha_chan; ++ch) {
		  outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan];
		}
	      }
	    }

	    ++outp;
	  }
	  i_plin(im, col, col+newcols, row+i_row, line);
	}
	pixels += newrows * newcols;
      }
      else {
	if (allow_incomplete) {
	  ok = 0;
	}
	else {
	  goto error;
	}
      }
    }
  }

  if (!ok) {
    if (pixels == 0) {
      i_push_error(0, "TIFF: No image data could be read from the image");
      goto error;
    }

    /* incomplete image */
    i_tags_setn(&im->tags, "i_incomplete", 1);
    i_tags_setn(&im->tags, "i_lines_read", pixels / width);
  }

  myfree(line);
  TIFFRGBAImageEnd(&img);
  _TIFFfree(raster);
  
  return im;

 error:
  myfree(line);
  _TIFFfree(raster);
  TIFFRGBAImageEnd(&img);
  i_img_destroy(im);
  return NULL;
}

char const *
i_tiff_libversion(void) {
  return TIFFGetVersion();
}

static int 
setup_paletted(read_state_t *state) {
  uint16 *maps[3];
  int i, ch;
  int color_count = 1 << state->bits_per_sample;

  state->img = i_img_pal_new(state->width, state->height, 3, 256);
  if (!state->img)
    return 0;

  /* setup the color map */
  if (!TIFFGetField(state->tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
    i_push_error(0, "Cannot get colormap for paletted image");
    i_img_destroy(state->img);
    return 0;
  }
  for (i = 0; i < color_count; ++i) {
    i_color c;
    for (ch = 0; ch < 3; ++ch) {
      c.channel[ch] = Sample16To8(maps[ch][i]);
    }
    i_addcolors(state->img, &c, 1);
  }

  return 1;
}

static int 
tile_contig_getter(read_state_t *state, read_putter_t putter) {
  uint32 tile_width, tile_height;
  uint32 this_tile_height, this_tile_width;
  uint32 rows_left, cols_left;
  uint32 x, y;

  state->raster = _TIFFmalloc(TIFFTileSize(state->tif));
  if (!state->raster) {
    i_push_error(0, "tiff: Out of memory allocating tile buffer");
    return 0;
  }

  TIFFGetField(state->tif, TIFFTAG_TILEWIDTH, &tile_width);
  TIFFGetField(state->tif, TIFFTAG_TILELENGTH, &tile_height);
  rows_left = state->height;
  for (y = 0; y < state->height; y += this_tile_height) {
    this_tile_height = rows_left > tile_height ? tile_height : rows_left;

    cols_left = state->width;
    for (x = 0; x < state->width; x += this_tile_width) {
      this_tile_width = cols_left > tile_width ? tile_width : cols_left;

      if (TIFFReadTile(state->tif,
		       state->raster,
		       x, y, 0, 0) < 0) {
	if (!state->allow_incomplete) {
	  return 0;
	}
      }
      else {
	putter(state, x, y, this_tile_width, this_tile_height, tile_width - this_tile_width);
      }

      cols_left -= this_tile_width;
    }

    rows_left -= this_tile_height;
  }

  return 1;
}

static int 
strip_contig_getter(read_state_t *state, read_putter_t putter) {
  uint32 rows_per_strip;
  tsize_t strip_size = TIFFStripSize(state->tif);
  uint32 y, strip_rows, rows_left;

  state->raster = _TIFFmalloc(strip_size);
  if (!state->raster) {
    i_push_error(0, "tiff: Out of memory allocating strip buffer");
    return 0;
  }
  
  TIFFGetFieldDefaulted(state->tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
  rows_left = state->height;
  for (y = 0; y < state->height; y += strip_rows) {
    strip_rows = rows_left > rows_per_strip ? rows_per_strip : rows_left;
    if (TIFFReadEncodedStrip(state->tif,
			     TIFFComputeStrip(state->tif, y, 0),
			     state->raster,
			     strip_size) < 0) {
      if (!state->allow_incomplete)
	return 0;
    }
    else {
      putter(state, 0, y, state->width, strip_rows, 0);
    }
    rows_left -= strip_rows;
  }

  return 1;
}

static int 
paletted_putter8(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, int extras) {
  unsigned char *p = state->raster;

  state->pixels_read += width * height;
  while (height > 0) {
    i_ppal(state->img, x, x + width, y, p);
    p += width + extras;
    --height;
    ++y;
  }

  return 1;
}

static int 
paletted_putter4(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, int extras) {
  uint32 img_line_size = (width + 1) / 2;
  uint32 skip_line_size = (width + extras + 1) / 2;
  unsigned char *p = state->raster;

  if (!state->line_buf)
    state->line_buf = mymalloc(state->width);

  state->pixels_read += width * height;
  while (height > 0) {
    unpack_4bit_to(state->line_buf, p, img_line_size);
    i_ppal(state->img, x, x + width, y, state->line_buf);
    p += skip_line_size;
    --height;
    ++y;
  }

  return 1;
}

static void
rgb_channels(read_state_t *state, int *out_channels) {
  uint16 extra_count;
  uint16 *extras;
  
  /* safe defaults */
  *out_channels = 3;
  state->alpha_chan = 0;
  state->scale_alpha = 0;
  state->color_channels = 3;

  /* plain RGB */
  if (state->samples_per_pixel == 3)
    return;
 
  if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
    mm_log((1, "tiff: samples != 3 but no extra samples tag\n"));
    return;
  }

  if (!extra_count) {
    mm_log((1, "tiff: samples != 3 but no extra samples listed"));
    return;
  }

  ++*out_channels;
  state->alpha_chan = 3;
  switch (*extras) {
  case EXTRASAMPLE_UNSPECIFIED:
  case EXTRASAMPLE_ASSOCALPHA:
    state->scale_alpha = 1;
    break;

  case EXTRASAMPLE_UNASSALPHA:
    state->scale_alpha = 0;
    break;

  default:
    mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
	    *extras));
    state->scale_alpha = 1;
    break;
  }
  mm_log((1, "tiff alpha channel %d scale %d\n", state->alpha_chan, state->scale_alpha));
}

static void
grey_channels(read_state_t *state, int *out_channels) {
  uint16 extra_count;
  uint16 *extras;
  
  /* safe defaults */
  *out_channels = 1;
  state->alpha_chan = 0;
  state->scale_alpha = 0;
  state->color_channels = 1;

  /* plain grey */
  if (state->samples_per_pixel == 1)
    return;
 
  if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
    mm_log((1, "tiff: samples != 1 but no extra samples tag\n"));
    return;
  }

  if (!extra_count) {
    mm_log((1, "tiff: samples != 1 but no extra samples listed"));
    return;
  }

  ++*out_channels;
  state->alpha_chan = 1;
  switch (*extras) {
  case EXTRASAMPLE_UNSPECIFIED:
  case EXTRASAMPLE_ASSOCALPHA:
    state->scale_alpha = 1;
    break;

  case EXTRASAMPLE_UNASSALPHA:
    state->scale_alpha = 0;
    break;

  default:
    mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
	    *extras));
    state->scale_alpha = 1;
    break;
  }
}

static int
setup_16_rgb(read_state_t *state) {
  int out_channels;

  rgb_channels(state, &out_channels);

  state->img = i_img_16_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);

  return 1;
}

static int
setup_16_grey(read_state_t *state) {
  int out_channels;

  grey_channels(state, &out_channels);

  state->img = i_img_16_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);

  return 1;
}

static int 
putter_16(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	  int row_extras) {
  uint16 *p = state->raster;
  int out_chan = state->img->channels;

  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    int ch;
    unsigned *outp = state->line_buf;

    for (i = 0; i < width; ++i) {
      for (ch = 0; ch < out_chan; ++ch) {
	outp[ch] = p[ch];
      }
      if (state->sample_signed) {
	for (ch = 0; ch < state->color_channels; ++ch)
	  outp[ch] ^= 0x8000;
      }
      if (state->alpha_chan && state->scale_alpha && outp[state->alpha_chan]) {
	for (ch = 0; ch < state->alpha_chan; ++ch) {
	  int result = 0.5 + (outp[ch] * 65535.0 / outp[state->alpha_chan]);
	  outp[ch] = CLAMP16(result);
	}
      }
      p += state->samples_per_pixel;
      outp += out_chan;
    }

    i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16);

    p += row_extras * state->samples_per_pixel;
    --height;
    ++y;
  }

  return 1;
}

static int
setup_8_rgb(read_state_t *state) {
  int out_channels;

  rgb_channels(state, &out_channels);

  state->img = i_img_8_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);

  return 1;
}

static int
setup_8_grey(read_state_t *state) {
  int out_channels;

  grey_channels(state, &out_channels);

  state->img = i_img_8_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(i_color) * state->width * out_channels);

  return 1;
}

static int 
putter_8(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	  int row_extras) {
  unsigned char *p = state->raster;
  int out_chan = state->img->channels;

  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    int ch;
    i_color *outp = state->line_buf;

    for (i = 0; i < width; ++i) {
      for (ch = 0; ch < out_chan; ++ch) {
	outp->channel[ch] = p[ch];
      }
      if (state->sample_signed) {
	for (ch = 0; ch < state->color_channels; ++ch)
	  outp->channel[ch] ^= 0x80;
      }
      if (state->alpha_chan && state->scale_alpha 
	  && outp->channel[state->alpha_chan]) {
	for (ch = 0; ch < state->alpha_chan; ++ch) {
	  int result = (outp->channel[ch] * 255 + 127) / outp->channel[state->alpha_chan];
	
	  outp->channel[ch] = CLAMP8(result);
	}
      }
      p += state->samples_per_pixel;
      outp++;
    }

    i_plin(state->img, x, x + width, y, state->line_buf);

    p += row_extras * state->samples_per_pixel;
    --height;
    ++y;
  }

  return 1;
}

static int
setup_32_rgb(read_state_t *state) {
  int out_channels;

  rgb_channels(state, &out_channels);

  state->img = i_img_double_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(i_fcolor) * state->width);

  return 1;
}

static int
setup_32_grey(read_state_t *state) {
  int out_channels;

  grey_channels(state, &out_channels);

  state->img = i_img_double_new(state->width, state->height, out_channels);
  if (!state->img)
    return 0;
  state->line_buf = mymalloc(sizeof(i_fcolor) * state->width);

  return 1;
}

static int 
putter_32(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	  int row_extras) {
  uint32 *p = state->raster;
  int out_chan = state->img->channels;

  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    int ch;
    i_fcolor *outp = state->line_buf;

    for (i = 0; i < width; ++i) {
#ifdef IEEEFP_TYPES
      if (state->sample_format == SAMPLEFORMAT_IEEEFP) {
	const float *pv = (const float *)p;
	for (ch = 0; ch < out_chan; ++ch) {
	  outp->channel[ch] = pv[ch];
	}
      }
      else {
#endif
	for (ch = 0; ch < out_chan; ++ch) {
	  if (state->sample_signed && ch < state->color_channels)
	    outp->channel[ch] = (p[ch] ^ 0x80000000UL) / 4294967295.0;
	  else
	    outp->channel[ch] = p[ch] / 4294967295.0;
	}
#ifdef IEEEFP_TYPES
      }
#endif

      if (state->alpha_chan && state->scale_alpha && outp->channel[state->alpha_chan]) {
	for (ch = 0; ch < state->alpha_chan; ++ch)
	  outp->channel[ch] /= outp->channel[state->alpha_chan];
      }
      p += state->samples_per_pixel;
      outp++;
    }

    i_plinf(state->img, x, x + width, y, state->line_buf);

    p += row_extras * state->samples_per_pixel;
    --height;
    ++y;
  }

  return 1;
}

static int
setup_bilevel(read_state_t *state) {
  i_color black, white;
  state->img = i_img_pal_new(state->width, state->height, 1, 256);
  if (!state->img)
    return 0;
  black.channel[0] = black.channel[1] = black.channel[2] = 
    black.channel[3] = 0;
  white.channel[0] = white.channel[1] = white.channel[2] = 
    white.channel[3] = 255;
  if (state->photometric == PHOTOMETRIC_MINISBLACK) {
    i_addcolors(state->img, &black, 1);
    i_addcolors(state->img, &white, 1);
  }
  else {
    i_addcolors(state->img, &white, 1);
    i_addcolors(state->img, &black, 1);
  }
  state->line_buf = mymalloc(state->width);

  return 1;
}

static int 
putter_bilevel(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	       int row_extras) {
  unsigned char *line_in = state->raster;
  size_t line_size = (width + row_extras + 7) / 8;
  
  /* tifflib returns the bits in MSB2LSB order even when the file is
     in LSB2MSB, so we only need to handle MSB2LSB */
  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    unsigned char *outp = state->line_buf;
    unsigned char *inp = line_in;
    unsigned mask = 0x80;

    for (i = 0; i < width; ++i) {
      *outp++ = *inp & mask ? 1 : 0;
      mask >>= 1;
      if (!mask) {
	++inp;
	mask = 0x80;
      }
    }

    i_ppal(state->img, x, x + width, y, state->line_buf);

    line_in += line_size;
    --height;
    ++y;
  }

  return 1;
}

static void
cmyk_channels(read_state_t *state, int *out_channels) {
  uint16 extra_count;
  uint16 *extras;
  
  /* safe defaults */
  *out_channels = 3;
  state->alpha_chan = 0;
  state->scale_alpha = 0;
  state->color_channels = 3;

  /* plain CMYK */
  if (state->samples_per_pixel == 4)
    return;
 
  if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
    mm_log((1, "tiff: CMYK samples != 4 but no extra samples tag\n"));
    return;
  }

  if (!extra_count) {
    mm_log((1, "tiff: CMYK samples != 4 but no extra samples listed"));
    return;
  }

  ++*out_channels;
  state->alpha_chan = 4;
  switch (*extras) {
  case EXTRASAMPLE_UNSPECIFIED:
  case EXTRASAMPLE_ASSOCALPHA:
    state->scale_alpha = 1;
    break;

  case EXTRASAMPLE_UNASSALPHA:
    state->scale_alpha = 0;
    break;

  default:
    mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
	    *extras));
    state->scale_alpha = 1;
    break;
  }
}

static int
setup_cmyk8(read_state_t *state) {
  int channels;

  cmyk_channels(state, &channels);
  state->img = i_img_8_new(state->width, state->height, channels);

  state->line_buf = mymalloc(sizeof(i_color) * state->width);

  return 1;
}

static int 
putter_cmyk8(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	       int row_extras) {
  unsigned char *p = state->raster;

  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    int ch;
    i_color *outp = state->line_buf;

    for (i = 0; i < width; ++i) {
      unsigned char c, m, y, k;
      c = p[0];
      m = p[1];
      y = p[2];
      k = 255 - p[3];
      if (state->sample_signed) {
	c ^= 0x80;
	m ^= 0x80;
	y ^= 0x80;
	k ^= 0x80;
      }
      outp->rgba.r = (k * (255 - c)) / 255;
      outp->rgba.g = (k * (255 - m)) / 255;
      outp->rgba.b = (k * (255 - y)) / 255;
      if (state->alpha_chan) {
	outp->rgba.a = p[state->alpha_chan];
	if (state->scale_alpha 
	    && outp->rgba.a) {
	  for (ch = 0; ch < 3; ++ch) {
	    int result = (outp->channel[ch] * 255 + 127) / outp->rgba.a;
	    outp->channel[ch] = CLAMP8(result);
	  }
	}
      }
      p += state->samples_per_pixel;
      outp++;
    }

    i_plin(state->img, x, x + width, y, state->line_buf);

    p += row_extras * state->samples_per_pixel;
    --height;
    ++y;
  }

  return 1;
}

static int
setup_cmyk16(read_state_t *state) {
  int channels;

  cmyk_channels(state, &channels);
  state->img = i_img_16_new(state->width, state->height, channels);

  state->line_buf = mymalloc(sizeof(unsigned) * state->width * channels);

  return 1;
}

static int 
putter_cmyk16(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_dim height, 
	       int row_extras) {
  uint16 *p = state->raster;
  int out_chan = state->img->channels;

  mm_log((4, "putter_cmyk16(%p, %" i_DF ", %" i_DF ", %" i_DF
	  ", %" i_DF ", %d)\n", state, i_DFcp(x, y), i_DFcp(width, height),
	  row_extras));

  state->pixels_read += width * height;
  while (height > 0) {
    i_img_dim i;
    int ch;
    unsigned *outp = state->line_buf;

    for (i = 0; i < width; ++i) {
      unsigned c, m, y, k;
      c = p[0];
      m = p[1];
      y = p[2];
      k = 65535 - p[3];
      if (state->sample_signed) {
	c ^= 0x8000;
	m ^= 0x8000;
	y ^= 0x8000;
	k ^= 0x8000;
      }
      outp[0] = (k * (65535U - c)) / 65535U;
      outp[1] = (k * (65535U - m)) / 65535U;
      outp[2] = (k * (65535U - y)) / 65535U;
      if (state->alpha_chan) {
	outp[3] = p[state->alpha_chan];
	if (state->scale_alpha 
	    && outp[3]) {
	  for (ch = 0; ch < 3; ++ch) {
	    int result = (outp[ch] * 65535 + 32767) / outp[3];
	    outp[3] = CLAMP16(result);
	  }
	}
      }
      p += state->samples_per_pixel;
      outp += out_chan;
    }

    i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16);

    p += row_extras * state->samples_per_pixel;
    --height;
    ++y;
  }

  return 1;
}

/*

  Older versions of tifflib we support don't define this, so define it
  ourselves.

  If you want this detection to do anything useful, use a newer
  release of tifflib.

 */
#if TIFFLIB_VERSION < 20031121

int 
TIFFIsCODECConfigured(uint16 scheme) {
  switch (scheme) {
    /* these schemes are all shipped with tifflib */
 case COMPRESSION_NONE:
 case COMPRESSION_PACKBITS:
 case COMPRESSION_CCITTRLE:
 case COMPRESSION_CCITTRLEW:
 case COMPRESSION_CCITTFAX3:
 case COMPRESSION_CCITTFAX4:
    return 1;

    /* these require external library support */
  default:
 case COMPRESSION_JPEG:
 case COMPRESSION_LZW:
 case COMPRESSION_DEFLATE:
 case COMPRESSION_ADOBE_DEFLATE:
    return 0;
  }
}

#endif

static int 
myTIFFIsCODECConfigured(uint16 scheme) {
#if TIFFLIB_VERSION < 20040724
  if (scheme == COMPRESSION_LZW)
    return 0;
#endif

  return TIFFIsCODECConfigured(scheme);
}

static void
tiffio_context_init(tiffio_context_t *c, io_glue *ig) {
  c->magic = TIFFIO_MAGIC;
  c->ig = ig;
#ifdef USE_EXT_WARN_HANDLER
  c->warn_buffer = NULL;
  c->warn_size = 0;
#endif
}

static void
tiffio_context_final(tiffio_context_t *c) {
  c->magic = TIFFIO_MAGIC;
#ifdef USE_EXT_WARN_HANDLER
  if (c->warn_buffer)
    myfree(c->warn_buffer);
#endif
}

/*
=back

=head1 AUTHOR

Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>

=head1 SEE ALSO

Imager(3)

=cut
*/