merge in tiff re-work branch
authorTony Cook <tony@develop=help.com>
Mon, 26 Nov 2007 10:30:01 +0000 (10:30 +0000)
committerTony Cook <tony@develop=help.com>
Mon, 26 Nov 2007 10:30:01 +0000 (10:30 +0000)
50 files changed:
Changes
Imager.pm
Imager.xs
MANIFEST
README
TODO
apidocs.perl
conv.c [deleted file]
conv.im [new file with mode: 0644]
gaussian.im
image.c
imager.h
imageri.h
imdatatypes.h
imext.c
imext.h
imexttypes.h
img16.c
imgdouble.c
lib/Imager/APIRef.pod
lib/Imager/Draw.pod
lib/Imager/Files.pod
lib/Imager/Handy.pod
lib/Imager/ImageTypes.pod
lib/Imager/Test.pm
log.c
log.h
palimg.c
t/t01introvert.t
t/t021sixteen.t
t/t022double.t
t/t023palette.t
t/t106tiff.t
t/t61filters.t
t/testtools.pl
testimg/alpha.tif [new file with mode: 0644]
testimg/comp4t.tif [new file with mode: 0644]
testimg/gralpha.tif [new file with mode: 0644]
testimg/grey16.tif [new file with mode: 0755]
testimg/grey32.tif [new file with mode: 0644]
testimg/imager.pbm [new file with mode: 0644]
testimg/imager.tif [new file with mode: 0644]
testimg/pengtile.tif [new file with mode: 0644]
testimg/rgb16.tif [new file with mode: 0644]
testimg/rgb16t.tif [new file with mode: 0644]
testimg/scmyka16.tif [new file with mode: 0644]
testimg/srgba16.tif [new file with mode: 0644]
testimg/srgba32.tif [new file with mode: 0644]
testimg/srgbaa.tif
tiff.c

diff --git a/Changes b/Changes
index 70aafab..2bd6672 100644 (file)
--- a/Changes
+++ b/Changes
@@ -3,6 +3,24 @@ Imager release history.  Older releases can be found in Changes.old
 Imager 0.62 - not yet released
 ===========
 
+ - major TIFF support re-work
+   http://rt.cpan.org/Ticket/Display.html?id=20329
+
+ - added a C level image interface for accessing samples from 1-32
+   bits, exposed this at the perl level in getsamples()
+
+ - the conv filter now works at floating point precision for high bit
+   images
+
+ - added is_bilevel method to test whether an image should be written as
+   a bilevel image if the image format supports it.
+
+ - added -log-stderr as an Imager import list option
+
+ - added some important types to Imager::APIRef
+
+ - added test_image_double() to Imager::Test
+
 Bug fixes:
 
  - Imager::Fountain couldn't read GIMP gradient files with 10 or more
index eac764e..df53072 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -117,6 +117,7 @@ use Imager::Font;
                newcolour
                NC
                NF
+                NCF
 );
 
 @EXPORT=qw(
@@ -136,6 +137,7 @@ use Imager::Font;
                newcolor
                NF
                NC
+                NCF
               )],
    all => [@EXPORT_OK],
    default => [qw(
@@ -426,13 +428,19 @@ BEGIN {
 # initlize Imager
 # NOTE: this might be moved to an import override later on
 
-#sub import {
-#  my $pack = shift;
-#  (look through @_ for special tags, process, and remove them);   
-#  use Data::Dumper;
-#  print Dumper($pack);
-#  print Dumper(@_);
-#}
+sub import {
+  my $i = 1;
+  while ($i < @_) {
+    if ($_[$i] eq '-log-stderr') {
+      init_log(undef, 4);
+      splice(@_, $i, 1);
+    }
+    else {
+      ++$i;
+    }
+  }
+  goto &Exporter::import;
+}
 
 sub init_log {
   i_init_log($_[0],$_[1]);
@@ -1025,6 +1033,14 @@ sub virtual {
   $self->{IMG} and i_img_virtual($self->{IMG});
 }
 
+sub is_bilevel {
+  my ($self) = @_;
+
+  $self->{IMG} or return;
+
+  return i_img_is_monochrome($self->{IMG});
+}
+
 sub tags {
   my ($self, %opts) = @_;
 
@@ -3041,7 +3057,7 @@ sub setscanline {
 
 sub getsamples {
   my $self = shift;
-  my %opts = ( type => '8bit', x=>0, @_);
+  my %opts = ( type => '8bit', x=>0, offset => 0, @_);
 
   defined $opts{width} or $opts{width} = $self->getwidth - $opts{x};
 
@@ -3054,18 +3070,103 @@ sub getsamples {
     $opts{channels} = [ 0 .. $self->getchannels()-1 ];
   }
 
-  if ($opts{type} eq '8bit') {
-    return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
-                  $opts{y}, @{$opts{channels}});
-  }
-  elsif ($opts{type} eq 'float') {
-    return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
-                   $opts{y}, @{$opts{channels}});
+  if ($opts{target}) {
+    my $target = $opts{target};
+    my $offset = $opts{offset};
+    if ($opts{type} eq '8bit') {
+      my @samples = i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
+                           $opts{y}, @{$opts{channels}})
+       or return;
+      @{$target}{$offset .. $offset + @samples - 1} = @samples;
+      return scalar(@samples);
+    }
+    elsif ($opts{type} eq 'float') {
+      my @samples = i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
+                            $opts{y}, @{$opts{channels}});
+      @{$target}{$offset .. $offset + @samples - 1} = @samples;
+      return scalar(@samples);
+    }
+    elsif ($opts{type} =~ /^(\d+)bit$/) {
+      my $bits = $1;
+
+      my @data;
+      my $count = i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, 
+                              $opts{y}, $bits, $target, 
+                              $offset, @{$opts{channels}});
+      unless (defined $count) {
+       $self->_set_error(Imager->_error_as_msg);
+       return;
+      }
+
+      return $count;
+    }
+    else {
+      $self->_set_error("invalid type parameter - must be '8bit' or 'float'");
+      return;
+    }
   }
   else {
-    $self->_set_error("invalid type parameter - must be '8bit' or 'float'");
+    if ($opts{type} eq '8bit') {
+      return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
+                    $opts{y}, @{$opts{channels}});
+    }
+    elsif ($opts{type} eq 'float') {
+      return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width},
+                     $opts{y}, @{$opts{channels}});
+    }
+    elsif ($opts{type} =~ /^(\d+)bit$/) {
+      my $bits = $1;
+
+      my @data;
+      i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, 
+                  $opts{y}, $bits, \@data, 0, @{$opts{channels}})
+       or return;
+      return @data;
+    }
+    else {
+      $self->_set_error("invalid type parameter - must be '8bit' or 'float'");
+      return;
+    }
+  }
+}
+
+sub setsamples {
+  my $self = shift;
+  my %opts = ( x => 0, offset => 0, @_ );
+
+  unless ($self->{IMG}) {
+    $self->_set_error('setsamples: empty input image');
+    return;
+  }
+
+  unless(defined $opts{data} && ref $opts{data}) {
+    $self->_set_error('setsamples: data parameter missing or invalid');
     return;
   }
+
+  unless ($opts{channels}) {
+    $opts{channels} = [ 0 .. $self->getchannels()-1 ];
+  }
+
+  unless ($opts{type} && $opts{type} =~ /^(\d+)bit$/) {
+    $self->_set_error('setsamples: type parameter missing or invalid');
+    return;
+  }
+  my $bits = $1;
+
+  unless (defined $opts{width}) {
+    $opts{width} = $self->getwidth() - $opts{x};
+  }
+
+  my $count = i_psamp_bits($self->{IMG}, $opts{x}, $opts{y}, $bits,
+                          $opts{channels}, $opts{data}, $opts{offset}, 
+                          $opts{width});
+  unless (defined $count) {
+    $self->_set_error(Imager->_error_as_msg);
+    return;
+  }
+
+  return $count;
 }
 
 # make an identity matrix of the given size
@@ -3446,6 +3547,7 @@ sub get_file_limits {
 
 sub newcolor { Imager::Color->new(@_); }
 sub newfont  { Imager::Font->new(@_); }
+sub NCF { Imager::Color::Float->new(@_) }
 
 *NC=*newcolour=*newcolor;
 *NF=*newfont;
@@ -3840,6 +3942,8 @@ img_set() - L<Imager::ImageTypes/img_set>
 
 init() - L<Imager::ImageTypes/init>
 
+is_bilevel - L<Imager::ImageTypes/is_bilevel>
+
 line() - L<Imager::Draw/line>
 
 load_plugin() - L<Imager::Filters/load_plugin>
@@ -3855,6 +3959,8 @@ maxcolors() - L<Imager::ImageTypes/maxcolors>
 
 NC() - L<Imager::Handy/NC>
 
+NCF() - L<Imager::Handy/NCF>
+
 new() - L<Imager::ImageTypes/new>
 
 newcolor() - L<Imager::Handy/newcolor>
@@ -3910,6 +4016,8 @@ setmask() - L<Imager::ImageTypes/setmask>
 
 setpixel() - L<Imager::Draw/setpixel>
 
+setsamples() - L<Imager::Draw/setsamples>
+
 setscanline() - L<Imager::Draw/setscanline>
 
 settag() - L<Imager::ImageTypes/settag>
index 963277a..92cf751 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1287,9 +1287,13 @@ i_sametype_chans(im, x, y, channels)
                int channels
 
 void
-i_init_log(name,level)
-             char*    name
+i_init_log(name_sv,level)
+             SV*    name_sv
               int     level
+       PREINIT:
+         const char *name = SvOK(name_sv) ? SvPV_nolen(name_sv) : NULL;
+       CODE:
+         i_init_log(name, level);
 
 void
 i_log_entry(string,level)
@@ -1343,6 +1347,25 @@ i_img_getdata(im)
                     sv_2mortal(newSVpv((char *)im->idata, im->bytes)) 
                     : &PL_sv_undef);
 
+void
+i_img_is_monochrome(im)
+       Imager::ImgRaw im
+      PREINIT:
+       int zero_is_white;
+       int result;
+      PPCODE:
+       result = i_img_is_monochrome(im, &zero_is_white);
+       if (result) {
+         if (GIMME_V == G_ARRAY) {
+           EXTEND(SP, 2);
+           PUSHs(&PL_sv_yes);
+           PUSHs(sv_2mortal(newSViv(zero_is_white)));
+         }
+         else {
+           EXTEND(SP, 1);
+           PUSHs(&PL_sv_yes);
+         }
+       }
 
 void
 i_line(im,x1,y1,x2,y2,val,endp)
@@ -2368,6 +2391,12 @@ i_writetiff_multi_wiol_faxable(ig, fine, ...)
       OUTPUT:
         RETVAL
 
+const char *
+i_tiff_libversion()
+
+bool
+i_tiff_has_compression(name)
+       const char *name
 
 #endif /* HAVE_LIBTIFF */
 
@@ -3772,6 +3801,108 @@ i_gsamp(im, l, r, y, ...)
           }
         }
 
+undef_neg_int
+i_gsamp_bits(im, l, r, y, bits, target, offset, ...)
+        Imager::ImgRaw im
+        int l
+        int r
+        int y
+       int bits
+       AV *target
+       int offset
+      PREINIT:
+        int *chans;
+        int chan_count;
+        unsigned *data;
+        int count, i;
+      CODE:
+       i_clear_error();
+        if (items < 8)
+          croak("No channel numbers supplied to g_samp()");
+        if (l < r) {
+          chan_count = items - 7;
+          chans = mymalloc(sizeof(int) * chan_count);
+          for (i = 0; i < chan_count; ++i)
+            chans[i] = SvIV(ST(i+7));
+          data = mymalloc(sizeof(unsigned) * (r-l) * chan_count);
+          count = i_gsamp_bits(im, l, r, y, data, chans, chan_count, bits);
+         myfree(chans);
+         for (i = 0; i < count; ++i) {
+           av_store(target, i+offset, newSVuv(data[i]));
+         }
+         myfree(data);
+         RETVAL = count;
+        }
+        else {
+         RETVAL = 0;
+        }
+      OUTPUT:
+       RETVAL
+
+undef_neg_int
+i_psamp_bits(im, l, y, bits, channels_sv, data_av, data_offset = 0, pixel_count = -1)
+        Imager::ImgRaw im
+        int l
+        int y
+       int bits
+       SV *channels_sv
+       AV *data_av
+        int data_offset
+        int pixel_count
+      PREINIT:
+       int chan_count;
+       int *channels;
+       int data_count;
+       int data_used;
+       unsigned *data;
+       int i;
+      CODE:
+       i_clear_error();
+       if (SvOK(channels_sv)) {
+         AV *channels_av;
+         if (!SvROK(channels_sv) || SvTYPE(SvRV(channels_sv)) != SVt_PVAV) {
+           croak("channels is not an array ref");
+         }
+         channels_av = (AV *)SvRV(channels_sv);
+         chan_count = av_len(channels_av) + 1;
+         if (chan_count < 1) {
+           croak("i_psamp_bits: no channels provided");
+         }
+         channels = mymalloc(sizeof(int) * chan_count);
+         for (i = 0; i < chan_count; ++i)
+           channels[i] = SvIV(*av_fetch(channels_av, i, 0));
+        }
+       else {
+         chan_count = im->channels;
+         channels = NULL;
+       }
+
+       data_count = av_len(data_av) + 1;
+       if (data_offset < 0) {
+         croak("data_offset must by non-negative");
+       }
+       if (data_offset > data_count) {
+         croak("data_offset greater than number of samples supplied");
+        }
+       if (pixel_count == -1 || 
+           data_offset + pixel_count * chan_count > data_count) {
+         pixel_count = (data_count - data_offset) / chan_count;
+       }
+
+       data_used = pixel_count * chan_count;
+       data = mymalloc(sizeof(unsigned) * data_count);
+       for (i = 0; i < data_used; ++i)
+         data[i] = SvUV(*av_fetch(data_av, data_offset + i, 0));
+
+       RETVAL = i_psamp_bits(im, l, l + pixel_count, y, data, channels, 
+                             chan_count, bits);
+
+       if (data)
+         myfree(data);
+       if (channels)
+         myfree(channels);
+      OUTPUT:
+       RETVAL
 
 Imager::ImgRaw
 i_img_masked_new(targ, mask, x, y, w, h)
