enhanced iolayer
authorTony Cook <tony@develop=help.com>
Fri, 4 Jan 2002 02:33:55 +0000 (02:33 +0000)
committerTony Cook <tony@develop=help.com>
Fri, 4 Jan 2002 02:33:55 +0000 (02:33 +0000)
multi image/file tiff support

21 files changed:
Changes
Imager.pm
Imager.xs
TODO
bmp.c
gif.c
image.h
iolayer.c
iolayer.h
jpeg.c
lib/Imager/Font.pm
png.c
pnm.c
raw.c
t/t07iolayer.t
t/t105gif.t
t/t106tiff.t
t/t35ttfont.t
t/t50basicoo.t
tga.c
tiff.c

diff --git a/Changes b/Changes
index cb1d692..31a0173 100644 (file)
--- a/Changes
+++ b/Changes
@@ -566,8 +566,18 @@ Revision history for Perl extension Imager.
         - applied T1 afm patch from Claes Jacobsson
         - split IM_INCPATH and IM_LIBPATH with $Config{path_sep}, so they
           work on Windows
-                               - Added memory pools for easy cleanup of temp buffers
-                               - Added read support for sgi .rgb files.
+        - Added memory pools for easy cleanup of temp buffers
+        - Added read support for sgi .rgb files.
+        - io_new_fd() now creates a FDSEEK io object
+        - implemented i_readgif_wiol()
+        - Imager->read() now uses i_readgif_wiol();
+        - extend callback iolayers at C and Perl levels
+        - implemented i_writegif_wiol()
+        - split out Perl iolayer initialization into private methods
+        - add tests for each type of iolayer in t50basicoo.t
+        - read/write multi-image tiff files
+        - tests in t50basicoo.t for multi-image/file
+
 =================================================================
 
         For latest versions check the Imager-devel pages:
index 4dfb83d..9c58571 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -85,6 +85,7 @@ use Imager::Font;
                i_writepng_wiol
 
                i_readgif
+               i_readgif_wiol
                i_readgif_callback
                i_writegif
                i_writegifmc
@@ -853,12 +854,108 @@ sub deltag {
   }
 }
 
