Egads
authorTony Cook <tony@develop=help.com>
Mon, 13 Aug 2001 08:20:05 +0000 (08:20 +0000)
committerTony Cook <tony@develop=help.com>
Mon, 13 Aug 2001 08:20:05 +0000 (08:20 +0000)
Merged in the exp_represent branch
Should have done this earlier

62 files changed:
Changes
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
README
TODO
convert.c
datatypes.h
design/represent.txt
error.c
feat.h
font.c
freetyp2.c [new file with mode: 0644]
gif.c
image.c
image.h
imagei.h [new file with mode: 0644]
img16.c [new file with mode: 0644]
imio.h
io.c
iolayer.c
iolayer.h
jpeg.c
lib/Imager/Color.pm
lib/Imager/Color/Float.pm [new file with mode: 0644]
lib/Imager/Font.pm
lib/Imager/Font/FreeType2.pm [new file with mode: 0644]
lib/Imager/Font/Win32.pm [new file with mode: 0644]
lib/Imager/Matrix2d.pm [new file with mode: 0644]
lib/Imager/Transform.pm
lib/Imager/interface.pod [new file with mode: 0644]
maskimg.c [new file with mode: 0644]
palimg.c [new file with mode: 0644]
plug.h
png.c
pnm.c
raw.c
rotate.c [new file with mode: 0644]
t/t01introvert.t [new file with mode: 0644]
t/t020masked.t [new file with mode: 0644]
t/t021sixteen.t [new file with mode: 0644]
t/t102png.t
t/t103raw.t
t/t105gif.t
t/t106tiff.t
t/t15color.t
t/t16matrix.t [new file with mode: 0644]
t/t30t1font.t
t/t37w32font.t [new file with mode: 0644]
t/t38ft2font.t [new file with mode: 0644]
t/t58trans2.t
t/t64copyflip.t
t/t67convert.t
t/t69rubthru.t [new file with mode: 0644]
t/t70newgif.t
tags.c [new file with mode: 0644]
testimg/screen3.gif [new file with mode: 0644]
testimg/test_gimp_pal [new file with mode: 0644]
tiff.c
typemap
win32.c [new file with mode: 0644]

diff --git a/Changes b/Changes
index 4aa8a08..8eb4e2c 100644 (file)
--- a/Changes
+++ b/Changes
@@ -419,6 +419,57 @@ Revision history for Perl extension Imager.
 
 0.39  pre1
        - split Imager::Font into a base, *::Type1 and *::Truetype
+        - writing faxable tiff now allows 2 and 4 channel images
+        - virtual image interface - 8-bit/sample images
+        - paletted images
+        - 16-bit/sample images
+        - masked images
+        - writing non-8bit images to raw
+        - writing   ''       ''   to tiff
+        - i_convert support for high-bit images and paletted images
+        - i_copy for high-bit and paletted images
+        - tests for rubthru
+        - rubthru can now rub a 2 channel image over a 1 or 3 channel 
+          image
+        - rubthru for high-bit images
+        - i_readgif_multi functions, which return all the frames from a
+          GIF file, along with a bunch of meta-information as tag
+        - OO interface to tags
+        - OO interface read_multi() to read multi-image files (just GIF
+          so far)
+        - documentation for the multi-image GIF reading and tags
+        - rotate() method with rotate by steps of 90 degrees (and docs)
+        - fixed a bug in i_img_pal_new_low()
+        - added gaussian to the filters list
+        - documented the individual filters
+       - fixed the right-hand side of bounding boxes for TT fonts 
+         where the last character overlaps the advance width (eg. 
+         italic fonts)
+        - added rotation by exact amounts in degrees or radians,
+          implemented using the matrix idea from Addi.  The procedural
+          matrix transformer is exposed, but still needs testing (as XS)
+          and needs an OO interface, hopefully with some helper tools,
+          like the preset interface with ->convert().
+        - MY::postamble() in Makefile.PL included a broken rule 
+          (Makefile.PL 1.13.2.5)
+       - support for GDI fonts under Win32
+       - made that work for cygwin too (needs the w32api package)
+       - freetype1 support under Win32
+       - t1lib support under Win32
+       - fixed a minor bug in font.c (invalid font files caused a SEGV)
+       - checked cygwin for both t1lib and freetype1
+        - freetype 2 support
+        - exposed the matrix_transform() function to the OO interface
+        - added Imager::Matrix2d convenience class
+        - support for setting the resolution when writing to PNG
+        - retrieve physical resolution to tags when reading from PNG
+        - found an XS bug in the interface to i_tags_add()
+        - fixed handling of writing text to a channel with freetype2 
+          (sometimes the edge of a character would damage the edge of the 
+          previous character)
+        - some utf8 support for freetype2
+        - some vertical layout support for freetype2
+        - named parameters for specifying colors, with quite a few options.
 
 =================================================================
 
index 55710bc..482f6c0 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;
@@ -71,7 +69,6 @@ use Imager::Font;
                i_t1_text
                i_t1_bbox
 
-
                i_tt_set_aa
                i_tt_cp
                i_tt_text
@@ -121,8 +118,6 @@ use Imager::Font;
                NF
 );
 
-
-
 @EXPORT=qw( 
           init_log
           i_list_formats
@@ -147,7 +142,6 @@ use Imager::Font;
                  unload_plugin
                 )]);
 
-
 BEGIN {
   require Exporter;
   require DynaLoader;
@@ -159,13 +153,15 @@ BEGIN {
 
 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';
   }
 
@@ -225,6 +221,11 @@ 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}); },
+                       };
 
   $FORMATGUESS=\&def_guess_type;
 }
@@ -310,7 +311,6 @@ sub _error_as_msg {
   return join(": ", map $_->[0], i_errors());
 }
 
-
 #
 # Methods to be called on objects.
 #
@@ -417,14 +417,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
@@ -435,7 +678,9 @@ 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});
   }
 
@@ -631,12 +876,10 @@ 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 (defined $fd) {
       $IO = io_new_fd($fd);
@@ -783,13 +1026,91 @@ sub write_multi {
   }
 }
 
+# read multiple images from a file
+sub read_multi {
+  my ($class, %opts) = @_;
+
+  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??
@@ -1064,13 +1385,6 @@ sub transform {
   }
 }
 
-
-
-
-
-
-
-
 sub rubthrough {
   my $self=shift;
   my %opts=(tx=>0,ty=>0,@_);
@@ -1078,7 +1392,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;
 }
 
@@ -1094,22 +1411,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; }
@@ -1356,17 +1737,6 @@ sub map {
   return $self;
 }
 
-
-
-
-
-
-
-
-
-
-
-
 # destructive border - image is shrunk by one pixel all around
 
 sub border {
@@ -1446,15 +1816,14 @@ sub string {
     return;
   }
 
-  $input{font}->draw(image=>$self, %input);
+  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(@_); }
@@ -1469,12 +1838,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
 
@@ -1558,11 +1924,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;
@@ -1626,6 +1987,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:
 
@@ -1798,6 +2192,52 @@ A simple example:
                        @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
@@ -2119,6 +2559,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
@@ -2240,6 +2728,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
@@ -2258,9 +2762,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
@@ -2275,19 +2780,86 @@ 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)
   contrast        intensity
-  hardinvert
+  conv            coef
+  gaussian        stddev
   gradgen         xo yo colors dist
+  hardinvert
+  noise           amount(3) subtype(0)
+  radnoise        xo(100) yo(100) ascale(17.0) rscale(0.02)
+  turbnoise       xo(0.0) yo(0.0) scale(10.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 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 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 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 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.
+
+=back
+
+A demonstration of the the filters can be found at:
+
+  http://www.develop-help.com/imager/filters.html
+
+(This is a slow link.)
 
 =head2 Color transformations
 
@@ -2659,6 +3231,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
@@ -2689,6 +3306,151 @@ 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 ares 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
+
+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
@@ -2707,7 +3469,8 @@ from Tony Cook.  See the README for a complete list.
 
 =head1 SEE ALSO
 
-perl(1), Imager::Color(3), Imager::Font, Affix::Infix2Postfix(3),
-Parse::RecDescent(3) http://www.eecs.umich.edu/~addi/perl/Imager/
+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
index 3c9b461..d2e20aa 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -16,6 +16,7 @@ extern "C" {
 
 typedef io_glue* Imager__IO;
 typedef i_color* Imager__Color;
+typedef i_fcolor* Imager__Color__Float;
 typedef i_img*   Imager__ImgRaw;
 
 
@@ -23,6 +24,10 @@ typedef i_img*   Imager__ImgRaw;
 typedef TT_Fonthandle* Imager__TTHandle;
 #endif
 
+#ifdef HAVE_FT2
+typedef FT2_Fonthandle* Imager__Font__FT2;
+#endif
+
 typedef struct i_reader_data_tag
 {
   /* presumably a CODE ref or name of a sub */
@@ -408,6 +413,13 @@ static void copy_colors_back(HV *hv, i_quantize *quant) {
   }
 }
 
+/* I don't think ICLF_* names belong at the C interface
+   this makes the XS code think we have them, to let us avoid 
+   putting function bodies in the XS code
+*/
+#define ICLF_new_internal(r, g, b, a) i_fcolor_new((r), (g), (b), (a))
+#define ICLF_DESTROY(cl) i_fcolor_destroy(cl)
+
 MODULE = Imager                PACKAGE = Imager::Color PREFIX = ICL_
 
 Imager::Color
@@ -452,8 +464,45 @@ ICL_rgba(cl)
 
 
 
+MODULE = Imager        PACKAGE = Imager::Color::Float  PREFIX=ICLF_
 
+Imager::Color::Float
+ICLF_new_internal(r, g, b, a)
+        double r
+        double g
+        double b
+        double a
+
+void
+ICLF_DESTROY(cl)
+        Imager::Color::Float    cl
 
+void
+ICLF_rgba(cl)
+        Imager::Color::Float    cl
+      PREINIT:
+        int ch;
+      PPCODE:
+        EXTEND(SP, MAXCHANNELS);
+        for (ch = 0; ch < MAXCHANNELS; ++ch) {
+        /* printf("%d: %g\n", ch, cl->channel[ch]); */
+          PUSHs(sv_2mortal(newSVnv(cl->channel[ch])));
+        }
+
+void
+ICLF_set_internal(cl,r,g,b,a)
+        Imager::Color::Float    cl
+        double     r
+        double     g
+        double     b
+        double     a
+      PPCODE:
+        cl->rgba.r = r;
+        cl->rgba.g = g;
+        cl->rgba.b = b;
+        cl->rgba.a = a;                
+        EXTEND(SP, 1);
+        PUSHs(ST(0));
 
 MODULE = Imager                PACKAGE = Imager::ImgRaw        PREFIX = IIM_
 
@@ -578,7 +627,8 @@ i_img_getdata(im)
     Imager::ImgRaw     im
              PPCODE:
               EXTEND(SP, 1);
-               PUSHs(sv_2mortal(newSVpv(im->data, im->bytes)));
+               PUSHs(im->idata ? sv_2mortal(newSVpv(im->idata, im->bytes)) 
+                    : &PL_sv_undef);
 
 
 void
@@ -748,7 +798,7 @@ i_copy(im,src)
     Imager::ImgRaw     src
 
 
-void
+undef_int
 i_rubthru(im,src,tx,ty)
     Imager::ImgRaw     im
     Imager::ImgRaw     src
@@ -760,6 +810,43 @@ i_flipxy(im, direction)
     Imager::ImgRaw     im
               int     direction
 
+Imager::ImgRaw
+i_rotate90(im, degrees)
+    Imager::ImgRaw      im
+               int      degrees
+
+Imager::ImgRaw
+i_rotate_exact(im, amount)
+    Imager::ImgRaw      im
+            double      amount
+
+Imager::ImgRaw
+i_matrix_transform(im, xsize, ysize, matrix)
+    Imager::ImgRaw      im
+               int      xsize
+               int      ysize
+      PREINIT:
+        double matrix[9];
+        AV *av;
+        IV len;
+        SV *sv1;
+        int i;
+      CODE:
+        if (!SvROK(ST(3)) || SvTYPE(SvRV(ST(3))) != SVt_PVAV)
+          croak("i_matrix_transform: parameter 4 must be an array ref\n");
+       av=(AV*)SvRV(ST(3));
+       len=av_len(av)+1;
+        if (len > 9)
+          len = 9;
+        for (i = 0; i < len; ++i) {
+         sv1=(*(av_fetch(av,i,0)));
+         matrix[i] = SvNV(sv1);
+        }
+        for (; i < 9; ++i)
+          matrix[i] = 0;
+        RETVAL = i_matrix_transform(im, xsize, ysize, matrix);        
+      OUTPUT:
+        RETVAL
 
 void
 i_gaussian(im,stdev)
@@ -1412,10 +1499,65 @@ i_readgif_callback(...)
             PUSHs(newRV_noinc((SV*)ct));
         }
 
+void
+i_readgif_multi(fd)
+        int     fd
+      PREINIT:
+        i_img **imgs;
+        int count;
+        int i;
+      PPCODE:
+        imgs = i_readgif_multi(fd, &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);
+        }
 
+void
+i_readgif_multi_scalar(data)
+      PREINIT:
+        i_img **imgs;
+        int count;
+        char *data;
+        unsigned int length;
+        int i;
+      PPCODE:
+        data = (char *)SvPV(ST(0), length);
+        imgs = i_readgif_multi_scalar(data, 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);
+        }
 
-
-
+void
+i_readgif_multi_callback(cb)
+      PREINIT:
+        i_reader_data rd;
+        i_img **imgs;
+        int count;
+        int i;
+      PPCODE:
+        rd.sv = ST(0);
+        imgs = i_readgif_multi_callback(read_callback, (char *)&rd, &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
 
@@ -1743,7 +1885,6 @@ i_errors()
       PREINIT:
         i_errmsg *errors;
        int i;
-       int count;
        AV *av;
        SV *ref;
        SV *sv;
@@ -1885,14 +2026,869 @@ DSO_call(handle,func_index,hv)
 
 
 # this is mostly for testing...
-Imager::Color
+SV *
 i_get_pixel(im, x, y)
        Imager::ImgRaw im
        int x
        int y;
+      PREINIT:
+        i_color *color;
+      CODE:
+       color = (i_color *)mymalloc(sizeof(i_color));
+       if (i_gpix(im, x, y, color) == 0) {
+          ST(0) = sv_newmortal();
+          sv_setref_pv(ST(0), "Imager::Color", (void *)color);
+        }
+        else {
+          myfree(color);
+          ST(0) = &PL_sv_undef;
+        }
+        
+
+int
+i_ppix(im, x, y, cl)
+        Imager::ImgRaw im
+        int x
+        int y
+        Imager::Color cl
+
+Imager::ImgRaw
+i_img_pal_new(x, y, channels, maxpal)
+       int     x
+        int    y
+        int     channels
+       int     maxpal
+
+Imager::ImgRaw
+i_img_to_pal(src, quant)
+        Imager::ImgRaw src
+      PREINIT:
+        HV *hv;
+        i_quantize quant;
+      CODE:
+        if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
+          croak("i_img_to_pal: second argument must be a hash ref");
+        hv = (HV *)SvRV(ST(1));
+        memset(&quant, 0, sizeof(quant));
+        quant.mc_size = 256;
+        quant.mc_colors = mymalloc(quant.mc_size * sizeof(i_color));
+       handle_quant_opts(&quant, hv);
+        RETVAL = i_img_to_pal(src, &quant);
+        if (RETVAL) {
+          copy_colors_back(hv, &quant);
+        }
+        myfree(quant.mc_colors);
+      OUTPUT:
+        RETVAL
+
+Imager::ImgRaw
+i_img_to_rgb(src)
+        Imager::ImgRaw src
+
+void
+i_gpal(im, l, r, y)
+        Imager::ImgRaw  im
+        int     l
+        int     r
+        int     y
+      PREINIT:
+        i_palidx *work;
+        int count, i;
+      PPCODE:
+        if (l < r) {
+          work = mymalloc((r-l) * sizeof(i_palidx));
+          count = i_gpal(im, l, r, y, work);
+          if (GIMME_V == G_ARRAY) {
+            EXTEND(SP, count);
+            for (i = 0; i < count; ++i) {
+              PUSHs(sv_2mortal(newSViv(work[i])));
+            }
+          }
+          else {
+            EXTEND(SP, 1);
+            PUSHs(sv_2mortal(newSVpv(work, count * sizeof(i_palidx))));
+          }
+          myfree(work);
+        }
+        else {
+          if (GIMME_V != G_ARRAY) {
+            EXTEND(SP, 1);
+            PUSHs(&PL_sv_undef);
+          }
+        }
+
+int
+i_ppal(im, l, y, ...)
+        Imager::ImgRaw  im
+        int     l
+        int     y
+      PREINIT:
+        i_palidx *work;
+        int count, i;
+      CODE:
+        if (items > 3) {
+          work = mymalloc(sizeof(i_palidx) * (items-3));
+          for (i=0; i < items-3; ++i) {
+            work[i] = SvIV(ST(i+3));
+          }
+          RETVAL = i_ppal(im, l, l+items-3, y, work);
+          myfree(work);
+        }
+        else {
+          RETVAL = 0;
+        }
+      OUTPUT:
+        RETVAL
+
+SV *
+i_addcolors(im, ...)
+        Imager::ImgRaw  im
+      PREINIT:
+        int index;
+        i_color *colors;
+        int i;
+      CODE:
+        if (items < 2)
+          croak("i_addcolors: no colors to add");
+        colors = mymalloc((items-1) * sizeof(i_color));
+        for (i=0; i < items-1; ++i) {
+          if (sv_isobject(ST(i+1)) 
+              && sv_derived_from(ST(i+1), "Imager::Color")) {
+            IV tmp = SvIV((SV *)SvRV(ST(i+1)));
+            colors[i] = *(i_color *)tmp;
+          }
+          else {
+            myfree(colors);
+            croak("i_plin: pixels must be Imager::Color objects");
+          }
+        }
+        index = i_addcolors(im, colors, items-1);
+        myfree(colors);
+        if (index == 0) {
+          ST(0) = sv_2mortal(newSVpv("0 but true", 0));
+        }
+        else if (index == -1) {
+          ST(0) = &PL_sv_undef;
+        }
+        else {
+          ST(0) = sv_2mortal(newSViv(index));
+        }
+
+int 
+i_setcolors(im, index, ...)
+        Imager::ImgRaw  im
+        int index
+      PREINIT:
+        i_color *colors;
+        int i;
+      CODE:
+        if (items < 3)
+          croak("i_setcolors: no colors to add");
+        colors = mymalloc((items-2) * sizeof(i_color));
+        for (i=0; i < items-2; ++i) {
+          if (sv_isobject(ST(i+2)) 
+              && sv_derived_from(ST(i+2), "Imager::Color")) {
+            IV tmp = SvIV((SV *)SvRV(ST(i+2)));
+            colors[i] = *(i_color *)tmp;
+          }
+          else {
+            myfree(colors);
+            croak("i_setcolors: pixels must be Imager::Color objects");
+          }
+        }
+        RETVAL = i_setcolors(im, index, colors, items-2);
+        myfree(colors);
+
+void
+i_getcolors(im, index, ...)
+        Imager::ImgRaw im
+        int index
+      PREINIT:
+        i_color *colors;
+        int count = 1;
+        int i;
+      PPCODE:
+        if (items > 3)
+          croak("i_getcolors: too many arguments");
+        if (items == 3)
+          count = SvIV(ST(2));
+        if (count < 1)
+          croak("i_getcolors: count must be positive");
+        colors = mymalloc(sizeof(i_color) * count);
+        if (i_getcolors(im, index, colors, count)) {
+          for (i = 0; i < count; ++i) {
+            i_color *pv;
+            SV *sv = sv_newmortal();
+            pv = mymalloc(sizeof(i_color));
+            *pv = colors[i];
+            sv_setref_pv(sv, "Imager::Color", (void *)pv);
+            PUSHs(sv);
+          }
+        }
+        myfree(colors);
+
+
+SV *
+i_colorcount(im)
+        Imager::ImgRaw im
+      PREINIT:
+        int count;
+      CODE:
+        count = i_colorcount(im);
+        if (count >= 0) {
+          ST(0) = sv_2mortal(newSViv(count));
+        }
+        else {
+          ST(0) = &PL_sv_undef;
+        }
+
+SV *
+i_maxcolors(im)
+        Imager::ImgRaw im
+      PREINIT:
+        int count;
+      CODE:
+        count = i_maxcolors(im);
+        if (count >= 0) {
+          ST(0) = sv_2mortal(newSViv(count));
+        }
+        else {
+          ST(0) = &PL_sv_undef;
+        }
+
+SV *
+i_findcolor(im, color)
+        Imager::ImgRaw im
+        Imager::Color color
+      PREINIT:
+        i_palidx index;
+      CODE:
+        if (i_findcolor(im, color, &index)) {
+          ST(0) = sv_2mortal(newSViv(index));
+        }
+        else {
+          ST(0) = &PL_sv_undef;
+        }
+
+int
+i_img_bits(im)
+        Imager::ImgRaw  im
+
+int
+i_img_type(im)
+        Imager::ImgRaw  im
+
+int
+i_img_virtual(im)
+        Imager::ImgRaw  im
+
+void
+i_gsamp(im, l, r, y, ...)
+        Imager::ImgRaw im
+        int l
+        int r
+        int y
+      PREINIT:
+        int *chans;
+        int chan_count;
+        i_sample_t *data;
+        int count, i;
+      PPCODE:
+        if (items < 5)
+          croak("No channel numbers supplied to g_samp()");
+        if (l < r) {
+          chan_count = items - 4;
+          chans = mymalloc(sizeof(int) * chan_count);
+          for (i = 0; i < chan_count; ++i)
+            chans[i] = SvIV(ST(i+4));
+          data = mymalloc(sizeof(i_sample_t) * (r-l) * chan_count);
+          count = i_gsamp(im, l, r, y, data, chans, chan_count);
+          if (GIMME_V == G_ARRAY) {
+            EXTEND(SP, count);
+            for (i = 0; i < count; ++i)
+              PUSHs(sv_2mortal(newSViv(data[i])));
+          }
+          else {
+            EXTEND(SP, 1);
+            PUSHs(sv_2mortal(newSVpv(data, count * sizeof(i_sample_t))));
+          }
+        }
+        else {
+          if (GIMME_V != G_ARRAY) {
+            EXTEND(SP, 1);
+            PUSHs(&PL_sv_undef);
+          }
+        }
+
+Imager::ImgRaw
+i_img_masked_new(targ, mask, x, y, w, h)
+        Imager::ImgRaw targ
+        int x
+        int y
+        int w
+        int h
+      PREINIT:
+        i_img *mask;
+      CODE:
+        if (SvOK(ST(1))) {
+          if (!sv_isobject(ST(1)) 
+              || !sv_derived_from(ST(1), "Imager::ImgRaw")) {
+            croak("i_img_masked_new: parameter 2 must undef or an image");
+          }
+          mask = (i_img *)SvIV((SV *)SvRV(ST(1)));
+        }
+        else
+          mask = NULL;
+        RETVAL = i_img_masked_new(targ, mask, x, y, w, h);
+      OUTPUT:
+        RETVAL
+
+int
+i_plin(im, l, y, ...)
+        Imager::ImgRaw  im
+        int     l
+        int     y
+      PREINIT:
+        i_color *work;
+        int count, i;
+      CODE:
+        if (items > 3) {
+          work = mymalloc(sizeof(i_color) * (items-3));
+          for (i=0; i < items-3; ++i) {
+            if (sv_isobject(ST(i+3)) 
+                && sv_derived_from(ST(i+3), "Imager::Color")) {
+              IV tmp = SvIV((SV *)SvRV(ST(i+3)));
+              work[i] = *(i_color *)tmp;
+            }
+            else {
+              myfree(work);
+              croak("i_plin: pixels must be Imager::Color objects");
+            }
+          }
+          /**(char *)0 = 1;*/
+          RETVAL = i_plin(im, l, l+items-3, y, work);
+          myfree(work);
+        }
+        else {
+          RETVAL = 0;
+        }
+      OUTPUT:
+        RETVAL
+
+int
+i_ppixf(im, x, y, cl)
+        Imager::ImgRaw im
+        int x
+        int y
+        Imager::Color::Float cl
+
+void
+i_gsampf(im, l, r, y, ...)
+        Imager::ImgRaw im
+        int l
+        int r
+        int y
+      PREINIT:
+        int *chans;
+        int chan_count;
+        i_fsample_t *data;
+        int count, i;
+      PPCODE:
+        if (items < 5)
+          croak("No channel numbers supplied to g_sampf()");
+        if (l < r) {
+          chan_count = items - 4;
+          chans = mymalloc(sizeof(int) * chan_count);
+          for (i = 0; i < chan_count; ++i)
+            chans[i] = SvIV(ST(i+4));
+          data = mymalloc(sizeof(i_fsample_t) * (r-l) * chan_count);
+          count = i_gsampf(im, l, r, y, data, chans, chan_count);
+          if (GIMME_V == G_ARRAY) {
+            EXTEND(SP, count);
+            for (i = 0; i < count; ++i)
+              PUSHs(sv_2mortal(newSVnv(data[i])));
+          }
+          else {
+            EXTEND(SP, 1);
+            PUSHs(sv_2mortal(newSVpv((void *)data, count * sizeof(i_fsample_t))));
+          }
+        }
+        else {
+          if (GIMME_V != G_ARRAY) {
+            EXTEND(SP, 1);
+            PUSHs(&PL_sv_undef);
+          }
+        }
+
+int
+i_plinf(im, l, y, ...)
+        Imager::ImgRaw  im
+        int     l
+        int     y
+      PREINIT:
+        i_fcolor *work;
+        int count, i;
+      CODE:
+        if (items > 3) {
+          work = mymalloc(sizeof(i_fcolor) * (items-3));
+          for (i=0; i < items-3; ++i) {
+            if (sv_isobject(ST(i+3)) 
+                && sv_derived_from(ST(i+3), "Imager::Color::Float")) {
+              IV tmp = SvIV((SV *)SvRV(ST(i+3)));
+              work[i] = *(i_fcolor *)tmp;
+            }
+            else {
+              myfree(work);
+              croak("i_plin: pixels must be Imager::Color::Float objects");
+            }
+          }
+          /**(char *)0 = 1;*/
+          RETVAL = i_plinf(im, l, l+items-3, y, work);
+          myfree(work);
+        }
+        else {
+          RETVAL = 0;
+        }
+      OUTPUT:
+        RETVAL
+
+SV *
+i_gpixf(im, x, y)
+       Imager::ImgRaw im
+       int x
+       int y;
+      PREINIT:
+        i_fcolor *color;
+      CODE:
+       color = (i_fcolor *)mymalloc(sizeof(i_fcolor));
+       if (i_gpixf(im, x, y, color) == 0) {
+          ST(0) = sv_newmortal();
+          sv_setref_pv(ST(0), "Imager::Color::Float", (void *)color);
+        }
+        else {
+          myfree(color);
+          ST(0) = &PL_sv_undef;
+        }
+        
+void
+i_glin(im, l, r, y)
+        Imager::ImgRaw im
+        int l
+        int r
+        int y
+      PREINIT:
+        i_color *vals;
+        int count, i;
+      PPCODE:
+        if (l < r) {
+          vals = mymalloc((r-l) * sizeof(i_color));
+          count = i_glin(im, l, r, y, vals);
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            SV *sv;
+            i_color *col = mymalloc(sizeof(i_color));
+            sv = sv_newmortal();
+            sv_setref_pv(sv, "Imager::Color", (void *)col);
+            PUSHs(sv);
+          }
+          myfree(vals);
+        }
+
+void
+i_glinf(im, l, r, y)
+        Imager::ImgRaw im
+        int l
+        int r
+        int y
+      PREINIT:
+        i_fcolor *vals;
+        int count, i;
+      PPCODE:
+        if (l < r) {
+          vals = mymalloc((r-l) * sizeof(i_fcolor));
+          count = i_glinf(im, l, r, y, vals);
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            SV *sv;
+            i_fcolor *col = mymalloc(sizeof(i_fcolor));
+            *col = vals[i];
+            sv = sv_newmortal();
+            sv_setref_pv(sv, "Imager::Color::Float", (void *)col);
+            PUSHs(sv);
+          }
+          myfree(vals);
+        }
+
+Imager::ImgRaw
+i_img_16_new(x, y, ch)
+        int x
+        int y
+        int ch
+
+undef_int
+i_tags_addn(im, name, code, idata)
+        Imager::ImgRaw im
+        int     code
+        int     idata
+      PREINIT:
+        char *name;
+        STRLEN len;
+      CODE:
+        if (SvOK(ST(1)))
+          name = SvPV(ST(1), len);
+        else
+          name = NULL;
+        RETVAL = i_tags_addn(&im->tags, name, code, idata);
+      OUTPUT:
+        RETVAL
+
+undef_int
+i_tags_add(im, name, code, data, idata)
+        Imager::ImgRaw  im
+        int code
+        int idata
+      PREINIT:
+        char *name;
+        char *data;
+        STRLEN len;
+      CODE:
+        if (SvOK(ST(1)))
+          name = SvPV(ST(1), len);
+        else
+          name = NULL;
+        if (SvOK(ST(3)))
+          data = SvPV(ST(3), len);
+        else {
+          data = NULL;
+          len = 0;
+        }
+        RETVAL = i_tags_add(&im->tags, name, code, data, len, idata);
+      OUTPUT:
+        RETVAL
+
+SV *
+i_tags_find(im, name, start)
+        Imager::ImgRaw  im
+        char *name
+        int start
+      PREINIT:
+        int entry;
+      CODE:
+        if (i_tags_find(&im->tags, name, start, &entry)) {
+          if (entry == 0)
+            ST(0) = sv_2mortal(newSVpv("0 but true", 0));
+          else
+            ST(0) = sv_2mortal(newSViv(entry));
+        } else {
+          ST(0) = &PL_sv_undef;
+        }
+
+SV *
+i_tags_findn(im, code, start)
+        Imager::ImgRaw  im
+        int             code
+        int             start
+      PREINIT:
+        int entry;
+      CODE:
+        if (i_tags_findn(&im->tags, code, start, &entry)) {
+          if (entry == 0)
+            ST(0) = sv_2mortal(newSVpv("0 but true", 0));
+          else
+            ST(0) = sv_2mortal(newSViv(entry));
+        }
+        else
+          ST(0) = &PL_sv_undef;
+
+int
+i_tags_delete(im, entry)
+        Imager::ImgRaw  im
+        int             entry
+      CODE:
+        RETVAL = i_tags_delete(&im->tags, entry);
+      OUTPUT:
+        RETVAL
+
+int
+i_tags_delbyname(im, name)
+        Imager::ImgRaw  im
+        char *          name
+      CODE:
+        RETVAL = i_tags_delbyname(&im->tags, name);
+      OUTPUT:
+        RETVAL
+
+int
+i_tags_delbycode(im, code)
+        Imager::ImgRaw  im
+        int             code
+      CODE:
+        RETVAL = i_tags_delbycode(&im->tags, code);
+      OUTPUT:
+        RETVAL
+
+void
+i_tags_get(im, index)
+        Imager::ImgRaw  im
+        int             index
+      PPCODE:
+        if (index >= 0 && index < im->tags.count) {
+          i_img_tag *entry = im->tags.tags + index;
+          EXTEND(SP, 5);
+        
+          if (entry->name) {
+            PUSHs(sv_2mortal(newSVpv(entry->name, 0)));
+          }
+          else {
+            PUSHs(sv_2mortal(newSViv(entry->code)));
+          }
+          if (entry->data) {
+            PUSHs(sv_2mortal(newSVpvn(entry->data, entry->size)));
+          }
+          else {
+            PUSHs(sv_2mortal(newSViv(entry->idata)));
+          }
+        }
+
+int
+i_tags_count(im)
+        Imager::ImgRaw  im
+      CODE:
+        RETVAL = im->tags.count;
+      OUTPUT:
+        RETVAL
+
+#ifdef HAVE_WIN32
+
+void
+i_wf_bbox(face, size, text)
+       char *face
+       int size
+       char *text
+      PREINIT:
+       int cords[6];
+      PPCODE:
+        if (i_wf_bbox(face, size, text, strlen(text), cords)) {
+          EXTEND(SP, 6);  
+          PUSHs(sv_2mortal(newSViv(cords[0])));
+          PUSHs(sv_2mortal(newSViv(cords[1])));
+          PUSHs(sv_2mortal(newSViv(cords[2])));
+          PUSHs(sv_2mortal(newSViv(cords[3])));
+          PUSHs(sv_2mortal(newSViv(cords[4])));
+          PUSHs(sv_2mortal(newSViv(cords[5])));
+        }
+
+undef_int
+i_wf_text(face, im, tx, ty, cl, size, text, align, aa)
+       char *face
+       Imager::ImgRaw im
+       int tx
+       int ty
+       Imager::Color cl
+       int size
+       char *text
+       int align
+       int aa
+      CODE:
+       RETVAL = i_wf_text(face, im, tx, ty, cl, size, text, strlen(text), 
+                          align, aa);
+      OUTPUT:
+       RETVAL
+
+undef_int
+i_wf_cp(face, im, tx, ty, channel, size, text, align, aa)
+       char *face
+       Imager::ImgRaw im
+       int tx
+       int ty
+       int channel
+       int size
+       char *text
+       int align
+       int aa
       CODE:
-       RETVAL = (i_color *)mymalloc(sizeof(i_color));
-       i_gpix(im, x, y, RETVAL);
+       RETVAL = i_wf_cp(face, im, tx, ty, channel, size, text, strlen(text), 
+                        align, aa);
       OUTPUT:
        RETVAL
 
+
+#endif
+
+#ifdef HAVE_FT2
+
+MODULE = Imager         PACKAGE = Imager::Font::FT2     PREFIX=FT2_
+
+#define FT2_DESTROY(font) i_ft2_destroy(font)
+
+void
+FT2_DESTROY(font)
+        Imager::Font::FT2 font
+
+MODULE = Imager         PACKAGE = Imager::Font::FreeType2 
+
+Imager::Font::FT2
+i_ft2_new(name, index)
+        char *name
+        int index
+
+undef_int
+i_ft2_setdpi(font, xdpi, ydpi)
+        Imager::Font::FT2 font
+        int xdpi
+        int ydpi
+
+void
+i_ft2_getdpi(font)
+        Imager::Font::FT2 font
+      PREINIT:
+        int xdpi, ydpi;
+      CODE:
+        if (i_ft2_getdpi(font, &xdpi, &ydpi)) {
+          EXTEND(SP, 2);
+          PUSHs(sv_2mortal(newSViv(xdpi)));
+          PUSHs(sv_2mortal(newSViv(ydpi)));
+        }
+
+undef_int
+i_ft2_sethinting(font, hinting)
+        Imager::Font::FT2 font
+        int hinting
+
+undef_int
+i_ft2_settransform(font, matrix)
+        Imager::Font::FT2 font
+      PREINIT:
+        double matrix[6];
+        int len;
+        AV *av;
+        SV *sv1;
+        int i;
+      CODE:
+        if (!SvROK(ST(1)) || SvTYPE(SvRV(ST(1))) != SVt_PVAV)
+          croak("i_ft2_settransform: parameter 2 must be an array ref\n");
+       av=(AV*)SvRV(ST(1));
+       len=av_len(av)+1;
+        if (len > 6)
+          len = 6;
+        for (i = 0; i < len; ++i) {
+         sv1=(*(av_fetch(av,i,0)));
+         matrix[i] = SvNV(sv1);
+        }
+        for (; i < 6; ++i)
+          matrix[i] = 0;
+        RETVAL = i_ft2_settransform(font, matrix);
+      OUTPUT:
+        RETVAL
+
+void
+i_ft2_bbox(font, cheight, cwidth, text)
+        Imager::Font::FT2 font
+        double cheight
+        double cwidth
+        char *text
+      PREINIT:
+        int bbox[6];
+        int i;
+      PPCODE:
+        if (i_ft2_bbox(font, cheight, cwidth, text, strlen(text), bbox)) {
+          EXTEND(SP, 6);
+          for (i = 0; i < 6; ++i)
+            PUSHs(sv_2mortal(newSViv(bbox[i])));
+        }
+
+void
+i_ft2_bbox_r(font, cheight, cwidth, text, vlayout, utf8)
+        Imager::Font::FT2 font
+        double cheight
+        double cwidth
+        char *text
+        int vlayout
+        int utf8
+      PREINIT:
+        int bbox[8];
+        int i;
+      PPCODE:
+#ifdef SvUTF8
+        if (SvUTF8(ST(3)))
+          utf8 = 1;
+#endif
+        if (i_ft2_bbox_r(font, cheight, cwidth, text, strlen(text), vlayout,
+                         utf8, bbox)) {
+          EXTEND(SP, 8);
+          for (i = 0; i < 8; ++i)
+            PUSHs(sv_2mortal(newSViv(bbox[i])));
+        }
+
+undef_int
+i_ft2_text(font, im, tx, ty, cl, cheight, cwidth, text, align, aa, vlayout, utf8)
+        Imager::Font::FT2 font
+        Imager::ImgRaw im
+        int tx
+        int ty
+        Imager::Color cl
+        double cheight
+        double cwidth
+        int align
+        int aa
+        int vlayout
+        int utf8
+      PREINIT:
+        char *text;
+        STRLEN len;
+      CODE:
+#ifdef SvUTF8
+        if (SvUTF8(ST(7))) {
+          utf8 = 1;
+        }
+#endif
+        text = SvPV(ST(7), len);
+        RETVAL = i_ft2_text(font, im, tx, ty, cl, cheight, cwidth, text,
+                            len, align, aa, vlayout, utf8);
+      OUTPUT:
+        RETVAL
+
+undef_int
+i_ft2_cp(font, im, tx, ty, channel, cheight, cwidth, text, align, aa, vlayout, utf8)
+        Imager::Font::FT2 font
+        Imager::ImgRaw im
+        int tx
+        int ty
+        int channel
+        double cheight
+        double cwidth
+        char *text
+        int align
+        int aa
+        int vlayout
+        int utf8
+      CODE:
+#ifdef SvUTF8
+        if (SvUTF8(ST(7)))
+          utf8 = 1;
+#endif
+        RETVAL = i_ft2_cp(font, im, tx, ty, channel, cheight, cwidth, text,
+                          strlen(text), align, aa, vlayout, 1);
+      OUTPUT:
+        RETVAL
+
+void
+ft2_transform_box(font, x0, x1, x2, x3)
+        Imager::Font::FT2 font
+        int x0
+        int x1
+        int x2
+        int x3
+      PREINIT:
+        int box[4];
+      PPCODE:
+        box[0] = x0; box[1] = x1; box[2] = x2; box[3] = x3;
+        ft2_transform_box(font, box);
+          EXTEND(SP, 4);
+          PUSHs(sv_2mortal(newSViv(box[0])));
+          PUSHs(sv_2mortal(newSViv(box[1])));
+          PUSHs(sv_2mortal(newSViv(box[2])));
+          PUSHs(sv_2mortal(newSViv(box[3])));
+        
+#endif
+
index 77a591e..6732f33 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -14,9 +14,11 @@ gaussian.c
 ppport.h
 image.c
 image.h