index b1e78c7..b4d6dfd 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -75,7 +75,7 @@ apidocs.perl    Build lib/Imager/APIRef.pm
 bigtest.perl   Library selection tester
 bmp.c           Reading and writing Windows BMP files
 color.c         Color translation and handling
-conv.c
+conv.im
 convert.c
 doco.perl
 datatypes.c
@@ -274,6 +274,7 @@ t/tr18561.t         Regression tests
 t/tr18561b.t
 tags.c
 testimg/209_yonge.jpg  Regression test: #17981
+testimg/alpha.tif      Alpha scaling test image
 testimg/bad1oflow.bmp   1-bit/pixel, overflow integer on 32-bit machines
 testimg/bad1wid0.bmp    1-bit/pixel, zero width
 testimg/bad24comp.bmp   24-bit/pixel, bad compression
@@ -302,11 +303,17 @@ testimg/base.jpg  Base JPEG test image
 testimg/comp4.bmp       Compressed 4-bit/pixel BMP
 testimg/comp4.tif       4-bit/pixel paletted TIFF
 testimg/comp4bad.tif    corrupted 4-bit/pixel paletted TIFF
+testimg/comp4t.tif      4-bit/pixel paletted TIFF (tiled)
 testimg/comp8.bmp       Compressed 8-bit/pixel BMP
 testimg/comp8.tif       8-bit/pixel paletted TIFF
 testimg/exiftest.jpg   Test image for EXIF parsing
 testimg/expected.gif
 testimg/gimpgrad        A GIMP gradient file
+testimg/gralpha.tif    Grey alpha test image
+testimg/grey16.tif     16-bit/sample greyscale TIFF
+testimg/grey32.tif     32-bit/sample greyscale+alpha TIFF
+testimg/imager.pbm     Test bi-level
+testimg/imager.tif     Test bi-level
 testimg/junk.ppm
 testimg/loccmap.gif
 testimg/longid.tga      Test TGA with a long id string
@@ -321,13 +328,17 @@ testimg/nocmap.gif
 testimg/palette.png
 testimg/palette_out.png
 testimg/penguin-base.ppm
+testimg/pengtile.tif   Tiled tiff image, same as penguin-base.ppm
 testimg/pgm.pgm                Simple pgm for testing the right sample is in the right place
+testimg/rgb16.tif      16-bit/sample RGB image - strips
+testimg/rgb16t.tif     16-bit/sample RGB image - tiled
 testimg/scale.gif
 testimg/scale.ppm
 testimg/scalei.gif
 testimg/scmyk.tif      Simple CMYK TIFF image
 testimg/scmyk.jpg      Simple CMYK JPEG image
 testimg/scmyka.tif     CMYK with one alpha channel
+testimg/scmyka16.tif   CMYK with one alpha channel (16-bit)
 testimg/scmykaa.tif    CMYK with 2 alpha channels
 testimg/screen2.gif
 testimg/screen3.gif
@@ -349,6 +360,8 @@ testimg/simple.pbm
 testimg/slab.tif       Lab color image
 testimg/srgb.tif       Simple RGB image
 testimg/srgba.tif      RGB with one alpha
+testimg/srgba16.tif
+testimg/srgba32.tif
 testimg/srgbaa.tif     RGB with 2 alpha
 testimg/test_gimp_pal   A simple GIMP palette file
 testimg/tiffwarn.tif   Generates a warning while being read
diff --git a/README b/README
index f11a287..602cf1d 100644 (file)
--- a/README
+++ b/README
@@ -110,10 +110,11 @@ Stock libungif 4.1.4 or later seems to fix all of the bugs, if you
 have a problem that version of linungif (or later), let us know and
 we'll look into it.
 
-Imager needs to have a libtiff version of at least 3.5.5.  In the
-future we might consider supporting older libtiff versions.  For
-now you can either configure Imager manually (by
-setting the IM_MANUAL environment variable to 1, in sh:
+Imager needs to have a libtiff version of at least 3.5.5, but you
+should use a later version since some noticable bugs have been fixed.
+
+For now you can either configure Imager manually (by setting the
+IM_MANUAL environment variable to 1, in sh:
 
 $ IM_MANUAL=1 perl Makefile.PL
 
diff --git a/TODO b/TODO
index 102ddb6..2afd254 100644 (file)
--- a/TODO
+++ b/TODO
@@ -28,7 +28,7 @@ general alpha channel fixes (#29879, etc)
 
 For 0.62:
 
-TIFF improvements (to be detailed) (#20329)
+TIFF improvements (to be detailed) (#20329) (done)
  - read/write 16-bit RGB w/ and w/o alpha
  - read 16-bit CMYK w/ and w/o alpha
  - read/write 32-bit RGB w/ and w/o alpha
index 2d36e31..dab9dbd 100644 (file)
@@ -11,7 +11,7 @@ my %funcs = map { $_ => 1 } @funcs;
 # look for files to parse
 
 my $mani = maniread;
-my @files = grep /\.(c|im)$/, keys %$mani;
+my @files = grep /\.(c|im|h)$/, keys %$mani;
 
 # scan each file for =item <func>\b
 my $func;
@@ -162,7 +162,7 @@ close OUT;
 
 
 sub make_func_list {
-  my $funcs;
+  my @funcs = qw(i_img i_color i_fcolor i_fill_t mm_log);
   open FUNCS, "< imexttypes.h"
     or die "Cannot open imexttypes.h: $!\n";
   my $in_struct;
diff --git a/conv.c b/conv.c
deleted file mode 100644 (file)
index 033fb52..0000000
--- a/conv.c
+++ /dev/null
@@ -1,80 +0,0 @@
-#include "imager.h"
-
-/*
-  General convolution for 2d decoupled filters
-  end effects are acounted for by increasing
-  scaling the result with the sum of used coefficients
-
-  coeff: (float array) coefficients for filter
-    len: length of filter.. number of coefficients
-           note that this has to be an odd number
-           (since the filter is even);
-*/
-
-void
-i_conv(i_img *im,const float *coeff,int len) {
-  int i,l,c,ch,center;
-  float pc;
-  i_color rcolor;
-  float res[11];
-  i_img timg;
-
-  mm_log((1,"i_conv(im %p, coeff %p, len %d)\n",im,coeff,len));
-  i_img_empty_ch(&timg,im->xsize,im->ysize,im->channels);
-
-  center=(len-1)/2;
-
-  /* don't move the calculation of pc up here, it depends on which pixels
-     are readable */
-  for(l=0;l<im->ysize;l++) {
-    for(i=0;i<im->xsize;i++) {
-      pc=0.0;
-      for(ch=0;ch<im->channels;ch++) res[ch]=0;
-      for(c=0;c<len;c++)
-       if (i_gpix(im,i+c-center,l,&rcolor)!=-1) {
-         for(ch=0;ch<im->channels;ch++) 
-            res[ch]+=(float)(rcolor.channel[ch])*coeff[c];
-         pc+=coeff[c];
-       }
-      for(ch=0;ch<im->channels;ch++) {
-        double temp = res[ch]/pc;
-        rcolor.channel[ch] = 
-          temp < 0 ? 0 : temp > 255 ? 255 : (unsigned char)temp;
-      }
-      i_ppix(&timg,i,l,&rcolor);
-    }
-  }
-
-  for(l=0;l<im->xsize;l++)
-    {
-      for(i=0;i<im->ysize;i++)
-       {
-         pc=0.0;
-         for(ch=0;ch<im->channels;ch++) res[ch]=0;
-         for(c=0;c<len;c++)
-           if (i_gpix(&timg,l,i+c-center,&rcolor)!=-1)
-             {
-               for(ch=0;ch<im->channels;ch++) 
-                  res[ch]+=(float)(rcolor.channel[ch])*coeff[c];
-               pc+=coeff[c];
-             }
-         for(ch=0;ch<im->channels;ch++) {
-            double temp = res[ch]/pc;
-            rcolor.channel[ch]= 
-              temp < 0 ? 0 : temp > 255 ? 255 : (unsigned char)temp;
-          }
-         i_ppix(im,l,i,&rcolor);
-       }
-    }
-  i_img_exorcise(&timg);
-}
-
-
-
-
-
-
-
-
-
diff --git a/conv.im b/conv.im
new file mode 100644 (file)
index 0000000..986bbae
--- /dev/null
+++ b/conv.im
@@ -0,0 +1,82 @@
+#include "imager.h"
+
+/*
+  General convolution for 2d decoupled filters
+  end effects are acounted for by increasing
+  scaling the result with the sum of used coefficients
+
+  coeff: (float array) coefficients for filter
+    len: length of filter.. number of coefficients
+           note that this has to be an odd number
+           (since the filter is even);
+*/
+
+void
+i_conv(i_img *im,const float *coeff,int len) {
+  int i,l,c,ch,center;
+  double pc;
+  double res[11];
+  i_img *timg;
+
+  mm_log((1,"i_conv(im %p, coeff %p, len %d)\n",im,coeff,len));
+  timg = i_sametype(im, im->xsize, im->ysize);
+
+  center=(len-1)/2;
+
+#code im->bits <= 8
+  IM_COLOR rcolor;
+  /* don't move the calculation of pc up here, it depends on which pixels
+     are readable */
+  for(l=0;l<im->ysize;l++) {
+    for(i=0;i<im->xsize;i++) {
+      pc=0.0;
+      for(ch=0;ch<im->channels;ch++) 
+       res[ch]=0;
+      for(c=0;c<len;c++)
+       if (IM_GPIX(im,i+c-center,l,&rcolor)!=-1) {
+         for(ch=0;ch<im->channels;ch++) 
+            res[ch] += (rcolor.channel[ch])*coeff[c];
+         pc+=coeff[c];
+       }
+      for(ch=0;ch<im->channels;ch++) {
+        double temp = res[ch]/pc;
+        rcolor.channel[ch] = 
+          temp < 0 ? 0 : temp > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : (IM_SAMPLE_T)temp;
+      }
+      IM_PPIX(timg,i,l,&rcolor);
+    }
+  }
+
+  for(l=0;l<im->xsize;l++) {
+    for(i=0;i<im->ysize;i++) {
+      pc=0.0;  
+      for(ch=0;ch<im->channels;ch++) res[ch]=0;
+      for(c=0;c<len;c++) {
+       if (IM_GPIX(timg,l,i+c-center,&rcolor)!=-1) {
+         for(ch=0;ch<im->channels;ch++) 
+           res[ch] += (rcolor.channel[ch])*coeff[c];
+         pc+=coeff[c];
+       }
+      }
+      for(ch=0;ch<im->channels;ch++) {
+       double temp = res[ch]/pc;
+       rcolor.channel[ch]= 
+         temp < 0 ? 0 : temp > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : (IM_SAMPLE_T)temp;
+      }
+      IM_PPIX(im,l,i,&rcolor);
+    }
+  }
+#/code
+
+  i_img_destroy(timg);
+}
+
+
+
+
+
+
+
+
+
index 34fe813..8489273 100644 (file)
@@ -72,7 +72,7 @@ i_gaussian(i_img *im, double stddev) {
        }
       for(ch=0;ch<im->channels;ch++) {
        double value = res[ch] / pc;
-       rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : value;
+       rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : IM_ROUND(value);
       }
       IM_PPIX(timg,i,l,&rcolor);
     }
@@ -91,7 +91,7 @@ i_gaussian(i_img *im, double stddev) {
        }
       for(ch=0;ch<im->channels;ch++) {
        double value = res[ch]/pc;
-       rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : value;
+       rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : IM_ROUND(value);
       }
       IM_PPIX(im,l,i,&rcolor);
     }
diff --git a/image.c b/image.c
index 85cbb2b..13b73d5 100644 (file)
--- a/image.c
+++ b/image.c
@@ -49,8 +49,58 @@ static int i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals);
 static int i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals);
 static int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, const int *chans, int chan_count);
 static int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, const int *chans, int chan_count);
-/*static int i_psamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count);
-  static int i_psampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count);*/
+
+/*
+=item i_img_alloc()
+=category Image Implementation
+
+Allocates a new i_img structure.
+
+When implementing a new image type perform the following steps in your
+image object creation function:
+
+=over
+
+=item 1.
+
+allocate the image with i_img_alloc().
+
+=item 2.
+
+initialize any function pointers or other data as needed, you can
+overwrite the whole block if you need to.
+
+=item 3.
+
+initialize Imager's internal data by calling i_img_init() on the image
+object.
+
+=back
+
+=cut
+*/
+
+i_img *
+i_img_alloc(void) {
+  return mymalloc(sizeof(i_img));
+}
+
+/*
+=item i_img_init(img)
+=category Image Implementation
+
+Imager interal initialization of images.
+
+Currently this does very little, in the future it may be used to
+support threads, or color profiles.
+
+=cut
+*/
+
+void
+i_img_init(i_img *img) {
+  img->im_data = NULL;
+}
 
 /* 
 =item ICL_new_internal(r, g, b, a)
@@ -232,6 +282,9 @@ static i_img IIM_base_8bit_direct =
   NULL, /* i_f_setcolors */
 
   NULL, /* i_f_destroy */
