]> git.imager.perl.org - imager.git/blobdiff - tiff.c
use $Config{path_sep} instead of working it out on our own
[imager.git] / tiff.c
diff --git a/tiff.c b/tiff.c
index ec6911ce2e3265b9f3555eac340086a1950275e9..23a0e822b8d5a69e0c8caff910f3348449549466 100644 (file)
--- a/tiff.c
+++ b/tiff.c
@@ -1,8 +1,13 @@
 #include "imager.h"
-#include "tiffio.h"
+#include <tiffio.h>
 #include "iolayer.h"
 #include "imageri.h"
 
+/* needed to implement our substitute TIFFIsCODECConfigured */
+#if TIFFLIB_VERSION < 20031121
+static int TIFFIsCODECConfigured(uint16 scheme);
+#endif
+
 /*
 =head1 NAME
 
@@ -35,11 +40,17 @@ Some of these functions are internal.
      ((((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))
+
 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, },
@@ -53,10 +64,105 @@ static struct tag_name text_tag_names[] =
   { "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);
 }
 
@@ -73,6 +179,8 @@ static void warn_handler(char const *module, char const *fmt, va_list 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;
@@ -91,9 +199,8 @@ static void warn_handler(char const *module, char const *fmt, va_list ap) {
 
 static int save_tiff_tags(TIFF *tif, i_img *im);
 
-static void expand_4bit_hl(unsigned char *buf, int count);
-
-static void pack_4bit_hl(unsigned char *buf, int count);
+static void 
+pack_4bit_to(unsigned char *dest, const unsigned char *src, int count);
 
 
 static toff_t sizeproc(thandle_t x) {
@@ -151,39 +258,154 @@ comp_munmap(thandle_t h, tdata_t p, toff_t off) {
   /* do nothing */
 }
 
