]> git.imager.perl.org - imager.git/blobdiff - Imager.pm
Adds reading capabilities for certain variants of targa, writer code has not been
[imager.git] / Imager.pm
index feebbd588b739a75d498271898a6496c8ff28eb7..3a85b1279ffcafddc2936dcaa2f144d128a403bb 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1,7 +1,5 @@
 package Imager;
 
-
-
 use strict;
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS %formats $DEBUG %filters %DSOs $ERRSTR $fontstate %OPCODES $I2P $FORMATGUESS);
 use IO::File;
@@ -16,17 +14,17 @@ use Imager::Font;
                DSO_close
                DSO_funclist
                DSO_call
-               
+
                load_plugin
                unload_plugin
-               
+
                i_list_formats
                i_has_format
-               
+
                i_color_new
                i_color_set
                i_color_info
-               
+
                i_img_empty
                i_img_empty_ch
                i_img_exorcise
@@ -42,7 +40,8 @@ use Imager::Font;
                i_box
                i_box_filled
                i_arc
-               
+               i_circle_aa
+
                i_bezier_multi
                i_poly_aa
 
@@ -52,11 +51,13 @@ use Imager::Font;
                i_scale_nn
                i_haar
                i_count_colors
-               
-               
+
                i_gaussian
                i_conv
-               
+
+               i_convert
+               i_map
+
                i_img_diff
 
                i_init_fonts
@@ -67,15 +68,11 @@ use Imager::Font;
                i_t1_text
                i_t1_bbox
 
-
                i_tt_set_aa
                i_tt_cp
                i_tt_text
                i_tt_bbox
 
-               i_readjpeg
-               i_writejpeg
-
                i_readjpeg_wiol
                i_writejpeg_wiol
 
@@ -83,8 +80,8 @@ use Imager::Font;
                i_writetiff_wiol
                i_writetiff_wiol_faxable
 
-               i_readpng
-               i_writepng
+               i_readpng_wiol
+               i_writepng_wiol
 
                i_readgif
                i_readgif_callback
@@ -94,10 +91,10 @@ use Imager::Font;
                i_writegif_callback
 
                i_readpnm_wiol
-               i_writeppm
+               i_writeppm_wiol
 
-               i_readraw
-               i_writeraw
+               i_readraw_wiol
+               i_writeraw_wiol
 
                i_contrast
                i_hardinvert
@@ -106,11 +103,11 @@ use Imager::Font;
                i_postlevels
                i_mosaic
                i_watermark
-               
+
                malloc_state
 
                list_formats
-               
+
                i_gifquant
 
                newfont
@@ -118,11 +115,8 @@ use Imager::Font;
                newcolour
                NC
                NF
-               
 );
 
-
-
 @EXPORT=qw( 
           init_log
           i_list_formats
@@ -147,25 +141,26 @@ use Imager::Font;
                  unload_plugin
                 )]);
 
