#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, int width, int height, int allow_incomplete);
static i_img *read_one_rgb_lines(TIFF *tif, int width, int 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 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, int x, int y, int width, 
			     int 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;
  unsigned long 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;
};

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 *, int, int, int, int, int);
static int paletted_putter4(read_state_t *, int, int, int, int, 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 *, int, int, int, int, 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 *, int, int, int, int, 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 *, int, int, int, int, int);

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

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

static int setup_cmyk16(read_state_t *state);
static int putter_cmyk16(read_state_t *, int, int, int, int, int);

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

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);
}

#define WARN_BUFFER_LIMIT 10000
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 HAVE_SNPRINTF
  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");
  }
}

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

static void 
pack_4bit_to(unsigned char *dest, const unsigned char *src, int 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 = (io_glue*)h;
  return (toff_t) ig->seekcb(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 i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
  i_img *im;
  uint32 width, height;
  uint16 samples_per_pixel;
  int tiled, error;
  float xres, yres;
  uint16 resunit;
  int gotXres, gotYres;
  uint16 photometric;
  uint16 bits_per_sample;
  uint16 planar_config;
  uint16 inkset;
  uint16 compress;
  int i;
  read_state_t state;
  read_setup_t setupf = NULL;
  read_getter_t getterf = NULL;
  read_putter_t putterf = NULL;

  error = 0;

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

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

  /* yes, this if() is horrible */
  if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
    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));
  }
  else if (bits_per_sample == 16 
	   && photometric == PHOTOMETRIC_RGB
	   && samples_per_pixel >= 3) {
    setupf = setup_16_rgb;
    putterf = putter_16;
  }
  else if (bits_per_sample == 16
	   && photometric == PHOTOMETRIC_MINISBLACK) {
    setupf = setup_16_grey;
    putterf = putter_16;
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_MINISBLACK) {
    setupf = setup_8_grey;
    putterf = putter_8;
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_RGB) {
    setupf = setup_8_rgb;
    putterf = putter_8;
  }
  else if (bits_per_sample == 32 
	   && photometric == PHOTOMETRIC_RGB
	   && samples_per_pixel >= 3) {
    setupf = setup_32_rgb;
    putterf = putter_32;
  }
  else if (bits_per_sample == 32
	   && photometric == PHOTOMETRIC_MINISBLACK) {
    setupf = setup_32_grey;
    putterf = putter_32;
  }
  else if (bits_per_sample == 1
	   && (photometric == PHOTOMETRIC_MINISBLACK
	       || photometric == PHOTOMETRIC_MINISWHITE)
	   && samples_per_pixel == 1) {
    setupf = setup_bilevel;
    putterf = putter_bilevel;
  }
  else if (bits_per_sample == 8
	   && photometric == PHOTOMETRIC_SEPARATED
	   && inkset == INKSET_CMYK
	   && samples_per_pixel >= 4) {
    setupf = setup_cmyk8;
    putterf = putter_cmyk8;
  }
  else if (bits_per_sample == 16
	   && photometric == PHOTOMETRIC_SEPARATED
	   && inkset == INKSET_CMYK
	   && samples_per_pixel >= 4) {
    setupf = setup_cmyk16;
    putterf = putter_cmyk16;
  }
  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) {
    unsigned long total_pixels = (unsigned long)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;

    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);
  if (warn_buffer && *warn_buffer) {
    i_tags_set(&im->tags, "i_warning", warn_buffer, -1);
    *warn_buffer = '\0';
  }

  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;
    }
  }
  
  return im;
}