+
+  i_gsamp_bits_fb,
+  NULL, /* i_f_psamp_bits */
 };
 
 /*static void set_8bit_direct(i_img *im) {
@@ -301,8 +354,8 @@ i_img_new() {
   i_img *im;
   
   mm_log((1,"i_img_struct()\n"));
-  if ( (im=mymalloc(sizeof(i_img))) == NULL)
-    i_fatal(2,"malloc() error\n");
+
+  im = i_img_alloc();
   
   *im = IIM_base_8bit_direct;
   im->xsize=0;
@@ -311,6 +364,8 @@ i_img_new() {
   im->ch_mask=MAXINT;
   im->bytes=0;
   im->idata=NULL;
+
+  i_img_init(im);
   
   mm_log((1,"(%p) <- i_img_struct\n",im));
   return im;
@@ -373,8 +428,7 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   }
 
   if (im == NULL)
-    if ( (im=mymalloc(sizeof(i_img))) == NULL)
-      i_fatal(2,"malloc() error\n");
+    im = i_img_alloc();
 
   memcpy(im, &IIM_base_8bit_direct, sizeof(i_img));
   i_tags_new(&im->tags);
@@ -388,6 +442,8 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   memset(im->idata,0,(size_t)im->bytes);
   
   im->ext_data = NULL;
+
+  i_img_init(im);
   
   mm_log((1,"(%p) <- i_img_empty_ch\n",im));
   return im;
@@ -705,20 +761,8 @@ i_copy(i_img *src) {
     }
   }
   else {
-    i_color temp;
-    int index;
-    int count;
     i_palidx *vals;
 
-    /* paletted image */
-    i_img_pal_new_low(im, x1, y1, src->channels, i_maxcolors(src));
-    /* copy across the palette */
-    count = i_colorcount(src);
-    for (index = 0; index < count; ++index) {
-      i_getcolors(src, index, &temp, 1);
-      i_addcolors(im, &temp, 1);
-    }
-
     vals = mymalloc(sizeof(i_palidx) * x1);
     for (y = 0; y < y1; ++y) {
       i_gpal(src, 0, x1, y, vals);
@@ -1985,6 +2029,80 @@ int i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry) {
 /*
 =back
 
+=head2 Fallback handler
+
+=over
+
+=item i_gsamp_bits_fb
+
+=cut
+*/
+
+int 
+i_gsamp_bits_fb(i_img *im, int l, int r, int y, unsigned *samps, 
+               const int *chans, int chan_count, int bits) {
+  if (bits < 1 || bits > 32) {
+    i_push_error(0, "Invalid bits, must be 1..32");
+    return -1;
+  }
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    double scale;
+    int ch, count, i, w;
+    
+    if (bits == 32)
+      scale = 4294967295.0;
+    else
+      scale = (double)(1 << bits) - 1;
+
+    if (r > im->xsize)
+      r = im->xsize;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return -1;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+       i_fcolor c;
+       i_gpixf(im, l+i, y, &c);
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = (unsigned)(c.channel[ch] * scale + 0.5);
+          ++count;
+        }
+      }
+    }
+    else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_error(0, "Invalid channel count");
+       return -1;
+      }
+      for (i = 0; i < w; ++i) {
+       i_fcolor c;
+       i_gpixf(im, l+i, y, &c);
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = (unsigned)(c.channel[ch] * scale + 0.5);
+          ++count;
+        }
+      }
+    }
+
+    return count;
+  }
+  else {
+    i_push_error(0, "Image position outside of image");
+    return -1;
+  }
+}
+
+/*
+=back
+
 =head2 Stream reading and writing wrapper functions
 
 =over
@@ -2353,7 +2471,7 @@ i_img_is_monochrome(i_img *im, int *zero_is_white) {
           colors[1].rgb.r == 0 &&
           colors[1].rgb.g == 0 &&
           colors[1].rgb.b == 0) {
-        *zero_is_white = 0;
+        *zero_is_white = 1;
         return 1;
       }
       else if (colors[0].rgb.r == 0 && 
@@ -2362,19 +2480,19 @@ i_img_is_monochrome(i_img *im, int *zero_is_white) {
                colors[1].rgb.r == 255 &&
                colors[1].rgb.g == 255 &&
                colors[1].rgb.b == 255) {
-        *zero_is_white = 1;
+        *zero_is_white = 0;
         return 1;
       }
     }
     else if (im->channels == 1) {
       if (colors[0].channel[0] == 255 &&
-          colors[1].channel[1] == 0) {
-        *zero_is_white = 0;
+          colors[1].channel[0] == 0) {
+        *zero_is_white = 1;
         return 1;
       }
       else if (colors[0].channel[0] == 0 &&
-               colors[0].channel[0] == 255) {
-        *zero_is_white = 1;
+               colors[1].channel[0] == 255) {
+        *zero_is_white = 0;
         return 1;         
       }
     }
index 1c02754..dbec079 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -56,6 +56,8 @@ i_img *i_img_empty(i_img *im,int x,int y);
 i_img *i_img_empty_ch(i_img *im,int x,int y,int ch);
 void   i_img_exorcise(i_img *im);
 void   i_img_destroy(i_img *im);
+i_img *i_img_alloc(void);
+void i_img_init(i_img *im);
 
 void   i_img_info(i_img *im,int *info);
 
@@ -112,6 +114,11 @@ extern int i_setcolors(i_img *im, int index, const i_color *colors,
 #define i_gsampf(im, l, r, y, samps, chans, count) \
   (((im)->i_f_gsampf)((im), (l), (r), (y), (samps), (chans), (count)))
 
+#define i_gsamp_bits(im, l, r, y, samps, chans, count, bits) \
+  (((im)->i_f_gsamp_bits) ? ((im)->i_f_gsamp_bits)((im), (l), (r), (y), (samps), (chans), (count), (bits)) : -1)
+#define i_psamp_bits(im, l, r, y, samps, chans, count, bits) \
+  (((im)->i_f_psamp_bits) ? ((im)->i_f_psamp_bits)((im), (l), (r), (y), (samps), (chans), (count), (bits)) : -1)
+
 #define i_findcolor(im, color, entry) \
   (((im)->i_f_findcolor) ? ((im)->i_f_findcolor)((im), (color), (entry)) : 0)
 
@@ -345,16 +352,13 @@ extern i_palidx *i_quant_translate(i_quantize *quant, i_img *img);
 extern void i_quant_transparent(i_quantize *quant, i_palidx *indices, i_img *img, i_palidx trans_index);
 
 extern i_img *i_img_pal_new(int x, int y, int channels, int maxpal);
-extern i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal);
 extern i_img *i_img_to_pal(i_img *src, i_quantize *quant);
 extern i_img *i_img_to_rgb(i_img *src);
 extern i_img *i_img_masked_new(i_img *targ, i_img *mask, int x, int y, 
                                int w, int h);
 extern i_img *i_img_16_new(int x, int y, int ch);
-extern i_img *i_img_16_new_low(i_img *im, int x, int y, int ch);
 extern i_img *i_img_to_rgb16(i_img *im);
 extern i_img *i_img_double_new(int x, int y, int ch);
-extern i_img *i_img_double_new_low(i_img *im, int x, int y, int ch);
 
 extern int i_img_is_monochrome(i_img *im, int *zero_is_white);
 
@@ -374,6 +378,8 @@ undef_int i_writetiff_wiol(i_img *im, io_glue *ig);
 undef_int i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count);
 undef_int i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine);
 undef_int i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine);
+char const * i_tiff_libversion(void);
+int i_tiff_has_compression(char const *name);
 
 #endif /* HAVE_LIBTIFF */
 
index df471cc..613bd5d 100644 (file)
--- a/imageri.h
+++ b/imageri.h
@@ -30,6 +30,10 @@ extern int i_findcolor_forward(i_img *im, const i_color *color,
 extern int i_setcolors_forward(i_img *im, int index, const i_color *colors, 
                                int count);
 
+/* fallback handler for gsamp_bits */
+extern int i_gsamp_bits_fb(i_img *im, int x, int r, int y, unsigned *samp, 
+                          const int *chans, int chan_count, int bits);
+
 #define SampleFTo16(num) ((int)((num) * 65535.0 + 0.01))
 /* we add that little bit to avoid rounding issues */
 #define Sample16ToF(num) ((num) / 65535.0)
index 2c00b79..8e9cdd3 100644 (file)
@@ -102,8 +102,110 @@ typedef int (*i_f_setcolors_t)(i_img *im, int index, const i_color *colors,
 
 typedef void (*i_f_destroy_t)(i_img *im);
 
+typedef int (*i_f_gsamp_bits_t)(i_img *im, int x, int r, int y, unsigned *samp,
+                           const int *chans, int chan_count, int bits);
+typedef int (*i_f_psamp_bits_t)(i_img *im, int x, int r, int y, unsigned const *samp,
+                                const int *chans, int chan_count, int bits);
+
 typedef int i_img_dim;
 
+/*
+=item i_img
+=category Data Types
+=synopsis i_img *img;
+
+This is Imager's image type.
+
+It contains the following members:
+
+=over
+
+=item *
+
+channels - the number of channels in the image
+
+=item *
+
+xsize, ysize - the width and height of the image in pixels
+
+=item *
+
+bytes - the number of bytes used to store the image data.  Undefined
+where virtual is non-zero.
+
+=item *
+
+ch_mask - a mask of writable channels.  eg. if this is 6 then only
+channels 1 and 2 are writable.  There may be bits set for which there
+are no channels in the image.
+
+=item *
+
+bits - the number of bits stored per sample.  Should be one of
+i_8_bits, i_16_bits, i_double_bits.
+
+=item *
+
+type - either i_direct_type for direct color images, or i_palette_type
+for paletted images.
+
+=item *
+
+virtual - if zero then this image is-self contained.  If non-zero then
+this image could be an interface to some other implementation.
+
+=item *
+
+idata - the image data.  This should not be directly accessed.  A new
+image implementation can use this to store its image data.
+i_img_destroy() will myfree() this pointer if it's non-null.
+
+=item *
+
+tags - a structure storing the image's tags.  This should only be
+accessed via the i_tags_*() functions.
+
+=item *
+
+ext_data - a pointer for use internal to an image implementation.
+This should be freed by the image's destroy handler.
+
+=item *
+
+im_data - data internal to Imager.  This is initialized by
+i_img_init().
+
+=item *
+
+i_f_ppix, i_f_ppixf, i_f_plin, i_f_plinf, i_f_gpix, i_f_gpixf,
+i_f_glin, i_f_glinf, i_f_gsamp, i_f_gampf - implementations for each
+of the required image functions.  An image implementation should
+initialize these between calling i_img_alloc() and i_img_init().
+
+=item *
+
+i_f_gpal, i_f_ppal, i_f_addcolors, i_f_getcolors, i_f_colorcount,
+i_f_maxcolors, i_f_findcolor, i_f_setcolors - implementations for each
+paletted image function.
+
+=item *
+
+i_f_destroy - custom image destruction function.  This should be used
+to release memory if necessary.
+
+=item *
+
+i_f_gsamp_bits - implements i_gsamp_bits() for this image.
+
+=item *
+
+i_f_psamp_bits - implements i_psamp_bits() for this image.
+
+=back
+
+=cut
+*/
+
 struct i_img_ {
   int channels;
   i_img_dim xsize,ysize;
@@ -141,6 +243,12 @@ struct i_img_ {
   i_f_setcolors_t i_f_setcolors;
 
   i_f_destroy_t i_f_destroy;
+
+  /* as of 0.61 */
+  i_f_gsamp_bits_t i_f_gsamp_bits;
+  i_f_psamp_bits_t i_f_psamp_bits;
+
+  void *im_data;
 };
 
 /* ext_data for paletted images
diff --git a/imext.c b/imext.c
index 3e568ca..3990374 100644 (file)
--- a/imext.c
+++ b/imext.c
@@ -112,7 +112,11 @@ im_ext_funcs imager_function_table =
     i_img_get_width,
     i_img_get_height,
     i_lhead,
-    i_loog
+    i_loog,
+
+    /* IMAGER_API_LEVEL 4 functions */
+    i_img_alloc,
+    i_img_init,
   };
 
 /* in general these functions aren't called by Imager internally, but
diff --git a/imext.h b/imext.h
index 2fca3e4..3f0484b 100644 (file)
--- a/imext.h
+++ b/imext.h
@@ -199,4 +199,7 @@ extern im_ext_funcs *imager_function_ext_table;
 #define i_lhead(file, line) ((im_extt->f_i_lhead)((file), (line)))
 #define i_loog (im_extt->f_i_loog)
 
+#define i_img_alloc() ((im_extt->f_i_img_alloc)())
+#define i_img_init(img) ((im_extt->f_i_img_init)(img))
+
 #endif
index e607d4a..fc3dcf7 100644 (file)
@@ -18,7 +18,7 @@
  will result in an increment of IMAGER_API_LEVEL.
 */
 
-#define IMAGER_API_LEVEL 3
+#define IMAGER_API_LEVEL 4
 
 typedef struct {
   int version;
@@ -152,6 +152,10 @@ typedef struct {
   void (*f_i_loog)(int level, const char *msg, ...);
 
   /* IMAGER_API_LEVEL 4 functions will be added here */
+  i_img *(*f_i_img_alloc)(void);
+  void (*f_i_img_init)(i_img *);
+
+  /* IMAGER_API_LEVEL 5 functions will be added here */
 } im_ext_funcs;
 
 #define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
diff --git a/img16.c b/img16.c
index e17d40d..028539d 100644 (file)
--- a/img16.c
+++ b/img16.c
@@ -35,6 +35,10 @@ static int i_gsamp_d16(i_img *im, int l, int r, int y, i_sample_t *samps,
                        int const *chans, int chan_count);
 static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps, 
                         int const *chans, int chan_count);
+static int i_gsamp_bits_d16(i_img *im, int l, int r, int y, unsigned *samps, 
+                           int const *chans, int chan_count, int bits);
+static int i_psamp_bits_d16(i_img *im, int l, int r, int y, unsigned const *samps, 
+                           int const *chans, int chan_count, int bits);
 
 /*
 =item IIM_base_16bit_direct
@@ -70,12 +74,17 @@ static i_img IIM_base_16bit_direct =
 
   NULL, /* i_f_gpal */
   NULL, /* i_f_ppal */
-  NULL, /* i_f_addcolor */
-  NULL, /* i_f_getcolor */
+  NULL, /* i_f_addcolors */
+  NULL, /* i_f_getcolors */
   NULL, /* i_f_colorcount */
+  NULL, /* i_f_maxcolors */
   NULL, /* i_f_findcolor */
+  NULL, /* i_f_setcolors */
 
   NULL, /* i_f_destroy */
+
+  i_gsamp_bits_d16,
+  i_psamp_bits_d16,
 };
 
 /* it's possible some platforms won't have a 16-bit integer type,
@@ -111,12 +120,9 @@ typedef unsigned short i_sample16_t;
 #define STORE16(bytes, offset, word) \
    (((i_sample16_t *)(bytes))[offset] = (word))
 #define STORE8as16(bytes, offset, byte) \
-   (((i_sample16_t *)(bytes))[offset] = (byte) * 256)
+   (((i_sample16_t *)(bytes))[offset] = (byte) * 256 + (byte))
 #define GET16(bytes, offset) \
      (((i_sample16_t *)(bytes))[offset])
-#define GET16as8(bytes, offset) \
-     (((i_sample16_t *)(bytes))[offset] / 256)
-
 #else
 
 /* we have to do this the hard way */
@@ -125,25 +131,34 @@ typedef unsigned short i_sample16_t;
     (((unsigned char *)(bytes))[(offset)*2+1] = (word) & 0xFF))
 #define STORE8as16(bytes, offset, byte) \
    ((((unsigned char *)(bytes))[(offset)*2] = (byte)), \
-    (((unsigned char *)(bytes))[(offset)*2+1] = 0))
+    (((unsigned char *)(bytes))[(offset)*2+1] = (byte)))
    
 #define GET16(bytes, offset) \
    (((unsigned char *)(bytes))[(offset)*2] * 256 \
     + ((unsigned char *)(bytes))[(offset)*2+1])