-static i_img *read_one_tiff(TIFF *tif) {
+static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
   i_img *im;
   uint32 width, height;
-  uint16 channels;
-  uint32* raster = NULL;
+  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;
-  int ch;
+  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, &channels);
+  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, channels));
+  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) {
-    channels = 3;
-    im = i_img_pal_new(width, height, channels, 256);
+    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 {
-    im = i_img_empty_ch(NULL, width, height, channels);
+    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)
@@ -192,6 +414,7 @@ static i_img *read_one_tiff(TIFF *tif) {
   /* general metadata */
   i_tags_addn(&im->tags, "tiff_bitspersample", 0, bits_per_sample);
   i_tags_addn(&im->tags, "tiff_photometric", 0, photometric);
+  TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &compress);
     
   /* resolution tags */
   TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
@@ -242,155 +465,14 @@ static i_img *read_one_tiff(TIFF *tif) {
     i_tags_add(&im->tags, "i_warning", 0, warn_buffer, -1, 0);
     *warn_buffer = '\0';
   }
-  
-  /*   TIFFPrintDirectory(tif, stdout, 0); good for debugging */
-
-  if (photometric == PHOTOMETRIC_PALETTE &&
-      (bits_per_sample == 4 || bits_per_sample == 8)) {
-    uint16 *maps[3];
-    char used[256];
-    int maxused;
-    uint32 row, col;
-    unsigned char *buffer;
-
-    if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
-      i_push_error(0, "Cannot get colormap for paletted image");
-      i_img_destroy(im);
-      return NULL;
-    }
-    buffer = (unsigned char *)_TIFFmalloc(width+2);
-    if (!buffer) {
-      i_push_error(0, "out of memory");
-      i_img_destroy(im);
-      return NULL;
-    }
-    row = 0;
-    memset(used, 0, sizeof(used));
-    while (row < height && TIFFReadScanline(tif, buffer, row, 0) > 0) {
-      if (bits_per_sample == 4)
-        expand_4bit_hl(buffer, (width+1)/2);
-      for (col = 0; col < width; ++col) {
-        used[buffer[col]] = 1;
-      }
-      i_ppal(im, 0, width, row, buffer);
-      ++row;
-    }
-    if (row < height) {
-      error = 1;
-    }
-    /* Ideally we'd optimize the palette, but that could be expensive
-       since we'd have to re-index every pixel.
-
-       Optimizing the palette (even at this level) might not 
-       be what the user wants, so I don't do it.
-
-       We'll add a function to optimize a paletted image instead.
-    */
-    maxused = (1 << bits_per_sample)-1;
-    if (!error) {
-      while (maxused >= 0 && !used[maxused])
-        --maxused;
-    }
-    for (i = 0; i < 1 << bits_per_sample; ++i) {
-      i_color c;
-      for (ch = 0; ch < 3; ++ch) {
-        c.channel[ch] = Sample16To8(maps[ch][i]);
-      }
-      i_addcolors(im, &c, 1);
+
+  for (i = 0; i < compress_value_count; ++i) {
+    if (compress_values[i].tag == compress) {
+      i_tags_add(&im->tags, "tiff_compression", 0, compress_values[i].name, -1, 0);
+      break;
     }
-    _TIFFfree(buffer);
   }
-  else {
-    if (tiled) {
-      int ok = 1;
-      uint32 row, col;
-      uint32 tile_width, tile_height;
-      
-      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");
-        return NULL;
-      }
-      
-      for( row = 0; row < height; row += tile_height ) {
-        for( col = 0; ok && col < width; col += tile_width ) {
-          uint32 i_row, x, newrows, newcols;
-          
-          /* Read the tile into an RGBA array */
-          if (!TIFFReadRGBATile(tif, col, row, raster)) {
-            ok = 0;
-            break;
-          }
-          newrows = (row+tile_height > height) ? height-row : tile_height;
-          mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
-          newcols = (col+tile_width  > width ) ? width-row  : tile_width;
-          for( i_row = 0; i_row < tile_height; i_row++ ) {
-            for(x = 0; x < newcols; x++) {
-              i_color val;
-              uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
-              val.rgba.r = TIFFGetR(temp);
-              val.rgba.g = TIFFGetG(temp);
-              val.rgba.b = TIFFGetB(temp);
-              val.rgba.a = TIFFGetA(temp);
-              i_ppix(im, col+x, row+i_row, &val);
-            }
-          }
-        }
-      }
-    } else {
-      uint32 rowsperstrip, row;
-      int 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;
-      }
-      
-      for( row = 0; row < height; row += rowsperstrip ) {
-        uint32 newrows, i_row;
-        
-        if (!TIFFReadRGBAStrip(tif, row, raster)) {
-          error++;
-          break;
-        }
-        
-        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;
-          for(x = 0; x<width; x++) {
-            i_color val;
-            uint32 temp = raster[x+width*(newrows-i_row-1)];
-            val.rgba.r = TIFFGetR(temp);
-            val.rgba.g = TIFFGetG(temp);
-            val.rgba.b = TIFFGetB(temp);
-            val.rgba.a = TIFFGetA(temp);
-            i_ppix(im, x, i_row+row, &val);
-          }
-        }
-      }
-    }
-  }
-  if (error) {
-    mm_log((1, "i_readtiff_wiol: error during reading\n"));
-    i_tags_addn(&im->tags, "i_incomplete", 0, 1);
-  }
-  if (raster)
-    _TIFFfree( raster );
-
   return im;
 }
 