+my @needseekcb = qw/tiff/;
+my %needseekcb = map { $_, $_ } @needseekcb;
+
+
+sub _get_reader_io {
+  my ($self, $input, $type) = @_;
+
+  if ($input->{fd}) {
+    return io_new_fd($input->{fd});
+  }
+  elsif ($input->{fh}) {
+    my $fd = fileno($input->{fh});
+    unless ($fd) {
+      $self->_set_error("Handle in fh option not opened");
+      return;
+    }
+    return io_new_fd($fd);
+  }
+  elsif ($input->{file}) {
+    my $file = IO::File->new($input->{file}, "r");
+    unless ($file) {
+      $self->_set_error("Could not open $input->{file}: $!");
+      return;
+    }
+    binmode $file;
+    return (io_new_fd(fileno($file)), $file);
+  }
+  elsif ($input->{data}) {
+    return io_new_buffer($input->{data});
+  }
+  elsif ($input->{callback} || $input->{readcb}) {
+    if ($needseekcb{$type} && !$input->{seekcb}) {
+      $self->_set_error("Format $type needs a seekcb parameter");
+    }
+    if ($input->{maxbuffer}) {
+      return io_new_cb($input->{writecb},
+                       $input->{callback} || $input->{readcb},
+                       $input->{seekcb}, $input->{closecb},
+                       $input->{maxbuffer});
+    }
+    else {
+      return io_new_cb($input->{writecb},
+                       $input->{callback} || $input->{readcb},
+                       $input->{seekcb}, $input->{closecb});
+    }
+  }
+  else {
+    $self->_set_error("file/fd/fh/data/callback parameter missing");
+    return;
+  }
+}
+
+sub _get_writer_io {
+  my ($self, $input, $type) = @_;
+
+  if ($input->{fd}) {
+    return io_new_fd($input->{fd});
+  }
+  elsif ($input->{fh}) {
+    my $fd = fileno($input->{fh});
+    unless ($fd) {
+      $self->_set_error("Handle in fh option not opened");
+      return;
+    }
+    return io_new_fd($fd);
+  }
+  elsif ($input->{file}) {
+    my $fh = new IO::File($input->{file},"w+");
+    unless ($fh) { 
+      $self->_set_error("Could not open file $input->{file}: $!");
+      return;
+    }
+    binmode($fh) or die;
+    return (io_new_fd(fileno($fh)), $fh);
+  }
+  elsif ($input->{data}) {
+    return io_new_bufchain();
+  }
+  elsif ($input->{callback} || $input->{writecb}) {
+    if ($input->{maxbuffer}) {
+      return io_new_cb($input->{callback} || $input->{writecb},
+                       $input->{readcb},
+                       $input->{seekcb}, $input->{closecb},
+                       $input->{maxbuffer});
+    }
+    else {
+      return io_new_cb($input->{callback} || $input->{writecb},
+                       $input->{readcb},
+                       $input->{seekcb}, $input->{closecb});
+    }
+  }
+  else {
+    $self->_set_error("file/fd/fh/data/callback parameter missing");
+    return;
+  }
+}
+
 # Read an image from file
 
 sub read {
   my $self = shift;
   my %input=@_;
-  my ($fh, $fd, $IO);
 
   if (defined($self->{IMG})) {
     # let IIM_DESTROY do the destruction, since the image may be
@@ -867,21 +964,6 @@ sub read {
     undef($self->{IMG});
   }
 
-  if (!$input{fd} and !$input{file} and !$input{data}) {
-    $self->{ERRSTR}='no file, fd or data parameter'; return undef;
-  }
-  if ($input{file}) {
-    $fh = new IO::File($input{file},"r");
-    if (!defined $fh) {
-      $self->{ERRSTR}='Could not open file'; return undef;
-    }
-    binmode($fh);
-    $fd = $fh->fileno();
-  }
-  if ($input{fd}) {
-    $fd=$input{fd};
-  }
-
   # FIXME: Find the format here if not specified
   # yes the code isn't here yet - next week maybe?
   # Next week?  Are you high or something?  That comment
@@ -891,15 +973,20 @@ sub read {
   if (!$input{'type'} and $input{file}) {
     $input{'type'}=$FORMATGUESS->($input{file});
   }
+  unless ($input{'type'}) {
+    $self->_set_error('type parameter missing and not possible to guess from extension'); 
+    return undef;
+  }
   if (!$formats{$input{'type'}}) {
     $self->{ERRSTR}='format not supported'; return undef;
   }
 
-  my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1, rgb=>1);
+  my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1, rgb=>1, gif=>1);
 
   if ($iolready{$input{'type'}}) {
     # Setup data source
-    $IO = defined $fd ? io_new_fd($fd) : io_new_buffer($input{data});
+    my ($IO, $fh) = $self->_get_reader_io(\%input, $input{'type'})
+      or return;
 
     if ( $input{'type'} eq 'jpeg' ) {
       ($self->{IMG},$self->{IPTCRAW})=i_readjpeg_wiol( $IO );
@@ -946,6 +1033,29 @@ sub read {
       $self->{DEBUG} && print "loading a bmp file\n";
     }
 
+    if ( $input{'type'} eq 'gif' ) {
+      if ($input{colors} && !ref($input{colors})) {
+       # must be a reference to a scalar that accepts the colour map
+       $self->{ERRSTR} = "option 'colors' must be a scalar reference";
+       return undef;
+      }
+      if ($input{colors}) {
+        my $colors;
+        ($self->{IMG}, $colors) =i_readgif_wiol( $IO );
+        if ($colors) {
+          ${ $input{colors} } = [ map { NC(@$_) } @$colors ];
+        }
+      }
+      else {
+        $self->{IMG} =i_readgif_wiol( $IO );
+      }
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}=$self->_error_as_msg();
+       return undef;
+      }
+      $self->{DEBUG} && print "loading a gif file\n";
+    }
+
     if ( $input{'type'} eq 'tga' ) {
       $self->{IMG}=i_readtga_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed
       if ( !defined($self->{IMG}) ) {
@@ -1003,6 +1113,7 @@ sub read {
       return undef;
     }
 
+    my ($fh, $fd);
     if ($input{file}) {
       $fh = new IO::File($input{file},"r");
       if (!defined $fh) {
@@ -1062,13 +1173,13 @@ sub write {
             compress=>1,
             wierdpack=>0,
             fax_fine=>1, @_);
-  my ($fh, $rc, $fd, $IO);
+  my $rc;
 
-  my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1 ); # this will be SO MUCH BETTER once they are all in there
+  my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1, 
+                 gif=>1 ); # this will be SO MUCH BETTER once they are all in there
 
   unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; }
 
-  if (!$input{file} and !$input{'fd'} and !$input{'data'}) { $self->{ERRSTR}='file/fd/data parameter missing'; return undef; }
   if (!$input{'type'} and $input{file}) { 
     $input{'type'}=$FORMATGUESS->($input{file});
   }
@@ -1079,21 +1190,11 @@ sub write {
 
   if (!$formats{$input{'type'}}) { $self->{ERRSTR}='format not supported'; return undef; }
 
-  if (exists $input{'fd'}) {
-    $fd=$input{'fd'};
-  } elsif (exists $input{'data'}) {
-    $IO = Imager::io_new_bufchain();
-  } else {
-    $fh = new IO::File($input{file},"w+");
-    if (!defined $fh) { $self->{ERRSTR}='Could not open file'; return undef; }
-    binmode($fh) or die;
-    $fd = $fh->fileno();
-  }
+  my ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'})
+    or return undef;
 
+  # this conditional is probably obsolete
   if ($iolready{$input{'type'}}) {
-    if (defined $fd) {
-      $IO = io_new_fd($fd);
-    }
 
     if ($input{'type'} eq 'tiff') {
       if (defined $input{class} && $input{class} eq 'fax') {
@@ -1144,6 +1245,19 @@ sub write {
        return undef;
       }
       $self->{DEBUG} && print "writing a tga file\n";
+    } elsif ( $input{'type'} eq 'gif' ) {
+      # compatibility with the old interfaces
+      if ($input{gifquant} eq 'lm') {
+        $input{make_colors} = 'addi';
+        $input{translate} = 'perturb';
+        $input{perturb} = $input{lmdither};
+      } elsif ($input{gifquant} eq 'gen') {
+        # just pass options through
+      } else {
+        $input{make_colors} = 'webmap'; # ignored
+        $input{translate} = 'giflib';
+      }
+      $rc = i_writegif_wiol($IO, \%input, $self->{IMG});
     }
 
     if (exists $input{'data'}) {
@@ -1155,98 +1269,51 @@ sub write {
       ${$input{data}} = $data;
     }
     return $self;
-  } else {
-    if ( $input{'type'} eq 'gif' ) {
-      if (not $input{gifplanes}) {
-       my $gp;
-       my $count=i_count_colors($self->{IMG}, 256);
-       $gp=8 if $count == -1;
-       $gp=1 if not $gp and $count <= 2;
-       $gp=2 if not $gp and $count <= 4;
-       $gp=3 if not $gp and $count <= 8;
-       $gp=4 if not $gp and $count <= 16;
-       $gp=5 if not $gp and $count <= 32;
-       $gp=6 if not $gp and $count <= 64;
-       $gp=7 if not $gp and $count <= 128;
-       $input{gifplanes} = $gp || 8;
-      }
-
-      if ($input{gifplanes}>8) {
-       $input{gifplanes}=8;
-      }
-      if ($input{gifquant} eq 'gen' || $input{callback}) {
-
-
-       if ($input{gifquant} eq 'lm') {
-
-         $input{make_colors} = 'addi';
-         $input{translate} = 'perturb';
-         $input{perturb} = $input{lmdither};
-       } elsif ($input{gifquant} eq 'gen') {
-         # just pass options through
-       } else {
-         $input{make_colors} = 'webmap'; # ignored
-         $input{translate} = 'giflib';
-       }
-
-       if ($input{callback}) {
-         defined $input{maxbuffer} or $input{maxbuffer} = -1;
-         $rc = i_writegif_callback($input{callback}, $input{maxbuffer},
-                                   \%input, $self->{IMG});
-       } else {
-         $rc = i_writegif_gen($fd, \%input, $self->{IMG});
-       }
-
-      } elsif ($input{gifquant} eq 'lm') {
-       $rc=i_writegif($self->{IMG},$fd,$input{gifplanes},$input{lmdither},$input{lmfixed});
-      } else {
-       $rc=i_writegifmc($self->{IMG},$fd,$input{gifplanes});
-      }
-      if ( !defined($rc) ) {
-       $self->{ERRSTR} = "Writing GIF file: "._error_as_msg(); return undef;
-      }
-      $self->{DEBUG} && print "writing a gif file\n";
-
-    }
   }
+
   return $self;
 }
 
 sub write_multi {
   my ($class, $opts, @images) = @_;
 
+  if (!$opts->{'type'} && $opts->{'file'}) {
+    $opts->{'type'} = $FORMATGUESS->($opts->{'file'});
+  }
+  unless ($opts->{'type'}) {
+    $class->_set_error('type parameter missing and not possible to guess from extension');
+    return;
+  }
+  # translate to ImgRaw
+  if (grep !UNIVERSAL::isa($_, 'Imager') || !$_->{IMG}, @images) {
+    $class->_set_error('Usage: Imager->write_multi({ options }, @images)');
+    return 0;
+  }
+  my @work = map $_->{IMG}, @images;
+  my ($IO, $file) = $class->_get_writer_io($opts, $opts->{'type'})
+    or return undef;
   if ($opts->{'type'} eq 'gif') {
     my $gif_delays = $opts->{gif_delays};
     local $opts->{gif_delays} = $gif_delays;
-    unless (ref $opts->{gif_delays}) {
+    if ($opts->{gif_delays} && !ref $opts->{gif_delays}) {
       # assume the caller wants the same delay for each frame
       $opts->{gif_delays} = [ ($gif_delays) x @images ];
     }
-    # translate to ImgRaw
-    if (grep !UNIVERSAL::isa($_, 'Imager') || !$_->{IMG}, @images) {
-      $ERRSTR = "Usage: Imager->write_multi({ options }, @images)";
-      return 0;
-    }
-    my @work = map $_->{IMG}, @images;
-    if ($opts->{callback}) {
-      # Note: you may need to fix giflib for this one to work
-      my $maxbuffer = $opts->{maxbuffer};
-      defined $maxbuffer or $maxbuffer = -1; # max by default
-      return i_writegif_callback($opts->{callback}, $maxbuffer,
-                                $opts, @work);
-    }
-    if ($opts->{fd}) {
-      return i_writegif_gen($opts->{fd}, $opts, @work);
+    my $res = i_writegif_wiol($IO, $opts, @work);
+    $res or $class->_set_error($class->_error_as_msg());
+    return $res;
+  }
+  elsif ($opts->{'type'} eq 'tiff') {
+    my $res;
+    $opts->{fax_fine} = 1 unless exists $opts->{fax_fine};
+    if ($opts->{'class'} && $opts->{'class'} eq 'fax') {
+      $res = i_writetiff_multi_wiol_faxable($IO, $opts->{fax_fine}, @work);
     }
     else {
-      my $fh = IO::File->new($opts->{file}, "w+");
-      unless ($fh) {
-       $ERRSTR = "Error creating $opts->{file}: $!";
-       return 0;
-      }
-      binmode($fh);
-      return i_writegif_gen(fileno($fh), $opts, @work);
+      $res = i_writetiff_multi_wiol($IO, @work);
     }
+    $res or $class->_set_error($class->_error_as_msg());
+    return $res;
   }
   else {
     $ERRSTR = "Sorry, write_multi doesn't support $opts->{'type'} yet";
@@ -1267,52 +1334,24 @@ sub read_multi {
     $ERRSTR = "No type parameter supplied and it couldn't be guessed";
     return;
   }
-  my $fd;
-  my $file;
-  if ($opts{file}) {
-    $file = IO::File->new($opts{file}, "r");
-    unless ($file) {
-      $ERRSTR = "Could not open file $opts{file}: $!";
-      return;
-    }
-    binmode $file;
-    $fd = fileno($file);
-  }
-  elsif ($opts{fh}) {
-    $fd = fileno($opts{fh});
-    unless ($fd) {
-      $ERRSTR = "File handle specified with fh option not open";
-      return;
-    }
-  }
-  elsif ($opts{fd}) {
-    $fd = $opts{fd};
-  }
-  elsif ($opts{callback} || $opts{data}) {
-    # don't fail here
-  }
-  else {
-    $ERRSTR = "You need to specify one of file, fd, fh, callback or data";
-    return;
-  }
 
+  my ($IO, $file) = $class->_get_reader_io(\%opts, $opts{'type'})
+    or return;
   if ($opts{'type'} eq 'gif') {
     my @imgs;
-    if ($fd) {
-      @imgs = i_readgif_multi($fd);
+    @imgs = i_readgif_multi_wiol($IO);
+    if (@imgs) {
+      return map { 
+        bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' 
+      } @imgs;
     }
     else {
-      if (Imager::i_giflib_version() < 4.0) {
-        $ERRSTR = "giflib3.x does not support callbacks";
-        return;
-      }
-      if ($opts{callback}) {
-        @imgs = i_readgif_multi_callback($opts{callback})
-      }
-      else {
-        @imgs = i_readgif_multi_scalar($opts{data});
-      }
+      $ERRSTR = _error_as_msg();
+      return;
     }
+  }
+  elsif ($opts{'type'} eq 'tiff') {
+    my @imgs = i_readtiff_multi_wiol($IO, -1);
     if (@imgs) {
       return map { 
         bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' 
@@ -2230,6 +2269,17 @@ sub errstr {
   ref $_[0] ? $_[0]->{ERRSTR} : $ERRSTR
 }
 
+sub _set_error {
+  my ($self, $msg) = @_;
+
+  if (ref $self) {
+    $self->{ERRSTR} = $msg;
+  }
+  else {
+    $ERRSTR = $msg;
+  }
+}
+
 # Default guess for the type of an image from extension
 
 sub def_guess_type {
@@ -2244,6 +2294,7 @@ sub def_guess_type {
   return 'tga'  if ($ext eq "tga");
   return 'rgb'  if ($ext eq "rgb");
   return 'gif'  if ($ext eq "gif");
+  return 'raw'  if ($ext eq "raw");
   return ();
 }
 
@@ -2327,9 +2378,8 @@ Imager - Perl extension for Generating 24 bit Images
 
 =head1 SYNOPSIS
 
-  use Imager qw(init);
+  use Imager;
 
-  init();
   $img = Imager->new();
   $img->open(file=>'image.ppm',type=>'pnm')
     || print "failed: ",$img->{ERRSTR},"\n";
@@ -2433,6 +2483,105 @@ downwards.
 
 =head2 Reading and writing images
 
+You can read and write a variety of images formats, assuming you have
+the appropriate libraries, and images can be read or written to/from
+files, file handles, file descriptors, scalars, or through callbacks.
+
+To see which image formats Imager is compiled to support the following
+code snippet is sufficient:
+
+  use Imager;
+  print join " ", keys %Imager::formats;
+
+This will include some other information identifying libraries rather
+than file formats.
+
+Reading writing to and from files is simple, use the C<read()>
+method to read an image:
+
+  my $img = Imager->new;
+  $img->read(file=>$filename, type=>$type)
+    or die "Cannot read $filename: ", $img->errstr;
+
+and the C<write()> method to write an image:
+
+  $img->write(file=>$filename, type=>$type)
+    or die "Cannot write $filename: ", $img->errstr;
+
+If the I<filename> includes an extension that Imager recognizes, then
+you don't need the I<type>, but you may want to provide one anyway.
+Imager currently does not check the files magic to determine the
+format.  It is possible to override the method for determining the 
+filetype from the filename.  If the data is given in another form than
+a file name a 
+
+When you read an image, Imager may set some tags, possibly including
+information about the spatial resolution, textual information, and
+animation information.  See L</Tags> for specifics.
+
+When reading or writing you can specify one of a variety of sources or
+targets:
+
+=over
+
+=item file
+
+The C<file> parameter is the name of the image file to be written to
+or read from.  If Imager recognizes the extension of the file you do
+not need to supply a C<type>.
+
+=item fh
+
+C<fh> is a file handle, typically either returned from
+C<<IO::File->new()>>, or a glob from an C<open> call.  You should call
+C<binmode> on the handle before passing it to Imager.
+
+=item fd
+
+C<fd> is a file descriptor.  You can get this by calling the
+C<fileno()> function on a file handle, or by using one of the standard
+file descriptor numbers.
+
+=item data
+
+When reading data, C<data> is a scalar containing the image file data,
+when writing, C<data> is a reference to the scalar to save the image
+file data too.  For GIF images you will need giflib 4 or higher, and
+you may need to patch giflib to use this option for writing.
+
+=item callback
+
+Imager will make calls back to your supplied coderefs to read, write
+and seek from/to/through the image file.
+
+When reading from a file you can use either C<callback> or C<readcb>
+to supply the read callback, and when writing C<callback> or
+C<writecb> to supply the write callback.
+
+When writing you can also supply the C<maxbuffer> option to set the
+maximum amount of data that will be buffered before your write
+callback is called.  Note: the amount of data supplied to your
+callback can be smaller or larger than this size.
+
+The read callback is called with 2 parameters, the minimum amount of
+data required, and the maximum amount that Imager will store in it's C
+level buffer.  You may want to return the minimum if you have a slow
+data source, or the maximum if you have a fast source and want to
+prevent many calls to your perl callback.  The read data should be
+returned as a scalar.
+
+Your write callback takes exactly one parameter, a scalar containing
+the data to be written.  Return true for success.
+
+The seek callback takes 2 parameters, a I<POSITION>, and a I<WHENCE>,
+defined in the same way as perl's seek function.
+
+You can also supply a C<closecb> which is called with no parameters
+when there is no more data to be written.  This could be used to flush
+buffered data.
+
+=back
+
 C<$img-E<gt>read()> generally takes two parameters, 'file' and 'type'.
 If the type of the file can be determined from the suffix of the file
 it can be omitted.  Format dependant parameters are: For images of
index 1b7ec5b..1d8ad77 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -130,6 +130,297 @@ static int write_callback(char *userdata, char const *data, int size) {
   return success;
 }
 
+#define CBDATA_BUFSIZE 8192
+
+struct cbdata {
+  /* the SVs we use to call back to Perl */
+  SV *writecb;
+  SV *readcb;
+  SV *seekcb;
+  SV *closecb;
+
+  /* we need to remember whether the buffer contains write data or 
+     read data
+   */
+  int reading;
+  int writing;
+
+  /* how far we've read into the buffer (not used for writing) */
+  int where;
+
+  /* the amount of space used/data available in the buffer */
+  int used;
+
+  /* the maximum amount to fill the buffer before flushing
+     If any write is larger than this then the buffer is flushed and 
+     the full write is performed.  The write is _not_ split into 
+     maxwrite sized calls
+   */
+  int maxlength;
+
+  char buffer[CBDATA_BUFSIZE];
+};
+
+/* 
+
+call_writer(cbd, buf, size)
+
+Low-level function to call the perl writer callback.
+
+*/
+
+static ssize_t call_writer(struct cbdata *cbd, void const *buf, size_t size) {
+  int count;
+  int success;
+  SV *sv;
+  dSP;
+
+  if (!SvOK(cbd->writecb))
+    return -1;
+
+  ENTER;
+  SAVETMPS;
+  EXTEND(SP, 1);
+  PUSHMARK(SP);
+  PUSHs(sv_2mortal(newSVpv((char *)buf, size)));
+  PUTBACK;
+
+  count = perl_call_sv(cbd->writecb, G_SCALAR);
+
+  SPAGAIN;
+  if (count != 1)
+    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+  sv = POPs;
+  success = SvTRUE(sv);
+
+
+  PUTBACK;
+  FREETMPS;
+  LEAVE;
+
+  return success ? size : 0;
+}
+
+static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size, 
+                           size_t maxread) {
+  int count;
+  int result;
+  SV *data;
+  dSP;
+
+  if (!SvOK(cbd->readcb))
+    return -1;
+
+  ENTER;
+  SAVETMPS;
+  EXTEND(SP, 2);
+  PUSHMARK(SP);
+  PUSHs(sv_2mortal(newSViv(size)));
+  PUSHs(sv_2mortal(newSViv(maxread)));
+  PUTBACK;
+
+  count = perl_call_sv(cbd->readcb, G_SCALAR);
+
+  SPAGAIN;
+
+  if (count != 1)
+    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+  data = POPs;
+
+  if (SvOK(data)) {
+    STRLEN len;
+    char *ptr = SvPV(data, len);
+    if (len > maxread)
+      croak("Too much data returned in reader callback");
+    
+    memcpy(buf, ptr, len);
+    result = len;
+  }
+  else {
+    result = -1;
+  }
+
+  PUTBACK;
+  FREETMPS;
+  LEAVE;
+
+  return result;
+}
+
+static ssize_t write_flush(struct cbdata *cbd) {
+  ssize_t result;
+
+  result = call_writer(cbd, cbd->buffer, cbd->used);
+  cbd->used = 0;
+  return result;
+}
+
+static off_t io_seeker(void *p, off_t offset, int whence) {
+  struct cbdata *cbd = p;
+  int count;
+  off_t result;
+  dSP;
+
+  if (!SvOK(cbd->seekcb))
+    return -1;
+
+  if (cbd->writing) {
+    if (cbd->used && write_flush(cbd) <= 0)
+      return -1;
+    cbd->writing = 0;
+  }
+  if (whence == SEEK_CUR && cbd->reading && cbd->where != cbd->used) {
+    offset -= cbd->where - cbd->used;
+  }
+  cbd->reading = 0;
+  cbd->where = cbd->used = 0;
+  
+  ENTER;
+  SAVETMPS;
+  EXTEND(SP, 2);
+  PUSHMARK(SP);
+  PUSHs(sv_2mortal(newSViv(offset)));
+  PUSHs(sv_2mortal(newSViv(whence)));
+  PUTBACK;
+
+  count = perl_call_sv(cbd->seekcb, G_SCALAR);
+
+  SPAGAIN;
+
+  if (count != 1)
+    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+  result = POPi;
+
+  PUTBACK;
+  FREETMPS;
+  LEAVE;
+
+  return result;
+}
+
+static ssize_t io_writer(void *p, void const *data, size_t size) {
+  struct cbdata *cbd = p;
+
+  /*printf("io_writer(%p, %p, %u)\n", p, data, size);*/
+  if (!cbd->writing) {
+    if (cbd->reading && cbd->where < cbd->used) {
+      /* we read past the place where the caller expected us to be
+         so adjust our position a bit */
+        *(char *)0 = 0;
+      if (io_seeker(p, cbd->where - cbd->used, SEEK_CUR) < 0) {
+        return -1;
+      }
+      cbd->reading = 0;
+    }
+    cbd->where = cbd->used = 0;
+  }
+  cbd->writing = 1;
+  if (cbd->used && cbd->used + size > cbd->maxlength) {
+    if (write_flush(cbd) <= 0) {
+      return 0;
+    }
+    cbd->used = 0;
+  }
+  if (cbd->used+size <= cbd->maxlength) {
+    memcpy(cbd->buffer + cbd->used, data, size);
+    cbd->used += size;
+    return size;
+  }
+  /* it doesn't fit - just pass it up */
+  return call_writer(cbd, data, size);
+}
+
+static ssize_t io_reader(void *p, void *data, size_t size) {
+  struct cbdata *cbd = p;
+  ssize_t total;
+  char *out = data; /* so we can do pointer arithmetic */
+  int i;
+
+  if (cbd->writing) {
+    if (write_flush(cbd) <= 0)
+      return 0;
+    cbd->writing = 0;
+  }
+
+  cbd->reading = 1;
+  if (size <= cbd->used - cbd->where) {
+    /* simplest case */
+    memcpy(data, cbd->buffer+cbd->where, size);
+    cbd->where += size;
+    return size;
+  }
+  total = 0;
+  memcpy(out, cbd->buffer + cbd->where, cbd->used - cbd->where);
+  total += cbd->used - cbd->where;
+  size  -= cbd->used - cbd->where;
+  out   += cbd->used - cbd->where;
+  if (size < sizeof(cbd->buffer)) {
+    int did_read;
+    int copy_size;
+    while (size
+          && (did_read = call_reader(cbd, cbd->buffer, size, 
+                                   sizeof(cbd->buffer))) > 0) {
+      cbd->where = 0;
+      cbd->used  = did_read;
+
+      copy_size = min(size, cbd->used);
+      memcpy(out, cbd->buffer, copy_size);
+      cbd->where += copy_size;
+      out   += copy_size;
+      total += copy_size;
+      size  -= copy_size;
+    }
+  }
+  else {
+    /* just read the rest - too big for our buffer*/
+    int did_read;
+    while ((did_read = call_reader(cbd, out, size, size)) > 0) {
+      size  -= did_read;
+      total += did_read;
+      out   += did_read;
+    }
+  }
+
+  return total;
+}
+
+static void io_closer(void *p) {
+  struct cbdata *cbd = p;
+
+  if (cbd->writing && cbd->used > 0) {
+    write_flush(cbd);
+    cbd->writing = 0;
+  }
+
+  if (SvOK(cbd->closecb)) {
+    dSP;
+
+    ENTER;
+    SAVETMPS;
+    PUSHMARK(SP);
+    PUTBACK;
+
+    perl_call_sv(cbd->closecb, G_VOID);
+
+    SPAGAIN;
+    PUTBACK;
+    FREETMPS;
+    LEAVE;
+  }
+}
+
+static void io_destroyer(void *p) {
+  struct cbdata *cbd = p;
+
+  SvREFCNT_dec(cbd->writecb);
+  SvREFCNT_dec(cbd->readcb);
+  SvREFCNT_dec(cbd->seekcb);
+  SvREFCNT_dec(cbd->closecb);
+}
+
 struct value_name {
   char *name;
   int value;
@@ -717,7 +1008,34 @@ io_new_buffer(data)
          RETVAL = io_new_buffer(data, length, my_SvREFCNT_dec, ST(0));
         OUTPUT:
           RETVAL
-       
+
+Imager::IO
+io_new_cb(writecb, readcb, seekcb, closecb, maxwrite = CBDATA_BUFSIZE)
+        SV *writecb;
+        SV *readcb;
+        SV *seekcb;
+        SV *closecb;
+        int maxwrite;
+      PREINIT:
+        struct cbdata *cbd;
+      CODE:
+        cbd = mymalloc(sizeof(struct cbdata));
+        SvREFCNT_inc(writecb);
+        cbd->writecb = writecb;
+        SvREFCNT_inc(readcb);
+        cbd->readcb = readcb;
+        SvREFCNT_inc(seekcb);
+        cbd->seekcb = seekcb;
+        SvREFCNT_inc(closecb);
+        cbd->closecb = closecb;
+        cbd->reading = cbd->writing = cbd->where = cbd->used = 0;
+        if (maxwrite > CBDATA_BUFSIZE)
+          maxwrite = CBDATA_BUFSIZE;
+        cbd->maxlength = maxwrite;
+        RETVAL = io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, 
+                           io_destroyer);
+      OUTPUT:
+        RETVAL
 
 void
 io_slurp(ig)
@@ -1425,23 +1743,123 @@ i_readtiff_wiol(ig, length)
         Imager::IO     ig
               int     length
 
+void
+i_readtiff_multi_wiol(ig, length)
+        Imager::IO     ig
+              int     length
+      PREINIT:
+        i_img **imgs;
+        int count;
+        int i;
+      PPCODE:
+        imgs = i_readtiff_multi_wiol(ig, length, &count);
+        if (imgs) {
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            SV *sv = sv_newmortal();
+            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
+            PUSHs(sv);
+          }
+          myfree(imgs);
+        }
+
 
 undef_int
 i_writetiff_wiol(im, ig)
     Imager::ImgRaw     im
         Imager::IO     ig
 
+undef_int
+i_writetiff_multi_wiol(ig, ...)
+        Imager::IO     ig
+      PREINIT:
+        int i;
+        int img_count;
+        i_img **imgs;
+      CODE:
+        if (items < 2)
+          croak("Usage: i_writetiff_multi_wiol(ig, images...)");
+        img_count = items - 1;
+        RETVAL = 1;
+       if (img_count < 1) {
+         RETVAL = 0;
+         i_clear_error();
+         i_push_error(0, "You need to specify images to save");
+       }
+       else {
+          imgs = mymalloc(sizeof(i_img *) * img_count);
+          for (i = 0; i < img_count; ++i) {
+           SV *sv = ST(1+i);
+           imgs[i] = NULL;
+           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+             imgs[i] = (i_img *)SvIV((SV*)SvRV(sv));
+           }
+           else {
+             i_clear_error();
+             i_push_error(0, "Only images can be saved");
+              myfree(imgs);
+             RETVAL = 0;
+             break;
+            }
+         }
+          if (RETVAL) {
+           RETVAL = i_writetiff_multi_wiol(ig, imgs, img_count);
+          }
+         myfree(imgs);
+       }
+      OUTPUT:
+        RETVAL
+
 undef_int
 i_writetiff_wiol_faxable(im, ig, fine)
     Imager::ImgRaw     im
         Imager::IO     ig
               int     fine
 
-
-#endif /* HAVE_LIBTIFF */
-
+undef_int
+i_writetiff_multi_wiol_faxable(ig, fine, ...)
+        Imager::IO     ig
+        int fine
+      PREINIT:
+        int i;
+        int img_count;
+        i_img **imgs;
+      CODE:
+        if (items < 3)
+          croak("Usage: i_writetiff_multi_wiol_faxable(ig, fine, images...)");
+        img_count = items - 2;
+        RETVAL = 1;
+       if (img_count < 1) {
+         RETVAL = 0;
+         i_clear_error();
+         i_push_error(0, "You need to specify images to save");
+       }
+       else {
+          imgs = mymalloc(sizeof(i_img *) * img_count);
+          for (i = 0; i < img_count; ++i) {
+           SV *sv = ST(2+i);
+           imgs[i] = NULL;
+           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+             imgs[i] = (i_img *)SvIV((SV*)SvRV(sv));
+           }
+           else {
+             i_clear_error();
+             i_push_error(0, "Only images can be saved");
+              myfree(imgs);
+             RETVAL = 0;
+             break;
+            }
+         }
+          if (RETVAL) {
+           RETVAL = i_writetiff_multi_wiol_faxable(ig, imgs, img_count, fine);
+          }
+         myfree(imgs);
+       }
+      OUTPUT:
+        RETVAL
 
 
+#endif /* HAVE_LIBTIFF */
 
 
 #ifdef HAVE_LIBPNG
@@ -1626,6 +2044,59 @@ i_writegif_callback(cb, maxbuffer,...)
        cleanup_gif_opts(&opts);
        cleanup_quant_opts(&quant);
 
+undef_int
+i_writegif_wiol(ig, opts,...)
+       Imager::IO ig
+      PREINIT:
+       i_quantize quant;
+       i_gif_opts opts;
+       i_img **imgs = NULL;
+       int img_count;
+       int i;
+       HV *hv;
+      CODE:
+       if (items < 3)
+           croak("Usage: i_writegif_wiol(IO,hashref, images...)");
+       if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
+           croak("i_writegif_callback: Second argument must be a hash ref");
+       hv = (HV *)SvRV(ST(1));
+       memset(&quant, 0, sizeof(quant));
+       quant.mc_size = 256;
+       memset(&opts, 0, sizeof(opts));
+       handle_quant_opts(&quant, hv);
+       handle_gif_opts(&opts, hv);
+       img_count = items - 2;
+       RETVAL = 1;
+       if (img_count < 1) {
+         RETVAL = 0;
+       }
+       else {
+          imgs = mymalloc(sizeof(i_img *) * img_count);
+          for (i = 0; i < img_count; ++i) {
+           SV *sv = ST(2+i);
+           imgs[i] = NULL;
+           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+             imgs[i] = (i_img *)SvIV((SV*)SvRV(sv));
+           }
+           else {
+             RETVAL = 0;
+             break;
+            }
+         }
+          if (RETVAL) {
+           RETVAL = i_writegif_wiol(ig, &quant, &opts, imgs, img_count);
+          }
+         myfree(imgs);
+          if (RETVAL) {
+           copy_colors_back(hv, &quant);
+          }
+       }
+       ST(0) = sv_newmortal();
+       if (RETVAL == 0) ST(0)=&PL_sv_undef;
+       else sv_setiv(ST(0), (IV)RETVAL);
+       cleanup_gif_opts(&opts);
+       cleanup_quant_opts(&quant);
+
 void
 i_readgif(fd)
               int     fd
@@ -1674,9 +2145,53 @@ i_readgif(fd)
             PUSHs(newRV_noinc((SV*)ct));
         }
 
+void
+i_readgif_wiol(ig)
+     Imager::IO         ig
+             PREINIT:
+               int*    colour_table;
+               int     colours, q, w;
+             i_img*    rimg;
+                 SV*    temp[3];
+                 AV*    ct; 
+                 SV*    r;
+              PPCODE:
+              colour_table = NULL;
+               colours = 0;
 
+       if(GIMME_V == G_ARRAY) {
+            rimg = i_readgif_wiol(ig,&colour_table,&colours);
+        } else {
+            /* don't waste time with colours if they aren't wanted */
+            rimg = i_readgif_wiol(ig,NULL,NULL);
+        }
+       
+       if (colour_table == NULL) {
+            EXTEND(SP,1);
+            r=sv_newmortal();
+            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+            PUSHs(r);
+       } else {
+            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
+            /* I don't know if I have the reference counts right or not :( */
+            /* Neither do I :-) */
+            /* No Idea here either */
 
+            ct=newAV();
+            av_extend(ct, colours);
+            for(q=0; q<colours; q++) {
+                for(w=0; w<3; w++)
+                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
+                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
+            }
+            myfree(colour_table);
 
+            EXTEND(SP,2);
+            r = sv_newmortal();
+            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+            PUSHs(r);
+            PUSHs(newRV_noinc((SV*)ct));
+        }
 
 void
 i_readgif_scalar(...)
@@ -1838,6 +2353,26 @@ i_readgif_multi_callback(cb)
           myfree(imgs);
         }
 
+void
+i_readgif_multi_wiol(ig)
+        Imager::IO ig
+      PREINIT:
+        i_img **imgs;
+        int count;
+        int i;
+      PPCODE:
+        imgs = i_readgif_multi_wiol(ig, &count);
+        if (imgs) {
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            SV *sv = sv_newmortal();
+            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
+            PUSHs(sv);
+          }
+          myfree(imgs);
+        }
+
+
 #endif
 
 
diff --git a/TODO b/TODO
index e4aae21..2128b8d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -26,7 +26,9 @@ MultiImage & metadata support:
   interface design that takes these factors into account.
 - define common i_* tags for specifying attribute common among images
   like spatial resolution (implement for other image types, especially 
-  TIFF)
+  TIFF)  (Spatial resolution is supported for all types that support 
+  it - are there any other common properties we can add?)
+  
 
 New Features:
 - Add mng support, pcx and aalib support.
@@ -37,12 +39,11 @@ New Features:
   - FITS
   - WMF (extract bitmap data on read)
   - gzip or bzip2 compressed raw
+  - postscript for output
 
 - Transforms, interpolated multidimensional lookup tables.
   Usefull for CMYK <-> RGB table lookup.
 
-- Finish antialiased filled polygon function.
-
 - advanced font layout (spacing, kerning, alignment) (Artur?)
 
 - ways to check if characters are present in a font, eg. checking if
diff --git a/bmp.c b/bmp.c
index 2b1fd34..286b3dc 100644 (file)
--- a/bmp.c
+++ b/bmp.c
@@ -432,6 +432,8 @@ write_1bit_data(io_glue *ig, i_img *im) {
   myfree(packed);
   myfree(line);
 
+  ig->closecb(ig);
+
   return 1;
 }
 
@@ -480,6 +482,8 @@ write_4bit_data(io_glue *ig, i_img *im) {
   myfree(packed);
   myfree(line);
 
+  ig->closecb(ig);
+
   return 1;
 }
 
@@ -517,6 +521,8 @@ write_8bit_data(io_glue *ig, i_img *im) {
   }
   myfree(line);
 
+  ig->closecb(ig);
+
   return 1;
 }
 
@@ -545,6 +551,7 @@ write_24bit_data(io_glue *ig, i_img *im) {
     return 0;
   chans = im->channels >= 3 ? bgr_chans : grey_chans;
   samples = mymalloc(line_size);
+  memset(samples, 0, line_size);
   for (y = im->ysize-1; y >= 0; --y) {
     i_gsamp(im, 0, im->xsize, y, samples, chans, 3);
     if (ig->writecb(ig, samples, line_size) < 0) {
@@ -555,6 +562,8 @@ write_24bit_data(io_glue *ig, i_img *im) {
   }
   myfree(samples);
 
+  ig->closecb(ig);
+
   return 1;
 }
 
diff --git a/gif.c b/gif.c
index bb981c7..2dc13c7 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -1,6 +1,11 @@
 #include "image.h"
 #include <gif_lib.h>
-
+#ifdef _MSCVER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
 /* XXX: Reading still needs to support reading all those gif properties */
 
 /*
@@ -570,9 +575,9 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
           results = mymalloc(result_alloc * sizeof(i_img *));
         }
         else {
-          i_img **newresults;
+          /* myrealloc never fails (it just dies if it can't allocate) */
           result_alloc *= 2;
-          newresults = myrealloc(results, result_alloc * sizeof(i_img *));
+          results = myrealloc(results, result_alloc * sizeof(i_img *));
         }
       }
       results[*count-1] = img;
@@ -732,6 +737,49 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
   return results;
 }
 
+#if IM_GIFMAJOR >= 4
+/* giflib declares this incorrectly as EgifOpen */
+extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
+
+static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
+#endif
+
+/*
+=item i_readgif_multi_wiol(ig, int *count)
+
+=cut
+*/
+
+i_img **
+i_readgif_multi_wiol(io_glue *ig, int *count) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    return i_readgif_multi(ig->source.fdseek.fd, count);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    i_clear_error();
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_multi_low(GifFile, count);
+#else
+    i_clear_error();
+    i_push_error(0, "callbacks not supported with giflib3");
+    
+    return NULL;
+#endif
+  }
+}
+
 /*
 =item i_readgif_multi(int fd, int *count)
 
@@ -977,10 +1025,60 @@ i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int
 
   return result;
 #else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
   return NULL;
 #endif
 }
 
+#if IM_GIFMAJOR >= 4
+
+static int
+io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
+  io_glue *ig = (io_glue *)gft->UserData;
+
+  return ig->readcb(ig, buf, length);
+}
+
+#endif
+
+i_img *
+i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return 0;
+    }
+    return i_readgif(fd, color_table, colors);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    i_clear_error();
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_low(GifFile, color_table, colors);
+  
+#else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
+  return NULL;
+#endif
+  }
+}
+
 /*
 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
 
@@ -1149,6 +1247,10 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
   /* giflib spews for 1 colour maps, reasonable, I suppose */
   if (map_size == 1)
     map_size = 2;