-#define GET16as8(bytes, offset) \
-   (((unsigned char *)(bytes))[(offset)*2] << 8)
 
 #endif
 
+#define GET16as8(bytes, offset) \
+     ((((i_sample16_t *)(bytes))[offset]+127) / 257)
+
 /*
-=item i_img_16_new_low(int x, int y, int ch)
+=item i_img_16_new(x, y, ch)
 
-Creates a new 16-bit per sample image.
+=category Image creation
+=synopsis i_img *img = i_img_16_new(width, height, channels);
+
+Create a new 16-bit/sample image.
+
+Returns the image on success, or NULL on failure.
 
 =cut
 */
-i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
+
+i_img *i_img_16_new(int x, int y, int ch) {
+  i_img *im;
   int bytes, line_bytes;
+
   mm_log((1,"i_img_16_new(x %d, y %d, ch %d)\n", x, y, ch));
 
   if (x < 1 || y < 1) {
@@ -169,6 +184,7 @@ i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
     return NULL;
   }
 
+  im = i_img_alloc();
   *im = IIM_base_16bit_direct;
   i_tags_new(&im->tags);
   im->xsize = x;
@@ -177,45 +193,10 @@ i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
   im->bytes = bytes;
   im->ext_data = NULL;
   im->idata = mymalloc(im->bytes);
-  if (im->idata) {
-    memset(im->idata, 0, im->bytes);
-  }
-  else {
-    i_tags_destroy(&im->tags);
-    im = NULL;
-  }
-  
-  return im;
-}
-
-/*
-=item i_img_16_new(x, y, ch)
-
-=category Image creation/destruction
-=synopsis i_img *img = i_img_16_new(width, height, channels);
-
-Create a new 16-bit/sample image.
-
-Returns the image on success, or NULL on failure.
-
-=cut
-*/
+  memset(im->idata, 0, im->bytes);
 
-i_img *i_img_16_new(int x, int y, int ch) {
-  i_img *im;
-  
-  i_clear_error();
+  i_img_init(im);
 
-  im = mymalloc(sizeof(i_img));
-  if (im) {
-    if (!i_img_16_new_low(im, x, y, ch)) {
-      myfree(im);
-      im = NULL;
-    }
-  }
-  
-  mm_log((1, "(%p) <- i_img_16_new\n", im));
-  
   return im;
 }
 
@@ -523,6 +504,121 @@ static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps,
   }
 }
 
+static int 
+i_gsamp_bits_d16(i_img *im, int l, int r, int y, unsigned *samps, 
+                           int const *chans, int chan_count, int bits) {
+  int ch, count, i, w;
+  int off;
+
+  if (bits != 16) {
+    return i_gsamp_bits_fb(im, l, r, y, samps, chans, chan_count, bits);
+  }
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return -1;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = GET16(im->idata, off+chans[ch]);
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+    else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_error(0, "Invalid channel count");
+       return -1;
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = GET16(im->idata, off+ch);
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+
+    return count;
+  }
+  else {
+    i_push_error(0, "Image position outside of image");
+    return -1;
+  }
+}
+
+static int 
+i_psamp_bits_d16(i_img *im, int l, int r, int y, unsigned const *samps, 
+                           int const *chans, int chan_count, int bits) {
+  int ch, count, i, w;
+  int off;
+
+  if (bits != 16) {
+    i_push_error(0, "Invalid bits for 16-bit image");
+    return -1;
+  }
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return -1;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+         if (im->ch_mask & (1 << ch))
+           STORE16(im->idata, off+chans[ch], *samps);
+         ++samps;
+         ++count;
+        }
+        off += im->channels;
+      }
+    }
+    else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_error(0, "Invalid channel count");
+       return -1;
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+         if (im->ch_mask & (1 << ch)) 
+           STORE16(im->idata, off+ch, *samps);
+         ++samps;
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+
+    return count;
+  }
+  else {
+    i_push_error(0, "Image position outside of image");
+    return -1;
+  }
+}
+
 /*
 =back
 
index b9df1e3..a12b5ee 100644 (file)
@@ -70,17 +70,21 @@ static i_img IIM_base_double_direct =
 
   NULL, /* i_f_gpal */
   NULL, /* i_f_ppal */
-  NULL, /* i_f_addcolor */
-  NULL, /* i_f_getcolor */
+  NULL, /* i_f_addcolors */
+  NULL, /* i_f_getcolors */
   NULL, /* i_f_colorcount */
+  NULL, /* i_f_maxcolors */
   NULL, /* i_f_findcolor */
+  NULL, /* i_f_setcolors */
 
   NULL, /* i_f_destroy */
+
+  i_gsamp_bits_fb,
+  NULL, /* i_f_psamp_bits */
 };
 
 /*
 =item i_img_double_new(int x, int y, int ch)
-
 =category Image creation/destruction
 =synopsis i_img *img = i_img_double_new(width, height, channels);
 
@@ -88,8 +92,9 @@ Creates a new double per sample image.
 
 =cut
 */
-i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) {
+i_img *i_img_double_new(int x, int y, int ch) {
   int bytes;
+  i_img *im;
 
   mm_log((1,"i_img_double_new(x %d, y %d, ch %d)\n", x, y, ch));
 
@@ -107,6 +112,7 @@ i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) {
     return NULL;
   }
   
+  im = i_img_alloc();
   *im = IIM_base_double_direct;
   i_tags_new(&im->tags);
   im->xsize = x;
@@ -115,31 +121,8 @@ i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) {
   im->bytes = bytes;
   im->ext_data = NULL;
   im->idata = mymalloc(im->bytes);
-  if (im->idata) {
-    memset(im->idata, 0, im->bytes);
-  }
-  else {
-    i_tags_destroy(&im->tags);
-    im = NULL;
-  }
-  
-  return im;
-}
-
-i_img *i_img_double_new(int x, int y, int ch) {
-  i_img *im;
-
-  i_clear_error();
-
-  im = mymalloc(sizeof(i_img));
-  if (im) {
-    if (!i_img_double_new_low(im, x, y, ch)) {
-      myfree(im);
-      im = NULL;
-    }
-  }
-  
-  mm_log((1, "(%p) <- i_img_double_new\n", im));
+  memset(im->idata, 0, im->bytes);
+  i_img_init(im);
   
   return im;
 }
index a786914..31fd8b8 100644 (file)
@@ -14,6 +14,9 @@ Imager::APIRef - Imager's C API.
   color.rgba.red = 255; color.rgba.green = 0; color.rgba.blue = 255;
 
 
+  # Data Types
+  i_img *img;
+
   # Drawing
   i_arc(im, 50, 50, 20, 45, 135, &color);
   i_arc_aa(im, 50, 50, 35, 90, 135, &color);
@@ -47,8 +50,10 @@ Imager::APIRef - Imager's C API.
 
   # Image
 
-  # Image creation/destruction
+  # Image creation
   i_img *img = i_img_16_new(width, height, channels);
+
+  # Image creation/destruction
   i_img *img = i_img_8_new(width, height, channels);
   i_img_destroy(img)
   i_img *img = i_img_double_new(width, height, channels);
@@ -56,14 +61,120 @@ Imager::APIRef - Imager's C API.
   i_img *img = i_sametype(src, width, height);
   i_img *img = i_sametype_chans(src, width, height, channels);
 
+  # Image Implementation
+
   # Image quantization
 
+  # Logging
+
   # Paletted images
 
   # Tags
 
 =head1 DESCRIPTION
 
+=head2 Data Types
+
+=over
+
+=item i_img
+
+This is Imager's image type.
+
+It contains the following members:
+
+=over
+
+=item *
+
+channels - the number of channels in the image
+
+=item *
+
+xsize, ysize - the width and height of the image in pixels
+
+=item *
+
+bytes - the number of bytes used to store the image data.  Undefined
+where virtual is non-zero.
+
+=item *
+
+ch_mask - a mask of writable channels.  eg. if this is 6 then only
+channels 1 and 2 are writable.  There may be bits set for which there
+are no channels in the image.
+
+=item *
+
+bits - the number of bits stored per sample.  Should be one of
+i_8_bits, i_16_bits, i_double_bits.
+
+=item *
+
+type - either i_direct_type for direct color images, or i_palette_type
+for paletted images.
+
+=item *
+
+virtual - if zero then this image is-self contained.  If non-zero then
+this image could be an interface to some other implementation.
+
+=item *
+
+idata - the image data.  This should not be directly accessed.  A new
+image implementation can use this to store its image data.
+i_img_destroy() will myfree() this pointer if it's non-null.
+
+=item *
+
+tags - a structure storing the image's tags.  This should only be
+accessed via the i_tags_*() functions.
+
+=item *
+
+ext_data - a pointer for use internal to an image implementation.
+This should be freed by the image's destroy handler.
+
+=item *
+
+im_data - data internal to Imager.  This is initialized by
+i_img_init().
+
+=item *
+
+i_f_ppix, i_f_ppixf, i_f_plin, i_f_plinf, i_f_gpix, i_f_gpixf,
+i_f_glin, i_f_glinf, i_f_gsamp, i_f_gampf - implementations for each
+of the required image functions.  An image implementation should
+initialize these between calling i_img_alloc() and i_img_init().
+
+=item *
+
+i_f_gpal, i_f_ppal, i_f_addcolors, i_f_getcolors, i_f_colorcount,
+i_f_maxcolors, i_f_findcolor, i_f_setcolors - implementations for each
+paletted image function.
+
+=item *
+
+i_f_destroy - custom image destruction function.  This should be used
+to release memory if necessary.
+
+=item *
+
+i_f_gsamp_bits - implements i_gsamp_bits() for this image.
+
+=item *
+
+i_f_psamp_bits - implements i_psamp_bits() for this image.
+
+=back
+
+
+=for comment
+From: File imdatatypes.h
+
+
+=back
+
 =head2 Drawing
 
 =over
@@ -659,7 +770,7 @@ From: File rubthru.im
 
 =back
 
-=head2 Image creation/destruction
+=head2 Image creation
 
 =over
 
@@ -674,6 +785,13 @@ Returns the image on success, or NULL on failure.
 =for comment
 From: File img16.c
 
+
+=back
+
+=head2 Image creation/destruction
+
+=over
+
 =item i_img_8_new(x, y, ch)
 
 
@@ -696,7 +814,6 @@ From: File image.c
 
 =item i_img_double_new(int x, int y, int ch)
 
-
 Creates a new double per sample image.
 
 
@@ -739,6 +856,53 @@ For paletted images the equivalent direct type is returned.
 From: File image.c
 
 
+=back
+
+=head2 Image Implementation
+
+=over
+
+=item i_img_alloc()
+
+Allocates a new i_img structure.
+
+When implementing a new image type perform the following steps in your
+image object creation function:
+
+=over
+
+=item 1.
+
+allocate the image with i_img_alloc().
+
+=item 2.
+
+initialize any function pointers or other data as needed, you can
+overwrite the whole block if you need to.
+
+=item 3.
+
+initialize Imager's internal data by calling i_img_init() on the image
+object.
+
+=back
+
+
+=for comment
+From: File image.c
+
+=item i_img_init(img)
+
+Imager interal initialization of images.
+
+Currently this does very little, in the future it may be used to
+support threads, or color profiles.
+
+
+=for comment
+From: File image.c
+
+
 =back
 
 =head2 Image quantization