+imagei.h
 datatypes.h
 datatypes.c
 feat.h
+img16.c
 io.c
 io.h
 log.c
@@ -31,24 +33,38 @@ pnm.c
 filters.c
 feat.c
 font.c
+freetyp2.c     Implements freetype2 font support
+maskimg.c
+palimg.c
 regmach.c
 regmach.h
+rotate.c
 stackmach.c
 stackmach.h
+tags.c
 trans2.c
 iolayer.h
 iolayer.c
 fontfiles/dcr10.afm
 fontfiles/dcr10.pfb
 fontfiles/dodge.ttf
-lib/Imager/regmach.pod
-lib/Imager/Regops.pm
+lib/Imager/Color.pm
+lib/Imager/Color/Float.pm
 lib/Imager/Expr.pm
 lib/Imager/Expr/Assem.pm
-lib/Imager/Color.pm
 lib/Imager/Font.pm
+lib/Imager/Font/Type1.pm
+lib/Imager/Font/Truetype.pm
+lib/Imager/Font/FreeType2.pm
+lib/Imager/interface.pod
+lib/Imager/Matrix2d.pm
+lib/Imager/regmach.pod
+lib/Imager/Regops.pm
 lib/Imager/Transform.pm
 t/t00basic.t
+t/t01introvert.t
+t/t020masked.t
+t/t021sixteen.t
 t/t05error.t
 t/t101jpeg.t
 t/t102png.t
@@ -60,6 +76,8 @@ t/t15color.t
 t/t30t1font.t
 t/t35ttfont.t
 t/t36oofont.t
+t/t37w32font.t         Tests Win32 GDI font support
+t/t38ft2font.t         Tests freetype2 support
 t/t40scale.t
 t/t50basicoo.t
 t/t55trans.t
@@ -70,6 +88,7 @@ t/t59assem.t
 t/t60dyntest.t
 t/t65crop.t
 t/t66paste.t
+t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
 t/t90cc.t
@@ -85,6 +104,8 @@ testimg/scale.gif
 testimg/scale.ppm
 testimg/scalei.gif
 testimg/screen2.gif
+testimg/screen3.gif
+testimg/test_gimp_pal
 testimg/trimgdesc.gif
 testimg/trmiddesc.gif
 typemap
@@ -106,3 +127,4 @@ dynfilt/mandelbrot.c
 dynfilt/flines.c
 dynfilt/compile.txt
 dynfilt/Makefile.PL
+win32.c                Implements font support through Win32 GDI
index 9e78d8e..6318d46 100644 (file)
@@ -62,7 +62,7 @@ if (defined $Config{'d_dlsymun'}) { $OSDEF  .= ' -DDLSYMUN'; }
           gaussian.o conv.o pnm.o raw.o feat.o font.o
           filters.o dynaload.o stackmach.o datatypes.o
           regmach.o trans2.o quant.o error.o convert.o
-          map.o);
+          map.o tags.o palimg.o maskimg.o img16.o rotate.o);
 
 %opts=(
        'NAME'         => 'Imager',
@@ -88,7 +88,7 @@ exit;
 
 sub MY::postamble {
 '
-dyntest.(MYEXTLIB) : dynfilt/Makefile
+dyntest.$(MYEXTLIB) : dynfilt/Makefile
        cd dynfilt && $(MAKE) $(PASTHRU)
 
 lib/Imager/Regops.pm : regmach.h regops.perl
@@ -261,12 +261,16 @@ sub pathcheck {
 sub init {
 
   @definc{'/usr/include'}=();
-  @incs=(qw(/usr/include /usr/local/include /usr/include/freetype /usr/local/include/freetype), split /:/, $INCPATH );
+  @incs=(qw(/usr/include /usr/local/include /usr/include/freetype /usr/local/include/freetype /usr/include/freetype2 /usr/local/include/freetype2), split /:/, $INCPATH );
   @libs=(split(/ /, $Config{'libpth'}), split(/:/, $LIBPATH) );
   if ($^O =~ /win32/i && $Config{cc} =~ /\bcl\b/i) {
     push(@incs, split /;/, $ENV{INCLUDE}) if exists $ENV{INCLUDE};
     push(@libs, split /;/, $ENV{LIB}) if exists $ENV{LIB};
   }
+  if ($^O eq 'cygwin') {
+    push(@libs, '/usr/lib/w32api') if -d '/usr/lib/w32api';
+    push(@incs, '/usr/include/w32api') if -d '/usr/lib/w32api';
+  }
 
   $formats{'jpeg'}={
                    order=>'21',
@@ -338,7 +342,7 @@ sub init {
                        order=>'30',
                        def=>'HAVE_LIBT1',
                        inccheck=>sub { $_[0] eq 't1lib.h' },
-                       libcheck=>sub { $_[0] eq 'libt1.a' or $_[0] eq "libt1.$lext" },
+                       libcheck=>sub { $_[0] eq "libt1$aext" or $_[0] eq "libt1.$lext" },
                        libfiles=>'-lt1',
                        objfiles=>'',
                        docs=>q{
@@ -352,7 +356,7 @@ sub init {
                        order=>'31',
                        def=>'HAVE_LIBTT',
                        inccheck=>sub { $_[0] eq 'freetype.h' },
-                       libcheck=>sub { $_[0] eq 'libttf.a' or $_[0] eq "libttf.$lext" },
+                       libcheck=>sub { $_[0] eq "libttf$aext" or $_[0] eq "libttf.$lext" },
                        libfiles=>'-lttf',
                        objfiles=>'',
                        docs=>q{
@@ -362,6 +366,32 @@ sub init {
                                used to rasterize for us. The only drawback is that there
                                are alot of badly designed fonts out there.}
                       };
+  $formats{'w32'} = {
+                    order=>40,
+                    def=>'HAVE_WIN32',
+                    inccheck=>sub { lc $_[0] eq 'windows.h' },
+                    libcheck=>sub { lc $_[0] eq 'gdi32.lib' 
+                                      || lc $_[0] eq 'libgdi32.a' },
+                    libfiles=>$^O eq 'cygwin' ? '-lgdi32' : '',
+                    objfiles=>'win32.o',
+                    docs => <<DOCS
+Uses the Win32 GDI for rendering text.
+
+This currently only works on under normal Win32 and cygwin.
+DOCS
+                   };
+  $formats{'freetype2'} = {
+                           order=>'29',
+                           def=>'HAVE_FT2',
+                           inccheck=>sub { lc $_[0] eq 'ft2build.h' },
+                           libcheck=>sub { $_[0] eq "libfreetype$aext" or $_[0] eq "libfreetype.$lext" },
+                           libfiles=>'-lfreetype',
+                           objfiles=>'freetyp2.o',
+                           docs=><<DOCS
+Freetype 2 supports both Truetype and Type 1 fonts, both of which are
+scalable.
+DOCS
+                          };
   # Make fix indent
   for (keys %formats) { $formats{$_}->{docs} =~ s/^\s+/  /mg; }
 }
diff --git a/README b/README
index 4e35693..4798a1f 100644 (file)
--- a/README
+++ b/README
@@ -169,15 +169,11 @@ font.
 =================
 
 Imager can be installed on Win32 systems.  This was ported and tested
-with Microsoft Visual C++ 6.0 with build 623 of ActivePerl.  If you
-have the appropriate libraries installed you can read and write PNG,
-TIFF, PPM and JPEG files.  There is currently no support for fonts
-under Win32, though it might be preferable to try to use Win32's
-native font support over the external librarie - why force the user to
-install yet another library?
-
-I haven't tried to target compilers other than VC++, since I don't
-have them installed.
+with Microsoft Visual C++ 6.0 with build 623 of ActivePerl.  You can
+use all of the features of Imager.  You can also use Win32 GDI fonts
+directly by supplying the 'face' parameter to Imager::Font->new(...).
+
+I've tested with both MSVC++ 6.0 and cygwin (perl 5.6.1).
 
 If you have any problems with the Win32 support, please email
 tony@develop-help.com (don't forget to use nmake instead of make).
diff --git a/TODO b/TODO
index 8196a99..5a2ceab 100644 (file)
--- a/TODO
+++ b/TODO
@@ -25,6 +25,9 @@ MultiImage & metadata support:
   local errors?
 - SEE design/represent.txt for proposed new structure and
   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)
 
 New Features:
 - Add mng support.
@@ -34,6 +37,23 @@ New Features:
 
 - Finish antialiased filled polygon function.
 
+- freetype 2 support
+
+- advanced font layout (spacing, kerning, alignment) (sky)
+
+- font synthesis - synthesize a bold or slanted font from a normal font
+  (or even from an existing bold or slanted font)
+- utf8 support for text output
+
+- image rotation, 3 ways of doing rotation:
+  - exact multiple of 90 degrees (the easy case) (done)
+  - rotation by exact angles that results in no scaling, using 
+    interpolation to produce a good result (done)
+  - rotation by shearing, which produces makes lengths in the image larger,
+    but could be useful
+
+- read_multi() needs to handle other multi-image types, such as TIFF 
+  (probably the most common)
 
 Clean up:
 - Make sure everything is doable with the OO interface
@@ -62,13 +82,6 @@ Clean up:
 
 
 Format specific issues:
-- should i_readgif returned colormap be an arrayref of 
-  Imager::Color objects?  Note that this will break 
-  compatibility with previous releases.
-
-- if gif_delays is a number instead of an arrayref, use that 
-  number for each frame
-
 - provide patches for libgif and libungif that fix their bugs
   and give a useful extension interface.  Probe for the 
   installation of the patches in Makefile.PL to let gif.c
@@ -79,10 +92,15 @@ Format specific issues:
   cases.  Also allow ascii mode.  Need to be able to write 
   pbm images which needs ties to the quantization code.
 
+- bmp, pcx and targa image formats
 
 Documentation:
 - Add to the documentation
 - Write a tutorial?
+- sample code and Imager/Samples.pod describing them
+- Imager/Cookbook.pod
+- modify the .pm files to put pod describing a function close to the 
+  function
 - Write a guide to installing the helper libraries
 - Go through the entire project and add comments in pod
   so doco.perl can be used to read them.
index 8302b20..61017e8 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -35,13 +35,14 @@ each output pixel:
 If im has the wrong number of channels or is the wrong size then
 i_convert() will re-create it.
 
+Now handles images with more than 8-bits/sample.
+
 =cut
 */
 
 int
 i_convert(i_img *im, i_img *src, float *coeff, int outchan, int inchan)
 {
-  i_color *vals;
   int x, y;
   int i, j;
   int ilimit;
@@ -59,36 +60,125 @@ i_convert(i_img *im, i_img *src, float *coeff, int outchan, int inchan)
     return 0;
   }
 
-  /* first check the output image */
-  if (im->channels != outchan || im->xsize != src->xsize 
-      || im->ysize != src->ysize) {
-    i_img_empty_ch(im, src->xsize, src->ysize, outchan);
+  if (im->type == i_direct_type || src->type == i_direct_type) {
+    /* first check the output image */
+    if (im->channels != outchan || im->xsize != src->xsize 
+        || im->ysize != src->ysize) {
+      i_img_exorcise(im);
+      i_img_empty_ch(im, src->xsize, src->ysize, outchan);
+    }
+    if (im->bits == i_8_bits && src->bits == i_8_bits) {
+      i_color *vals;
+      
+      vals = mymalloc(sizeof(i_color) * src->xsize);
+      for (y = 0; y < src->ysize; ++y) {
+        i_glin(src, 0, src->xsize, y, vals);
+        for (x = 0; x < src->xsize; ++x) {
+          for (j = 0; j < outchan; ++j) {
+            work[j] = 0;
+            for (i = 0; i < ilimit; ++i) {
+              work[j] += coeff[i+inchan*j] * vals[x].channel[i];
+            }
+            if (i < inchan) {
+              work[j] += coeff[i+inchan*j] * 255.9;
+            }
+          }
+          for (j = 0; j < outchan; ++j) {
+            if (work[j] < 0)
+              vals[x].channel[j] = 0;
+            else if (work[j] >= 256)
+              vals[x].channel[j] = 255;
+            else
+              vals[x].channel[j] = work[j];
+          }
+        }
+        i_plin(im, 0, src->xsize, y, vals);
+      }
+      myfree(vals);
+    }
+    else {
+      i_fcolor *vals;
+      
+      vals = mymalloc(sizeof(i_fcolor) * src->xsize);
+      for (y = 0; y < src->ysize; ++y) {
+        i_glinf(src, 0, src->xsize, y, vals);
+        for (x = 0; x < src->xsize; ++x) {
+          for (j = 0; j < outchan; ++j) {
+            work[j] = 0;
+            for (i = 0; i < ilimit; ++i) {
+              work[j] += coeff[i+inchan*j] * vals[x].channel[i];
+            }
+            if (i < inchan) {
+              work[j] += coeff[i+inchan*j];
+            }
+          }
+          for (j = 0; j < outchan; ++j) {
+            if (work[j] < 0)
+              vals[x].channel[j] = 0;
+            else if (work[j] >= 1)
+              vals[x].channel[j] = 1;
+            else
+              vals[x].channel[j] = work[j];
+          }
+        }
+        i_plinf(im, 0, src->xsize, y, vals);
+      }
+      myfree(vals);
+    }
   }
-  vals = mymalloc(sizeof(i_color) * src->xsize);
-  for (y = 0; y < src->ysize; ++y) {
-    i_glin(src, 0, src->xsize, y, vals);
-    for (x = 0; x < src->xsize; ++x) {
+  else {
+    int count;
+    int outcount;
+    int index;
+    i_color *colors;
+    i_palidx *vals;
+
+    if (im->channels != outchan || im->xsize != src->xsize 
+        || im->ysize != src->ysize
+        || i_maxcolors(im) < i_colorcount(src)) {
+      i_img_exorcise(im);
+      i_img_pal_new_low(im, src->xsize, src->ysize, outchan, 
+                        i_maxcolors(src));
+    }
+    /* just translate the color table */
+    count = i_colorcount(src);
+    outcount = i_colorcount(im);
+    colors = mymalloc(count * sizeof(i_color));
+    i_getcolors(src, 0, colors, count);
+    for (index = 0; index < count; ++index) {
       for (j = 0; j < outchan; ++j) {
-       work[j] = 0;
-       for (i = 0; i < ilimit; ++i) {
-         work[j] += coeff[i+inchan*j] * vals[x].channel[i];
-       }
-       if (i < inchan) {
-         work[j] += coeff[i+inchan*j] * 255.9;
-       }
+        work[j] = 0;
+        for (i = 0; i < ilimit; ++i) {
+          work[j] += coeff[i+inchan*j] * colors[index].channel[i];
+        }
+        if (i < inchan) {
+          work[j] += coeff[i+inchan*j] * 255.9;
+        }
       }
       for (j = 0; j < outchan; ++j) {
-       if (work[j] < 0)
-         vals[x].channel[j] = 0;
-       else if (work[j] >= 256)
-         vals[x].channel[j] = 255;
-       else
-         vals[x].channel[j] = work[j];
+        if (work[j] < 0)
+          colors[index].channel[j] = 0;
+        else if (work[j] >= 255)
+          colors[index].channel[j] = 255;
+        else
+          colors[index].channel[j] = work[j];
       }
     }
-    i_plin(im, 0, src->xsize, y, vals);
+    if (count < outcount) {
+      i_setcolors(im, 0, colors, count);
+    }
+    else {
+      i_setcolors(im, 0, colors, outcount);
+      i_addcolors(im, colors, count-outcount);
+    }
+    /* and copy the indicies */
+    vals = mymalloc(sizeof(i_palidx) * im->xsize);
+    for (y = 0; y < im->ysize; ++y) {
+      i_gpal(src, 0, im->xsize, y, vals);
+      i_ppal(im, 0, im->xsize, y, vals);
+    }
   }
-  myfree(vals);
+
   return 1;
 }
 
index 1a2d5de..78f6398 100644 (file)
@@ -5,10 +5,19 @@
 
 #define MAXCHANNELS 4
 
-typedef struct { unsigned char gray_color; } gray_color;
-typedef struct { unsigned char r,g,b; } rgb_color;
-typedef struct { unsigned char r,g,b,a; } rgba_color;
-typedef struct { unsigned char c,m,y,k; } cmyk_color;
+/* used for palette indices in some internal code (which might be 
+   exposed at some point
+*/
+typedef unsigned char i_palidx;
+
+/* We handle 2 types of sample, this is hopefully the most common, and the
+   smaller of the ones we support */
+typedef unsigned char i_sample_t;
+
+typedef struct { i_sample_t gray_color; } gray_color;
+typedef struct { i_sample_t r,g,b; } rgb_color;
+typedef struct { i_sample_t r,g,b,a; } rgba_color;
+typedef struct { i_sample_t c,m,y,k; } cmyk_color;
 
 typedef int undef_int; /* special value to put in typemaps to retun undef on 0 and 1 on 1 */
 
@@ -17,32 +26,129 @@ typedef union {
   rgb_color rgb;
   rgba_color rgba;
   cmyk_color cmyk;
-  unsigned char channel[MAXCHANNELS];
+  i_sample_t channel[MAXCHANNELS];
   unsigned int ui;
 } i_color;
 
+/* this is the larger sample type, it should be able to accurately represent
+   any sample size we use */
+typedef double i_fsample_t;
 
-struct _i_img {
+typedef struct { i_fsample_t gray_color; } i_fgray_color_t;
+typedef struct { i_fsample_t r, g, b; } i_frgb_color_t;
+typedef struct { i_fsample_t r, g, b, a; } i_frgba_color_t;
+typedef struct { i_fsample_t c, m, y, k; } i_fcmyk_color_t;
+
+typedef union {
+  i_fgray_color_t gray;
+  i_frgb_color_t rgb;
+  i_frgba_color_t rgba;
+  i_fcmyk_color_t cmyk;
+  i_fsample_t channel[MAXCHANNELS];
+} i_fcolor;
+
+typedef enum {
+  i_direct_type, /* direct colour, keeps RGB values per pixel */
+  i_palette_type, /* keeps a palette index per pixel */
+} i_img_type_t;
+
+typedef enum { 
+  /* bits per sample, not per pixel */
+  /* a paletted image might have one bit per sample */
+  i_8_bits = 8,
+  i_16_bits = 16,
+  i_double_bits = 64
+} i_img_bits_t;
+
+typedef struct {
+  char *name; /* name of a given tag, might be NULL */
+  int code; /* number of a given tag, -1 if it has no meaning */
+  char *data; /* value of a given tag if it's not an int, may be NULL */
+  int size; /* size of the data */
+  int idata; /* value of a given tag if data is NULL */
+} i_img_tag;
+
+typedef struct {
+  int count; /* how many tags have been set */
+  int alloc; /* how many tags have been allocated for */
+  i_img_tag *tags;
+} i_img_tags;
+
+typedef struct i_img_ i_img;
+typedef int (*i_f_ppix_t)(i_img *im, int x, int y, i_color *pix);
+typedef int (*i_f_ppixf_t)(i_img *im, int x, int y, i_fcolor *pix);
+typedef int (*i_f_plin_t)(i_img *im, int x, int r, int y, i_color *vals);
+typedef int (*i_f_plinf_t)(i_img *im, int x, int r, int y, i_fcolor *vals);
+typedef int (*i_f_gpix_t)(i_img *im, int x, int y, i_color *pix);
+typedef int (*i_f_gpixf_t)(i_img *im, int x, int y, i_fcolor *pix);
+typedef int (*i_f_glin_t)(i_img *im, int x, int r, int y, i_color *vals);
+typedef int (*i_f_glinf_t)(i_img *im, int x, int r, int y, i_fcolor *vals);
+
+typedef int (*i_f_gsamp_t)(i_img *im, int x, int r, int y, i_sample_t *samp,
+                           int *chans, int chan_count);
+typedef int (*i_f_gsampf_t)(i_img *im, int x, int r, int y, i_fsample_t *samp,
+                            int *chan, int chan_count);
+
+typedef int (*i_f_gpal_t)(i_img *im, int x, int r, int y, i_palidx *vals);
+typedef int (*i_f_ppal_t)(i_img *im, int x, int r, int y, i_palidx *vals);
+typedef int (*i_f_addcolors_t)(i_img *im, i_color *colors, int count);
+typedef int (*i_f_getcolors_t)(i_img *im, int i, i_color *, int count);
+typedef int (*i_f_colorcount_t)(i_img *im);
+typedef int (*i_f_maxcolors_t)(i_img *im);
+typedef int (*i_f_findcolor_t)(i_img *im, i_color *color, i_palidx *entry);
+typedef int (*i_f_setcolors_t)(i_img *im, int index, i_color *colors, 
+                              int count);
+
+typedef void (*i_f_destroy_t)(i_img *im);
+
+struct i_img_ {
   int channels;
   int xsize,ysize,bytes;
-  unsigned char *data;
   unsigned int ch_mask;
+  i_img_bits_t bits;
+  i_img_type_t type;
+  int virtual; /* image might not keep any data, must use functions */
+  unsigned char *idata; /* renamed to force inspection of existing code */
+                        /* can be NULL if virtual is non-zero */
+  i_img_tags tags;
 
-  int (*i_f_ppix) (struct _i_img *,int,int,i_color *); 
-  int (*i_f_gpix) (struct _i_img *,int,int,i_color *);
-  int (*i_f_plin) (struct _i_img *,int l, int r, int y, i_color *);
-  int (*i_f_glin) (struct _i_img *,int l, int r, int y, i_color *);
   void *ext_data;
-};
 
-typedef struct _i_img i_img;
+  /* interface functions */
+  i_f_ppix_t i_f_ppix;
+  i_f_ppixf_t i_f_ppixf;
+  i_f_plin_t i_f_plin;
+  i_f_plinf_t i_f_plinf;
+  i_f_gpix_t i_f_gpix;
+  i_f_gpixf_t i_f_gpixf;
+  i_f_glin_t i_f_glin;
+  i_f_glinf_t i_f_glinf;
+  i_f_gsamp_t i_f_gsamp;
+  i_f_gsampf_t i_f_gsampf;
+  
+  /* only valid for type == i_palette_type */
+  i_f_gpal_t i_f_gpal;
+  i_f_ppal_t i_f_ppal;
+  i_f_addcolors_t i_f_addcolors;
+  i_f_getcolors_t i_f_getcolors;
+  i_f_colorcount_t i_f_colorcount;
+  i_f_maxcolors_t i_f_maxcolors;
+  i_f_findcolor_t i_f_findcolor;
+  i_f_setcolors_t i_f_setcolors;
+
+  i_f_destroy_t i_f_destroy;
+};
 
-/* used for palette indices in some internal code (which might be 
-   exposed at some point
-*/
-typedef unsigned char i_palidx;
+/* ext_data for paletted images
+ */
+typedef struct {
+  int count; /* amount of space used in palette (in entries) */
+  int alloc; /* amount of space allocated for palette (in entries) */
+  i_color *pal;
+  int last_found;
+} i_img_pal_ext;
 
-/* Helper datatypes
+/* Helper datatypes
   The types in here so far are:
 
   doubly linked bucket list - pretty efficient
@@ -114,7 +220,7 @@ struct octt {
   int cnt;
 };
 
-struct octt *octt_new();
+struct octt *octt_new(void);
 int octt_add(struct octt *ct,unsigned char r,unsigned char g,unsigned char b);
 void octt_dump(struct octt *ct);
 void octt_count(struct octt *ct,int *tot,int max,int *overflow);
index aa69660..b047a88 100644 (file)
@@ -9,6 +9,8 @@
   Varying Bits/Sample
   Paletted Images
   Performance
+  Robustness
+  XS Changes
 
 =head1 DESCRIPTION
 
@@ -193,7 +195,7 @@ The basic interface would include:
     i_palette_type, /* keeps a palette index per pixel */
   } i_img_types;
 
-  /* interface functions 
+  /* interface functions */
   typedef int (*i_f_gpal_t)(i_img *im, int x, int r, int y, i_palidx *vals);
   typedef int (*i_f_ppal_t)(i_img *im, int x, int r, int y, i_palidx *vals);
   typedef int (*i_f_addcolor_t)(i_img *im, i_color *);
@@ -269,6 +271,12 @@ We might want to add functions to set/retrieve the whole palette at
 once, though setting the whole palette at once would make existing
 image data fairly useless.
 
+=head1 XS CHANGES
+
+I had been considering moving the i_img object from being an
+Imager::ImgRef object to being an Imager object.  But I don't see much
+point to it, so I'll leave it the way it is now.
+
 =head1 AUTHOR
 
 Tony Cook <tony@develop-help.com>
@@ -277,5 +285,6 @@ Tony Cook <tony@develop-help.com>
 
 16May2001 - initially completed version, could use some polishing
 16May2001 - Added i_error stack to the image structure.
+24May2001 - Added XS Changes section (TC)
 
 =cut
diff --git a/error.c b/error.c
index e350d3c..4e6c062 100644 (file)
--- a/error.c
+++ b/error.c
@@ -190,7 +190,7 @@ void i_clear_error() {
 }
 
 /*
-=item i_push_error(char const *msg)
+=item i_push_error(int code, char const *msg)
 
 Called by an imager function to push an error message onto the stack.
 
diff --git a/feat.h b/feat.h
index a8d50b1..a935c62 100644 (file)
--- a/feat.h
+++ b/feat.h
@@ -18,6 +18,12 @@ static char *i_format_list[]={
 #endif
 #ifdef HAVE_LIBTT
   "tt",
+#endif
+#ifdef HAVE_WIN32
+  "w32",
+#endif
+#ifdef HAVE_FT2
+  "ft2",
 #endif
   "raw",
   "pnm",
diff --git a/font.c b/font.c
index 5dfb3cf..c836013 100644 (file)
--- a/font.c
+++ b/font.c
@@ -77,6 +77,11 @@ i_init_fonts() {
   init_tt();
 #endif
 
+#ifdef HAVE_FT2
+  if (!i_ft2_init())
+    return 0;
+#endif
+
   return(1); /* FIXME: Always true - check the return values of the init_t1 and init_tt functions */
 }
 
@@ -119,7 +124,7 @@ Shuts the t1lib font rendering engine down.
 */
 
 void
-i_close_t1() {
+i_close_t1(void) {
   T1_CloseLib();
 }
 
@@ -235,6 +240,8 @@ i_t1_cp(i_img *im,int xb,int yb,int channel,int fontnum,float points,char* str,i
   if (im == NULL) { mm_log((1,"i_t1_cp: Null image in input\n")); return(0); }
 
   glyph=T1_AASetString( fontnum, str, len, 0, T1_KERNING, points, NULL);
+  if (glyph == NULL)
+    return 0;
 
   mm_log((1,"metrics: ascent: %d descent: %d\n",glyph->metrics.ascent,glyph->metrics.descent));
   mm_log((1," leftSideBearing: %d rightSideBearing: %d\n",glyph->metrics.leftSideBearing,glyph->metrics.rightSideBearing));
@@ -334,6 +341,8 @@ i_t1_text(i_img *im,int xb,int yb,i_color *cl,int fontnum,float points,char* str
   if (im == NULL) { mm_log((1,"i_t1_cp: Null image in input\n")); return(0); }
 
   glyph=T1_AASetString( fontnum, str, len, 0, T1_KERNING, points, NULL);
+  if (glyph == NULL)
+    return 0;
 
   mm_log((1,"metrics:  ascent: %d descent: %d\n",glyph->metrics.ascent,glyph->metrics.descent));
   mm_log((1," leftSideBearing: %d rightSideBearing: %d\n",glyph->metrics.leftSideBearing,glyph->metrics.rightSideBearing));
@@ -1115,18 +1124,31 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
   for ( i = 0; i < len; ++i ) {
     j = ustr[i];
     if ( i_tt_get_glyph(handle,inst,j) ) {
-      width += handle->instanceh[inst].gmetrics[j].advance   / 64;
-      casc   = handle->instanceh[inst].gmetrics[j].bbox.yMax / 64;
-      cdesc  = handle->instanceh[inst].gmetrics[j].bbox.yMin / 64;
+      TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + j;
+      width += gm->advance   / 64;
+      casc   = gm->bbox.yMax / 64;
+      cdesc  = gm->bbox.yMin / 64;
 
       mm_log((1, "i_tt_box_inst: glyph='%c' casc=%d cdesc=%d\n", j, casc, cdesc));
 
       if (first) {
-       start    = handle->instanceh[inst].gmetrics[j].bbox.xMin / 64;
-       ascent   = handle->instanceh[inst].gmetrics[j].bbox.yMax / 64;
-       descent  = handle->instanceh[inst].gmetrics[j].bbox.yMin / 64;
+       start    = gm->bbox.xMin / 64;
+       ascent   = gm->bbox.yMax / 64;
+       descent  = gm->bbox.yMin / 64;
        first = 0;
       }
+      if (i == len-1) {
+       /* the right-side bearing - in case the right-side of a 
+          character goes past the right of the advance width,
+          as is common for italic fonts
+       */
+       int rightb = gm->advance - gm->bearingX 
+         - (gm->bbox.xMax - gm->bbox.xMin);
+       /* fprintf(stderr, "font info last: %d %d %d %d\n", 
+          gm->bbox.xMax, gm->bbox.xMin, gm->advance, rightb); */
+       if (rightb < 0)
+         width -= rightb/64;
+      }
 
       ascent  = (ascent  >  casc ?  ascent : casc );
       descent = (descent < cdesc ? descent : cdesc);
diff --git a/freetyp2.c b/freetyp2.c
new file mode 100644 (file)
index 0000000..0a2b96a
--- /dev/null
@@ -0,0 +1,871 @@
+/*
+=head1 NAME
+
+freetyp2.c - font support via the FreeType library version 2.
+
+=head1 SYNOPSIS
+
+  if (!i_ft2_init()) { error }
+  FT2_Fonthandle *font;
+  font = i_ft2_new(name, index);
+  if (!i_ft2_setdpi(font, xdpi, ydpi)) { error }
+  if (!i_ft2_getdpi(font, &xdpi, &ydpi)) { error }
+  double matrix[6];
+  if (!i_ft2_settransform(font, matrix)) { error }
+  int bbox[6];
+  if (!i_ft2_bbox(font, cheight, cwidth, text, length, bbox)) { error }
+  i_img *im = ...;
+  i_color cl;
+  if (!i_ft2_text(font, im, tx, ty, cl, cheight, cwidth, text, length, align,
+                  aa)) { error }
+  if (!i_ft2_cp(font, im, tx, ty, channel, cheight, cwidth, text, length,
+                align, aa)) { error }
+  i_ft2_destroy(font);
+
+=head1 DESCRIPTION
+
+Implements Imager font support using the FreeType2 library.
+
+The FreeType2 library understands several font file types, including
+Truetype, Type1 and Windows FNT.
+
+=over 
+
+=cut
+*/
+
+#include "image.h"
+#include <stdio.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+static void ft2_push_message(int code);
+static unsigned long utf8_advance(char **p, int *len);
+
+static FT_Library library;
+
+/*
+=item i_ft2_init(void)
+
+Initializes the Freetype 2 library.
+
+Returns true on success, false on failure.
+
+=cut
+*/
+int
+i_ft2_init(void) {
+  FT_Error error;
+
+  i_clear_error();
+  error = FT_Init_FreeType(&library);
+  if (error) {
+    ft2_push_message(error);
+    i_push_error(0, "Initializing Freetype2");
+    return 0;
+  }
+  return 1;
+}
+
+struct FT2_Fonthandle {
+  FT_Face face;
+  int xdpi, ydpi;
+  int hint;
+
+  /* used to adjust so we can align the draw point to the top-left */
+  double matrix[6];
+};
+
+/*
+=item i_ft2_new(char *name, int index)
+
+Creates a new font object, from the file given by I<name>.  I<index>
+is the index of the font in a file with multiple fonts, where 0 is the
+first font.
+
+Return NULL on failure.
+
+=cut
+*/
+
+FT2_Fonthandle *
+i_ft2_new(char *name, int index) {
+  FT_Error error;
+  FT2_Fonthandle *result;
+  FT_Face face;
+  double matrix[6] = { 1, 0, 0,
+                       0, 1, 0 };
+
+  i_clear_error();
+  error = FT_New_Face(library, name, index, &face);
+  if (error) {
+    ft2_push_message(error);
+    i_push_error(error, "Opening face");
+    return NULL;
+  }
+
+  result = mymalloc(sizeof(FT2_Fonthandle));
+  result->face = face;
+  result->xdpi = result->ydpi = 72;
+
+  /* by default we disable hinting on a call to i_ft2_settransform()
+     if we don't do this, then the hinting can the untransformed text
+     to be a different size to the transformed text.
+     Obviously we have it initially enabled.
+  */
+  result->hint = 1; 
+
+  /* I originally forgot this:   :/ */
+  /*i_ft2_settransform(result, matrix); */
+  result->matrix[0] = 1; result->matrix[1] = 0; result->matrix[2] = 0;
+  result->matrix[3] = 0; result->matrix[4] = 1; result->matrix[5] = 0;
+
+  return result;
+}
+
+/*
+=item i_ft2_destroy(FT2_Fonthandle *handle)
+
+Destroys a font object, which must have been the return value of
+i_ft2_new().
+
+=cut
+*/
+void
+i_ft2_destroy(FT2_Fonthandle *handle) {
+  FT_Done_Face(handle->face);
+  myfree(handle);
+}
+
+/*
+=item i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi)
+
+Sets the resolution in dots per inch at which point sizes scaled, by
+default xdpi and ydpi are 72, so that 1 point maps to 1 pixel.
+
+Both xdpi and ydpi should be positive.
+
+Return true on success.
+
+=cut
+*/
+int
+i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi) {
+  i_clear_error();
+  if (xdpi > 0 && ydpi > 0) {
+    handle->xdpi = xdpi;
+    handle->ydpi = ydpi;
+    return 0;
+  }
+  else {
+    i_push_error(0, "resolutions must be positive");
+    return 0;
+  }
+}
+
+/*
+=item i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi)
+
+Retrieves the current horizontal and vertical resolutions at which
+point sizes are scaled.
+
+=cut
+*/
+int
+i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi) {
+  *xdpi = handle->xdpi;
+  *ydpi = handle->ydpi;
+
+  return 1;
+}
+
+/*
+=item i_ft2_settransform(FT2_FontHandle *handle, double *matrix)
+
+Sets a transormation matrix for output.
+
+This should be a 2 x 3 matrix like:
+
+ matrix[0]   matrix[1]   matrix[2]
+ matrix[3]   matrix[4]   matrix[5]
+
+=cut
+*/
+int
+i_ft2_settransform(FT2_Fonthandle *handle, double *matrix) {
+  FT_Matrix m;
+  FT_Vector v;
+  int i;
+
+  m.xx = matrix[0] * 65536;
+  m.xy = matrix[1] * 65536;
+  v.x  = matrix[2]; /* this could be pels of 26.6 fixed - not sure */
+  m.yx = matrix[3] * 65536;
+  m.yy = matrix[4] * 65536;
+  v.y  = matrix[5]; /* see just above */
+
+  FT_Set_Transform(handle->face, &m, &v);
+
+  for (i = 0; i < 6; ++i)
+    handle->matrix[i] = matrix[i];
+  handle->hint = 0;
+
+  return 1;
+}
+
+/*
+=item i_ft2_sethinting(FT2_Fonthandle *handle, int hinting)
+
+If hinting is non-zero then glyph hinting is enabled, otherwise disabled.
+
+i_ft2_settransform() disables hinting to prevent distortions in
+gradual text transformations.
+
+=cut
+*/
+int i_ft2_sethinting(FT2_Fonthandle *handle, int hinting) {
+  handle->hint = hinting;
+  return 1;
+}
+
+/*
+=item i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, char *text, int len, int *bbox)
+
+Retrieves bounding box information for the font at the given 
+character width and height.  This ignores the transformation matrix.
+
+Returns non-zero on success.
+
+=cut
+*/
+int
+i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, 
+           char *text, int len, int *bbox) {
+  FT_Error error;
+  int width;
+  int index;
+  int first;
+  int ascent = 0, descent = 0;
+  int glyph_ascent, glyph_descent;
+  FT_Glyph_Metrics *gm;
+  int start = 0;
+
+  error = FT_Set_Char_Size(handle->face, cwidth*64, cheight*64, 
+                           handle->xdpi, handle->ydpi);
+  if (error) {
+    ft2_push_message(error);
+    i_push_error(0, "setting size");
+  }
+
+  first = 1;
+  width = 0;
+  while (len--) {
+    int c = (unsigned char)*text++;
+    
+    index = FT_Get_Char_Index(handle->face, c);
+    error = FT_Load_Glyph(handle->face, index, FT_LOAD_DEFAULT);
+    if (error) {
+      ft2_push_message(error);
+      i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)", 
+                    c, index);
+      return 0;
+    }
+    gm = &handle->face->glyph->metrics;
+    glyph_ascent = gm->horiBearingY / 64;
+    glyph_descent = glyph_ascent - gm->height/64;
+    if (first) {
+      start = gm->horiBearingX / 64;
+      /* handles -ve values properly */
+      ascent = glyph_ascent;
+      descent = glyph_descent;
+      first = 0;
+    }
+
+    if (glyph_ascent > ascent)
+      ascent = glyph_ascent;
+    if (glyph_descent > descent)
+      descent = glyph_descent;
+
+    width += gm->horiAdvance / 64;
+
+    if (len == 0) {
+      /* last character 
+       handle the case where the right the of the character overlaps the 
+       right*/
+      int rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
+      if (rightb < 0)
+        width -= rightb / 64;
+    }
+  }
+
+  bbox[0] = start;
+  bbox[1] = handle->face->size->metrics.ascender / 64;
+  bbox[2] = width + start;
+  bbox[3] = handle->face->size->metrics.descender / 64;
+  bbox[4] = descent;
+  bbox[5] = ascent;
+
+  return 1;
+}
+
+/*
+=item transform_box(FT2_FontHandle *handle, int bbox[4])
+
+bbox contains coorinates of a the top-left and bottom-right of a bounding 
+box relative to a point.
+
+This is then transformed and the values in bbox[4] are the top-left
+and bottom-right of the new bounding box.
+
+This is meant to provide the bounding box of a transformed character
+box.  The problem is that if the character was round and is rotated,
+the real bounding box isn't going to be much different from the
+original, but this function will return a _bigger_ bounding box.  I
+suppose I could work my way through the glyph outline, but that's
+too much hard work.
+
+=cut
+*/
+void ft2_transform_box(FT2_Fonthandle *handle, int bbox[4]) {
+  double work[8];
+  double *matrix = handle->matrix;
+  int i;
+  
+  work[0] = matrix[0] * bbox[0] + matrix[1] * bbox[1];
+  work[1] = matrix[3] * bbox[0] + matrix[4] * bbox[1];
+  work[2] = matrix[0] * bbox[2] + matrix[1] * bbox[1];
+  work[3] = matrix[3] * bbox[2] + matrix[4] * bbox[1];
+  work[4] = matrix[0] * bbox[0] + matrix[1] * bbox[3];
+  work[5] = matrix[3] * bbox[0] + matrix[4] * bbox[3];
+  work[6] = matrix[0] * bbox[2] + matrix[1] * bbox[3];
+  work[7] = matrix[3] * bbox[2] + matrix[4] * bbox[3];
+
+  bbox[0] = floor(min(min(work[0], work[2]),min(work[4], work[6])));
+  bbox[1] = floor(min(min(work[1], work[3]),min(work[5], work[7])));
+  bbox[2] = ceil(max(max(work[0], work[2]),max(work[4], work[6])));
+  bbox[3] = ceil(max(max(work[1], work[3]),max(work[5], work[7])));
+}
+
+/*
+=item expand_bounds(int bbox[4], int bbox2[4]) 
+
+Treating bbox[] and bbox2[] as 2 bounding boxes, produces a new
+bounding box in bbox[] that encloses both.
+
+=cut
+*/
+static void expand_bounds(int bbox[4], int bbox2[4]) {
+  bbox[0] = min(bbox[0], bbox2[0]);
+  bbox[1] = min(bbox[1], bbox2[1]);
+  bbox[2] = max(bbox[2], bbox2[2]);
+  bbox[3] = max(bbox[3], bbox2[3]);
+}
+
+/*
+=item i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth, char *text, int len, int vlayout, int utf8, int *bbox)
+
+Retrieves bounding box information for the font at the given 
+character width and height.
+
+This version finds the rectangular bounding box of the glyphs, with
+the text as transformed by the transformation matrix.  As with
+i_ft2_bbox (bbox[0], bbox[1]) will the the offset from the start of
+the topline to the top-left of the bounding box.  Unlike i_ft2_bbox()
+this could be near the bottom left corner of the box.
+
+(bbox[4], bbox[5]) is the offset to the start of the baseline.
+(bbox[6], bbox[7]) is the offset from the start of the baseline to the
+end of the baseline.
+
+Returns non-zero on success.
+
+=cut
+*/
+int
+i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth, 
+           char *text, int len, int vlayout, int utf8, int *bbox) {
+  FT_Error error;
+  int width;
+  int index;
+  int first;
+  int ascent = 0, descent = 0;
+  int glyph_ascent, glyph_descent;
+  FT_Glyph_Metrics *gm;
+  int start = 0;
+  int work[4];
+  int bounds[4];
+  double x = 0, y = 0;
+  int i;
+  FT_GlyphSlot slot;
+  int advx, advy;
+  int loadFlags = FT_LOAD_DEFAULT;
+
+  if (vlayout)
+    loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
+
+  error = FT_Set_Char_Size(handle->face, cwidth*64, cheight*64, 
+                           handle->xdpi, handle->ydpi);
+  if (error) {
+    ft2_push_message(error);
+    i_push_error(0, "setting size");
+  }
+
+  first = 1;
+  width = 0;
+  while (len) {
+    unsigned long c;
+    if (utf8) {
+      c = utf8_advance(&text, &len);
+      if (c == ~0UL) {
+        i_push_error(0, "invalid UTF8 character");
+        return 0;
+      }
+    }
+    else {
+      c = (unsigned char)*text++;
+      --len;
+    }
+
+    index = FT_Get_Char_Index(handle->face, c);
+    error = FT_Load_Glyph(handle->face, index, loadFlags);
+    if (error) {
+      ft2_push_message(error);
+      i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)", 
+                    c, index);
+      return 0;
+    }
+    slot = handle->face->glyph; 
+    gm = &slot->metrics;
+
+    /* these probably don't mean much for vertical layouts */
+    glyph_ascent = gm->horiBearingY / 64;
+    glyph_descent = glyph_ascent - gm->height/64;
+    if (vlayout) {
+      work[0] = gm->vertBearingX;
+      work[1] = gm->vertBearingY;
+    }
+    else {
+      work[0] = gm->horiBearingX;
+      work[1] = gm->horiBearingY;
+    }
+    work[2] = gm->width  + work[0];
+    work[3] = work[1] - gm->height;
+    if (first) {
+      bbox[4] = work[0] * handle->matrix[0] + work[1] * handle->matrix[1] + handle->matrix[2];
+      bbox[5] = work[0] * handle->matrix[3] + work[1] * handle->matrix[4] + handle->matrix[5];
+      bbox[4] = bbox[4] < 0 ? -(-bbox[4] + 32)/64 : (bbox[4] + 32) / 64;
+      bbox[5] /= 64;
+    }
+    ft2_transform_box(handle, work);
+    for (i = 0; i < 4; ++i)
+      work[i] /= 64;
+    work[0] += x;
+    work[1] += y;
+    work[2] += x;
+    work[3] += y;
+    if (first) {
+      for (i = 0; i < 4; ++i)
+        bounds[i] = work[i];
+      ascent = glyph_ascent;
+      descent = glyph_descent;
+      first = 0;
+    }
+    else {
+      expand_bounds(bounds, work);
+    }
+    x += slot->advance.x / 64;
+    y += slot->advance.y / 64;
+    
+    if (glyph_ascent > ascent)
+      ascent = glyph_ascent;
+    if (glyph_descent > descent)
+      descent = glyph_descent;
+
+    if (len == 0) {
+      /* last character 
+       handle the case where the right the of the character overlaps the 
+       right*/
+      /*int rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
+      if (rightb < 0)
+      width -= rightb / 64;*/
+    }
+  }
+
+  /* at this point bounds contains the bounds relative to the CP,
+     and x, y hold the final position relative to the CP */
+  /*bounds[0] -= x;
+  bounds[1] -= y;
+  bounds[2] -= x;
+  bounds[3] -= y;*/
+
+  bbox[0] = bounds[0];
+  bbox[1] = -bounds[3];
+  bbox[2] = bounds[2];
+  bbox[3] = -bounds[1];
+  bbox[6] = x;
+  bbox[7] = -y;
+
+  return 1;
+}
+
+
+
+static int
+make_bmp_map(FT_Bitmap *bitmap, unsigned char *map);
+
+/*
+=item i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl, double cheight, double cwidth, char *text, int len, int align, int aa)
+
+Renders I<text> to (I<tx>, I<ty>) in I<im> using color I<cl> at the given 
+I<cheight> and I<cwidth>.
+
+If align is 0, then the text is rendered with the top-left of the
+first character at (I<tx>, I<ty>).  If align is non-zero then the text
+is rendered with (I<tx>, I<ty>) aligned with the base-line of the
+characters.
+
+If aa is non-zero then the text is anti-aliased.
+
+Returns non-zero on success.
+
+=cut
+*/
+int
+i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
+           double cheight, double cwidth, char *text, int len, int align,
+           int aa, int vlayout, int utf8) {
+  FT_Error error;
+  int index;
+  FT_Glyph_Metrics *gm;
+  int bbox[6];
+  FT_GlyphSlot slot;
+  int x, y;
+  unsigned char *bmp;
+  unsigned char map[256];
+  char last_mode = ft_pixel_mode_none; 
+  int last_grays = -1;
+  int ch;
+  i_color pel;
+  int loadFlags = FT_LOAD_DEFAULT;
+
+  if (vlayout) {
+    if (!FT_HAS_VERTICAL(handle->face)) {
+      i_push_error(0, "face has no vertical metrics");
+      return 0;
+    }
+    loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
+  }
+  if (!handle->hint)
+    loadFlags |= FT_LOAD_NO_HINTING;
+
+  /* set the base-line based on the string ascent */
+  if (!i_ft2_bbox(handle, cheight, cwidth, text, len, bbox))
+    return 0;
+
+  if (!align) {
+    /* this may need adjustment */
+    tx -= bbox[0] * handle->matrix[0] + bbox[5] * handle->matrix[1] + handle->matrix[2];
+    ty += bbox[0] * handle->matrix[3] + bbox[5] * handle->matrix[4] + handle->matrix[5];
+  }
+  while (len) {
+    unsigned long c;
+    if (utf8) {
+      c = utf8_advance(&text, &len);
+      if (c == ~0UL) {
+        i_push_error(0, "invalid UTF8 character");
+        return 0;
+      }
+    }
+    else {
+      c = (unsigned char)*text++;
+      --len;
+    }
+    
+    index = FT_Get_Char_Index(handle->face, c);
+    error = FT_Load_Glyph(handle->face, index, loadFlags);
+    if (error) {
+      ft2_push_message(error);
+      i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)", 
+                    c, index);
+      return 0;
+    }
+    slot = handle->face->glyph;
+    gm = &slot->metrics;
+
+    error = FT_Render_Glyph(slot, aa ? ft_render_mode_normal : ft_render_mode_mono);
+    if (error) {
+      ft2_push_message(error);
+      i_push_errorf(0, "rendering glyph 0x%04X (character \\x%02X)");
+      return 0;
+    }
+    if (slot->bitmap.pixel_mode == ft_pixel_mode_mono) {
+      bmp = slot->bitmap.buffer;
+      for (y = 0; y < slot->bitmap.rows; ++y) {
+        int pos = 0;
+        int bit = 0x80;
+        for (x = 0; x < slot->bitmap.width; ++x) {
+          if (bmp[pos] & bit)
+            i_ppix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, cl);
+
+          bit >>= 1;
+          if (bit == 0) {
+            bit = 0x80;
+            ++pos;
+          }
+        }
+        bmp += slot->bitmap.pitch;
+      }
+    }
+    else {
+      /* grey scale or something we can treat as greyscale */
+      /* we create a map to convert from the bitmap values to 0-255 */
+      if (last_mode != slot->bitmap.pixel_mode 
+          || last_grays != slot->bitmap.num_grays) {
+        if (!make_bmp_map(&slot->bitmap, map))
+          return 0;
+        last_mode = slot->bitmap.pixel_mode;
+        last_grays = slot->bitmap.num_grays;
+      }
+      
+      bmp = slot->bitmap.buffer;
+      for (y = 0; y < slot->bitmap.rows; ++y) {
+        for (x = 0; x < slot->bitmap.width; ++x) {
+          int value = map[bmp[x]];
+          if (value) {
+            i_gpix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel);
+            for (ch = 0; ch < im->channels; ++ch) {
+              pel.channel[ch] = 
+                ((255-value)*pel.channel[ch] + value * cl->channel[ch]) / 255;
+            }
+            i_ppix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel);
+          }
+        }
+        bmp += slot->bitmap.pitch;
+      }
+    }
+
+    tx += slot->advance.x / 64;
+    ty -= slot->advance.y / 64;
+  }
+
+  return 1;
+}
+
+/*
+=item i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel, double cheight, double cwidth, char *text, int len, int align, int aa)
+
+Renders I<text> to (I<tx>, I<ty>) in I<im> to I<channel> at the given 
+I<cheight> and I<cwidth>.
+
+If align is 0, then the text is rendered with the top-left of the
+first character at (I<tx>, I<ty>).  If align is non-zero then the text
+is rendered with (I<tx>, I<ty>) aligned with the base-line of the
+characters.
+
+If aa is non-zero then the text is anti-aliased.
+
+Returns non-zero on success.
+
+=cut
+*/
+
+i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel,
+         double cheight, double cwidth, char *text, int len, int align,
+         int aa, int vlayout, int utf8) {
+  int bbox[8];
+  i_img *work;
+  i_color cl, cl2;
+  int x, y;
+
+  if (vlayout && !FT_HAS_VERTICAL(handle->face)) {
+    i_push_error(0, "face has no vertical metrics");
+    return 0;
+  }
+
+  if (!i_ft2_bbox_r(handle, cheight, cwidth, text, len, vlayout, utf8, bbox))
+    return 0;
+
+  work = i_img_empty_ch(NULL, bbox[2]-bbox[0]+1, bbox[3]-bbox[1]+1, 1);
+  cl.channel[0] = 255;
+  if (!i_ft2_text(handle, work, -bbox[0], -bbox[1], &cl, cheight, cwidth, 
+                  text, len, 1, aa, vlayout, utf8))
+    return 0;
+
+  if (!align) {
+    tx -= bbox[4];
+    ty += bbox[5];
+  }
+  
+  /* render to the specified channel */
+  /* this will be sped up ... */
+  for (y = 0; y < work->ysize; ++y) {
+    for (x = 0; x < work->xsize; ++x) {
+      i_gpix(work, x, y, &cl);
+      i_gpix(im, tx + x + bbox[0], ty + y + bbox[1], &cl2);
+      cl2.channel[channel] = cl.channel[0];
+      i_ppix(im, tx + x + bbox[0], ty + y + bbox[1], &cl2);
+    }
+  }
+
+  return 1;
+}
+
+/* uses a method described in fterrors.h to build an error translation
+   function
+*/
+#undef __FT_ERRORS_H__
+#define FT_ERRORDEF(e, v, s) case v: i_push_error(code, s); return;
+#define FT_ERROR_START_LIST
+#define FT_ERROR_END_LIST
+
+/*
+=back
+
+=head2 Internal Functions
+
+These functions are used in the implementation of freetyp2.c and should not
+(usually cannot) be called from outside it.
+
+=over
+
+=item ft2_push_message(int code)
+
+Pushes an error message corresponding to code onto the error stack.
+
+=cut
+*/
+static void ft2_push_message(int code) {
+  char unknown[40];
+
+  switch (code) {
+#include FT_ERRORS_H
+  }
+
+  sprintf(unknown, "Unknown Freetype2 error code 0x%04X\n", code);
+  i_push_error(code, unknown);
+}
+
+/*
+=item make_bmp_map(FT_Bitmap *bitmap, unsigned char *map)
+
+Creates a map to convert grey levels from the glyphs bitmap into
+values scaled 0..255.
+
+=cut
+*/
+static int
+make_bmp_map(FT_Bitmap *bitmap, unsigned char *map) {
+  int scale;
+  int i;
+
+  switch (bitmap->pixel_mode) {
+  case ft_pixel_mode_grays:
+    scale = bitmap->num_grays;
+    break;
+    
+  default:
+    i_push_errorf(0, "I can't handle pixel mode %d", bitmap->pixel_mode);
+    return 0;
+  }
+
+  /* build the table */
+  for (i = 0; i < scale; ++i)
+    map[i] = i * 255 / (bitmap->num_grays - 1);
+
+  return 1;
+}
+
+struct utf8_size {
+  int mask, expect;
+  int size;
+};
+
+struct utf8_size utf8_sizes[] =
+{
+  { 0x80, 0x00, 1 },
+  { 0xE0, 0xC0, 2 },
+  { 0xF0, 0xE0, 3 },
+  { 0xF8, 0xF0, 4 },
+};
+
+/*
+=item utf8_advance(char **p, int *len)
+
+Retreive a UTF8 character from the stream.
+
+Modifies *p and *len to indicate the consumed characters.
+
+This doesn't support the extended UTF8 encoding used by later versions
+of Perl.
+
+=cut
+*/
+
+unsigned long utf8_advance(char **p, int *len) {
+  unsigned char c;
+  int i, ci, clen = 0;
+  unsigned char codes[3];
+  if (*len == 0)
+    return ~0UL;
+  c = *(*p)++; --*len;
+
+  for (i = 0; i < sizeof(utf8_sizes)/sizeof(*utf8_sizes); ++i) {
+    if ((c & utf8_sizes[i].mask) == utf8_sizes[i].expect) {
+      clen = utf8_sizes[i].size;
+    }
+  }
+  if (clen == 0 || *len < clen-1) {
+    --*p; ++*len;
+    return ~0UL;
+  }
+
+  /* check that each character is well formed */
+  i = 1;
+  ci = 0;
+  while (i < clen) {
+    if (((*p)[ci] & 0xC0) != 0x80) {
+      --*p; ++*len;
+      return ~0UL;
+    }
+    codes[ci] = (*p)[ci];
+    ++ci; ++i;
+  }
+  *p += clen-1; *len -= clen-1;
+  if (c & 0x80) {
+    if ((c & 0xE0) == 0xC0) {
+      return ((c & 0x1F) << 6) + (codes[0] & 0x3F);
+    }
+    else if ((c & 0xF0) == 0xE0) {
+      return ((c & 0x0F) << 12) | ((codes[0] & 0x3F) << 6) | (codes[1] & 0x3f);
+    }
+    else if ((c & 0xF8) == 0xF0) {
+      return ((c & 0x07) << 18) | ((codes[0] & 0x3F) << 12) 
+              | ((codes[1] & 0x3F) << 6) | (codes[2] & 0x3F);
+    }
+    else {
+      *p -= clen; *len += clen;
+      return ~0UL;
+    }
+  }
+  else {
+    return c;
+  }
+}
+
+/*
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>, with a fair amount of help from
+reading the code in font.c.
+
+=head1 SEE ALSO
+
+font.c, Imager::Font(3), Imager(3)
+
+http://www.freetype.org/
+
+=cut
+*/
+
diff --git a/gif.c b/gif.c
index 8a14226..5d82b41 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -53,10 +53,12 @@ functionality with giflib3.
 */
 
 static char const *gif_error_msg(int code);