+  while (i < map_size) {
+    colors[i].Red = colors[i].Green = colors[i].Blue = 0;
+    ++i;
+  }
   
   map = MakeMapObject(map_size, colors);
   mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
@@ -1705,8 +1807,6 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
 #if IM_GIFMAJOR >= 4
   GifFileType *gf;
   i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
-  /* giflib declares this incorrectly as EgifOpen */
-  extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
   int result;
 
   i_clear_error();
@@ -1725,10 +1825,74 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
   result = i_writegif_low(quant, gf, imgs, count, opts);
   return free_gen_write_data(gwd, result);
 #else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
   return 0;
 #endif
 }
 
+#if IM_GIFMAJOR >= 4
+
+static int
+io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
+  io_glue *ig = (io_glue *)gft->UserData;
+
+  return ig->writecb(ig, data, length);
+}
+
+#endif
+
+/*
+=item i_writegif_wiol(ig, quant, opts, imgs, count)
+
+=cut
+*/
+undef_int
+i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs,
+                int count) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return 0;
+    }
+    /* giflib opens the fd with fdopen(), which is then closed when fclose()
+       is called - dup it so the caller's fd isn't closed */
+    return i_writegif_gen(quant, fd, imgs, count, opts);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+    int result;
+
+    i_clear_error();
+
+    gif_set_version(quant, opts);
+
+    if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
+      return 0;
+    }
+    
+    result = i_writegif_low(quant, GifFile, imgs, count, opts);
+    
+    ig->closecb(ig);
+
+    return result;
+#else
+    i_clear_error();
+    i_push_error(0, "callbacks not supported with giflib3");
+    
+    return 0;
+#endif
+  }
+}
+
 /*
 =item gif_error_msg(int code)
 
diff --git a/image.h b/image.h
index 19bd8e8..dfaf282 100644 (file)
--- a/image.h
+++ b/image.h
@@ -525,8 +525,11 @@ undef_int i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor);
 
 #ifdef HAVE_LIBTIFF
 i_img   * i_readtiff_wiol(io_glue *ig, int length);
+i_img  ** i_readtiff_multi_wiol(io_glue *ig, int length, int *count);
 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);
 
 #endif /* HAVE_LIBTIFF */
 