@@ -788,6 +952,33 @@ The method used depends on the tr_* members of quant.
 From: File quant.c
 
 
+=back
+
+=head2 Logging
+
+=over
+
+=item i_loog(level, format, ...)
+
+This is an internal function called by the mm_log() macro.
+
+
+=for comment
+From: File log.c
+
+=item mm_log((level, format, ...))
+
+This is the main entry point to logging. Note that the extra set of
+parentheses are required due to limitations in C89 macros.
+
+This will format a string with the current file and line number to the
+log file if logging is enabled.
+
+
+=for comment
+From: File log.h
+
+
 =back
 
 =head2 Paletted images
@@ -1150,11 +1341,19 @@ will change:
 
 =item *
 
-B<i_lhead>
+B<i_color>
+
+=item *
+
+B<i_fcolor>
 
 =item *
 
-B<i_loog>
+B<i_fill_t>
+
+=item *
+
+B<i_lhead>
 
 
 
index bd66295..3b27358 100644 (file)
@@ -922,11 +922,22 @@ type - the type of sample data to return.  Default: C<8bit>.
 
 Permited values are C<8bit> and C<float>.
 
+As of Imager 0.61 this can be C<16bit> only for 16 bit images.
+
 =item *
 
 channels - a reference to an array of channels to return, where 0 is
 the first channel.  Default: C< [ 0 .. $self->getchannels()-1 ] >
 
+=item *
+
+target - if an array reference is supplied in target then the samples
+will be stored here instead of being returned.
+
+=item *
+
+offset - the offset within the array referenced by I<target>
+
 =back
 
 In list context this will return a list of integers between 0 and 255
@@ -937,6 +948,9 @@ In scalar context this will return a string of packed bytes, as with
 C< pack("C*", ...) > when I<type> is C<8bit> or a string of packed
 doubles as with C< pack("d*", ...) > when I<type> is C<float>.
 
+If the I<target> option is supplied then only a count of samples is
+returned.
+
 Example: Check if any pixels in an image have a non-zero alpha
 channel:
 
@@ -960,6 +974,59 @@ Example: Convert a 2 channel grey image into a 4 channel RGBA image:
     $out->setscanline(y=>$y, pixels=>$data);
   }
 
+Retrieve 16-bit samples:
+
+  if ($img->bits == 16) {
+    my @samples;
+    $img->getsamples(x => 0, y => $y, target => \@samples, type => '16bit');
+  }
+
+=item setsamples
+
+This allows writing of samples back to some images.  Currently this is
+only supported for 16-bit/sample images.
+
+Parameters:
+
+=over
+
+=item *
+
+y - vertical position of the scanline.  This parameter is required.
+
+=item *
+
+x - position to start on the scanline.  Default: 0
+
+=item *
+
+width - number of pixels to write.  Default: $img->getwidth - x.  The
+minimum of this and the number of pixels represented by the samples
+provided will be written.
+
+=item *
+
+type - the type of sample data to write.  This parameter is required.
+
+As of Imager 0.61 this can only be C<16bit> only for 16 bit images.
+
+=item *
+
+channels - a reference to an array of channels to return, where 0 is
+the first channel.  Default: C< [ 0 .. $self->getchannels()-1 ] >
+
+=item *
+
+data - a reference to an array of samples to write.  Required.
+
+=item *
+
+offset - the starting offset within the array referenced by I<data>
+
+=back
+
+Returns the number of samples written.
+
 =back
 
 =head1 Packed Color Data
index 5782205..288fd09 100644 (file)
@@ -742,8 +742,7 @@ For greyscale images channel 0 is used, for color images channel 1
 use $img->to_paletted() to product a bi-level image.  This way you can
 use dithering:
 
-  my $bilevel = $img->to_paletted(colors=>[ NC(0,0,0), NC(255,255,255) ],
-                                  make_colors => 'none',
+  my $bilevel = $img->to_paletted(make_colors => 'mono',
                                   translate => 'errdiff',
                                   errdiff => 'stucki');
 
@@ -769,11 +768,78 @@ direct color images are read at 8-bits/sample.
 TIFF supports the spatial resolution tags.  See the
 C<tiff_resolutionunit> tag for some extra options.
 
+As of Imager 0.62 Imager reads:
+
+=over
+
+=item *
+
+16-bit grey, RGB, or CMYK image, including a possible alpha channel as
+a 16-bit/sample image.
+
+=item *
+
+32-bit grey, RGB image, including a possible alpha channel as a
+double/sample image.
+
+=item *
+
+bi-level images as paletted images containing only black and white,
+which other formats will also write as bi-level.
+
+=item *
+
+tiled paletted images are now handled correctly
+
+=back
+
 The following tags are set in a TIFF image when read, and can be set
 to control output:
 
 =over
 
+=item tiff_compression
+
+When reading an image this is set to the numeric value of the TIFF
+compression tag.
+
+On writing you can set this to either a numeric compression tag value,
+or one of the following values:
+
+  Ident     Number  Description
+  none         1    No compression
+  packbits   32773  Macintosh RLE
+  ccittrle     2    CCITT RLE
+  fax3         3    CCITT Group 3 fax encoding (T.4)
+  t4           3    As above
+  fax4         4    CCITT Group 4 fax encoding (T.6)
+  t6           4    As above
+  lzw          5    LZW
+  jpeg         7    JPEG
+  zip          8    Deflate (GZIP) Non-standard
+  deflate      8    As above.
+  oldzip     32946  Deflate with an older code.
+  ccittrlew  32771  Word aligned CCITT RLE
+
+In general a compression setting will be ignored where it doesn't make
+sense, eg. C<jpeg> will be ignored for compression if the image is
+being written as bilevel.
+
+Imager attempts to check that your build of libtiff supports the given
+compression, and will fallback to C<packbits> if it isn't enabled.
+eg. older distributions didn't include LZW compression, and JPEG
+compression is only available if libtiff is configured with libjpeg's
+location.
+
+  $im->write(file => 'foo.tif', tiff_compression => 'lzw')
+    or die $im->errstr;
+
+=item tiff_jpegquality
+
+If I<tiff_compression> if C<jpeg> then this can be a number from 1 to
+100 giving the JPEG compression quality.  High values are better
+quality and larger files.
+
 =item tiff_resolutionunit
 
 The value of the ResolutionUnit tag.  This is ignored on writing if
index 73eeaa4..7bb2c7c 100644 (file)
@@ -35,6 +35,13 @@ new() method.
 
   my $font = NF(file => 'foo.ttf');
 
+=item NCF
+
+Create a new L<Imager::Color::Font> object, supplying any parameter to
+the new() method.
+
+  my $colorf = NCF(1.0, 0, 0);
+
 =back
 
 =head1 BUGS
index 81d8cab..71a0f08 100644 (file)
@@ -331,6 +331,42 @@ pixels, for example masked images.
 This may also be used for non-native Imager images in the future, for
 example, for an Imager object that draws on an SDL surface.
 
+=item is_bilevel
+
+Tests if the image will be written as a monochrome or bi-level image
+for formats that support that image organization.
+
+In scalar context, returns true if the image is bi-level.
+
+In list context returns a list:
+
+  ($is_bilevel, $zero_is_white) = $img->is_bilevel;
+
+An image is considered bi-level, if all of the following are true:
+
+=over
+
+=item *
+
+the image is a paletted image
+
+=item *
+
+the image has 1 or 3 channels
+
+=item *
+
+the image has only 2 colors in the palette
+
+=item *
+
+those 2 colors are black and white, in either order.
+
+=back
+
+If a real bi-level organization image is ever added to Imager, this
+function will return true for that too.
+
 =back
 
 =head2 Direct Type Images
index 0689736..994ccc4 100644 (file)
@@ -4,11 +4,13 @@ use Test::Builder;
 require Exporter;
 use vars qw(@ISA @EXPORT_OK);
 @ISA = qw(Exporter);
-@EXPORT_OK = qw(diff_text_with_nul test_image_raw test_image_16 test_image 
+@EXPORT_OK = qw(diff_text_with_nul 
+                test_image_raw test_image_16 test_image test_image_double 
                 is_color3 is_color1 is_color4 
                 is_fcolor4
                 is_image is_image_similar 
-                image_bounds_checks);
+                image_bounds_checks mask_tests
+                test_colorf_gpix test_color_gpix test_colorf_glin);
 
 sub diff_text_with_nul {
   my ($desc, $text1, $text2, @params) = @_;
@@ -201,6 +203,19 @@ sub test_image_16 {
   $img;
 }
 
+sub test_image_double {
+  my $green = Imager::Color->new(0, 255, 0, 255);
+  my $blue  = Imager::Color->new(0, 0, 255, 255);
+  my $red   = Imager::Color->new(255, 0, 0, 255);
+  my $img = Imager->new(xsize => 150, ysize => 150, bits => 'double');
+  $img->box(filled => 1, color => $green, box => [ 70, 25, 130, 125 ]);
+  $img->box(filled => 1, color => $blue,  box => [ 20, 25, 80, 125 ]);
+  $img->arc(x => 75, y => 75, r => 30, color => $red);
+  $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]);
+
+  $img;
+}
+
 sub is_image_similar($$$$) {
   my ($left, $right, $limit, $comment) = @_;
 
@@ -248,6 +263,22 @@ sub is_image_similar($$$$) {
   if ($diff > $limit) {
     $builder->ok(0, $comment);
     $builder->diag("image data difference > $limit - $diff");
+   
+    if ($limit == 0) {
+      # find the first mismatch
+      CHECK:
+      for my $y (0 .. $left->getheight()-1) {
+       for my $x (0.. $left->getwidth()-1) {
+         my @lsamples = $left->getsamples(x => $x, y => $y, width => 1);
+         my @rsamples = $right->getsamples(x => $x, y => $y, width => 1);
+          if ("@lsamples" ne "@rsamples") {
+            $builder->diag("first mismatch at ($x, $y) - @lsamples vs @rsamples");
+            last CHECK;
+          }
+       }
+      }
+    }
+
     return;
   }
   
@@ -288,6 +319,151 @@ sub image_bounds_checks {
   $builder->ok(!$im->setpixel(x => 0, y => 10, color => $blackf), 'bounds check set (0, 10) float');
 }
 
+sub test_colorf_gpix {
+  my ($im, $x, $y, $expected, $epsilon, $comment) = @_;
+
+  my $builder = Test::Builder->new;
+  
+  defined $comment or $comment = '';
+
+  my $c = Imager::i_gpixf($im, $x, $y);
+  unless ($c) {
+    $builder->ok(0, "$comment - retrieve color at ($x,$y)");
+    return;
+  }
+  unless ($builder->ok(_colorf_cmp($c, $expected, $epsilon) == 0,
+            "$comment - got right color ($x, $y)")) {
+    print "# got: (", join(",", ($c->rgba)[0,1,2]), ")\n";
+    print "# expected: (", join(",", ($expected->rgba)[0,1,2]), ")\n";
+    return;
+  }
+  1;
+}
+
+sub test_color_gpix {
+  my ($im, $x, $y, $expected, $comment) = @_;
+
+  my $builder = Test::Builder->new;
+  
+  defined $comment or $comment = '';
+  my $c = Imager::i_get_pixel($im, $x, $y);
+  unless ($c) {
+    $builder->ok(0, "$comment - retrieve color at ($x,$y)");
+    return;
+  }
+  unless ($builder->ok(_color_cmp($c, $expected) == 0,
+     "got right color ($x, $y)")) {
+    print "# got: (", join(",", ($c->rgba)[0,1,2]), ")\n";
+    print "# expected: (", join(",", ($expected->rgba)[0,1,2]), ")\n";
+    return;
+  }
+
+  return 1;
+}
+
+sub test_colorf_glin {
+  my ($im, $x, $y, $pels, $comment) = @_;
+
+  my $builder = Test::Builder->new;
+  
+  my @got = Imager::i_glinf($im, $x, $x+@$pels, $y);
+  @got == @$pels
+    or return $builder->is_num(scalar(@got), scalar(@$pels), "$comment - pixels retrieved");
+  
+  return $builder->ok(!grep(_colorf_cmp($pels->[$_], $got[$_], 0.005), 0..$#got),
+     "$comment - check colors ($x, $y)");
+}
+
+sub _colorf_cmp {
+  my ($c1, $c2, $epsilon) = @_;
+
+  defined $epsilon or $epsilon = 0;
+
+  my @s1 = $c1->rgba;
+  my @s2 = $c2->rgba;
+
+  # print "# (",join(",", @s1[0..2]),") <=> (",join(",", @s2[0..2]),")\n";
+  return abs($s1[0]-$s2[0]) >= $epsilon && $s1[0] <=> $s2[0] 
+    || abs($s1[1]-$s2[1]) >= $epsilon && $s1[1] <=> $s2[1]
+      || abs($s1[2]-$s2[2]) >= $epsilon && $s1[2] <=> $s2[2];
+}
+
+sub _color_cmp {
+  my ($c1, $c2) = @_;
+
+  my @s1 = $c1->rgba;
+  my @s2 = $c2->rgba;
+
+  return $s1[0] <=> $s2[0] 
+    || $s1[1] <=> $s2[1]
+      || $s1[2] <=> $s2[2];
+}
+
+# these test the action of the channel mask on the image supplied
+# which should be an OO image.
+sub mask_tests {
+  my ($im, $epsilon) = @_;
+
+  my $builder = Test::Builder->new;
+
+  defined $epsilon or $epsilon = 0;
+
+  # we want to check all four of ppix() and plin(), ppix() and plinf()
+  # basic test procedure:
+  #   first using default/all 1s mask, set to white
+  #   make sure we got white
+  #   set mask to skip a channel, set to grey
+  #   make sure only the right channels set
+
+  print "# channel mask tests\n";
+  # 8-bit color tests
+  my $white = Imager::NC(255, 255, 255);
+  my $grey = Imager::NC(128, 128, 128);
+  my $white_grey = Imager::NC(128, 255, 128);
+
+  print "# with ppix\n";
+  $builder->ok($im->setmask(mask=>~0), "set to default mask");
+  $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$white), "set to white all channels");
+  test_color_gpix($im->{IMG}, 0, 0, $white, "ppix");
+  $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
+  $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$grey), "set to grey, no channel 2");
+  test_color_gpix($im->{IMG}, 0, 0, $white_grey, "ppix masked");
+
+  print "# with plin\n";
+  $builder->ok($im->setmask(mask=>~0), "set to default mask");
+  $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels => [$white]), 
+     "set to white all channels");
+  test_color_gpix($im->{IMG}, 0, 1, $white, "plin");
+  $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
+  $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels=>[$grey]), 
+     "set to grey, no channel 2");
+  test_color_gpix($im->{IMG}, 0, 1, $white_grey, "plin masked");
+
+  # float color tests
+  my $whitef = Imager::NCF(1.0, 1.0, 1.0);
+  my $greyf = Imager::NCF(0.5, 0.5, 0.5);
+  my $white_greyf = Imager::NCF(0.5, 1.0, 0.5);
+
+  print "# with ppixf\n";
+  $builder->ok($im->setmask(mask=>~0), "set to default mask");
+  $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$whitef), "set to white all channels");
+  test_colorf_gpix($im->{IMG}, 0, 2, $whitef, $epsilon, "ppixf");
+  $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
+  $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$greyf), "set to grey, no channel 2");
+  test_colorf_gpix($im->{IMG}, 0, 2, $white_greyf, $epsilon, "ppixf masked");
+
+  print "# with plinf\n";
+  $builder->ok($im->setmask(mask=>~0), "set to default mask");
+  $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels => [$whitef]), 
+     "set to white all channels");
+  test_colorf_gpix($im->{IMG}, 0, 3, $whitef, $epsilon, "plinf");
+  $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
+  $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels=>[$greyf]), 
+     "set to grey, no channel 2");
+  test_colorf_gpix($im->{IMG}, 0, 3, $white_greyf, $epsilon, "plinf masked");
+
+}
+
 1;
 
 __END__