-static void gif_push_error();
+static void gif_push_error(void);
 
 #if IM_GIFMAJOR >= 4
 
+static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length);
+
 /*
 =item gif_scalar_info
 
@@ -374,6 +376,447 @@ i_readgif(int fd, int **colour_table, int *colours) {
   return i_readgif_low(GifFile, colour_table, colours);
 }
 
+/*
+
+Internal function called by i_readgif_multi_low() in error handling
+
+*/
+static void free_images(i_img **imgs, int count) {
+  int i;
+  for (i = 0; i < count; ++i)
+    i_img_destroy(imgs[i]);
+  myfree(imgs);
+}
+
+/*
+=item i_readgif_multi_low(GifFileType *gf, int *count)
+
+Reads one of more gif images from the given GIF file.
+
+Returns a pointer to an array of i_img *, and puts the count into 
+*count.
+
+Unlike the normal i_readgif*() functions the images are paletted
+images rather than a combined RGB image.
+
+This functions sets tags on the images returned:
+
+=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.
+
+=cut
+*/
+
+i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
+  i_img *img;
+  int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
+  int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
+  ColorMapObject *ColorMap;
+  GifRecordType RecordType;
+  GifByteType *Extension;
+  
+  GifRowType GifRow;
+  int got_gce = 0;
+  int trans_index; /* transparent index if we see a GCE */
+  int gif_delay; /* delay from a GCE */
+  int user_input; /* user input flag from a GCE */
+  int disposal; /* disposal method from a GCE */
+  int got_ns_loop = 0;
+  int ns_loop;
+  char *comment = NULL; /* a comment */
+  i_img **results = NULL;
+  int result_alloc = 0;
+  int channels;
+  
+  *count = 0;
+
+  mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
+
+  BackGround = GifFile->SBackGroundColor;
+
+  Size = GifFile->SWidth * sizeof(GifPixelType);
+  
+  if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
+    m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
+
+  /* Scan the content of the GIF file and load the image(s) in: */
+  do {
+    if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "Unable to get record type");
+      free_images(results, *count);
+      DGifCloseFile(GifFile);
+      return NULL;
+    }
+    
+    switch (RecordType) {
+    case IMAGE_DESC_RECORD_TYPE:
+      if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Unable to get image descriptor");
+        free_images(results, *count);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+
+      if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
+       mm_log((1, "Adding local colormap\n"));
+       ColorMapSize = ColorMap->ColorCount;
+      } else {
+       /* No colormap and we are about to read in the image - 
+           abandon for now */
+       mm_log((1, "Going in with no colormap\n"));
+       i_push_error(0, "Image does not have a local or a global color map");
+        free_images(results, *count);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      
+      Width = GifFile->Image.Width;
+      Height = GifFile->Image.Height;
+      channels = 3;
+      if (got_gce && trans_index >= 0)
+        channels = 4;
+      img = i_img_pal_new(Width, Height, channels, 256);
+      /* populate the palette of the new image */
+      for (i = 0; i < ColorMapSize; ++i) {
+        i_color col;
+        col.rgba.r = ColorMap->Colors[i].Red;
+        col.rgba.g = ColorMap->Colors[i].Green;
+        col.rgba.b = ColorMap->Colors[i].Blue;
+        if (channels == 4 && trans_index == i)
+          col.rgba.a = 0;
+        else
+          col.rgba.a = 255;
+       
+        i_addcolors(img, &col, 1);
+      }
+      ++*count;
+      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] = img;
+      i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
+      /**(char *)0 = 1;*/
+      i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
+      i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
+      i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
+      i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
+      if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
+        i_tags_addn(&img->tags, "gif_background", 0, 
+                    GifFile->SBackGroundColor);
+      }
+      if (GifFile->Image.ColorMap) {
+        i_tags_addn(&img->tags, "gif_localmap", 0, 1);
+      }
+      
+      if (got_gce) {
+        if (trans_index >= 0)
+          i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
+        i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
+        i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
+        i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
+      }
+      got_gce = 0;
+      if (got_ns_loop)
+        i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
+      if (comment) {
+        i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
+        myfree(comment);
+        comment = NULL;
+      }
+
+      ImageNum++;
+      mm_log((1,"i_readgif_multi: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
+
+      if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
+         GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
+       i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
+       free_images(results, *count);        
+       DGifCloseFile(GifFile);
+       return(0);
+      }
+      if (GifFile->Image.Interlace) {
+       for (Count = i = 0; i < 4; i++) {
+          for (j = InterlacedOffset[i]; j < Height; 
+               j += InterlacedJumps[i]) {
+            Count++;
+            if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+              gif_push_error();
+              i_push_error(0, "Reading GIF line");
+              free_images(results, *count);
+              DGifCloseFile(GifFile);
+              return NULL;
+            }
+            
+            i_ppal(img, 0, Width, j, GifRow);
+          }
+       }
+      }
+      else {
+       for (i = 0; i < Height; i++) {
+         if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+           gif_push_error();
+           i_push_error(0, "Reading GIF line");
+            free_images(results, *count);
+           DGifCloseFile(GifFile);
+           return NULL;
+         }
+
+          i_ppal(img, 0, Width, i, GifRow);
+       }
+      }
+      break;
+    case EXTENSION_RECORD_TYPE:
+      /* Skip any extension blocks in file: */
+      if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Reading extension record");
+        free_images(results, *count);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      if (ExtCode == 0xF9) {
+        got_gce = 1;
+        if (Extension[1] & 1)
+          trans_index = Extension[4];
+        else
+          trans_index = -1;
+        gif_delay = Extension[2] + 256 * Extension[3];
+        user_input = (Extension[0] & 2) != 0;
+        disposal = (Extension[0] >> 2) & 3;
+      }
+      if (ExtCode == 0xFF && *Extension == 11) {
+        if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
+          if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
+            gif_push_error();
+            i_push_error(0, "reading loop extension");
+            free_images(results, *count);
+            DGifCloseFile(GifFile);
+            return NULL;
+          }
+          if (Extension && *Extension == 3) {
+            got_ns_loop = 1;
+            ns_loop = Extension[2] + 256 * Extension[3];
+          }
+        }
+      }
+      else if (ExtCode == 0xFE) {
+        /* while it's possible for a GIF file to contain more than one
+           comment, I'm only implementing a single comment per image, 
+           with the comment saved into the following image.
+           If someone wants more than that they can implement it.
+           I also don't handle comments that take more than one block.
+        */
+        if (!comment) {
+          comment = mymalloc(*Extension+1);
+          memcpy(comment, Extension+1, *Extension);
+          comment[*Extension] = '\0';
+        }
+      }
+      while (Extension != NULL) {
+       if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
+         gif_push_error();
+         i_push_error(0, "reading next block of extension");
+          free_images(results, *count);
+         DGifCloseFile(GifFile);
+         return NULL;
+       }
+      }
+      break;
+    case TERMINATE_RECORD_TYPE:
+      break;
+    default:               /* Should be trapped by DGifGetRecordType. */
+      break;
+    }
+  } while (RecordType != TERMINATE_RECORD_TYPE);
+
+  if (comment) {
+    if (*count) {
+      i_tags_add(&(results[*count-1]->tags), "gif_comment", 0, comment, 
+                 strlen(comment), 0);
+    }
+    myfree(comment);
+  }
+  
+  myfree(GifRow);
+  
+  if (DGifCloseFile(GifFile) == GIF_ERROR) {
+    gif_push_error();
+    i_push_error(0, "Closing GIF file object");
+    free_images(results, *count);
+    return NULL;
+  }
+
+  return results;
+}
+
+/*
+=item i_readgif_multi(int fd, int *count)
+
+=cut
+*/
+i_img **
+i_readgif_multi(int fd, int *count) {
+  GifFileType *GifFile;
+
+  i_clear_error();
+  
+  mm_log((1,"i_readgif_multi(fd %d, &count %p)\n", fd, count));
+
+  if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib file object");
+    mm_log((1,"i_readgif: Unable to open file\n"));
+    return NULL;
+  }
+
+  return i_readgif_multi_low(GifFile, count);
+}
+
+/*
+=item i_readgif_multi_scalar(char *data, int length, int *count)
+
+=cut
+*/
+i_img **
+i_readgif_multi_scalar(char *data, int length, int *count) {
+#if IM_GIFMAJOR >= 4
+  GifFileType *GifFile;
+  struct gif_scalar_info gsi;
+
+  i_clear_error();
+  
+  gsi.cpos=0;
+  gsi.length=length;
+  gsi.data=data;
+
+  mm_log((1,"i_readgif_multi_scalar(data %p, length %d, &count %p)\n", 
+          data, length, count));
+
+  if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
+    mm_log((1,"i_readgif_multi_scalar: Unable to open scalar datasource.\n"));
+    return NULL;
+  }
+
+  return i_readgif_multi_low(GifFile, count);
+#else
+  return NULL;
+#endif
+}
+
+/*
+=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
+
+Read a GIF file into an Imager RGB file, the data of the GIF file is
+retreived by callin the user supplied callback function.
+
+This function is only used with giflib 4 and higher.
+
+=cut
+*/
+
+i_img**
+i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
+#if IM_GIFMAJOR >= 4
+  GifFileType *GifFile;
+  i_img **result;
+
+  i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
+
+  i_clear_error();
+  
+  mm_log((1,"i_readgif_multi_callback(callback %p, userdata %p, count %p)\n", cb, userdata, count));
+  if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
+    mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
+    myfree(gci);
+    return NULL;
+  }
+
+  result = i_readgif_multi_low(GifFile, count);
+  free_gen_read_data(gci);
+
+  return result;
+#else
+  return NULL;
+#endif
+}
+
 /*
 =item i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
 
@@ -1243,7 +1686,7 @@ an error message and pushes it on the error stack.
 =cut
 */
 
-static void gif_push_error() {
+static void gif_push_error(void) {
   int code = GifLastError(); /* clears saved error */
 
   i_push_error(code, gif_error_msg(code));
diff --git a/image.c b/image.c
index e8833ea..8486a21 100644 (file)
--- a/image.c
+++ b/image.c
@@ -1,4 +1,5 @@
 #include "image.h"
+#include "imagei.h"
 #include "io.h"
 
 /*
@@ -37,7 +38,18 @@ Some of these functions are internal.
 #define minmax(a,b,i) ( ((a>=i)?a: ( (b<=i)?b:i   )) )
 
 /* Hack around an obscure linker bug on solaris - probably due to builtin gcc thingies */
-void fake() { ceil(1); }
+void fake(void) { ceil(1); }
+
+static int i_ppix_d(i_img *im, int x, int y, i_color *val);
+static int i_gpix_d(i_img *im, int x, int y, i_color *val);
+static int i_glin_d(i_img *im, int l, int r, int y, i_color *vals);
+static int i_plin_d(i_img *im, int l, int r, int y, i_color *vals);
+static int i_ppixf_d(i_img *im, int x, int y, i_fcolor *val);
+static int i_gpixf_d(i_img *im, int x, int y, i_fcolor *val);
+static int i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_plinf_d(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count);
+static int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count);
 
 /* 
 =item ICL_new_internal(r, g, b, a)
@@ -150,6 +162,94 @@ ICL_DESTROY(i_color *cl) {
   myfree(cl);
 }
 
+/*
+=item i_fcolor_new(double r, double g, double b, double a)
+
+=cut
+*/
+i_fcolor *i_fcolor_new(double r, double g, double b, double a) {
+  i_fcolor *cl = NULL;
+
+  mm_log((1,"i_fcolor_new(r %g,g %g,b %g,a %g)\n", r, g, b, a));
+
+  if ( (cl=mymalloc(sizeof(i_fcolor))) == NULL) m_fatal(2,"malloc() error\n");
+  cl->rgba.r = r;
+  cl->rgba.g = g;
+  cl->rgba.b = b;
+  cl->rgba.a = a;
+  mm_log((1,"(%p) <- i_fcolor_new\n",cl));
+
+  return cl;
+}
+
+/*
+=item i_fcolor_destroy(i_fcolor *cl) 
+
+=cut
+*/
+void i_fcolor_destroy(i_fcolor *cl) {
+  myfree(cl);
+}
+
+/*
+=item IIM_base_8bit_direct (static)
+
+A static i_img object used to initialize direct 8-bit per sample images.
+
+=cut
+*/
+static i_img IIM_base_8bit_direct =
+{
+  0, /* channels set */
+  0, 0, 0, /* xsize, ysize, bytes */
+  ~0, /* ch_mask */
+  i_8_bits, /* bits */
+  i_direct_type, /* type */
+  0, /* virtual */
+  NULL, /* idata */
+  { 0, 0, NULL }, /* tags */
+  NULL, /* ext_data */
+
+  i_ppix_d, /* i_f_ppix */
+  i_ppixf_d, /* i_f_ppixf */
+  i_plin_d, /* i_f_plin */
+  i_plinf_d, /* i_f_plinf */
+  i_gpix_d, /* i_f_gpix */
+  i_gpixf_d, /* i_f_gpixf */
+  i_glin_d, /* i_f_glin */
+  i_glinf_d, /* i_f_glinf */
+  i_gsamp_d, /* i_f_gsamp */
+  i_gsampf_d, /* i_f_gsampf */
+
+  NULL, /* i_f_gpal */
+  NULL, /* i_f_ppal */
+  NULL, /* i_f_addcolors */
+  NULL, /* i_f_getcolors */
+  NULL, /* i_f_colorcount */
+  NULL, /* i_f_maxcolors */
+  NULL, /* i_f_findcolor */
+  NULL, /* i_f_setcolors */
+
+  NULL, /* i_f_destroy */
+};
+
+/*static void set_8bit_direct(i_img *im) {
+  im->i_f_ppix = i_ppix_d;
+  im->i_f_ppixf = i_ppixf_d;
+  im->i_f_plin = i_plin_d;
+  im->i_f_plinf = i_plinf_d;
+  im->i_f_gpix = i_gpix_d;
+  im->i_f_gpixf = i_gpixf_d;
+  im->i_f_glin = i_glin_d;
+  im->i_f_glinf = i_glinf_d;
+  im->i_f_gpal = NULL;
+  im->i_f_ppal = NULL;
+  im->i_f_addcolor = NULL;
+  im->i_f_getcolor = NULL;
+  im->i_f_colorcount = NULL;
+  im->i_f_findcolor = NULL;
+  }*/
+
 /*
 =item IIM_new(x, y, ch)
 
@@ -174,6 +274,7 @@ IIM_new(int x,int y,int ch) {
 void
 IIM_DESTROY(i_img *im) {
   mm_log((1,"IIM_DESTROY(im* %p)\n",im));
+  i_img_destroy(im);
   /*   myfree(cl); */
 }
 