@@ -400,7 +482,7 @@ static i_img *read_one_tiff(TIFF *tif) {
 =cut
 */
 i_img*
-i_readtiff_wiol(io_glue *ig, int length, int page) {
+i_readtiff_wiol(io_glue *ig, int allow_incomplete, int page) {
   TIFF* tif;
   TIFFErrorHandler old_handler;
   TIFFErrorHandler old_warn_handler;
@@ -416,7 +498,7 @@ i_readtiff_wiol(io_glue *ig, int length, int page) {
   /* Also add code to check for mmapped code */
 
   io_glue_commit_types(ig);
-  mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
+  mm_log((1, "i_readtiff_wiol(ig %p, allow_incomplete %d, page %d)\n", ig, allow_incomplete, page));
   
   tif = TIFFClientOpen("(Iolayer)", 
                       "rm", 
@@ -443,11 +525,12 @@ i_readtiff_wiol(io_glue *ig, int length, int 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);
+  im = read_one_tiff(tif, allow_incomplete);
 
   if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
   TIFFSetErrorHandler(old_handler);
@@ -505,7 +588,7 @@ i_readtiff_multi_wiol(io_glue *ig, int length, int *count) {
 
   *count = 0;
   do {
-    i_img *im = read_one_tiff(tif);
+    i_img *im = read_one_tiff(tif, 0);
     if (!im)
       break;
     if (++*count > result_alloc) {
@@ -635,180 +718,92 @@ i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) {
   return 1;
 }
 
-undef_int
-i_writetiff_low(TIFF *tif, i_img *im) {
-  uint32 width, height;
-  uint16 channels;
-  uint16 predictor = 0;
-  int quality = 75;
-  int jpegcolormode = JPEGCOLORMODE_RGB;
-  uint16 compression = COMPRESSION_PACKBITS;
-  i_color val;
-  uint16 photometric;
-  uint32 rowsperstrip = (uint32) -1;  /* Let library pick default */
-  unsigned char *linebuf = NULL;
-  uint32 y;
-  tsize_t linebytes;
-  int ch, ci, rc;
-  uint32 x;
-  int got_xres, got_yres, aspect_only, resunit;
-  double xres, yres;
-  uint16 bitspersample = 8;
-  uint16 samplesperpixel;
-  uint16 *colors = NULL;
-
-  width    = im->xsize;
-  height   = im->ysize;
-  channels = im->channels;
+static uint16
+find_compression(char const *name, uint16 *compress) {
+  int i;
 
-  switch (channels) {
-  case 1:
-    photometric = PHOTOMETRIC_MINISBLACK;
-    break;
-  case 3:
-    photometric = PHOTOMETRIC_RGB;
-    if (compression == COMPRESSION_JPEG && jpegcolormode == JPEGCOLORMODE_RGB) photometric = PHOTOMETRIC_YCBCR;
-    else if (im->type == i_palette_type) {
-      photometric = PHOTOMETRIC_PALETTE;
+  for (i = 0; i < compress_value_count; ++i) {
+    if (strcmp(compress_values[i].name, name) == 0) {
+      *compress = (uint16)compress_values[i].tag;
+      return 1;
     }
-    break;
-  default:
-    /* This means a colorspace we don't handle yet */
-    mm_log((1, "i_writetiff_wiol: don't handle %d channel images.\n", channels));
-    return 0;
   }
+  *compress = COMPRESSION_NONE;
 
-  /* Add code to get the filename info from the iolayer */
-  /* Also add code to check for mmapped code */
-
-  /*io_glue_commit_types(ig);*/
-  /*mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));*/
+  return 0;
+}
 
-  mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d\n", width, height, channels));
-  
-  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      width)   ) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); 
-    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 (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  ) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); 
-    return 0; 
+  if (i_tags_get_int(&im->tags, "tiff_compression", 0, &value)) {
+    if ((uint16)value == value
+       && myTIFFIsCODECConfigured((uint16)value))
+      return (uint16)value;
   }
-  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT)) {
-    mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); 
-    return 0; 
+
+  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_PLANARCONFIG, PLANARCONFIG_CONTIG)) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); 
-    return 0; 
+  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, im->ysize)) {
+    i_push_error(0, "write TIFF: setting length tag");
+    return 0;
   }
-  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC,   photometric)) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); 
-    return 0; 
+  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) {
+    i_push_error(0, "write TIFF: setting orientation tag");
+    return 0;
   }
-  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION,   compression)) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); 
-    return 0; 
+  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
+    i_push_error(0, "write TIFF: setting planar configuration tag");
+    return 0;
   }