-
 BEGIN {
   require Exporter;
   require DynaLoader;
 
-  $VERSION = '0.38pre9';
+  $VERSION = '0.39pre1';
   @ISA = qw(Exporter DynaLoader);
   bootstrap Imager $VERSION;
 }
 
 BEGIN {
   i_init_fonts(); # Initialize font engines
+  Imager::Font::__init();
   for(i_list_formats()) { $formats{$_}++; }
 
   if ($formats{'t1'}) {
     i_t1_set_aa(1);
   }
 
-  if (!$formats{'t1'} and !$formats{'tt'}) {
+  if (!$formats{'t1'} and !$formats{'tt'} 
+      && !$formats{'ft2'} && !$formats{'w32'}) {
     $fontstate='no font support';
   }
 
@@ -173,6 +168,16 @@ BEGIN {
 
   $DEBUG=0;
 
+  # the members of the subhashes under %filters are:
+  #  callseq - a list of the parameters to the underlying filter in the
+  #            order they are passed
+  #  callsub - a code ref that takes a named parameter list and calls the
+  #            underlying filter
+  #  defaults - a hash of default values
+  #  names - defines names for value of given parameters so if the names 
+  #          field is foo=> { bar=>1 }, and the user supplies "bar" as the
+  #          foo parameter, the filter will receive 1 for the foo
+  #          parameter
   $filters{contrast}={
                      callseq => ['image','intensity'],
                      callsub => sub { my %hsh=@_; i_contrast($hsh{image},$hsh{intensity}); } 
@@ -225,6 +230,100 @@ BEGIN {
                            defaults => { },
                            callsub => sub { my %hsh=@_; i_nearest_color($hsh{image}, $hsh{xo}, $hsh{yo}, $hsh{colors}, $hsh{dist}); }
                           };
+  $filters{gaussian} = {
+                        callseq => [ 'image', 'stddev' ],
+                        defaults => { },
+                        callsub => sub { my %hsh = @_; i_gaussian($hsh{image}, $hsh{stddev}); },
+                       };
+  $filters{mosaic} =
+    {
+     callseq => [ qw(image size) ],
+     defaults => { size => 20 },
+     callsub => sub { my %hsh = @_; i_mosaic($hsh{image}, $hsh{size}) },
+    };
+  $filters{bumpmap} =
+    {
+     callseq => [ qw(image bump elevation lightx lighty st) ],
+     defaults => { elevation=>0, st=> 2 },
+     callsub => sub { 
+       my %hsh = @_;
+       i_bumpmap($hsh{image}, $hsh{bump}{IMG}, $hsh{elevation},
+                 $hsh{lightx}, $hsh{lighty}, $hsh{st});
+     },
+    };
+  $filters{postlevels} =
+    {
+     callseq  => [ qw(image levels) ],
+     defaults => { levels => 10 },
+     callsub  => sub { my %hsh = @_; i_postlevels($hsh{image}, $hsh{levels}); },
+    };
+  $filters{watermark} =
+    {
+     callseq  => [ qw(image wmark tx ty pixdiff) ],
+     defaults => { pixdiff=>10, tx=>0, ty=>0 },
+     callsub  => 
+     sub { 
+       my %hsh = @_; 
+       i_watermark($hsh{image}, $hsh{wmark}{IMG}, $hsh{tx}, $hsh{ty}, 
+                   $hsh{pixdiff}); 
+     },
+    };
+  $filters{fountain} =
+    {
+     callseq  => [ qw(image xa ya xb yb ftype repeat combine super_sample ssample_param segments) ],
+     names    => {
+                  ftype => { linear         => 0,
+                             bilinear       => 1,
+                             radial         => 2,
+                             radial_square  => 3,
+                             revolution     => 4,
+                             conical        => 5 },
+                  repeat => { none      => 0,
+                              sawtooth  => 1,
+                              triangle  => 2,
+                              saw_both  => 3,
+                              tri_both  => 4,
+                            },
+                  super_sample => {
+                                   none    => 0,
+                                   grid    => 1,
+                                   random  => 2,
+                                   circle  => 3,
+                                  },
+                  combine => {
+                              none      => 0,
+                              normal    => 1,
+                              multiply  => 2, mult => 2,
+                              dissolve  => 3,
+                              add       => 4,
+                              subtract  => 5, sub => 5,
+                              diff      => 6,
+                              lighten   => 7,
+                              darken    => 8,
+                              hue       => 9,
+                              sat       => 10,
+                              value     => 11,
+                              color     => 12,
+                             },
+                 },
+     defaults => { ftype => 0, repeat => 0, combine => 0,
+                   super_sample => 0, ssample_param => 4,
+                   segments=>[ 
+                              [ 0, 0.5, 1,
+                                Imager::Color->new(0,0,0),
+                                Imager::Color->new(255, 255, 255),
+                                0, 0,
+                              ],
+                             ],
+                 },
+     callsub  => 
+     sub {
+       my %hsh = @_;
+       i_fountain($hsh{image}, $hsh{xa}, $hsh{ya}, $hsh{xb}, $hsh{yb},
+                  $hsh{ftype}, $hsh{repeat}, $hsh{combine}, $hsh{super_sample},
+                  $hsh{ssample_param}, $hsh{segments});
+     },
+    };
 
   $FORMATGUESS=\&def_guess_type;
 }
@@ -310,7 +409,6 @@ sub _error_as_msg {
   return join(": ", map $_->[0], i_errors());
 }
 
-
 #
 # Methods to be called on objects.
 #
@@ -332,7 +430,6 @@ sub new {
   return $self;
 }
 
-
 # Copy an entire image with no changes 
 # - if an image has magic the copy of it will not be magical
 
@@ -377,21 +474,27 @@ sub crop {
                                @hsh{qw(left right bottom top)});
   $l=0 if not defined $l;
   $t=0 if not defined $t;
+
+  $r||=$l+delete $hsh{'width'}    if defined $l and exists $hsh{'width'};
+  $b||=$t+delete $hsh{'height'}   if defined $t and exists $hsh{'height'};
+  $l||=$r-delete $hsh{'width'}    if defined $r and exists $hsh{'width'};
+  $t||=$b-delete $hsh{'height'}   if defined $b and exists $hsh{'height'};
+
   $r=$self->getwidth if not defined $r;
   $b=$self->getheight if not defined $b;
 
   ($l,$r)=($r,$l) if $l>$r;
   ($t,$b)=($b,$t) if $t>$b;
 
-  if ($hsh{'width'}) { 
-    $l=int(0.5+($w-$hsh{'width'})/2); 
-    $r=$l+$hsh{'width'}; 
+  if ($hsh{'width'}) {
+    $l=int(0.5+($w-$hsh{'width'})/2);
+    $r=$l+$hsh{'width'};
   } else {
     $hsh{'width'}=$r-$l;
   }
-  if ($hsh{'height'}) { 
-    $b=int(0.5+($h-$hsh{'height'})/2); 
-    $t=$h+$hsh{'height'}; 
+  if ($hsh{'height'}) {
+    $b=int(0.5+($h-$hsh{'height'})/2);
+    $t=$h+$hsh{'height'};
   } else {
     $hsh{'height'}=$b-$t;
   }
@@ -399,7 +502,7 @@ sub crop {
 #    print "l=$l, r=$r, h=$hsh{'width'}\n";
 #    print "t=$t, b=$b, w=$hsh{'height'}\n";
 
-  my $dst=Imager->new(xsize=>$hsh{'width'},ysize=>$hsh{'height'},channels=>$self->getchannels());
+  my $dst=Imager->new(xsize=>$hsh{'width'}, ysize=>$hsh{'height'}, channels=>$self->getchannels());
 
   i_copyto($dst->{IMG},$self->{IMG},$l,$t,$r,$b,0,0);
   return $dst;
@@ -411,14 +514,257 @@ sub crop {
 sub img_set {
   my $self=shift;
 
-  my %hsh=(xsize=>100,ysize=>100,channels=>3,@_);
+  my %hsh=(xsize=>100, ysize=>100, channels=>3, bits=>8, type=>'direct', @_);
 
   if (defined($self->{IMG})) {
-    i_img_destroy($self->{IMG});
+    # let IIM_DESTROY destroy it, it's possible this image is
+    # referenced from a virtual image (like masked)
+    #i_img_destroy($self->{IMG});
     undef($self->{IMG});
   }
 
-  $self->{IMG}=Imager::ImgRaw::new($hsh{'xsize'},$hsh{'ysize'},$hsh{'channels'});
+  if ($hsh{type} eq 'paletted' || $hsh{type} eq 'pseudo') {
+    $self->{IMG} = i_img_pal_new($hsh{xsize}, $hsh{ysize}, $hsh{channels},
+                                 $hsh{maxcolors} || 256);
+  }
+  elsif ($hsh{bits} == 16) {
+    $self->{IMG} = i_img_16_new($hsh{xsize}, $hsh{ysize}, $hsh{channels});
+  }
+  else {
+    $self->{IMG}=Imager::ImgRaw::new($hsh{'xsize'}, $hsh{'ysize'},
+                                     $hsh{'channels'});
+  }
+}
+
+# created a masked version of the current image
+sub masked {
+  my $self = shift;
+
+  $self or return undef;
+  my %opts = (left    => 0, 
+              top     => 0, 
+              right   => $self->getwidth, 
+              bottom  => $self->getheight,
+              @_);
+  my $mask = $opts{mask} ? $opts{mask}{IMG} : undef;
+
+  my $result = Imager->new;
+  $result->{IMG} = i_img_masked_new($self->{IMG}, $mask, $opts{left}, 
+                                    $opts{top}, $opts{right} - $opts{left},
+                                    $opts{bottom} - $opts{top});
+  # keep references to the mask and base images so they don't
+  # disappear on us
+  $result->{DEPENDS} = [ $self->{IMG}, $mask ];
+
+  $result;
+}
+
+# convert an RGB image into a paletted image
+sub to_paletted {
+  my $self = shift;
+  my $opts;
+  if (@_ != 1 && !ref $_[0]) {
+    $opts = { @_ };
+  }
+  else {
+    $opts = shift;
+  }
+
+  my $result = Imager->new;
+  $result->{IMG} = i_img_to_pal($self->{IMG}, $opts);
+
+  #print "Type ", i_img_type($result->{IMG}), "\n";
+
+  $result->{IMG} or undef $result;
+
+  return $result;
+}
+
+# convert a paletted (or any image) to an 8-bit/channel RGB images
+sub to_rgb8 {
+  my $self = shift;
+  my $result;
+
+  if ($self->{IMG}) {
+    $result = Imager->new;
+    $result->{IMG} = i_img_to_rgb($self->{IMG})
+      or undef $result;
+  }
+
+  return $result;
+}
+
+sub addcolors {
+  my $self = shift;
+  my %opts = (colors=>[], @_);
+
+  @{$opts{colors}} or return undef;
+
+  $self->{IMG} and i_addcolors($self->{IMG}, @{$opts{colors}});
+}
+
+sub setcolors {
+  my $self = shift;
+  my %opts = (start=>0, colors=>[], @_);
+  @{$opts{colors}} or return undef;
+
+  $self->{IMG} and i_setcolors($self->{IMG}, $opts{start}, @{$opts{colors}});
+}
+
+sub getcolors {
+  my $self = shift;
+  my %opts = @_;
+  if (!exists $opts{start} && !exists $opts{count}) {
+    # get them all
+    $opts{start} = 0;
+    $opts{count} = $self->colorcount;
+  }
+  elsif (!exists $opts{count}) {
+    $opts{count} = 1;
+  }
+  elsif (!exists $opts{start}) {
+    $opts{start} = 0;
+  }
+  
+  $self->{IMG} and 
+    return i_getcolors($self->{IMG}, $opts{start}, $opts{count});
+}
+
+sub colorcount {
+  i_colorcount($_[0]{IMG});
+}
+
+sub maxcolors {
+  i_maxcolors($_[0]{IMG});
+}
+
+sub findcolor {
+  my $self = shift;
+  my %opts = @_;
+  $opts{color} or return undef;
+
+  $self->{IMG} and i_findcolor($self->{IMG}, $opts{color});
+}
+
+sub bits {
+  my $self = shift;
+  $self->{IMG} and i_img_bits($self->{IMG});
+}
+
+sub type {
+  my $self = shift;
+  if ($self->{IMG}) {
+    return i_img_type($self->{IMG}) ? "paletted" : "direct";
+  }
+}
+
+sub virtual {
+  my $self = shift;
+  $self->{IMG} and i_img_virtual($self->{IMG});
+}
+
+sub tags {
+  my ($self, %opts) = @_;
+
+  $self->{IMG} or return;
+
+  if (defined $opts{name}) {
+    my @result;
+    my $start = 0;
+    my $found;
+    while (defined($found = i_tags_find($self->{IMG}, $opts{name}, $start))) {
+      push @result, (i_tags_get($self->{IMG}, $found))[1];
+      $start = $found+1;
+    }
+    return wantarray ? @result : $result[0];
+  }
+  elsif (defined $opts{code}) {
+    my @result;
+    my $start = 0;
+    my $found;
+    while (defined($found = i_tags_findn($self->{IMG}, $opts{code}, $start))) {
+      push @result, (i_tags_get($self->{IMG}, $found))[1];
+      $start = $found+1;
+    }
+    return @result;
+  }
+  else {
+    if (wantarray) {
+      return map { [ i_tags_get($self->{IMG}, $_) ] } 0.. i_tags_count($self->{IMG})-1;
+    }
+    else {
+      return i_tags_count($self->{IMG});
+    }
+  }
+}
+
+sub addtag {
+  my $self = shift;
+  my %opts = @_;
+
+  return -1 unless $self->{IMG};
+  if ($opts{name}) {
+    if (defined $opts{value}) {
+      if ($opts{value} =~ /^\d+$/) {
+        # add as a number
+        return i_tags_addn($self->{IMG}, $opts{name}, 0, $opts{value});
+      }
+      else {
+        return i_tags_add($self->{IMG}, $opts{name}, 0, $opts{value}, 0);
+      }
+    }
+    elsif (defined $opts{data}) {
+      # force addition as a string
+      return i_tags_add($self->{IMG}, $opts{name}, 0, $opts{data}, 0);
+    }
+    else {
+      $self->{ERRSTR} = "No value supplied";
+      return undef;
+    }
+  }
+  elsif ($opts{code}) {
+    if (defined $opts{value}) {
+      if ($opts{value} =~ /^\d+$/) {
+        # add as a number
+        return i_tags_addn($self->{IMG}, $opts{code}, 0, $opts{value});
+      }
+      else {
+        return i_tags_add($self->{IMG}, $opts{code}, 0, $opts{value}, 0);
+      }
+    }
+    elsif (defined $opts{data}) {
+      # force addition as a string
+      return i_tags_add($self->{IMG}, $opts{code}, 0, $opts{data}, 0);
+    }
+    else {
+      $self->{ERRSTR} = "No value supplied";
+      return undef;
+    }
+  }
+  else {
+    return undef;
+  }
+}
+
+sub deltag {
+  my $self = shift;
+  my %opts = @_;
+
+  return 0 unless $self->{IMG};
+
+  if (defined $opts{index}) {
+    return i_tags_delete($self->{IMG}, $opts{index});
+  }
+  elsif (defined $opts{name}) {
+    return i_tags_delbyname($self->{IMG}, $opts{name});
+  }
+  elsif (defined $opts{code}) {
+    return i_tags_delbycode($self->{IMG}, $opts{code});
+  }
+  else {
+    $self->{ERRSTR} = "Need to supply index, name, or code parameter";
+    return 0;
+  }
 }
 
 # Read an image from file
@@ -429,139 +775,200 @@ sub read {
   my ($fh, $fd, $IO);
 
   if (defined($self->{IMG})) {
-    i_img_destroy($self->{IMG});
+    # let IIM_DESTROY do the destruction, since the image may be
+    # referenced from elsewhere
+    #i_img_destroy($self->{IMG});
     undef($self->{IMG});
   }
 
-  if (!$input{fd} and !$input{file} and !$input{data}) { $self->{ERRSTR}='no file, fd or data parameter'; return undef; }
+  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; }
+    if (!defined $fh) {
+      $self->{ERRSTR}='Could not open file'; return undef;
+    }
     binmode($fh);
     $fd = $fh->fileno();
   }
-  if ($input{fd}) { $fd=$input{fd} };
+  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
+  # has been there for half a year dude.
+  # Look, i just work here, ok?
 
-  if (!$input{type} and $input{file}) { $input{type}=$FORMATGUESS->($input{file}); }
-  if (!$formats{$input{type}}) { $self->{ERRSTR}='format not supported'; return undef; }
+  if (!$input{type} and $input{file}) {
+    $input{type}=$FORMATGUESS->($input{file});
+  }
+  if (!$formats{$input{type}}) {
+    $self->{ERRSTR}='format not supported'; return undef;
+  }
 
-  my %iolready=(jpeg=>1, tiff=>1, pnm=>1);
+  my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1);
 
   if ($iolready{$input{type}}) {
     # Setup data source
-    $IO = io_new_fd($fd); # sort of simple for now eh?
+    $IO = io_new_fd($fd);      # sort of simple for now eh?
 
     if ( $input{type} eq 'jpeg' ) {
       ($self->{IMG},$self->{IPTCRAW})=i_readjpeg_wiol( $IO );
-      if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read jpeg image'; return undef; }
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read jpeg image'; return undef;
+      }
       $self->{DEBUG} && print "loading a jpeg file\n";
       return $self;
     }
 
     if ( $input{type} eq 'tiff' ) {
       $self->{IMG}=i_readtiff_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed
-      if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read tiff image'; return undef; }
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read tiff image'; return undef;
+      }
       $self->{DEBUG} && print "loading a tiff file\n";
       return $self;
     }
 
     if ( $input{type} eq 'pnm' ) {
       $self->{IMG}=i_readpnm_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed
-      if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read pnm image: '._error_as_msg(); return undef; }
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read pnm image: '._error_as_msg(); return undef;
+      }
       $self->{DEBUG} && print "loading a pnm file\n";
       return $self;
     }
 
-  } else {
+    if ( $input{type} eq 'png' ) {
+      $self->{IMG}=i_readpng_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read png image';
+       return undef;
+      }
+      $self->{DEBUG} && print "loading a png file\n";
+    }
 
-  # Old code for reference while changing the new stuff
+    if ( $input{type} eq 'bmp' ) {
+      $self->{IMG}=i_readbmp_wiol( $IO );
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read bmp image';
+       return undef;
+      }
+      $self->{DEBUG} && print "loading a bmp 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}) ) {
+       $self->{ERRSTR}=$self->_error_as_msg();
+#      $self->{ERRSTR}='unable to read tga image';
+       return undef;
+      }
+      $self->{DEBUG} && print "loading a tga file\n";
+    }
 