@@ -197,18 +298,13 @@ i_img_new() {
   if ( (im=mymalloc(sizeof(i_img))) == NULL)
     m_fatal(2,"malloc() error\n");
   
+  *im = IIM_base_8bit_direct;
   im->xsize=0;
   im->ysize=0;
   im->channels=3;
   im->ch_mask=MAXINT;
   im->bytes=0;
-  im->data=NULL;
-
-  im->i_f_ppix=i_ppix_d;
-  im->i_f_gpix=i_gpix_d;
-  im->i_f_plin=i_plin_d;
-  im->i_f_glin=i_glin_d;
-  im->ext_data=NULL;
+  im->idata=NULL;
   
   mm_log((1,"(%p) <- i_img_struct\n",im));
   return im;
@@ -223,32 +319,17 @@ Re-new image reference (assumes 3 channels)
    x - xsize of destination image
    y - ysize of destination image
 
+**FIXME** what happens if a live image is passed in here?
+
+Should this just call i_img_empty_ch()?
+
 =cut
 */
 
 i_img *
 i_img_empty(i_img *im,int x,int y) {
   mm_log((1,"i_img_empty(*im %p, x %d, y %d)\n",im, x, y));
-  if (im==NULL)
-    if ( (im=mymalloc(sizeof(i_img))) == NULL)
-      m_fatal(2,"malloc() error\n");
-  
-  im->xsize    = x;
-  im->ysize    = y;
-  im->channels = 3;
-  im->ch_mask  = MAXINT;
-  im->bytes=x*y*im->channels;
-  if ( (im->data = mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); 
-  memset(im->data, 0, (size_t)im->bytes);
-
-  im->i_f_ppix = i_ppix_d;
-  im->i_f_gpix = i_gpix_d;
-  im->i_f_plin = i_plin_d;
-  im->i_f_glin = i_glin_d;
-  im->ext_data = NULL;
-  
-  mm_log((1,"(%p) <- i_img_empty\n", im));
-  return im;
+  return i_img_empty_ch(im, x, y, 3);
 }
 
 /* 
@@ -270,19 +351,17 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   if (im == NULL)
     if ( (im=mymalloc(sizeof(i_img))) == NULL)
       m_fatal(2,"malloc() error\n");
-  
+
+  memcpy(im, &IIM_base_8bit_direct, sizeof(i_img));
+  i_tags_new(&im->tags);
   im->xsize    = x;
   im->ysize    = y;
   im->channels = ch;
   im->ch_mask  = MAXINT;
   im->bytes=x*y*im->channels;
-  if ( (im->data=mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); 
-  memset(im->data,0,(size_t)im->bytes);
+  if ( (im->idata=mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); 
+  memset(im->idata,0,(size_t)im->bytes);
   
-  im->i_f_ppix = i_ppix_d;
-  im->i_f_gpix = i_gpix_d;
-  im->i_f_plin = i_plin_d;
-  im->i_f_glin = i_glin_d;
   im->ext_data = NULL;
   
   mm_log((1,"(%p) <- i_img_empty_ch\n",im));
@@ -302,8 +381,11 @@ Free image data.
 void
 i_img_exorcise(i_img *im) {
   mm_log((1,"i_img_exorcise(im* 0x%x)\n",im));
-  if (im->data != NULL) { myfree(im->data); }
-  im->data     = NULL;
+  i_tags_destroy(&im->tags);
+  if (im->i_f_destroy)
+    (im->i_f_destroy)(im);
+  if (im->idata != NULL) { myfree(im->idata); }
+  im->idata    = NULL;
   im->xsize    = 0;
   im->ysize    = 0;
   im->channels = 0;
@@ -356,7 +438,7 @@ i_img_info(i_img *im,int *info) {
   mm_log((1,"i_img_info(im 0x%x)\n",im));
   if (im != NULL) {
     mm_log((1,"i_img_info: xsize=%d ysize=%d channels=%d mask=%ud\n",im->xsize,im->ysize,im->channels,im->ch_mask));
-    mm_log((1,"i_img_info: data=0x%d\n",im->data));
+    mm_log((1,"i_img_info: idata=0x%d\n",im->idata));
     info[0] = im->xsize;
     info[1] = im->ysize;
     info[2] = im->channels;
@@ -412,7 +494,7 @@ range.
 =cut
 */
 int
-i_ppix(i_img *im, int x, int y, i_color *val) { return im->i_f_ppix(im, x, y, val); }
+(i_ppix)(i_img *im, int x, int y, i_color *val) { return im->i_f_ppix(im, x, y, val); }
 
 /*
 =item i_gpix(im, x, y, &col)
@@ -424,127 +506,7 @@ Returns true if the pixel could be retrieved, false otherwise.
 =cut
 */
 int
-i_gpix(i_img *im, int x, int y, i_color *val) { return im->i_f_gpix(im, x, y, val); }
-
-/*
-=item i_ppix_d(im, x, y, col)
-
-Internal function.
-
-This is the function kept in the i_f_ppix member of an i_img object.
-It does a normal store of a pixel into the image with range checking.
-
-Returns true if the pixel could be set, false otherwise.
-
-=cut
-*/
-int
-i_ppix_d(i_img *im, int x, int y, i_color *val) {
-  int ch;
-  
-  if ( x>-1 && x<im->xsize && y>-1 && y<im->ysize ) {
-    for(ch=0;ch<im->channels;ch++)
-      if (im->ch_mask&(1<<ch)) 
-       im->data[(x+y*im->xsize)*im->channels+ch]=val->channel[ch];
-    return 0;
-  }
-  return -1; /* error was clipped */
-}
-
-/*
-=item i_gpix_d(im, x, y, &col)
-
-Internal function.
-
-This is the function kept in the i_f_gpix member of an i_img object.
-It does normal retrieval of a pixel from the image with range checking.
-
-Returns true if the pixel could be set, false otherwise.
-
-=cut
-*/
-int 
-i_gpix_d(i_img *im, int x, int y, i_color *val) {
-  int ch;
-  if (x>-1 && x<im->xsize && y>-1 && y<im->ysize) {
-    for(ch=0;ch<im->channels;ch++) 
-       val->channel[ch]=im->data[(x+y*im->xsize)*im->channels+ch];
-    return 0;
-  }
-  return -1; /* error was cliped */
-}
-
-/*
-=item i_glin_d(im, l, r, y, vals)
-
-Reads a line of data from the image, storing the pixels at vals.
-
-The line runs from (l,y) inclusive to (r,y) non-inclusive
-
-vals should point at space for (r-l) pixels.
-
-l should never be less than zero (to avoid confusion about where to
-put the pixels in vals).
-
-Returns the number of pixels copied (eg. if r, l or y is out of range)
-
-=cut */
-int
-i_glin_d(i_img *im, int l, int r, int y, i_color *vals) {
-  int ch, count, i;
-  unsigned char *data;
-  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
-    if (r > im->xsize)
-      r = im->xsize;
-    data = im->data + (l+y*im->xsize) * im->channels;
-    count = r - l;
-    for (i = 0; i < count; ++i) {
-      for (ch = 0; ch < im->channels; ++ch)
-       vals[i].channel[ch] = *data++;
-    }
-    return count;
-  }
-  else {
-    return 0;
-  }
-}
-/*
-=item i_plin_d(im, l, r, y, vals)
-
-Writes a line of data into the image, using the pixels at vals.
-
-The line runs from (l,y) inclusive to (r,y) non-inclusive
-
-vals should point at (r-l) pixels.
-
-l should never be less than zero (to avoid confusion about where to
-get the pixels in vals).
-
-Returns the number of pixels copied (eg. if r, l or y is out of range)
-
-=cut */
-int
-i_plin_d(i_img *im, int l, int r, int y, i_color *vals) {
-  int ch, count, i;
-  unsigned char *data;
-  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
-    if (r > im->xsize)
-      r = im->xsize;
-    data = im->data + (l+y*im->xsize) * im->channels;
-    count = r - l;
-    for (i = 0; i < count; ++i) {
-      for (ch = 0; ch < im->channels; ++ch) {
-       if (im->ch_mask & (1 << ch)) 
-         *data = vals[i].channel[ch];
-       ++data;
-      }
-    }
-    return count;
-  }
-  else {
-    return 0;
-  }
-}
+(i_gpix)(i_img *im, int x, int y, i_color *val) { return im->i_f_gpix(im, x, y, val); }
 
 /*
 =item i_ppix_pch(im, x, y, ch)
@@ -560,7 +522,8 @@ Warning: this ignores the vptr interface for images.
 */
 float
 i_gpix_pch(i_img *im,int x,int y,int ch) {
-  if (x>-1 && x<im->xsize && y>-1 && y<im->ysize) return ((float)im->data[(x+y*im->xsize)*im->channels+ch]/255);
+  /* FIXME */
+  if (x>-1 && x<im->xsize && y>-1 && y<im->ysize) return ((float)im->idata[(x+y*im->xsize)*im->channels+ch]/255);
   else return 0;
 }
 
@@ -619,24 +582,39 @@ If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped.
 
 void
 i_copyto(i_img *im, i_img *src, int x1, int y1, int x2, int y2, int tx, int ty) {
-  i_color pv;
   int x, y, t, ttx, tty;
-
+  
   if (x2<x1) { t=x1; x1=x2; x2=t; }
   if (y2<y1) { t=y1; y1=y2; y2=t; }
-
+  
   mm_log((1,"i_copyto(im* %p, src %p, x1 %d, y1 %d, x2 %d, y2 %d, tx %d, ty %d)\n",
          im, src, x1, y1, x2, y2, tx, ty));
-
+  
+  if (im->bits == i_8_bits) {
+    i_color pv;
     tty = ty;
     for(y=y1; y<y2; y++) {
-    ttx = tx;
-    for(x=x1; x<x2; x++) {
-      i_gpix(src, x,   y,   &pv);
-      i_ppix(im,  ttx, tty, &pv);
-      ttx++;
+      ttx = tx;
+      for(x=x1; x<x2; x++) {
+        i_gpix(src, x,   y,   &pv);
+        i_ppix(im,  ttx, tty, &pv);
+        ttx++;
+      }
+      tty++;
+    }
+  }
+  else {
+    i_fcolor pv;
+    tty = ty;
+    for(y=y1; y<y2; y++) {
+      ttx = tx;
+      for(x=x1; x<x2; x++) {
+        i_gpixf(src, x,   y,   &pv);
+        i_ppixf(im,  ttx, tty, &pv);
+        ttx++;
+      }
+      tty++;
     }
-    tty++;
   }
 }
 
@@ -650,21 +628,58 @@ Copies the contents of the image I<src> over the image I<im>.
 
 void
 i_copy(i_img *im, i_img *src) {
-  i_color *pv;
   int y, y1, x1;
 
   mm_log((1,"i_copy(im* %p,src %p)\n", im, src));
 
   x1 = src->xsize;
   y1 = src->ysize;
-  i_img_empty_ch(im, x1, y1, src->channels);
-  pv = mymalloc(sizeof(i_color) * x1);
-  
-  for (y = 0; y < y1; ++y) {
-    i_glin(src, 0, x1, y, pv);
-    i_plin(im, 0, x1, y, pv);
+  if (src->type == i_direct_type) {
+    if (src->bits == i_8_bits) {
+      i_color *pv;
+      i_img_empty_ch(im, x1, y1, src->channels);
+      pv = mymalloc(sizeof(i_color) * x1);
+      
+      for (y = 0; y < y1; ++y) {
+        i_glin(src, 0, x1, y, pv);
+        i_plin(im, 0, x1, y, pv);
+      }
+      myfree(pv);
+    }
+    else {
+      /* currently the only other depth is 16 */
+      i_fcolor *pv;
+      i_img_16_new_low(im, x1, y1, src->channels);
+      pv = mymalloc(sizeof(i_fcolor) * x1);
+      for (y = 0; y < y1; ++y) {
+        i_glinf(src, 0, x1, y, pv);
+        i_plinf(im, 0, x1, y, pv);
+      }
+      myfree(pv);
+    }
+  }
+  else {
+    i_color temp;
+    int index;
+    int count;
+    i_palidx *vals;
+
+    /* paletted image */
+    i_img_pal_new_low(im, x1, y1, src->channels, i_maxcolors(src));
+    /* copy across the palette */
+    count = i_colorcount(src);
+    for (index = 0; index < count; ++index) {
+      i_getcolors(src, index, &temp, 1);
+      i_addcolors(im, &temp, 1);
+    }
+
+    vals = mymalloc(sizeof(i_palidx) * x1);
+    for (y = 0; y < y1; ++y) {
+      i_gpal(src, 0, x1, y, vals);
+      i_ppal(im, 0, x1, y, vals);
+    }
+    myfree(vals);
   }
-  myfree(pv);
 }
 
 
@@ -681,31 +696,84 @@ unmodified.
 =cut
 */
 
-void
+int
 i_rubthru(i_img *im,i_img *src,int tx,int ty) {
-  i_color pv, orig, dest;
   int x, y, ttx, tty;
+  int chancount;
+  int chans[3];
+  int alphachan;
+  int ch;
 
   mm_log((1,"i_rubthru(im %p, src %p, tx %d, ty %d)\n", im, src, tx, ty));
+  i_clear_error();
 
-  if (im->channels  != 3) { fprintf(stderr,"Destination is not in rgb mode.\n"); exit(3); }
-  if (src->channels != 4) { fprintf(stderr,"Source is not in rgba mode.\n"); exit(3); }
-
-  ttx = tx;
-  for(x=0; x<src->xsize; x++) {
-    tty=ty;
-    for(y=0;y<src->ysize;y++) {
-      /* fprintf(stderr,"reading (%d,%d) writing (%d,%d).\n",x,y,ttx,tty); */
-      i_gpix(src, x,   y,   &pv);
-      i_gpix(im,  ttx, tty, &orig);
-      dest.rgb.r = (pv.rgba.a*pv.rgba.r+(255-pv.rgba.a)*orig.rgb.r)/255;
-      dest.rgb.g = (pv.rgba.a*pv.rgba.g+(255-pv.rgba.a)*orig.rgb.g)/255;
-      dest.rgb.b = (pv.rgba.a*pv.rgba.b+(255-pv.rgba.a)*orig.rgb.b)/255;
-      i_ppix(im, ttx, tty, &dest);
-      tty++;
+  if (im->channels == 3 && src->channels == 4) {
+    chancount = 3;
+    chans[0] = 0; chans[1] = 1; chans[2] = 2;
+    alphachan = 3;
+  }
+  else if (im->channels == 3 && src->channels == 2) {
+    chancount = 3;
+    chans[0] = chans[1] = chans[2] = 0;
+    alphachan = 1;
+  }
+  else if (im->channels == 1 && src->channels == 2) {
+    chancount = 1;
+    chans[0] = 0;
+    alphachan = 1;
+  }
+  else {
+    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (3,2) or (1,2)");
+    return 0;
+  }
+
+  if (im->bits <= 8) {
+    /* if you change this code, please make sure the else branch is
+       changed in a similar fashion - TC */
+    int alpha;
+    i_color pv, orig, dest;
+    ttx = tx;
+    for(x=0; x<src->xsize; x++) {
+      tty=ty;
+      for(y=0;y<src->ysize;y++) {
+        /* fprintf(stderr,"reading (%d,%d) writing (%d,%d).\n",x,y,ttx,tty); */
+        i_gpix(src, x,   y,   &pv);
+        i_gpix(im,  ttx, tty, &orig);
+        alpha = pv.channel[alphachan];
+        for (ch = 0; ch < chancount; ++ch) {
+          dest.channel[ch] = (alpha * pv.channel[chans[ch]]
+                              + (255 - alpha) * orig.channel[ch])/255;
+        }
+        i_ppix(im, ttx, tty, &dest);
+        tty++;
+      }
+      ttx++;
+    }
+  }
+  else {
+    double alpha;
+    i_fcolor pv, orig, dest;
+
+    ttx = tx;
+    for(x=0; x<src->xsize; x++) {
+      tty=ty;
+      for(y=0;y<src->ysize;y++) {
+        /* fprintf(stderr,"reading (%d,%d) writing (%d,%d).\n",x,y,ttx,tty); */
+        i_gpixf(src, x,   y,   &pv);
+        i_gpixf(im,  ttx, tty, &orig);
+        alpha = pv.channel[alphachan];
+        for (ch = 0; ch < chancount; ++ch) {
+          dest.channel[ch] = alpha * pv.channel[chans[ch]]
+                              + (1 - alpha) * orig.channel[ch];
+        }
+        i_ppixf(im, ttx, tty, &dest);
+        tty++;
+      }
+      ttx++;
     }
-    ttx++;
   }
+
+  return 1;
 }
 
 
@@ -980,6 +1048,42 @@ i_scale_nn(i_img *im, float scx, float scy) {
   return new_img;
 }
 
+/*
+=item i_sametype(i_img *im, int xsize, int ysize)
+
+Returns an image of the same type (sample size, channels, paletted/direct).
+
+For paletted images the palette is copied from the source.
+
+=cut
+*/
+
+i_img *i_sametype(i_img *src, int xsize, int ysize) {
+  if (src->type == i_direct_type) {
+    if (src->bits == 8) {
+      return i_img_empty_ch(NULL, xsize, ysize, src->channels);
+    }
+    else if (src->bits == 16) {
+      return i_img_16_new(xsize, ysize, src->channels);
+    }
+    else {
+      i_push_error(0, "Unknown image bits");
+      return NULL;
+    }
+  }
+  else {
+    i_color col;
+    int i;
+
+    i_img *targ = i_img_pal_new(xsize, ysize, src->channels, i_maxcolors(src));
+    for (i = 0; i < i_colorcount(src); ++i) {
+      i_getcolors(src, i, &col, 1);
+      i_addcolors(targ, &col, 1);
+    }
+
+    return targ;
+  }
+}
 
 /*
 =item i_transform(im, opx, opxl, opy, opyl, parm, parmlen)
@@ -1159,6 +1263,577 @@ symbol_table_t symbol_table={i_has_format,ICL_set_internal,ICL_info,
 
 
 /*
+=back
+
+=head2 8-bit per sample image internal functions
+
+These are the functions installed in an 8-bit per sample image.
+
+=over
+
+=item i_ppix_d(im, x, y, col)
+
+Internal function.
+
+This is the function kept in the i_f_ppix member of an i_img object.
+It does a normal store of a pixel into the image with range checking.
+
+Returns 0 if the pixel could be set, -1 otherwise.
+
+=cut
+*/
+int
+i_ppix_d(i_img *im, int x, int y, i_color *val) {
+  int ch;
+  
+  if ( x>-1 && x<im->xsize && y>-1 && y<im->ysize ) {
+    for(ch=0;ch<im->channels;ch++)
+      if (im->ch_mask&(1<<ch)) 
+       im->idata[(x+y*im->xsize)*im->channels+ch]=val->channel[ch];
+    return 0;
+  }
+  return -1; /* error was clipped */
+}
+
+/*
+=item i_gpix_d(im, x, y, &col)
+
+Internal function.
+
+This is the function kept in the i_f_gpix member of an i_img object.
+It does normal retrieval of a pixel from the image with range checking.
+
+Returns 0 if the pixel could be set, -1 otherwise.
+
+=cut
+*/
+int 
+i_gpix_d(i_img *im, int x, int y, i_color *val) {
+  int ch;
+  if (x>-1 && x<im->xsize && y>-1 && y<im->ysize) {
+    for(ch=0;ch<im->channels;ch++) 
+       val->channel[ch]=im->idata[(x+y*im->xsize)*im->channels+ch];
+    return 0;
+  }
+  return -1; /* error was cliped */
+}
+
+/*
+=item i_glin_d(im, l, r, y, vals)
+
+Reads a line of data from the image, storing the pixels at vals.
+
+The line runs from (l,y) inclusive to (r,y) non-inclusive
+
+vals should point at space for (r-l) pixels.
+
+l should never be less than zero (to avoid confusion about where to
+put the pixels in vals).
+
+Returns the number of pixels copied (eg. if r, l or y is out of range)
+
+=cut
+*/
+int
+i_glin_d(i_img *im, int l, int r, int y, i_color *vals) {
+  int ch, count, i;
+  unsigned char *data;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch)
+       vals[i].channel[ch] = *data++;
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_plin_d(im, l, r, y, vals)
+
+Writes a line of data into the image, using the pixels at vals.
+
+The line runs from (l,y) inclusive to (r,y) non-inclusive
+
+vals should point at (r-l) pixels.
+
+l should never be less than zero (to avoid confusion about where to
+get the pixels in vals).
+
+Returns the number of pixels copied (eg. if r, l or y is out of range)
+
+=cut
+*/
+int
+i_plin_d(i_img *im, int l, int r, int y, i_color *vals) {
+  int ch, count, i;
+  unsigned char *data;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+       if (im->ch_mask & (1 << ch)) 
+         *data = vals[i].channel[ch];
+       ++data;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_ppixf_d(im, x, y, val)
+
+=cut
+*/
+int
+i_ppixf_d(i_img *im, int x, int y, i_fcolor *val) {
+  int ch;
+  
+  if ( x>-1 && x<im->xsize && y>-1 && y<im->ysize ) {
+    for(ch=0;ch<im->channels;ch++)
+      if (im->ch_mask&(1<<ch)) {
+       im->idata[(x+y*im->xsize)*im->channels+ch] = 
+          SampleFTo8(val->channel[ch]);
+      }
+    return 0;
+  }
+  return -1; /* error was clipped */
+}
+
+/*
+=item i_gpixf_d(im, x, y, val)
+
+=cut
+*/
+int
+i_gpixf_d(i_img *im, int x, int y, i_fcolor *val) {
+  int ch;
+  if (x>-1 && x<im->xsize && y>-1 && y<im->ysize) {
+    for(ch=0;ch<im->channels;ch++) {
+      val->channel[ch] = 
+        Sample8ToF(im->idata[(x+y*im->xsize)*im->channels+ch]);
+    }
+    return 0;
+  }
+  return -1; /* error was cliped */
+}
+
+/*
+=item i_glinf_d(im, l, r, y, vals)
+
+Reads a line of data from the image, storing the pixels at vals.
+
+The line runs from (l,y) inclusive to (r,y) non-inclusive
+
+vals should point at space for (r-l) pixels.
+
+l should never be less than zero (to avoid confusion about where to
+put the pixels in vals).
+
+Returns the number of pixels copied (eg. if r, l or y is out of range)
+
+=cut
+*/
+int
+i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  int ch, count, i;
+  unsigned char *data;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch)
+       vals[i].channel[ch] = SampleFTo8(*data++);
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_plinf_d(im, l, r, y, vals)
+
+Writes a line of data into the image, using the pixels at vals.
+
+The line runs from (l,y) inclusive to (r,y) non-inclusive
+
+vals should point at (r-l) pixels.
+
+l should never be less than zero (to avoid confusion about where to
+get the pixels in vals).
+
+Returns the number of pixels copied (eg. if r, l or y is out of range)
+
+=cut
+*/
+int
+i_plinf_d(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  int ch, count, i;
+  unsigned char *data;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+       if (im->ch_mask & (1 << ch)) 
+         *data = Sample8ToF(vals[i].channel[ch]);
+       ++data;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count)
+
+Reads sample values from im for the horizontal line (l, y) to (r-1,y)
+for the channels specified by chans, an array of int with chan_count
+elements.
+
+Returns the number of samples read (which should be (r-l) * bits_set(chan_mask)
+
+=cut
+*/
+int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, 
+              int *chans, int chan_count) {
+  int ch, count, i, w;
+  unsigned char *data;
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return 0;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = data[chans[ch]];
+          ++count;
+        }
+        data += im->channels;
+      }
+    }
+    else {
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = data[ch];
+          ++count;
+        }
+        data += im->channels;
+      }
+    }
+
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count)
+
+Reads sample values from im for the horizontal line (l, y) to (r-1,y)
+for the channels specified by chan_mask, where bit 0 is the first
+channel.
+
+Returns the number of samples read (which should be (r-l) * bits_set(chan_mask)
+
+=cut
+*/
+int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, 
+               int *chans, int chan_count) {
+  int ch, count, i, w;
+  unsigned char *data;
+  for (ch = 0; ch < chan_count; ++ch) {
+    if (chans[ch] < 0 || chans[ch] >= im->channels) {
+      i_push_errorf(0, "No channel %d in this image", chans[ch]);
+    }
+  }
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = im->idata + (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return 0;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = data[chans[ch]];
+          ++count;
+        }
+        data += im->channels;
+      }
+    }
+    else {
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = data[ch];
+          ++count;
+        }
+        data += im->channels;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=back
+
+=head2 Image method wrappers
+
+These functions provide i_fsample_t functions in terms of their
+i_sample_t versions.
+
+=over
+
+=item i_ppixf_fp(i_img *im, int x, int y, i_fcolor *pix)
+
+=cut
+*/
+
+int i_ppixf_fp(i_img *im, int x, int y, i_fcolor *pix) {
+  i_color temp;
+  int ch;
+
+  for (ch = 0; ch < im->channels; ++ch)
+    temp.channel[ch] = SampleFTo8(pix->channel[ch]);
+  
+  return i_ppix(im, x, y, &temp);
+}
+
+/*
+=item i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix)
+
+=cut
+*/
+int i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix) {
+  i_color temp;
+  int ch;
+
+  if (i_gpix(im, x, y, &temp)) {
+    for (ch = 0; ch < im->channels; ++ch)
+      pix->channel[ch] = Sample8ToF(temp.channel[ch]);
+    return 0;
+  }
+  else 
+    return -1;
+}
+
+/*
+=item i_plinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix)
+
+=cut
+*/
+int i_plinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) {
+  i_color *work;
+
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (r > l) {
+      int ret;
+      int i, ch;
+      work = mymalloc(sizeof(i_color) * (r-l));
+      for (i = 0; i < r-l; ++i) {
+        for (ch = 0; ch < im->channels; ++ch) 
+          work[i].channel[ch] = SampleFTo8(pix[i].channel[ch]);
+      }
+      ret = i_plin(im, l, r, y, work);
+      myfree(work);
+
+      return ret;
+    }
+    else {
+      return 0;
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_glinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix)
+
+=cut
+*/
+int i_glinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) {
+  i_color *work;
+
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (r > l) {
+      int ret;
+      int i, ch;
+      work = mymalloc(sizeof(i_color) * (r-l));
+      ret = i_plin(im, l, r, y, work);
+      for (i = 0; i < r-l; ++i) {
+        for (ch = 0; ch < im->channels; ++ch) 
+          pix[i].channel[ch] = Sample8ToF(work[i].channel[ch]);
+      }
+      myfree(work);
+
+      return ret;
+    }
+    else {
+      return 0;
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gsampf_fp(i_img *im, int l, int r, int y, i_fsample_t *samp, int *chans, int chan_count)
+
+=cut
+*/
+int i_gsampf_fp(i_img *im, int l, int r, int y, i_fsample_t *samp, 
+                int *chans, int chan_count) {
+  i_sample_t *work;
+
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (r > l) {
+      int ret;
+      int i;
+      work = mymalloc(sizeof(i_sample_t) * (r-l));
+      ret = i_gsamp(im, l, r, y, work, chans, chan_count);
+      for (i = 0; i < ret; ++i) {
+          samp[i] = Sample8ToF(work[i]);
+      }
+      myfree(work);
+
+      return ret;
+    }
+    else {
+      return 0;
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=back
+
+=head2 Palette wrapper functions
+
+Used for virtual images, these forward palette calls to a wrapped image, 
+assuming the wrapped image is the first pointer in the structure that 
+im->ext_data points at.
+
+=over
+
+=item i_addcolors_forward(i_img *im, i_color *colors, int count)
+
+=cut
+*/
+int i_addcolors_forward(i_img *im, i_color *colors, int count) {
+  return i_addcolors(*(i_img **)im->ext_data, colors, count);
+}
+
+/*
+=item i_getcolors_forward(i_img *im, int i, i_color *color, int count)
+
+=cut
+*/
+int i_getcolors_forward(i_img *im, int i, i_color *color, int count) {
+  return i_getcolors(*(i_img **)im->ext_data, i, color, count);
+}
+
+/*
+=item i_setcolors_forward(i_img *im, int i, i_color *color, int count)
+
+=cut
+*/
+int i_setcolors_forward(i_img *im, int i, i_color *color, int count) {
+  return i_setcolors(*(i_img **)im->ext_data, i, color, count);
+}
+
+/*
+=item i_colorcount_forward(i_img *im)
+
+=cut
+*/
+int i_colorcount_forward(i_img *im) {
+  return i_colorcount(*(i_img **)im->ext_data);
+}
+
+/*
+=item i_maxcolors_forward(i_img *im)
+
+=cut
+*/
+int i_maxcolors_forward(i_img *im) {
+  return i_maxcolors(*(i_img **)im->ext_data);
+}
+
+/*
+=item i_findcolor_forward(i_img *im, i_color *color, i_palidx *entry)
+
+=cut
+*/
+int i_findcolor_forward(i_img *im, i_color *color, i_palidx *entry) {
+  return i_findcolor(*(i_img **)im->ext_data, color, entry);
+}
+
+/*
+=back
+
+=head2 Stream reading and writing wrapper functions
+
+=over
+
 =item i_gen_reader(i_gen_read_data *info, char *buf, int length)
 
 Performs general read buffering for file readers that permit reading
diff --git a/image.h b/image.h
index ac8979c..b99469d 100644 (file)
--- a/image.h
+++ b/image.h
@@ -39,6 +39,9 @@ void     ICL_info        (i_color *cl);
 void     ICL_DESTROY     (i_color *cl);
 void     ICL_add         (i_color *dst, i_color *src, int ch);
 
+extern i_fcolor *i_fcolor_new(double r, double g, double b, double a);
+extern void i_fcolor_destroy(i_fcolor *cl);
+
 i_img *IIM_new(int x,int y,int ch);
 void   IIM_DESTROY(i_img *im);
 i_img *i_img_new( void );
@@ -49,6 +52,10 @@ void   i_img_destroy(i_img *im);
 
 void   i_img_info(i_img *im,int *info);
 
+extern i_img *i_sametype(i_img *im, int xsize, int ysize);
+
+i_img *i_img_pal_new(int x, int y, int ch, int maxpal);
+
 /* Image feature settings */
 
 void   i_img_setmask    (i_img *im,int ch_mask);
@@ -59,14 +66,56 @@ int    i_img_getchannels(i_img *im);
 
 int i_ppix(i_img *im,int x,int y,i_color *val);
 int i_gpix(i_img *im,int x,int y,i_color *val);
+int i_ppixf(i_img *im,int x,int y,i_color *val);
+int i_gpixf(i_img *im,int x,int y,i_color *val);
 
+#define i_ppix(im, x, y, val) (((im)->i_f_ppix)((im), (x), (y), (val)))
+#define i_gpix(im, x, y, val) (((im)->i_f_gpix)((im), (x), (y), (val)))
+#define i_ppixf(im, x, y, val) (((im)->i_f_ppixf)((im), (x), (y), (val)))
+#define i_gpixf(im, x, y, val) (((im)->i_f_gpixf)((im), (x), (y), (val)))
+
+#if 0
 int i_ppix_d(i_img *im,int x,int y,i_color *val);
 int i_gpix_d(i_img *im,int x,int y,i_color *val);
 int i_plin_d(i_img *im,int l, int r, int y, i_color *val);
 int i_glin_d(i_img *im,int l, int r, int y, i_color *val);
+#endif
 
 #define i_plin(im, l, r, y, val) (((im)->i_f_plin)(im, l, r, y, val))
 #define i_glin(im, l, r, y, val) (((im)->i_f_glin)(im, l, r, y, val))
+#define i_plinf(im, l, r, y, val) (((im)->i_f_plinf)(im, l, r, y, val))
+#define i_glinf(im, l, r, y, val) (((im)->i_f_glinf)(im, l, r, y, val))
+
+#define i_gsamp(im, l, r, y, samps, chans, count) \
+  (((im)->i_f_gsamp)((im), (l), (r), (y), (samps), (chans), (count)))
+#define i_gsampf(im, l, r, y, samps, chans, count) \
+  (((im)->i_f_gsampf)((im), (l), (r), (y), (samps), (chans), (count)))
+
+#define i_findcolor(im, color, entry) \
+  (((im)->i_f_findcolor) ? ((im)->i_f_findcolor)((im), (color), (entry)) : 0)
+
+#define i_gpal(im, l, r, y, vals) \
+  (((im)->i_f_gpal) ? ((im)->i_f_gpal)((im), (l), (r), (y), (vals)) : 0)
+#define i_ppal(im, l, r, y, vals) \
+  (((im)->i_f_ppal) ? ((im)->i_f_ppal)((im), (l), (r), (y), (vals)) : 0)
+#define i_addcolors(im, colors, count) \
+  (((im)->i_f_addcolors) ? ((im)->i_f_addcolors)((im), (colors), (count)) : 0)
+#define i_getcolors(im, index, color, count) \
+  (((im)->i_f_getcolors) ? \
+   ((im)->i_f_getcolors)((im), (index), (color), (count)) : 0)
+#define i_setcolors(im, index, color, count) \
+  (((im)->i_f_setcolors) ? \
+   ((im)->i_f_setcolors)((im), (index), (color), (count)) : 0)
+#define i_colorcount(im) \
+  (((im)->i_f_colorcount) ? ((im)->i_f_colorcount)(im) : -1)
+#define i_maxcolors(im) \
+  (((im)->i_f_maxcolors) ? ((im)->i_f_maxcolors)(im) : -1)
+#define i_findcolor(im, color, entry) \
+  (((im)->i_f_findcolor) ? ((im)->i_f_findcolor)((im), (color), (entry)) : 0)
+
+#define i_img_virtual(im) ((im)->virtual)
+#define i_img_type(im) ((im)->type)
+#define i_img_bits(im) ((im)->bits)
 
 float i_gpix_pch(i_img *im,int x,int y,int ch);
 
@@ -81,11 +130,12 @@ void i_circle_aa   (i_img *im,float x, float y,float rad,i_color *val);
 void i_copyto      (i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty);
 void i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_color *trans);
 void i_copy        (i_img *im,i_img *src);
-void i_rubthru     (i_img *im,i_img *src,int tx,int ty);
+int i_rubthru     (i_img *im,i_img *src,int tx,int ty);
 
 undef_int i_flipxy (i_img *im, int direction);
-
-
+extern i_img *i_rotate90(i_img *im, int degrees);
+extern i_img *i_rotate_exact(i_img *im, double amount);
+extern i_img *i_matrix_transform(i_img *im, int xsize, int ysize, double *matrix);
 
 void i_bezier_multi(i_img *im,int l,double *x,double *y,i_color *val);
 void i_poly_aa     (i_img *im,int l,double *x,double *y,i_color *val);
@@ -99,6 +149,7 @@ void i_conv        (i_img *im,float *coeff,int len);
 
 /* colour manipulation */
 extern int i_convert(i_img *im, i_img *src, float *coeff, int outchan, int inchan);
+extern void i_map(i_img *im, unsigned char (*maps)[256], unsigned int mask);
 
 float i_img_diff   (i_img *im1,i_img *im2);
 
@@ -158,11 +209,38 @@ undef_int i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int c
 
 #endif  /* End of freetype headers */
 
+#ifdef HAVE_FT2
+
+typedef struct FT2_Fonthandle FT2_Fonthandle;
+extern int i_ft2_init(void);
+extern FT2_Fonthandle * i_ft2_new(char *name, int index);
+extern void i_ft2_destroy(FT2_Fonthandle *handle);
+extern int i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi);
+extern int i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi);
+extern int i_ft2_settransform(FT2_Fonthandle *handle, double *matrix);
+extern int i_ft2_sethinting(FT2_Fonthandle *handle, int hinting);
+extern int i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, 
+                      char *text, int len, int *bbox);
+extern int i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, 
+                      i_color *cl, double cheight, double cwidth, 
+                      char *text, int len, int align, int aa, int vlayout,
+                      int utf8);
+extern int i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, 
+                    int channel, double cheight, double cwidth, 
+                    char *text, int len, int align, int aa, int vlayout, 
+                    int utf8);
 
+#endif
 
+#ifdef WIN32
 
+extern int i_wf_bbox(char *face, int size, char *text, int length, int *bbox);
+extern int i_wf_text(char *face, i_img *im, int tx, int ty, i_color *cl, 
+                    int size, char *text, int len, int align, int aa);
+extern int i_wf_cp(char *face, i_img *im, int tx, int ty, int channel, 
+                  int size, char *text, int len, int align, int aa);
 
-
+#endif
 
 /* functions for reading and writing formats */
 
@@ -351,6 +429,15 @@ extern void quant_makemap(i_quantize *quant, i_img **imgs, int count);
 extern i_palidx *quant_translate(i_quantize *quant, i_img *img);
 extern void quant_transparent(i_quantize *quant, i_palidx *indices, i_img *img, i_palidx trans_index);
 
+extern i_img *i_img_pal_new(int x, int y, int channels, int maxpal);
+extern i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal);
+extern i_img *i_img_to_pal(i_img *src, i_quantize *quant);
+extern i_img *i_img_to_rgb(i_img *src);
+extern i_img *i_img_masked_new(i_img *targ, i_img *mask, int x, int y, 
+                               int w, int h);
+extern i_img *i_img_16_new(int x, int y, int ch);
+extern i_img *i_img_16_new_low(i_img *im, int x, int y, int ch);
+
 #ifdef HAVE_LIBJPEG
 i_img *   
 i_readjpeg_wiol(io_glue *ig, int length, char** iptc_itext, int *itlength);
@@ -370,9 +457,12 @@ undef_int i_writepng_wiol(i_img *im, io_glue *ig);
 #endif /* HAVE_LIBPNG */
 
 #ifdef HAVE_LIBGIF
-i_img   * i_readgif(int fd, 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);
+i_img *i_readgif(int fd, 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);
 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);
@@ -447,7 +537,7 @@ typedef struct {
   void(*i_arc)(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val);
   void(*i_copyto)(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty);
   void(*i_copyto_trans)(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_color *trans);
-  void(*i_rubthru)(i_img *im,i_img *src,int tx,int ty);
+  int(*i_rubthru)(i_img *im,i_img *src,int tx,int ty);
 
 } symbol_table_t;
 
@@ -466,13 +556,24 @@ extern i_error_cb i_set_error_cb(i_error_cb);
 extern i_failed_cb i_set_failed_cb(i_failed_cb);
 extern void i_set_argv0(char const *);
 extern int i_set_errors_fatal(int new_fatal);
-extern i_errmsg *i_errors();
+extern i_errmsg *i_errors(void);
 
 extern void i_push_error(int code, char const *msg);
 extern void i_push_errorf(int code, char const *fmt, ...);
 extern void i_push_errorvf(int code, char const *fmt, va_list);
-extern void i_clear_error();
+extern void i_clear_error(void);
 extern int i_failed(int code, char const *msg);
 
+/* image tag processing */
+extern void i_tags_new(i_img_tags *tags);
+extern int i_tags_addn(i_img_tags *tags, char *name, int code, int idata);
+extern int i_tags_add(i_img_tags *tags, char *name, int code, char *data, 
+                      int size, int idata);
+extern void i_tags_destroy(i_img_tags *tags);
+extern int i_tags_find(i_img_tags *tags, char *name, int start, int *entry);
+extern int i_tags_findn(i_img_tags *tags, int code, int start, int *entry);
+extern int i_tags_delete(i_img_tags *tags, int entry);
+extern int i_tags_delbyname(i_img_tags *tags, char *name);
+extern int i_tags_delbycode(i_img_tags *tags, int code);
 
 #endif