@@ -537,17 +540,20 @@ undef_int i_writepng_wiol(i_img *im, io_glue *ig);
 
 #ifdef HAVE_LIBGIF
 i_img *i_readgif(int fd, int **colour_table, int *colours);
+i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours);
 i_img *i_readgif_scalar(char *data, int length, int **colour_table, int *colours);
 i_img *i_readgif_callback(i_read_callback_t callback, char *userdata, int **colour_table, int *colours);
 extern i_img **i_readgif_multi(int fd, int *count);
 extern i_img **i_readgif_multi_scalar(char *data, int length, int *count);
 extern i_img **i_readgif_multi_callback(i_read_callback_t callback, char *userdata, int *count);
+extern i_img **i_readgif_multi_wiol(io_glue *ig, int *count);
 undef_int i_writegif(i_img *im,int fd,int colors,int pixdev,int fixedlen,i_color fixed[]);
 undef_int i_writegifmc(i_img *im,int fd,int colors);
 undef_int i_writegifex(i_img *im,int fd);
 undef_int i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts);
 undef_int i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxbuffer, i_img **imgs, int count, i_gif_opts *opts);
-
+undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, 
+                          i_img **imgs, int count);
 void i_qdist(i_img *im);
 
 #endif /* HAVE_LIBGIF */
index f7f12c2..efcbf55 100644 (file)
--- a/iolayer.c
+++ b/iolayer.c
@@ -57,8 +57,11 @@ Some of these functions are internal.
 =cut
 */
 
-
-
+static ssize_t fd_read(io_glue *ig, void *buf, size_t count);
+static ssize_t fd_write(io_glue *ig, const void *buf, size_t count);
+static off_t fd_seek(io_glue *ig, off_t offset, int whence);
+static void fd_close(io_glue *ig);
+static ssize_t fd_size(io_glue *ig);
 
 /*
  * Callbacks for sources that cannot seek
@@ -95,14 +98,18 @@ static
 ssize_t 
 realseek_read(io_glue *ig, void *buf, size_t count) {
   io_ex_rseek *ier = ig->exdata;
-  int fd           = (int)ig->source.cb.p;
+  void *p          = ig->source.cb.p;
   ssize_t       rc = 0;
   size_t        bc = 0;
   char       *cbuf = buf;
 
-  IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) );
-  /* Is this a good idea? Would it be better to handle differently? skip handling? */
-  while( count!=bc && (rc = ig->source.cb.readcb(fd,cbuf+bc,count-bc))>0 ) bc+=rc;
+  IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, "
+                  "count = %d\n", fd, (long) ier->cpos, buf, count) );
+  /* Is this a good idea? Would it be better to handle differently?
+     skip handling? */
+  while( count!=bc && (rc = ig->source.cb.readcb(p,cbuf+bc,count-bc))>0 ) {
+    bc+=rc;
+  }
   
   ier->cpos += bc;
   IOL_DEB( printf("realseek_read: rc = %d, bc = %d\n", rc, bc) );