-  samplesperpixel = channels;
-  if (photometric == PHOTOMETRIC_PALETTE) {
-    uint16 *out[3];
-    i_color c;
-    int count = i_colorcount(im);
-    int size;
-    int ch, i;
-    
-    samplesperpixel = 1;
-    if (count > 16)
-      bitspersample = 8;
-    else
-      bitspersample = 4;
-    size = 1 << bitspersample;
-    colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size);
-    out[0] = colors;
-    out[1] = colors + size;
-    out[2] = colors + 2 * size;
-    
-    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_BITSPERSAMPLE, bitspersample)) { 
-      mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n", 
-              bitspersample)); 
-      return 0;
-    }
-    if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) {
-      mm_log((1, "i_writetiff_wiol: TIFFSetField colormap\n")); 
-      return 0;
-    }
+  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) {
+    i_push_error(0, "write TIFF: setting photometric tag");
+    return 0;
   }
-  else {
-    if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitspersample)) { 
-      mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n", 
-              bitspersample)); 
-      return 0;
-    }
+  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compress)) {
+    i_push_error(0, "write TIFF: setting compression tag");
+    return 0;
   }
-  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel)) { 
-    mm_log((1, "i_writetiff_wiol: TIFFSetField samplesperpixel=%d failed\n", samplesperpixel)); 
+  if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) {
+    i_push_error(0, "write TIFF: setting bits per sample tag");
     return 0;
   }
-
-  switch (compression) {
-  case COMPRESSION_JPEG:
-    mm_log((1, "i_writetiff_wiol: jpeg compression\n"));
-    if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality)        ) { 
-      mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality));
-      return 0; 
-    }
-    if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { 
-      mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); 
-      return 0; 
-    }
-    break;
-  case COMPRESSION_LZW:
-    mm_log((1, "i_writetiff_wiol: lzw compression\n"));
-    break;
-  case COMPRESSION_DEFLATE:
-    mm_log((1, "i_writetiff_wiol: deflate compression\n"));
-    if (predictor != 0) 
-      if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { 
-        mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); 
-        return 0; 
-      }
-    break;
-  case COMPRESSION_PACKBITS:
-    mm_log((1, "i_writetiff_wiol: packbits compression\n"));
-    break;
-  default:
-    mm_log((1, "i_writetiff_wiol: unknown compression %d\n", compression));
+  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel)) {
+    i_push_error(0, "write TIFF: setting samples per pixel tag");
     return 0;
   }
-  
-  linebytes = channels * width;
-  linebytes = TIFFScanlineSize(tif) > linebytes ? linebytes 
-    : TIFFScanlineSize(tif);
-  /* working space for the scanlines - we go from 8-bit/pixel to 4 */
-  if (photometric == PHOTOMETRIC_PALETTE && bitspersample == 4)
-    linebytes += linebytes + 1;
-  linebuf = (unsigned char *)_TIFFmalloc(linebytes);
-  
-  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, rowsperstrip))) {
-    mm_log((1, "i_writetiff_wiol: TIFFSetField rowsperstrip=%d\n", rowsperstrip)); return 0; }
-
-  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
-  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
-
-  mm_log((1, "i_writetiff_wiol: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
-  mm_log((1, "i_writetiff_wiol: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
-  mm_log((1, "i_writetiff_wiol: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
-  mm_log((1, "i_writetiff_wiol: bitspersample = %d\n", bitspersample));
 
   got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
   got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
@@ -834,132 +829,501 @@ i_writetiff_low(TIFF *tif, i_img *im) {
       }
     }
     if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
-      i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag");
+      i_push_error(0, "write TIFF: setting xresolution tag");
       return 0;
     }
     if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
-      i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag");
+      i_push_error(0, "write TIFF: setting yresolution tag");
       return 0;
     }
     if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
-      i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag");
+      i_push_error(0, "write TIFF: setting resolutionunit tag");
       return 0;
     }
   }
 
-  if (!save_tiff_tags(tif, im)) {
-    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 (photometric == PHOTOMETRIC_PALETTE) {
-    for (y = 0; y < height; ++y) {
-      i_gpal(im, 0, width, y, linebuf);
-      if (bitspersample == 4)
-        pack_4bit_hl(linebuf, width);
-      if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
-        mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
-        if (linebuf) _TIFFfree(linebuf);
-        if (colors) _TIFFfree(colors);
-        return 0;
-      }
-    }
+  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; 
   }
-  else {
-    for (y=0; y<height; y++) {
-      ci = 0;
-      for(x=0; x<width; x++) { 
-        (void) i_gpix(im, x, y,&val);
-        for(ch=0; ch<channels; ch++) 
-          linebuf[ci++] = val.channel[ch];
+
+  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;
       }
-      if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
-        mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
-        if (linebuf) _TIFFfree(linebuf);
-        if (colors) _TIFFfree(colors);
-        return 0;
+      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;
+    }
   }
-  if (linebuf) _TIFFfree(linebuf);
-  if (colors) _TIFFfree(colors);
-  return 1;
-}
-
-/*
-=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode)
 
-Stores an image in the iolayer object.
+  _TIFFfree(out_row);
+  myfree(in_row);
 
-   ig - io_object that defines source to write to 
-   imgs,count - the images to write
+  return 1;
+}
 
-=cut 
-*/
+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;
+}
 