/*
=item i_readtiff_wiol(im, ig)

=cut
*/
i_img*
i_readtiff_wiol(io_glue *ig, int allow_incomplete, int page) {
  TIFF* tif;
  TIFFErrorHandler old_handler;
  TIFFErrorHandler old_warn_handler;
  i_img *im;

  i_clear_error();
  old_handler = TIFFSetErrorHandler(error_handler);
  old_warn_handler = TIFFSetWarningHandler(warn_handler);
  if (warn_buffer)
    *warn_buffer = '\0';

  /* 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));
  
  tif = TIFFClientOpen("(Iolayer)", 
		       "rm", 
		       (thandle_t) ig,
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc) comp_seek,
		       (TIFFCloseProc) ig->closecb,
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc) comp_mmap,
		       (TIFFUnmapFileProc) 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);
    return NULL;
  }

  if (page != 0) {
    if (!TIFFSetDirectory(tif, page)) {
      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);
      TIFFClose(tif);
      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);
  TIFFClose(tif);
  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;
  i_img **results = NULL;
  int result_alloc = 0;
  int dirnum = 0;

  i_clear_error();
  old_handler = TIFFSetErrorHandler(error_handler);
  old_warn_handler = TIFFSetWarningHandler(warn_handler);
  if (warn_buffer)
    *warn_buffer = '\0';

  /* 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, length %d)\n", ig));
  
  tif = TIFFClientOpen("(Iolayer)", 
		       "rm", 
		       (thandle_t) ig,
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc) comp_seek,
		       (TIFFCloseProc) ig->closecb,
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc) comp_mmap,
		       (TIFFUnmapFileProc) 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);
    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 (TIFFSetDirectory(tif, ++dirnum));

  TIFFSetWarningHandler(old_warn_handler);
  TIFFSetErrorHandler(old_handler);
  TIFFClose(tif);
  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;

  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=%d\n", 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;
  int 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;
  int 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;
  unsigned out_size;
  int 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;
  int 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;
  int 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;
  int 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;

  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;

  old_handler = TIFFSetErrorHandler(error_handler);

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

  /* FIXME: Enable the mmap interface */
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) ig, 
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc)      comp_seek,
		       (TIFFCloseProc)     ig->closecb, 
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc)   comp_mmap,
		       (TIFFUnmapFileProc) 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);
    return 0;
  }

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

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

  TIFFSetErrorHandler(old_handler);
  (void) TIFFClose(tif);

  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;

  old_handler = TIFFSetErrorHandler(error_handler);

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

  /* FIXME: Enable the mmap interface */
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) ig, 
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc)      comp_seek,
		       (TIFFCloseProc)     ig->closecb, 
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc)   comp_mmap,
		       (TIFFUnmapFileProc) 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);
    return 0;
  }

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

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

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);

  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;

  old_handler = TIFFSetErrorHandler(error_handler);

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

  /* FIXME: Enable the mmap interface */

  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) ig, 
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc)      comp_seek,
		       (TIFFCloseProc)     ig->closecb, 
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc)   comp_mmap,
		       (TIFFUnmapFileProc) 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);
    return 0;
  }

  if (!i_writetiff_low(tif, img)) {
    TIFFClose(tif);
    TIFFSetErrorHandler(old_handler);
    return 0;
  }

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);

  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;

  old_handler = TIFFSetErrorHandler(error_handler);

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

  /* FIXME: Enable the mmap interface */
  
  tif = TIFFClientOpen("No name", 
		       "wm",
		       (thandle_t) ig, 
		       (TIFFReadWriteProc) ig->readcb,
		       (TIFFReadWriteProc) ig->writecb,
		       (TIFFSeekProc)      comp_seek,
		       (TIFFCloseProc)     ig->closecb, 
		       ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
		       (TIFFMapFileProc)   comp_mmap,
		       (TIFFUnmapFileProc) 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);
    return 0;
  }

  if (!i_writetiff_low_faxable(tif, im, fine)) {
    TIFFClose(tif);
    TIFFSetErrorHandler(old_handler);
    return 0;
  }

  (void) TIFFClose(tif);
  TIFFSetErrorHandler(old_handler);

  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, 
	       int 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, 
			 int pixel_count) {
  int i = 0;
  while (i < pixel_count) {
    if ((i & 1) == 0) {
      *dest = *src++ << 4;
    }
    else {
      *dest++ |= *src++;
    }
    ++i;
  }
}

static i_img *
make_rgb(TIFF *tif, int width, int height, int *alpha_chan) {
  uint16 photometric;
  uint16 channels, 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++;
  }

  return i_img_8_new(width, height, channels);
}

static i_img *
read_one_rgb_lines(TIFF *tif, int width, int 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, int width, int 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, int x, int y, int width, int height, int extras) {
  unsigned char *p = state->raster;

  state->pixels_read += (unsigned long) 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, int x, int y, int width, int 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 += (unsigned long) 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;

  /* 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;

  /* 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, int x, int y, int width, int height, 
	  int row_extras) {
  uint16 *p = state->raster;
  int out_chan = state->img->channels;

  state->pixels_read += (unsigned long) width * height;
  while (height > 0) {
    int 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->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, int x, int y, int width, int height, 
	  int row_extras) {
  unsigned char *p = state->raster;
  int out_chan = state->img->channels;

  state->pixels_read += (unsigned long) width * height;
  while (height > 0) {
    int 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->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, int x, int y, int width, int height, 
	  int row_extras) {
  uint32 *p = state->raster;
  int out_chan = state->img->channels;

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

    for (i = 0; i < width; ++i) {
      for (ch = 0; ch < out_chan; ++ch) {
	outp->channel[ch] = p[ch] / 4294967295.0;
      }
      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, int x, int y, int width, int 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 += (unsigned long) width * height;
  while (height > 0) {
    int 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;

  /* 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, int x, int y, int width, int height, 
	       int row_extras) {
  unsigned char *p = state->raster;

  state->pixels_read += (unsigned long) width * height;
  while (height > 0) {
    int 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];
      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, int x, int y, int width, int height, 
	       int row_extras) {
  uint16 *p = state->raster;
  int out_chan = state->img->channels;

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

  state->pixels_read += (unsigned long) width * height;
  while (height > 0) {
    int 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];
      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);
}

/*
=back

=head1 AUTHOR

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

=head1 SEE ALSO

Imager(3)

=cut
*/