@@ -126,15 +133,19 @@ static
 ssize_t 
 realseek_write(io_glue *ig, const void *buf, size_t count) {
   io_ex_rseek *ier = ig->exdata;
-  int           fd = (int)ig->source.cb.p;
+  void          *p = ig->source.cb.p;
   ssize_t       rc = 0;
   size_t        bc = 0;
   char       *cbuf = (char*)buf; 
   
-  IOL_DEB( printf("realseek_write: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) );
-  /* Is this a good idea? Would it be better to handle differently? skip handling? */
+  IOL_DEB( printf("realseek_write: ig = %p, ier->cpos = %ld, buf = %p, "
+                  "count = %d\n", ig, (long) ier->cpos, buf, count) );
 
-  while( count!=bc && (rc = ig->source.cb.writecb(fd,cbuf+bc,count-bc))>0 ) bc+=rc;
+  /* Is this a good idea? Would it be better to handle differently? 
+     skip handling? */
+  while( count!=bc && (rc = ig->source.cb.writecb(p,cbuf+bc,count-bc))>0 ) {
+    bc+=rc;
+  }
 
   ier->cpos += bc;
   IOL_DEB( printf("realseek_write: rc = %d, bc = %d\n", rc, bc) );
@@ -145,19 +156,19 @@ realseek_write(io_glue *ig, const void *buf, size_t count) {
 /*
 =item realseek_close(ig)
 
-Closes a source that can be seeked on.  Not sure if this should be an actual close
-or not.  Does nothing for now.  Should be fixed.
+Closes a source that can be seeked on.  Not sure if this should be an
+actual close or not.  Does nothing for now.  Should be fixed.
 
    ig - data source
 
-=cut
-*/
+=cut */
 
 static
 void
 realseek_close(io_glue *ig) {
   mm_log((1, "realseek_close(ig %p)\n", ig));
-  /* FIXME: Do stuff here */
+  if (ig->source.cb.closecb)
+    ig->source.cb.closecb(ig->source.cb.p);
 }
 
 
@@ -177,38 +188,16 @@ static
 off_t
 realseek_seek(io_glue *ig, off_t offset, int whence) {
   /*  io_ex_rseek *ier = ig->exdata; Needed later */
-  int fd           = (int)ig->source.cb.p;
+  void *p = ig->source.cb.p;
   int rc;
   IOL_DEB( printf("realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
-  rc = lseek(fd, offset, whence);
+  rc = ig->source.cb.seekcb(p, offset, whence);
 
   IOL_DEB( printf("realseek_seek: rc %ld\n", (long) rc) );
   return rc;
   /* FIXME: How about implementing this offset handling stuff? */
 }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
 /*
  * Callbacks for sources that are a fixed size buffer
  */
@@ -790,26 +779,36 @@ io_obj_setp_bufchain(io_obj *io) {
 
 
 /*
-=item io_obj_setp_cb(io, p, readcb, writecb, seekcb)
+=item io_obj_setp_cb2(io, p, readcb, writecb, seekcb, closecb, destroycb)
 
 Sets an io_object for reading from a source that uses callbacks
 
    io      - io object that describes a source
-   p       - pointer to data for callbacks
-   readcb  - read callback to read from source
-   writecb - write callback to write to source
-   seekcb  - seek callback to seek on source
+   p         - pointer to data for callbacks
+   readcb    - read callback to read from source
+   writecb   - write callback to write to source
+   seekcb    - seek callback to seek on source
+   closecb   - flush any pending data
+   destroycb - release any extra resources
 
 =cut
 */
 
 void
-io_obj_setp_cb(io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb) {
-  io->cb.type    = CBSEEK;
-  io->cb.p       = p;
-  io->cb.readcb  = readcb;
-  io->cb.writecb = writecb;
-  io->cb.seekcb  = seekcb;
+io_obj_setp_cb2(io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb) {
+  io->cb.type      = CBSEEK;
+  io->cb.p         = p;
+  io->cb.readcb    = readcb;
+  io->cb.writecb   = writecb;
+  io->cb.seekcb    = seekcb;
+  io->cb.closecb   = closecb;
+  io->cb.destroycb = destroycb;
+}
+
+void
+io_obj_setp_cb(io_obj *io, void *p, readl readcb, writel writecb, 
+               seekl seekcb) {
+  io_obj_setp_cb2(io, p, readcb, writecb, seekcb, NULL, NULL);
 }
 
 /*
@@ -878,6 +877,15 @@ io_glue_commit_types(io_glue *ig) {
       ig->closecb = buffer_close;
     }
     break;
+  case FDSEEK:
+    {
+      ig->exdata  = NULL;
+      ig->readcb  = fd_read;
+      ig->writecb = fd_write;
+      ig->seekcb  = fd_seek;
+      ig->closecb = fd_close;
+      break;
+    }
   }
 }
 
@@ -972,16 +980,32 @@ io_new_fd(int fd) {
   mm_log((1, "io_new_fd(fd %d)\n", fd));
   ig = mymalloc(sizeof(io_glue));
   memset(ig, 0, sizeof(*ig));
+  ig->source.type = FDSEEK;
+  ig->source.fdseek.fd = fd;
+#if 0
 #ifdef _MSC_VER
   io_obj_setp_cb(&ig->source, (void*)fd, _read, _write, _lseek);
 #else
   io_obj_setp_cb(&ig->source, (void*)fd, read, write, lseek);
+#endif
 #endif
   mm_log((1, "(%p) <- io_new_fd\n", ig));
   return ig;
 }
 
+io_glue *io_new_cb(void *p, readl readcb, writel writecb, seekl seekcb, 
+                   closel closecb, destroyl destroycb) {
+  io_glue *ig;
 
+  mm_log((1, "io_new_cb(p %p, readcb %p, writecb %p, seekcb %p, closecb %p, "
+          "destroycb %p)\n", p, readcb, writecb, seekcb, closecb, destroycb));
+  ig = mymalloc(sizeof(io_glue));
+  memset(ig, 0, sizeof(ig));
+  io_obj_setp_cb2(&ig->source, p, readcb, writecb, seekcb, closecb, destroycb);
+  mm_log((1, "(%p) <- io_new_cb\n", ig));
+
+  return ig;
+}
 
 /*
 =item io_slurp(ig)
@@ -1024,6 +1048,44 @@ io_slurp(io_glue *ig, unsigned char **c) {
   return rc;
 }
 
+/*
+=item fd_read(ig, buf, count)
+
+=cut
+*/
+static ssize_t fd_read(io_glue *ig, void *buf, size_t count) {
+#ifdef _MSC_VER
+  return _read(ig->source.fdseek.fd, buf, count);
+#else
+  return read(ig->source.fdseek.fd, buf, count);
+#endif
+}
+
+static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) {
+#ifdef _MSC_VER
+  return _write(ig->source.fdseek.fd, buf, count);
+#else
+  return write(ig->source.fdseek.fd, buf, count);
+#endif
+}
+
+static off_t fd_seek(io_glue *ig, off_t offset, int whence) {
+#ifdef _MSC_VER
+  return _lseek(ig->source.fdseek.fd, offset, whence);
+#else
+  return lseek(ig->source.fdseek.fd, offset, whence);
+#endif
+}
+
+static void fd_close(io_glue *ig) {
+  /* no, we don't close it */
+}
+
+static ssize_t fd_size(io_glue *ig) {
+  mm_log((1, "fd_size(ig %p) unimplemented\n", ig));
+  
+  return -1;
+}
 
 /*
 =item io_glue_DESTROY(ig)
@@ -1050,9 +1112,10 @@ io_glue_DESTROY(io_glue *ig) {
     }
     break;
   case CBSEEK:
-  default:
     {
       io_ex_rseek *ier = ig->exdata;
+      if (ig->source.cb.destroycb)
+        ig->source.cb.destroycb(ig->source.cb.p);
       myfree(ier);
     }
     break;
@@ -1066,6 +1129,8 @@ io_glue_DESTROY(io_glue *ig) {
       myfree(ieb);
     }
     break;
+  default:
+    break;
   }
   myfree(ig);
 }
index a17d0ce..8d00258 100644 (file)
--- a/iolayer.h
+++ b/iolayer.h
@@ -47,10 +47,12 @@ typedef void   (*closebufp)(void *p);
 
 /* Callbacks we get */
 
-typedef ssize_t(*readl) (int fd, void *buf, size_t count);
-typedef ssize_t(*writel)(int fd, const void *buf, size_t count);
-typedef off_t  (*seekl) (int fd, off_t offset, int whence);
-typedef ssize_t(*sizel) (int fd);
+typedef ssize_t(*readl) (void *p, void *buf, size_t count);
+typedef ssize_t(*writel)(void *p, const void *buf, size_t count);
+typedef off_t  (*seekl) (void *p, off_t offset, int whence);
+typedef void   (*closel)(void *p);
+typedef void   (*destroyl)(void *p);
+typedef ssize_t(*sizel) (void *p);
 
 extern char *io_type_names[];
 
@@ -122,6 +124,8 @@ typedef struct {
   readl                readcb;
   writel       writecb;
   seekl                seekcb;
+  closel        closecb;
+  destroyl      destroycb;
 } io_cb;
 
 typedef union {
@@ -144,6 +148,7 @@ typedef struct _io_glue {
 
 void io_obj_setp_buffer(io_obj *io, char *p, size_t len, closebufp closecb, void *closedata);
 void io_obj_setp_cb      (io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb);
+void io_obj_setp_cb2     (io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb);
 void io_glue_commit_types(io_glue *ig);
 void io_glue_gettypes    (io_glue *ig, int reqmeth);
 
@@ -152,6 +157,7 @@ void io_glue_gettypes    (io_glue *ig, int reqmeth);
 io_glue *io_new_fd(int fd);
 io_glue *io_new_bufchain(void);
 io_glue *io_new_buffer(char *data, size_t len, closebufp closecb, void *closedata);
+io_glue *io_new_cb(void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb);
 size_t   io_slurp(io_glue *ig, unsigned char **c);
 void io_glue_DESTROY(io_glue *ig);
 
diff --git a/jpeg.c b/jpeg.c
index 98c6e96..e3bc772 100644 (file)
--- a/jpeg.c
+++ b/jpeg.c
@@ -488,6 +488,8 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
 
   jpeg_destroy_compress(&cinfo);
 
+  ig->closecb(ig);
+
   return(1);
 }
 
index 9655cbd..9807df0 100644 (file)
@@ -475,7 +475,7 @@ Checks if the characters in $text are defined by the font.
 In a list context returns a list of true or false value corresponding
 to the characters in $text, true if the character is defined, false if
 not.  In scalar context returns a string of NUL or non-NUL
-characters.  Supports UTF8.
+characters.  Supports UTF8 where the font driver supports UTF8.
 
 Not all fonts support this method (use $font->can("has_chars") to
 check.)
diff --git a/png.c b/png.c
index 76e18d4..65c3c18 100644 (file)
--- a/png.c
+++ b/png.c
@@ -181,6 +181,8 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
 
   png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
 
+  ig->closecb(ig);
+
   return(1);
 }
 
diff --git a/pnm.c b/pnm.c
index 5073a8c..aff05f8 100644 (file)
--- a/pnm.c
+++ b/pnm.c
@@ -498,6 +498,7 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
     mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
     return(0);
   }
+  ig->closecb(ig);
 
   return(1);
 }
diff --git a/raw.c b/raw.c
index 928b33a..0d0868b 100644 (file)
--- a/raw.c
+++ b/raw.c
@@ -155,5 +155,8 @@ i_writeraw_wiol(i_img* im, io_glue *ig) {
       }
     }
   }
+
+  ig->closecb(ig);
+
   return(1);
 }
index 174466a..7a87f03 100644 (file)
@@ -1,4 +1,4 @@
-BEGIN { $|=1; print "1..7\n"; }
+BEGIN { $|=1; print "1..20\n"; }
 END { print "not ok 1\n" unless $loaded; };
 use Imager qw(:all);
 ++$loaded;
@@ -35,7 +35,7 @@ $IO3 = Imager::io_new_buffer($data);
 $im = Imager::i_readpnm_wiol($IO3, -1);
 
 print "ok 3\n";
-
+undef $IO3;
 
 open(FH, "<testimg/penguin-base.ppm") or die $!;
 binmode(FH);
@@ -61,8 +61,74 @@ print "ok 6\n";
 $IO6 = Imager::io_new_buffer($data2);
 $im3 = Imager::i_readpnm_wiol($IO6, -1);
 
-Imager::i_img_diff($im, $im3) ? print "not ok 7\n" : print "ok 7\n";
-
-
-
-
+ok(7, Imager::i_img_diff($im, $im3) == 0, "read from buffer");
+
+my $work = $data;
+my $pos = 0;
+sub io_reader {
+  my ($size, $maxread) = @_;
+  my $out = substr($work, $pos, $maxread);
+  $pos += length $out;
+  $out;
+}
+sub io_reader2 {
+  my ($size, $maxread) = @_;
+  my $out = substr($work, $pos, $maxread);
+  $pos += length $out;
+  $out;
+}
+my $IO7 = Imager::io_new_cb(undef, \&io_reader, undef, undef);
+ok(8, $IO7, "making readcb object");
+my $im4 = Imager::i_readpnm_wiol($IO7, -1);
+ok(9, $im4, "read from cb");
+ok(10, Imager::i_img_diff($im, $im4) == 0, "read from cb image match");
+
+$pos = 0;
+$IO7 = Imager::io_new_cb(undef, \&io_reader2, undef, undef);
+ok(11, $IO7, "making short readcb object");
+my $im5 = Imager::i_readpnm_wiol($IO7, -1);
+ok(12, $im4, "read from cb2");
+ok(13, Imager::i_img_diff($im, $im5) == 0, "read from cb2 image match");
+
+sub io_writer {
+  my ($what) = @_;
+  substr($work, $pos, $pos+length $what) = $what;
+  $pos += length $what;
+
+  1;
+}
+
+my $did_close;
+sub io_close {
+  ++$did_close;
+}
+
+my $IO8 = Imager::io_new_cb(\&io_writer, undef, undef, \&io_close);
+ok(14, $IO8, "making writecb object");
+$pos = 0;
+$work = '';
+ok(15, Imager::i_writeppm_wiol($im, $IO8), "write to cb");
+# I originally compared this to $data, but that doesn't include the
+# Imager header
+ok(16, $work eq $data2, "write image match");
+ok(17, $did_close, "did close");
+
+# with a short buffer, no closer
+my $IO9 = Imager::io_new_cb(\&io_writer, undef, undef, undef, 1);
+ok(18, $IO9, "making short writecb object");
+$pos = 0;
+$work = '';
+ok(19, Imager::i_writeppm_wiol($im, $IO9), "write to short cb");
+ok(20, $work eq $data2, "short write image match");
+
+sub ok {
+  my ($num, $ok, $what) = @_;
+
+  if ($ok) {
+    print "ok $num # $what\n";
+  }
+  else {
+    print "not ok $num # $what\n";
+  }
+  $ok;
+}
index a55203b..998788b 100644 (file)
@@ -168,7 +168,9 @@ if (!i_has_format("gif")) {
     close FH;
     print "ok 13\n";
 
+    my $can_write_callback = 0;
     if ($gifver >= 4.0) {
+      ++$can_write_callback;
       unless (fork) {
        # this can SIGSEGV with some versions of giflib
        open FH, ">testout/t105_anim_cb.gif" or die $!;
@@ -188,6 +190,7 @@ if (!i_has_format("gif")) {
        exit;
       }
       if (wait > 0 && $?) {
+        $can_write_callback = 0;
        print "not ok 14 # you probably need to patch giflib\n";
        print <<EOS;
 #--- egif_lib.c        2000/12/11 07:33:12     1.1
index 18eff9c..1f5253c 100644 (file)
@@ -1,5 +1,5 @@
 #!perl -w
-print "1..43\n";
+print "1..69\n";
 use Imager qw(:all);
 $^W=1; # warnings during command-line tests
 $|=1;  # give us some progress in the test harness
@@ -24,7 +24,7 @@ i_box_filled($timg, 2, 2, 18, 18, $trans);
 my $test_num;
 
 if (!i_has_format("tiff")) {
-  for (1..43) {
+  for (1..69) {
     print "ok $_ # skip no tiff support\n";
   }
 } else {
@@ -206,6 +206,131 @@ if (!i_has_format("tiff")) {
   $diff = i_img_diff($img4->{IMG}, $cmp4->{IMG});
   print "# diff $diff\n";
   ok($diff == 0, "written image doesn't match read");
+
+  my $work;
+  my $seekpos;
+  sub io_writer {
+    my ($what) = @_;
+    if ($seekpos > length $work) {
+      $work .= "\0" x ($seekpos - length $work);
+    }
+    substr($work, $seekpos, length $what) = $what;
+    $seekpos += length $what;
+
+    1;
+  }
+  sub io_reader {
+    my ($size, $maxread) = @_;
+    #print "io_reader($size, $maxread) pos $seekpos\n";
+    my $out = substr($work, $seekpos, $maxread);
+    $seekpos += length $out;
+    $out;
+  }
+  sub io_reader2 {
+    my ($size, $maxread) = @_;
+    #print "io_reader2($size, $maxread) pos $seekpos\n";
+    my $out = substr($work, $seekpos, $size);
+    $seekpos += length $out;
+    $out;
+  }
+  use IO::Seekable;
+  sub io_seeker {
+    my ($offset, $whence) = @_;
+    #print "io_seeker($offset, $whence)\n";
+    if ($whence == SEEK_SET) {
+      $seekpos = $offset;
+    }
+    elsif ($whence == SEEK_CUR) {
+      $seekpos += $offset;
+    }
+    else { # SEEK_END
+      $seekpos = length($work) + $offset;
+    }
+    #print "-> $seekpos\n";
+    $seekpos;
+  }
+  my $did_close;
+  sub io_closer {
+    ++$did_close;
+  }
+
+  # read via cb
+  $work = $tiffdata;
+  $seekpos = 0;
+  my $IO2 = Imager::io_new_cb(undef, \&io_reader, \&io_seeker, undef);
+  ok($IO2, "new readcb obj");
+  my $img5 = i_readtiff_wiol($IO2, -1);
+  ok($img5, "read via cb");
+  ok(i_img_diff($img5, $img) == 0, "read from cb diff");
+
+  # read via cb2
+  $work = $tiffdata;
+  $seekpos = 0;
+  my $IO3 = Imager::io_new_cb(undef, \&io_reader2, \&io_seeker, undef);
+  ok($IO3, "new readcb2 obj");
+  my $img6 = i_readtiff_wiol($IO3, -1);
+  ok($img6, "read via cb2");
+  ok(i_img_diff($img6, $img) == 0, "read from cb2 diff");
+
+  # write via cb
+  $work = '';
+  $seekpos = 0;
+  my $IO4 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker,
+                              \&io_closer);
+  ok($IO4, "new writecb obj");
+  ok(i_writetiff_wiol($img, $IO4), "write to cb");
+  ok($work eq $odata, "write cb match");
+  ok($did_close, "write cb did close");
+  open D1, ">d1.tiff" or die;
+  print D1 $work;
+  close D1;
+  open D2, ">d2.tiff" or die;
+  print D2 $tiffdata;
+  close D2;
+
+  # write via cb2
+  $work = '';
+  $seekpos = 0;
+  $did_close = 0;
+  my $IO5 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker,
+                              \&io_closer, 1);
+  ok($IO5, "new writecb obj 2");
+  ok(i_writetiff_wiol($img, $IO5), "write to cb2");
+  ok($work eq $odata, "write cb2 match");
+  ok($did_close, "write cb2 did close");
+
+  open D3, ">d3.tiff" or die;
+  print D3 $work;
+  close D3;
+
+  # multi-image write/read
+  my @imgs;
+  push(@imgs, map $ooim->copy(), 1..3);
+  for my $i (0..$#imgs) {
+    $imgs[$i]->addtag(name=>"tiff_pagename", value=>"Page ".($i+1));
+  }
+  my $rc = Imager->write_multi({file=>'testout/t106_multi.tif'}, @imgs);
+  ok($rc, "writing multiple images to tiff");
+  my @out = Imager->read_multi(file=>'testout/t106_multi.tif');
+  ok(@out == @imgs, "reading multiple images from tiff");
+  @out == @imgs or print "# ",scalar @out, " ",Imager->errstr,"\n";
+  for my $i (0..$#imgs) {
+    ok(i_img_diff($imgs[$i]{IMG}, $out[$i]{IMG}) == 0,
+       "comparing image $i");
+    my ($tag) = $out[$i]->tags(name=>'tiff_pagename');
+    ok($tag eq "Page ".($i+1),
+       "tag doesn't match original image");
+  }
+
+  # multi-image fax files
+  ok(Imager->write_multi({file=>'testout/t106_faxmulti.tiff', class=>'fax'},
+                         $oofim, $oofim), "write multi fax image");
+  @imgs = Imager->read_multi(file=>'testout/t106_faxmulti.tiff');
+  ok(@imgs == 2, "reading multipage fax");
+  ok(Imager::i_img_diff($imgs[0]{IMG}, $oofim->{IMG}) == 0,
+     "compare first fax image");
+  ok(Imager::i_img_diff($imgs[1]{IMG}, $oofim->{IMG}) == 0,
+     "compare second fax image");
 }
 
 sub ok {
index d4fb51f..af0a9e5 100644 (file)
@@ -66,6 +66,8 @@ i_tt_text($ttraw,$backgr,100,100,$bgcolor,50.0,'test',4,1);
 my $ugly = Imager::i_tt_new("./fontfiles/ImUgly.ttf");
 i_tt_text($ugly, $backgr,100, 50, $bgcolor, 14, 'g%g', 3, 1);
 i_tt_text($ugly, $backgr,150, 50, $bgcolor, 14, 'delta', 5, 1);
+i_tt_text($ttraw, $backgr, 20, 10, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1);
+i_tt_text($ttraw, $backgr, 20, 30, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1);
 
 
 open(FH,">testout/t35ttfont2.ppm") || die "cannot open testout/t35ttfont.ppm\n";
index 4da6d08..61373a1 100644 (file)
-# Before `make install' is performed this script should be runnable with
-# `make test'. After `make install' it should work as `perl test.pl'
-
+#!perl -w
 ######################### We start with some black magic to print on failure.
 
-# Change 1..1 below to 1..last_test_to_print .
-# (It may become useful if the test is moved to ./t subdirectory.)
+# this used to do the check for the load of Imager, but I want to be able 
+# to count tests, which means I need to load Imager first
+# since many of the early tests already do this, we don't really need to
 
-BEGIN { $| = 1; print "1..2\n"; }
-END {print "not ok 1\n" unless $loaded;}
+use strict;
 use Imager;
+use IO::Seekable;
 
-$loaded = 1;
+Imager::init("log"=>"testout/t50basicoo.log");
 
-print "ok 1\n";
+# single image/file types
+my @types = qw( jpeg png raw ppm gif tiff bmp tga );
 
-Imager::init("log"=>"testout/t50basicoo.log");
+# multiple image/file formats
+my @mtypes = qw(tiff gif);
+
+my %hsh=%Imager::formats;
+
+my $test_num = 0;
+my $count;
+for my $type (@types) {
+  $count += 31 if $hsh{$type};
+}
+for my $type (@mtypes) {
+  $count += 7 if $hsh{$type};
+}
 
-%hsh=%Imager::formats;
+print "1..$count\n";
 
 print "# avaliable formats:\n";
 for(keys %hsh) { print "# $_\n"; }
 
 #print Dumper(\%hsh);
 
-$img = Imager->new();
-
-@types = qw( jpeg png raw ppm gif tiff );
+my $img = Imager->new();
 
+my %files;
 @files{@types} = ({ file => "testout/t101.jpg"  },
                  { file => "testout/t102.png"  },
-                 { file => "testout/t103.raw", xsize=>150, ysize=>150, type=>"raw" },
+                 { file => "testout/t103.raw", xsize=>150, ysize=>150
+                    #, type=>"raw" # TODO: was this there for a reason?
+                  },
                  { file => "testout/t104.ppm"  },
                  { file => "testout/t105.gif"  },
-                 { file => "testout/t106.tiff" });
+                 { file => "testout/t106.tiff" },
+                  { file => "testout/t107_24bit.bmp" },
+                  { file => "testout/t108_24bit.tga" }, );
+my %writeopts =
+  (
+   gif=> { make_colors=>'webmap', translate=>'closest', gifquant=>'gen' },
+  );
 
-for $type (@types) {
+for my $type (@types) {
   next unless $hsh{$type};
+  print "# type $type\n";
   my %opts = %{$files{$type}};
   my @a = map { "$_=>${opts{$_}}" } keys %opts;
   print "#opening Format: $type, options: @a\n";
-  $img->read( %opts ) or die "failed: ",$img->errstr,"\n";
+  ok($img->read( %opts ), "reading from file", $img);
+  #or die "failed: ",$img->errstr,"\n";
+
+  my %mopts = %opts;
+  delete $mopts{file};
+  
+  # read from a file handle
+  my $fh = IO::File->new($opts{file}, "r");
+  if (ok($fh, "opening $opts{file}")) {
+    binmode $fh;
+    my $fhimg = Imager->new;
+    my $fhrc = $fhimg->read(fh=>$fh, %mopts);
+    if (ok(!$fhrc, "check that type is required")) {
+      ok ($fhimg->errstr =~ /type parameter missing/, "check for no type error");
+    }
+    else {
+      skip("previous test failed");
+    }
+    if (ok($fhimg->read(fh=>$fh, %mopts, type=>$type), "read from fh")) {
+      ok(Imager::i_img_diff($img->{IMG}, $fhimg->{IMG}) == 0,
+         "image comparison after fh read");
+    }
+    else {
+      skip("no image to compare");
+    }
+    ok($fh->seek(0, SEEK_SET), "seek after read");
+    
+    # read from a fd
+    my $fdimg = Imager->new;
+    if (ok($fdimg->read(fd=>fileno($fh), %mopts, type=>$type), 
+           "read from fd")) {
+      ok(Imager::i_img_diff($img->{IMG}, $fdimg->{IMG}) == 0,
+         "image comparistion after fd read");
+    }
+    else {
+      skip("no image to compare");
+    }
+    ok($fh->seek(0, SEEK_SET), "seek after fd read");
+    ok($fh->close, "close fh after reads");
+  }
+  else {
+    skip("couldn't open the damn file: $!", 7);
+  }
+
+  if ($type ne 'gif' || Imager::i_giflib_version() >= 4) {
+    # read from a memory buffer
+    open DATA, "< $opts{file}"
+      or die "Cannot open $opts{file}: $!";
+    binmode DATA;
+    my $data = do { local $/; <DATA> };
+    close DATA;
+    my $bimg = Imager->new;
+    
+    if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", 
+           $img)) {
+      ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0,
+         "comparing buffer read image");
+    }
+    else {
+      skip("nothing to compare");
+    }
+    
+    # read from callbacks, both with minimum and maximum reads
+    my $buf = $data;
+    my $seekpos = 0;
+    my $reader_min = 
+      sub { 
+        my ($size, $maxread) = @_;
+        my $out = substr($buf, $seekpos, $size);
+        $seekpos += length $out;
+        $out;
+      };
+    my $reader_max = 
+      sub { 
+        my ($size, $maxread) = @_;
+        my $out = substr($buf, $seekpos, $maxread);
+        $seekpos += length $out;
+        $out;
+      };
+    my $seeker =
+      sub {
+        my ($offset, $whence) = @_;
+        #print "io_seeker($offset, $whence)\n";
+        if ($whence == SEEK_SET) {
+          $seekpos = $offset;
+        }
+        elsif ($whence == SEEK_CUR) {
+          $seekpos += $offset;
+        }
+        else { # SEEK_END
+          $seekpos = length($buf) + $offset;
+        }
+        #print "-> $seekpos\n";
+        $seekpos;
+      };
+    my $cbimg = Imager->new;
+    ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts),
+       "read from callback min", $cbimg);
+    ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
+       "comparing mincb image");
+    $seekpos = 0;
+    ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts),
+       "read from callback max", $cbimg);
+    ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
+       "comparing maxcb image");
+  }
+  else {
+    skip("giflib < 4 doesn't support callbacks", 6);
+  }
 }
 