-  if (!$input{type} and $input{file}) { $input{type}=$FORMATGUESS->($input{file}); }
-  if (!$input{type}) { $self->{ERRSTR}='type parameter missing and not possible to guess from extension'; return undef; }
+    if ( $input{type} eq 'raw' ) {
+      my %params=(datachannels=>3,storechannels=>3,interleave=>1,%input);
 
-  if (!$formats{$input{type}}) { $self->{ERRSTR}='format not supported'; return undef; }
+      if ( !($params{xsize} && $params{ysize}) ) {
+       $self->{ERRSTR}='missing xsize or ysize parameter for raw';
+       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} };
+      $self->{IMG} = i_readraw_wiol( $IO,
+                                    $params{xsize},
+                                    $params{ysize},
+                                    $params{datachannels},
+                                    $params{storechannels},
+                                    $params{interleave});
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}='unable to read raw image';
+       return undef;
+      }
+      $self->{DEBUG} && print "loading a raw file\n";
+    }
+
+  } else {
 
-  if ( $input{type} eq 'gif' ) {
-    my $colors;
-    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";
+    # Old code for reference while changing the new stuff
+
+    if (!$input{type} and $input{file}) {
+      $input{type}=$FORMATGUESS->($input{file});
+    }
+
+    if (!$input{type}) {
+      $self->{ERRSTR}='type parameter missing and not possible to guess from extension'; return undef;
+    }
+
+    if (!$formats{$input{type}}) {
+      $self->{ERRSTR}='format not supported';
       return undef;
     }
-    if (exists $input{data}) {
-      if ($input{colors}) {
-       ($self->{IMG}, $colors) = i_readgif_scalar($input{data});
-      }
-      else {
-       $self->{IMG}=i_readgif_scalar($input{data});
+
+    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};
     }
-    else { 
-      if ($input{colors}) {
-       ($self->{IMG}, $colors) = i_readgif( $fd );
+
+    if ( $input{type} eq 'gif' ) {
+      my $colors;
+      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;
       }
-      else {
-       $self->{IMG} = i_readgif( $fd )
+      if (exists $input{data}) {
+       if ($input{colors}) {
+         ($self->{IMG}, $colors) = i_readgif_scalar($input{data});
+       } else {
+         $self->{IMG}=i_readgif_scalar($input{data});
+       }
+      } else {
+       if ($input{colors}) {
+         ($self->{IMG}, $colors) = i_readgif( $fd );
+       } else {
+         $self->{IMG} = i_readgif( $fd )
+       }
+      }
+      if ($colors) {
+       # we may or may not change i_readgif to return blessed objects...
+       ${ $input{colors} } = [ map { NC(@$_) } @$colors ];
+      }
+      if ( !defined($self->{IMG}) ) {
+       $self->{ERRSTR}= 'reading GIF:'._error_as_msg();
+       return undef;
       }
+      $self->{DEBUG} && print "loading a gif file\n";
     }
-    if ($colors) {
-      # we may or may not change i_readgif to return blessed objects...
-      ${$input{colors}} = [ map { NC(@$_) } @$colors ];
-    }
-    if ( !defined($self->{IMG}) ) {
-      $self->{ERRSTR}= 'reading GIF:'._error_as_msg(); return undef;
-    }
-    $self->{DEBUG} && print "loading a gif file\n";
-  } elsif ( $input{type} eq 'jpeg' ) {
-    if (exists $input{data}) { ($self->{IMG},$self->{IPTCRAW})=i_readjpeg_scalar($input{data}); }
-    else { ($self->{IMG},$self->{IPTCRAW})=i_readjpeg( $fd ); }
-    if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read jpeg image'; return undef; }
-    $self->{DEBUG} && print "loading a jpeg file\n";
-  } elsif ( $input{type} eq 'png' ) {
-    if (exists $input{data}) { $self->{IMG}=i_readpng_scalar($input{data}); }
-    else { $self->{IMG}=i_readpng( $fd ); }
-    if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read png image'; return undef; }
-    $self->{DEBUG} && print "loading a png file\n";
-  } elsif ( $input{type} eq 'raw' ) {
-    my %params=(datachannels=>3,storechannels=>3,interleave=>1);
-    for(keys(%input)) { $params{$_}=$input{$_}; }
-
-    if ( !($params{xsize} && $params{ysize}) ) { $self->{ERRSTR}='missing xsize or ysize parameter for raw'; return undef; }
-    $self->{IMG}=i_readraw( $fd, $params{xsize}, $params{ysize},
-                          $params{datachannels}, $params{storechannels}, $params{interleave});
-    if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read raw image'; return undef; }
-    $self->{DEBUG} && print "loading a raw file\n";
   }
   return $self;
-  }
 }
 
-
 # Write an image to file
-
 sub write {
   my $self = shift;
-  my %input=(jpegquality=>75, gifquant=>'mc', lmdither=>6.0, lmfixed=>[], @_);
+  my %input=(jpegquality=>75, gifquant=>'mc', lmdither=>6.0, lmfixed=>[], 
+            fax_fine=>1, @_);
   my ($fh, $rc, $fd, $IO);
 
-  my %iolready=( tiff=>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 ); # 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}) { $input{type}=$FORMATGUESS->($input{file}); }
+  if (!$input{type} and $input{file}) { $input{type}=$FORMATGUESS->($input{file}); }
   if (!$input{type}) { $self->{ERRSTR}='type parameter missing and not possible to guess from extension'; return undef; }
 
   if (!$formats{$input{type}}) { $self->{ERRSTR}='format not supported'; return undef; }
@@ -573,39 +980,76 @@ sub write {
   } else {
     $fh = new IO::File($input{file},"w+");
     if (!defined $fh) { $self->{ERRSTR}='Could not open file'; return undef; }
-    binmode($fh);
+    binmode($fh) or die;
     $fd = $fh->fileno();
   }
 
-
-
   if ($iolready{$input{type}}) {
-    if ($fd) {
+    if (defined $fd) {
       $IO = io_new_fd($fd);
     }
 
     if ($input{type} eq 'tiff') {
-      if ($input{class} eq 'fax') {
-       if (!i_writetiff_wiol_faxable($self->{IMG}, $IO)) { 
+      if (defined $input{class} && $input{class} eq 'fax') {
+       if (!i_writetiff_wiol_faxable($self->{IMG}, $IO, $input{fax_fine})) {
          $self->{ERRSTR}='Could not write to buffer';
          return undef;
        }
-      }
-      else {
-       if (!i_writetiff_wiol($self->{IMG}, $IO)) { 
-         $self->{ERRSTR}='Could not write to buffer'; 
-         return undef; 
+      } else {
+       if (!i_writetiff_wiol($self->{IMG}, $IO)) {
+         $self->{ERRSTR}='Could not write to buffer';
+         return undef;
        }
       }
+    } elsif ( $input{type} eq 'pnm' ) {
+      if ( ! i_writeppm_wiol($self->{IMG},$IO) ) {
+       $self->{ERRSTR}='unable to write pnm image';
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a pnm file\n";
+    } elsif ( $input{type} eq 'raw' ) {
+      if ( !i_writeraw_wiol($self->{IMG},$IO) ) {
+       $self->{ERRSTR}='unable to write raw image';
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a raw file\n";
+    } elsif ( $input{type} eq 'png' ) {
+      if ( !i_writepng_wiol($self->{IMG}, $IO) ) {
+       $self->{ERRSTR}='unable to write png image';
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a png file\n";
+    } elsif ( $input{type} eq 'jpeg' ) {
+      if ( !i_writejpeg_wiol($self->{IMG}, $IO, $input{jpegquality})) {
+        $self->{ERRSTR} = $self->_error_as_msg();
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a jpeg file\n";
+    } elsif ( $input{type} eq 'bmp' ) {
+      if ( !i_writebmp_wiol($self->{IMG}, $IO) ) {
+       $self->{ERRSTR}='unable to write bmp image';
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a bmp file\n";
+    } elsif ( $input{type} eq 'tga' ) {
+      if ( !i_writetga_wiol($self->{IMG}, $IO) ) {
+       $self->{ERRSTR}=$self->_error_as_msg();
+#      $self->{ERRSTR}='unable to write tga image';
+       return undef;
+      }
+      $self->{DEBUG} && print "writing a tga file\n";
     }
 
-    my $data = io_slurp($IO);
-    if (!$data) { $self->{ERRSTR}='Could not slurp from buffer'; return undef; }
-
-    ${$input{data}} = $data;
+    if (exists $input{'data'}) {
+      my $data = io_slurp($IO);
+      if (!$data) {
+       $self->{ERRSTR}='Could not slurp from buffer';
+       return undef;
+      }
+      ${$input{data}} = $data;
+    }
     return $self;
   } else {
-
     if ( $input{type} eq 'gif' ) {
       if (not $input{gifplanes}) {
        my $gp;
@@ -647,8 +1091,6 @@ sub write {
          $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 {
@@ -659,43 +1101,7 @@ sub write {
       }
       $self->{DEBUG} && print "writing a gif file\n";
 
-    } elsif ( $input{type} eq 'jpeg' ) {
-      $rc=i_writejpeg($self->{IMG},$fd,$input{jpegquality});
-      if ( !defined($rc) ) {
-       $self->{ERRSTR}='unable to write jpeg image'; return undef;
-      }
-      $self->{DEBUG} && print "writing a jpeg file\n";
-    } elsif ( $input{type} eq 'png' ) { 
-      $rc=i_writepng($self->{IMG},$fd);
-      if ( !defined($rc) ) {
-       $self->{ERRSTR}='unable to write png image'; return undef;
-      }
-      $self->{DEBUG} && print "writing a png file\n";
-    } elsif ( $input{type} eq 'pnm' ) {
-      $rc=i_writeppm($self->{IMG},$fd);
-      if ( !defined($rc) ) {
-       $self->{ERRSTR}='unable to write pnm image'; return undef;
-      }
-      $self->{DEBUG} && print "writing a pnm file\n";
-    } elsif ( $input{type} eq 'raw' ) {
-      $rc=i_writeraw($self->{IMG},$fd);
-      if ( !defined($rc) ) {
-       $self->{ERRSTR}='unable to write raw image'; return undef;
-      }
-      $self->{DEBUG} && print "writing a raw file\n";
-    } elsif ( $input{type} eq 'tiff' ) {
-      if ($input{class} eq 'fax') {
-       $rc=i_writetiff_wiol($self->{IMG},io_new_fd($fd) );
-      }
-      else {
-       $rc=i_writetiff_wiol_faxable($self->{IMG},io_new_fd($fd) );
-      }
-      if ( !defined($rc) ) {
-       $self->{ERRSTR}='unable to write tiff image'; return undef;
-      }
-      $self->{DEBUG} && print "writing a tiff file\n";
     }
-
   }
   return $self;
 }
@@ -704,6 +1110,12 @@ sub write_multi {
   my ($class, $opts, @images) = @_;
 
   if ($opts->{type} eq 'gif') {
+    my $gif_delays = $opts->{gif_delays};
+    local $opts->{gif_delays} = $gif_delays;
+    unless (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)";
@@ -736,13 +1148,91 @@ sub write_multi {
   }
 }
 
-# Destroy an Imager object
+# read multiple images from a file
+sub read_multi {
+  my ($class, %opts) = @_;
 
-sub DESTROY {
-  my $self=shift;
+  if ($opts{file} && !exists $opts{type}) {
+    # guess the type 
+    my $type = $FORMATGUESS->($opts{file});
+    $opts{type} = $type;
+  }
+  unless ($opts{type}) {
+    $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;
+  }
+
+  if ($opts{type} eq 'gif') {
+    my @imgs;
+    if ($fd) {
+      @imgs = i_readgif_multi($fd);
+    }
+    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});
+      }
+    }
+    if (@imgs) {
+      return map { 
+        bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' 
+      } @imgs;
+    }
+    else {
+      $ERRSTR = _error_as_msg();
+      return;
+    }
+  }
+
+  $ERRSTR = "Cannot read multiple images from $opts{type} files";
+  return;
+}
+
+# Destroy an Imager object
+
+sub DESTROY {
+  my $self=shift;
   #    delete $instances{$self};
   if (defined($self->{IMG})) {
-    i_img_destroy($self->{IMG});
+    # the following is now handled by the XS DESTROY method for
+    # Imager::ImgRaw object
+    # Re-enabling this will break virtual images
+    # tested for in t/t020masked.t
+    # i_img_destroy($self->{IMG});
     undef($self->{IMG});
   } else {
 #    print "Destroy Called on an empty image!\n"; # why did I put this here??
@@ -764,6 +1254,14 @@ sub filter {
     $self->{ERRSTR}='type parameter not matching any filter'; return undef;
   }
 
+  if ($filters{$input{type}}{names}) {
+    my $names = $filters{$input{type}}{names};
+    for my $name (keys %$names) {
+      if (defined $input{$name} && exists $names->{$name}{$input{$name}}) {
+        $input{$name} = $names->{$name}{$input{$name}};
+      }
+    }
+  }
   if (defined($filters{$input{type}}{defaults})) {
     %hsh=('image',$self->{IMG},%{$filters{$input{type}}{defaults}},%input);
   } else {
@@ -1017,13 +1515,6 @@ sub transform {
   }
 }
 
-
-
-
-
-
-
-
 sub rubthrough {
   my $self=shift;
   my %opts=(tx=>0,ty=>0,@_);
@@ -1031,7 +1522,10 @@ sub rubthrough {
   unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; }
   unless ($opts{src} && $opts{src}->{IMG}) { $self->{ERRSTR}='empty input image for source'; return undef; }
 
-  i_rubthru($self->{IMG}, $opts{src}->{IMG}, $opts{tx},$opts{ty});
+  unless (i_rubthru($self->{IMG}, $opts{src}->{IMG}, $opts{tx},$opts{ty})) {
+    $self->{ERRSTR} = $self->_error_as_msg();
+    return undef;
+  }
   return $self;
 }
 
@@ -1047,22 +1541,86 @@ sub flip {
   return ();
 }
 
+sub rotate {
+  my $self = shift;
+  my %opts = @_;
+  if (defined $opts{right}) {
+    my $degrees = $opts{right};
+    if ($degrees < 0) {
+      $degrees += 360 * int(((-$degrees)+360)/360);
+    }
+    $degrees = $degrees % 360;
+    if ($degrees == 0) {
+      return $self->copy();
+    }
+    elsif ($degrees == 90 || $degrees == 180 || $degrees == 270) {
+      my $result = Imager->new();
+      if ($result->{IMG} = i_rotate90($self->{IMG}, $degrees)) {
+        return $result;
+      }
+      else {
+        $self->{ERRSTR} = $self->_error_as_msg();
+        return undef;
+      }
+    }
+    else {
+      $self->{ERRSTR} = "Parameter 'right' must be a multiple of 90 degrees";
+      return undef;
+    }
+  }
+  elsif (defined $opts{radians} || defined $opts{degrees}) {
+    my $amount = $opts{radians} || $opts{degrees} * 3.1415926535 / 180;
+
+    my $result = Imager->new;
+    if ($result->{IMG} = i_rotate_exact($self->{IMG}, $amount)) {
+      return $result;
+    }
+    else {
+      $self->{ERRSTR} = $self->_error_as_msg();
+      return undef;
+    }
+  }
+  else {
+    $self->{ERRSTR} = "Only the 'right' parameter is available";
+    return undef;
+  }
+}
+
+sub matrix_transform {
+  my $self = shift;
+  my %opts = @_;
+
+  if ($opts{matrix}) {
+    my $xsize = $opts{xsize} || $self->getwidth;
+    my $ysize = $opts{ysize} || $self->getheight;
+
+    my $result = Imager->new;
+    $result->{IMG} = i_matrix_transform($self->{IMG}, $xsize, $ysize, 
+                                        $opts{matrix})
+      or return undef;
+
+    return $result;
+  }
+  else {
+    $self->{ERRSTR} = "matrix parameter required";
+    return undef;
+  }
+}
 
+# blame Leolo :)
+*yatf = \&matrix_transform;
 
 # These two are supported for legacy code only
 
 sub i_color_new {
-  return Imager::Color->new($_[0], $_[1], $_[2], $_[3]);
+  return Imager::Color->new(@_);
 }
 
 sub i_color_set {
-  return Imager::Color::set($_[0], $_[1], $_[2], $_[3], $_[4]);
+  return Imager::Color::set(@_);
 }
 
-
-
 # Draws a box between the specified corner points.
-
 sub box {
   my $self=shift;
   unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; }
@@ -1076,8 +1634,25 @@ sub box {
     $opts{'ymax'} = max($opts{'box'}->[1],$opts{'box'}->[3]);
   }
 
-  if ($opts{filled}) { i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
-  else { i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
+  if ($opts{filled}) { 
+    i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+                 $opts{ymax},$opts{color}); 
+  }
+  elsif ($opts{fill}) {
+    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+      # assume it's a hash ref
+      require 'Imager/Fill.pm';
+      unless ($opts{fill} = Imager::Fill->new(%{$opts{fill}})) {
+        $self->{ERRSTR} = $Imager::ERRSTR;
+        return undef;
+      }
+    }
+    i_box_cfill($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+                $opts{ymax},$opts{fill}{fill});
+  }
+  else { 
+    i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color});
+  }
   return $self;
 }
 
@@ -1092,7 +1667,20 @@ sub arc {
            'x'=>$self->getwidth()/2,
            'y'=>$self->getheight()/2,
            'd1'=>0, 'd2'=>361, @_);
-  i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},$opts{'d2'},$opts{'color'}); 
+  if ($opts{fill}) {
+    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+      # assume it's a hash ref
+      require 'Imager/Fill.pm';
+      $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+    }
+    i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+                $opts{'d2'}, $opts{fill}{fill});
+  }
+  else {
+    i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+          $opts{'d2'},$opts{'color'}); 
+  }
+
   return $self;
 }
 
@@ -1174,6 +1762,164 @@ sub polybezier {
   return $self;
 }
 
+sub flood_fill {
+  my $self = shift;
+  my %opts = ( color=>Imager::Color->new(255, 255, 255), @_ );
+
+  unless (exists $opts{x} && exists $opts{'y'}) {
+    $self->{ERRSTR} = "missing seed x and y parameters";
+    return undef;
+  }
+  
+  if ($opts{fill}) {
+    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+      # assume it's a hash ref
+      require 'Imager/Fill.pm';
+      $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+    }
+    i_flood_cfill($self->{IMG}, $opts{x}, $opts{'y'}, $opts{fill}{fill});
+  }
+  else {
+    i_flood_fill($self->{IMG}, $opts{x}, $opts{'y'}, $opts{color});
+  }
+
+  $self;
+}
+
+# make an identity matrix of the given size
+sub _identity {
+  my ($size) = @_;
+
+  my $matrix = [ map { [ (0) x $size ] } 1..$size ];
+  for my $c (0 .. ($size-1)) {
+    $matrix->[$c][$c] = 1;
+  }
+  return $matrix;
+}
+
+# general function to convert an image
+sub convert {
+  my ($self, %opts) = @_;
+  my $matrix;
+
+  # the user can either specify a matrix or preset
+  # the matrix overrides the preset
+  if (!exists($opts{matrix})) {
+    unless (exists($opts{preset})) {
+      $self->{ERRSTR} = "convert() needs a matrix or preset";
+      return;
+    }
+    else {
+      if ($opts{preset} eq 'gray' || $opts{preset} eq 'grey') {
+       # convert to greyscale, keeping the alpha channel if any
+       if ($self->getchannels == 3) {
+         $matrix = [ [ 0.222, 0.707, 0.071 ] ];
+       }
+       elsif ($self->getchannels == 4) {
+         # preserve the alpha channel
+         $matrix = [ [ 0.222, 0.707, 0.071, 0 ],
+                     [ 0,     0,     0,     1 ] ];
+       }
+       else {
+         # an identity
+         $matrix = _identity($self->getchannels);
+       }
+      }
+      elsif ($opts{preset} eq 'noalpha') {
+       # strip the alpha channel
+       if ($self->getchannels == 2 or $self->getchannels == 4) {
+         $matrix = _identity($self->getchannels);
+         pop(@$matrix); # lose the alpha entry
+       }
+       else {
+         $matrix = _identity($self->getchannels);
+       }
+      }
+      elsif ($opts{preset} eq 'red' || $opts{preset} eq 'channel0') {
+       # extract channel 0
+       $matrix = [ [ 1 ] ];
+      }
+      elsif ($opts{preset} eq 'green' || $opts{preset} eq 'channel1') {
+       $matrix = [ [ 0, 1 ] ];
+      }
+      elsif ($opts{preset} eq 'blue' || $opts{preset} eq 'channel2') {
+       $matrix = [ [ 0, 0, 1 ] ];
+      }
+      elsif ($opts{preset} eq 'alpha') {
+       if ($self->getchannels == 2 or $self->getchannels == 4) {
+         $matrix = [ [ (0) x ($self->getchannels-1), 1 ] ];
+       }
+       else {
+         # the alpha is just 1 <shrug>
+         $matrix = [ [ (0) x $self->getchannels, 1 ] ];
+       }
+      }
+      elsif ($opts{preset} eq 'rgb') {
+       if ($self->getchannels == 1) {
+         $matrix = [ [ 1 ], [ 1 ], [ 1 ] ];
+       }
+       elsif ($self->getchannels == 2) {
+         # preserve the alpha channel
+         $matrix = [ [ 1, 0 ], [ 1, 0 ], [ 1, 0 ], [ 0, 1 ] ];
+       }
+       else {
+         $matrix = _identity($self->getchannels);
+       }
+      }
+      elsif ($opts{preset} eq 'addalpha') {
+       if ($self->getchannels == 1) {
+         $matrix = _identity(2);
+       }
+       elsif ($self->getchannels == 3) {
+         $matrix = _identity(4);
+       }
+       else {
+         $matrix = _identity($self->getchannels);
+       }
+      }
+      else {
+       $self->{ERRSTR} = "Unknown convert preset $opts{preset}";
+       return undef;
+      }
+    }
+  }
+  else {
+    $matrix = $opts{matrix};
+  }
+
+  my $new = Imager->new();
+  $new->{IMG} = i_img_new();
+  unless (i_convert($new->{IMG}, $self->{IMG}, $matrix)) {
+    # most likely a bad matrix
+    $self->{ERRSTR} = _error_as_msg();
+    return undef;
+  }
+  return $new;
+}
+
+
+# general function to map an image through lookup tables
+
+sub map {
+  my ($self, %opts) = @_;
+  my @chlist = qw( red green blue alpha );
+
+  if (!exists($opts{'maps'})) {
+    # make maps from channel maps
+    my $chnum;
+    for $chnum (0..$#chlist) {
+      if (exists $opts{$chlist[$chnum]}) {
+       $opts{'maps'}[$chnum] = $opts{$chlist[$chnum]};
+      } elsif (exists $opts{'all'}) {
+       $opts{'maps'}[$chnum] = $opts{'all'};
+      }
+    }
+  }
+  if ($opts{'maps'} and $self->{IMG}) {
+    i_map($self->{IMG}, $opts{'maps'} );
+  }
+  return $self;
+}
 
 # destructive border - image is shrunk by one pixel all around
 
@@ -1254,60 +2000,14 @@ sub string {
     return;
   }
 
-  my $aa=1;
-  my $font=$input{'font'};
-  my $align=$font->{'align'} unless exists $input{'align'};
-  my $color=$input{'color'} || $font->{'color'};
-  my $size=$input{'size'}   || $font->{'size'};
-
-  if (!defined($size)) { $self->{ERRSTR}='No size parameter and no default in font'; return undef; }
-
-  $aa=$font->{'aa'} if exists $font->{'aa'};
-  $aa=$input{'aa'} if exists $input{'aa'};
-
-
-
-#  unless($font->can('text')) {
-#    $self->{ERRSTR}="font is unable to do what we need";
-#    return;
-#  }
-
-#  use Data::Dumper; 
-#  warn Dumper($font);
-
-#  print "Channel=".$input{'channel'}."\n";
-
-  if ( $font->{'type'} eq 't1' ) {
-    if ( exists $input{'channel'} ) {
-      Imager::Font::t1_set_aa_level($aa);
-      i_t1_cp($self->{IMG},$input{'x'},$input{'y'},
-             $input{'channel'},$font->{'id'},$size,
-             $input{'string'},length($input{'string'}),1);
-    } else {
-      Imager::Font::t1_set_aa_level($aa);
-      i_t1_text($self->{IMG},$input{'x'},$input{'y'},
-               $color,$font->{'id'},$size,
-               $input{'string'},length($input{'string'}),1);
-    }
-  }
-
-  if ( $font->{'type'} eq 'tt' ) {
-    if ( exists $input{'channel'} ) {
-      i_tt_cp($font->{'id'},$self->{IMG},$input{'x'},$input{'y'},$input{'channel'},
-             $size,$input{'string'},length($input{'string'}),$aa); 
-    } else {
-      i_tt_text($font->{'id'},$self->{IMG},$input{'x'},$input{'y'},$color,$size,
-               $input{'string'},length($input{'string'}),$aa); 
-    }
+  unless ($input{font}->draw(image=>$self, %input)) {
+    $self->{ERRSTR} = $self->_error_as_msg();
+    return;
   }
 
   return $self;
 }
 
-
-
-
-
 # Shortcuts that can be exported
 
 sub newcolor { Imager::Color->new(@_); }
@@ -1322,12 +2022,9 @@ sub newfont  { Imager::Font->new(@_); }
 
 #### Utility routines
 
-sub errstr { $_[0]->{ERRSTR} }
-
-
-
-
-
+sub errstr { 
+  ref $_[0] ? $_[0]->{ERRSTR} : $ERRSTR
+}
 
 # Default guess for the type of an image from extension
 
@@ -1339,6 +2036,8 @@ sub def_guess_type {
   return 'jpeg' if ($ext =~ m/^jpe?g$/);
   return 'pnm'  if ($ext =~ m/^p[pgb]m$/);
   return 'png'  if ($ext eq "png");
+  return 'bmp'  if ($ext eq "bmp" || $ext eq "dib");
+  return 'tga'  if ($ext eq "tga");
   return 'gif'  if ($ext eq "gif");
   return ();
 }
@@ -1411,11 +2110,6 @@ sub parseiptc {
   return (caption=>$caption,photogr=>$photogr,headln=>$headln,credit=>$credit);
 }
 
-
-
-
-
-
 # Autoload methods go after =cut, and are processed by the autosplit program.
 
 1;
@@ -1479,6 +2173,39 @@ If you have an existing image, use img_set() to change it's dimensions
 
   $img->img_set(xsize=>500, ysize=>500, channels=>4);
 
+To create paletted images, set the 'type' parameter to 'paletted':
+
+  $img = Imager->new(xsize=>200, ysize=>200, channels=>3, type=>'paletted');
+
+which creates an image with a maxiumum of 256 colors, which you can
+change by supplying the C<maxcolors> parameter.
+
+You can create a new paletted image from an existing image using the
+to_paletted() method:
+
+ $palimg = $img->to_paletted(\%opts)
+
+where %opts contains the options specified under L<Quantization options>.
+
+You can convert a paletted image (or any image) to an 8-bit/channel
+RGB image with:
+
+  $rgbimg = $img->to_rgb8;
+
+Warning: if you draw on a paletted image with colors that aren't in
+the palette, the image will be internally converted to a normal image.
+
+For improved color precision you can use the bits parameter to specify
+16 bites per channel:
+
+  $img = Imager->new(xsize=>200, ysize=>200, channels=>3, bits=>16);
+
+Note that as of this writing all functions should work on 16-bit
+images, but at only 8-bit/channel precision.
+
+Currently only 8 and 16/bit per channel image types are available,
+this may change later.
+
 Color objects are created by calling the Imager::Color->new()
 method:
 
@@ -1524,6 +2251,8 @@ When writing to a tiff image file you can also specify the 'class'
 parameter, which can currently take a single value, "fax".  If class
 is set to fax then a tiff image which should be suitable for faxing
 will be written.  For the best results start with a grayscale image.
+By default the image is written at fine resolution you can override
+this by setting the "fax_fine" parameter to 0.
 
 If you are reading from a gif image file, you can supply a 'colors'
 parameter which must be a reference to a scalar.  The referenced
@@ -1540,7 +2269,12 @@ the file descriptor for the file:
 For writing using the 'fd' option you will probably want to set $| for
 that descriptor, since the writes to the file descriptor bypass Perl's
 (or the C libraries) buffering.  Setting $| should avoid out of order
-output.
+output.  For example a common idiom when writing a CGI script is:
+
+  # the $| _must_ come before you send the content-type
+  $| = 1;
+  print "Content-Type: image/jpeg\n\n";
+  $img->write(fd=>fileno(STDOUT), type=>'jpeg') or die $img->errstr;
 
 *Note that load() is now an alias for read but will be removed later*
 
@@ -1633,6 +2367,8 @@ options>.
 
 =back
 
+You must also specify the file format using the 'type' option.
+
 The current aim is to support other multiple image formats in the
 future, such as TIFF, and to support reading multiple images from a
 single file.
@@ -1643,10 +2379,56 @@ A simple example:
     # ... code to put images in @images
     Imager->write_multi({type=>'gif',
                         file=>'anim.gif',
-                        gif_delays=>[ 10 x @images ] },
+                        gif_delays=>[ (10) x @images ] },
                        @images)
     or die "Oh dear!";
 
+You can read multi-image files (currently only GIF files) using the
+read_multi() method:
+
+  my @imgs = Imager->read_multi(file=>'foo.gif')
+    or die "Cannot read images: ",Imager->errstr;
+
+The possible parameters for read_multi() are:
+
+=over
+
+=item file
+
+The name of the file to read in.
+
+=item fh
+
+A filehandle to read in.  This can be the name of a filehandle, but it
+will need the package name, no attempt is currently made to adjust
+this to the caller's package.
+
+=item fd
+
+The numeric file descriptor of an open file (or socket).
+
+=item callback
+
+A function to be called to read in data, eg. reading a blob from a
+database incrementally.
+
+=item data
+
+The data of the input file in memory.
+
+=item type
+
+The type of file.  If the file is parameter is given and provides
+enough information to guess the type, then this parameter is optional.
+
+=back
+
+Note: you cannot use the callback or data parameter with giflib
+versions before 4.0.
+
+When reading from a GIF file with read_multi() the images are returned
+as paletted images.
+
 =head2 Gif options
 
 These options can be specified when calling write_multi() for gif
@@ -1673,6 +2455,9 @@ The images are written interlaced if this is non-zero.
 A reference to an array containing the delays between images, in 1/100
 seconds.
 
+If you want the same delay for every frame you can simply set this to
+the delay in 1/100 seconds.
+
 =item gif_user_input
 
 A reference to an array contains user input flags.  If the given flag
@@ -1965,6 +2750,54 @@ the function return undef.  Examples:
     print "Less than 512 colors in image\n";
   }
 
+The bits() method retrieves the number of bits used to represent each
+channel in a pixel, typically 8.  The type() method returns either
+'direct' for truecolor images or 'paletted' for paletted images.  The
+virtual() method returns non-zero if the image contains no actual
+pixels, for example masked images.
+
+=head2 Paletted Images
+
+In general you can work with paletted images in the same way as RGB
+images, except that if you attempt to draw to a paletted image with a
+color that is not in the image's palette, the image will be converted
+to an RGB image.  This means that drawing on a paletted image with
+anti-aliasing enabled will almost certainly convert the image to RGB.
+
+You can add colors to a paletted image with the addcolors() method:
+
+   my @colors = ( Imager::Color->new(255, 0, 0), 
+                  Imager::Color->new(0, 255, 0) );
+   my $index = $img->addcolors(colors=>\@colors);
+
+The return value is the index of the first color added, or undef if
+adding the colors would overflow the palette.
+
+Once you have colors in the palette you can overwrite them with the
+setcolors() method:
+
+  $img->setcolors(start=>$start, colors=>\@colors);
+
+Returns true on success.
+
+To retrieve existing colors from the palette use the getcolors() method:
+
+  # get the whole palette
+  my @colors = $img->getcolors();
+  # get a single color
+  my $color = $img->getcolors(start=>$index);
+  # get a range of colors
+  my @colors = $img->getcolors(start=>$index, count=>$count);
+
+To quickly find a color in the palette use findcolor():
+
+  my $index = $img->findcolor(color=>$color);
+
+which returns undef on failure, or the index of the color.
+
+You can get the current palette size with $img->colorcount, and the
+maximum size of the palette with $img->maxcolors.
+
 =head2 Drawing Methods
 
 IMPLEMENTATION MORE OR LESS DONE CHECK THE TESTS
@@ -1990,6 +2823,18 @@ Arc:
 This creates a filled red arc with a 'center' at (200, 100) and spans
 10 degrees and the slice has a radius of 20. SEE section on BUGS.
 
+Both the arc() and box() methods can take a C<fill> parameter which
+can either be an Imager::Fill object, or a reference to a hash
+containing the parameters used to create the fill:
+
+  $img->box(xmin=>10, ymin=>30, xmax=>150, ymax=>60,
+            fill => { hatch=>'cross2' });
+  use Imager::Fill;
+  my $fill = Imager::Fill->new(hatch=>'stipple');
+  $img->box(fill=>$fill);
+
+See L<Imager::Fill> for the type of fills you can use.
+
 Circle:
   $img->circle(color=>$green, r=50, x=>200, y=>100);
 
@@ -2011,6 +2856,20 @@ The point set can either be specified as an arrayref to an array of
 array references (where each such array represents a point).  The
 other way is to specify two array references.
 
+You can fill a region that all has the same color using the
+flood_fill() method, for example:
+
+  $img->flood_fill(x=>50, y=>50, color=>$color);
+
+will fill all regions the same color connected to the point (50, 50).
+
+You can also use a general fill, so you could fill the same region
+with a check pattern using:
+
+  $img->flood_fill(x=>50, y=>50, fill=>{ hatch=>'check2x2' });
+
+See L<Imager::Fill> for more information on general fills.
+
 =head2 Text rendering
 
 Text rendering is described in the Imager::Font manpage.
@@ -2086,6 +2945,22 @@ parameter which can take the values C<h>, C<v>, C<vh> and C<hv>.
   $img->flip(dir=>"vh");      # vertical and horizontal flip
   $nimg = $img->copy->flip(dir=>"v"); # make a copy and flip it vertically
 
+=head2 Rotating images
+
+Use the rotate() method to rotate an image.
+
+To rotate by an exact amount in degrees or radians, use the 'degrees'
+or 'radians' parameter:
+
+  my $rot20 = $img->rotate(degrees=>20);
+  my $rotpi4 = $img->rotate(radians=>3.14159265/4);
+
+To rotate in steps of 90 degrees, use the 'right' parameter:
+
+  my $rotated = $img->rotate(right=>270);
+
+Rotations are clockwise for positive values.
+
 =head2 Blending Images
 
 To put an image or a part of an image directly
@@ -2104,9 +2979,10 @@ method that does this is rubthrough.
 
   $img->rubthrough(src=>$srcimage,tx=>30,ty=>50); 
 
-That will take the image C<$srcimage> and overlay it with the 
-upper left corner at (30,50).  The C<$srcimage> must be a 4 channel
-image.  The last channel is used as an alpha channel.
+That will take the image C<$srcimage> and overlay it with the upper
+left corner at (30,50).  You can rub 2 or 4 channel images onto a 3
+channel image, or a 2 channel image onto a 1 channel image.  The last
+channel is used as an alpha channel.
 
 
 =head2 Filters
@@ -2121,19 +2997,426 @@ running the C<filterlist.perl> script that comes with the module
 source.
 
   Filter          Arguments
-  turbnoise
   autolevels      lsat(0.1) usat(0.1) skew(0)
-  radnoise
-  noise           amount(3) subtype(0)
+  bumpmap         bump elevation(0) lightx lighty st(2)
   contrast        intensity
-  hardinvert
+  conv            coef
+  fountain        xa ya xb yb ftype(linear) repeat(none) combine(none)
+                  super_sample(none) ssample_param(4) segments(see below)
+  gaussian        stddev
   gradgen         xo yo colors dist
+  hardinvert
+  mosaic          size(20)
+  noise           amount(3) subtype(0)
+  postlevels      levels(10)
+  radnoise        xo(100) yo(100) ascale(17.0) rscale(0.02)
+  turbnoise       xo(0.0) yo(0.0) scale(10.0)
+  watermark       wmark pixdiff(10) tx(0) ty(0)
 
 The default values are in parenthesis.  All parameters must have some
 value but if a parameter has a default value it may be omitted when
 calling the filter function.
 
-FIXME: make a seperate pod for filters?
+The filters are:
+
+=over
+
+=item autolevels
+
+scales the value of each channel so that the values in the image will
+cover the whole possible range for the channel.  I<lsat> and I<usat>
+truncate the range by the specified fraction at the top and bottom of
+the range respectivly..
+
+=item bumpmap
+
+uses the channel I<elevation> image I<bump> as a bumpmap on your
+image, with the light at (I<lightx>, I<lightty>), with a shadow length
+of I<st>.
+
+=item contrast
+
+scales each channel by I<intensity>.  Values of I<intensity> < 1.0
+will reduce the contrast.
+
+=item conv
+
+performs 2 1-dimensional convolutions on the image using the values
+from I<coef>.  I<coef> should be have an odd length.
+
+=item fountain
+
+renders a fountain fill, similar to the gradient tool in most paint
+software.  The default fill is a linear fill from opaque black to
+opaque white.  The points A(xa, ya) and B(xb, yb) control the way the
+fill is performed, depending on the ftype parameter:
+
+=over
+
+=item linear
+
+the fill ramps from A through to B.
+
+=item bilinear
+
+the fill ramps in both directions from A, where AB defines the length
+of the gradient.
+
+=item radial
+
+A is the center of a circle, and B is a point on it's circumference.
+The fill ramps from the center out to the circumference.
+
+=item radial_square
+
+A is the center of a square and B is the center of one of it's sides.
+This can be used to rotate the square.  The fill ramps out to the
+edges of the square.
+
+=item revolution
+
+A is the centre of a circle and B is a point on it's circumference.  B
+marks the 0 and 360 point on the circle, with the fill ramping
+clockwise.
+
+=item conical
+
+A is the center of a circle and B is a point on it's circumference.  B
+marks the 0 and point on the circle, with the fill ramping in both
+directions to meet opposite.
+
+=back
+
+The I<repeat> option controls how the fill is repeated for some
+I<ftype>s after it leaves the AB range:
+
+=over
+
+=item none
+
+no repeats, points outside of each range are treated as if they were
+on the extreme end of that range.
+
+=item sawtooth
+
+the fill simply repeats in the positive direction
+
+=item triangle
+
+the fill repeats in reverse and then forward and so on, in the
+positive direction
+
+=item saw_both
+
+the fill repeats in both the positive and negative directions (only
+meaningful for a linear fill).
+
+=item tri_both
+
+as for triangle, but in the negative direction too (only meaningful
+for a linear fill).
+
+=back
+
+By default the fill simply overwrites the whole image (unless you have
+parts of the range 0 through 1 that aren't covered by a segment), if
+any segments of your fill have any transparency, you can set the
+I<combine> option to 'normal' to have the fill combined with the
+existing pixels.  See the description of I<combine> in L<Imager/Fill>.
+
+If your fill has sharp edges, for example between steps if you use
+repeat set to 'triangle', you may see some aliased or ragged edges.
+You can enable super-sampling which will take extra samples within the
+pixel in an attempt anti-alias the fill.
+
+The possible values for the super_sample option are:
+
+=over
+
+=item none
+
+no super-sampling is done
+
+=item grid
+
+a square grid of points are sampled.  The number of points sampled is
+the square of ceil(0.5 + sqrt(ssample_param)).
+
+=item random
+
+a random set of points within the pixel are sampled.  This looks
+pretty bad for low ssample_param values.  
+
+=item circle
+
+the points on the radius of a circle within the pixel are sampled.
+This seems to produce the best results, but is fairly slow (for now).
+
+=back
+
+You can control the level of sampling by setting the ssample_param
+option.  This is roughly the number of points sampled, but depends on
+the type of sampling.
+
+The segments option is an arrayref of segments.  You really should use
+the Imager::Fountain class to build your fountain fill.  Each segment
+is an array ref containing:
+
+=over
+
+=item start
+
+a floating point number between 0 and 1, the start of the range of fill parameters covered by this segment.
+
+=item middle
+
+a floating point number between start and end which can be used to
+push the color range towards one end of the segment.
+
+=item end
+
+a floating point number between 0 and 1, the end of the range of fill
+parameters covered by this segment.  This should be greater than
+start.
+
+=item c0 
+
+=item c1
+
+The colors at each end of the segment.  These can be either
+Imager::Color or Imager::Color::Float objects.
+
+=item segment type
+
+The type of segment, this controls the way the fill parameter varies
+over the segment. 0 for linear, 1 for curved (unimplemented), 2 for
+sine, 3 for sphere increasing, 4 for sphere decreasing.
+
+=item color type
+
+The way the color varies within the segment, 0 for simple RGB, 1 for
+hue increasing and 2 for hue decreasing.
+
+=back
+
+Don't forgot to use Imager::Fountain instead of building your own.
+Really.  It even loads GIMP gradient files.
+
+=item gaussian
+
+performs a gaussian blur of the image, using I<stddev> as the standard
+deviation of the curve used to combine pixels, larger values give
+bigger blurs.  For a definition of Gaussian Blur, see:
+
+  http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node99.html
+
+=item gradgen
+
+renders a gradient, with the given I<colors> at the corresponding
+points (x,y) in I<xo> and I<yo>.  You can specify the way distance is
+measured for color blendeing by setting I<dist> to 0 for Euclidean, 1
+for Euclidean squared, and 2 for Manhattan distance.
+
+=item hardinvert
+
+inverts the image, black to white, white to black.  All channels are
+inverted, including the alpha channel if any.
+
+=item mosaic
+
+produces averaged tiles of the given I<size>.
+
+=item noise
+
+adds noise of the given I<amount> to the image.  If I<subtype> is
+zero, the noise is even to each channel, otherwise noise is added to
+each channel independently.
+
+=item radnoise
+
+renders radiant Perlin turbulent noise.  The centre of the noise is at
+(I<xo>, I<yo>), I<ascale> controls the angular scale of the noise ,
+and I<rscale> the radial scale, higher numbers give more detail.
+
+=item postlevels
+
+alters the image to have only I<levels> distinct level in each
+channel.
+
+=item turbnoise
+
+renders Perlin turbulent noise.  (I<xo>, I<yo>) controls the origin of
+the noise, and I<scale> the scale of the noise, with lower numbers
+giving more detail.
+
+=item watermark
+
+applies I<wmark> as a watermark on the image with strength I<pixdiff>,
+with an origin at (I<tx>, I<ty>)
+
+=back
+
+A demonstration of most of the filters can be found at:
+
+  http://www.develop-help.com/imager/filters.html
+
+(This is a slow link.)
+
+=head2 Color transformations
+
+You can use the convert method to transform the color space of an
+image using a matrix.  For ease of use some presets are provided.
+
+The convert method can be used to:
+
+=over 4
+
+=item *
+
+convert an RGB or RGBA image to grayscale.
+
+=item *
+
+convert a grayscale image to RGB.
+
+=item *
+
+extract a single channel from an image.
+
+=item *
+
+set a given channel to a particular value (or from another channel)
+
+=back
+
+The currently defined presets are:
+
+=over
+
+=item gray
+
+=item grey
+
+converts an RGBA image into a grayscale image with alpha channel, or
+an RGB image into a grayscale image without an alpha channel.
+
+This weights the RGB channels at 22.2%, 70.7% and 7.1% respectively.
+
+=item noalpha
+
+removes the alpha channel from a 2 or 4 channel image.  An identity
+for other images.
+
+=item red
+
+=item channel0
+
+extracts the first channel of the image into a single channel image
+
+=item green
+
+=item channel1
+
+extracts the second channel of the image into a single channel image
+
+=item blue
+
+=item channel2
+
+extracts the third channel of the image into a single channel image
+
+=item alpha
+
+extracts the alpha channel of the image into a single channel image.
+
+If the image has 1 or 3 channels (assumed to be grayscale of RGB) then
+the resulting image will be all white.
+
+=item rgb
+
+converts a grayscale image to RGB, preserving the alpha channel if any
+
+=item addalpha
+
+adds an alpha channel to a grayscale or RGB image.  Preserves an
+existing alpha channel for a 2 or 4 channel image.
+
+=back
+
+For example, to convert an RGB image into a greyscale image:
+
+  $new = $img->convert(preset=>'grey'); # or gray
+
+or to convert a grayscale image to an RGB image:
+
+  $new = $img->convert(preset=>'rgb');
+
+The presets aren't necessary simple constants in the code, some are
+generated based on the number of channels in the input image.
+
+If you want to perform some other colour transformation, you can use
+the 'matrix' parameter.
+
+For each output pixel the following matrix multiplication is done:
+
+     channel[0]       [ [ $c00, $c01, ...  ]        inchannel[0]
+   [     ...      ] =          ...              x [     ...        ]
+     channel[n-1]       [ $cn0, ...,  $cnn ] ]      inchannel[max]
+                                                          1
+
+So if you want to swap the red and green channels on a 3 channel image:
+
+  $new = $img->convert(matrix=>[ [ 0, 1, 0 ],
+                                 [ 1, 0, 0 ],
+                                 [ 0, 0, 1 ] ]);
+
+or to convert a 3 channel image to greyscale using equal weightings:
+
+  $new = $img->convert(matrix=>[ [ 0.333, 0.333, 0.334 ] ])
+
+=head2 Color Mappings
+
+You can use the map method to map the values of each channel of an
+image independently using a list of lookup tables.  It's important to
+realize that the modification is made inplace.  The function simply
+returns the input image again or undef on failure.
+
+Each channel is mapped independently through a lookup table with 256
+entries.  The elements in the table should not be less than 0 and not
+greater than 255.  If they are out of the 0..255 range they are
+clamped to the range.  If a table does not contain 256 entries it is
+silently ignored.
+
+Single channels can mapped by specifying their name and the mapping
+table.  The channel names are C<red>, C<green>, C<blue>, C<alpha>.
+
+  @map = map { int( $_/2 } 0..255;
+  $img->map( red=>\@map );
+
+It is also possible to specify a single map that is applied to all
+channels, alpha channel included.  For example this applies a gamma
+correction with a gamma of 1.4 to the input image.
+
+  $gamma = 1.4;
+  @map = map { int( 0.5 + 255*($_/255)**$gamma ) } 0..255;
+  $img->map(all=> \@map);
+
+The C<all> map is used as a default channel, if no other map is
+specified for a channel then the C<all> map is used instead.  If we
+had not wanted to apply gamma to the alpha channel we would have used:
+
+  $img->map(all=> \@map, alpha=>[]);
+
+Since C<[]> contains fewer than 256 element the gamma channel is
+unaffected.
+
+It is also possible to simply specify an array of maps that are
+applied to the images in the rgba order.  For example to apply
+maps to the C<red> and C<blue> channels one would use:
+
+  $img->map(maps=>[\@redmap, [], \@bluemap]);
+
+
 
 =head2 Transformations
 
@@ -2317,7 +3600,8 @@ A few examples:
 
 =item rpnexpr=>'x 25 % 15 * y 35 % 10 * getp1 !pat x y getp1 !pix @pix sat 0.7 gt @pat @pix ifp'
 
-tiles a smaller version of the input image over itself where the colour has a saturation over 0.7.
+tiles a smaller version of the input image over itself where the
+colour has a saturation over 0.7.
 
 =item rpnexpr=>'x 25 % 15 * y 35 % 10 * getp1 !pat y 360 / !rat x y getp1 1 @rat - pmult @pat @rat pmult padd'
 
@@ -2348,6 +3632,51 @@ For details on expression parsing see L<Imager::Expr>.  For details on
 the virtual machine used to transform the images, see
 L<Imager::regmach.pod>.
 
+=head2 Matrix Transformations
+
+Rather than having to write code in a little language, you can use a
+matrix to perform transformations, using the matrix_transform()
+method:
+
+  my $im2 = $im->matrix_transform(matrix=>[ -1, 0, $im->getwidth-1,
+                                            0,  1, 0,
+                                            0,  0, 1 ]);
+
+By default the output image will be the same size as the input image,
+but you can supply the xsize and ysize parameters to change the size.
+
+Rather than building matrices by hand you can use the Imager::Matrix2d
+module to build the matrices.  This class has methods to allow you to
+scale, shear, rotate, translate and reflect, and you can combine these
+with an overloaded multiplication operator.
+
+WARNING: the matrix you provide in the matrix operator transforms the
+co-ordinates within the B<destination> image to the co-ordinates
+within the I<source> image.  This can be confusing.
+
+Since Imager has 3 different fairly general ways of transforming an
+image spatially, this method also has a yatf() alias.  Yet Another
+Transformation Function.
+
+=head2 Masked Images
+
+Masked images let you control which pixels are modified in an
+underlying image.  Where the first channel is completely black in the
+mask image, writes to the underlying image are ignored.
+
+For example, given a base image called $img:
+
+  my $mask = Imager->new(xsize=>$img->getwidth, ysize=>getheight,
+                         channels=>1);
+  # ... draw something on the mask
+  my $maskedimg = $img->masked(mask=>$mask);
+
+You can specifiy the region of the underlying image that is masked
+using the left, top, right and bottom options.
+
+If you just want a subset of the image, without masking, just specify
+the region without specifying a mask.
+
 =head2 Plugins
 
 It is possible to add filters to the module without recompiling the
@@ -2378,6 +3707,165 @@ Note: This seems to test ok on the following systems:
 Linux, Solaris, HPUX, OpenBSD, FreeBSD, TRU64/OSF1, AIX.
 If you test this on other systems please let me know.
 
+=head2 Tags
+
+Image tags contain meta-data about the image, ie. information not
+stored as pixels of the image.
+
+At the perl level each tag has a name or code and a value, which is an
+integer or an arbitrary string.  An image can contain more than one
+tag with the same name or code.
+
+You can retrieve tags from an image using the tags() method, you can
+get all of the tags in an image, as a list of array references, with
+the code or name of the tag followed by the value of the tag:
+
+  my @alltags = $img->tags;
+
+or you can get all tags that have a given name:
+
+  my @namedtags = $img->tags(name=>$name);
+
+or a given code:
+
+  my @tags = $img->tags(code=>$code);
+
+You can add tags using the addtag() method, either by name:
+
+  my $index = $img->addtag(name=>$name, value=>$value);
+
+or by code:
+
+  my $index = $img->addtag(code=>$code, value=>$value);
+
+You can remove tags with the deltag() method, either by index:
+
+  $img->deltag(index=>$index);
+
+or by name:
+
+  $img->deltag(name=>$name);
+
+or by code:
+
+  $img->deltag(code=>$code);
+
+In each case deltag() returns the number of tags deleted.
+
+When you read a GIF image using read_multi(), each image can include
+the following tags:
+
+=over
+
+=item gif_left
+
+the offset of the image from the left of the "screen" ("Image Left
+Position")
+
+=item gif_top
+
+the offset of the image from the top of the "screen" ("Image Top Position")
+
+=item gif_interlace
+
+non-zero if the image was interlaced ("Interlace Flag")
+
+=item gif_screen_width
+
+=item gif_screen_height
+
+the size of the logical screen ("Logical Screen Width", 
+"Logical Screen Height")
+
+=item gif_local_map
+
+Non-zero if this image had a local color map.
+
+=item gif_background
+
+The index in the global colormap of the logical screen's background
+color.  This is only set if the current image uses the global
+colormap.
+
+=item gif_trans_index
+
+The index of the color in the colormap used for transparency.  If the
+image has a transparency then it is returned as a 4 channel image with
+the alpha set to zero in this palette entry. ("Transparent Color Index")
+
+=item gif_delay
+
+The delay until the next frame is displayed, in 1/100 of a second. 
+("Delay Time").
+
+=item gif_user_input
+
+whether or not a user input is expected before continuing (view dependent) 
+("User Input Flag").
+
+=item gif_disposal
+
+how the next frame is displayed ("Disposal Method")
+
+=item gif_loop
+
+the number of loops from the Netscape Loop extension.  This may be zero.
+
+=item gif_comment
+
+the first block of the first gif comment before each image.
+
+=back
+
+Where applicable, the ("name") is the name of that field from the GIF89 
+standard.
+
+The following tags are set in a TIFF image when read, and can be set
+to control output:
+
+=over
+
+=item tiff_resolutionunit
+
+The value of the ResolutionUnit tag.  This is ignored on writing if
+the i_aspect_only tag is non-zero.
+
+=back
+
+The following tags are set when a Windows BMP file is read:
+
+=over
+
+=item bmp_compression
+
+The type of compression, if any.
+
+=item bmp_important_colors
+
+The number of important colors as defined by the writer of the image.
+
+=back
+
+Some standard tags will be implemented as time goes by:
+
+=over
+
+=item i_xres
+
+=item i_yres
+
+The spatial resolution of the image in pixels per inch.  If the image
+format uses a different scale, eg. pixels per meter, then this value
+is converted.  A floating point number stored as a string.
+
+=item i_aspect_only
+
+If this is non-zero then the values in i_xres and i_yres are treated
+as a ratio only.  If the image format does not support aspect ratios
+then this is scaled so the smaller value is 72dpi.
+
+=back
+
 =head1 BUGS
 
 box, arc, circle do not support antialiasing yet.  arc, is only filled
@@ -2391,13 +3879,13 @@ only 2 colors used - it will have a 128 colortable anyway.
 
 =head1 AUTHOR
 
-Arnar M. Hrafnkelsson, addi@umich.edu
-And a great deal of help from others - see the README for a complete
-list.
+Arnar M. Hrafnkelsson, addi@umich.edu, and recently lots of assistance
+from Tony Cook.  See the README for a complete list.
+
 =head1 SEE ALSO
 
-perl(1), Imager::Color(3), Affix::Infix2Postfix(3), Parse::RecDescent(3)
+perl(1), Imager::Color(3), Imager::Font(3), Imager::Matrix2d(3), 
+Affix::Infix2Postfix(3), Parse::RecDescent(3) 
 http://www.eecs.umich.edu/~addi/perl/Imager/
 
-
 =cut