diff --git a/imagei.h b/imagei.h
new file mode 100644 (file)
index 0000000..d12680b
--- /dev/null
+++ b/imagei.h
@@ -0,0 +1,42 @@
+/* Declares utility functions useful across various files which
+   aren't meant to be available externally
+*/
+
+#ifndef IMAGEI_H_
+#define IMAGEI_H_
+
+#include "image.h"
+
+/* wrapper functions that implement the floating point sample version of a 
+   function in terms of the 8-bit sample version
+*/
+extern int i_ppixf_fp(i_img *im, int x, int y, i_fcolor *pix);
+extern int i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix);
+extern int i_plinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix);
+extern int i_glinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix);
+extern int i_gsampf_fp(i_img *im, int l, int r, int y, i_fsample_t *samp,
+                       int *chans, int chan_count);
+
+/* wrapper functions that forward palette calls to the underlying image,
+   assuming the underlying image is the first pointer in whatever
+   ext_data points at
+*/
+extern int i_addcolors_forward(i_img *im, i_color *, int count);
+extern int i_getcolors_forward(i_img *im, int i, i_color *, int count);
+extern int i_colorcount_forward(i_img *im);
+extern int i_maxcolors_forward(i_img *im);
+extern int i_findcolor_forward(i_img *im, i_color *color, i_palidx *entry);
+extern int i_setcolors_forward(i_img *im, int index, i_color *colors, 
+                               int count);
+
+#define SampleFTo16(num) ((int)((num) * 65535.0 + 0.01))
+/* we add that little bit to avoid rounding issues */
+#define Sample16ToF(num) ((num) / 65535.0)
+
+#define SampleFTo8(num) ((int)((num) * 255.0 + 0.01))
+#define Sample8ToF(num) ((num) / 255.0)
+
+#define Sample16To8(num) ((num) / 257)
+#define Sample8To16(num) ((num) * 257)
+
+#endif
diff --git a/img16.c b/img16.c
new file mode 100644 (file)
index 0000000..3d8f2c5
--- /dev/null
+++ b/img16.c
@@ -0,0 +1,402 @@
+/*
+=head1 NAME
+
+img16.c - implements 16-bit images
+
+=head1 SYNOPSIS
+
+  i_img *im = i_img_16_new(int x, int y, int channels);
+  # use like a normal image
+
+=head1 DESCRIPTION
+
+Implements 16-bit/sample images.
+
+This basic implementation is required so that we have some larger 
+sample image type to work with.
+
+=over
+
+=cut
+*/
+
+#include "image.h"
+#include "imagei.h"
+
+static int i_ppix_d16(i_img *im, int x, int y, i_color *val);
+static int i_gpix_d16(i_img *im, int x, int y, i_color *val);
+static int i_glin_d16(i_img *im, int l, int r, int y, i_color *vals);
+static int i_plin_d16(i_img *im, int l, int r, int y, i_color *vals);
+static int i_ppixf_d16(i_img *im, int x, int y, i_fcolor *val);
+static int i_gpixf_d16(i_img *im, int x, int y, i_fcolor *val);
+static int i_glinf_d16(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_plinf_d16(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_gsamp_d16(i_img *im, int l, int r, int y, i_sample_t *samps, 
+                       int *chans, int chan_count);
+static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps, 
+                        int *chans, int chan_count);
+
+/*
+=item IIM_base_16bit_direct
+
+Base structure used to initialize a 16-bit/sample image.
+
+Internal.
+
+=cut
+*/
+static i_img IIM_base_16bit_direct =
+{
+  0, /* channels set */
+  0, 0, 0, /* xsize, ysize, bytes */
+  ~0, /* ch_mask */
+  i_16_bits, /* bits */
+  i_direct_type, /* type */
+  0, /* virtual */
+  NULL, /* idata */
+  { 0, 0, NULL }, /* tags */
+  NULL, /* ext_data */
+
+  i_ppix_d16, /* i_f_ppix */
+  i_ppixf_d16, /* i_f_ppixf */
+  i_plin_d16, /* i_f_plin */
+  i_plinf_d16, /* i_f_plinf */
+  i_gpix_d16, /* i_f_gpix */
+  i_gpixf_d16, /* i_f_gpixf */
+  i_glin_d16, /* i_f_glin */
+  i_glinf_d16, /* i_f_glinf */
+  i_gsamp_d16, /* i_f_gsamp */
+  i_gsampf_d16, /* i_f_gsampf */
+
+  NULL, /* i_f_gpal */
+  NULL, /* i_f_ppal */
+  NULL, /* i_f_addcolor */
+  NULL, /* i_f_getcolor */
+  NULL, /* i_f_colorcount */
+  NULL, /* i_f_findcolor */
+
+  NULL, /* i_f_destroy */
+};
+
+/* it's possible some platforms won't have a 16-bit integer type,
+   so we check for one otherwise we work by bytes directly
+
+   We do assume 8-bit char
+*/
+#if __STDC_VERSION__ >= 199901L
+/* C99 should define something useful */
+#include <stdint.h>
+#ifdef UINT16_MAX
+typedef uint16_t i_sample16_t;
+#define GOT16
+#endif
+#endif
+
+/* check out unsigned short */
+#ifndef GOT16
+#include <limits.h>
+#if USHRT_MAX == 65535
+typedef unsigned short i_sample16_t;
+#define GOT16
+#endif
+#endif
+
+#ifdef GOT16
+
+/* we have a real 16-bit unsigned integer */
+#define STORE16(bytes, offset, word) \
+   (((i_sample16_t *)(bytes))[offset] = (word))
+#define STORE8as16(bytes, offset, byte) \
+   (((i_sample16_t *)(bytes))[offset] = (byte) * 256)
+#define GET16(bytes, offset) \
+     (((i_sample16_t *)(bytes))[offset])
+#define GET16as8(bytes, offset) \
+     (((i_sample16_t *)(bytes))[offset] / 256)
+
+#else
+
+/* we have to do this the hard way */
+#define STORE16(bytes, offset, word) \
+   ((((unsigned char *)(bytes))[(offset)*2] = (word) >> 8), \
+    (((unsigned char *)(bytes))[(offset)*2+1] = (word) & 0xFF))
+#define STORE8as16(bytes, offset, byte) \
+   ((((unsigned char *)(bytes))[(offset)*2] = (byte)), \
+    (((unsigned char *)(bytes))[(offset)*2+1] = 0))
+   
+#define GET16(bytes, offset) \
+   (((unsigned char *)(bytes))[(offset)*2] * 256 \
+    + ((unsigned char *)(bytes))[(offset)*2+1])
+#define GET16as8(bytes, offset) \
+   (((unsigned char *)(bytes))[(offset)*2] << 8)
+
+#endif
+
+/*
+=item i_img_16_new(int x, int y, int ch)
+
+Creates a new 16-bit per sample image.
+*/
+i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
+  mm_log((1,"i_img_16_new(x %d, y %d, ch %d)\n", x, y, ch));
+  
+  *im = IIM_base_16bit_direct;
+  i_tags_new(&im->tags);
+  im->xsize = x;
+  im->ysize = y;
+  im->channels = ch;
+  im->bytes = x * y * ch * 2;
+  im->ext_data = NULL;
+  im->idata = mymalloc(im->bytes);
+  if (im->idata) {
+    memset(im->idata, 0, im->bytes);
+  }
+  else {
+    i_tags_destroy(&im->tags);
+    im = NULL;
+  }
+  
+  return im;
+}
+
+i_img *i_img_16_new(int x, int y, int ch) {
+  i_img *im;
+
+  im = mymalloc(sizeof(i_img));
+  if (im) {
+    if (!i_img_16_new_low(im, x, y, ch)) {
+      myfree(im);
+      im = NULL;
+    }
+  }
+  
+  mm_log((1, "(%p) <- i_img_16_new\n", im));
+  
+  return im;
+}
+
+static int i_ppix_d16(i_img *im, int x, int y, i_color *val) {
+  int off, ch;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y > im->ysize) 
+    return -1;
+
+  off = (x + y * im->xsize) * im->channels;
+  for (ch = 0; ch < im->channels; ++ch)
+    STORE8as16(im->idata, off+ch, val->channel[ch]);
+
+  return 0;
+}
+
+static int i_gpix_d16(i_img *im, int x, int y, i_color *val) {
+  int off, ch;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y > im->ysize) 
+    return -1;
+
+  off = (x + y * im->xsize) * im->channels;
+  for (ch = 0; ch < im->channels; ++ch)
+    val->channel[ch] = GET16as8(im->idata, off+ch);
+
+  return 0;
+}
+
+static int i_ppixf_d16(i_img *im, int x, int y, i_fcolor *val) {
+  int off, ch;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y > im->ysize) 
+    return -1;
+
+  off = (x + y * im->xsize) * im->channels;
+  for (ch = 0; ch < im->channels; ++ch)
+    STORE16(im->idata, off+ch, SampleFTo16(val->channel[ch]));
+
+  return 0;
+}
+
+static int i_gpixf_d16(i_img *im, int x, int y, i_fcolor *val) {
+  int off, ch;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y > im->ysize) 
+    return -1;
+
+  off = (x + y * im->xsize) * im->channels;
+  for (ch = 0; ch < im->channels; ++ch)
+    val->channel[ch] = Sample16ToF(GET16(im->idata, off+ch));
+
+  return 0;
+}
+
+static int i_glin_d16(i_img *im, int l, int r, int y, i_color *vals) {
+  int ch, count, i;
+  int off;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+       vals[i].channel[ch] = GET16as8(im->idata, off);
+        ++off;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_plin_d16(i_img *im, int l, int r, int y, i_color *vals) {
+  int ch, count, i;
+  int off;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+        STORE8as16(im->idata, off, vals[i].channel[ch]);
+        ++off;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_glinf_d16(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  int ch, count, i;
+  int off;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+       vals[i].channel[ch] = Sample16ToF(GET16(im->idata, off));
+        ++off;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_plinf_d16(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  int ch, count, i;
+  int off;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      for (ch = 0; ch < im->channels; ++ch) {
+        STORE16(im->idata, off, SampleFTo16(vals[i].channel[ch]));
+        ++off;
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_gsamp_d16(i_img *im, int l, int r, int y, i_sample_t *samps, 
+                       int *chans, int chan_count) {
+  int ch, count, i, w;
+  int off;
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return 0;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = GET16as8(im->idata, off+chans[ch]);
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+    else {
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = GET16as8(im->idata, off+ch);
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps, 
+                        int *chans, int chan_count) {
+  int ch, count, i, w;
+  int off;
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    off = (l+y*im->xsize) * im->channels;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return 0;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = Sample16ToF(GET16(im->idata, off+chans[ch]));
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+    else {
+      for (i = 0; i < w; ++i) {
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = Sample16ToF(GET16(im->idata, off+ch));
+          ++count;
+        }
+        off += im->channels;
+      }
+    }
+
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
diff --git a/imio.h b/imio.h
index 959f09d..1334f6b 100644 (file)
--- a/imio.h
+++ b/imio.h
@@ -28,9 +28,10 @@ void  bndcheck_all(void);
 #else
 
 #define malloc_comm(a,b) (mymalloc(a))
-void malloc_state();
+void malloc_state(void);
 void* mymalloc(int size);
 void myfree(void *p);
+void *myrealloc(void *p, size_t newsize);
 
 #endif /* IMAGER_MALLOC_DEBUG */
 
diff --git a/io.c b/io.c
index 0d9dd82..80c6618 100644 (file)
--- a/io.c
+++ b/io.c
@@ -180,7 +180,7 @@ mymalloc(int size) {
 
   mm_log((1, "mymalloc(size %d)\n", size));
   if ( (buf = malloc(size)) == NULL ) {
-    mm_log((1, "mymalloc: unable to malloc\n", size));
+    mm_log((1, "mymalloc: unable to malloc %d\n", size));
     fprintf(stderr,"Unable to malloc.\n"); exit(3);
   }
   return buf;
@@ -192,6 +192,19 @@ myfree(void *p) {
   free(p);
 }
 
+void *
+myrealloc(void *block, size_t size) {
+  void *result;
+
+  mm_log((1, "myrealloc(block %p, size %u)\n", block, size));
+  if ((result = realloc(block, size)) == NULL) {
+    mm_log((1, "myrealloc: out of memory\n"));
+    fprintf(stderr, "Out of memory.\n");
+    exit(3);
+  }
+  return result;
+}
+
 #endif /* IMAGER_MALLOC_DEBUG */
 
 
index b6c8d1c..93e9564 100644 (file)
--- a/iolayer.c
+++ b/iolayer.c
@@ -203,7 +203,7 @@ realseek_seek(io_glue *ig, off_t offset, int whence) {
 
 static
 io_blink*
-io_blink_new() {
+io_blink_new(void) {
   io_blink *ib;
 
   mm_log((1, "io_blink_new()\n"));
index 358de0d..50392c6 100644 (file)
--- a/iolayer.h
+++ b/iolayer.h
@@ -141,7 +141,7 @@ void io_glue_gettypes    (io_glue *ig, int reqmeth);
 
 /* XS functions */
 io_glue *io_new_fd(int fd);
-io_glue *io_new_bufchain();
+io_glue *io_new_bufchain(void);
 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 578d0f9..6aba125 100644 (file)
--- a/jpeg.c
+++ b/jpeg.c
@@ -332,7 +332,7 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
   buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
   while (cinfo.output_scanline < cinfo.output_height) {
     (void) jpeg_read_scanlines(&cinfo, buffer, 1);
-    memcpy(im->data+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
+    memcpy(im->idata+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
   }
   (void) jpeg_finish_decompress(&cinfo);
   jpeg_destroy_decompress(&cinfo);
@@ -361,8 +361,6 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
   if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write JPEG, improper colorspace.\n"); exit(3); }
   quality = qfactor;
 
-  image_buffer = im->data;
-
   cinfo.err = jpeg_std_error(&jerr);
 
   jpeg_create_compress(&cinfo);
@@ -390,9 +388,37 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
 
   row_stride = im->xsize * im->channels;       /* JSAMPLEs per row in image_buffer */
 
-  while (cinfo.next_scanline < cinfo.image_height) {
-    row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
-    (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+  if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
+    image_buffer=im->idata;
+
+    while (cinfo.next_scanline < cinfo.image_height) {
+      /* jpeg_write_scanlines expects an array of pointers to scanlines.
+       * Here the array is only one element long, but you could pass
+       * more than one scanline at a time if that's more convenient.
+       */
+      row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
+      (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+    }
+  }
+  else {
+    unsigned char *data = mymalloc(im->xsize * im->channels);
+    if (data) {
+      while (cinfo.next_scanline < cinfo.image_height) {
+        /* jpeg_write_scanlines expects an array of pointers to scanlines.
+         * Here the array is only one element long, but you could pass
+         * more than one scanline at a time if that's more convenient.
+         */
+        i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data, 
+                NULL, im->channels);
+        row_pointer[0] = data;
+        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+      }
+    }
+    else {
+      jpeg_finish_compress(&cinfo);
+      jpeg_destroy_compress(&cinfo);
+      return 0; /* out of memory? */
+    }
   }
 
   /* Step 6: Finish compression */
index 2248798..522aa2d 100644 (file)
@@ -6,12 +6,214 @@ use vars qw();
 
 # It's just a front end to the XS creation functions.
 
+# used in converting hsv to rgb
+my @hsv_map = 
+  (
+   'vkm', 'nvm', 'mvk', 'mnv', 'kmv', 'vmn'
+  );
+
+sub _hsv_to_rgb {
+  my ($hue, $sat, $val) = @_;
+
+  # HSV conversions from pages 401-403 "Procedural Elements for Computer 
+  # Graphics", 1985, ISBN 0-07-053534-5.
+
+  my @result;
+  if ($sat <= 0) {
+    return ( 255 * $val, 255 * $val, 255 * $val );
+  }
+  else {
+    $val >= 0 or $val = 0;
+    $val <= 1 or $val = 1;
+    $sat <= 1 or $sat = 1;
+    $hue >= 360 and $hue %= 360;
+    $hue < 0 and $hue += 360;
+    $hue /= 60.0;
+    my $i = int($hue);
+    my $f = $hue - $i;
+    $val *= 255;
+    my $m = $val * (1.0 - $sat);
+    my $n = $val * (1.0 - $sat * $f);
+    my $k = $val * (1.0 - $sat * (1 - $f));
+    my $v = $val;
+    my %fields = ( m=>$m, n=>$n, v=>$v, k=>$k, );
+    return @fields{split //, $hsv_map[$i]};
+  }
+}
+
+# cache of loaded gimp files
+# each key is a filename, under each key is a hashref with the following 
+# keys:
+#   mod_time => last mod_time of file
+#   colors => hashref name to arrayref of colors
+my %gimp_cache;
+
+# palette search locations
+# this is pretty rude
+# $HOME is replaced at runtime
+my @gimp_search =
+  (
+   '$HOME/.gimp-1.2/palettes/Named_Colors',
+   '$HOME/.gimp-1.1/palettes/Named_Colors',
+   '$HOME/.gimp/palettes/Named_Colors',
+   '/usr/share/gimp/1.2/palettes/Named_Colors',
+   '/usr/share/gimp/1.1/palettes/Named_Colors',
+   '/usr/share/gimp/palettes/Named_Colors',
+  );
+
+sub _load_gimp_palette {
+  my ($filename) = @_;
+
+  if (open PAL, "< $filename") {
+    my $hdr = <PAL>;
+    chomp $hdr;
+    unless ($hdr =~ /GIMP Palette/) {
+      close PAL;
+      $Imager::ERRSTR = "$filename is not a GIMP palette file";
+      return;
+    }
+    my $line;
+    my %pal;
+    my $mod_time = (stat PAL)[9];
+    while (defined($line = <PAL>)) {
+      next if $line =~ /^#/ || $line =~ /^\s*$/;
+      chomp $line;
+      my ($r,$g, $b, $name) = split ' ', $line, 4;
+      if ($name) {
+        $name =~ s/\s*\([\d\s]+\)\s*$//;
+        $pal{lc $name} = [ $r, $g, $b ];
+      }
+    }
+    close PAL;
+
+    $gimp_cache{$filename} = { mod_time=>$mod_time, colors=>\%pal };
+
+    return 1;
+  }
+  else {
+    $Imager::ERRSTR = "Cannot open palette file $filename: $!";
+    return;
+  }
+}
+
+sub _get_gimp_color {
+  my %args = @_;
+
+  my $filename;
+  if ($args{palette}) {
+    $filename = $args{palette};
+  }
+  else {
+    # try to make one up - this is intended to die if tainting is
+    # enabled and $ENV{HOME} is tainted.  To avoid that untaint $ENV{HOME}
+    # or set the palette parameter
+    for my $attempt (@gimp_search) {
+      my $work = $attempt; # don't modify the source array
+      $work =~ s/\$HOME/$ENV{HOME}/;
+      if (-e $work) {
+        $filename = $work;
+        last;
+      }
+    }
+    if (!$filename) {
+      $Imager::ERRSTR = "No GIMP palette found";
+      return ();
+    }
+  }
+
+  if ((!$gimp_cache{$filename} 
+      || (stat $filename)[9] != $gimp_cache{$filename})
+     && !_load_gimp_palette($filename)) {
+    return ();
+  }
+
+  if (!$gimp_cache{$filename}{colors}{lc $args{name}}) {
+    $Imager::ERRSTR = "Color '$args{name}' isn't in $filename";
+    return ();
+  }
+
+  return @{$gimp_cache{$filename}{colors}{lc $args{name}}};
+}
+
+my @x_search = 
+  (
+   '/usr/lib/X11/rgb.txt', # seems fairly standard
+   '/usr/local/lib/X11/rgb.txt', # seems possible
+   '/usr/X11R6/lib/X11/rgb.txt', # probably the same as the first
+  );
+
+# x rgb.txt cache
+# same structure as %gimp_cache
+my %x_cache;
+
+sub _load_x_rgb {
+  my ($filename) = @_;
+
+  local *RGB;
+  if (open RGB, "< $filename") {
+    my $line;
+    my %pal;
+    my $mod_time = (stat RGB)[9];
+    while (defined($line = <RGB>)) {
+      # the version of rgb.txt supplied with GNU Emacs uses # for comments
+      next if $line =~ /^[!#]/ || $line =~ /^\s*$/;
+      chomp $line;
+      my ($r,$g, $b, $name) = split ' ', $line, 4;
+      if ($name) {
+        $pal{lc $name} = [ $r, $g, $b ];
+      }
+    }
+    close RGB;
+
+    $x_cache{$filename} = { mod_time=>$mod_time, colors=>\%pal };
+
+    return 1;
+  }
+  else {
+    $Imager::ERRSTR = "Cannot open palette file $filename: $!";
+    return;
+  }
+}
+
+sub _get_x_color {
+  my %args = @_;
+
+  my $filename;
+  if ($args{palette}) {
+    $filename = $args{palette};
+  }
+  else {
+    for my $attempt (@x_search) {
+      if (-e $attempt) {
+        $filename = $attempt;
+        last;
+      }
+    }
+    if (!$filename) {
+      $Imager::ERRSTR = "No X rgb.txt palette found";
+      return ();
+    }
+  }
+
+  if ((!$x_cache{$filename} 
+      || (stat $filename)[9] != $x_cache{$filename})
+     && !_load_x_rgb($filename)) {
+    return ();
+  }
+
+  if (!$x_cache{$filename}{colors}{lc $args{name}}) {
+    $Imager::ERRSTR = "Color '$args{name}' isn't in $filename";
+    return ();
+  }
+
+  return @{$x_cache{$filename}{colors}{lc $args{name}}};
+}
 
 # Parse color spec into an a set of 4 colors
 
 sub pspec {
-  return (@_,255) if @_ == 3;
-  return (@_    ) if @_ == 4;
+  return (@_,255) if @_ == 3 && !grep /[^\d.+eE-]/, @_;
+  return (@_    ) if @_ == 4 && !grep /[^\d.+eE-]/, @_;
   if ($_[0] =~ 
       /^\#?([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/i) {
     return (hex($1),hex($2),hex($3),hex($4));
@@ -19,11 +221,99 @@ sub pspec {
   if ($_[0] =~ /^\#?([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/i) {
     return (hex($1),hex($2),hex($3),255);
   }
+  if ($_[0] =~ /^\#([\da-f])([\da-f])([\da-f])$/i) {
+    return (hex($1) * 17, hex($2) * 17, hex($3) * 17, 255);
+  }
+  my %args;
+  if (@_ == 1) {
+    # a named color
+    %args = ( name => @_ );
+  }
+  else {
+    %args = @_;
+  }
+  my @result;
+  if (exists $args{gray}) {
+    @result = $args{gray};
+  }
+  elsif (exists $args{grey}) {
+    @result = $args{grey};
+  }
+  elsif ((exists $args{red} || exists $args{r}) 
+         && (exists $args{green} || exists $args{g})
+         && (exists $args{blue} || exists $args{b})) {
+    @result = ( exists $args{red} ? $args{red} : $args{r},
+                exists $args{green} ? $args{green} : $args{g},
+                exists $args{blue} ? $args{blue} : $args{b} );
+  }
+  elsif ((exists $args{hue} || exists $args{h}) 
+         && (exists $args{saturation} || exists $args{'s'})
+         && (exists $args{value} || exists $args{v})) {
+    my $hue = exists $args{hue}        ? $args{hue}        : $args{h};
+    my $sat = exists $args{saturation} ? $args{saturation} : $args{'s'};
+    my $val = exists $args{value}      ? $args{value}      : $args{v};
+    
+    @result = _hsv_to_rgb($hue, $sat, $val);
+  }
+  elsif (exists $args{web}) {
+    if ($args{web} =~ /^#?([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])$/i) {
+      @result = (hex($1),hex($2),hex($3));
+    }
+    elsif ($args{web} =~ /^#?([\da-f])([\da-f])([\da-f])$/i) {
+      @result = (hex($1) * 17, hex($2) * 17, hex($3) * 17);
+    }
+  }
+  elsif ($args{name}) {
+    unless (@result = _get_gimp_color(%args)) {
+      unless (@result = _get_x_color(%args)) {
+        $Imager::ERRSTR = "No color named $args{name} found";
+        return ();
+      }
+    }
+  }
+  elsif ($args{gimp}) {
+    @result = _get_gimp_color(name=>$args{gimp}, %args);
+  }
+  elsif ($args{xname}) {
+    @result = _get_x_color(name=>$args{xname}, %args);
+  }
+  elsif ($args{rgb}) {
+    @result = @{$args{rgb}};
+  }
+  elsif ($args{rgba}) {
+    @result = @{$args{rgba}};
+    return @result if @result == 4;
+  }
+  elsif ($args{hsv}) {
+    @result = _hsv_to_rgb(@{$args{hsv}});
+  }
+  elsif ($args{channels}) {
+    return @{$args{channels}};
+  }
+  elsif (exists $args{channel0} || $args{c0}) {
+    my $i = 0;
+    while (exists $args{"channel$i"} || exists $args{"c$i"}) {
+      push(@result, 
+           exists $args{"channel$i"} ? $args{"channel$i"} : $args{"c$i"});
+      ++$i;
+    }
+  }
+  else {
+    $Imager::ERRSTR = "No color specification found";
+    return ();
+  }
+  if (@result) {
+    if (exists $args{alpha} || exists $args{a}) {
+      push(@result, exists $args{alpha} ? $args{alpha} : $args{a});
+    }
+    while (@result < 4) {
+      push(@result, 255);
+    }
+    return @result;
+  }
   return ();
 }
 
-
-
 sub new {
   shift; # get rid of class name.
   my @arg = pspec(@_);
@@ -36,10 +326,6 @@ sub set {
   return @arg ? set_internal($self, $arg[0],$arg[1],$arg[2],$arg[3]) : ();
 }
 
-
-
-
-
 1;
 
 __END__
@@ -91,6 +377,134 @@ Calling info merely dumps the relevant colorcode to the log.
 
 =back
 
+You can specify colors in several different ways, you can just supply
+simple values:
+
+=over
+
+=item *
+
+simple numeric parameters - if you supply 3 or 4 numeric arguments, you get a color made up of those RGB (and possibly A) components.
+
+=item *
+
+a six hex digit web color, either 'RRGGBB' or '#RRGGBB'
+
+=item *
+
+an eight hex digit web color, either 'RRGGBBAA' or '#RRGGBBAA'.
+
+=item *
+
+a 3 hex digit web color, '#RGB' - a value of F becomes 255.
+
+=item *
+
+a color name, from whichever of the gimp Named_Colors file or X
+rgb.txt is found first.  The same as using the name keyword.
+
+=back
+
+You can supply named parameters:
+
+=over
+
+=item *
+
+'red', 'green' and 'blue', optionally shortened to 'r', 'g' and 'b'.
+The color components in the range 0 to 255.
+
+ # all of the following are equivalent
+ my $c1 = Imager::Color->new(red=>100, blue=>255, green=>0);
+ my $c2 = Imager::Color->new(r=>100, b=>255, g=>0);
+ my $c3 = Imager::Color->new(r=>100, blue=>255, g=>0);
+
+=item *
+
+'hue', 'saturation' and 'value', optionally shortened to 'h', 's' and
+'v', to specify a HSV color.  0 <= hue < 360, 0 <= s <= 1 and 0 <= v
+<= 1.
+
+  # the same as RGB(127,255,127)
+  my $c1 = Imager::Color->new(hue=>120, v=>1, s=>0.5);
+  my $c1 = Imager::Color->new(hue=>120, value=>1, saturation=>0.5);
+
+=item *
+
+'web', which can specify a 6 or 3 hex digit web color, in any of the
+forms '#RRGGBB', '#RGB', 'RRGGBB' or 'RGB'.
+
+  my $c1 = Imager::Color->new(web=>'#FFC0C0'); # pale red
+
+=item *
+
+'gray' or 'grey' which specifies a single channel, from 0 to 255.
+
+  # exactly the same
+  my $c1 = Imager::Color->new(gray=>128);
+  my $c1 = Imager::Color->new(grey=>128);
+
+=item *
+
+'rgb' which takes a 3 member arrayref, containing each of the red,
+green and blue values.
+
+  # the same
+  my $c1 = Imager::Color->new(rgb=>[255, 100, 0]);
+  my $c1 = Imager::Color->new(r=>255, g=>100, b=>0);
+
+=item *
+
+'hsv' which takes a 3 member arrayref, containting each of hue,
+saturation and value.
+
+  # the same
+  my $c1 = Imager::Color->new(hsv=>[120, 0.5, 1]);
+  my $c1 = Imager::Color->new(hue=>120, v=>1, s=>0.5);
+
+=item *
+
+'gimp' which specifies a color from a GIMP palette file.  You can
+specify the filename of the palette file with the 'palette' parameter,
+or let Imager::Color look in various places, typically
+"$HOME/gimp-1.x/palettes/Named_Colors" with and without the version
+number, and in /usr/share/gimp/palettes/.  The palette file must have
+color names.
+
+  my $c1 = Imager::Color->new(gimp=>'snow');
+  my $c1 = Imager::Color->new(gimp=>'snow', palette=>'testimg/test_gimp_pal);
+
+=item *
+
+'xname' which specifies a color from an X11 rgb.txt file.  You can
+specify the filename of the rgb.txt file with the 'palette' parameter,
+or let Imager::Color look in various places, typically
+'/usr/lib/X11/rgb.txt'.
+
+  my $c1 = Imager::Color->new(xname=>'blue') # usually RGB(0, 0, 255)
+
+=item *
+
+'name' which specifies a name from either a GIMP palette or an X
+rgb.txt file, whichever is found first.
+
+=item *
+
+'channel0', 'channel1', etc, each of which specifies a single channel.  These can be abbreviated to 'c0', 'c1' etc.
+
+=item * 
+
+'channels' which takes an arrayref of the channel values.
+
+=back
+
+Optionally you can add an alpha channel to a color with the 'alpha' or
+'a' parameter.
+
+These color specifications can be used for both constructing new
+colors with the new() method and modifying existing colors with the
+set() method.
+
 =head1 AUTHOR
 
 Arnar M. Hrafnkelsson, addi@umich.edu
@@ -103,4 +517,3 @@ Imager(3)
 http://www.eecs.umich.edu/~addi/perl/Imager/
 
 =cut
-
diff --git a/lib/Imager/Color/Float.pm b/lib/Imager/Color/Float.pm
new file mode 100644 (file)
index 0000000..85e5e94
--- /dev/null
@@ -0,0 +1,106 @@
+package Imager::Color::Float;
+
+use Imager;
+use strict;
+use vars qw();
+
+# It's just a front end to the XS creation functions.
+
+
+# Parse color spec into an a set of 4 colors
+
+sub pspec {
+  return (@_,1) if @_ == 3;
+  return (@_    ) if @_ == 4;
+  if ($_[0] =~ 
+      /^\#?([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/i) {
+    return (hex($1)/255.99,hex($2)/255.99,hex($3)/255.99,hex($4)/255.99);
+  }
+  if ($_[0] =~ /^\#?([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/i) {
+    return (hex($1)/255.99,hex($2)/255.99,hex($3)/255.99,1);
+  }
+  return ();
+}
+
+
+
+sub new {
+  shift; # get rid of class name.
+  my @arg = pspec(@_);
+  return @arg ? new_internal($arg[0],$arg[1],$arg[2],$arg[3]) : ();
+}
+
+sub set {
+  my $self = shift;
+  my @arg = pspec(@_);
+  return @arg ? set_internal($self, $arg[0],$arg[1],$arg[2],$arg[3]) : ();
+}
+
+
+
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Imager::Color::Float - Rough floating point sample colour handling
+
+=head1 SYNOPSIS
+
+  $color = Imager::Color->new($red, $green, $blue);
+  $color = Imager::Color->new($red, $green, $blue, $alpha);
+  $color = Imager::Color->new("#C0C0FF"); # html color specification
+
+  $color->set($red, $green, $blue);
+  $color->set($red, $green, $blue, $alpha);
+  $color->set("#C0C0FF"); # html color specification
+
+  ($red, $green, $blue, $alpha) = $color->rgba();
+  @hsv = $color->hsv(); # not implemented but proposed
+
+  $color->info();
+
+
+=head1 DESCRIPTION
+
+This module handles creating color objects used by imager.  The idea is
+that in the future this module will be able to handle colorspace calculations
+as well.
+
+=over 4
+
+=item new
+
+This creates a color object to pass to functions that need a color argument.
+
+=item set
+
+This changes an already defined color.  Note that this does not affect any places
+where the color has been used previously.
+
+=item rgba
+
+This returns the rgba code of the color the object contains.
+
+=item info
+
+Calling info merely dumps the relevant colorcode to the log.
+
+=back
+
+=head1 AUTHOR
+
+Arnar M. Hrafnkelsson, addi@umich.edu
+And a great deal of help from others - see the README for a complete
+list.
+
+=head1 SEE ALSO
+
+Imager(3)
+http://www.eecs.umich.edu/~addi/perl/Imager/
+
+=cut
+
index 30bd889..1d0716c 100644 (file)
@@ -1,12 +1,47 @@
+
 package Imager::Font;
 
 use Imager::Color;
 use strict;
 use File::Spec;
 
-# This class is a container
-# and works for both truetype and t1 fonts.
-
+# the aim here is that we can:
+#  - add file based types in one place: here
+#  - make sure we only attempt to create types that exist
+#  - give reasonable defaults
+#  - give the user some control over which types get used
+my %drivers =
+  (
+   tt=>{
+        class=>'Imager::Font::Truetype',
+        module=>'Imager/Font/Truetype.pm',
+        files=>'.*\.ttf$',
+       },
+   t1=>{
+        class=>'Imager::Font::Type1',
+        module=>'Imager/Font/Type1.pm',
+        files=>'.*\.pfb$',
+       },
+   ft2=>{
+         class=>'Imager::Font::FreeType2',
+         module=>'Imager/Font/FreeType2.pm',
+         files=>'.*\.(pfb|ttf|fon)$', # maybe this should be dynamic
+        },
+   w32=>{
+         class=>'Imager::Font::Win32',
+         module=>'Imager/Font/Win32.pm',
+        },
+  );
+
+# this currently should only contain file based types, don't add w32
+my @priority = qw(t1 tt ft2);
+
+# when Imager::Font is loaded, Imager.xs has not been bootstrapped yet
+# this function is called from Imager.pm to finish initialization
+sub __init {
+  @priority = grep Imager::i_has_format($_), @priority;
+  delete @drivers{grep !Imager::i_has_format($_), keys %drivers};
+}
 
 # search method
 # 1. start by checking if file is the parameter
@@ -35,14 +70,22 @@ sub new {
     }
 
     $type=$hsh{'type'};
-    if (!defined($type) or $type !~ m/^(t1|tt)/) {
-      $type='tt' if $file =~ m/\.ttf$/i;
-      $type='t1' if $file =~ m/\.pfb$/i;
+    if (!defined($type) or !$drivers{$type}) {
+      for my $drv (@priority) {
+        undef $type;
+        my $re = $drivers{$drv}{files} or next;
+        if ($file =~ /$re/i) {
+          $type = $drv;
+          last;
+        }
+      }
     }
     if (!defined($type)) {
       $Imager::ERRSTR="Font type not found";
       return;
     }
+  } elsif ($hsh{face}) {
+    $type = "w32";
   } else {
     $Imager::ERRSTR="No font file specified";
     return;
@@ -55,19 +98,8 @@ sub new {
 
   # here we should have the font type or be dead already.
 
-  if ($type eq 't1') {
-    require 'Imager/Font/Type1.pm';
-    return Imager::Font::Type1->new(%hsh);
-  }
-
-  if ($type eq 'tt') {
-    require 'Imager/Font/Truetype.pm';
-    return Imager::Font::Truetype->new(%hsh);
-  }
-  # it would be nice to have some generic mechanism to select the
-  # class
-  
-  return undef;
+  require $drivers{$type}{module};
+  return $drivers{$type}{class}->new(%hsh);
 }
 
 # returns first defined parameter
@@ -100,6 +132,9 @@ sub draw {
     return undef;
   }
   $input{align} = _first($input{align}, 1);
+  $input{utf8} = _first($input{utf8}, $self->{utf8}, 0);
+  $input{vlayout} = _first($input{vlayout}, $self->{vlayout}, 0);
+
   $self->_draw(%input);
 }
 
@@ -128,6 +163,55 @@ sub bounding_box {
   return @box;
 }
 
+sub dpi {
+  my $self = shift;
+
+  # I'm assuming a default of 72 dpi
+  my @old = (72, 72);
+  if (@_) {
+    $Imager::ERRSTR = "Setting dpi not implemented for this font type";
+    return;
+  }
+
+  return @old;
+}
+
+sub transform {
+  my $self = shift;
+
+  my %hsh = @_;
+
+  # this is split into transform() and _transform() so we can 
+  # implement other tags like: degrees=>12, which would build a
+  # 12 degree rotation matrix
+  # but I'll do that later
+  unless ($hsh{matrix}) {
+    $Imager::ERRSTR = "You need to supply a matrix";
+    return;
+  }
+  
+  return $self->_transform(%hsh);
+}
+
+sub _transform {
+  $Imager::ERRSTR = "This type of font cannot be transformed";
+  return;
+}
+
+sub utf8 {
+  return 0;
+}
+
+sub priorities {
+  my $self = shift;
+  my @old = @priority;
+
+  if (@_) {
+    @priority = grep Imager::i_has_format($_), @_;
+  }
+  return @old;
+}
+
 1;
 
 __END__
@@ -140,6 +224,7 @@ Imager::Font - Font handling for Imager.
 
   $t1font = Imager::Font->new(file => 'pathtofont.pfb');
   $ttfont = Imager::Font->new(file => 'pathtofont.ttf');
+  $w32font = Imager::Font->new(face => 'Times New Roman');
 
   $blue = Imager::Color->new("#0000FF");
   $font = Imager::Font->new(file  => 'pathtofont.ttf',
@@ -182,7 +267,8 @@ this:
   use Imager;
   print "Has truetype"      if $Imager::formats{tt};
   print "Has t1 postscript" if $Imager::formats{t1};
-
+  print "Has Win32 fonts"   if $Imager::formats{w32};
+  print "Has Freetype2"     if $Imager::formats{ft2};
 
 =over 4
 
@@ -214,7 +300,18 @@ calling C<Imager::Font->new()> the they take the following values:
   size  => 15
   aa    => 0
 
+To use Win32 fonts supply the facename of the font:
+
+  $font = Imager::Font->new(face=>'Arial Bold Italic');
+
+There isn't any access to other logical font attributes, but this
+typically isn't necessary for Win32 TrueType fonts, since you can
+contruct the full name of the font as above.
+
+Other logical font attributes may be added if there is sufficient demand.
+
 =item bounding_box
+
 Returns the bounding box for the specified string.  Example:
 
   ($neg_width,
@@ -282,6 +379,62 @@ values in a font, such as color and size.  If parameters are passed to
 the string function they are used instead of the defaults stored in
 the font.
 
+The following parameters can be supplied to the string() method:
+
+=over
+
+=item string
+
+The text to be rendered.  If this isn't present the 'text' parameter
+is used.  If neither is present the call will fail.
+
+=item aa
+
+If non-zero the output will be anti-aliased.
+
+=item x
+
+=item y
+
+The start point for rendering the text.  See the align parameter.
+
+=item align
+
+If non-zero the point supplied in (x,y) will be on the base-line, if
+zero then (x,y) will be at the top-left of the first character.
+
+=item channel
+
+If present, the text will be written to the specified channel of the
+image and the color parameter will be ignore.
+
+=item color
+
+The color to draw the text in.
+
+=item size
+
+The point-size to draw the text at.
+
+=item sizew
+
+For drivers that support it, the width to draw the text at.  Defaults
+to be value of the 'size' parameter.
+
+=item utf8
+
+For drivers that support it, treat the string as UTF8 encoded.  For
+versions of perl that support Unicode (5.6 and later), this will be
+enabled automatically if the 'string' parameter is already a UTF8
+string. See L<UTF8> for more information.
+
+=item vlayout
+
+For drivers that support it, draw the text vertically.  Note: I
+haven't found a font that has the appropriate metrics yet.
+
+=back
+
 If string() is called with the C<channel> parameter then the color
 isn't used and the font is drawn in only one channel of the image.
 This can be quite handy to create overlays.  See the examples for tips
@@ -291,6 +444,30 @@ Sometimes it is necessary to know how much space a string takes before
 rendering it.  The bounding_box() method described earlier can be used
 for that.
 
+=item dpi()
+
+=item dpi(xdpi=>$xdpi, ydpi=>$ydpi)
+
+=item dpi(dpi=>$dpi)
+
+Set retrieve the spatial resolution of the image in dots per inch.
+The default is 72 dpi.
+
+This isn't implemented for all font types yet.
+
+=item transform(matrix=>$matrix)
+
+Applies a transformation to the font, where matrix is an array ref of
+numbers representing a 2 x 3 matrix:
+
+  [  $matrix->[0],  $matrix->[1],  $matrix->[2],
+     $matrix->[3],  $matrix->[4],  $matrix->[5]   ]
+
+Not all font types support transformations, these will return false.
+
+It's possible that a driver will disable hinting if you use a
+transformation, to prevent discontinuities in the transformations.
+See the end of the test script t/t38ft2font.t for an example.
 
 =item logo
 
@@ -305,10 +482,87 @@ Imager::Font->new(file=>"arial.ttf", color=>$blue, aa=>1)
             ->string(text=>"Plan XYZ", border=>5)
             ->write(file=>"xyz.png");
 
+=back
+
+=head1 UTF8
+
+There are 2 ways of rendering Unicode characters with Imager:
 
+=over
+
+=item *
+
+For versions of perl that support it, use perl's native UTF8 strings.
+This is the simplest method.
+
+=item *
+
+Hand build your own UTF8 encoded strings.  Only recommended if your
+version of perl has no UTF8 support.
 
 =back
 
+Imager won't construct characters for you, so if want to output
+unicode character 00C3 "LATIN CAPITAL LETTER A WITH DIAERESIS", and
+your font doesn't support it, Imager will I<not> build it from 0041
+"LATIN CAPITAL LETTER A" and 0308 "COMBINING DIAERESIS".
+
+=head2 Native UTF8 Support
+
+If your version of perl supports UTF8 and the driver supports UTF8,
+just use the $im->string() method, and it should do the right thing.
+
+=head2 Build your own
+
+In this case you need to build your own UTF8 encoded characters.
+
+For example:
+
+ $x = pack("C*", 0xE2, 0x80, 0x90); # character code 0x2010 HYPHEN
+
+You need to be be careful with versions of perl that have UTF8
+support, since your string may end up doubly UTF8 encoded.
+
+For example:
+
+ $x = "A\xE2\x80\x90\x41\x{2010}";
+ substr($x, -1, 0) = ""; 
+ # at this point $x is has the UTF8 flag set, but has 5 characters,
+ # none, of which is the constructed UTF8 character
+
+The test script t/t38ft2font.t has a small example of this after the 
+comment:
+
+  # an attempt using emulation of UTF8
+
+=head1 DRIVER CONTROL
+
+If you don't supply a 'type' parameter to Imager::Font->new(), but you
+do supply a 'file' parameter, Imager will attempt to guess which font
+driver to used based on the extension of the font file.
+
+Since some formats can be handled by more than one driver, a priority
+list is used to choose which one should be used, if a given format can
+be handled by more than one driver.
+
+The current priority can be retrieved with:
+
+  @drivers = Imager::Font->priorities();
+
+You can set new priorities and save the old priorities with:
+
+  @old = Imager::Font->priorities(@drivers);
+
+If you supply driver names that are not currently supported, they will
+be ignored.
+
+Imager supports both T1Lib and Freetype2 for working with Type 1
+fonts, but currently only T1Lib does any caching, so by default T1Lib
+is given a higher priority.  Since Imager's Freetype2 support can also
+do font transformations, you may want to give that a higher priority:
+
+  my @old = Imager::Font->priorities(qw(tt ft2 t1));
+
 =head1 AUTHOR
 
 Arnar M. Hrafnkelsson, addi@umich.edu
diff --git a/lib/Imager/Font/FreeType2.pm b/lib/Imager/Font/FreeType2.pm
new file mode 100644 (file)
index 0000000..c0d9bce
--- /dev/null
@@ -0,0 +1,158 @@
+package Imager::Font::FreeType2;
+use strict;
+use Imager::Color;
+use vars qw(@ISA);
+@ISA = qw(Imager::Font);
+sub new {
+  my $class = shift;
+  my %hsh=(color=>Imager::Color->new(255,0,0,0),
+          size=>15,
+          @_);
+
+  unless ($hsh{file}) {
+    $Imager::ERRSTR = "No font file specified";
+    return;
+  }
+  unless (-e $hsh{file}) {
+    $Imager::ERRSTR = "Font file $hsh{file} not found";
+    return;
+  }
+  unless ($Imager::formats{ft2}) {
+    $Imager::ERRSTR = "Freetype2 not supported in this build";
+    return;
+  }
+  my $id = i_ft2_new($hsh{file}, $hsh{index} || 0);
+  unless ($id) { # the low-level code may miss some error handling
+    $Imager::ERRSTR = Imager::_error_as_msg();
+    return;
+  }
+  return bless {
+               id       => $id,
+               aa       => $hsh{aa} || 0,
+               file     => $hsh{file},
+               type     => 't1',
+               size     => $hsh{size},
+               color    => $hsh{color},
+                utf8     => $hsh{utf8},
+                vlayout  => $hsh{vlayout},
+              }, $class;
+}
+
+sub _draw {
+  my $self = shift;
+  my %input = @_;
+  if (exists $input{channel}) {
+    i_ft2_cp($self->{id}, $input{image}{IMG}, $input{x}, $input{'y'},
+             $input{channel}, $input{size}, $input{sizew} || 0,
+             $input{string}, , $input{align}, $input{aa}, $input{vlayout},
+             $input{utf8});
+  } else {
+    i_ft2_text($self->{id}, $input{image}{IMG}, 
+               $input{x}, $input{'y'}, 
+               $input{color}, $input{size}, $input{sizew} || 0,
+               $input{string}, $input{align}, $input{aa}, $input{vlayout},
+               $input{utf8});
+  }
+}
+
+sub _bounding_box {
+  my $self = shift;
+  my %input = @_;
+  return i_ft2_bbox($self->{id}, $input{size}, $input{sizew}, $input{string});
+}
+
+sub dpi {
+  my $self = shift;
+  my @old = i_ft2_getdpi($self->{id});
+  if (@_) {
+    my %hsh = @_;
+    my $result;
+    unless ($hsh{xdpi} && $hsh{ydpi}) {
+      if ($hsh{dpi}) {
+        $hsh{xdpi} = $hsh{ydpi} = $hsh{dpi};
+      }
+      else {
+        $Imager::ERRSTR = "dpi method requires xdpi and ydpi or just dpi";
+        return;
+      }
+      i_ft2_setdpi($self->{id}, $hsh{xdpi}, $hsh{ydpi}) or return;
+    }
+  }
+  
+  return @old;
+}
+
+sub hinting {
+  my ($self, %opts) = @_;
+
+  i_ft2_sethinting($self->{id}, $opts{hinting} || 0);
+}
+
+sub _transform {
+  my $self = shift;
+
+  my %hsh = @_;
+  my $matrix = $hsh{matrix} or return undef;
+
+  return i_ft2_settransform($self->{id}, $matrix)
+}
+
+sub utf8 {
+  return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+  Imager::Font::FreeType2 - low-level functions for FreeType2 text output
+
+=head1 DESCRIPTION
+
+Imager::Font creates a Imager::Font::FreeType2 object when asked to.
+
+See Imager::Font to see how to use this type.
+
+This class provides low-level functions that require the caller to
+perform data validation.
+
+This driver supports:
+
+=over
+
+=item transform()
+
+=item dpi()
+
+=item draw()
+
+The following parameters:
+
+=over
+
+=item utf8
+
+=item vlayour
+
+=item sizew
+
+=back
+
+=back
+
+=head2 Special behaviors
+
+If you call transform() to set a transformation matrix, hinting will
+be switched off.  This prevents sudden jumps in the size of the text
+caused by the hinting when the transformation is the identity matrix.
+If for some reason you want hinting enabled, use
+$font->hinting(hinting=>1) to re-enable hinting.  This will need to be
+called after I<each> call to transform().
+
+=head1 AUTHOR
+
+Addi, Tony
+
+=cut
diff --git a/lib/Imager/Font/Win32.pm b/lib/Imager/Font/Win32.pm
new file mode 100644 (file)
index 0000000..cd4b94e
--- /dev/null
@@ -0,0 +1,55 @@
+package Imager::Font::Win32;
+use strict;
+use vars qw(@ISA);
+@ISA = qw(Imager::Font);
+
+# called by Imager::Font::new()
+# since Win32's HFONTs include the size information this
+# is just a stub
+sub new {
+  my ($class, %opts) = @_;
+
+  return bless \%opts, $class;
+}
+
+sub _bounding_box {
+  my ($self, %opts) = @_;
+  
+  my @bbox = i_wf_bbox($self->{face}, $opts{size}, $opts{string});
+}
+
+sub _draw {
+  my $self = shift;
+
+  my %input = @_;
+  if (exists $input{channel}) {
+    Imager::i_wf_cp($self->{face}, $input{image}{IMG}, $input{x}, $input{'y'},
+                   $input{channel}, $input{size},
+                   $input{string}, $input{align}, $input{aa});
+  }
+  else {
+    Imager::i_wf_text($self->{face}, $input{image}{IMG}, $input{x}, 
+                     $input{'y'}, $input{color}, $input{size}, 
+                     $input{string}, $input{align}, $input{aa});
+  }
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Imager::Font::Win32 - uses Win32 GDI services for text output
+
+=head1 SYNOPSIS
+
+  my $font = Imager::Font->new(face=>"Arial");
+
+=head1 DESCRIPTION
+
+Implements font support using Win32 GDI calls.  See Imager::Font for 
+usage information.
+
+=cut
diff --git a/lib/Imager/Matrix2d.pm b/lib/Imager/Matrix2d.pm
new file mode 100644 (file)
index 0000000..bab68c0
--- /dev/null
@@ -0,0 +1,390 @@
+package Imager::Matrix2d;
+use strict;
+
+=head1 NAME
+
+  Imager::Matrix2d - simple wrapper for matrix construction
+
+=head1 SYNOPSIS
+
+  use Imager::Matrix2d;
+  $m1 = Imager::Matrix2d->identity;
+  $m2 = Imager::Matrix2d->rotate(radians=>$angle, x=>$cx, y=>$cy);
+  $m3 = Imager::Matrix2d->translate(x=>$dx, y=>$dy);
+  $m4 = Imager::Matrix2d->shear(x=>$sx, y=>$sy);
+  $m5 = Imager::Matrix2d->reflect(axis=>$axis);
+  $m6 = Imager::Matrix2d->scale(x=>$xratio, y=>$yratio);
+  $m6 = $m1 * $m2;
+  $m7 = $m1 + $m2;
+  use Imager::Matrix2d qw(:handy);
+  # various m2d_* functions imported 
+  # where m2d_(.*) calls Imager::Matrix2d->$1()
+
+=head1 DESCRIPTION
+
+This class provides a simple wrapper around a reference to an array of
+9 co-efficients, treated as a matrix:
+
+ [ 0, 1, 2,
+   3, 4, 5,
+   6, 7, 8 ]
+
+Most of the methods in this class are constructors.  The others are
+overloaded operators.
+
+Note that since Imager represents images with y increasing from top to
+bottom, rotation angles are clockwise, rather than counter-clockwise.
+
+=over
+
+=cut
+
+use vars qw(@EXPORT_OK %EXPORT_TAGS @ISA);
+@ISA = 'Exporter';
+require 'Exporter.pm';
+@EXPORT_OK = qw(m2d_rotate m2d_identity m2d_translate m2d_shear 
+                m2d_reflect m2d_scale);
+%EXPORT_TAGS =
+  (
+   handy=> [ qw(m2d_rotate m2d_identity m2d_translate m2d_shear 
+                m2d_reflect m2d_scale) ],
+  );
+
+use overload 
+  '*' => \&_mult,
+  '+' => \&_add,
+  '""'=>\&_string;
+
+=item identity()
+
+Returns the identity matrix.
+
+=cut
+
+sub identity {
+  return bless [ 1, 0, 0,
+                 0, 1, 0,
+                 0, 0, 1 ], $_[0];
+}
+
+=item rotate(radians=>$angle)
+
+=item rotate(degrees=>$angle)
+
+Creates a matrix that rotates around the origin, or around the point
+(x,y) if the 'x' and 'y' parameters are provided.
+
+=cut
+
+sub rotate {
+  my ($class, %opts) = @_;
+  my $angle;
+
+  if (defined $opts{radians}) {
+    $angle = $opts{radians};
+  }
+  elsif (defined $opts{degrees}) {
+    $angle = $opts{degrees} * 3.1415926535 / 180;
+  }
+  else {
+    $Imager::ERRSTR = "degrees or radians parameter required";
+    return undef;
+  }
+
+  if ($opts{x} && $opts{'y'}) {
+    return $class->translate(x=>-$opts{x}, 'y'=>-$opts{'y'})
+      * $class->rotate(radians=>$angle)
+        * $class->translate(x=>$opts{x}, 'y'=>$opts{'y'});
+  }
+  else {
+    my $sin = sin($angle);
+    my $cos = cos($angle);
+    return bless [ $cos, -$sin, 0,
+                   $sin,  $cos, 0,
+                   0,     0,    1 ], $class;
+  }
+}
+
+=item translate(x=>$dx, y=>$dy)
+
+Translates by the specify amounts.
+
+=cut
+sub translate {
+  my ($class, %opts) = @_;
+
+  if (defined $opts{x} && defined $opts{'y'}) {
+    return bless [ 1, 0, $opts{x},
+                   0, 1, $opts{'y'},
+                   0, 0, 1 ], $class;
+  }
+  
+  $Imager::ERRSTR = 'x and y parameters required';
+  return undef;
+}
+
+=item shear(x=>$sx, y=>$sy)
+
+Shear by the given amounts.
+
+=cut
+sub shear {
+  my ($class, %opts) = @_;
+
+  if (defined $opts{x} || defined $opts{'y'}) {
+    return bless [ 1,             $opts{x}||0, 0,
+                   $opts{'y'}||0, 1,           0,
+                   0,             0,           1 ], $class;
+  }
+  $Imager::ERRSTR = 'x and y parameters required';
+  return undef;
+}
+
+=item reflect(axis=>$axis)
+
+Reflect around the given axis, either 'x' or 'y'.
+
+=item reflect(radians=>$angle)
+
+=item reflect(degrees=>$angle)
+
+Reflect around a line drawn at the given angle from the origin.
+
+=cut
+
+sub reflect {
+  my ($class, %opts) = @_;
+  
+  if (defined $opts{axis}) {
+    my $result = $class->identity;
+    if ($opts{axis} eq "y") {
+      $result->[0] = -$result->[0];
+    }
+    elsif ($opts{axis} eq "x") {
+      $result->[4] = -$result->[4];
+    }
+    else {
+      $Imager::ERRSTR = 'axis must be x or y';
+      return undef;
+    }
+
+    return $result;
+  }
+  my $angle;
+  if (defined $opts{radians}) {
+    $angle = $opts{radians};
+  }
+  elsif (defined $opts{degrees}) {
+    $angle = $opts{degrees} * 3.1415926535 / 180;
+  }
+  else {
+    $Imager::ERRSTR = 'axis, degrees or radians parameter required';
+    return undef;
+  }
+
+  # fun with matrices
+  return $class->rotate(radians=>-$angle) * $class->reflect(axis=>'x') 
+    * $class->rotate(radians=>$angle);
+}
+
+=item scale(x=>$xratio, y=>$yratio)
+
+Scales at the given ratios.
+
+You can also specify a center for the scaling with the cx and cy
+parameters.
+
+=cut
+
+sub scale {
+  my ($class, %opts) = @_;
+
+  if (defined $opts{x} || defined $opts{'y'}) {
+    $opts{x} = 1 unless defined $opts{x};
+    $opts{'y'} = 1 unless defined $opts{'y'};
+    if ($opts{cx} || $opts{cy}) {
+      return $class->translate(x=>-$opts{cx}, 'y'=>-$opts{cy})
+        * $class->scale(x=>$opts{x}, 'y'=>$opts{'y'})
+          * $class->translate(x=>$opts{cx}, 'y'=>$opts{cy});
+    }
+    else {
+      return bless [ $opts{x}, 0,          0,
+                     0,        $opts{'y'}, 0,
+                     0,        0,          1 ], $class;
+    }
+  }
+  else {
+    $Imager::ERRSTR = 'x or y parameter required';
+    return undef;
+  }
+}
+
+=item _mult()
+
+Implements the overloaded '*' operator.  Internal use.
+
+Currently both the left and right-hand sides of the operator must be
+an Imager::Matrix2d.
+
+=cut
+sub _mult {
+  my ($left, $right, $order) = @_;
+
+  if (ref($right) && UNIVERSAL::isa($right, __PACKAGE__)) {
+    if ($order) {
+      ($left, $right) = ($right, $left);
+    }
+    my @result;
+    for my $i (0..2) {
+      for my $j (0..2) {
+        my $accum = 0;
+        for my $k (0..2) {
+          $accum += $left->[3*$i + $k] * $right->[3*$k + $j];
+        }
+        $result[3*$i+$j] = $accum;
+      }
+    }
+    return bless \@result, __PACKAGE__;
+  }
+  else {
+    # presumably N * matrix or matrix * N
+    return undef; # for now
+  }
+}
+
+=item _add()
+
+Implements the overloaded binary '+' operator.
+
+Currently both the left and right sides of the operator must be
+Imager::Matrix2d objects.
+
+=cut
+sub _add {
+  my ($left, $right, $order) = @_;
+
+  if (ref($right) && UNIVERSAL::isa($right, __PACKAGE__)) {
+    my @result;
+    for (0..8) {
+      push @result, $left->[$_] + $right->[$_];
+    }
+    
+    return bless \@result, __PACKAGE__;
+  }
+  else {
+    return undef;
+  }
+}
+
+=item _string()
+
+Implements the overloaded stringification operator.
+
+This returns a string containing 3 lines of text with no terminating
+newline.
+
+I tried to make it fairly nicely formatted.  You might disagree :)
+
+=cut
+sub _string {
+  my ($m) = @_;
+
+  my $maxlen = 0;
+  for (@$m[0..8]) {
+    if (length() > $maxlen) {
+      $maxlen = length;
+    }
+  }
+  $maxlen <= 9 or $maxlen = 9;
+
+  my @left = ('[ ', '  ', '  ');
+  my @right = ("\n", "\n", ']');
+  my $out;
+  my $width = $maxlen+2;
+  for my $i (0..2) {
+    $out .= $left[$i];
+    for my $j (0..2) {
+      my $val = $m->[$i*3+$j];
+      if (length $val > 9) {
+        $val = sprintf("%9f", $val);
+        if ($val =~ /\./ && $val !~ /e/i) {
+          $val =~ s/0+$//;
+          $val =~ s/\.$//;
+        }
+        $val =~ s/^\s//;
+      }
+      $out .= sprintf("%-${width}s", "$val, ");
+    }
+    $out =~ s/ +\Z/ /;
+    $out .= $right[$i];
+  }
+  $out;
+}
+
+=back
+
+The following functions are shortcuts to the various constructors.
+
+These are not methods.
+
+You can import these methods with:
+
+  use Imager::Matrix2d ':handy';
+
+=over
+
+=item m2d_identity
+
+=item m2d_rotate()
+
+=item m2d_translate()
+
+=item m2d_shear()
+
+=item m2d_reflect()
+
+=back
+
+=cut
+
+sub m2d_identity {
+  return __PACKAGE__->identity;
+}
+
+sub m2d_rotate {
+  return __PACKAGE__->rotate(@_);
+}
+
+sub m2d_translate {
+  return __PACKAGE__->translate(@_);
+}
+
+sub m2d_shear {
+  return __PACKAGE__->shear(@_);
+}
+
+sub m2d_reflect {
+  return __PACKAGE__->reflect(@_);
+}
+
+sub m2d_scale {
+  return __PACKAGE__->scale(@_);
+}
+
+1;
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 BUGS
+
+Needs a way to invert matrixes.
+
+=head1 SEE ALSO
+
+Imager(3), Imager::Font(3)
+
+http://www.eecs.umich.edu/~addi/perl/Imager/
+
+=cut
index 3ccaeed..077c805 100644 (file)
@@ -247,6 +247,7 @@ EOS
    circleripple=>
    {
     type=>'rpnexpr',
+    desc=>'Adds a circular ripple effect',
     rpnexpr=><<'EOS',
 x y cx cy distance !dist
 @dist freq / sin !scale
@@ -268,6 +269,7 @@ EOS
    spiral=>
    {
     type=>'rpnexpr',
+    desc=>'Render a colorful spiral',
     rpnexpr=><<'EOS',
 x y cx cy distance !d y cy - x cx - atan2 !a
 @d spacing / @a + pi 2 * % !a2 
@@ -493,7 +495,7 @@ Returns a list of input image descriptions, or the number of them,
 depending on content.
 
 The list contains hashrefs, which current contain only one member,
-desc, a description of the use of the image.
+desc, a description of the use of the input image.
 
 =item my $out = $tran->transform(\%opts, \%constants, @imgs)
 
diff --git a/lib/Imager/interface.pod b/lib/Imager/interface.pod
new file mode 100644 (file)
index 0000000..1ab1cfb
--- /dev/null
@@ -0,0 +1,337 @@
+=head1 NAME
+
+Imager::interface.pod - decribes the virtual image interface
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+The Imager virtual interface aims to allow image types to be created
+for special purposes, both to allow consistent access to images with
+different sample sizes, and organizations, but also to allow creation
+of synthesized or virtual images.
+
+This is a C level interface rather than Perl.
+
+=head2 Existing Images
+
+As of this writing we have the following concrete image types:
+
+=over
+
+=item *
+
+8-bit/sample direct images
+
+=item *
+
+16-bit/sample direct images
+
+=item *
+
+8-bit/sample 8-bit/index paletted images
+
+=back
+
+Currently there is only one virtual image type:
+
+=over
+
+=item *
+
+masked images, where a mask image can control write access to an
+underlying image.
+
+=back
+
+Other possible concrete images include:
+
+=over
+
+=item *
+
+"bitmaps", 1 bit/sample images (perhaps limited to a single channel)
+
+=item *
+
+16-bit/index paletted images
+
+=back
+
+Some other possible virtual images:
+
+=over
+
+=item *
+
+image alpha combining, where the combining function can be specified
+(see the layer modes in graphical editors like the GIMP or photoshop.
+
+=back
+
+=head1 THE INTERFACE
+
+Each image type needs to define a number of functions which implement
+the image operations.
+
+The image structure includes information describes the image, which
+can be used to determine the structure of the image:
+
+=over
+
+=item channels
+
+the number of samples kept for each pixel in the image.  For paletted
+images the samples are kept for each entry in the palette.
+
+=item xsize, ysize
+
+the dimensions of the image in pixels.
+
+=item bytes
+
+the number of bytes of data kept for the image.  Zero for virtual
+images.  Does not include the space required for the palette for
+paletted images.
+
+=item ch_mask
+
+controls which samples will be written to for direct images.
+
+=item bits
+
+the number of bits kept for each sample.  There are enum values
+i_8_bits, i_16_bits and i_double_bits (64).
+
+=item type
+
+the type of image, either i_direct_type or i_palette_type.  Direct
+images keep the samples for every pixel image, while i_palette_type
+images keep an index into a color table for each pixel.
+
+=item virtual
+
+whether the image keeps any pixel data.  If this is non-zero then
+idata points to image data, otherwise it points to implementation
+defined data, though ext_data is more likely to be used for that.
+
+=item idata
+
+image data.  If the image is 8-bit direct, non-virtual, then this
+consists of each sample of the image stored one after another,
+otherwise it is implementation defined.
+
+=item tags
+
+will be used to store meta-data for an image, eg. tags from a TIFF
+file, or animation information from a GIF file.  Currently unused.
+
+=item ext_data
+
+for internal use of image types.  This is not released by the standard
+i_img_exorcise() function.  If you create a new image type and want to
+store a pointer to allocated memory here you should point i_f_destroy
+at a function that will release the data.
+
+=back
+
+If a caller has no knowledge of the internal format of an image, the
+caller must call the appropriate image function pointer.  Imager
+provides macros that wrap these functions, so it isn't necessary to
+call them directly.
+
+Many functions have a similar function with an 'f' suffix, these take
+or return samples specified with floating point values rather than
+8-bit integers (unsigned char).  Floating point samples are returned
+in the range 0 to 1 inclusive.
+
+=over
+
+=item i_f_ppix(im, x, y, color)
+
+=item i_f_ppixf(im, x, y, fcolor)
+
+stores the specified color at pixel (x,y) in the image.  If the pixel
+can be stored return 0, otherwise -1.  An image type may choose to
+return 0 under some circumstances, eg. writing to a masked area of an
+image.  The color or fcolor always contains the actual samples to be
+written, rather than a palette index.
+
+=item i_f_plin(im, l, r, y, colors)
+
+=item i_f_plinf(im, l, r, y, fcolors)
+
+stores (r-l) pixels at positions (l,y) ... (r-1, y) from the array
+specified by colors (or fcolors).  Returns the number of pixels
+written to.  If l is negative it will return 0.  If r > im->xsize then
+only (im->xsize - l) will be written.
+
+=item i_f_gpix(im, x, y, color)
+
+=item i_f_gpixf(im, x, y, fcolor)
+
+retrieves a single pixel from position (x,y).  This returns the
+samples rather than the index for paletted images.
+
+=item i_f_glin(im, l, r, y, colors)
+
+=item i_f_glinf(im, l, r, y, fcolors)
+
+retrieves (r-l) pixels from positions (l, y) through (r-1, y) into the
+array specified by colors.  Returns the number of pixels retrieved.
+If l < 0 no pixels are retrieved.  If r > im->xsize then pixels (l, y)
+... (im->xsize-1, y) are retrieved.  Retrieves the samples rather than
+the color indexes for paletted images.
+
+=item i_f_gsamp(im, l, r, y, samples, chans, chan_count)
+
+=item i_f_gsampf(im, l, r, y, fsamples, chans, chan_count)
+
+Retrieves samples from channels specified by chans (for length
+chan_count) from pixels at positions (l,y) ... (r-1, y).  If chans is
+NULL then samples from channels 0 ... chan_count-1 will be retrieved.
+Returns the number of sample retrieved (_not_ the number of channels).
+If a channel in chans is not present in the image or l < 0, returns 0.
+If r > im->xsize, then the samples from (l,y) ... (im->xsize-1, y) are
+returned.
+
+=back
+
+The following are for images where type == i_palette_type only.
+
+=over
+
+=item i_f_gpal(im, l, r, y, vals)
+
+Retrieves color indexes from the image for pixels (l, y) ... (r-1, y)
+into vals.  Returns the number of indexes retrieved.
+
+=item i_f_ppal(im, l, r, y, vals)
+
+Stores color indexes into the image for pixels (l, y) ... (r-1, y)
+from vals.  Returns the number of indexes retrieved.  If indices are
+outside the range of the images palette, then you may have problems
+reading those pixels with i_gpix() or i_glin().
+
+=item i_f_addcolors(im, colors, count)
+
+Adds the count colors to the image's palette.  Returns the index of
+the first color added, or -1 if there is not enough space for count
+colors.
+
+=item i_f_getcolors(im, index, colors, count)
+
+Retrieves count colors from the image's palette starting from entry
+index in the palette.  Returns non-zero on success.
+
+=item i_f_colorcount(im)
+
+Returns the number of colors in the image's palette.  Returns -1 if
+this is not a paletted image.
+
+=item i_f_maxcolors(im)
+
+Returns the maximum number of colors that can fit in the image's
+palette.  Returns -1 if this is not a paletted image.
+
+=item i_f_findcolor(im, color, entry)
+
+Searches the image's palette for the specified color, setting *entry
+to the index and returning non-zero.  Returns zero if the color is not
+found.
+
+=item i_f_setcolors_t(im, index, colors, count)
+
+Sets count colors starting from index in the image from the array
+colors.  The colors to be set must already have entries in the image's
+palette.  Returns non-zero on success.
+
+=back
+
+Finally, the i_f_destroy function pointer can be set which is called
+when the image is destroyed.  This can be used to release memory
+pointed to by ext_data or release any other resources.
+
+When writing to a paletted image with i_ppix() or i_plin() and the
+color you are writing doesn't exist in the image, then it's possible
+that the image will be internally converted to a direct image with the
+same number of channels.
+
+=head1 TOOLS
+
+Several functions have been written to simplify creating new image types.
+
+These tools are available by including imagei.h.
+
+=head2 Floating point wrappers
+
+These functions implement the floating point sample versions of each
+interface function in terms of the integer sample version.
+
+These are:
+
+=over
+
+=item i_ppixf_fp
+
+=item i_gpixf_fp
+
+=item i_plinf_fp
+
+=item i_glinf_fp
+
+=item i_gsampf_fp
+
+=back
+
+
+=head2 Forwarding functions
+
+These functions are used in virtual images where the call should
+simply be forwarded to the underlying image.  The underlying image is
+assumed to be the first pointer in a structure pointed at by ext_data.
+
+If this is not the case then these functions will just crash :)
+
+=over
+
+=item i_addcolors_forward
+
+=item i_getcolors_forward
+
+=item i_colorcount_forward
+
+=item i_maxcolors_forward
+
+=item i_findcolor_forward
+
+=item i_setcolors_forward
+
+=back
+
+=head2 Sample macros
+
+Imagei.h defines several macros for converting samples between
+different sizes.
+
+Each macro is of the form SampleI<size>ToI<size> where I<size> is one
+of 8, 16, or F (for floating-point samples).
+
+=over
+
+=item SampleFTo16(sample)
+
+=item Sample16ToF(sample)
+
+=item SampleFTo8(sample)
+
+=item Sample8ToF(sample)
+
+=item Sample16To8(num)
+
+=item Sample8To16(num)
+
+=back
+
+=cut
diff --git a/maskimg.c b/maskimg.c
new file mode 100644 (file)
index 0000000..8039711
--- /dev/null
+++ b/maskimg.c
@@ -0,0 +1,514 @@
+/*
+=head1 NAME
+
+maskimg.c - implements masked images/image subsets
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=over
+=cut
+*/
+
+#include "image.h"
+#include "imagei.h"
+
+#include <stdio.h>
+/*
+=item i_img_mask_ext
+
+A pointer to this type of object is kept in the ext_data of a masked 
+image.
+
+=cut
+*/
+
+typedef struct {
+  i_img *targ;
+  i_img *mask;
+  int xbase, ybase;
+  i_sample_t *samps; /* temp space */
+} i_img_mask_ext;
+
+#define MASKEXT(im) ((i_img_mask_ext *)((im)->ext_data))
+
+static void i_destroy_masked(i_img *im);
+static int i_ppix_masked(i_img *im, int x, int y, i_color *pix);
+static int i_ppixf_masked(i_img *im, int x, int y, i_fcolor *pix);
+static int i_plin_masked(i_img *im, int l, int r, int y, i_color *vals);
+static int i_plinf_masked(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_gpix_masked(i_img *im, int x, int y, i_color *pix);
+static int i_gpixf_masked(i_img *im, int x, int y, i_fcolor *pix);
+static int i_glin_masked(i_img *im, int l, int r, int y, i_color *vals);
+static int i_glinf_masked(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_gsamp_masked(i_img *im, int l, int r, int y, i_sample_t *samp, 
+                          int *chans, int chan_count);
+static int i_gsampf_masked(i_img *im, int l, int r, int y, i_fsample_t *samp, 
+                           int *chans, int chan_count);
+static int i_gpal_masked(i_img *im, int l, int r, int y, i_palidx *vals);
+static int i_ppal_masked(i_img *im, int l, int r, int y, i_palidx *vals);
+
+/*
+=item IIM_base_masked
+
+The basic data we copy into a masked image.
+
+=cut
+*/
+static i_img IIM_base_masked =
+{
+  0, /* channels set */
+  0, 0, 0, /* xsize, ysize, bytes */
+  ~0, /* ch_mask */
+  i_8_bits, /* bits */
+  i_palette_type, /* type */
+  1, /* virtual */
+  NULL, /* idata */
+  { 0, 0, NULL }, /* tags */
+  NULL, /* ext_data */
+
+  i_ppix_masked, /* i_f_ppix */
+  i_ppixf_masked, /* i_f_ppixf */
+  i_plin_masked, /* i_f_plin */
+  i_plinf_masked, /* i_f_plinf */
+  i_gpix_masked, /* i_f_gpix */
+  i_gpixf_masked, /* i_f_gpixf */
+  i_glin_masked, /* i_f_glin */
+  i_glinf_masked, /* i_f_glinf */
+  i_gsamp_masked, /* i_f_gsamp */
+  i_gsampf_masked, /* i_f_gsampf */
+
+  i_gpal_masked, /* i_f_gpal */
+  i_ppal_masked, /* i_f_ppal */
+  i_addcolors_forward, /* i_f_addcolors */
+  i_getcolors_forward, /* i_f_getcolors */
+  i_colorcount_forward, /* i_f_colorcount */
+  i_maxcolors_forward, /* i_f_maxcolors */
+  i_findcolor_forward, /* i_f_findcolor */
+  i_setcolors_forward, /* i_f_setcolors */
+
+  i_destroy_masked, /* i_f_destroy */
+};
+
+/*
+=item i_img_masked_new(i_img *targ, i_img *mask, int xbase, int ybase, int w, int h)
+
+Create a new masked image.
+
+The image mask is optional, in which case the image is just a view of
+a rectangular portion of the image.
+
+The mask only has an effect of writing to the image, the entire view
+of the underlying image is readable.
+
+pixel access to mimg(x,y) is translated to targ(x+xbase, y+ybase), as long 
+as (0 <= x < w) and (0 <= y < h).
+
+For a pixel to be writable, the pixel mask(x,y) must have non-zero in
+it's first channel.  No scaling of the pixel is done, the channel 
+sample is treated as boolean.
+
+=cut
+*/
+
+i_img *i_img_masked_new(i_img *targ, i_img *mask, int x, int y, int w, int h) {
+  i_img *im;
+  i_img_mask_ext *ext;
+
+  i_clear_error();
+  if (x >= targ->xsize || y >= targ->ysize) {
+    i_push_error(0, "subset outside of target image");
+    return NULL;
+  }
+  if (mask) {
+    if (w > mask->xsize)
+      w = mask->xsize;
+    if (h > mask->ysize)
+      h = mask->ysize;
+  }
+  if (x+w > targ->xsize)
+    w = targ->xsize - x;
+  if (y+h > targ->ysize)
+    h = targ->ysize - y;
+
+  im = mymalloc(sizeof(i_img));
+  memcpy(im, &IIM_base_masked, sizeof(i_img));
+  im->xsize = w;
+  im->ysize = h;
+  im->channels = targ->channels;
+  im->bits = targ->bits;
+  im->type = targ->type;
+  ext = mymalloc(sizeof(*ext));
+  ext->targ = targ;
+  ext->mask = mask;
+  ext->xbase = x;
+  ext->ybase = y;
+  ext->samps = mymalloc(sizeof(i_sample_t) * im->xsize);
+  im->ext_data = ext;
+
+  return im;
+}
+
+/*
+=item i_destroy_masked(i_img *im)
+
+The destruction handler for masked images.
+
+Releases the ext_data.
+
+Internal function.
+
+=cut
+*/
+
+static void i_destroy_masked(i_img *im) {
+  myfree(MASKEXT(im)->samps);
+  myfree(im->ext_data);
+}
+
+/*
+=item i_ppix_masked(i_img *im, int x, int y, i_color *pix)
+
+Write a pixel to a masked image.
+
+Internal function.
+
+=cut
+*/
+static int i_ppix_masked(i_img *im, int x, int y, i_color *pix) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  int result;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
+    return -1;
+  if (ext->mask) {
+    i_sample_t samp;
+    
+    if (i_gsamp(ext->mask, x, x+1, y, &samp, NULL, 1) && !samp)
+      return 0; /* pretend it was good */
+  }
+  result = i_ppix(ext->targ, x + ext->xbase, y + ext->ybase, pix);
+  im->type = ext->targ->type;
+  return result;
+}
+
+/*
+=item i_ppixf_masked(i_img *im, int x, int y, i_fcolor *pix)
+
+Write a pixel to a masked image.
+
+Internal function.
+
+=cut
+*/
+static int i_ppixf_masked(i_img *im, int x, int y, i_fcolor *pix) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  int result;
+
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
+    return -1;
+  if (ext->mask) {
+    i_sample_t samp;
+    
+    if (i_gsamp(ext->mask, x, x+1, y, &samp, NULL, 1) && !samp)
+      return 0; /* pretend it was good */
+  }
+  result = i_ppixf(ext->targ, x + ext->xbase, y + ext->ybase, pix);
+  im->type = ext->targ->type;
+  return result;
+}
+
+/*
+=item i_plin_masked(i_img *im, int l, int r, int y, i_color *vals)
+
+Write a row of data to a masked image.
+
+Internal function.
+
+=cut
+*/
+static int i_plin_masked(i_img *im, int l, int r, int y, i_color *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  int result;
+
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (ext->mask) {
+      int i;
+      int simple = 0;
+      i_sample_t *samps = ext->samps;
+      int w = r - l;
+
+      i_gsamp(ext->mask, l, r, y, samps, NULL, 1);
+      if (w < 10)
+        simple = 0;
+      else {
+        /* the idea is to make a fast scan to see how often the state
+           changes */
+        int changes = 0;
+        for (i = 0; i < w-1; ++i)
+          if (!samps[i] != !samps[i+1])
+            ++changes;
+        if (changes > w/3) /* just rough */
+          simple = 1;
+      }
+      if (simple) {
+        /* we'd be calling a usually more complicated i_plin function
+           almost as often as the usually simple i_ppix(), so just
+           do a simple scan
+        */
+        for (i = 0; i < w; ++i) {
+          if (samps[i])
+            i_ppix(ext->targ, l + i + ext->xbase, y + ext->ybase, vals + i);
+        }
+        im->type = ext->targ->type;
+        return r-l;
+      }
+      else {
+        /* the scan above indicates there should be some contiguous 
+           regions, look for them and render
+        */
+        int start;
+        i = 0;
+        while (i < w) {
+          while (i < w && !samps[i])
+            ++i;
+          start = i;
+          while (i < w && samps[i])
+            ++i;
+          if (i != start)
+            i_plin(ext->targ, l + start + ext->xbase, l + i + ext->xbase, 
+                   y + ext->ybase, vals + start);
+        }
+        im->type = ext->targ->type;
+        return w;
+      }
+    }
+    else {
+      int result = i_plin(ext->targ, l + ext->xbase, r + ext->xbase, 
+                          y + ext->ybase, vals);
+      im->type = ext->targ->type;
+      return result;
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_plinf_masked(i_img *im, int l, int r, int y, i_fcolor *vals)
+
+Write a row of data to a masked image.
+
+Internal function.
+
+=cut
+*/
+static int i_plinf_masked(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (ext->mask) {
+      int i;
+      int simple = 0;
+      i_sample_t *samps = ext->samps;
+      int w = r - l;
+
+      i_gsamp(ext->mask, l, r, y, samps, NULL, 1);
+      if (w < 10)
+        simple = 0;
+      else {
+        /* the idea is to make a fast scan to see how often the state
+           changes */
+        int changes = 0;
+        for (i = 0; i < w-1; ++i)
+          if (!samps[i] != !samps[i+1])
+            ++changes;
+        if (changes > w/3) /* just rough */
+          simple = 1;
+      }
+      if (simple) {
+        /* we'd be calling a usually more complicated i_plin function
+           almost as often as the usually simple i_ppix(), so just
+           do a simple scan
+        */
+        for (i = 0; i < w; ++i) {
+          if (samps[i])
+            i_ppixf(ext->targ, l + i + ext->xbase, y + ext->ybase, vals+i);
+        }
+        im->type = ext->targ->type;
+        return r-l;
+      }
+      else {
+        /* the scan above indicates there should be some contiguous 
+           regions, look for them and render
+        */
+        int start;
+        i = 0;
+        while (i < w) {
+          while (i < w && !samps[i])
+            ++i;
+          start = i;
+          while (i < w && samps[i])
+            ++i;
+          if (i != start)
+            i_plinf(ext->targ, l + start + ext->xbase, l + i + ext->xbase, 
+                    y + ext->ybase, vals + start);
+        }
+        im->type = ext->targ->type;
+        return w;
+      }
+    }
+    else {
+      int result = i_plinf(ext->targ, l + ext->xbase, r + ext->xbase, 
+                           y + ext->ybase, vals);
+      im->type = ext->targ->type;
+      return result;
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gpix_masked(i_img *im, int x, int y, i_color *pix)
+
+Read a pixel from a masked image.
+
+Internal.
+
+=cut
+*/
+static int i_gpix_masked(i_img *im, int x, int y, i_color *pix) {
+  i_img_mask_ext *ext = MASKEXT(im);
+
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
+    return -1;
+
+  return i_gpix(ext->targ, x + ext->xbase, y + ext->ybase, pix);
+}
+
+/*
+=item i_gpixf_masked(i_img *im, int x, int y, i_fcolor *pix)
+
+Read a pixel from a masked image.
+
+Internal.
+
+=cut
+*/
+static int i_gpixf_masked(i_img *im, int x, int y, i_fcolor *pix) {
+  i_img_mask_ext *ext = MASKEXT(im);
+
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
+    return -1;
+
+  return i_gpixf(ext->targ, x + ext->xbase, y + ext->ybase, pix);
+}
+
+static int i_glin_masked(i_img *im, int l, int r, int y, i_color *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    return i_glin(ext->targ, l + ext->xbase, r + ext->xbase, 
+                  y + ext->ybase, vals);
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_glinf_masked(i_img *im, int l, int r, int y, i_fcolor *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    return i_glinf(ext->targ, l + ext->xbase, r + ext->xbase, 
+                  y + ext->ybase, vals);
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_gsamp_masked(i_img *im, int l, int r, int y, i_sample_t *samp, 
+                          int *chans, int chan_count) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    return i_gsamp(ext->targ, l + ext->xbase, r + ext->xbase, 
+                  y + ext->ybase, samp, chans, chan_count);
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_gsampf_masked(i_img *im, int l, int r, int y, i_fsample_t *samp, 
+                          int *chans, int chan_count) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    return i_gsampf(ext->targ, l + ext->xbase, r + ext->xbase, 
+                    y + ext->ybase, samp, chans, chan_count);
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_gpal_masked(i_img *im, int l, int r, int y, i_palidx *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    return i_gpal(ext->targ, l + ext->xbase, r + ext->xbase, 
+                  y + ext->ybase, vals);
+  }
+  else {
+    return 0;
+  }
+}
+
+static int i_ppal_masked(i_img *im, int l, int r, int y, i_palidx *vals) {
+  i_img_mask_ext *ext = MASKEXT(im);
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    if (ext->mask) {
+      int i;
+      i_sample_t *samps = ext->samps;
+      int w = r - l;
+      int start;
+      
+      i = 0;
+      while (i < w) {
+        while (i < w && !samps[i])
+          ++i;
+        start = i;
+        while (i < w && samps[i])
+          ++i;
+        if (i != start)
+          i_ppal(ext->targ, l+start+ext->xbase, l+i+ext->xbase, 
+                 y+ext->ybase, vals+start);
+      }
+      return w;
+    }
+    else {
+      return i_ppal(ext->targ, l + ext->xbase, r + ext->xbase, 
+                    y + ext->ybase, vals);
+    }
+  }
+  else {
+    return 0;
+  }
+}
+
diff --git a/palimg.c b/palimg.c
new file mode 100644 (file)
index 0000000..0a989f8
--- /dev/null
+++ b/palimg.c
@@ -0,0 +1,555 @@
+/*
+=head1 NAME
+
+  palimg.c - implements paletted images for Imager.
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+Implements paletted images using the new image interface.
+
+=over
+
+=item IIM_base_8bit_pal
+
+Basic 8-bit/sample paletted image
+
+=cut
+*/
+
+#include "image.h"
+#include "imagei.h"
+
+#define PALEXT(im) ((i_img_pal_ext*)((im)->ext_data))
+static int i_ppix_p(i_img *im, int x, int y, i_color *val);
+static int i_gpix_p(i_img *im, int x, int y, i_color *val);
+static int i_glin_p(i_img *im, int l, int r, int y, i_color *vals);
+static int i_plin_p(i_img *im, int l, int r, int y, i_color *vals);
+static int i_gsamp_p(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count);
+static int i_gpal_p(i_img *pm, int l, int r, int y, i_palidx *vals);
+static int i_ppal_p(i_img *pm, int l, int r, int y, i_palidx *vals);
+static int i_addcolors_p(i_img *im, i_color *color, int count);
+static int i_getcolors_p(i_img *im, int i, i_color *color, int count);
+static int i_colorcount_p(i_img *im);
+static int i_maxcolors_p(i_img *im);
+static int i_findcolor_p(i_img *im, i_color *color, i_palidx *entry);
+static int i_setcolors_p(i_img *im, int index, i_color *color, int count);
+
+static void i_destroy_p(i_img *im);
+
+static i_img IIM_base_8bit_pal =
+{
+  0, /* channels set */
+  0, 0, 0, /* xsize, ysize, bytes */
+  ~0, /* ch_mask */
+  i_8_bits, /* bits */
+  i_palette_type, /* type */
+  0, /* virtual */
+  NULL, /* idata */
+  { 0, 0, NULL }, /* tags */
+  NULL, /* ext_data */
+
+  i_ppix_p, /* i_f_ppix */
+  i_ppixf_fp, /* i_f_ppixf */
+  i_plin_p, /* i_f_plin */
+  i_plinf_fp, /* i_f_plinf */
+  i_gpix_p, /* i_f_gpix */
+  i_gpixf_fp, /* i_f_gpixf */
+  i_glin_p, /* i_f_glin */
+  i_glinf_fp, /* i_f_glinf */
+  i_gsamp_p, /* i_f_gsamp */
+  i_gsampf_fp, /* i_f_gsampf */
+
+  i_gpal_p, /* i_f_gpal */
+  i_ppal_p, /* i_f_ppal */
+  i_addcolors_p, /* i_f_addcolors */
+  i_getcolors_p, /* i_f_getcolors */
+  i_colorcount_p, /* i_f_colorcount */
+  i_maxcolors_p, /* i_f_maxcolors */
+  i_findcolor_p, /* i_f_findcolor */
+  i_setcolors_p, /* i_f_setcolors */
+
+  i_destroy_p, /* i_f_destroy */
+};
+
+/*
+=item i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal)
+
+Creates a new paletted image.
+
+Currently 0 < maxpal <= 256
+
+=cut
+*/
+i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
+  i_img_pal_ext *palext;
+
+  i_clear_error();
+  if (maxpal < 0 || maxpal > 256) {
+    i_push_error(0, "Maximum of 256 palette entries");
+    return NULL;
+  }
+  if (x < 1 || y < 1) {
+    i_push_error(0, "Image sizes must be positive");
+    return NULL;
+  }
+  if (channels < 1 || channels > MAXCHANNELS) {
+    i_push_errorf(0, "Channels must be postive and <= %d", MAXCHANNELS);
+    return NULL;
+  }
+
+  memcpy(im, &IIM_base_8bit_pal, sizeof(i_img));
+  palext = mymalloc(sizeof(i_img_pal_ext));
+  palext->pal = mymalloc(sizeof(i_color) * maxpal);
+  palext->count = 0;
+  palext->alloc = maxpal;
+  palext->last_found = -1;
+  im->ext_data = palext;
+  i_tags_new(&im->tags);
+  im->bytes = sizeof(i_palidx) * x * y;
+  im->idata = mymalloc(im->bytes);
+  im->channels = channels;
+  im->xsize = x;
+  im->ysize = y;
+  
+  return im;
+}
+
+i_img *i_img_pal_new(int x, int y, int channels, int maxpal) {
+  i_img *im = mymalloc(sizeof(i_img));
+
+  return i_img_pal_new_low(im, x, y, channels, maxpal);
+}
+
+/*
+=item i_img_rgb_convert(i_img *targ, i_img *src)
+
+Converts paletted data in src to RGB data in targ
+
+Internal function.
+
+src must be a paletted image and targ must be an RGB image with the
+same width, height and channels.
+
+=cut
+*/
+static void i_img_rgb_convert(i_img *targ, i_img *src) {
+  i_color *row = mymalloc(sizeof(i_color) * targ->xsize);
+  int y;
+  for (y = 0; y < targ->ysize; ++y) {
+    i_glin(src, 0, src->xsize, y, row);
+    i_plin(targ, 0, src->xsize, y, row);
+  }
+  myfree(row);
+}
+
+/*
+=item i_img_to_rgb_inplace(im)
+
+Converts im from a paletted image to an RGB image.
+
+The conversion is done in place.
+
+The conversion cannot be done for virtual images.
+
+=cut
+*/
+int i_img_to_rgb_inplace(i_img *im) {
+  i_img temp;
+  i_color *pal;
+  int palsize;
+
+  if (im->virtual)
+    return 0;
+
+  if (im->type == i_direct_type)
+    return 1; /* trivial success */
+
+  i_img_empty_ch(&temp, im->xsize, im->ysize, im->channels);
+  i_img_rgb_convert(&temp, im);
+
+  /* nasty hack */
+  (im->i_f_destroy)(im);
+  myfree(im->idata);
+  *im = temp;
+
+  return 1;
+}
+
+/*
+=item i_img_to_pal(i_img *im, i_quantize *quant)
+
+Converts an RGB image to a paletted image
+
+=cut
+*/
+i_img *i_img_to_pal(i_img *src, i_quantize *quant) {
+  i_palidx *result;
+  i_img *im;
+  
+  im = i_img_pal_new(src->xsize, src->ysize, src->channels, quant->mc_size);
+
+  quant_makemap(quant, &src, 1);
+  result = quant_translate(quant, src);
+
+  /* copy things over */
+  memcpy(im->idata, result, im->bytes);
+  PALEXT(im)->count = quant->mc_count;
+  memcpy(PALEXT(im)->pal, quant->mc_colors, sizeof(i_color) * quant->mc_count);
+
+  myfree(result);
+
+  return im;
+}
+
+/*
+=item i_img_to_rgb(i_img *src)
+
+=cut
+*/
+i_img *i_img_to_rgb(i_img *src) {
+  i_img *im = i_img_empty_ch(NULL, src->xsize, src->ysize, src->channels);
+  i_img_rgb_convert(im, src);
+
+  return im;
+}
+
+/*
+=item i_destroy_p(i_img *im)
+
+Destroys data related to a paletted image.
+
+=cut
+*/
+static void i_destroy_p(i_img *im) {
+  if (im) {
+    i_img_pal_ext *palext = im->ext_data;
+    if (palext) {
+      if (palext->pal)
+        myfree(palext->pal);
+      myfree(palext);
+    }
+  }
+}
+
+/*
+=item i_ppix_p(i_img *im, int x, int y, i_color *val)
+
+Write to a pixel in the image.
+
+Warning: converts the image to a RGB image if the color isn't already
+present in the image.
+
+=cut
+*/
+int i_ppix_p(i_img *im, int x, int y, i_color *val) {
+  i_palidx which;
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
+    return -1;
+  if (i_findcolor(im, val, &which)) {
+    ((i_palidx *)im->idata)[x + y * im->xsize] = which;
+    return 0;
+  }
+  else {
+    if (i_img_to_rgb_inplace(im)) {
+      return i_ppix(im, x, y, val);
+    }
+    else
+      return -1;
+  }
+}
+
+/*
+=item i_gpix(i_img *im, int x, int y, i_color *val)
+
+Retrieve a pixel, converting from a palette index to a color.
+
+=cut
+*/
+int i_gpix_p(i_img *im, int x, int y, i_color *val) {
+  i_palidx which;
+  if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+    return -1;
+  }
+  which = ((i_palidx *)im->idata)[x + y * im->xsize];
+  if (which > PALEXT(im)->count)
+    return -1;
+  *val = PALEXT(im)->pal[which];
+
+  return 0;
+}
+
+/*
+=item i_glinp(i_img *im, int l, int r, int y, i_color *vals)
+
+Retrieve a row of pixels.
+
+=cut
+*/
+int i_glin_p(i_img *im, int l, int r, int y, i_color *vals) {
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    int palsize = PALEXT(im)->count;
+    i_color *pal = PALEXT(im)->pal;
+    i_palidx *data;
+    int count, i;
+    if (r > im->xsize)
+      r = im->xsize;
+    data = ((i_palidx *)im->idata) + l + y * im->xsize;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      i_palidx which = *data++;
+      if (which < palsize)
+        vals[i] = pal[which];
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_plin_p(i_img *im, int l, int r, int y, i_color *vals)
+
+Write a line of color data to the image.
+
+If any color value is not in the image when the image is converted to 
+RGB.
+
+=cut
+*/
+int i_plin_p(i_img *im, int l, int r, int y, i_color *vals) {
+  int ch, count, i;
+  i_palidx *data;
+  i_palidx which;
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    if (r > im->xsize)
+      r = im->xsize;
+    data = ((i_palidx *)im->idata) + l + y * im->xsize;
+    count = r - l;
+    for (i = 0; i < count; ++i) {
+      if (i_findcolor(im, vals+i, &which)) {
+        ((i_palidx *)data)[i] = which;
+      }
+      else {
+        if (i_img_to_rgb_inplace(im)) {
+          return i+i_plin(im, l+i, r, y, vals+i);
+        }
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gsamp_p(i_img *im, int l, int r, int y, i_sample_t *samps, int chans, int chan_count)
+
+=cut
+*/
+int i_gsamp_p(i_img *im, int l, int r, int y, i_sample_t *samps, 
+              int *chans, int chan_count) {
+  int ch;
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    int palsize = PALEXT(im)->count;
+    i_color *pal = PALEXT(im)->pal;
+    i_palidx *data;
+    int count, i, w;
+    if (r > im->xsize)
+      r = im->xsize;
+    data = ((i_palidx *)im->idata) + l + y * im->xsize;
+    count = 0;
+    w = r - l;
+    if (chans) {
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+        }
+      }
+
+      for (i = 0; i < w; ++i) {
+        i_palidx which = *data++;
+        if (which < palsize) {
+          for (ch = 0; ch < chan_count; ++ch) {
+            *samps++ = pal[which].channel[chans[ch]];
+            ++count;
+          }
+        }
+      }
+    }
+    else {
+      for (i = 0; i < w; ++i) {
+        i_palidx which = *data++;
+        if (which < palsize) {
+          for (ch = 0; ch < chan_count; ++ch) {
+            *samps++ = pal[which].channel[ch];
+            ++count;
+          }
+        }
+      }
+    }
+    return count;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_gpal_p(i_img *im, int l, int r, int y, i_palidx *vals)
+
+=cut
+*/
+
+int i_gpal_p(i_img *im, int l, int r, int y, i_palidx *vals) {
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    i_palidx *data;
+    int i, w;
+    if (r > im->xsize)
+      r = im->xsize;
+    data = ((i_palidx *)im->idata) + l + y * im->xsize;
+    w = r - l;
+    for (i = 0; i < w; ++i) {
+      *vals++ = *data++;
+    }
+    return i;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_ppal_p(i_img *im, int l, int r, int y, i_palidx *vals)
+
+=cut
+*/
+
+int i_ppal_p(i_img *im, int l, int r, int y, i_palidx *vals) {
+  if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
+    i_palidx *data;
+    int i, w;
+    if (r > im->xsize)
+      r = im->xsize;
+    data = ((i_palidx *)im->idata) + l + y * im->xsize;
+    w = r - l;
+    for (i = 0; i < w; ++i) {
+      *data++ = *vals++;
+    }
+    return i;
+  }
+  else {
+    return 0;
+  }
+}
+
+/*
+=item i_addcolors_p(i_img *im, i_color *color, int count)
+
+=cut
+*/
+int i_addcolors_p(i_img *im, i_color *color, int count) {
+  if (PALEXT(im)->count + count <= PALEXT(im)->alloc) {
+    int result = PALEXT(im)->count;
+    int index = result;
+
+    PALEXT(im)->count += count;
+    while (count) {
+      PALEXT(im)->pal[index++] = *color++;
+      --count;
+    }
+
+    return result;
+  }
+  else
+    return -1;
+}
+
+/*
+=item i_getcolors_p(i_img *im, int i, i_color *color, int count)
+
+=cut
+*/
+int i_getcolors_p(i_img *im, int i, i_color *color, int count) {
+  if (i >= 0 && i+count <= PALEXT(im)->count) {
+    while (count) {
+      *color++ = PALEXT(im)->pal[i++];
+      --count;
+    }
+    return 1;
+  }
+  else
+    return 0;
+}
+
+static int color_eq(i_img *im, i_color *c1, i_color *c2) {
+  int ch;
+  for (ch = 0; ch < im->channels; ++ch) {
+    if (c1->channel[ch] != c2->channel[ch])
+      return 0;
+  }
+  return 1;
+}
+
+/*
+=item i_colorcount_p(i_img *im)
+
+=cut
+*/
+int i_colorcount_p(i_img *im) {
+  return PALEXT(im)->count;
+}
+
+/*
+=item i_maxcolors_p(i_img *im)
+
+=cut
+*/
+int i_maxcolors_p(i_img *im) {
+  return PALEXT(im)->alloc;
+}
+
+/*
+=item i_setcolors_p(i_img *im, int index, i_color *colors, int count)
+
+=cut
+*/
+int i_setcolors_p(i_img *im, int index, i_color *colors, int count) {
+  if (index >= 0 && count >= 1 && index + count < PALEXT(im)->count) {
+    while (count) {
+      PALEXT(im)->pal[index++] = *colors++;
+      --count;
+    }
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+=item i_findcolor_p(i_img *im)
+
+=cut
+*/
+int i_findcolor_p(i_img *im, i_color *color, i_palidx *entry) {
+  if (PALEXT(im)->count) {
+    int i;
+    /* often the same color comes up several times in a row */
+    if (PALEXT(im)->last_found >= 0) {
+      if (color_eq(im, color, PALEXT(im)->pal + PALEXT(im)->last_found)) {
+        *entry = PALEXT(im)->last_found;
+        return 1;
+      }
+    }
+    for (i = 0; i < PALEXT(im)->count; ++i) {
+      if (color_eq(im, color, PALEXT(im)->pal + i)) {
+        PALEXT(im)->last_found = *entry = i;
+        return 1;
+      }
+    }
+  }
+  return 0;
+}
diff --git a/plug.h b/plug.h
index 6eaffd6..3e23b83 100644 (file)
--- a/plug.h
+++ b/plug.h
 #define i_img_setmask(im,ch_mask) (symbol_table->i_img_setmask(im,ch_mask))
 #define i_img_getmask(im) (symbol_table->i_img_getmask(im))
 
+/*
+Not needed?  The i_gpix() macro in image.h will call the right function
+directly.
 #define i_ppix(im,x,y,val) (symbol_table->i_ppix(im,x,y,val))
 #define i_gpix(im,x,y,val) (symbol_table->i_gpix(im,x,y,val))
+*/
 
 #define i_box(im, x1, y1, x2, y2,val) (symbol_table->i_box(im, x1, y1, x2, y2,val))
 #define i_draw(im, x1, y1, x2, y2,val) (symbol_table->i_draw(im, x1, y1, x2, y2,val))
diff --git a/png.c b/png.c
index ffad73e..83cea1b 100644 (file)
--- a/png.c
+++ b/png.c
@@ -95,6 +95,10 @@ i_writepng(i_img *im, int fd) {
   png_infop info_ptr;
   int width,height,y;
   volatile int cspace,channels;
+  double xres, yres;
+  int aspect_only, have_res;
+  double offx, offy;
+  char offunit[20] = "pixel";
 
   mm_log((1,"i_writepng(0x%x,fd %d)\n",im,fd));
   
@@ -163,8 +167,48 @@ i_writepng(i_img *im, int fd) {
   png_set_IHDR(png_ptr, info_ptr, width, height, 8, cspace,
               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 
+  have_res = 1;
+  if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
+    if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
+      ; /* nothing to do */
+    else
+      yres = xres;
+  }
+  else {
+    if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
+      xres = yres;
+    else
+      have_res = 0;
+  }
+  if (have_res) {
+    aspect_only = 0;
+    i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only);
+    xres /= 0.0254;
+    yres /= 0.0254;
+    png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, 
+                 aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
+  }
+
   png_write_info(png_ptr, info_ptr);
-  for (y = 0; y < height; y++) png_write_row(png_ptr, (png_bytep) &(im->data[channels*width*y]));
+  if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
+    for (y = 0; y < height; y++) 
+      png_write_row(png_ptr, (png_bytep) &(im->idata[channels*width*y]));
+  }
+  else {
+    unsigned char *data = mymalloc(im->xsize * im->channels);
+    if (data) {
+      for (y = 0; y < height; y++) {
+        i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
+        png_write_row(png_ptr, (png_bytep)data);
+      }
+      myfree(data);
+    }
+    else {
+      fclose(fp);
+      png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+      return 0;
+    }
+  }
   png_write_end(png_ptr, info_ptr);
   png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
 
@@ -181,6 +225,10 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
   png_infop info_ptr;
   int width,height,y;
   volatile int cspace,channels;
+  double xres, yres;
+  int aspect_only, have_res;
+  double offx, offy;
+  char offunit[20] = "pixel";
 
   io_glue_commit_types(ig);
   mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig));
@@ -241,9 +289,49 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
   png_set_IHDR(png_ptr, info_ptr, width, height, 8, cspace,
               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 
+  have_res = 1;
+  if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
+    if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
+      ; /* nothing to do */
+    else
+      yres = xres;
+  }
+  else {
+    if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
+      xres = yres;
+    else
+      have_res = 0;
+  }
+  if (have_res) {
+    aspect_only = 0;
+    i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only);
+    xres /= 0.0254;
+    yres /= 0.0254;
+    png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, 
+                 aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
+  }
+
   png_write_info(png_ptr, info_ptr);
 
-  for (y = 0; y < height; y++) png_write_row(png_ptr, (png_bytep) &(im->data[channels*width*y]));
+  if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
+    for (y = 0; y < height; y++) 
+      png_write_row(png_ptr, (png_bytep) &(im->idata[channels*width*y]));
+  }
+  else {
+    unsigned char *data = mymalloc(im->xsize * im->channels);
+    if (data) {
+      for (y = 0; y < height; y++) {
+        i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
+        png_write_row(png_ptr, (png_bytep)data);
+      }
+      myfree(data);
+    }
+    else {
+      fclose(fp);
+      png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+      return 0;
+    }
+  }
 
   png_write_end(png_ptr, info_ptr);
 
@@ -254,6 +342,7 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
 
 
 
+static void get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr);
 
 i_img*
 i_readpng_wiol(io_glue *ig, int length) {
@@ -322,13 +411,32 @@ i_readpng_wiol(io_glue *ig, int length) {
   im = i_img_empty_ch(NULL,width,height,channels);
 
   for (pass = 0; pass < number_passes; pass++)
-    for (y = 0; y < height; y++) { png_read_row(png_ptr,(png_bytep) &(im->data[channels*width*y]), NULL); }
+    for (y = 0; y < height; y++) { png_read_row(png_ptr,(png_bytep) &(im->idata[channels*width*y]), NULL); }
   
   png_read_end(png_ptr, info_ptr); 
   
+  get_png_tags(im, png_ptr, info_ptr);
+
   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
   
   mm_log((1,"(0x%08X) <- i_readpng_scalar\n", im));  
   
   return im;
 }
+
+static void get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr) {
+  png_uint_32 xres, yres;
+  int unit_type;
+  if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) {
+    mm_log((1,"pHYs (%d, %d) %d\n", xres, yres, unit_type));
+    if (unit_type == PNG_RESOLUTION_METER) {
+      i_tags_set_float(&im->tags, "i_xres", 0, xres * 0.0254);
+      i_tags_set_float(&im->tags, "i_yres", 0, xres * 0.0254);
+    }
+    else {
+      i_tags_addn(&im->tags, "i_xres", 0, xres);
+      i_tags_addn(&im->tags, "i_yres", 0, yres);
+      i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
+    }
+  }
+}
diff --git a/pnm.c b/pnm.c
index 350d353..2765c4c 100644 (file)
--- a/pnm.c
+++ b/pnm.c
@@ -412,14 +412,36 @@ i_writeppm(i_img *im,int fd) {
 
   if (im->channels==3) {
     sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
-    
+
     if (mywrite(fd,header,strlen(header))<0) {
       i_push_error(errno, "could not write ppm header");
       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
       return(0);
     }
     
-    rc=mywrite(fd,im->data,im->bytes);
+    if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
+      rc=mywrite(fd,im->idata,im->bytes);
+    }
+    else {
+      unsigned char *data = mymalloc(3 * im->xsize);
+      if (data != NULL) {
+        int y = 0;
+        int x, ch;
+        unsigned char *p;
+        static int rgb_chan[3] = { 0, 1, 2 };
+
+        rc = 0;
+        while (y < im->ysize && rc >= 0) {
+          i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3);
+          rc = mywrite(fd, data, im->xsize * 3);
+        }
+        myfree(data);
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+    }
     if (rc<0) {
       i_push_error(errno, "could not write ppm data");
       mm_log((1,"i_writeppm: unable to write ppm data.\n"));
@@ -434,8 +456,30 @@ i_writeppm(i_img *im,int fd) {
       mm_log((1,"i_writeppm: unable to write pgm header.\n"));
       return(0);
     }
-    
-    rc=mywrite(fd,im->data,im->bytes);
+
+    if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
+      rc=mywrite(fd,im->idata,im->bytes);
+    }
+    else {
+      unsigned char *data = mymalloc(im->xsize);
+      if (data != NULL) {
+        int y = 0;
+        int x, ch;
+        int chan = 0;
+        unsigned char *p;
+
+        rc = 0;
+        while (y < im->ysize && rc >= 0) {
+          i_gsamp(im, 0, im->xsize, y, data, &chan, 1);
+          rc = mywrite(fd, data, im->xsize);
+        }
+        myfree(data);
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+    }
     if (rc<0) {
       i_push_error(errno, "could not write pgm data");
       mm_log((1,"i_writeppm: unable to write pgm data.\n"));
@@ -466,16 +510,37 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
 
   io_glue_commit_types(ig);
 
-  if (im->channels==3) {
+  if (im->channels == 3) {
     sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
-    
-    if (ig->writecb(ig, header, strlen(header) )<0) {
+    if (ig->writecb(ig,header,strlen(header))<0) {
       i_push_error(errno, "could not write ppm header");
       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
       return(0);
     }
-    
-    rc = ig->writecb(ig, im->data, im->bytes);
+
+    if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
+      rc = ig->writecb(ig,im->idata,im->bytes);
+    }
+    else {
+      unsigned char *data = mymalloc(3 * im->xsize);
+      if (data != NULL) {
+        int y = 0;
+        int x, ch;
+        unsigned char *p;
+        static int rgb_chan[3] = { 0, 1, 2 };
+
+        rc = 0;
+        while (y < im->ysize && rc >= 0) {
+          i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3);
+          rc = ig->writecb(ig, data, im->xsize * 3);
+        }
+        myfree(data);
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+    }
     if (rc<0) {
       i_push_error(errno, "could not write ppm data");
       mm_log((1,"i_writeppm: unable to write ppm data.\n"));
@@ -485,13 +550,35 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
   else if (im->channels == 1) {
     sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n",
            im->xsize, im->ysize);
-    if (ig->writecb(ig, header, strlen(header)) < 0) {
+    if (ig->writecb(ig,header, strlen(header)) < 0) {
       i_push_error(errno, "could not write pgm header");
       mm_log((1,"i_writeppm: unable to write pgm header.\n"));
       return(0);
     }
-    
-    rc = ig->writecb(ig, im->data, im->bytes);
+
+    if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
+      rc=ig->writecb(ig,im->idata,im->bytes);
+    }
+    else {
+      unsigned char *data = mymalloc(im->xsize);
+      if (data != NULL) {
+        int y = 0;
+        int x, ch;
+        int chan = 0;
+        unsigned char *p;
+
+        rc = 0;
+        while (y < im->ysize && rc >= 0) {
+          i_gsamp(im, 0, im->xsize, y, data, &chan, 1);
+          rc = ig->writecb(ig, data, im->xsize);
+        }
+        myfree(data);
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+    }
     if (rc<0) {
       i_push_error(errno, "could not write pgm data");
       mm_log((1,"i_writeppm: unable to write pgm data.\n"));
@@ -503,6 +590,6 @@ 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);
   }
-  
+
   return(1);
 }
diff --git a/raw.c b/raw.c
index e9dba97..6b17669 100644 (file)
--- a/raw.c
+++ b/raw.c
@@ -5,6 +5,7 @@
 #include <unistd.h>
 #endif
 #include <string.h>
+#include <errno.h>
 
 
 #define TRUE 1
@@ -69,7 +70,8 @@ i_readraw_wiol(io_glue *ig, int x, int y, int datachannels, int storechannels, i
     if (rc!=inbuflen) { fprintf(stderr,"Premature end of file.\n"); exit(2); }
     interleave(inbuffer,ilbuffer,im->xsize,datachannels);
     expandchannels(ilbuffer,exbuffer,im->xsize,datachannels,storechannels);
-    memcpy(&(im->data[im->xsize*storechannels*k]),exbuffer,exbuflen);
+    /* FIXME? Do we ever want to save to a virtual image? */
+    memcpy(&(im->idata[im->xsize*storechannels*k]),exbuffer,exbuflen);
     k++;
   }
 
@@ -84,14 +86,72 @@ i_readraw_wiol(io_glue *ig, int x, int y, int datachannels, int storechannels, i
 undef_int
 i_writeraw_wiol(i_img* im, io_glue *ig) {
   int rc;
+
   io_glue_commit_types(ig);
+  i_clear_error();
   mm_log((1,"writeraw(im %p,ig %p)\n", im, ig));
   
   if (im == NULL) { mm_log((1,"Image is empty\n")); return(0); }
-  rc = ig->writecb(ig, im->data, im->bytes);
-  if (rc != im->bytes) {
-    mm_log((1,"i_writeraw: Couldn't write to file\n"));
-    return(0); 
+  if (!im->virtual) {
+    rc=ig->writecb(ig,im->idata,im->bytes);
+    if (rc!=im->bytes) { 
+      i_push_error(errno, "Could not write to file");
+      mm_log((1,"i_writeraw: Couldn't write to file\n")); 
+      return(0);
+    }
+  }
+  else {
+    int y;
+    
+    if (im->type == i_direct_type) {
+      /* just save it as 8-bits, maybe support saving higher bit count
+         raw images later */
+      int line_size = im->xsize * im->channels;
+      unsigned char *data = mymalloc(line_size);
+      if (data) {
+        int y = 0;
+        rc = line_size;
+        while (rc == line_size && y < im->ysize) {
+          i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
+          rc = ig->writecb(ig, data, line_size);
+          ++y;
+        }
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+      if (rc != line_size) {
+        i_push_error(errno, "write error");
+        return 0;
+      }
+    }
+    else {
+      /* paletted image - assumes the caller puts the palette somewhere 
+         else
+      */
+      int line_size = sizeof(i_palidx) * im->xsize;
+      i_palidx *data = mymalloc(sizeof(i_palidx) * im->xsize);
+      if (data) {
+        int y = 0;
+        rc = line_size;
+        while (rc == line_size && y < im->ysize) {
+         i_gpal(im, 0, im->xsize, y, data);
+          rc = ig->writecb(ig, data, line_size);
+          ++y;
+        }
+        myfree(data);
+      }
+      else {
+        i_push_error(0, "Out of memory");
+        return 0;
+      }
+      if (rc != line_size) {
+        i_push_error(errno, "write error");
+        return 0;
+      }
+    }
   }
+
   return(1);
 }
diff --git a/rotate.c b/rotate.c
new file mode 100644 (file)
index 0000000..5c09dcd
--- /dev/null
+++ b/rotate.c
@@ -0,0 +1,440 @@
+/*
+=head1 NAME
+
+  rotate.c - implements image rotations
+
+=head1 SYNOPSIS
+
+  i_img *i_rotate90(i_img *src, int degrees)
+
+=head1 DESCRIPTION
+
+Implements basic 90 degree rotations of an image.
+
+Other rotations will be added as tuits become available.
+
+=cut
+*/
+
+#include "image.h"
+#include <math.h> /* for floor() */
+
+i_img *i_rotate90(i_img *src, int degrees) {
+  i_img *targ;
+  int x, y;
+
+  i_clear_error();
+
+  if (degrees == 180) {
+    /* essentially the same as flipxy(..., 2) except that it's not
+       done in place */
+    targ = i_sametype(src, src->xsize, src->ysize);
+    if (src->type == i_direct_type) {
+      if (src->bits == i_8_bits) {
+        i_color *vals = mymalloc(src->xsize * sizeof(i_color));
+        for (y = 0; y < src->ysize; ++y) {
+          i_color tmp;
+          i_glin(src, 0, src->xsize, y, vals);
+          for (x = 0; x < src->xsize/2; ++x) {
+            tmp = vals[x];
+            vals[x] = vals[src->xsize - x - 1];
+            vals[src->xsize - x - 1] = tmp;
+          }
+          i_plin(targ, 0, src->xsize, src->ysize - y - 1, vals);
+        }
+        myfree(vals);
+      }
+      else {
+        i_fcolor *vals = mymalloc(src->xsize * sizeof(i_fcolor));
+        for (y = 0; y < src->ysize; ++y) {
+          i_fcolor tmp;
+          i_glinf(src, 0, src->xsize, y, vals);
+          for (x = 0; x < src->xsize/2; ++x) {
+            tmp = vals[x];
+            vals[x] = vals[src->xsize - x - 1];
+            vals[src->xsize - x - 1] = tmp;
+          }
+          i_plinf(targ, 0, src->xsize, src->ysize - y - 1, vals);
+        }
+        myfree(vals);
+      }
+    }
+    else {
+      i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx));
+
+      for (y = 0; y < src->ysize; ++y) {
+        i_palidx tmp;
+        i_gpal(src, 0, src->xsize, y, vals);
+        for (x = 0; x < src->xsize/2; ++x) {
+          tmp = vals[x];
+          vals[x] = vals[src->xsize - x - 1];
+          vals[src->xsize - x - 1] = tmp;
+        }
+        i_ppal(targ, 0, src->xsize, src->ysize - y - 1, vals);
+      }
+      
+      myfree(vals);
+    }
+
+    return targ;
+  }
+  else if (degrees == 270 || degrees == 90) {
+    int tx, txstart, txinc;
+    int ty, tystart, tyinc;
+
+    if (degrees == 270) {
+      txstart = 0;
+      txinc = 1;
+      tystart = src->xsize-1;
+      tyinc = -1;
+    }
+    else {
+      txstart = src->ysize-1;
+      txinc = -1;
+      tystart = 0;
+      tyinc = 1;
+    }
+    targ = i_sametype(src, src->ysize, src->xsize);
+    if (src->type == i_direct_type) {
+      if (src->bits == i_8_bits) {
+        i_color *vals = mymalloc(src->xsize * sizeof(i_color));
+
+        tx = txstart;
+        for (y = 0; y < src->ysize; ++y) {
+          i_glin(src, 0, src->xsize, y, vals);
+          ty = tystart;
+          for (x = 0; x < src->xsize; ++x) {
+            i_ppix(targ, tx, ty, vals+x);
+            ty += tyinc;
+          }
+          tx += txinc;
+        }
+        myfree(vals);
+      }
+      else {
+        i_fcolor *vals = mymalloc(src->xsize * sizeof(i_fcolor));
+
+        tx = txstart;
+        for (y = 0; y < src->ysize; ++y) {
+          i_glinf(src, 0, src->xsize, y, vals);
+          ty = tystart;
+          for (x = 0; x < src->xsize; ++x) {
+            i_ppixf(targ, tx, ty, vals+x);
+            ty += tyinc;
+          }
+          tx += txinc;
+        }
+        myfree(vals);
+      }
+    }
+    else {
+      i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx));
+      
+      tx = txstart;
+      for (y = 0; y < src->ysize; ++y) {
+        i_gpal(src, 0, src->xsize, y, vals);
+        ty = tystart;
+        for (x = 0; x < src->xsize; ++x) {
+          i_ppal(targ, tx, tx+1, ty, vals+x);
+          ty += tyinc;
+        }
+        tx += txinc;
+      }
+      myfree(vals);
+    }
+    return targ;
+  }
+  else {
+    i_push_error(0, "i_rotate90() only rotates at 90, 180, or 270 degrees");
+    return NULL;
+  }
+}
+
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_color interp_i_color(i_color before, i_color after, double pos,
+                              int channels) {
+  i_color out;
+  int ch;
+
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+
+  return out;
+}
+
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos,
+                                int channels) {
+  i_fcolor out;
+  int ch;
+
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+
+  return out;
+}
+
+i_img *i_matrix_transform(i_img *src, int xsize, int ysize, double *matrix) {
+  i_img *result = i_sametype(src, xsize, ysize);
+  int x, y;
+  int ch;
+  int i, j;
+  double sx, sy, sz;
+  double out[3];
+
+  if (src->type == i_direct_type) {
+    if (src->bits == i_8_bits) {
+      i_color *vals = mymalloc(xsize * sizeof(i_color));
+      i_color black;
+
+      for (ch = 0; ch < src->channels; ++ch)
+        black.channel[ch] = 0;
+
+      for (y = 0; y < ysize; ++y) {
+        for (x = 0; x < xsize; ++x) {
+          /* dividing by sz gives us the ability to do perspective 
+             transforms */
+          sz = x * matrix[6] + y * matrix[7] + matrix[8];
+          if (abs(sz) > 0.0000001) {
+            sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz;
+            sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz;
+          }
+
+          /* anything outside these ranges is either a broken co-ordinate
+             or outside the source */
+          if (abs(sz) > 0.0000001 
+              && sx >= -1 && sx < src->xsize
+              && sy >= -1 && sy < src->ysize) {
+
+            if (sx != (int)sx) {
+              if (sy != (int)sy) {
+                i_color c[2][2]; 
+                i_color ci2[2];
+                for (i = 0; i < 2; ++i)
+                  for (j = 0; j < 2; ++j)
+                    if (i_gpix(src, floor(sx)+i, floor(sy)+j, &c[j][i]))
+                      c[j][i] = black;
+                for (j = 0; j < 2; ++j)
+                  ci2[j] = interp_i_color(c[j][0], c[j][1], sx, src->channels);
+                vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels);
+              }
+              else {
+                i_color ci2[2];
+                for (i = 0; i < 2; ++i)
+                  if (i_gpix(src, floor(sx)+i, sy, ci2+i))
+                    ci2[i] = black;
+                vals[x] = interp_i_color(ci2[0], ci2[1], sx, src->channels);
+              }
+            }
+            else {
+              if (sy != (int)sy) {
+                i_color ci2[2];
+                for (i = 0; i < 2; ++i)
+                  if (i_gpix(src, sx, floor(sy)+i, ci2+i))
+                    ci2[i] = black;
+                vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels);
+              }
+              else {
+                /* all the world's an integer */
+                i_gpix(src, sx, sy, vals+x);
+              }
+            }
+          }
+          else {
+            vals[x] = black;
+          }
+        }
+        i_plin(result, 0, xsize, y, vals);
+      }
+      myfree(vals);
+    }
+    else {
+      i_fcolor *vals = mymalloc(xsize * sizeof(i_fcolor));
+      i_fcolor black;
+
+      for (ch = 0; ch < src->channels; ++ch)
+        black.channel[ch] = 0;
+
+      for (y = 0; y < ysize; ++y) {
+        for (x = 0; x < xsize; ++x) {
+          /* dividing by sz gives us the ability to do perspective 
+             transforms */
+          sz = x * matrix[6] + y * matrix[7] + matrix[8];
+          if (abs(sz) > 0.0000001) {
+            sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz;
+            sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz;
+          }
+
+          /* anything outside these ranges is either a broken co-ordinate
+             or outside the source */
+          if (abs(sz) > 0.0000001 
+              && sx >= -1 && sx < src->xsize
+              && sy >= -1 && sy < src->ysize) {
+
+            if (sx != (int)sx) {
+              if (sy != (int)sy) {
+                i_fcolor c[2][2]; 
+                i_fcolor ci2[2];
+                for (i = 0; i < 2; ++i)
+                  for (j = 0; j < 2; ++j)
+                    if (i_gpixf(src, floor(sx)+i, floor(sy)+j, &c[j][i]))
+                      c[j][i] = black;
+                for (j = 0; j < 2; ++j)
+                  ci2[j] = interp_i_fcolor(c[j][0], c[j][1], sx, src->channels);
+                vals[x] = interp_i_fcolor(ci2[0], ci2[1], sy, src->channels);
+              }
+              else {
+                i_fcolor ci2[2];
+                for (i = 0; i < 2; ++i)
+                  if (i_gpixf(src, floor(sx)+i, sy, ci2+i))
+                    ci2[i] = black;
+                vals[x] = interp_i_fcolor(ci2[0], ci2[1], sx, src->channels);
+              }
+            }
+            else {
+              if (sy != (int)sy) {
+                i_fcolor ci2[2];
+                for (i = 0; i < 2; ++i)
+                  if (i_gpixf(src, sx, floor(sy)+i, ci2+i))
+                    ci2[i] = black;
+                vals[x] = interp_i_fcolor(ci2[0], ci2[1], sy, src->channels);
+              }
+              else {
+                /* all the world's an integer */
+                i_gpixf(src, sx, sy, vals+x);
+              }
+            }
+          }
+          else {
+            vals[x] = black;
+          }
+        }
+        i_plinf(result, 0, xsize, y, vals);
+      }
+      myfree(vals);
+    }
+  }
+  else {
+    /* don't interpolate for a palette based image */
+    i_palidx *vals = mymalloc(xsize * sizeof(i_palidx));
+    i_palidx black = 0;
+    i_color min;
+    int minval;
+    int ix, iy;
+    
+    i_getcolors(src, 0, &min, 1);
+    minval = 0;
+    for (ch = 0; ch < src->channels; ++ch) {
+      minval += min.channel[ch];
+    }
+
+    /* find the darkest color */
+    for (i = 1; i < i_colorcount(src); ++i) {
+      i_color temp;
+      int tempval;
+      i_getcolors(src, i, &temp, 1);
+      tempval = 0;
+      for (ch = 0; ch < src->channels; ++ch) {
+        tempval += temp.channel[ch];
+      }
+      if (tempval < minval) {
+        black = i;
+        min = temp;
+        minval = tempval;
+      }
+    }
+
+    for (y = 0; y < ysize; ++y) {
+      for (x = 0; x < xsize; ++x) {
+        /* dividing by sz gives us the ability to do perspective 
+           transforms */
+        sz = x * matrix[6] + y * matrix[7] + matrix[8];
+        if (abs(sz) > 0.0000001) {
+          sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz;
+          sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz;
+        }
+        
+        /* anything outside these ranges is either a broken co-ordinate
+           or outside the source */
+        if (abs(sz) > 0.0000001 
+            && sx >= -0.5 && sx < src->xsize-0.5
+            && sy >= -0.5 && sy < src->ysize-0.5) {
+          
+          /* all the world's an integer */
+          ix = (int)(sx+0.5);
+          iy = (int)(sy+0.5);
+          i_gpal(src, ix, ix+1, iy, vals+x);
+        }
+        else {
+          vals[x] = black;
+        }
+      }
+      i_ppal(result, 0, xsize, y, vals);
+    }
+    myfree(vals);
+  }
+
+  return result;
+}
+
+i_matrix_mult(double *dest, double *left, double *right) {
+  int i, j, k;
+  double accum;
+  
+  for (i = 0; i < 3; ++i) {
+    for (j = 0; j < 3; ++j) {
+      accum = 0.0;
+      for (k = 0; k < 3; ++k) {
+        accum += left[3*i+k] * right[3*k+j];
+      }
+      dest[3*i+j] = accum;
+    }
+  }
+}
+
+i_img *i_rotate_exact(i_img *src, double amount) {
+  double xlate1[9] = { 0 };
+  double rotate[9];
+  double xlate2[9] = { 0 };
+  double temp[9], matrix[9];
+  int x1, x2, y1, y2, newxsize, newysize;
+
+  /* first translate the centre of the image to (0,0) */
+  xlate1[0] = 1;
+  xlate1[2] = src->xsize/2.0;
+  xlate1[4] = 1;
+  xlate1[5] = src->ysize/2.0;
+  xlate1[8] = 1;
+
+  /* rotate around (0.0) */
+  rotate[0] = cos(amount);
+  rotate[1] = sin(amount);
+  rotate[2] = 0;
+  rotate[3] = -rotate[1];
+  rotate[4] = rotate[0];
+  rotate[5] = 0;
+  rotate[6] = 0;
+  rotate[7] = 0;
+  rotate[8] = 1;
+
+  x1 = ceil(abs(src->xsize * rotate[0] + src->ysize * rotate[1]));
+  x2 = ceil(abs(src->xsize * rotate[0] - src->ysize * rotate[1]));
+  y1 = ceil(abs(src->xsize * rotate[3] + src->ysize * rotate[4]));
+  y2 = ceil(abs(src->xsize * rotate[3] - src->ysize * rotate[4]));
+  newxsize = x1 > x2 ? x1 : x2;
+  newysize = y1 > y2 ? y1 : y2;
+  /* translate the centre back to the center of the image */
+  xlate2[0] = 1;
+  xlate2[2] = -newxsize/2;
+  xlate2[4] = 1;
+  xlate2[5] = -newysize/2;
+  xlate2[8] = 1;
+  i_matrix_mult(temp, xlate1, rotate);
+  i_matrix_mult(matrix, temp, xlate2);
+
+  return i_matrix_transform(src, newxsize, newysize, matrix);
+}
diff --git a/t/t01introvert.t b/t/t01introvert.t
new file mode 100644 (file)
index 0000000..c1c725d
--- /dev/null
@@ -0,0 +1,267 @@
+#!perl -w
+# t/t01introvert.t - tests internals of image formats
+# to make sure we get expected values
+#
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+use strict;
+
+my $loaded;
+BEGIN { $| = 1; print "1..71\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use Imager qw(:handy :all);
+$loaded = 1;
+print "ok 1\n";
+
+init_log("testout/t01introvert.log",1);
+
+my $im_g = Imager::ImgRaw::new(100, 101, 1);
+
+print Imager::i_img_getchannels($im_g) == 1 
+  ? "ok 2\n" : "not ok 2 # 1 channel image channel count mismatch\n";
+print Imager::i_img_getmask($im_g) & 1 
+  ? "ok 3\n" : "not ok 3 # 1 channel image bad mask\n";
+print Imager::i_img_virtual($im_g) 
+  ? "not ok 4 # 1 channel image thinks it is virtual\n" : "ok 4\n";
+print Imager::i_img_bits($im_g) == 8
+  ? "ok 5\n" : "not ok 5 # 1 channel image has bits != 8\n";
+print Imager::i_img_type($im_g) == 0 # direct
+  ? "ok 6\n" : "not ok 6 # 1 channel image isn't direct\n";
+
+my @ginfo = Imager::i_img_info($im_g);
+print $ginfo[0] == 100 
+  ? "ok 7\n" : "not ok 7 # 1 channel image width incorrect\n";
+print $ginfo[1] == 101
+  ? "ok 8\n" : "not ok 8 # 1 channel image height incorrect\n";
+
+undef $im_g; # can we check for release after this somehow?
+
+my $im_rgb = Imager::ImgRaw::new(100, 101, 3);
+
+print Imager::i_img_getchannels($im_rgb) == 3
+  ? "ok 9\n" : "not ok 9 # 3 channel image channel count mismatch\n";
+print +(Imager::i_img_getmask($im_rgb) & 7) == 7
+  ? "ok 10\n" : "not ok 10 # 3 channel image bad mask\n";
+print Imager::i_img_bits($im_rgb) == 8
+  ? "ok 11\n" : "not ok 11 # 3 channel image has bits != 8\n";
+print Imager::i_img_type($im_rgb) == 0 # direct
+  ? "ok 12\n" : "not ok 12 # 3 channel image isn't direct\n";
+
+undef $im_rgb;
+
+my $im_pal = Imager::i_img_pal_new(100, 101, 3, 256);
+
+print $im_pal ? "ok 13\n" : "not ok 13 # couldn't make paletted image\n";
+print Imager::i_img_getchannels($im_pal) == 3
+  ? "ok 14\n" : "not ok 14 # pal img channel count mismatch\n";
+print Imager::i_img_bits($im_pal) == 8
+  ? "ok 15\n" : "not ok 15 # pal img bits != 8\n";
+print Imager::i_img_type($im_pal) == 1
+  ? "ok 16\n" : "not ok 16 # pal img isn't paletted\n";
+
+my $red = NC(255, 0, 0);
+my $green = NC(0, 255, 0);
+my $blue = NC(0, 0, 255);
+
+my $red_idx = check_add(17, $im_pal, $red, 0);
+my $green_idx = check_add(21, $im_pal, $green, 1);
+my $blue_idx = check_add(25, $im_pal, $blue, 2);
+
+# basic writing of palette indicies
+# fill with red
+Imager::i_ppal($im_pal, 0, 0, ($red_idx) x 100) == 100
+  or print "not ";
+print "ok 29\n";
+# and blue
+Imager::i_ppal($im_pal, 50, 0, ($blue_idx) x 50) == 50
+  or print "not ";
+print "ok 30\n";
+
+# make sure we get it back
+my @pals = Imager::i_gpal($im_pal, 0, 100, 0);
+grep($_ != $red_idx, @pals[0..49]) and print "not ";
+print "ok 31\n";
+grep($_ != $blue_idx, @pals[50..99]) and print "not ";
+print "ok 32\n";
+Imager::i_gpal($im_pal, 0, 100, 0) eq "\0" x 50 . "\2" x 50 or print "not ";
+print "ok 33\n";
+my @samp = Imager::i_gsamp($im_pal, 0, 100, 0, 0, 1, 2);
+@samp == 300 or print "not ";
+print "ok 34\n";
+my @samp_exp = ((255, 0, 0) x 50, (0, 0, 255) x 50);
+my $diff = array_ncmp(\@samp, \@samp_exp);
+$diff == 0 or print "not ";
+print "ok 35\n";
+my $samp = Imager::i_gsamp($im_pal, 0, 100, 0, 0, 1, 2);
+length($samp) == 300 or print "not ";
+print "ok 36\n";
+$samp eq "\xFF\0\0" x 50 . "\0\0\xFF" x 50
+  or print "not ";
+print "ok 37\n";
+
+# reading indicies as colors
+my $c_red = Imager::i_get_pixel($im_pal, 0, 0)
+  or print "not ";
+print "ok 38\n";
+color_cmp($red, $c_red) == 0
+  or print "not ";
+print "ok 39\n";
+my $c_blue = Imager::i_get_pixel($im_pal, 50, 0)
+  or print "not ";
+print "ok 40\n";
+color_cmp($blue, $c_blue) == 0
+  or print "not ";
+print "ok 41\n";
+
+# drawing with colors
+Imager::i_ppix($im_pal, 0, 0, $green) and print "not ";
+print "ok 42\n";
+# that was in the palette, should still be paletted
+print Imager::i_img_type($im_pal) == 1
+  ? "ok 43\n" : "not ok 43 # pal img isn't paletted (but still should be)\n";
+
+my $c_green = Imager::i_get_pixel($im_pal, 0, 0)
+  or print "not ";
+print "ok 44\n";
+color_cmp($green, $c_green) == 0
+  or print "not ";
+print "ok 45\n";
+
+Imager::i_colorcount($im_pal) == 3 or print "not ";
+print "ok 46\n";
+Imager::i_findcolor($im_pal, $green) == 1 or print "not ";
+print "ok 47\n";
+
+my $black = NC(0, 0, 0);
+# this should convert the image to RGB
+Imager::i_ppix($im_pal, 1, 0, $black) and print "not ";
+print "ok 48\n";
+print Imager::i_img_type($im_pal) == 0
+  ? "ok 49\n" : "not ok 49 # pal img shouldn't be paletted now\n";
+
+my %quant =
+  (
+   colors => [$red, $green, $blue, $black],
+   makemap => 'none',
+  );
+my $im_pal2 = Imager::i_img_to_pal($im_pal, \%quant);
+$im_pal2 or print "not ";
+print "ok 50\n";
+@{$quant{colors}} == 4 or print "not ";
+print "ok 51\n";
+Imager::i_gsamp($im_pal2, 0, 100, 0, 0, 1, 2) 
+  eq "\0\xFF\0\0\0\0"."\xFF\0\0" x 48 . "\0\0\xFF" x 50
+  or print "not ";
+print "ok 52\n";
+
+# test the OO interfaces
+my $impal2 = Imager->new(type=>'pseudo', xsize=>200, ysize=>201)
+  or print "not ";
+print "ok 53\n";
+$impal2->getchannels == 3 or print "not ";
+print "ok 54\n";
+$impal2->bits == 8 or print "not ";
+print "ok 55\n";
+$impal2->type eq 'paletted' or print "not ";
+print "ok 56\n";
+
+{
+  my $red_idx = $impal2->addcolors(colors=>[$red])
+    or print "not ";
+  print "ok 57\n";
+  $red_idx == 0 or print "not ";
+  print "ok 58\n";
+  my $blue_idx = $impal2->addcolors(colors=>[$blue, $green])
+    or print "not ";
+  print "ok 59\n";
+  $blue_idx == 1 or print "not ";
+  print "ok 60\n";
+  my $green_idx = $blue_idx + 1;
+  my $c = $impal2->getcolors(start=>$green_idx);
+  color_cmp($green, $c) == 0 or print "not ";
+  print "ok 61\n";
+  my @cols = $impal2->getcolors;
+  @cols == 3 or print "not ";
+  print "ok 62\n";
+  my @exp = ( $red, $blue, $green );
+  for my $i (0..2) {
+    if (color_cmp($cols[$i], $exp[$i])) {
+      print "not ";
+      last;
+    }
+  }
+  print "ok 63\n";
+  $impal2->colorcount == 3 or print "not ";
+  print "ok 64\n";
+  $impal2->maxcolors == 256 or print "not ";
+  print "ok 65\n";
+  $impal2->findcolor(color=>$blue) == 1 or print "not ";
+  print "ok 66\n";
+  $impal2->setcolors(start=>0, colors=>[ $blue, $red ]) or print "not ";
+  print "ok 67\n";
+
+  # make an rgb version
+  my $imrgb2 = $impal2->to_rgb8();
+  $imrgb2->type eq 'direct' or print "not ";
+  print "ok 68\n";
+
+  # and back again, specifying the palette
+  my @colors = ( $red, $blue, $green );
+  my $impal3 = $imrgb2->to_paletted(colors=>\@colors,
+                                    make_colors=>'none',
+                                    translate=>'closest')
+    or print "not ";
+  print "ok 69\n";
+  dump_colors(@colors);
+  print "# in image\n";
+  dump_colors($impal3->getcolors);
+  $impal3->colorcount == 3 or print "not ";
+  print "ok 70\n";
+  $impal3->type eq 'paletted' or print "not ";
+  print "ok 71\n";
+}
+
+sub check_add {
+  my ($base, $im, $color, $expected) = @_;
+  my $index = Imager::i_addcolors($im, $color)
+    or print "not ";
+  print "ok ",$base++,"\n";
+  print "# $index\n";
+  $index == $expected
+    or print "not ";
+  print "ok ",$base++,"\n";
+  my ($new) = Imager::i_getcolors($im, $index)
+    or print "not ";
+  print "ok ",$base++,"\n";
+  color_cmp($new, $color) == 0
+    or print "not ";
+  print "ok ",$base++,"\n";
+
+  $index;
+}
+
+sub color_cmp {
+  my ($l, $r) = @_;
+  my @l = $l->rgba;
+  my @r = $r->rgba;
+  return $l[0] <=> $r[0]
+    || $l[1] <=> $r[1]
+      || $l[2] <=> $r[2];
+}
+
+sub array_ncmp {
+  my ($a1, $a2) = @_;
+  my $len = @$a1 < @$a2 ? @$a1 : @$a2;
+  for my $i (0..$len-1) {
+    my $diff = $a1->[$i] <=> $a2->[$i] 
+      and return $diff;
+  }
+  return @$a1 <=> @$a2;
+}
+
+sub dump_colors {
+  for my $col (@_) {
+    print "# ", map(sprintf("%02X", $_), ($col->rgba)[0..2]),"\n";
+  }
+}
diff --git a/t/t020masked.t b/t/t020masked.t
new file mode 100644 (file)
index 0000000..bddacd3
--- /dev/null
@@ -0,0 +1,159 @@
+#!perl -w
+
+BEGIN { $| = 1; print "1..35\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use Imager qw(:all :handy);
+#use Data::Dumper;
+$loaded = 1;
+print "ok 1\n";
+init_log("testout/t020masked.log", 1);
+
+my $base_rgb = Imager::ImgRaw::new(100, 100, 3);
+# put something in there
+my $red = NC(255, 0, 0);
+my $green = NC(0, 255, 0);
+my $blue = NC(0, 0, 255);
+my $white = NC(255, 255, 255);
+my @cols = ($red, $green, $blue);
+for my $y (0..99) {
+  Imager::i_plin($base_rgb, 0, $y, ($cols[$y % 3] ) x 100);
+}
+
+# first a simple subset image
+my $s_rgb = Imager::i_img_masked_new($base_rgb, undef, 25, 25, 50, 50);
+
+print Imager::i_img_getchannels($s_rgb) == 3
+  ? "ok 2\n" : "not ok 2 # 1 channel image channel count mismatch\n";
+print Imager::i_img_getmask($s_rgb) & 1 
+  ? "ok 3\n" : "not ok 3 # 1 channel image bad mask\n";
+print Imager::i_img_virtual($s_rgb) == 0
+  ? "not ok 4 # 1 channel image thinks it isn't virtual\n" : "ok 4\n";
+print Imager::i_img_bits($s_rgb) == 8
+  ? "ok 5\n" : "not ok 5 # 1 channel image has bits != 8\n";
+print Imager::i_img_type($s_rgb) == 0 # direct
+  ? "ok 6\n" : "not ok 6 # 1 channel image isn't direct\n";
+
+my @ginfo = i_img_info($s_rgb);
+print $ginfo[0] == 50 
+  ? "ok 7\n" : "not ok 7 # image width incorrect\n";
+print $ginfo[1] == 50
+  ? "ok 8\n" : "not ok 8 # image height incorrect\n";
+
+# sample some pixels through the subset
+my $c = Imager::i_get_pixel($s_rgb, 0, 0);
+color_cmp($c, $green) == 0 or print "not ";
+print "ok 9\n";
+$c = Imager::i_get_pixel($s_rgb, 49, 49);
+# (25+49)%3 = 2
+color_cmp($c, $blue) == 0 or print "not ";
+print "ok 10\n";
+
+# try writing to it
+for my $y (0..49) {
+  Imager::i_plin($s_rgb, 0, $y, ($cols[$y % 3]) x 50);
+}
+print "ok 11\n";
+# and checking the target image
+$c = Imager::i_get_pixel($base_rgb, 25, 25);
+color_cmp($c, $red) == 0 or print "not ";
+print "ok 12\n";
+$c = Imager::i_get_pixel($base_rgb, 29, 29);
+color_cmp($c, $green) == 0 or print "not ";
+print "ok 13\n";
+
+undef $s_rgb;
+
+# a basic background
+for my $y (0..99) {
+  Imager::i_plin($base_rgb, 0, $y, ($red ) x 100);
+}
+my $mask = Imager::ImgRaw::new(50, 50, 1);
+# some venetian blinds
+for my $y (4..20) {
+  Imager::i_plin($mask, 5, $y*2, ($white) x 40);
+}
+# with a strip down the middle
+for my $y (0..49) {
+  Imager::i_plin($mask, 20, $y, ($white) x 8);
+}
+my $m_rgb = Imager::i_img_masked_new($base_rgb, $mask, 25, 25, 50, 50);
+$m_rgb or print "not ";
+print "ok 14\n";
+for my $y (0..49) {
+  Imager::i_plin($m_rgb, 0, $y, ($green) x 50);
+}
+my @color_tests =
+  (
+   [ 25+0,  25+0,  $red ],
+   [ 25+19, 25+0,  $red ],
+   [ 25+20, 25+0,  $green ],
+   [ 25+27, 25+0,  $green ],
+   [ 25+28, 25+0,  $red ],
+   [ 25+49, 25+0,  $red ],
+   [ 25+19, 25+7,  $red ],
+   [ 25+19, 25+8,  $green ],
+   [ 25+19, 25+9,  $red ],
+   [ 25+0,  25+8,  $red ],
+   [ 25+4,  25+8,  $red ],
+   [ 25+5,  25+8,  $green ],
+   [ 25+44, 25+8,  $green ],
+   [ 25+45, 25+8,  $red ],
+   [ 25+49, 25+49, $red ],
+  );
+my $test_num = 15;
+for my $test (@color_tests) {
+  color_test($test_num++, $base_rgb, @$test);
+}
+
+{
+  # tests for the OO versions, fairly simple, since the basic functionality
+  # is covered by the low-level interface tests
+   
+  my $base = Imager->new(xsize=>100, ysize=>100)
+    or print "not ";
+  print "ok 30\n";
+  $base->box(color=>$blue, filled=>1); # fill it all
+  my $mask = Imager->new(xsize=>80, ysize=>80, channels=>1);
+  $mask->box(color=>$white, filled=>1, xmin=>5, xmax=>75, ymin=>5, ymax=>75);
+  my $m_img = $base->masked(mask=>$mask, left=>5, top=>5)
+    or print "not ";
+  print "ok 31\n";
+  $m_img->getwidth == 80 or print "not ";
+  print "ok 32\n";
+  $m_img->box(color=>$green, filled=>1);
+  color_cmp(Imager::i_get_pixel($m_img->{IMG}, 0, 0), $blue) == 0 
+    or print "not ";
+  print "ok 33\n";
+  color_cmp(Imager::i_get_pixel($m_img->{IMG}, 5, 5), $green) == 0 
+    or print "not ";
+  print "ok 34\n";
+
+  # older versions destroyed the Imager::ImgRaw object manually in 
+  # Imager::DESTROY rather than letting Imager::ImgRaw::DESTROY 
+  # destroy the object
+  # so we test here by destroying the base and mask objects and trying 
+  # to draw to the masked wrapper
+  # you may need to test with ElectricFence to trigger the problem
+  undef $mask;
+  undef $base;
+  $m_img->box(color=>$blue, filled=>1);
+  print "ok 35\n";
+}
+
+sub color_test {
+  my ($num, $im, $x, $y, $expected) = @_;
+  my $c = Imager::i_get_pixel($im, $x, $y);
+  color_cmp($c, $expected) == 0 or print "not ";
+  print "ok $num # $x, $y\n";
+}
+
+sub color_cmp {
+  my ($l, $r) = @_;
+  my @l = $l->rgba;
+  my @r = $r->rgba;
+  # print "# (",join(",", @l[0..2]),") <=> (",join(",", @r[0..2]),")\n";
+  return $l[0] <=> $r[0]
+    || $l[1] <=> $r[1]
+      || $l[2] <=> $r[2];
+}
+
diff --git a/t/t021sixteen.t b/t/t021sixteen.t
new file mode 100644 (file)
index 0000000..09a8b5b
--- /dev/null
@@ -0,0 +1,107 @@
+#!perl -w
+use strict;
+BEGIN { $| = 1; print "1..29\n"; }
+my $loaded;
+END {print "not ok 1\n" unless $loaded;}
+use Imager qw(:all :handy);
+#use Data::Dumper;
+$loaded = 1;
+print "ok 1\n";
+init_log("testout/t021sixteen.t", 1);
+
+use Imager::Color::Float;
+
+my $im_g = Imager::i_img_16_new(100, 101, 1);
+
+print Imager::i_img_getchannels($im_g) == 1 
+  ? "ok 2\n" : "not ok 2 # 1 channel image channel count mismatch\n";
+print Imager::i_img_getmask($im_g) & 1 
+  ? "ok 3\n" : "not ok 3 # 1 channel image bad mask\n";
+print Imager::i_img_virtual($im_g) 
+  ? "not ok 4 # 1 channel image thinks it is virtual\n" : "ok 4\n";
+print Imager::i_img_bits($im_g) == 16
+  ? "ok 5\n" : "not ok 5 # 1 channel image has bits != 16\n";
+print Imager::i_img_type($im_g) == 0 # direct
+  ? "ok 6\n" : "not ok 6 # 1 channel image isn't direct\n";
+
+my @ginfo = i_img_info($im_g);
+print $ginfo[0] == 100 
+  ? "ok 7\n" : "not ok 7 # 1 channel image width incorrect\n";
+print $ginfo[1] == 101
+  ? "ok 8\n" : "not ok 8 # 1 channel image height incorrect\n";
+
+undef $im_g;
+
+my $im_rgb = Imager::i_img_16_new(100, 101, 3);
+
+print Imager::i_img_getchannels($im_rgb) == 3
+  ? "ok 9\n" : "not ok 9 # 3 channel image channel count mismatch\n";
+print +(Imager::i_img_getmask($im_rgb) & 7) == 7
+  ? "ok 10\n" : "not ok 10 # 3 channel image bad mask\n";
+print Imager::i_img_bits($im_rgb) == 16
+  ? "ok 11\n" : "not ok 11 # 3 channel image has bits != 16\n";
+print Imager::i_img_type($im_rgb) == 0 # direct
+  ? "ok 12\n" : "not ok 12 # 3 channel image isn't direct\n";
+
+my $redf = NCF(1, 0, 0);
+my $greenf = NCF(0, 1, 0);
+my $bluef = NCF(0, 0, 1);
+
+# fill with red
+for my $y (0..101) {
+  Imager::i_plinf($im_rgb, 0, $y, ($redf) x 100);
+}
+print "ok 13\n";
+# basic sanity
+test_colorf_gpix(14, $im_rgb, 0,  0,   $redf);
+test_colorf_gpix(16, $im_rgb, 99, 0,   $redf);
+test_colorf_gpix(18, $im_rgb, 0,  100, $redf);
+test_colorf_gpix(20, $im_rgb, 99, 100, $redf);
+test_colorf_glin(22, $im_rgb, 0,  0,   ($redf) x 100);
+test_colorf_glin(24, $im_rgb, 0,  100, ($redf) x 100);
+
+Imager::i_plinf($im_rgb, 20, 1, ($greenf) x 60);
+test_colorf_glin(26, $im_rgb, 0, 1, 
+                 ($redf) x 20, ($greenf) x 60, ($redf) x 20);
+
+# basic OO tests
+my $oo16img = Imager->new(xsize=>200, ysize=>201, bits=>16)
+  or print "not ";
+print "ok 28\n";
+$oo16img->bits == 16 or print "not ";
+print "ok 29\n";
+
+
+sub NCF {
+  return Imager::Color::Float->new(@_);
+}
+
+sub test_colorf_gpix {
+  my ($test_base, $im, $x, $y, $expected) = @_;
+  my $c = Imager::i_gpixf($im, $x, $y);
+  $c or print "not ";
+  print "ok ",$test_base++,"\n";
+  colorf_cmp($c, $expected) == 0 or print "not ";
+  print "ok ",$test_base++,"\n";
+}
+
+sub test_colorf_glin {
+  my ($test_base, $im, $x, $y, @pels) = @_;
+
+  my @got = Imager::i_glinf($im, $x, $x+@pels, $y);
+  @got == @pels or print "not ";
+  print "ok ",$test_base++,"\n";
+  grep(colorf_cmp($pels[$_], $got[$_]), 0..$#got) and print "not ";
+  print "ok ",$test_base++,"\n";
+}
+
+sub colorf_cmp {
+  my ($c1, $c2) = @_;
+  my @s1 = map { int($_*65535.99) } $c1->rgba;
+  my @s2 = map { int($_*65535.99) } $c2->rgba;
+
+  # print "# (",join(",", @s1[0..2]),") <=> (",join(",", @s2[0..2]),")\n";
+  return $s1[0] <=> $s2[0] 
+    || $s1[1] <=> $s2[1]
+      || $s1[2] <=> $s2[2];
+}
index d938de9..a28ae93 100644 (file)
@@ -7,7 +7,7 @@
 # (It may become useful if the test is moved to ./t subdirectory.)
 use lib qw(blib/lib blib/arch);
 
-BEGIN { $| = 1; print "1..10\n"; }
+BEGIN { $| = 1; print "1..12\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all);
 
@@ -36,10 +36,14 @@ i_box_filled($timg, 0, 0, 20, 20, $green);
 i_box_filled($timg, 2, 2, 18, 18, $trans);
 
 if (!i_has_format("png")) {
-  for (2..10) {
+  for (2..12) {
     print "ok $_ # skip no png support\n";
   }
 } else {
+  Imager::i_tags_add($img, "i_xres", 0, "300", 0);
+  Imager::i_tags_add($img, "i_yres", 0, undef, 200);
+  # the following confuses the GIMP
+  #Imager::i_tags_add($img, "i_aspect_only", 0, undef, 1);
   open(FH,">testout/t102.png") || die "cannot open testout/t102.png for writing\n";
   binmode(FH);
   $IO = Imager::io_new_fd(fileno(FH));
@@ -59,15 +63,22 @@ if (!i_has_format("png")) {
   print i_img_diff($img, $cmpimg)
     ? "not ok 4 # saved image different\n" : "ok 4\n";