-$img2 =  $img->crop(width=>50, height=>50);
+for my $type (@types) {
+  next unless $hsh{$type};
+
+  print "# write tests for $type\n";
+  # test writes
+  next unless $hsh{$type};
+  my $file = "testout/t50out.$type";
+  my $wimg = Imager->new;
+  # if this doesn't work, we're so screwed up anyway
+  
+  ok($wimg->read(file=>"testout/t104.ppm"),
+     "cannot read base file", $wimg);
+
+  # first to a file
+  print "# writing $type to a file\n";
+  my %extraopts;
+  %extraopts = %{$writeopts{$type}} if $writeopts{$type};
+  ok($wimg->write(file=>$file, %extraopts),
+     "writing $type to a file $file", $wimg);
+
+  print "# writing $type to a FH\n";
+  # to a FH
+  my $fh = IO::File->new($file, "w+")
+    or die "Could not create $file: $!";
+  binmode $fh;
+  ok($wimg->write(fh=>$fh, %extraopts, type=>$type),
+     "writing $type to a FH", $wimg);
+  ok($fh->seek(0, SEEK_END) > 0,
+     "seek after writing $type to a FH");
+  ok(print($fh "SUFFIX\n"),
+     "write to FH after writing $type");
+  ok($fh->close, "closing FH after writing $type");
+
+  if ($type ne 'gif' || Imager::i_giflib_version() >= 4) {
+    if (ok(open(DATA, "< $file"), "opening data source")) {
+      binmode DATA;
+      my $data = do { local $/; <DATA> };
+      close DATA;
+
+      # writing to a buffer
+      print "# writing $type to a buffer\n";
+      my $buf = '';
+      ok($wimg->write(data=>\$buf, %extraopts, type=>$type),
+         "writing $type to a buffer", $wimg);
+      $buf .= "SUFFIX\n";
+      open DATA, "> testout/t50_buf.$type"
+        or die "Cannot create $type buffer file: $!";
+      binmode DATA;
+      print DATA $buf;
+      close DATA;
+      ok($data eq $buf, "comparing file data to buffer");
+
+      $buf = '';
+      my $seekpos = 0;
+      my $did_close;
+      my $writer = 
+        sub {
+          my ($what) = @_;
+          if ($seekpos > length $buf) {
+            $buf .= "\0" x ($seekpos - length $buf);
+          }
+          substr($buf, $seekpos, length $what) = $what;
+          $seekpos += length $what;
+          $did_close = 0; # the close must be last
+          1;
+        };
+      my $reader_min = 
+        sub { 
+          my ($size, $maxread) = @_;
+          my $out = substr($buf, $seekpos, $size);
+          $seekpos += length $out;
+          $out;
+        };
+      my $reader_max = 
+        sub { 
+          my ($size, $maxread) = @_;
+          my $out = substr($buf, $seekpos, $maxread);
+          $seekpos += length $out;
+          $out;
+        };
+      use IO::Seekable;
+      my $seeker =
+        sub {
+          my ($offset, $whence) = @_;
+          #print "io_seeker($offset, $whence)\n";
+          if ($whence == SEEK_SET) {
+            $seekpos = $offset;
+          }
+          elsif ($whence == SEEK_CUR) {
+            $seekpos += $offset;
+          }
+          else { # SEEK_END
+            $seekpos = length($buf) + $offset;
+          }
+          #print "-> $seekpos\n";
+          $seekpos;
+        };
+      
+      my $closer = sub { ++$did_close; };
+      
+      print "# writing $type via callbacks (mb=1)\n";
+      ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer,
+                   readcb=>$reader_min,
+                   %extraopts, type=>$type, maxbuffer=>1),
+         "writing $type to callback (mb=1)", $wimg);
+
+      ok($did_close, "checking closecb called");
+      $buf .= "SUFFIX\n";
+      ok($data eq $buf, "comparing callback output to file data");
+      print "# writing $type via callbacks (no mb)\n";
+      $buf = '';
+      $did_close = 0;
+      $seekpos = 0;
+      # we don't use the closecb here - used to make sure we don't get 
+      # a warning/error on an attempt to call an undef close sub
+      ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min,
+                   %extraopts, type=>$type),
+         "writing $type to callback (no mb)", $wimg);
+      $buf .= "SUFFIX\n";
+      ok($data eq $buf, "comparing callback output to file data");
+    }
+    else {
+      skip("couldn't open data source", 7);
+    }
+  }
+  else {
+    skip("giflib < 4 doesn't support callbacks", 8);
+  }
+}
+
+my $img2 =  $img->crop(width=>50, height=>50);
 $img2 -> write(file=> 'testout/t50.ppm', type=>'pnm');
 
 undef($img);
 