@@ -347,6 +523,10 @@ Returns a 150x150x3 8-bit/sample OO test image.
 
 Returns a 150x150x3 16-bit/sample OO test image.
 
+=item test_image_double()
+
+Returns a 150x150x3 double/sample OO test image.
+
 =item diff_text_with_nul($test_name, $text1, $text2, @options)
 
 Creates 2 test images and writes $text1 to the first image and $text2
@@ -365,6 +545,25 @@ image to ensure that it fails in those locations.
 
 Any new image type should pass these tests.  Does 16 separate tests.
 
+=item test_colorf_gpix($im, $x, $y, $expected, $epsilon, $comment)
+
+Retrieves the pixel ($x,$y) from the low-level image $im and compares
+it to the floating point color $expected, with a tolerance of epsilon.
+
+=item test_color_gpix($im, $x, $y, $expected, $comment)
+
+Retrieves the pixel ($x,$y) from the low-level image $im and compares
+it to the floating point color $expected.
+
+=item test_colorf_glin($im, $x, $y, $pels, $comment)
+
+Retrieves the floating point pixels ($x, $y)-[$x+@$pels, $y] from the
+low level image $im and compares them against @$pels.
+
+=item mask_tests($im, $epsilon)
+
+Perform a standard set of mask tests on the OO image $im.
+
 =back
 
 =head1 AUTHOR
diff --git a/log.c b/log.c
index fb08ffd..2f043a9 100644 (file)
--- a/log.c
+++ b/log.c
@@ -67,6 +67,14 @@ void i_fatal(int exitcode,const char *fmt, ... ) { exit(exitcode); }
 
 #endif
 
+/*
+=item i_loog(level, format, ...)
+=category Logging
+
+This is an internal function called by the mm_log() macro.
+
+=cut
+*/
 
 void
 i_loog(int level,const char *fmt, ... ) {
diff --git a/log.h b/log.h
index 12d3467..5b1f483 100644 (file)
--- a/log.h
+++ b/log.h
@@ -15,6 +15,18 @@ void i_init_log( const char *name, int onoff );
 void i_loog(int level,const char *msg, ... );
 void i_fatal ( int exitcode,const char *fmt, ... );
 
+/*
+=item mm_log((level, format, ...))
+=category Logging
+
+This is the main entry point to logging. Note that the extra set of
+parentheses are required due to limitations in C89 macros.
+
+This will format a string with the current file and line number to the
+log file if logging is enabled.
+
+=cut
+*/
 
 #ifdef IMAGER_LOG
 #define mm_log(x) { i_lhead(__FILE__,__LINE__); i_loog x; } 
index 75c56cc..6bae01c 100644 (file)
--- a/palimg.c
+++ b/palimg.c
@@ -71,18 +71,28 @@ static i_img IIM_base_8bit_pal =
   i_setcolors_p, /* i_f_setcolors */
 
   i_destroy_p, /* i_f_destroy */
+
+  i_gsamp_bits_fb,
+  NULL, /* i_f_psamp_bits */
 };
 
 /*
-=item i_img_pal_new_low(im, x, y, channels, maxpal)
+=item i_img_pal_new(x, y, channels, maxpal)
+
+=category Image creation/destruction
+=synopsis i_img *img = i_img_pal_new(width, height, channels, max_palette_size)
+
+Creates a new paletted image of the supplied dimensions.
 
-Creates a new paletted image.
+I<maxpal> is the maximum palette size and should normally be 256.
 
-Currently 0 < maxpal <= 256
+Returns a new image or NULL on failure.
 
 =cut
 */
-i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
+i_img *
+i_img_pal_new(int x, int y, int channels, int maxpal) {
+  i_img *im;
   i_img_pal_ext *palext;
   int bytes, line_bytes;
 
@@ -114,6 +124,7 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
     return NULL;
   }
 
+  im = i_img_alloc();
   memcpy(im, &IIM_base_8bit_pal, sizeof(i_img));
   palext = mymalloc(sizeof(i_img_pal_ext));
   palext->pal = mymalloc(sizeof(i_color) * maxpal);
@@ -128,34 +139,9 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
   memset(im->idata, 0, im->bytes);
   im->xsize = x;
   im->ysize = y;
-  
-  return im;
-}
-
-/*
-=item i_img_pal_new(x, y, channels, maxpal)
-
-=category Image creation/destruction
-=synopsis i_img *img = i_img_pal_new(width, height, channels, max_palette_size)
-
-Creates a new paletted image of the supplied dimensions.
-
-I<maxpal> is the maximum palette size and should normally be 256.
-
-Returns a new image or NULL on failure.
-
-=cut
-*/
-
-i_img *i_img_pal_new(int x, int y, int channels, int maxpal) {
-  i_img *im;
-  mm_log((1, "i_img_pal_new(x %d, y %d, channels %d, maxpal %d)\n", x, y, channels, maxpal));
-  im = mymalloc(sizeof(i_img));
-  if (!i_img_pal_new_low(im, x, y, channels, maxpal)) {
-    myfree(im);
-    im = NULL;
-  }
 
+  i_img_init(im);
+  
   return im;
 }
 
index f38f88a..c9ae822 100644 (file)
@@ -3,7 +3,7 @@
 # to make sure we get expected values
 
 use strict;
-use Test::More tests => 223;
+use Test::More tests => 228;
 
 BEGIN { use_ok(Imager => qw(:handy :all)) }
 
@@ -167,6 +167,15 @@ is($impal2->type, 'paletted', "check type");
   is($impal3->type, 'paletted', "and is paletted");
 }
 
+{ # basic checks, 8-bit direct images
+  my $im = Imager->new(xsize => 2, ysize => 3);
+  ok($im, 'create 8-bit direct image');
+  is($im->bits, 8, '8 bits');
+  ok(!$im->virtual, 'not virtual');
+  is($im->type, 'direct', 'direct image');
+  ok(!$im->is_bilevel, 'not mono');
+}
+
 ok(!Imager->new(xsize=>0, ysize=>1), "fail to create 0 height image");
 cmp_ok(Imager->errstr, '=~', qr/Image sizes must be positive/,
        "0 height error message check");
index c04f31f..abcfcd6 100644 (file)
@@ -1,15 +1,14 @@
 #!perl -w
 use strict;
-use Test::More tests => 103;
+use Test::More tests => 104;
 
 BEGIN { use_ok(Imager=>qw(:all :handy)) }
 
 init_log("testout/t021sixteen.log", 1);
 
-require "t/testtools.pl";
-
 use Imager::Color::Float;
-use Imager::Test qw(test_image is_image image_bounds_checks);
+use Imager::Test qw(test_image is_image image_bounds_checks test_colorf_gpix
+                    test_colorf_glin mask_tests is_color3);
 
 my $im_g = Imager::i_img_16_new(100, 101, 1);
 
@@ -42,22 +41,66 @@ for my $y (0..101) {
 }
 pass("fill with red");
 # basic sanity
-test_colorf_gpix($im_rgb, 0,  0,   $redf);
-test_colorf_gpix($im_rgb, 99, 0,   $redf);
-test_colorf_gpix($im_rgb, 0,  100, $redf);
-test_colorf_gpix($im_rgb, 99, 100, $redf);
-test_colorf_glin($im_rgb, 0,  0,   ($redf) x 100);
-test_colorf_glin($im_rgb, 0,  100, ($redf) x 100);
+test_colorf_gpix($im_rgb, 0,  0,   $redf, 0, "top-left");
+test_colorf_gpix($im_rgb, 99, 0,   $redf, 0, "top-right");
+test_colorf_gpix($im_rgb, 0,  100, $redf, 0, "bottom left");
+test_colorf_gpix($im_rgb, 99, 100, $redf, 0, "bottom right");
+test_colorf_glin($im_rgb, 0,  0,   [ ($redf) x 100 ], "first line");
+test_colorf_glin($im_rgb, 0,  100, [ ($redf) x 100 ], "last line");
 
 Imager::i_plinf($im_rgb, 20, 1, ($greenf) x 60);
 test_colorf_glin($im_rgb, 0, 1, 
-                 ($redf) x 20, ($greenf) x 60, ($redf) x 20);
+                 [ ($redf) x 20, ($greenf) x 60, ($redf) x 20 ],
+               "added some green in the middle");
+{
+  my @samples;
+  is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0 .. 2), 12, 
+     "i_gsamp_bits all channels - count")
+    or print "# ", Imager->_error_as_msg(), "\n";
+  is_deeply(\@samples, [ 65535, 0, 0,   65535, 0, 0,
+                        0, 65535, 0,   0, 65535, 0 ],
+           "check samples retrieved");
+  @samples = ();
+  is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0, 2), 8, 
+     "i_gsamp_bits some channels - count")
+    or print "# ", Imager->_error_as_msg(), "\n";
+  is_deeply(\@samples, [ 65535, 0,   65535, 0,
+                        0, 0,       0, 0     ],
+           "check samples retrieved");
+  # fail gsamp
+  is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0, 3), undef,
+     "i_gsamp_bits fail bad channel");
+  is(Imager->_error_as_msg(), 'No channel 3 in this image', 'check message');
+
+  is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 17, \@samples, 0, 0, 2), 8, 
+     "i_gsamp_bits succeed high bits");
+  is($samples[0], 131071, "check correct with high bits");
+
+  # write some samples back
+  my @wr_samples = 
+    ( 
+     0, 0, 65535,
+     65535, 0, 0,  
+     0, 65535, 0,  
+     65535, 65535, 0 
+    );
+  is(Imager::i_psamp_bits($im_rgb, 18, 2, 16, [ 0 .. 2 ], \@wr_samples),
+     12, "write 16-bit samples")
+    or print "# ", Imager->_error_as_msg(), "\n";
+  @samples = ();
+  is(Imager::i_gsamp_bits($im_rgb, 18, 22, 2, 16, \@samples, 0, 0 .. 2), 12, 
+     "read them back")
+    or print "# ", Imager->_error_as_msg(), "\n";
+  is_deeply(\@samples, \@wr_samples, "check they match");
+  my $c = Imager::i_get_pixel($im_rgb, 18, 2);
+  is_color3($c, 0, 0, 255, "check it write to the right places");
+}
 
 # basic OO tests
 my $oo16img = Imager->new(xsize=>200, ysize=>201, bits=>16);
 ok($oo16img, "make a 16-bit oo image");
 is($oo16img->bits,  16, "test bits");
-
+isnt($oo16img->is_bilevel, "should not be considered mono");
 # make sure of error handling
 ok(!Imager->new(xsize=>0, ysize=>1, bits=>16),
     "fail to create a 0 pixel wide image");
@@ -165,3 +208,16 @@ cmp_ok(Imager->errstr, '=~', qr/channels must be between 1 and 4/,
   my $im = Imager->new(xsize => 10, ysize => 10, bits => 16);
   image_bounds_checks($im);
 }
+
+{
+  my $im = Imager->new(xsize => 10, ysize => 10, bits => 16, channels => 3);
+  my @wr_samples = map int(rand 65536), 1..30;
+  is($im->setsamples('y' => 1, data => \@wr_samples, type => '16bit'),
+     30, "write 16-bit to OO image")
+    or print "# ", $im->errstr, "\n";
+  my @samples;
+  is($im->getsamples(y => 1, target => \@samples, type => '16bit'),
+     30, "read 16-bit from OO image")
+    or print "# ", $im->errstr, "\n";
+  is_deeply(\@wr_samples, \@samples, "check it matches");
+}
index 403aef6..e5234b1 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-use Test::More tests => 97;
+use Test::More tests => 98;
 
 BEGIN { use_ok(Imager => qw(:all :handy)) }
 require "t/testtools.pl";