-undef_int
-i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
-  TIFF* tif;
-  TIFFErrorHandler old_handler;
-  int i;
+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;
 
-  old_handler = TIFFSetErrorHandler(error_handler);
+  mm_log((1, "tiff - write_one_paletted8(tif %p, im %p)\n", tif, im));
 
-  io_glue_commit_types(ig);
-  i_clear_error();
-  mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n", 
-          ig, imgs, count));
+  /* ignore a silly choice */
+  if (compress == COMPRESSION_JPEG ||
+      compress == COMPRESSION_CCITTRLE ||
+      compress == COMPRESSION_CCITTFAX3 ||
+      compress == COMPRESSION_CCITTFAX4)
+    compress = COMPRESSION_PACKBITS;
 
-  /* 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 (!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 (!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);
+  if (!set_palette(tif, im, 256))
     return 0;
-  }
 
-  for (i = 0; i < count; ++i) {
-    if (!i_writetiff_low(tif, imgs[i])) {
-      TIFFClose(tif);
-      TIFFSetErrorHandler(old_handler);
-      return 0;
-    }
+  out_size = TIFFScanlineSize(tif);
+  out_row = (unsigned char *)_TIFFmalloc( out_size );
 
-    if (!TIFFWriteDirectory(tif)) {
-      i_push_error(0, "Cannot write TIFF directory");
-      TIFFClose(tif);
-      TIFFSetErrorHandler(old_handler);
+  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;
     }
   }
 
-  TIFFSetErrorHandler(old_handler);
-  (void) TIFFClose(tif);
+  _TIFFfree(out_row);
 
   return 1;
 }
 
-/*
-=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode)
+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;
 
-Stores an image in the iolayer object.
+  mm_log((1, "tiff - write_one_paletted4(tif %p, im %p)\n", tif, im));
 
-   ig - io_object that defines source to write to 
-   imgs,count - the images to write
-   fine_mode - select fine or normal mode fax images
+  /* 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);
+
+  io_glue_commit_types(ig);
+  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 
 */
@@ -1156,32 +1520,1034 @@ static int save_tiff_tags(TIFF *tif, i_img *im) {
 }
 
 
-/*
-=item expand_4bit_hl(buf, count)
+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;
+  }
+}
 
-Expands 4-bit/entry packed data into 1 byte/entry.
+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;
+  }
+}
 
-buf must contain count bytes to be expanded and have 2*count bytes total 
-space.
+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;
 
-The data is expanded in place.
+  TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &in_channels);
+  TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
 
-=cut
-*/
+  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;
 
-static void expand_4bit_hl(unsigned char *buf, int count) {
-  while (--count >= 0) {
-    buf[count*2+1] = buf[count] & 0xF;
-    buf[count*2] = buf[count] >> 4;
+  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 void pack_4bit_hl(unsigned char *buf, int count) {
-  int i = 0;
-  while (i < count) {
-    buf[i/2] = (buf[i] << 4) + buf[i+1];
-    i += 2;
+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);
 }
 
 /*
@@ -1189,7 +2555,7 @@ static void pack_4bit_hl(unsigned char *buf, int count) {
 
 =head1 AUTHOR
 
-Arnar M. Hrafnkelsson <addi@umich.edu>
+Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tony@imager.perl.org>
 
 =head1 SEE ALSO