+# multi image/file tests
+print "# multi-image write tests\n";
+for my $type (@mtypes) {
+  next unless $hsh{$type};
+  print "# $type\n";
+
+  my $file = "testout/t50out.$type";
+  my $wimg = Imager->new;
+
+  # if this doesn't work, we're so screwed up anyway
+  ok($wimg->read(file=>"testout/t50out.$type"),
+     "reading base file", $wimg);
+
+  ok(my $wimg2 = $wimg->copy, "copying base image", $wimg);
+  ok($wimg2->flip(dir=>'h'), "flipping base image", $wimg2);
+
+  my @out = ($wimg, $wimg2);
+  my %extraopts;
+  %extraopts = %{$writeopts{$type}} if $writeopts{$type};
+  ok(Imager->write_multi({ file=>"testout/t50_multi.$type", %extraopts },
+                         @out),
+     "writing multiple to a file", "Imager");
+
+  # make sure we get the same back
+  my @images = Imager->read_multi(file=>"testout/t50_multi.$type");
+  if (ok(@images == @out, "checking read image count")) {
+    for my $i (0 .. $#out) {
+      my $diff = Imager::i_img_diff($out[$i]{IMG}, $images[$i]{IMG});
+      print "# diff $diff\n";
+      ok($diff == 0, "comparing image $i");
+    }
+  }
+  else {
+    skip("wrong number of images read", 2);
+  }
+}
+
+
 Imager::malloc_state();
 
-print "ok 2\n";
+#print "ok 2\n";
+
+sub ok {
+  my ($ok, $msg, $img) = @_;
+
+  ++$test_num;
+  if ($ok) {
+    print "ok $test_num # $msg\n";
+  }
+  else {
+    my $err;
+    $err = $img->errstr if $img;
+    # VMS (if we ever support it) wants the whole line in one print
+    my $line = "not ok $test_num # line ".(caller)[2].": $msg";
+    $line .= ": $err" if $err;
+    print $line, "\n";
+  }
+
+  $ok;
+}
+
+sub skip {
+  my ($why, $skipcount) = @_;
+
+  $skipcount ||= 1;
+  for (1.. $skipcount) {
+    ++$test_num;
+    print "ok $test_num # skipped $why\n";
+  }
+}
diff --git a/tga.c b/tga.c
index 46f3900..361f732 100644 (file)
--- a/tga.c
+++ b/tga.c
@@ -868,6 +868,9 @@ i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idst
     myfree(buf);
     myfree(vals);
   }
+
+  ig->closecb(ig);
+
   return 1;
 }
 
diff --git a/tiff.c b/tiff.c
index 1485a19..8f72bc2 100644 (file)
--- a/tiff.c
+++ b/tiff.c
@@ -86,26 +86,12 @@ comp_seek(thandle_t h, toff_t o, int w) {
   return (toff_t) ig->seekcb(ig, o, w);
 }
 
-
-/*
-=item i_readtiff_wiol(ig, length)
-
-Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
-
-   ig     - io_glue object
-   length - maximum length to read from data source, before closing it
-
-=cut
-*/
-
-i_img*
-i_readtiff_wiol(io_glue *ig, int length) {
+static i_img *read_one_tiff(TIFF *tif) {
   i_img *im;
   uint32 width, height;
   uint16 channels;
   uint32* raster = NULL;
   int tiled, error;
-  TIFF* tif;
   float xres, yres;
   uint16 resunit;
   int gotXres, gotYres;
@@ -113,37 +99,9 @@ i_readtiff_wiol(io_glue *ig, int length) {
   uint16 bits_per_sample;
   int i;
   int ch;
-  TIFFErrorHandler old_handler;
-
-  i_clear_error();
-  old_handler = TIFFSetErrorHandler(error_handler);
 
   error = 0;
 
-  /* 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_readtiff_wiol(ig %p, length %d)\n", ig, length));
-  
-  tif = TIFFClientOpen("(Iolayer)", 
-                      "rm", 
-                      (thandle_t) ig,
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc) comp_seek,
-                      (TIFFCloseProc) ig->closecb,
-                      (TIFFSizeProc) ig->sizecb,
-                      (TIFFMapFileProc) NULL,
-                      (TIFFUnmapFileProc) NULL);
-  
-  if (!tif) {
-    mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
-    i_push_error(0, "opening file");
-    TIFFSetErrorHandler(old_handler);
-    return NULL;
-  }
-
   TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
   TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
   TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
@@ -207,17 +165,13 @@ i_readtiff_wiol(io_glue *ig, int length) {
 
     if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
       i_push_error(0, "Cannot get colormap for paletted image");
-      TIFFSetErrorHandler(old_handler);
       i_img_destroy(im);
-      TIFFClose(tif);
       return NULL;
     }
     buffer = (unsigned char *)_TIFFmalloc(width+2);
     if (!buffer) {
       i_push_error(0, "out of memory");
-      TIFFSetErrorHandler(old_handler);
       i_img_destroy(im);
-      TIFFClose(tif);
       return NULL;
     }
     row = 0;
@@ -270,8 +224,6 @@ i_readtiff_wiol(io_glue *ig, int length) {
       if (!raster) {
         i_img_destroy(im);
         i_push_error(0, "No space for raster buffer");
-        TIFFSetErrorHandler(old_handler);
-        TIFFClose(tif);
         return NULL;
       }
       
@@ -309,8 +261,6 @@ i_readtiff_wiol(io_glue *ig, int length) {
       if (!raster) {
         i_img_destroy(im);
         i_push_error(0, "No space for raster buffer");
-        TIFFSetErrorHandler(old_handler);
-        TIFFClose(tif);
         return NULL;
       }
       
@@ -346,29 +296,227 @@ i_readtiff_wiol(io_glue *ig, int length) {
   }
   if (raster)
     _TIFFfree( raster );
+
+  return im;
+}
+
+/*
+=item i_readtiff_wiol(im, ig)
+
+=cut
+*/
+i_img*
+i_readtiff_wiol(io_glue *ig, int length) {
+  TIFF* tif;
+  TIFFErrorHandler old_handler;
+  i_img *im;
+
+  i_clear_error();
+  old_handler = TIFFSetErrorHandler(error_handler);
+
+  /* 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_readtiff_wiol(ig %p, length %d)\n", ig, length));
+  
+  tif = TIFFClientOpen("(Iolayer)", 
+                      "rm", 
+                      (thandle_t) ig,
+                      (TIFFReadWriteProc) ig->readcb,
+                      (TIFFReadWriteProc) ig->writecb,
+                      (TIFFSeekProc) comp_seek,
+                      (TIFFCloseProc) ig->closecb,
+                      (TIFFSizeProc) ig->sizecb,
+                      (TIFFMapFileProc) NULL,
+                      (TIFFUnmapFileProc) NULL);
+  
+  if (!tif) {
+    mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
+    i_push_error(0, "opening file");
+    TIFFSetErrorHandler(old_handler);
+    return NULL;
+  }
+
+  im = read_one_tiff(tif);
+
   if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
   TIFFSetErrorHandler(old_handler);
   TIFFClose(tif);
   return im;
 }
 
+/*
+=item i_readtiff_multi_wiol(ig, length, *count)
 
+Reads multiple images from a TIFF.
 
-/*
-=item i_writetif_wiol(im, ig)
+=cut
+*/
+i_img**
+i_readtiff_multi_wiol(io_glue *ig, int length, int *count) {
+  TIFF* tif;
+  TIFFErrorHandler old_handler;
+  i_img **results = NULL;
+  int result_alloc = 0;
+  int dirnum = 0;
 
-Stores an image in the iolayer object.
+  i_clear_error();
+  old_handler = TIFFSetErrorHandler(error_handler);
 
-   im - image object to write out
-   ig - io_object that defines source to write to 
+  /* Add code to get the filename info from the iolayer */
+  /* Also add code to check for mmapped code */
 
-=cut 
-*/
+  io_glue_commit_types(ig);
+  mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
+  
+  tif = TIFFClientOpen("(Iolayer)", 
+                      "rm", 
+                      (thandle_t) ig,
+                      (TIFFReadWriteProc) ig->readcb,
+                      (TIFFReadWriteProc) ig->writecb,
+                      (TIFFSeekProc) comp_seek,
+                      (TIFFCloseProc) ig->closecb,
+                      (TIFFSizeProc) ig->sizecb,
+                      (TIFFMapFileProc) NULL,
+                      (TIFFUnmapFileProc) NULL);
+  
+  if (!tif) {
+    mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
+    i_push_error(0, "opening file");
+    TIFFSetErrorHandler(old_handler);
+    return NULL;
+  }
 
-/* FIXME: Add an options array in here soonish */
+  *count = 0;
+  do {
+    i_img *im = read_one_tiff(tif);
+    if (!im)
+      break;
+    if (++*count > result_alloc) {
+      if (result_alloc == 0) {
+        result_alloc = 5;
+        results = mymalloc(result_alloc * sizeof(i_img *));
+      }
+      else {
+        i_img **newresults;
+        result_alloc *= 2;
+        newresults = myrealloc(results, result_alloc * sizeof(i_img *));
+      }
+    }
+    results[*count-1] = im;
+  } while (TIFFSetDirectory(tif, ++dirnum));
+
+  TIFFSetErrorHandler(old_handler);
+  TIFFClose(tif);
+  return results;
+}
 
 undef_int
-i_writetiff_wiol(i_img *im, io_glue *ig) {
+i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) {
+  uint32 width, height;
+  unsigned char *linebuf = NULL;
+  uint32 y;
+  int rc;
+  uint32 x;
+  int luma_mask;
+  uint32 rowsperstrip;
+  float vres = fine ? 196 : 98;
+  int luma_chan;
+
+  width    = im->xsize;
+  height   = im->ysize;
+
+  switch (im->channels) {
+  case 1:
+  case 2:
+    luma_chan = 0;
+    break;
+  case 3:
+  case 4:
+    luma_chan = 1;
+    break;
+  default:
+    /* This means a colorspace we don't handle yet */
+    mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
+    return 0;
+  }
+
+  /* Add code to get the filename info from the iolayer */
+  /* Also add code to check for mmapped code */
+
+
+  mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
+  
+  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      width)   )
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  )
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,   1)        )
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
+
+  linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
+  
+  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
+    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
+
+  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
+  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
+
+  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
+  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
+  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
+
+  if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
+    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
+  if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
+    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0; 
+  }
+
+  if (!save_tiff_tags(tif, im)) {
+    return 0;
+  }
+
+  for (y=0; y<height; y++) {
+    int linebufpos=0;
+    for(x=0; x<width; x+=8) { 
+      int bits;
+      int bitpos;
+      i_sample_t luma[8];
+      uint8 bitval = 128;
+      linebuf[linebufpos]=0;
+      bits = width-x; if(bits>8) bits=8;
+      i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
+      for(bitpos=0;bitpos<bits;bitpos++) {
+       linebuf[linebufpos] |= ((luma[bitpos]>=128)?bitval:0);
+       bitval >>= 1;
+      }
+      linebufpos++;
+    }
+    if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
+      mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
+      break;
+    }
+  }
+  if (linebuf) _TIFFfree(linebuf);
+
+  return 1;
+}
+
+undef_int
+i_writetiff_low(TIFF *tif, i_img *im) {
   uint32 width, height;
   uint16 channels;
   uint16 predictor = 0;
@@ -383,17 +531,12 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
   tsize_t linebytes;
   int ch, ci, rc;
   uint32 x;
-  TIFF* tif;
   int got_xres, got_yres, got_aspectonly, aspect_only, resunit;
   double xres, yres;
   uint16 bitspersample = 8;
   uint16 samplesperpixel;
   uint16 *colors = NULL;
 
-  char *cc = mymalloc( 123 );
-  myfree(cc);
-
-
   width    = im->xsize;
   height   = im->ysize;
   channels = im->channels;
@@ -418,37 +561,35 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
   /* 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));
+  /*io_glue_commit_types(ig);*/
+  /*mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));*/
 
-  /* FIXME: Enable the mmap interface */
+  mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d\n", width, height, channels));
   
-  tif = TIFFClientOpen("No name", 
-                      "wm",
-                      (thandle_t) ig, 
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc)      comp_seek,
-                      (TIFFCloseProc)     ig->closecb, 
-                      (TIFFSizeProc)      ig->sizecb,
-                      (TIFFMapFileProc)   NULL,
-                      (TIFFUnmapFileProc) NULL);
-  
-
-
-  if (!tif) {
-    mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
-    return 0;
+  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      width)   ) { 
+    mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); 
+    return 0; 
+  }
+  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  ) { 
+    mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); 
+    return 0; 
+  }
+  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT)) {
+    mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); 
+    return 0; 
+  }
+  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { 
+    mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); 
+    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_COMPRESSION,   compression)) { 
+    mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); 
+    return 0; 
   }