@@ -64,6 +64,7 @@ test_colorf_glin($im_rgb, 0, 1,
 my $ooimg = Imager->new(xsize=>200, ysize=>201, bits=>'double');
 ok($ooimg, "couldn't make double image");
 is($ooimg->bits, 'double', "oo didn't give double image");
+ok(!$ooimg->is_bilevel, 'not monochrome');
 
 # check that the image is copied correctly
 my $oocopy = $ooimg->copy;
index ec464fb..062e807 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 # some of this is tested in t01introvert.t too
 use strict;
-use Test::More tests => 107;
+use Test::More tests => 121;
 BEGIN { use_ok("Imager"); }
 
 use Imager::Test qw(image_bounds_checks);
@@ -280,6 +280,9 @@ cmp_ok(Imager->errstr, '=~', qr/Channels must be positive and <= 4/,
                                   translate => 'closest');
   is($mono->type, 'paletted', "check we get right image type");
   is($mono->colorcount, 2, "only 2 colors");
+  my ($is_mono, $ziw) = $mono->is_bilevel;
+  ok($is_mono, "check monochrome check true");
+  is($ziw, 0, "check ziw false");
   my @colors = $mono->getcolors;
   iscolor($colors[0], $black, "check first entry");
   iscolor($colors[1], $white, "check second entry");
@@ -289,6 +292,36 @@ cmp_ok(Imager->errstr, '=~', qr/Channels must be positive and <= 4/,
   is($pixels[2], 0, "check black pixel");
 }
 
+{ # check for the various mono images we accept
+  my $mono_8_bw_3 = Imager->new(xsize => 2, ysize => 2, channels => 3, 
+                             type => 'paletted');
+  ok($mono_8_bw_3->addcolors(colors => [ qw/000000 FFFFFF/ ]), 
+     "mono8bw3 - add colors");
+  ok($mono_8_bw_3->is_bilevel, "it's mono");
+  is(($mono_8_bw_3->is_bilevel)[1], 0, 'zero not white');
+  
+  my $mono_8_wb_3 = Imager->new(xsize => 2, ysize => 2, channels => 3, 
+                             type => 'paletted');
+  ok($mono_8_wb_3->addcolors(colors => [ qw/FFFFFF 000000/ ]), 
+     "mono8wb3 - add colors");
+  ok($mono_8_wb_3->is_bilevel, "it's mono");
+  is(($mono_8_wb_3->is_bilevel)[1], 1, 'zero is white');
+  
+  my $mono_8_bw_1 = Imager->new(xsize => 2, ysize => 2, channels => 1, 
+                             type => 'paletted');
+  ok($mono_8_bw_1->addcolors(colors => [ qw/000000 FFFFFF/ ]), 
+     "mono8bw - add colors");
+  ok($mono_8_bw_1->is_bilevel, "it's mono");
+  is(($mono_8_bw_1->is_bilevel)[1], 0, 'zero not white');
+  
+  my $mono_8_wb_1 = Imager->new(xsize => 2, ysize => 2, channels => 1, 
+                             type => 'paletted');
+  ok($mono_8_wb_1->addcolors(colors => [ qw/FFFFFF 000000/ ]), 
+     "mono8wb - add colors");
+  ok($mono_8_wb_1->is_bilevel, "it's mono");
+  is(($mono_8_wb_1->is_bilevel)[1], 1, 'zero is white');
+}
+
 { # check bounds checking
   my $im = Imager->new(xsize => 10, ysize => 10, type=>'paletted');
   ok($im->addcolors(colors => [ $black ]), "add color of pixel bounds check writes");
index 0f6a0fa..0872749 100644 (file)
@@ -1,7 +1,8 @@
 #!perl -w
 use strict;
-use Test::More tests => 129;
+use Test::More tests => 213;
 use Imager qw(:all);
+use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double);
 $^W=1; # warnings during command-line tests
 $|=1;  # give us some progress in the test harness
 init_log("testout/t106tiff.log",1);
@@ -33,7 +34,17 @@ SKIP:
     cmp_ok($im->errstr, '=~', "format 'tiff' not supported", "check no tiff message");
     ok(!grep($_ eq 'tiff', Imager->read_types), "check tiff not in read types");
     ok(!grep($_ eq 'tiff', Imager->write_types), "check tiff not in write types");
-    skip("no tiff support", 123);
+    skip("no tiff support", 209);
+  }
+
+  my $ver_string = Imager::i_tiff_libversion();
+  ok(my ($full, $major, $minor, $point) = 
+     $ver_string =~ /Version +((\d+)\.(\d+).(\d+))/,
+     "extract library version");
+  # make something we can compare
+  my $cmp_ver = sprintf("%03d%03d%03d", $major, $minor, $point);
+  if ($cmp_ver lt '003007000') {
+    diag("You have an old version of libtiff - $full, some tests will be skipped");
   }
 
   Imager::i_tags_add($img, "i_xres", 0, "300", 0);
@@ -44,7 +55,8 @@ SKIP:
   open(FH,">testout/t106.tiff") || die "cannot open testout/t106.tiff for writing\n";
   binmode(FH); 
   my $IO = Imager::io_new_fd(fileno(FH));
-  ok(i_writetiff_wiol($img, $IO), "write low level");
+  ok(i_writetiff_wiol($img, $IO), "write low level")
+    or print "# ", Imager->_error_as_msg, "\n";
   close(FH);
 
   open(FH,"testout/t106.tiff") or die "cannot open testout/t106.tiff\n";
@@ -142,7 +154,8 @@ SKIP:
 
   # paletted reads
   my $img4 = Imager->new;
-  ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted");
+  ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted")
+    or print "# ", $img4->errstr, "\n";
   is($img4->type, 'paletted', "image isn't paletted");
   print "# colors: ", $img4->colorcount,"\n";
   cmp_ok($img4->colorcount, '<=', 16, "more than 16 colors!");
@@ -155,6 +168,10 @@ SKIP:
   my $diff = i_img_diff($img4->{IMG}, $bmp4->{IMG});
   print "# diff $diff\n";
   ok($diff == 0, "image mismatch");
+  my $img4t = Imager->new;
+  ok($img4t->read(file => 'testimg/comp4t.tif'), "read 4-bit paletted, tiled")
+    or print "# ", $img4t->errstr, "\n";
+  is_image($bmp4, $img4t, "check tiled version matches");
   my $img8 = Imager->new;
   ok($img8->read(file=>'testimg/comp8.tif'), "reading 8-bit paletted");
   is($img8->type, 'paletted', "image isn't paletted");
@@ -431,22 +448,34 @@ SKIP:
     my $photo_cielab = 8;
     my @alpha_images =
       (
-       [ 'srgb.tif',    3, $photo_rgb ],
-       [ 'srgba.tif',   4, $photo_rgb  ],
-       [ 'srgbaa.tif',  4, $photo_rgb  ],
-       [ 'scmyk.tif',   3, $photo_cmyk ],
-       [ 'scmyka.tif',  4, $photo_cmyk ],
-       [ 'scmykaa.tif', 4, $photo_cmyk ],
-       [ 'slab.tif',    3, $photo_cielab ],
+       [ 'srgb.tif',    3, $photo_rgb,    '003005005' ],
+       [ 'srgba.tif',   4, $photo_rgb,    '003005005' ],
+       [ 'srgbaa.tif',  4, $photo_rgb,    '003005005' ],
+       [ 'scmyk.tif',   3, $photo_cmyk,   '003005005' ],
+       [ 'scmyka.tif',  4, $photo_cmyk,   '003005005' ],
+       [ 'scmykaa.tif', 4, $photo_cmyk,   '003005005' ],
+       [ 'slab.tif',    3, $photo_cielab, '003006001' ],
       );
+
     for my $test (@alpha_images) {
-      my $im = Imager->new;
-      ok($im->read(file => "testimg/$test->[0]"),
-        "read alpha test $test->[0]")
+      my ($input, $channels, $photo, $need_ver) = @$test;
+      
+    SKIP: {
+       my $skipped = $channels == 4 ? 4 : 3;
+       $need_ver le $cmp_ver
+         or skip("Your ancient tifflib is buggy/limited for this test", $skipped);
+       my $im = Imager->new;
+       ok($im->read(file => "testimg/$input"),
+          "read alpha test $input")
          or print "# ", $im->errstr, "\n";
-      is($im->getchannels, $test->[1], "channels for $test->[0] match");
-      is($im->tags(name=>'tiff_photometric'), $test->[2],
-        "photometric for $test->[0] match");
+       is($im->getchannels, $channels, "channels for $input match");
+       is($im->tags(name=>'tiff_photometric'), $photo,
+          "photometric for $input match");
+       $channels == 4
+         or next;
+       my $c = $im->getpixel(x => 0, 'y' => 7);
+       is(($c->rgba)[3], 0, "bottom row should have 0 alpha");
+      }
     }
   }
 
@@ -454,5 +483,256 @@ SKIP:
     ok(grep($_ eq 'tiff', Imager->read_types), "check tiff in read types");
     ok(grep($_ eq 'tiff', Imager->write_types), "check tiff in write types");
   }
+
+  { # reading tile based images
+    my $im = Imager->new;
+    ok($im->read(file => 'testimg/pengtile.tif'), "read tiled image");
+    # compare it
+    my $comp = Imager->new;
+    ok($comp->read(file => 'testimg/penguin-base.ppm'), 'read comparison image');
+    is_image($im, $comp, 'compare them');
+  }
+
+ SKIP:
+  { # failing to read tile based images
+    # we grab our tiled image and patch a tile offset to nowhere
+    ok(open(TIFF, '< testimg/pengtile.tif'), 'open pengtile.tif')
+      or skip 'cannot open testimg/pengtile.tif', 4;
+
+    $cmp_ver ge '0030057'
+      or skip("Your ancient tifflib has bad error handling", 4);
+    binmode TIFF;
+    my $data = do { local $/; <TIFF>; };
+    
+    # patch a tile offset
+    substr($data, 0x5AFE, 4) = pack("H*", "1F5C0000");
+
+    #open PIPE, "| bytedump -a | less" or die;
+    #print PIPE $data;
+    #close PIPE;
+
+    my $allow = Imager->new;
+    ok($allow->read(data => $data, allow_incomplete => 1),
+       "read incomplete tiled");
+    ok($allow->tags(name => 'i_incomplete'), 'i_incomplete set');
+    is($allow->tags(name => 'i_lines_read'), 173, 
+       'check i_lines_read set appropriately');
+
+    my $fail = Imager->new;
+    ok(!$fail->read(data => $data), "read fail tiled");
+  }
+
+  { # read 16-bit/sample
+    my $im16 = Imager->new;
+    ok($im16->read(file => 'testimg/rgb16.tif'), "read 16-bit rgb");
+    is($im16->bits, 16, 'got a 16-bit image');
+    my $im16t = Imager->new;
+    ok($im16t->read(file => 'testimg/rgb16t.tif'), "ready 16-bit rgb tiled");
+    is($im16t->bits, 16, 'got a 16-bit image');
+    is_image($im16, $im16t, 'check they match');
+
+    my $grey16 = Imager->new;
+    ok($grey16->read(file => 'testimg/grey16.tif'), "read 16-bit grey")
+      or print "# ", $grey16->errstr, "\n";
+    is($grey16->bits, 16, 'got a 16-bit image');
+    is($grey16->getchannels, 1, 'and its grey');
+    my $comp16 = $im16->convert(matrix => [ [ 0.299, 0.587, 0.114 ] ]);
+    is_image($grey16, $comp16, 'compare grey to converted');
+
+    my $grey32 = Imager->new;
+    ok($grey32->read(file => 'testimg/grey32.tif'), "read 32-bit grey")
+      or print "# ", $grey32->errstr, "\n";
+    is($grey32->bits, 'double', 'got a double image');
+    is($grey32->getchannels, 2, 'and its grey + alpha');
+    is($grey32->tags(name => 'tiff_bitspersample'), 32, 
+       "check bits per sample");
+    my $base = test_image_double->convert(preset =>'grey')
+      ->convert(preset => 'addalpha');
+    is_image($grey32, $base, 'compare to original');
+  }
+
+  { # read 16, 32-bit/sample and compare to the original
+    my $rgba = Imager->new;
+    ok($rgba->read(file => 'testimg/srgba.tif'),
+       "read base rgba image");
+    my $rgba16 = Imager->new;
+    ok($rgba16->read(file => 'testimg/srgba16.tif'),
+       "read 16-bit/sample rgba image");
+    is_image($rgba, $rgba16, "check they match");
+    is($rgba16->bits, 16, 'check we got the right type');
+    
+    my $rgba32 = Imager->new;
+    ok($rgba32->read(file => 'testimg/srgba32.tif'),
+       "read 32-bit/sample rgba image");
+    is_image($rgba, $rgba32, "check they match");
+    is($rgba32->bits, 'double', 'check we got the right type');
+
+    my $cmyka16 = Imager->new;
+    ok($cmyka16->read(file => 'testimg/scmyka16.tif'),
+       "read cmyk 16-bit")
+      or print "# ", $cmyka16->errstr, "\n";
+    is($cmyka16->bits, 16, "check we got the right type");
+    is_image_similar($rgba, $cmyka16, 10, "check image data");
+  }
+  { # read bi-level
+    my $pbm = Imager->new;
+    ok($pbm->read(file => 'testimg/imager.pbm'), "read original pbm");
+    my $tif = Imager->new;
+    ok($tif->read(file => 'testimg/imager.tif'), "read mono tif");
+    is_image($pbm, $tif, "compare them");
+    is($tif->type, 'paletted', 'check image type');
+    is($tif->colorcount, 2, 'check we got a "mono" image');
+  }
+
+  { # check alpha channels scaled correctly for fallback handler
+    my $im = Imager->new;
+    ok($im->read(file=>'testimg/alpha.tif'), 'read alpha check image');
+    my @colors =
+      (
+       [ 0, 0, 0 ],
+       [ 255, 255, 255 ],
+       [ 127, 0, 127 ],
+       [ 127, 127, 0 ],
+      );
+    my @alphas = ( 255, 191, 127, 63 );
+    my $ok = 1;
+    my $msg = 'alpha check ok';
+  CHECKER:
+    for my $y (0 .. 3) {
+      for my $x (0 .. 3) {
+       my $c = $im->getpixel(x => $x, 'y' => $y);
+       my @c = $c->rgba;
+       my $alpha = pop @c;
+       if ($alpha != $alphas[$y]) {
+         $ok = 0;
+         $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]";
+         last CHECKER;
+       }
+       my $expect = $colors[$x];
+       for my $ch (0 .. 2) {
+         if (abs($expect->[$ch]-$c[$ch]) > 3) {
+           $ok = 0;
+           $msg = "($x,$y)[$ch] color mismatch got $c[$ch] vs expected $expect->[$ch]";
+           last CHECKER;
+         }
+       }
+      }
+    }
+    ok($ok, $msg);
+  }
+
+  { # check alpha channels scaled correctly for greyscale
+    my $im = Imager->new;
+    ok($im->read(file=>'testimg/gralpha.tif'), 'read alpha check grey image');
+    my @greys = ( 0, 255, 52, 112 );
+    my @alphas = ( 255, 191, 127, 63 );
+    my $ok = 1;
+    my $msg = 'alpha check ok';
+  CHECKER:
+    for my $y (0 .. 3) {
+      for my $x (0 .. 3) {
+       my $c = $im->getpixel(x => $x, 'y' => $y);
+       my ($grey, $alpha) = $c->rgba;
+       if ($alpha != $alphas[$y]) {
+         $ok = 0;
+         $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]";
+         last CHECKER;
+       }
+       if (abs($greys[$x] - $grey) > 3) {
+         $ok = 0;
+         $msg = "($x,$y) grey mismatch $grey vs $greys[$x]";
+         last CHECKER;
+       }
+      }
+    }
+    ok($ok, $msg);
+  }
+
+  { # 16-bit writes
+    my $orig = test_image_16();
+    my $data;
+    ok($orig->write(data => \$data, type => 'tiff', 
+                   tiff_compression => 'none'), "write 16-bit/sample");
+    my $im = Imager->new;
+    ok($im->read(data => $data), "read it back");
+    is_image($im, $orig, "check read data matches");
+    is($im->tags(name => 'tiff_bitspersample'), 16, "correct bits");
+    is($im->bits, 16, 'check image bits');
+    is($im->tags(name => 'tiff_photometric'), 2, "correct photometric");
+    is($im->tags(name => 'tiff_compression'), 'none', "no compression");
+    is($im->getchannels, 3, 'correct channels');
+  }
+
+  { # 8-bit writes
+    # and check compression
+    my $compress = Imager::i_tiff_has_compression('lzw') ? 'lzw' : 'packbits';
+    my $orig = test_image()->convert(preset=>'grey')
+      ->convert(preset => 'addalpha');
+    my $data;
+    ok($orig->write(data => \$data, type => 'tiff',
+                   tiff_compression=> $compress),
+       "write 8 bit");
+    my $im = Imager->new;
+    ok($im->read(data => $data), "read it back");
+    is_image($im, $orig, "check read data matches");
+    is($im->tags(name => 'tiff_bitspersample'), 8, 'correct bits');
+    is($im->bits, 8, 'check image bits');
+    is($im->tags(name => 'tiff_photometric'), 1, 'correct photometric');
+    is($im->tags(name => 'tiff_compression'), $compress,
+       "$compress compression");
+    is($im->getchannels, 2, 'correct channels');
+  }
+
+  { # double writes
+    my $orig = test_image_double()->convert(preset=>'addalpha');
+    my $data;
+    ok($orig->write(data => \$data, type => 'tiff', 
+                   tiff_compression => 'none'), 
+       "write 32-bit/sample from double")
+      or print "# ", $orig->errstr, "\n";
+    my $im = Imager->new;
+    ok($im->read(data => $data), "read it back");
+    is_image($im, $orig, "check read data matches");
+    is($im->tags(name => 'tiff_bitspersample'), 32, "correct bits");
+    is($im->bits, 'double', 'check image bits');
+    is($im->tags(name => 'tiff_photometric'), 2, "correct photometric");
+    is($im->tags(name => 'tiff_compression'), 'none', "no compression");
+    is($im->getchannels, 4, 'correct channels');
+  }
+
+  { # bilevel
+    my $im = test_image()->convert(preset => 'grey')
+      ->to_paletted(make_colors => 'mono',
+                   translate => 'errdiff');
+    my $faxdata;
+
+    # fax compression is written as miniswhite
+    ok($im->write(data => \$faxdata, type => 'tiff', 
+                 tiff_compression => 'fax3'),
+       "write bilevel fax compressed");
+    my $fax = Imager->new;
+    ok($fax->read(data => $faxdata), "read it back");
+    ok($fax->is_bilevel, "got a bi-level image back");
+    is($fax->tags(name => 'tiff_compression'), 'fax3',
+       "check fax compression used");
+    is_image($fax, $im, "compare to original");
+
+    # other compresion written as minisblack
+    my $packdata;
+    ok($im->write(data => \$packdata, type => 'tiff',
+                 tiff_compression => 'jpeg'),
+       "write bilevel packbits compressed");
+    my $packim = Imager->new;
+    ok($packim->read(data => $packdata), "read it back");
+    ok($packim->is_bilevel, "got a bi-level image back");
+    is($packim->tags(name => 'tiff_compression'), 'packbits',
+       "check fallback compression used");
+    is_image($packim, $im, "compare to original");
+  }
+
+  { # fallback handling of tiff
+    is(Imager::i_tiff_has_compression('none'), 1, "can always do uncompresed");
+    is(Imager::i_tiff_has_compression('xxx'), '', "can't do xxx compression");
+  }
 }
 
index 9038222..061eee3 100644 (file)
@@ -27,7 +27,7 @@ test($imbase, {type=>'conv', coef=>[ -0.5, 1, -0.5, ], },
   my $imbase16 = $imbase->to_rgb16;
   my $gauss16 = test($imbase16,  {type=>'gaussian', stddev=>5 },
                     'testout/t61_gaussian16.ppm');
-  is_image_similar($gauss, $gauss16, 200000, "8 and 16 gaussian match");
+  is_image_similar($gauss, $gauss16, 250000, "8 and 16 gaussian match");
 }
 
 
index a7a79ce..497edc1 100644 (file)
@@ -175,6 +175,3 @@ sub mask_tests {
 
 }
 
-sub NCF {
-  return Imager::Color::Float->new(@_);
-}
diff --git a/testimg/alpha.tif b/testimg/alpha.tif
new file mode 100644 (file)
index 0000000..b56a9a0
Binary files /dev/null and b/testimg/alpha.tif differ
diff --git a/testimg/comp4t.tif b/testimg/comp4t.tif
new file mode 100644 (file)
index 0000000..0140a27
Binary files /dev/null and b/testimg/comp4t.tif differ
diff --git a/testimg/gralpha.tif b/testimg/gralpha.tif
new file mode 100644 (file)
index 0000000..7fa6def
Binary files /dev/null and b/testimg/gralpha.tif differ
diff --git a/testimg/grey16.tif b/testimg/grey16.tif
new file mode 100755 (executable)
index 0000000..9dc3a21
Binary files /dev/null and b/testimg/grey16.tif differ
diff --git a/testimg/grey32.tif b/testimg/grey32.tif
new file mode 100644 (file)
index 0000000..a3844b8
Binary files /dev/null and b/testimg/grey32.tif differ
diff --git a/testimg/imager.pbm b/testimg/imager.pbm
new file mode 100644 (file)
index 0000000..610caf3
Binary files /dev/null and b/testimg/imager.pbm differ
diff --git a/testimg/imager.tif b/testimg/imager.tif
new file mode 100644 (file)
index 0000000..8c2bfed
Binary files /dev/null and b/testimg/imager.tif differ
diff --git a/testimg/pengtile.tif b/testimg/pengtile.tif
new file mode 100644 (file)
index 0000000..dd4229f
Binary files /dev/null and b/testimg/pengtile.tif differ
diff --git a/testimg/rgb16.tif b/testimg/rgb16.tif
new file mode 100644 (file)
index 0000000..19fa32b
Binary files /dev/null and b/testimg/rgb16.tif differ
diff --git a/testimg/rgb16t.tif b/testimg/rgb16t.tif
new file mode 100644 (file)
index 0000000..4ae3247
Binary files /dev/null and b/testimg/rgb16t.tif differ
diff --git a/testimg/scmyka16.tif b/testimg/scmyka16.tif
new file mode 100644 (file)
index 0000000..e24fbd4
Binary files /dev/null and b/testimg/scmyka16.tif differ
diff --git a/testimg/srgba16.tif b/testimg/srgba16.tif
new file mode 100644 (file)
index 0000000..e6d8e5f
Binary files /dev/null and b/testimg/srgba16.tif differ
diff --git a/testimg/srgba32.tif b/testimg/srgba32.tif
new file mode 100644 (file)
index 0000000..7725f5f
Binary files /dev/null and b/testimg/srgba32.tif differ
index afa7d87..3214858 100644 (file)
Binary files a/testimg/srgbaa.tif and b/testimg/srgbaa.tif differ
diff --git a/tiff.c b/tiff.c
index 8406ca6..7c5548e 100644 (file)
--- a/tiff.c
+++ b/tiff.c
@@ -1,8 +1,14 @@
 #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
+#include "tiffconf.h"
+static int TIFFIsCODECConfigured(uint16 scheme);
+#endif
+
 /*
 =head1 NAME
 
@@ -35,11 +41,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 +65,102 @@ 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_CCITT_T4 },
+    { "fax4",     COMPRESSION_CCITTFAX4 },
+    { "t6",       COMPRESSION_CCITT_T6 },
+    { "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);
+
+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 +177,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 +197,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) {
@@ -154,56 +259,151 @@ comp_munmap(thandle_t h, tdata_t p, toff_t off) {
 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 "));
 
-  /* separated defaults to CMYK, but if the user is using some strange
-     ink system we can't work out the color anyway */
-  if (photometric == PHOTOMETRIC_SEPARATED && channels >= 4) {
-    /* TIFF can have more than one alpha channel on an image,
-       but Imager can't, only store the first one */
-    
-    channels = channels == 4 ? 3 : 4;
-
-    /* unfortunately the RGBA functions don't try to deal with the alpha
-       channel on CMYK images, at some point I'm planning on expanding
-       TIFF support to handle 16-bit/sample images and I'll deal with
-       it then */
+  /* 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;
+    }
 
-  /* 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 */
-  if (channels > 4)
-    channels = 4;
-
-  if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
-    channels = 3;
-    im = i_img_pal_new(width, height, channels, 256);
+    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)
@@ -212,6 +412,7 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
   /* 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);
@@ -262,172 +463,14 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
     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) {
-      if (allow_incomplete) {
-        i_tags_setn(&im->tags, "i_lines_read", row);
-      }
-      else {
-        i_img_destroy(im);
-        _TIFFfree(buffer);
-        return NULL;
-      }
-      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)) {
-          if (allow_incomplete) {
-            i_tags_setn(&im->tags, "i_lines_read", row);
-            error++;
-            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;
-          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_setn(&im->tags, "i_incomplete", 1);
-  }
-  if (raster)
-    _TIFFfree( raster );
-
   return im;
 }
 
@@ -673,180 +716,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)
+       && TIFFIsCODECConfigured(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
+       && TIFFIsCODECConfigured((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 TIFFIsCODECConfigured(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);
@@ -872,113 +827,482 @@ 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);
+  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;
     }
+  }
 
-    if (!TIFFWriteDirectory(tif)) {
-      i_push_error(0, "Cannot write TIFF directory");
-      TIFFClose(tif);
+  _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);
+
+  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;
     }
@@ -1194,40 +1518,1039 @@ 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;
+
+  im = make_rgb(tif, width, height, &alpha_chan);
+  if (!im)
+    return NULL;
+
+  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;
+  }
+
+  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 TIFFLIB_VERSION < 20031121
+
+int TIFFIsCODECConfigured(uint16 scheme) {
+  switch (scheme) {
+ case COMPRESSION_NONE:
+#if PACKBITS_SUPPORT
+ case COMPRESSION_PACKBITS:
+#endif
+
+#if CCITT_SUPPORT
+ case COMPRESSION_CCITTRLE:
+ case COMPRESSION_CCITTRLEW:
+ case COMPRESSION_CCITTFAX3:
+ case COMPRESSION_CCITTFAX4:
+#endif
+
+#if JPEG_SUPPORT
+ case COMPRESSION_JPEG:
+#endif
+
+#if LZW_SUPPORT
+ case COMPRESSION_LZW:
+#endif
+
+#if ZIP_SUPPORT
+ case COMPRESSION_DEFLATE:
+ case COMPRESSION_ADOBE_DEFLATE:
+#endif
+    return 1;
+
+  default:
+    return 0;
+  }
+}
+
+#endif
+
 /*
 =back
 
 =head1 AUTHOR
 
-Arnar M. Hrafnkelsson <addi@umich.edu>
+Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tony@imager.perl.org>
 
 =head1 SEE ALSO