-
-  mm_log((1, "i_writetiff_wiol: 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; }
-  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  ) { mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT)) { mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); 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_COMPRESSION,   compression)) { mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); return 0; }
   samplesperpixel = channels;
   if (photometric == PHOTOMETRIC_PALETTE) {
     uint16 *out[3];
@@ -503,8 +644,14 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
   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; }
+    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"));
@@ -512,7 +659,10 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
   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; }
+      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"));
@@ -565,27 +715,20 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
       }
     }
     if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
-      TIFFClose(tif);
-      i_img_destroy(im);
       i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag");
       return 0;
     }
     if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
-      TIFFClose(tif);
-      i_img_destroy(im);
       i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag");
       return 0;
     }
     if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
-      TIFFClose(tif);
-      i_img_destroy(im);
       i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag");
       return 0;
     }
   }
 
   if (!save_tiff_tags(tif, im)) {
-    TIFFClose(tif);
     return 0;
   }
 
@@ -596,7 +739,9 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
         pack_4bit_hl(linebuf, width);
       if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
         mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
-        break;
+        if (linebuf) _TIFFfree(linebuf);
+        if (colors) _TIFFfree(colors);
+        return 0;
       }
     }
   }
@@ -610,67 +755,97 @@ i_writetiff_wiol(i_img *im, io_glue *ig) {
       }
       if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
         mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
-        break;
+        if (linebuf) _TIFFfree(linebuf);
+        if (colors) _TIFFfree(colors);
+        return 0;
       }
     }
   }
-  (void) TIFFClose(tif);
   if (linebuf) _TIFFfree(linebuf);
   if (colors) _TIFFfree(colors);
   return 1;
 }
 
 /*
-=item i_writetiff_wiol_faxable(i_img *, io_glue *)
+=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode)
 
-Stores an image in the iolayer object in faxable tiff format.
+Stores an image in the iolayer object.
 
-   im - image object to write out
    ig - io_object that defines source to write to 
+   imgs,count - the images to write
 
-Note, this may be rewritten to use to simply be a call to a
-lower-level function that gives more options for writing tiff at some
-point.
-
-=cut
+=cut 
 */
 
 undef_int
-i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
-  uint32 width, height;
-  unsigned char *linebuf = NULL;
-  uint32 y;
-  int rc;
-  uint32 x;
+i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
   TIFF* tif;
-  int luma_mask;
-  uint32 rowsperstrip;
-  float vres = fine ? 196 : 98;
-  int luma_chan;
+  int i;
 
-  width    = im->xsize;
-  height   = im->ysize;
+  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));
 
-  switch (im->channels) {
-  case 1:
-  case 2:
-    luma_chan = 0;
-    break;
-  case 3:
-  case 4:
-    luma_chan = 1;
-    break;
-  default:
-    /* This means a colorspace we don't handle yet */
-    mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
+  /* 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, 
+                      (TIFFSizeProc)      ig->sizecb,
+                      (TIFFMapFileProc)   NULL,
+                      (TIFFUnmapFileProc) NULL);
+  
+
+
+  if (!tif) {
+    mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
     return 0;
   }
 
-  /* Add code to get the filename info from the iolayer */
-  /* Also add code to check for mmapped code */
+  for (i = 0; i < count; ++i) {
+    if (!i_writetiff_low(tif, imgs[i])) {
+      TIFFClose(tif);
+      return 0;
+    }
+
+    if (!TIFFWriteDirectory(tif)) {
+      i_push_error(0, "Cannot write TIFF directory");
+      TIFFClose(tif);
+      return 0;
+    }
+  }
+
+  (void) TIFFClose(tif);
+  return 1;
+}
+
+/*
+=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode)
+
+Stores an image in the iolayer object.
+
+   ig - io_object that defines source to write to 
+   imgs,count - the images to write
+   fine_mode - select fine or normal mode fax images
+
+=cut 
+*/
+
+
+undef_int
+i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
+  TIFF* tif;
+  int i;
 
   io_glue_commit_types(ig);
-  mm_log((1, "i_writetiff_wiol_faxable(im 0x%p, ig 0x%p)\n", im, 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 */
   
@@ -684,99 +859,152 @@ i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
                       (TIFFSizeProc)      ig->sizecb,
                       (TIFFMapFileProc)   NULL,
                       (TIFFUnmapFileProc) NULL);
+  
+
 
   if (!tif) {
-    mm_log((1, "i_writetiff_wiol_faxable: Unable to open tif file for writing\n"));
+    mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
     return 0;
   }
 
-  mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
-  
-  if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      width)   )
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     height)  )
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,   1)        )
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
+  for (i = 0; i < count; ++i) {
+    if (!i_writetiff_low_faxable(tif, imgs[i], fine)) {
+      TIFFClose(tif);
+      return 0;
+    }
 
-  linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
-  
-  if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
-    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
+    if (!TIFFWriteDirectory(tif)) {
+      i_push_error(0, "Cannot write TIFF directory");
+      TIFFClose(tif);
+      return 0;
+    }
+  }
 
-  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
-  TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
+  (void) TIFFClose(tif);
+  return 1;
+}
 
-  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
-  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
-  mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
+/*
+=item i_writetiff_wiol(im, ig)
 
-  if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
-    { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
-  if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
-    mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0; 
+Stores an image in the iolayer object.
+
+   im - image object to write out
+   ig - io_object that defines source to write to 
+
+=cut 
+*/
+undef_int
+i_writetiff_wiol(i_img *img, io_glue *ig) {
+  TIFF* tif;
+  int i;
+
+  io_glue_commit_types(ig);
+  i_clear_error();
+  mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", img, ig));
+
+  /* FIXME: Enable the mmap interface */
+  
+  tif = TIFFClientOpen("No name", 
+                      "wm",
+                      (thandle_t) ig, 
+                      (TIFFReadWriteProc) ig->readcb,
+                      (TIFFReadWriteProc) ig->writecb,
+                      (TIFFSeekProc)      comp_seek,
+                      (TIFFCloseProc)     ig->closecb, 
+                      (TIFFSizeProc)      ig->sizecb,
+                      (TIFFMapFileProc)   NULL,
+                      (TIFFUnmapFileProc) NULL);
+  
+
+
+  if (!tif) {
+    mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
+    return 0;
   }
 
-  if (!save_tiff_tags(tif, im)) {
+  if (!i_writetiff_low(tif, img)) {
     TIFFClose(tif);
     return 0;
   }
 
-  for (y=0; y<height; y++) {
-    int linebufpos=0;
-    for(x=0; x<width; x+=8) { 
-      int bits;
-      int bitpos;
-      i_sample_t luma[8];
-      uint8 bitval = 128;
-      linebuf[linebufpos]=0;
-      bits = width-x; if(bits>8) bits=8;
-      i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
-      for(bitpos=0;bitpos<bits;bitpos++) {
-       linebuf[linebufpos] |= ((luma[bitpos]>=128)?bitval:0);
-       bitval >>= 1;
-      }
-      linebufpos++;
-    }
-    if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
-      mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
-      break;
-    }
+  (void) TIFFClose(tif);
+  return 1;
+}
+
+
+
+/*
+=item i_writetiff_wiol_faxable(i_img *, io_glue *)
+
+Stores an image in the iolayer object in faxable tiff format.
+
+   im - image object to write out
+   ig - io_object that defines source to write to 
+
+Note, this may be rewritten to use to simply be a call to a
+lower-level function that gives more options for writing tiff at some
+point.
+
+=cut
+*/
+
+undef_int
+i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
+  TIFF* tif;
+  int i;
+
+  io_glue_commit_types(ig);
+  i_clear_error();
+  mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", im, ig));
+
+  /* FIXME: Enable the mmap interface */
+  
+  tif = TIFFClientOpen("No name", 
+                      "wm",
+                      (thandle_t) ig, 
+                      (TIFFReadWriteProc) ig->readcb,
+                      (TIFFReadWriteProc) ig->writecb,
+                      (TIFFSeekProc)      comp_seek,
+                      (TIFFCloseProc)     ig->closecb, 
+                      (TIFFSizeProc)      ig->sizecb,
+                      (TIFFMapFileProc)   NULL,
+                      (TIFFUnmapFileProc) NULL);
+  
+
+
+  if (!tif) {
+    mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
+    return 0;
   }
+
+  if (!i_writetiff_low_faxable(tif, im, fine)) {
+    TIFFClose(tif);
+    return 0;
+  }
+
   (void) TIFFClose(tif);
-  if (linebuf) _TIFFfree(linebuf);
   return 1;
 }
 
 static int save_tiff_tags(TIFF *tif, i_img *im) {
   int i;
-
   for (i = 0; i < text_tag_count; ++i) {
     int entry;
     if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) {
       if (!TIFFSetField(tif, text_tag_names[i].tag, 
-                       im->tags.tags[entry].data)) {
-       i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
-       return 0;
+                       im->tags.tags[entry].data)) {
+       i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
+       return 0;
       }
     }
   }
-
   return 1;
 }
 
+
 /*
 =item expand_4bit_hl(buf, count)