commit changes from draw branch
authorTony Cook <tony@develop=help.com>
Tue, 25 Mar 2008 08:20:50 +0000 (08:20 +0000)
committerTony Cook <tony@develop=help.com>
Tue, 25 Mar 2008 08:20:50 +0000 (08:20 +0000)
32 files changed:
Changes
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
compose.im [new file with mode: 0644]
design/draw.pod [new file with mode: 0644]
draw.c
fills.c
filters.im
fuzz/fuzz.pl
hlines.c
image.c
imager.h
imdatatypes.h
imrender.h
imtoc.perl [deleted file]
lib/Imager/APIRef.pod
lib/Imager/Draw.pod
lib/Imager/Fill.pm
lib/Imager/Fountain.pm
lib/Imager/Preprocess.pm [new file with mode: 0644]
lib/Imager/Transformations.pod
paste.im [new file with mode: 0644]
polygon.c
render.im
rendert.h
rubthru.im
t/t20fill.t
t/t66paste.t
t/t69rubthru.t
t/x11rubthru.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index e7de08a..b635fe3 100644 (file)
--- a/Changes
+++ b/Changes
@@ -6,6 +6,29 @@ Imager 0.63 - unreleased
  - the font libraries are now only initialized when needed.
    http://rt.cpan.org/Ticket/Display.html?id=28825
 
+ - moved the imtoc.perl code into Imager::Preprocess
+
+ - paste() and rubthrough() now adapt the source image data to the
+   destination, so you can now safely paste/rubthrough from greyscale
+   images to color images or back, or from alpha channel images to
+   noalpha channels or back.
+   https://rt.cpan.org/Ticket/Display.html?id=30908
+
+ - rubthrough() now falls back to pasting when the source doesn't have
+   an alpha channel.  This effectively treats the source as having a
+   max alpha channel, the right thing to do.
+   http://rt.cpan.org/Ticket/Display.html?id=29944
+
+ - re-worked most of the area filling code to use a common set of
+   functions when filling.
+   Corrected normal combine mode.
+   Rewrote most of the combine modes to match the way the SVG draft
+   defines them with respect to a translucent source and destination.
+   Added tests for translucent source and destination.
+   Added tests to check 8-bit/sample and double/sample combines work
+   similarly.
+   https://rt.cpan.org/Ticket/Display.html?id=29879
+
 Bug fixes:
 
  - Imager::Matrix2d->translate() now only requires one of the x or y
index 08781af..b00cdf2 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -157,6 +157,19 @@ my %attempted_to_load;
 # library keys that are image file formats
 my %file_formats = map { $_ => 1 } qw/tiff pnm gif png jpeg raw bmp tga/;
 
+# image pixel combine types
+my @combine_types = 
+  qw/none normal multiply dissolve add subtract diff lighten darken
+     hue saturation value color/;
+my %combine_types;
+@combine_types{@combine_types} = 0 .. $#combine_types;
+$combine_types{mult} = $combine_types{multiply};
+$combine_types{'sub'}  = $combine_types{subtract};
+$combine_types{sat}  = $combine_types{saturation};
+
+# this will be used to store global defaults at some point
+my %defaults;
+
 BEGIN {
   require Exporter;
   @ISA = qw(Exporter);
@@ -558,6 +571,22 @@ sub _color {
   return $result;
 }
 
+sub _combine {
+  my ($self, $combine, $default) = @_;
+
+  if (!defined $combine && ref $self) {
+    $combine = $self->{combine};
+  }
+  defined $combine or $combine = $defaults{combine};
+  defined $combine or $combine = $default;
+
+  if (exists $combine_types{$combine}) {
+    $combine = $combine_types{$combine};
+  }
+  
+  return $combine;
+}
+
 sub _valid_image {
   my ($self) = @_;
 
@@ -2405,7 +2434,7 @@ sub transform2 {
 
 sub rubthrough {
   my $self=shift;
-  my %opts=(tx => 0,ty => 0, @_);
+  my %opts= @_;
 
   unless ($self->{IMG}) { 
     $self->{ERRSTR}='empty input image'; 
@@ -2422,15 +2451,108 @@ sub rubthrough {
           src_maxy => $opts{src}->getheight(),
           %opts);
 
-  unless (i_rubthru($self->{IMG}, $opts{src}->{IMG}, $opts{tx}, $opts{ty},
+  my $tx = $opts{tx};
+  defined $tx or $tx = $opts{left};
+  defined $tx or $tx = 0;
+
+  my $ty = $opts{ty};
+  defined $ty or $ty = $opts{top};
+  defined $ty or $ty = 0;
+
+  unless (i_rubthru($self->{IMG}, $opts{src}->{IMG}, $tx, $ty,
                     $opts{src_minx}, $opts{src_miny}, 
                     $opts{src_maxx}, $opts{src_maxy})) {
     $self->_set_error($self->_error_as_msg());
     return undef;
   }
+
   return $self;
 }
 
+sub compose {
+  my $self = shift;
+  my %opts =
+    ( 
+     opacity => 1.0,
+     mask_left => 0,
+     mask_top => 0,
+     @_
+    );
+
+  unless ($self->{IMG}) {
+    $self->_set_error("compose: empty input image");
+    return;
+  }
+
+  unless ($opts{src}) {
+    $self->_set_error("compose: src parameter missing");
+    return;
+  }
+  
+  unless ($opts{src}{IMG}) {
+    $self->_set_error("compose: src parameter empty image");
+    return;
+  }
+  my $src = $opts{src};
+
+  my $left = $opts{left};
+  defined $left or $left = $opts{tx};
+  defined $left or $left = 0;
+
+  my $top = $opts{top};
+  defined $top or $top = $opts{ty};
+  defined $top or $top = 0;
+
+  my $src_left = $opts{src_left};
+  defined $src_left or $src_left = $opts{src_minx};
+  defined $src_left or $src_left = 0;
+
+  my $src_top = $opts{src_top};
+  defined $src_top or $src_top = $opts{src_miny};
+  defined $src_top or $src_top = 0;
+
+  my $width = $opts{width};
+  if (!defined $width && defined $opts{src_maxx}) {
+    $width = $opts{src_maxx} - $src_left;
+  }
+  defined $width or $width = $src->getwidth() - $src_left;
+
+  my $height = $opts{height};
+  if (!defined $height && defined $opts{src_maxy}) {
+    $height = $opts{src_maxy} - $src_top;
+  }
+  defined $height or $height = $src->getheight() - $src_top;
+
+  my $combine = $self->_combine($opts{combine}, 'normal');
+
+  if ($opts{mask}) {
+    unless ($opts{mask}{IMG}) {
+      $self->_set_error("compose: mask parameter empty image");
+      return;
+    }
+
+    my $mask_left = $opts{mask_left};
+    defined $mask_left or $mask_left = $opts{mask_minx};
+    defined $mask_left or $mask_left = 0;
+    
+    my $mask_top = $opts{mask_top};
+    defined $mask_top or $mask_top = $opts{mask_miny};
+    defined $mask_top or $mask_top = 0;
+
+    i_compose_mask($self->{IMG}, $src->{IMG}, $opts{mask}{IMG}, 
+                  $left, $top, $src_left, $src_top,
+                  $mask_left, $mask_top, $width, $height, 
+                  $combine, $opts{opacity})
+      or return;
+  }
+  else {
+    i_compose($self->{IMG}, $src->{IMG}, $left, $top, $src_left, $src_top,
+             $width, $height, $combine, $opts{opacity})
+      or return;
+  }
+
+  return $self;
+}
 
 sub flip {
   my $self  = shift;
@@ -3615,6 +3737,10 @@ sub def_guess_type {
   return ();
 }
 
+sub combines {
+  return @combine_types;
+}
+
 # get the minimum of a list
 
 sub _min {
@@ -3862,7 +3988,8 @@ This example creates a completely black image of width 400 and height
 
 =head1 ERROR HANDLING
 
-In general a method will return false when it fails, if it does use the errstr() method to find out why:
+In general a method will return false when it fails, if it does use
+the errstr() method to find out why:
 
 =over
 
@@ -3911,6 +4038,10 @@ circle() - L<Imager::Draw/circle>
 
 colorcount() - L<Imager::Draw/colorcount>
 
+combines() - L<Imager::Draw/combines>
+
+compose() - L<Imager::Transformations/compose>
+
 convert() - L<Imager::Transformations/"Color transformations"> -
 transform the color space
 
index 53162f0..5feb0ea 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1640,6 +1640,34 @@ i_rubthru(im,src,tx,ty,src_minx,src_miny,src_maxx,src_maxy)
               int     src_maxx
               int     src_maxy
 
+undef_int
+i_compose(out, src, out_left, out_top, src_left, src_top, width, height, combine = ic_normal, opacity = 0.0)
+    Imager::ImgRaw out
+    Imager::ImgRaw src
+       int out_left
+       int out_top
+       int src_left
+       int src_top
+       int width
+       int height
+       int combine
+       double opacity
+
+undef_int
+i_compose_mask(out, src, mask, out_left, out_top, src_left, src_top, mask_left, mask_top, width, height, combine = ic_normal, opacity = 0.0)
+    Imager::ImgRaw out
+    Imager::ImgRaw src
+    Imager::ImgRaw mask
+       int out_left
+       int out_top
+       int src_left
+       int src_top
+       int mask_left
+       int mask_top
+       int width
+       int height
+       int combine
+       double opacity
 
 undef_int
 i_flipxy(im, direction)
index 1e9441b..3f5aa5e 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -75,6 +75,7 @@ apidocs.perl    Build lib/Imager/APIRef.pm
 bigtest.perl   Library selection tester
 bmp.c           Reading and writing Windows BMP files
 color.c         Color translation and handling
+compose.im
 conv.im
 convert.c
 doco.perl
@@ -136,7 +137,6 @@ imio.h
 immacros.h
 imperl.h
 imrender.h     Buffer rending engine function declarations
-imtoc.perl      Sample size adapter pre-processor
 io.c
 iolayer.c
 iolayer.h
@@ -170,6 +170,7 @@ lib/Imager/ImageTypes.pod
 lib/Imager/Inline.pod           Using Imager with Inline::C
 lib/Imager/LargeSamples.pod    Track large sample support
 lib/Imager/Matrix2d.pm
+lib/Imager/Preprocess.pm
 lib/Imager/Regops.pm
 lib/Imager/Test.pm
 lib/Imager/Transform.pm
@@ -183,6 +184,7 @@ log.h
 map.c
 maskimg.c
 palimg.c
+paste.im
 plug.h
 png.c
 pnm.c
index 6260711..f13b7e1 100644 (file)
@@ -156,7 +156,7 @@ my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o
               regmach.o trans2.o quant.o error.o convert.o
               map.o tags.o palimg.o maskimg.o img16.o rotate.o
               bmp.o tga.o color.o fills.o imgdouble.o limits.o hlines.o
-              imext.o scale.o rubthru.o render.o);
+              imext.o scale.o rubthru.o render.o paste.o compose.o);
 
 $Recommends{Imager} =
   { 'Parse::RecDescent' => 0 };
@@ -168,7 +168,7 @@ my %opts=(
           'DEFINE'       => "$OSDEF $CFLAGS",
           'INC'          => "$lib_cflags $DFLAGS $F_INC",
           'OBJECT'       => join(' ', @objs, $F_OBJECT),
-          clean          => { FILES=>'testout meta.tmp rubthru.c scale.c' },
+          clean          => { FILES=>'testout meta.tmp rubthru.c scale.c conv.c  filters.c gaussian.c render.c rubthru.c' },
           PM             => gen_PM(),
          PREREQ_PM      => { 'Test::More' => 0.47 },
          );
@@ -236,8 +236,8 @@ sub _im_rule {
   (my $c = $im) =~ s/\.im$/.c/;
   return <<MAKE;
 
-$c: $im imtoc.perl
-       $perl imtoc.perl $im $c
+$c: $im lib/Imager/Preprocess.pm
+       $perl -Ilib -MImager::Preprocess -epreprocess $im $c
 
 MAKE
 
diff --git a/compose.im b/compose.im
new file mode 100644 (file)
index 0000000..5e0c236
--- /dev/null
@@ -0,0 +1,211 @@
+#include "imager.h"
+#include "imrender.h"
+#include "imageri.h"
+
+int
+i_compose_mask(i_img *out, i_img *src, i_img *mask, 
+              int out_left, int out_top,
+              int src_left, int src_top,
+              int mask_left, int mask_top,
+              int width, int height,
+              int combine,
+              double opacity) {
+  i_render r;
+  int dy;
+  i_fill_combine_f combinef_8;
+  i_fill_combinef_f combinef_double;
+  int channel_zero = 0;
+
+  i_clear_error();
+  if (out_left >= out->xsize
+      || out_top >= out->ysize
+      || src_left >= src->xsize
+      || src_top >= src->ysize
+      || width <= 0
+      || height <= 0
+      || out_left + width <= 0
+      || out_top + height <= 0
+      || src_left + width <= 0
+      || src_top + height <= 0
+      || mask_left >= mask->xsize
+      || mask_top >= mask->ysize
+      || mask_left + width <= 0
+      || mask_top + height <= 0)
+    return 0;
+
+  if (out_left < 0) {
+    width = out_left + width;
+    out_left = 0;
+  }
+  if (out_left + width > out->xsize)
+    width = out->xsize - out_left;
+
+  if (out_top < 0) {
+    height = out_top + height;
+    out_top = 0;
+  }
+  if (out_top + height > out->ysize)
+    height = out->ysize - out_top;
+
+  if (src_left < 0) {
+    width = src_left + width;
+    src_left = 0;
+  }
+  if (src_left + width > src->xsize)
+    width = src->xsize - src_left;
+
+  if (src_top < 0) {
+    height = src_top + height;
+    src_top = 0;
+  }
+  if (src_top + height > src->ysize)
+    height = src->ysize - src_left;
+
+  if (mask_left < 0) {
+    width = mask_left + width;
+    mask_left = 0;
+  }
+  if (mask_left + width > mask->xsize)
+    width = mask->xsize - mask_left;
+  
+  if (mask_top < 0) {
+    height = mask->ysize + height;
+    mask_top = 0;
+  }
+  if (mask_top + height > mask->ysize)
+    height = mask->xsize - mask_top;
+
+  if (opacity > 1.0)
+    opacity = 1.0;
+  else if (opacity <= 0)
+    return 0;
+
+  i_get_combine(combine, &combinef_8, &combinef_double);
+
+  i_render_init(&r, out, width);
+#code out->bits <= 8 && src->bits<= 8 && mask->bits <= 8
+  IM_COLOR *src_line = mymalloc(sizeof(IM_COLOR) * width);
+  IM_SAMPLE_T *mask_line = mymalloc(sizeof(IM_SAMPLE_T) * width);
+  int adapt_channels = out->channels;
+
+  if (adapt_channels == 1 || adapt_channels == 3)
+    ++adapt_channels;
+
+  for (dy = 0; dy < height; ++dy) {
+    IM_GLIN(src, src_left, src_left + width, src_top + dy, src_line);
+    IM_ADAPT_COLORS(adapt_channels, src->channels, src_line, width);
+    IM_GSAMP(mask, mask_left, mask_left + width, mask_top + dy, 
+            mask_line, &channel_zero, 1);
+    if (opacity < 1.0) {
+      int i;
+      IM_SAMPLE_T *maskp = mask_line;
+      for (i = 0; i < width; ++i) {
+       *maskp = IM_ROUND(*maskp * opacity);
+       ++maskp;
+      }
+    }
+    IM_RENDER_LINE(&r, out_left, out_top+dy, width, mask_line, src_line,
+                  IM_SUFFIX(combinef));
+  }
+  myfree(src_line);
+  myfree(mask_line);
+  
+#/code
+  i_render_done(&r);
+
+  return 1;
+}
+
+int
+i_compose(i_img *out, i_img *src,
+         int out_left, int out_top,
+         int src_left, int src_top,
+         int width, int height,
+         int combine,
+         double opacity) {
+  i_render r;
+  int dy;
+  i_fill_combine_f combinef_8;
+  i_fill_combinef_f combinef_double;
+
+  i_clear_error();
+  if (out_left >= out->xsize
+      || out_top >= out->ysize
+      || src_left >= src->xsize
+      || src_top >= src->ysize
+      || width <= 0
+      || height <= 0
+      || out_left + width <= 0
+      || out_top + height <= 0
+      || src_left + width <= 0
+      || src_top + height <= 0)
+    return 0;
+
+  if (out_left < 0) {
+    width = out_left + width;
+    out_left = 0;
+  }
+  if (out_left + width > out->xsize)
+    width = out->xsize - out_left;
+
+  if (out_top < 0) {
+    height = out_top + height;
+    out_top = 0;
+  }
+  if (out_top + height > out->ysize)
+    height = out->ysize - out_top;
+
+  if (src_left < 0) {
+    width = src_left + width;
+    src_left = 0;
+  }
+  if (src_left + width > src->xsize)
+    width = src->xsize - src_left;
+
+  if (src_top < 0) {
+    height = src_top + height;
+    src_top = 0;
+  }
+  if (src_top + height > src->ysize)
+    height = src->ysize - src_left;
+
+  if (opacity > 1.0)
+    opacity = 1.0;
+  else if (opacity <= 0)
+    return 0;
+
+  i_get_combine(combine, &combinef_8, &combinef_double);
+
+  i_render_init(&r, out, width);
+#code out->bits <= 8 && src->bits <= 8
+  IM_COLOR *src_line = mymalloc(sizeof(IM_COLOR) * width);
+  IM_SAMPLE_T *mask_line = NULL;
+  int adapt_channels = out->channels;
+
+  if (opacity != 1.0) {
+    int i;
+    IM_SAMPLE_T mask_value = IM_ROUND(opacity * IM_SAMPLE_MAX);
+    mask_line = mymalloc(sizeof(IM_SAMPLE_T) * width);
+
+    for (i = 0; i < width; ++i)
+      mask_line[i] = mask_value;
+  }
+
+  if (adapt_channels == 1 || adapt_channels == 3)
+    ++adapt_channels;
+
+  for (dy = 0; dy < height; ++dy) {
+    IM_GLIN(src, src_left, src_left + width, src_top + dy, src_line);
+    IM_ADAPT_COLORS(adapt_channels, src->channels, src_line, width);
+    IM_RENDER_LINE(&r, out_left, out_top+dy, width, mask_line, src_line,
+                  IM_SUFFIX(combinef));
+  }
+  myfree(src_line);
+  if (mask_line)
+    myfree(mask_line);
+  
+#/code
+  i_render_done(&r);
+
+  return 1;
+}
diff --git a/design/draw.pod b/design/draw.pod
new file mode 100644 (file)
index 0000000..f43e737
--- /dev/null
@@ -0,0 +1,74 @@
+=head1 NAME
+
+draw.pod - overview of planned drawing changes
+
+=head1 SYNOPSIS
+
+  fix the processing done by the current combine modes
+  combine modes for most drawing operations
+  thick lines
+
+=head1 combine mode fixes
+
+Currently:
+
+=over
+
+=item *
+
+the calculations are just wrong
+
+=item *
+
+they don't handle adapting to the target channel count
+
+=back
+
+To do:
+
+=over
+
+=item *
+
+add a render function that calls the fill function then applies it to
+the target based on the combine mode.
+
+=item *
+
+modify fill functions to always produce RGBA
+
+=item *
+
+have the combine mode functions just generate the color, the render
+function can then alpha adjust it and apply it to the target
+
+=back
+
+=head1 combine modes for drawing functions
+
+=over
+
+=item *
+
+write versions of each function that take a combine mode
+
+=back
+
+=head1 thick lines
+
+Two possible approaches:
+
+=over
+
+=item *
+
+given a polyline, generate a polygon for the entire shape - more
+complex, overlap problems
+
+=item *
+
+given a polyline, generate polygons for each segment, draw to a work
+image and use that to compose the fill - possible problems joining the
+segments
+
+=back
diff --git a/draw.c b/draw.c
index 8cbe445..baeca5c 100644 (file)
--- a/draw.c
+++ b/draw.c
@@ -2,7 +2,7 @@
 #include "draw.h"
 #include "log.h"
 #include "imageri.h"
-
+#include "imrender.h"
 #include <limits.h>
 
 static void
@@ -58,64 +58,6 @@ i_mmarray_render(i_img *im,i_mmarray *ar,i_color *val) {
   for(i=0;i<ar->lines;i++) if (ar->data[i].max!=-1) for(x=ar->data[i].min;x<ar->data[i].max;x++) i_ppix(im,x,i,val);
 }
 
-void
-i_mmarray_render_fill(i_img *im,i_mmarray *ar,i_fill_t *fill) {
-  int x, w, y;
-  if (im->bits == i_8_bits && fill->fill_with_color) {
-    i_color *line = mymalloc(sizeof(i_color) * im->xsize); /* checked 5jul05 tonyc */
-    i_color *work = NULL;
-    if (fill->combine)
-      work = mymalloc(sizeof(i_color) * im->xsize); /* checked 5jul05 tonyc */
-    for(y=0;y<ar->lines;y++) {
-      if (ar->data[y].max!=-1) {
-        x = ar->data[y].min;
-        w = ar->data[y].max-ar->data[y].min;
-
-        if (fill->combine) {
-          i_glin(im, x, x+w, y, line);
-          (fill->fill_with_color)(fill, x, y, w, im->channels, work);
-          (fill->combine)(line, work, im->channels, w);
-        }
-        else {
-          (fill->fill_with_color)(fill, x, y, w, im->channels, line);
-        }
-        i_plin(im, x, x+w, y, line);
-      }
-    }
-  
-    myfree(line);
-    if (work)
-      myfree(work);
-  }
-  else {
-    i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize); /* checked 5jul05 tonyc */
-    i_fcolor *work = NULL;
-    if (fill->combinef)
-      work = mymalloc(sizeof(i_fcolor) * im->xsize); /* checked 5jul05 tonyc */
-    for(y=0;y<ar->lines;y++) {
-      if (ar->data[y].max!=-1) {
-        x = ar->data[y].min;
-        w = ar->data[y].max-ar->data[y].min;
-
-        if (fill->combinef) {
-          i_glinf(im, x, x+w, y, line);
-          (fill->fill_with_fcolor)(fill, x, y, w, im->channels, work);
-          (fill->combinef)(line, work, im->channels, w);
-        }
-        else {
-          (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line);
-        }
-        i_plinf(im, x, x+w, y, line);
-      }
-    }
-  
-    myfree(line);
-    if (work)
-      myfree(work);
-  }
-}
-
-
 static
 void
 i_arcdraw(int x1, int y1, int x2, int y2, i_mmarray *ar) {
@@ -572,6 +514,7 @@ Fills the box from (x1,y1) to (x2,y2) inclusive with fill.
 
 void
 i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) {
+  i_render r;
   mm_log((1,"i_box_cfill(im* 0x%x,x1 %d,y1 %d,x2 %d,y2 %d,fill 0x%x)\n",im,x1,y1,x2,y2,fill));
 
   ++x2;
@@ -585,51 +528,15 @@ i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) {
     y2 = im->ysize-1;
   if (x1 >= x2 || y1 > y2)
     return;
-  if (im->bits == i_8_bits && fill->fill_with_color) {
-    i_color *line = mymalloc(sizeof(i_color) * (x2 - x1)); /* checked 5jul05 tonyc */
-    i_color *work = NULL;
-    if (fill->combine)
-      work = mymalloc(sizeof(i_color) * (x2-x1)); /* checked 5jul05 tonyc */
-    while (y1 <= y2) {
-      if (fill->combine) {
-        i_glin(im, x1, x2, y1, line);
-        (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, work);
-        (fill->combine)(line, work, im->channels, x2-x1);
-      }
-      else {
-        (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line);
-      }
-      i_plin(im, x1, x2, y1, line);
-      ++y1;
-    }
-    myfree(line);
-    if (work)
-      myfree(work);
-  }
-  else {
-    i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1)); /* checked 5jul05 tonyc */
-    i_fcolor *work;
-    work = mymalloc(sizeof(i_fcolor) * (x2 - x1)); /* checked 5jul05 tonyc */
-
-    while (y1 <= y2) {
-      if (fill->combine) {
-        i_glinf(im, x1, x2, y1, line);
-        (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, work);
-        (fill->combinef)(line, work, im->channels, x2-x1);
-      }
-      else {
-        (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line);
-      }
-      i_plinf(im, x1, x2, y1, line);
-      ++y1;
-    }
-    myfree(line);
-    if (work)
-      myfree(work);
+
+  i_render_init(&r, im, x2-x1);
+  while (y1 <= y2) {
+    i_render_fill(&r, x1, y1, x2-x1, NULL, fill);
+    ++y1;
   }
+  i_render_done(&r);
 }
 
-
 /* 
 =item i_line(im, x1, y1, x2, y2, val, endp)
 
@@ -750,90 +657,6 @@ i_line_dda(i_img *im, int x1, int y1, int x2, int y2, i_color *val) {
   }
 }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-void
-i_line_aa3(i_img *im,int x1,int y1,int x2,int y2,i_color *val) {
-  i_color tval;
-  float alpha;
-  float dsec,dfrac;
-  int temp,dx,dy,isec,ch;
-
-  mm_log((1,"i_line_aa(im* 0x%x,x1 %d,y1 %d,x2 %d,y2 %d,val 0x%x)\n",im,x1,y1,x2,y2,val));
-
-  dy=y2-y1;
-  dx=x2-x1;
-
-  if (abs(dx)>abs(dy)) { /* alpha < 1 */
-    if (x2<x1) { temp=x1; x1=x2; x2=temp; temp=y1; y1=y2; y2=temp; }
-    alpha=(float)(y2-y1)/(float)(x2-x1);
-
-    dsec=y1;
-    while(x1<=x2) {
-      isec=(int)dsec;
-      dfrac=dsec-isec;
-      /*      dfrac=1-(1-dfrac)*(1-dfrac); */
-      /* This is something we can play with to try to get better looking lines */
-
-      i_gpix(im,x1,isec,&tval);
-      for(ch=0;ch<im->channels;ch++) tval.channel[ch]=(unsigned char)(dfrac*(float)tval.channel[ch]+(1-dfrac)*(float)val->channel[ch]);
-      i_ppix(im,x1,isec,&tval);
-      
-      i_gpix(im,x1,isec+1,&tval);
-      for(ch=0;ch<im->channels;ch++) tval.channel[ch]=(unsigned char)((1-dfrac)*(float)tval.channel[ch]+dfrac*(float)val->channel[ch]);
-      i_ppix(im,x1,isec+1,&tval);
-      
-      dsec+=alpha;
-      x1++;
-    }
-  } else {
-    if (y2<y1) { temp=y1; y1=y2; y2=temp; temp=x1; x1=x2; x2=temp; }
-    alpha=(float)(x2-x1)/(float)(y2-y1);
-    dsec=x1;
-    while(y1<=y2) {
-      isec=(int)dsec;
-      dfrac=dsec-isec;
-      /*      dfrac=sqrt(dfrac); */
-      /* This is something we can play with */
-      i_gpix(im,isec,y1,&tval);
-      for(ch=0;ch<im->channels;ch++) tval.channel[ch]=(unsigned char)(dfrac*(float)tval.channel[ch]+(1-dfrac)*(float)val->channel[ch]);
-      i_ppix(im,isec,y1,&tval);
-
-      i_gpix(im,isec+1,y1,&tval);
-      for(ch=0;ch<im->channels;ch++) tval.channel[ch]=(unsigned char)((1-dfrac)*(float)tval.channel[ch]+dfrac*(float)val->channel[ch]);
-      i_ppix(im,isec+1,y1,&tval);
-
-      dsec+=alpha;
-      y1++;
-    }
-  }
-}
-
-
 /*
 =item i_line_aa(im, x1, x2, y1, y2, color, endp)
 
@@ -1450,76 +1273,24 @@ cfill_from_btm(i_img *im, i_fill_t *fill, struct i_bitmap *btm,
   int x, y;
   int start;
 
-  if (im->bits == i_8_bits && fill->fill_with_color) {
-    /* bxmax/bxmin are inside the image, hence this won't overflow */
-    i_color *line = mymalloc(sizeof(i_color) * (bxmax - bxmin)); /* checked 5jul05 tonyc */
-    i_color *work = NULL;
-    if (fill->combine)
-      work = mymalloc(sizeof(i_color) * (bxmax - bxmin)); /* checked 5jul05 tonyc */
-
-    for(y=bymin; y<=bymax; y++) {
-      x = bxmin;
-      while (x < bxmax) {
-        while (x < bxmax && !btm_test(btm, x, y)) {
-          ++x;
-        }
-        if (btm_test(btm, x, y)) {
-          start = x;
-          while (x < bxmax && btm_test(btm, x, y)) {
-            ++x;
-          }
-          if (fill->combine) {
-            i_glin(im, start, x, y, line);
-            (fill->fill_with_color)(fill, start, y, x-start, im->channels, 
-                                    work);
-            (fill->combine)(line, work, im->channels, x-start);
-          }
-          else {
-            (fill->fill_with_color)(fill, start, y, x-start, im->channels, 
-                                    line);
-          }
-          i_plin(im, start, x, y, line);
-        }
+  i_render r;
+
+  i_render_init(&r, im, bxmax - bxmin + 1);
+
+  for(y=bymin; y<=bymax; y++) {
+    x = bxmin;
+    while (x <= bxmax) {
+      while (x <= bxmax && !btm_test(btm, x, y)) {
+       ++x;
       }
-    }
-    myfree(line);
-    if (work)
-      myfree(work);
-  }
-  else {
-    /* bxmax/bxmin are inside the image, hence this won't overflow */
-    i_fcolor *line = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin)); /* checked 5jul05 tonyc */
-    i_fcolor *work = NULL;
-    if (fill->combinef)
-      work = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin)); /* checked 5jul05 tonyc */
-    
-    for(y=bymin;y<=bymax;y++) {
-      x = bxmin;
-      while (x < bxmax) {
-        while (x < bxmax && !btm_test(btm, x, y)) {
-          ++x;
-        }
-        if (btm_test(btm, x, y)) {
-          start = x;
-          while (x < bxmax && btm_test(btm, x, y)) {
-            ++x;
-          }
-          if (fill->combinef) {
-            i_glinf(im, start, x, y, line);
-            (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, 
-                                    work);
-            (fill->combinef)(line, work, im->channels, x-start);
-          }
-          else {
-            (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, 
-                                    line);
-          }
-          i_plinf(im, start, x, y, line);
-        }
+      if (btm_test(btm, x, y)) {
+       start = x;
+       while (x <= bxmax && btm_test(btm, x, y)) {
+         ++x;
+       }
+       i_render_fill(&r, start, y, x-start, NULL, fill);
       }
     }
-    myfree(line);
-    if (work)
-      myfree(work);
   }
+  i_render_done(&r);
 }
diff --git a/fills.c b/fills.c
index 251d6b7..55139ed 100644 (file)
--- a/fills.c
+++ b/fills.c
@@ -166,10 +166,6 @@ static void fill_solid(i_fill_t *, int x, int y, int width, int channels,
                        i_color *);
 static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, 
                         i_fcolor *);
-static void fill_solid_comb(i_fill_t *, int x, int y, int width, int channels, 
-                            i_color *);
-static void fill_solidf_comb(i_fill_t *, int x, int y, int width, 
-                             int channels, i_fcolor *);
 
 static i_fill_solid_t base_solid_fill =
 {
@@ -181,16 +177,6 @@ static i_fill_solid_t base_solid_fill =
     NULL,
   },
 };
-static i_fill_solid_t base_solid_fill_comb =
-{
-  {
-    fill_solid_comb,
-    fill_solidf_comb,
-    NULL,
-    NULL,
-    NULL,
-  },
-};
 
 /*
 =item i_fill_destroy(fill)
@@ -228,12 +214,11 @@ i_new_fill_solidf(const i_fcolor *c, int combine) {
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */
   
+  *fill = base_solid_fill;
   if (combine) {
-    *fill = base_solid_fill_comb;
     i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
   }
-  else
-    *fill = base_solid_fill;
+
   fill->fc = *c;
   for (ch = 0; ch < MAXCHANNELS; ++ch) {
     fill->c.channel[ch] = SampleFTo8(c->channel[ch]);
@@ -260,12 +245,11 @@ i_new_fill_solid(const i_color *c, int combine) {
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */
 
+  *fill = base_solid_fill;
   if (combine) {
-    *fill = base_solid_fill_comb;
     i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
   }
-  else
-    *fill = base_solid_fill;
+
   fill->c = *c;
   for (ch = 0; ch < MAXCHANNELS; ++ch) {
     fill->fc.channel[ch] = Sample8ToF(c->channel[ch]);
@@ -490,6 +474,16 @@ struct i_fill_image_t {
   double matrix[9];
 };
 
+static struct i_fill_image_t
+image_fill_proto =
+  {
+    {
+      fill_image,
+      fill_imagef,
+      NULL
+    }
+  };
+
 /*
 =item i_new_fill_image(im, matrix, xoff, yoff, combine)
 
@@ -508,9 +502,7 @@ i_fill_t *
 i_new_fill_image(i_img *im, const double *matrix, int xoff, int yoff, int combine) {
   struct i_fill_image_t *fill = mymalloc(sizeof(*fill)); /* checked 14jul05 tonyc */
 
-  fill->base.fill_with_color = fill_image;
-  fill->base.fill_with_fcolor = fill_imagef;
-  fill->base.destroy = NULL;
+  *fill = image_fill_proto;
 
   if (combine) {
     i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
@@ -555,8 +547,10 @@ The 8-bit sample fill function for non-combining solid fills.
 static void
 fill_solid(i_fill_t *fill, int x, int y, int width, int channels, 
            i_color *data) {
+  i_color c = T_SOLID_FILL(fill)->c;
+  i_adapt_colors(channels > 2 ? 4 : 2, 4, &c, 1);
   while (width-- > 0) {
-    *data++ = T_SOLID_FILL(fill)->c;
+    *data++ = c;
   }
 }
 
@@ -570,45 +564,23 @@ The floating sample fill function for non-combining solid fills.
 static void
 fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, 
            i_fcolor *data) {
-  while (width-- > 0) {
-    *data++ = T_SOLID_FILL(fill)->fc;
-  }
-}
-
-/*
-=item fill_solid_comb(fill, x, y, width, channels, data)
-
-The 8-bit sample fill function for combining solid fills.
-
-=cut
-*/
-static void
-fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-                i_color *data) {
-  i_color c = T_SOLID_FILL(fill)->c;
-
-  while (width-- > 0) {
-    *data++ = c;
-  }
-}
-
-/*
-=item fill_solidf_comb(fill, x, y, width, channels, data)
-
-The floating sample fill function for combining solid fills.
-
-=cut
-*/
-static void
-fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_fcolor *data) {
   i_fcolor c = T_SOLID_FILL(fill)->fc;
-
+  i_adapt_fcolors(channels > 2 ? 4 : 2, 4, &c, 1);
   while (width-- > 0) {
     *data++ = c;
   }
 }
 
+static i_fill_hatch_t
+hatch_fill_proto =
+  {
+    {
+      fill_hatch,
+      fill_hatchf,
+      NULL
+    }
+  };
+
 /*
 =item i_new_hatch_low(fg, bg, ffg, fbg, combine, hatch, cust_hatch, dx, dy)
 
@@ -624,9 +596,7 @@ i_new_hatch_low(const i_color *fg, const i_color *bg,
                 int dx, int dy) {
   i_fill_hatch_t *fill = mymalloc(sizeof(i_fill_hatch_t)); /* checked 14jul05 tonyc */
 
-  fill->base.fill_with_color = fill_hatch;
-  fill->base.fill_with_fcolor = fill_hatchf;
-  fill->base.destroy = NULL;
+  *fill = hatch_fill_proto;
   /* Some Sun C didn't like the condition expressions that were here.
      See https://rt.cpan.org/Ticket/Display.html?id=21944
    */
@@ -954,467 +924,6 @@ static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
   }
 }
 
-static void combine_replace(i_color *, i_color *, int, int);
-static void combine_replacef(i_fcolor *, i_fcolor *, int, int);
-static void combine_alphablend(i_color *, i_color *, int, int);
-static void combine_alphablendf(i_fcolor *, i_fcolor *, int, int);
-static void combine_mult(i_color *, i_color *, int, int);
-static void combine_multf(i_fcolor *, i_fcolor *, int, int);
-static void combine_dissolve(i_color *, i_color *, int, int);
-static void combine_dissolvef(i_fcolor *, i_fcolor *, int, int);
-static void combine_add(i_color *, i_color *, int, int);
-static void combine_addf(i_fcolor *, i_fcolor *, int, int);
-static void combine_subtract(i_color *, i_color *, int, int);
-static void combine_subtractf(i_fcolor *, i_fcolor *, int, int);
-static void combine_diff(i_color *, i_color *, int, int);
-static void combine_difff(i_fcolor *, i_fcolor *, int, int);
-static void combine_darken(i_color *, i_color *, int, int);
-static void combine_darkenf(i_fcolor *, i_fcolor *, int, int);
-static void combine_lighten(i_color *, i_color *, int, int);
-static void combine_lightenf(i_fcolor *, i_fcolor *, int, int);
-static void combine_hue(i_color *, i_color *, int, int);
-static void combine_huef(i_fcolor *, i_fcolor *, int, int);
-static void combine_sat(i_color *, i_color *, int, int);
-static void combine_satf(i_fcolor *, i_fcolor *, int, int);
-static void combine_value(i_color *, i_color *, int, int);
-static void combine_valuef(i_fcolor *, i_fcolor *, int, int);
-static void combine_color(i_color *, i_color *, int, int);
-static void combine_colorf(i_fcolor *, i_fcolor *, int, int);
-
-static struct i_combines {
-  i_fill_combine_f combine;
-  i_fill_combinef_f combinef;
-} combines[] =
-{
-  { /* replace */
-    combine_replace,
-    combine_replacef,
-  },
-  { /* alpha blend */
-    combine_alphablend,
-    combine_alphablendf,
-  },
-  {
-    /* multiply */
-    combine_mult,
-    combine_multf,
-  },
-  {
-    /* dissolve */
-    combine_dissolve,
-    combine_dissolvef,
-  },
-  {
-    /* add */
-    combine_add,
-    combine_addf,
-  },
-  {
-    /* subtract */
-    combine_subtract,
-    combine_subtractf,
-  },
-  {
-    /* diff */
-    combine_diff,
-    combine_difff,
-  },
-  {
-    combine_lighten,
-    combine_lightenf,
-  },
-  {
-    combine_darken,
-    combine_darkenf,
-  },
-  {
-    combine_hue,
-    combine_huef,
-  },
-  {
-    combine_sat,
-    combine_satf,
-  },
-  {
-    combine_value,
-    combine_valuef,
-  },
-  {
-    combine_color,
-    combine_colorf,
-  },
-};
-
-/*
-=item i_get_combine(combine, color_func, fcolor_func)
-
-=cut
-*/
-
-void i_get_combine(int combine, i_fill_combine_f *color_func, 
-                   i_fill_combinef_f *fcolor_func) {
-  if (combine < 0 || combine > sizeof(combines) / sizeof(*combines))
-    combine = 0;
-
-  *color_func = combines[combine].combine;
-  *fcolor_func = combines[combine].combinef;
-}
-
-static void combine_replace(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    *out++ = *in++;
-  }
-}
-
-static void combine_replacef(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  while (count--) {
-    *out++ = *in++;
-  }
-}
-
-static void combine_alphablend(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    COMBINE(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_alphablendf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  while (count--) {
-    COMBINEF(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_mult(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    double mult[MAXCHANNELS];
-    mult[3] = in->channel[3];
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3)
-        mult[ch] = (out->channel[ch] * in->channel[ch]) * (1.0 / 255);
-    } 
-    COMBINEA(*out, mult, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_multf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_fcolor c = *in;
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3)
-        c.channel[ch] = out->channel[ch] * in->channel[ch];
-    } 
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_dissolve(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    if (in->channel[3] > rand() * (255.0 / RAND_MAX))
-      COMBINE(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_dissolvef(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  while (count--) {
-    if (in->channel[3] > rand() * (1.0 / RAND_MAX))
-      COMBINEF(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_add(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_color c = *in;
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3) {
-        int total = out->channel[ch] + in->channel[ch];
-        if (total > 255)
-          total = 255;
-        c.channel[ch] = total;
-      }
-    } 
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_addf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_fcolor c = *in;
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3) {
-        double total = out->channel[ch] + in->channel[ch];
-        if (total > 1.0)
-          total = 1.0;
-        out->channel[ch] = total;
-      }
-    } 
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_subtract(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_color c = *in;
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3) {
-        int total = out->channel[ch] - in->channel[ch];
-        if (total < 0)
-          total = 0;
-        c.channel[ch] = total;
-      }
-    } 
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_subtractf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_fcolor c = *in;
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3) {
-        double total = out->channel[ch] - in->channel[ch];
-        if (total < 0)
-          total = 0;
-        c.channel[ch] = total;
-      }
-    } 
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_diff(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_color c = *in;
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3) 
-        c.channel[ch] = abs(out->channel[ch] - in->channel[ch]);
-    } 
-    COMBINE(*out, c, channels)
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_difff(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    i_fcolor c = *in;
-    for (ch = 0; ch < (channels); ++ch) { 
-      if (ch != 3)
-        c.channel[ch] = fabs(out->channel[ch] - in->channel[ch]);
-    }
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_darken(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3 && out->channel[ch] < in->channel[ch])
-        in->channel[ch] = out->channel[ch];
-    } 
-    COMBINE(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_darkenf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3 && out->channel[ch] < in->channel[ch])
-        in->channel[ch] = out->channel[ch];
-    } 
-    COMBINEF(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_lighten(i_color *out, i_color *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3 && out->channel[ch] > in->channel[ch])
-        in->channel[ch] = out->channel[ch];
-    } 
-    COMBINE(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_lightenf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  int ch;
-
-  while (count--) {
-    for (ch = 0; ch < channels; ++ch) { 
-      if (ch != 3 && out->channel[ch] > in->channel[ch])
-        in->channel[ch] = out->channel[ch];
-    } 
-    COMBINEF(*out, *in, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_hue(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    i_color c = *out;
-    i_rgb_to_hsv(&c);
-    i_rgb_to_hsv(in);
-    c.channel[0] = in->channel[0];
-    i_hsv_to_rgb(&c);
-    c.channel[3] = in->channel[3];
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_huef(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  while (count--) {
-    i_fcolor c = *out;
-    i_rgb_to_hsvf(&c);
-    i_rgb_to_hsvf(in);
-    c.channel[0] = in->channel[0];
-    i_hsv_to_rgbf(&c);
-    c.channel[3] = in->channel[3];
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_sat(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    i_color c = *out;
-    i_rgb_to_hsv(&c);
-    i_rgb_to_hsv(in);
-    c.channel[1] = in->channel[1];
-    i_hsv_to_rgb(&c);
-    c.channel[3] = in->channel[3];
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_satf(i_fcolor *out, i_fcolor *in, int channels, int count) {
-  while (count--) {
-    i_fcolor c = *out;
-    i_rgb_to_hsvf(&c);
-    i_rgb_to_hsvf(in);
-    c.channel[1] = in->channel[1];
-    i_hsv_to_rgbf(&c);
-    c.channel[3] = in->channel[3];
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_value(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    i_color c = *out;
-    i_rgb_to_hsv(&c);
-    i_rgb_to_hsv(in);
-    c.channel[2] = in->channel[2];
-    i_hsv_to_rgb(&c);
-    c.channel[3] = in->channel[3];
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_valuef(i_fcolor *out, i_fcolor *in, int channels, 
-                           int count) {
-  while (count--) {
-    i_fcolor c = *out;
-    i_rgb_to_hsvf(&c);
-    i_rgb_to_hsvf(in);
-    c.channel[2] = in->channel[2];
-    i_hsv_to_rgbf(&c);
-    c.channel[3] = in->channel[3];
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_color(i_color *out, i_color *in, int channels, int count) {
-  while (count--) {
-    i_color c = *out;
-    i_rgb_to_hsv(&c);
-    i_rgb_to_hsv(in);
-    c.channel[0] = in->channel[0];
-    c.channel[1] = in->channel[1];
-    i_hsv_to_rgb(&c);
-    c.channel[3] = in->channel[3];
-    COMBINE(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
-static void combine_colorf(i_fcolor *out, i_fcolor *in, int channels, 
-                           int count) {
-  while (count--) {
-    i_fcolor c = *out;
-    i_rgb_to_hsvf(&c);
-    i_rgb_to_hsvf(in);
-    c.channel[0] = in->channel[0];
-    c.channel[1] = in->channel[1];
-    i_hsv_to_rgbf(&c);
-    c.channel[3] = in->channel[3];
-    COMBINEF(*out, c, channels);
-    ++out;
-    ++in;
-  }
-}
-
 
 /*
 =back
index d76a8b1..bad32a7 100644 (file)
@@ -1713,6 +1713,17 @@ fill_fountf(i_fill_t *fill, int x, int y, int width, int channels,
 static void
 fount_fill_destroy(i_fill_t *fill);
 
+static i_fill_fountain_t
+fount_fill_proto =
+  {
+    {
+      NULL,
+      fill_fountf,
+      fount_fill_destroy
+    }
+  };
+
+
 /*
 =item i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs)
 
@@ -1733,9 +1744,7 @@ i_new_fill_fount(double xa, double ya, double xb, double yb,
                  int count, i_fountain_seg *segs) {
   i_fill_fountain_t *fill = mymalloc(sizeof(i_fill_fountain_t));
   
-  fill->base.fill_with_color = NULL;
-  fill->base.fill_with_fcolor = fill_fountf;
-  fill->base.destroy = fount_fill_destroy;
+  *fill = fount_fill_proto;
   if (combine)
     i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
   else {
index 8503229..69173a8 100644 (file)
@@ -3,6 +3,7 @@ use blib;
 use strict;
 use Imager;
 use Getopt::Long;
+use Time::HiRes qw(time);
 
 my $count = 1000;
 GetOptions("c=i"=>\$count)
@@ -41,8 +42,15 @@ for my $i (1 .. $count) {
     print "  replace $offset/$len: ", unpack("H*", $ins), "\n";
     substr($data, $offset, $len, $ins);
   }
+  my $start = time;
   my $im = Imager->new;
-  if ($im->read(data => $data)) {
+  my $result = $im->read(data => $data);
+  my $dur = time() - $start;
+  if ($dur > 1.0) {
+    print "***Took too long to load\n";
+  }
+  printf "   Took %f seconds\n", time() - $start;
+  if ($result) {
     print "<< Success\n";
   }
   else {
index cd74f24..6fc3c04 100644 (file)
--- a/hlines.c
+++ b/hlines.c
@@ -263,8 +263,26 @@ i_int_hlines_fill_fill(im, hlines, fill)
 */
 void
 i_int_hlines_fill_fill(i_img *im, i_int_hlines *hlines, i_fill_t *fill) {
+  i_render r;
   int y, i;
 
+  i_render_init(&r, im, im->xsize);
+
+  for (y = hlines->start_y; y < hlines->limit_y; ++y) {
+    i_int_hline_entry *entry = hlines->entries[y - hlines->start_y];
+    if (entry) {
+      for (i = 0; i < entry->count; ++i) {
+       i_int_hline_seg *seg = entry->segs + i;
+       int width = seg->x_limit-seg->minx;
+       
+       i_render_fill(&r, seg->minx, y, width, NULL, fill);
+      }
+    }
+  }
+  i_render_done(&r);
+  
+#if 1
+#else
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * im->xsize);
     i_color *work = NULL;
@@ -327,6 +345,7 @@ i_int_hlines_fill_fill(i_img *im, i_int_hlines *hlines, i_fill_t *fill) {
     if (work)
       myfree(work);
   }
+#endif
 }
 
 /*
diff --git a/image.c b/image.c
index 13b73d5..110f5ef 100644 (file)
--- a/image.c
+++ b/image.c
@@ -643,75 +643,6 @@ i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,co
     }
 }
 
-/*
-=item i_copyto(dest, src, x1, y1, x2, y2, tx, ty)
-
-=category Image
-
-Copies image data from the area (x1,y1)-[x2,y2] in the source image to
-a rectangle the same size with it's top-left corner at (tx,ty) in the
-destination image.
-
-If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped.
-
-=cut
-*/
-
-void
-i_copyto(i_img *im, i_img *src, int x1, int y1, int x2, int y2, int tx, int ty) {
-  int x, y, t, ttx, tty;
-  
-  if (x2<x1) { t=x1; x1=x2; x2=t; }
-  if (y2<y1) { t=y1; y1=y2; y2=t; }
-  if (tx < 0) {
-    /* adjust everything equally */
-    x1 += -tx;
-    x2 += -tx;
-    tx = 0;
-  }
-  if (ty < 0) {
-    y1 += -ty;
-    y2 += -ty;
-    ty = 0;
-  }
-  if (x1 >= src->xsize || y1 >= src->ysize)
-    return; /* nothing to do */
-  if (x2 > src->xsize)
-    x2 = src->xsize;
-  if (y2 > src->ysize)
-    y2 = src->ysize;
-  if (x1 == x2 || y1 == y2)
-    return; /* nothing to do */
-
-  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 *row = mymalloc(sizeof(i_color) * (x2-x1));
-    tty = ty;
-    for(y=y1; y<y2; y++) {
-      ttx = tx;
-      i_glin(src, x1, x2, y, row);
-      i_plin(im, tx, tx+x2-x1, tty, row);
-      tty++;
-    }
-    myfree(row);
-  }
-  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++;
-    }
-  }
-}
-
 /*
 =item i_copy(src)
 
index 0adbf5e..d0b1923 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -175,7 +175,15 @@ void i_copyto      (i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int
 void i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,const i_color *trans);
 i_img* i_copy        (i_img *src);
 int  i_rubthru     (i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny, int src_maxx, int src_maxy);
-
+extern int 
+i_compose_mask(i_img *out, i_img *src, i_img *mask,
+              int out_left, int out_top, int src_left, int src_top,
+              int mask_left, int mask_top, int width, int height,
+              int combine, double opacity);
+extern int 
+i_compose(i_img *out, i_img *src,
+              int out_left, int out_top, int src_left, int src_top,
+              int width, int height, int combine, double opacity);
 
 undef_int i_flipxy (i_img *im, int direction);
 extern i_img *i_rotate90(i_img *im, int degrees);
@@ -575,4 +583,11 @@ void  malloc_state(void);
 #include "imrender.h"
 #include "immacros.h"
 
+extern void
+i_adapt_colors(int dest_channels, int src_channels, i_color *colors, 
+              size_t count);
+extern void
+i_adapt_fcolors(int dest_channels, int src_channels, i_fcolor *colors, 
+              size_t count);
+
 #endif
index 8e9cdd3..21fe65a 100644 (file)
@@ -362,6 +362,16 @@ typedef void (*i_fill_with_fcolor_f)
      (struct i_fill_tag *fill, int x, int y, int width, int channels,
       i_fcolor *data);
 typedef void (*i_fill_destroy_f)(struct i_fill_tag *fill);
+
+/* combine functions modify their target and are permitted to modify
+   the source to prevent having to perform extra copying/memory
+   allocations, etc
+   The out array has I<channels> channels.
+
+   The in array has I<channels> channels + an alpha channel if one
+   isn't included in I<channels>.
+*/
+
 typedef void (*i_fill_combine_f)(i_color *out, i_color *in, int channels, 
                                  int count);
 typedef void (*i_fill_combinef_f)(i_fcolor *out, i_fcolor *in, int channels,
@@ -416,11 +426,11 @@ typedef struct i_fill_tag
 {
   /* called for 8-bit/sample image (and maybe lower) */
   /* this may be NULL, if so call fill_with_fcolor */
-  i_fill_with_color_f fill_with_color;
+  i_fill_with_color_f f_fill_with_color;
 
   /* called for other sample sizes */
   /* this must be non-NULL */
-  i_fill_with_fcolor_f fill_with_fcolor;
+  i_fill_with_fcolor_f f_fill_with_fcolor;
 
   /* called if non-NULL to release any extra resources */
   i_fill_destroy_f destroy;
index 213362d..9ded022 100644 (file)
@@ -10,5 +10,14 @@ i_render_done(i_render *r);
 extern void
 i_render_color(i_render *r, int x, int y, int width, unsigned char const *src,
                i_color const *color);
+extern void
+i_render_fill(i_render *r, int x, int y, int width, unsigned char const *src,
+             i_fill_t *fill);
+extern void
+i_render_line(i_render *r, int x, int y, int width, const i_sample_t *src,
+             i_color *line, i_fill_combine_f combine);
+extern void
+i_render_linef(i_render *r, int x, int y, int width, const double *src,
+             i_fcolor *line, i_fill_combinef_f combine);
 
 #endif
diff --git a/imtoc.perl b/imtoc.perl
deleted file mode 100644 (file)
index 285cfce..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-#!perl -w
-use strict;
-
-my $src = shift;
-my $dest = shift
-  or usage();
-
-open SRC, "< $src"
-  or die "Cannot open $src: $!\n";
-
-my $cond;
-my $cond_line;
-my $save_code;
-my @code;
-my $code_line;
-my @out;
-my $failed;
-
-push @out, 
-  "#define IM_ROUND_8(x) ((int)((x)+0.5))\n",
-  "#define IM_ROUND_double(x) (x)\n",
-  "#define IM_LIMIT_8(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))\n",
-  "#define IM_LIMIT_double(x) ((x) < 0.0 ? 0.0 : (x) > 1.0 ? 1.0 : (x))\n",
-  "#line 1 \"$src\"\n";
-while (defined(my $line = <SRC>)) {
-  if ($line =~ /^\#code\s+(\S.+)$/) {
-    $save_code
-      and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
-
-    $cond = $1;
-    $cond_line = $.;
-    $code_line = $. + 1;
-    $save_code = 1;
-  }
-  elsif ($line =~ /^\#code\s*$/) {
-    $save_code
-      and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
-
-    $cond = '';
-    $cond_line = 0;
-    $code_line = $. + 1;
-    $save_code = 1;
-  }
-  elsif ($line =~ /^\#\/code$/) {
-    $save_code
-      or do { warn "$src:$.:#/code without #code\n"; ++$failed; next; };
-
-    if ($cond) {
-      push @out, "#line $cond_line \"$src\"\n";
-      push @out, "  if ($cond) {\n";
-    }
-    push @out, "#undef IM_EIGHT_BIT\n",
-      "#define IM_EIGHT_BIT 1\n";
-    push @out, "#line $code_line \"$src\"\n";
-    push @out, byte_samples(@code);
-    push @out, "  }\n", "  else {\n"
-      if $cond;
-    push @out, "#undef IM_EIGHT_BIT\n";
-    push @out, "#line $code_line \"$src\"\n";
-    push @out, double_samples(@code);
-    push @out, "  }\n"
-      if $cond;
-    push @out, "#line $. \"$src\"\n";
-    @code = ();
-    $save_code = 0;
-  }
-  elsif ($save_code) {
-    push @code, $line;
-  }
-  else {
-    push @out, $line;
-  }
-}
-
-if ($save_code) {
-  warn "$src:$code_line:#code block not closed by EOF\n";
-  ++$failed;
-}
-
-close SRC;
-
-$failed 
-  and die "Errors during parsing, aborting\n";
-
-open DEST, "> $dest"
-  or die "Cannot open $dest: $!\n";
-print DEST @out;
-close DEST;
-
-sub byte_samples {
-  # important we make a copy
-  my @lines = @_;
-
-  for (@lines) {
-    s/\bIM_GPIX\b/i_gpix/g;
-    s/\bIM_GLIN\b/i_glin/g;
-    s/\bIM_PPIX\b/i_ppix/g;
-    s/\bIM_PLIN\b/i_plin/g;
-    s/\bIM_GSAMP\b/i_gsamp/g;
-    s/\bIM_SAMPLE_MAX\b/255/g;
-    s/\bIM_SAMPLE_T/i_sample_t/g;
-    s/\bIM_COLOR\b/i_color/g;
-    s/\bIM_WORK_T\b/int/g;
-    s/\bIM_Sf\b/"%d"/g;
-    s/\bIM_Wf\b/"%d"/g;
-    s/\bIM_SUFFIX\((\w+)\)/$1_8/g;
-    s/\bIM_ROUND\(/IM_ROUND_8(/g;
-    s/\bIM_LIMIT\(/IM_LIMIT_8(/g;
-  }
-
-  @lines;
-}
-
-sub double_samples {
-  # important we make a copy
-  my @lines = @_;
-
-  for (@lines) {
-    s/\bIM_GPIX\b/i_gpixf/g;
-    s/\bIM_GLIN\b/i_glinf/g;
-    s/\bIM_PPIX\b/i_ppixf/g;
-    s/\bIM_PLIN\b/i_plinf/g;
-    s/\bIM_GSAMP\b/i_gsampf/g;
-    s/\bIM_SAMPLE_MAX\b/1.0/g;
-    s/\bIM_SAMPLE_T/i_fsample_t/g;
-    s/\bIM_COLOR\b/i_fcolor/g;
-    s/\bIM_WORK_T\b/double/g;
-    s/\bIM_Sf\b/"%f"/g;
-    s/\bIM_Wf\b/"%f"/g;
-    s/\bIM_SUFFIX\((\w+)\)/$1_double/g;
-    s/\bIM_ROUND\(/IM_ROUND_double(/g;
-    s/\bIM_LIMIT\(/IM_LIMIT_double(/g;
-  }
-
-  @lines;
-}
-
-=head1 NAME
-
-imtoc.perl - simple preprocessor for handling multiple sample sizes
-
-=head1 SYNOPSIS
-
-  /* in the source: */
-  #code condition true to work with 8-bit samples
-  ... code using preprocessor types/values ...
-  #/code
-
-  perl imtoc.perl foo.im foo.c
-
-=head1 DESCRIPTION
-
-This is a simple preprocessor that aims to reduce duplication of
-source code when implementing an algorithm both for 8-bit samples and
-double samples in Imager.
-
-Imager's Makefile.PL currently scans the MANIFEST for .im files and
-adds Makefile files to convert these to .c files.
-
-The beginning of a sample-independent section of code is preceded by:
-
-  #code expression
-
-where I<expression> should return true if processing should be done at
-8-bits/sample.
-
-You can also use a #code block around a function definition to produce
-8-bit and double sample versions of a function.  In this case #code
-has no expression and you will need to use IM_SUFFIX() to produce
-different function names.
-
-The end of a sample-independent section of code is terminated by:
-
-  #/code
-
-#code sections cannot be nested.
-
-#/code without a starting #code is an error.
-
-The following types and values are defined in a #code section:
-
-=over
-
-=item *
-
-IM_GPIX(im, x, y, &col)
-
-=item *
-
-IM_GLIN(im, l, r, y, colors)
-
-=item *
-
-IM_PPIX(im, x, y, &col)
-
-=item *
-
-IM_PLIN(im, x, y, colors)
-
-=item *
-
-IM_GSAMP(im, l, r, y, samples, chans, chan_count)
-
-These correspond to the appropriate image function, eg. IM_GPIX()
-becomes i_gpix() or i_gpixf() as appropriate.
-
-=item *
-
-IM_SAMPLE_MAX - maximum value for a sample
-
-=item *
-
-IM_SAMPLE_T - type of a sample (i_sample_t or i_fsample_t)
-
-=item *
-
-IM_COLOR - color type, either i_color or i_fcolor.
-
-=item *
-
-IM_WORK_T - working sample type, either int or double.
-
-=item *
-
-IM_Sf - format string for the sample type, C<"%d"> or C<"%f">.
-
-=item *
-
-IM_Wf - format string for the work type, C<"%d"> or C<"%f">.
-
-=item *
-
-IM_SUFFIX(identifier) - adds _8 or _double onto the end of identifier.
-
-=item *
-
-IM_EIGHT_BIT - this is a macro defined only in 8-bit/sample code.
-
-=back
-
-Other types, functions and values may be added in the future.
-
-=head1 AUTHOR
-
-Tony Cook <tony@imager.perl.org>
-
-=cut
index e22a5b0..8220a1a 100644 (file)
@@ -722,7 +722,7 @@ If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped.
 
 
 =for comment
-From: File image.c
+From: File paste.im
 
 =item i_copyto_trans(im, src, x1, y1, x2, y2, tx, ty, trans)
 
index 3b27358..ec2cbb3 100644 (file)
@@ -1060,6 +1060,91 @@ color indexes, not sample values:
   my $packed_index_data = pack("C*", $black_index, $red_index);
   $im->setscanline(y => $y, pixels => $packed_index_data, type => 'index');
 
+=head1 Combine Types
+
+Some methods accept a C<combine> parameter, this can be any of the
+following:
+
+=over
+
+=item none
+
+The fill pixel replaces the target pixel.
+
+=item normal
+
+The fill pixels alpha value is used to combine it with the target pixel.
+
+=item multiply
+
+=item mult
+
+Each channel of fill and target is multiplied, and the result is
+combined using the alpha channel of the fill pixel.
+
+=item dissolve
+
+If the alpha of the fill pixel is greater than a random number, the
+fill pixel is alpha combined with the target pixel.
+
+=item add
+
+The channels of the fill and target are added together, clamped to the range of the samples and alpha combined with the target.
+
+=item subtract
+
+The channels of the fill are subtracted from the target, clamped to be
+>= 0, and alpha combined with the target.
+
+=item diff
+
+The channels of the fill are subtracted from the target and the
+absolute value taken this is alpha combined with the target.
+
+=item lighten
+
+The higher value is taken from each channel of the fill and target
+pixels, which is then alpha combined with the target.
+
+=item darken
+
+The higher value is taken from each channel of the fill and target
+pixels, which is then alpha combined with the target.
+
+=item hue
+
+The combination of the saturation and value of the target is combined
+with the hue of the fill pixel, and is then alpha combined with the
+target.
+
+=item sat
+
+The combination of the hue and value of the target is combined
+with the saturation of the fill pixel, and is then alpha combined with the
+target.
+
+=item value
+
+The combination of the hue and value of the target is combined
+with the value of the fill pixel, and is then alpha combined with the
+target.
+
+=item color
+
+The combination of the value of the target is combined with the hue
+and saturation of the fill pixel, and is then alpha combined with the
+target.
+
+=back
+
+=over
+
+=item combines
+
+Returns a list of possible combine types.
+
+=back
+
 =head1 BUGS
 
 box, arc, do not support antialiasing yet.  Arc, is only filled as of
index 0258189..a540cd7 100644 (file)
@@ -14,25 +14,13 @@ my @hatch_types =
 my %hatch_types;
 @hatch_types{@hatch_types} = 0..$#hatch_types;
 
-my @combine_types = 
-  qw/none normal multiply dissolve add subtract diff lighten darken
-     hue saturation value color/;
-my %combine_types;
-@combine_types{@combine_types} = 0 .. $#combine_types;
-$combine_types{mult} = $combine_types{multiply};
-$combine_types{'sub'}  = $combine_types{subtract};
-$combine_types{sat}  = $combine_types{saturation};
-
 *_color = \&Imager::_color;
 
 sub new {
   my ($class, %hsh) = @_;
 
   my $self = bless { }, $class;
-  $hsh{combine} ||= 0;
-  if (exists $combine_types{$hsh{combine}}) {
-    $hsh{combine} = $combine_types{$hsh{combine}};
-  }
+  $hsh{combine} = Imager->_combine($hsh{combine}, 0);
   if ($hsh{solid}) {
     my $solid = _color($hsh{solid});
     if (UNIVERSAL::isa($solid, 'Imager::Color')) {
@@ -151,7 +139,7 @@ sub hatches {
 }
 
 sub combines {
-  return @combine_types;
+  return Imager->combines;
 }
 
 1;
@@ -209,80 +197,8 @@ fountain (similar to gradients in paint software)
 
 =item combine
 
-The way in which the fill data is combined with the underlying image,
-possible values include:
-
-=over
-
-=item none
-
-The fill pixel replaces the target pixel.
-
-=item normal
-
-The fill pixels alpha value is used to combine it with the target pixel.
-
-=item multiply
-
-=item mult
-
-Each channel of fill and target is multiplied, and the result is
-combined using the alpha channel of the fill pixel.
-
-=item dissolve
-
-If the alpha of the fill pixel is greater than a random number, the
-fill pixel is alpha combined with the target pixel.
-
-=item add
-
-The channels of the fill and target are added together, clamped to the range of the samples and alpha combined with the target.
-
-=item subtract
-
-The channels of the fill are subtracted from the target, clamped to be
->= 0, and alpha combined with the target.
-
-=item diff
-
-The channels of the fill are subtracted from the target and the
-absolute value taken this is alpha combined with the target.
-
-=item lighten
-
-The higher value is taken from each channel of the fill and target
-pixels, which is then alpha combined with the target.
-
-=item darken
-
-The higher value is taken from each channel of the fill and target
-pixels, which is then alpha combined with the target.
-
-=item hue
-
-The combination of the saturation and value of the target is combined
-with the hue of the fill pixel, and is then alpha combined with the
-target.
-
-=item sat
-
-The combination of the hue and value of the target is combined
-with the saturation of the fill pixel, and is then alpha combined with the
-target.
-
-=item value
-
-The combination of the hue and value of the target is combined
-with the value of the fill pixel, and is then alpha combined with the
-target.
-
-=item color
-
-The combination of the value of the target is combined with the hue
-and saturation of the fill pixel, and is then alpha combined with the
-target.
-
-=back
+The way in which the fill data is combined with the underlying image.
+See L<Imager::Draw/"Combine Types">.
 
 =back
 
index c8e99a5..72d0b8a 100644 (file)
@@ -187,12 +187,12 @@ This can take any of the following values:
 
 Each channel is simple scaled between c0 and c1.
 
-=item hsvup
+=item hueup
 
 The color is converted to a HSV value and the scaling is done such
 that the hue increases as the fill parameter increases.
 
-=item hsvdown
+=item huedown
 
 The color is converted to a HSV value and the scaling is done such
 that the hue decreases as the fill parameter increases.
diff --git a/lib/Imager/Preprocess.pm b/lib/Imager/Preprocess.pm
new file mode 100644 (file)
index 0000000..0e03eaf
--- /dev/null
@@ -0,0 +1,297 @@
+package Imager::Preprocess;
+use strict;
+require Exporter;
+use vars qw(@ISA @EXPORT);
+
+@EXPORT = qw(preprocess);
+@ISA = qw(Exporter);
+
+
+sub preprocess {
+  my $src = shift @ARGV;
+  my $dest = shift @ARGV
+    or usage();
+
+  open SRC, "< $src"
+  or die "Cannot open $src: $!\n";
+
+  my $cond;
+  my $cond_line;
+  my $save_code;
+  my @code;
+  my $code_line;
+  my @out;
+  my $failed;
+
+  push @out, 
+    "#define IM_ROUND_8(x) ((int)((x)+0.5))\n",
+    "#define IM_ROUND_double(x) (x)\n",
+    "#define IM_LIMIT_8(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))\n",
+    "#define IM_LIMIT_double(x) ((x) < 0.0 ? 0.0 : (x) > 1.0 ? 1.0 : (x))\n",
+    "#line 1 \"$src\"\n";
+  while (defined(my $line = <SRC>)) {
+    if ($line =~ /^\#code\s+(\S.+)$/) {
+      $save_code
+       and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
+      
+      $cond = $1;
+      $cond_line = $.;
+      $code_line = $. + 1;
+      $save_code = 1;
+    }
+    elsif ($line =~ /^\#code\s*$/) {
+      $save_code
+       and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
+      
+      $cond = '';
+      $cond_line = 0;
+      $code_line = $. + 1;
+      $save_code = 1;
+    }
+    elsif ($line =~ /^\#\/code$/) {
+      $save_code
+       or do { warn "$src:$.:#/code without #code\n"; ++$failed; next; };
+      
+      if ($cond) {
+       push @out, "#line $cond_line \"$src\"\n";
+       push @out, "  if ($cond) {\n";
+      }
+      push @out,
+       "#undef IM_EIGHT_BIT\n",
+       "#define IM_EIGHT_BIT 1\n",
+       "#undef IM_FILL_COMBINE\n",
+        "#define IM_FILL_COMBINE(fill) ((fill)->combine)\n",
+       "#undef IM_FILL_FILLER\n",
+        "#define IM_FILL_FILLER(fill) ((fill)->f_fill_with_color)\n";
+      push @out, "#line $code_line \"$src\"\n";
+      push @out, byte_samples(@code);
+      push @out, "  }\n", "  else {\n"
+       if $cond;
+      push @out, 
+       "#undef IM_EIGHT_BIT\n",
+       "#undef IM_FILL_COMBINE\n",
+        "#define IM_FILL_COMBINE(fill) ((fill)->combinef)\n",
+       "#undef IM_FILL_FILLER\n",
+        "#define IM_FILL_FILLER(fill) ((fill)->f_fill_with_fcolor)\n";
+      push @out, "#line $code_line \"$src\"\n";
+      push @out, double_samples(@code);
+      push @out, "  }\n"
+       if $cond;
+      push @out, "#line ",$.+1," \"$src\"\n";
+      @code = ();
+      $save_code = 0;
+    }
+    elsif ($save_code) {
+      push @code, $line;
+    }
+    else {
+      push @out, $line;
+    }
+  }
+  
+  if ($save_code) {
+    warn "$src:$code_line:#code block not closed by EOF\n";
+    ++$failed;
+  }
+
+  close SRC;
+  
+  $failed 
+    and die "Errors during parsing, aborting\n";
+  
+  open DEST, "> $dest"
+    or die "Cannot open $dest: $!\n";
+  print DEST @out;
+  close DEST;
+}
+  
+sub byte_samples {
+  # important we make a copy
+  my @lines = @_;
+  
+  for (@lines) {
+    s/\bIM_GPIX\b/i_gpix/g;
+    s/\bIM_GLIN\b/i_glin/g;
+    s/\bIM_PPIX\b/i_ppix/g;
+    s/\bIM_PLIN\b/i_plin/g;
+    s/\bIM_GSAMP\b/i_gsamp/g;
+    s/\bIM_SAMPLE_MAX\b/255/g;
+    s/\bIM_SAMPLE_MAX2\b/65025/g;
+    s/\bIM_SAMPLE_T/i_sample_t/g;
+    s/\bIM_COLOR\b/i_color/g;
+    s/\bIM_WORK_T\b/int/g;
+    s/\bIM_Sf\b/"%d"/g;
+    s/\bIM_Wf\b/"%d"/g;
+    s/\bIM_SUFFIX\((\w+)\)/$1_8/g;
+    s/\bIM_ROUND\(/IM_ROUND_8(/g;
+    s/\bIM_ADAPT_COLORS\(/i_adapt_colors(/g;
+    s/\bIM_LIMIT\(/IM_LIMIT_8(/g;
+    s/\bIM_RENDER_LINE\(/i_render_line(/g;
+    s/\bIM_FILL_COMBINE_F\b/i_fill_combine_f/g;
+  }
+  
+  @lines;
+}
+
+sub double_samples {
+  # important we make a copy
+  my @lines = @_;
+  
+  for (@lines) {
+    s/\bIM_GPIX\b/i_gpixf/g;
+    s/\bIM_GLIN\b/i_glinf/g;
+    s/\bIM_PPIX\b/i_ppixf/g;
+    s/\bIM_PLIN\b/i_plinf/g;
+    s/\bIM_GSAMP\b/i_gsampf/g;
+    s/\bIM_SAMPLE_MAX\b/1.0/g;
+    s/\bIM_SAMPLE_MAX2\b/1.0/g;
+    s/\bIM_SAMPLE_T/i_fsample_t/g;
+    s/\bIM_COLOR\b/i_fcolor/g;
+    s/\bIM_WORK_T\b/double/g;
+    s/\bIM_Sf\b/"%f"/g;
+    s/\bIM_Wf\b/"%f"/g;
+    s/\bIM_SUFFIX\((\w+)\)/$1_double/g;
+    s/\bIM_ROUND\(/IM_ROUND_double(/g;
+    s/\bIM_ADAPT_COLORS\(/i_adapt_fcolors(/g;
+    s/\bIM_LIMIT\(/IM_LIMIT_double(/g;
+    s/\bIM_RENDER_LINE\(/i_render_linef(/g;
+    s/\bIM_FILL_COMBINE_F\b/i_fill_combinef_f/g;
+  }
+
+  @lines;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Imager::Preprocess - simple preprocessor for handling multiple sample sizes
+
+=head1 SYNOPSIS
+
+  /* in the source: */
+  #code condition true to work with 8-bit samples
+  ... code using preprocessor types/values ...
+  #/code
+
+  perl -MImager -epreprocess foo.im foo.c
+
+=head1 DESCRIPTION
+
+This is a simple preprocessor that aims to reduce duplication of
+source code when implementing an algorithm both for 8-bit samples and
+double samples in Imager.
+
+Imager's Makefile.PL currently scans the MANIFEST for .im files and
+adds Makefile files to convert these to .c files.
+
+The beginning of a sample-independent section of code is preceded by:
+
+  #code expression
+
+where I<expression> should return true if processing should be done at
+8-bits/sample.
+
+You can also use a #code block around a function definition to produce
+8-bit and double sample versions of a function.  In this case #code
+has no expression and you will need to use IM_SUFFIX() to produce
+different function names.
+
+The end of a sample-independent section of code is terminated by:
+
+  #/code
+
+#code sections cannot be nested.
+
+#/code without a starting #code is an error.
+
+The following types and values are defined in a #code section:
+
+=over
+
+=item *
+
+IM_GPIX(im, x, y, &col)
+
+=item *
+
+IM_GLIN(im, l, r, y, colors)
+
+=item *
+
+IM_PPIX(im, x, y, &col)
+
+=item *
+
+IM_PLIN(im, x, y, colors)
+
+=item *
+
+IM_GSAMP(im, l, r, y, samples, chans, chan_count)
+
+These correspond to the appropriate image function, eg. IM_GPIX()
+becomes i_gpix() or i_gpixf() as appropriate.
+
+=item *
+
+IM_ADAPT_COLORS(dest_channes, src_channels, colors, count)
+
+Call i_adapt_colors() or i_adapt_fcolors().
+
+=item *
+
+IM_FILL_COMBINE(fill) - retrieve the combine function from a fill
+object.
+
+=item *
+
+IM_FILL_FILLER(fill) - retrieve the fill_with_* function from a fill
+object.
+
+=item *
+
+IM_SAMPLE_MAX - maximum value for a sample
+
+=item *
+
+IM_SAMPLE_MAX2 - maximum value for a sample, squared
+
+=item *
+
+IM_SAMPLE_T - type of a sample (i_sample_t or i_fsample_t)
+
+=item *
+
+IM_COLOR - color type, either i_color or i_fcolor.
+
+=item *
+
+IM_WORK_T - working sample type, either int or double.
+
+=item *
+
+IM_Sf - format string for the sample type, C<"%d"> or C<"%f">.
+
+=item *
+
+IM_Wf - format string for the work type, C<"%d"> or C<"%f">.
+
+=item *
+
+IM_SUFFIX(identifier) - adds _8 or _double onto the end of identifier.
+
+=item *
+
+IM_EIGHT_BIT - this is a macro defined only in 8-bit/sample code.
+
+=back
+
+Other types, functions and values may be added in the future.
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=cut
index cfb3e30..d351fd9 100644 (file)
@@ -28,6 +28,9 @@ Imager::Transformations - Simple transformations of one image into another.
                    src_minx=>20, src_miny=>30,
                    src_maxx=>20, src_maxy=>30);
 
+  $img->compose(src => $src, tx => 30, ty => 20, combine => 'color');
+  $img->compose(src => $src, tx => 30, ty => 20, combine => 'color');
+                mask => $mask, opacity => 0.5);
 
   $img->flip(dir=>"h");       # horizontal flip
   $img->flip(dir=>"vh");      # vertical and horizontal flip
@@ -60,9 +63,9 @@ The methods described in Imager::Transformations fall into two categories.
 Either they take an existing image and modify it in place, or they 
 return a modified copy.
 
-Functions that modify inplace are C<flip()>, C<paste()> and
-C<rubthrough()>.  If the original is to be left intact it's possible
-to make a copy and alter the copy:
+Functions that modify inplace are C<flip()>, C<paste()>,
+C<rubthrough()> and C<compose()>.  If the original is to be left
+intact it's possible to make a copy and alter the copy:
 
   $flipped = $img->copy()->flip(dir=>'h');
 
@@ -562,7 +565,13 @@ sub image to be pasted.
               left => 10, top => 10,
               src_minx => 20, src_miny => 20,
               src_maxx => 40, src_maxx => 40);
-              
+
+If the source image has an alpha channel and the target doesn't, then
+the source is treated as if composed onto a black background.
+
+If the source image is color and the target is grayscale, the the
+source is treated as if run through C< convert(preset=>'gray') >.
+
 =item rubthrough
 
 A more complicated way of blending images is where one image is
@@ -618,6 +627,91 @@ right corner of the source image.
 rubthrough() returns true on success.  On failure check
 $target->errstr for the reason for failure.
 
+=item compose
+
+Draws the source image over the target image, with the src alpha
+channel modified by the optional mask and the opacity.
+
+  $img->compose(src=>$overlay,
+                tx=>30,       ty=>50,
+                src_minx=>20, src_miny=>30,
+                src_maxx=>20, src_maxy=>30,
+                mask => $mask, opacity => 0.5);
+
+That will take the sub image defined by I<$overlay> and
+I<[src_minx,src_maxx)[src_miny,src_maxy)> and overlay it on top of
+I<$img> 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.
+
+Parameters:
+
+=over
+
+=item *
+
+src - the source image to draw onto the target.  Required.
+
+=item *
+
+tx, ty - location in the the target image ($self) to render the top
+left corner of the source.  These can also be supplied as C<left> and
+C<right>.  Default: (0, 0).
+
+=item *
+
+src_minx, src_miny - the top left corner in the source to transfer to
+the target image.  Default: (0, 0).
+
+=item *
+
+src_maxx, src_maxy - the bottom right in the source image of the sub
+image to overlay.  This position is B<non> inclusive.  Default: bottom
+right corner of the source image.
+
+=item *
+
+mask - a mask image.  The first channel of this image is used to
+modify the alpha channel of the source image.  This can me used to
+mask out portions of the source image.  Where the first channel is
+zero none of the source image will be used, where the first channel is
+max the full alpha of the source image will be used, as further
+modified by the opacity.
+
+=item *
+
+opacity - further modifies the alpha channel of the source image, in
+the range 0.0 to 1.0.  Default: 1.0.
+
+=item *
+
+combine - the method to combine the source pixels with the target.
+See the combine option documentation in Imager::Fill.  Default:
+normal.
+
+=back
+
+Calling compose() with no mask, combine set to C<normal>, opacity set
+to C<1.0> is equivalent to calling rubthrough().
+
+compose() is intended to be produce similar effects to layers in
+interactive paint software.
+
+  # overlay all of $source onto $targ
+  $targ->compose(tx => 20, ty => 25, src => $source);
+
+  # overlay the top left corner of $source onto $targ
+  $targ->compose(tx => 20, ty => 25, src => $source,
+                    src_maxx => 20, src_maxy => 20);
+
+  # overlay the bottom right corner of $source onto $targ
+  $targ->compose(tx => 20, ty => 30, src => $src,
+                    src_minx => $src->getwidth() - 20,
+                    src_miny => $src->getheight() - 20);
+
+compose() returns true on success.  On failure check $target->errstr
+for the reason for failure.
+
 =item flip
 
 An inplace horizontal or vertical flip is possible by calling the
diff --git a/paste.im b/paste.im
new file mode 100644 (file)
index 0000000..f162359
--- /dev/null
+++ b/paste.im
@@ -0,0 +1,232 @@
+#include "imager.h"
+
+/*
+=item i_copyto(dest, src, x1, y1, x2, y2, tx, ty)
+
+=category Image
+
+Copies image data from the area (x1,y1)-[x2,y2] in the source image to
+a rectangle the same size with it's top-left corner at (tx,ty) in the
+destination image.
+
+If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped.
+
+=cut
+*/
+
+void
+i_copyto(i_img *im, i_img *src, int x1, int y1, int x2, int y2, int tx, int ty) {
+  int x, y, t, ttx, tty;
+  
+  if (x2<x1) { t=x1; x1=x2; x2=t; }
+  if (y2<y1) { t=y1; y1=y2; y2=t; }
+  if (tx < 0) {
+    /* adjust everything equally */
+    x1 += -tx;
+    x2 += -tx;
+    tx = 0;
+  }
+  if (ty < 0) {
+    y1 += -ty;
+    y2 += -ty;
+    ty = 0;
+  }
+  if (x1 >= src->xsize || y1 >= src->ysize)
+    return; /* nothing to do */
+  if (x2 > src->xsize)
+    x2 = src->xsize;
+  if (y2 > src->ysize)
+    y2 = src->ysize;
+  if (x1 == x2 || y1 == y2)
+    return; /* nothing to do */
+
+  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));
+
+#code im->bits == i_8_bits
+  IM_COLOR *row = mymalloc(sizeof(IM_COLOR) * (x2-x1));
+  tty = ty;
+  for(y=y1; y<y2; y++) {
+    ttx = tx;
+    IM_GLIN(src, x1, x2, y, row);
+    if (src->channels != im->channels)
+      IM_ADAPT_COLORS(im->channels, src->channels, row, x2-x1);
+    IM_PLIN(im, tx, tx+x2-x1, tty, row);
+    tty++;
+  }
+  myfree(row);
+#/code
+}
+
+#define color_to_grey(col) ((col)->rgb.r * 0.222  + (col)->rgb.g * 0.707 + (col)->rgb.b * 0.071)
+
+#code
+void
+#ifdef IM_EIGHT_BIT
+i_adapt_colors
+#else
+i_adapt_fcolors
+#endif
+(int out_channels, int in_channels, IM_COLOR *colors, 
+              size_t count) {
+  int i;
+  if (out_channels == in_channels)
+    return;
+  if (count == 0)
+    return;
+
+  switch (out_channels) {
+  case 1:
+    {
+      switch (in_channels) {
+      case 2:
+       /* apply alpha against a black background */
+       while (count) {
+         colors->channel[0] = colors->channel[0] * colors->channel[1] / IM_SAMPLE_MAX;
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 3:
+       /* convert to grey */
+       while (count) {
+         colors->channel[0] = IM_ROUND(color_to_grey(colors));
+         ++colors;
+         --count;
+       }
+       return;
+           
+      case 4:
+       while (count) {
+         colors->channel[0] = IM_ROUND(color_to_grey(colors) * colors->channel[3] / IM_SAMPLE_MAX);
+         ++colors;
+         --count;
+       }
+       return;
+
+      default:
+       i_fatal(3, "i_adapt_colors: in_channels of %d invalid\n", in_channels);
+       return; /* avoid warnings */
+      }
+    }
+
+  case 2:
+    {
+      switch (in_channels) {
+      case 1:
+       while (count) {
+         colors->channel[1] = IM_SAMPLE_MAX;
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 3:
+       while (count) {
+         colors->channel[0] = IM_ROUND(color_to_grey(colors));
+         colors->channel[1] = IM_SAMPLE_MAX;
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 4:
+       while (count) {
+         colors->channel[0] = IM_ROUND(color_to_grey(colors));
+         colors->channel[1] = colors->channel[3];
+         ++colors;
+         --count;
+       }
+       return;
+
+      default:
+       i_fatal(3, "i_adapt_colors: in_channels of %d invalid\n", in_channels);
+       return; /* avoid warnings */
+      }
+    }
+
+  case 3:
+    {
+      switch (in_channels) {
+      case 1:
+       while (count) {
+         colors->channel[1] = colors->channel[2] = colors->channel[0];
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 2:
+       while (count) {
+         int alpha = colors->channel[1];
+         colors->channel[0] = colors->channel[1] = colors->channel[2] =
+           IM_ROUND(colors->channel[0] * alpha / IM_SAMPLE_MAX);
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 4:
+       while (count) {
+         int alpha = colors->channel[3];
+         colors->channel[0] = 
+           IM_ROUND(colors->channel[0] * alpha / IM_SAMPLE_MAX);
+         colors->channel[1] = 
+           IM_ROUND(colors->channel[1] * alpha / IM_SAMPLE_MAX);
+         colors->channel[2] = 
+           IM_ROUND(colors->channel[2] * alpha / IM_SAMPLE_MAX);
+         ++colors;
+         --count;
+       }
+       return;
+
+      default:
+       i_fatal(3, "i_adapt_colors: in_channels of %d invalid\n", in_channels);
+       return; /* avoid warnings */
+      }
+    }
+
+  case 4:
+    {
+      switch (in_channels) {
+      case 1:
+       while (count) {
+         colors->channel[1] = colors->channel[2] = colors->channel[0];
+         colors->channel[3] = IM_SAMPLE_MAX;
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 2:
+       while (count) {
+         colors->channel[3] = colors->channel[1];
+         colors->channel[1] = colors->channel[2] = colors->channel[0];
+         ++colors;
+         --count;
+       }
+       return;
+
+      case 3:
+       while (count) {
+         colors->channel[3] = IM_SAMPLE_MAX;
+         ++colors;
+         --count;
+       }
+       return;
+
+      default:
+       i_fatal(3, "i_adapt_colors: in_channels of %d invalid\n", in_channels);
+       return; /* avoid warnings */
+      }
+    }
+
+  default:
+    i_fatal(3, "i_adapt_colors: out_channels of %d invalid\n", out_channels);
+    return; /* avoid warnings */
+  }
+}
+
+#/code
+
index 9a8b8b2..da5d7be 100644 (file)
--- a/polygon.c
+++ b/polygon.c
@@ -1,7 +1,7 @@
 #include "imager.h"
 #include "draw.h"
 #include "log.h"
-
+#include "imrender.h"
 
 #define IMTRUNC(x) ((int)((x)*16))
 
@@ -644,6 +644,7 @@ i_poly_aa(i_img *im, int l, const double *x, const double *y, const i_color *val
   i_poly_aa_low(im, l, x, y, val, scanline_flush);
 }
 
+#if 0
 struct poly_cfill_state {
   i_color *fillbuf;
   i_color *linebuf;
@@ -767,9 +768,52 @@ scanline_flush_cfill_f(i_img *im, ss_scanline *ss, int y, const void *ctx) {
     i_plinf(im, left, right, y, line);
   }
 }
+#endif
+
+struct poly_render_state {
+  i_render render;
+  i_fill_t *fill;
+  unsigned char *cover;
+};
+
+static void
+scanline_flush_render(i_img *im, ss_scanline *ss, int y, const void *ctx) {
+  int x, ch, tv;
+  int pos;
+  int left, right;
+  struct poly_render_state const *state = (struct poly_render_state const *)ctx;
+
+  left = 0;
+  while (left < im->xsize && ss->line[left] <= 0)
+    ++left;
+  if (left < im->xsize) {
+    right = im->xsize;
+    /* since going from the left found something, moving from the 
+       right should */
+    while (/* right > left && */ ss->line[right-1] <= 0) 
+      --right;
+    
+    /* convert to the format the render interface wants */
+    for (x = left; x < right; ++x) {
+      state->cover[x-left] = saturate(ss->line[x]);
+    }
+    i_render_fill(&state->render, left, y, right-left, state->cover, 
+                 state->fill);
+  }
+}
 
 void
-i_poly_aa_cfill(i_img *im, int l, const double *x, const double *y, i_fill_t *fill) {
+i_poly_aa_cfill(i_img *im, int l, const double *x, const double *y, 
+               i_fill_t *fill) {
+  struct poly_render_state ctx;
+
+  i_render_init(&ctx.render, im, im->xsize);
+  ctx.fill = fill;
+  ctx.cover = mymalloc(im->xsize);
+  i_poly_aa_low(im, l, x, y, &ctx, scanline_flush_render);
+  myfree(ctx.cover);
+  i_render_done(&ctx.render);
+#if 0
   if (im->bits == i_8_bits && fill->fill_with_color) {
     struct poly_cfill_state ctx;
     ctx.fillbuf = mymalloc(sizeof(i_color) * im->xsize * 2);
@@ -790,4 +834,5 @@ i_poly_aa_cfill(i_img *im, int l, const double *x, const double *y, i_fill_t *fi
     myfree(ctx.fillbuf);
     myfree(ctx.cover);
   }
+#endif
 }
index 54d47df..57d2804 100644 (file)
--- a/render.im
+++ b/render.im
@@ -7,6 +7,10 @@ Render utilities
 
 typedef void (*render_color_f)(i_render *, int, int, int, unsigned char const *src, i_color const *color);
 
+#define i_has_alpha(channels) ((channels) == 2 || (channels) == 4)
+
+#define i_color_channels(channels) (i_has_alpha(channels) ? (channels)-1 : (channels))
+
 #code
 
 static void IM_SUFFIX(render_color_alpha)(i_render *r, int x, int y, int width, unsigned char const *src, i_color const *color);
@@ -21,29 +25,141 @@ static render_color_f IM_SUFFIX(render_color_tab)[] =
     IM_SUFFIX(render_color_alpha),
   };
 
+static void IM_SUFFIX(combine_line_noalpha)(IM_COLOR *out, IM_COLOR const *in, int channels, int count);
+static void IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in, int channels, int count);
+/* the copy variant copies the source alpha to the the output alpha channel */
+static void IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in, int channels, int count);
+
+static void IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, int count);
+static void IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, int count);
+
 #/code
 
 void
 i_render_init(i_render *r, i_img *im, int width) {
   r->magic = RENDER_MAGIC;
   r->im = im;
-  r->width = width;
+  r->line_width = width;
   r->line_8 = NULL;
   r->line_double = NULL;
-#code im->bits <= 8
-    r->IM_SUFFIX(line) = mymalloc(sizeof(i_fcolor) * width);
-#/code
+  r->fill_width = width;
+  r->fill_line_8 = NULL;
+  r->fill_line_double = NULL;
 }
 
 void
 i_render_done(i_render *r) {
   if (r->line_8)
     myfree(r->line_8);
-  else
+  if (r->line_double)
     myfree(r->line_double);
+  if (r->fill_line_8)
+    myfree(r->fill_line_8);
+  if (r->fill_line_double)
+    myfree(r->fill_line_double);
   r->magic = 0;
 }
 
+static void
+alloc_line(i_render *r, int width, int eight_bit) {
+  if (width > r->line_width) {
+    int new_width = r->line_width * 2;
+    if (new_width < width)
+      new_width = width;
+
+    if (eight_bit) {
+      if (r->line_8)
+       r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width);
+      else
+       r->line_8 = mymalloc(sizeof(i_color) * new_width);
+      if (r->line_double) {
+       myfree(r->line_double);
+       r->line_double = NULL;
+      }
+    }
+    else {
+      if (r->line_double)
+       r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width);
+      else
+       r->line_double = mymalloc(sizeof(i_fcolor) * new_width);
+      if (r->line_8) {
+       myfree(r->line_8);
+       r->line_double = NULL;
+      }
+    }
+
+    r->line_width = new_width;
+  }
+  else {
+    if (eight_bit) {
+      if (!r->line_8)
+       r->line_8 = mymalloc(sizeof(i_color) * r->line_width);
+      if (r->line_double) {
+       myfree(r->line_double);
+       r->line_double = NULL;
+      }
+    }
+    else {
+      if (!r->line_double)
+       r->line_double = mymalloc(sizeof(i_fcolor) * r->line_width);
+      if (r->line_8) {
+       myfree(r->line_8);
+       r->line_8 = NULL;
+      }
+    }
+  }
+}
+
+static void
+alloc_fill_line(i_render *r, int width, int eight_bit) {
+  if (width > r->fill_width) {
+    int new_width = r->fill_width * 2;
+    if (new_width < width)
+      new_width = width;
+
+    if (eight_bit) {
+      if (r->line_8)
+       r->fill_line_8 = myrealloc(r->fill_line_8, sizeof(i_color) * new_width);
+      else
+       r->fill_line_8 = mymalloc(sizeof(i_color) * new_width);
+      if (r->fill_line_double) {
+       myfree(r->fill_line_double);
+       r->fill_line_double = NULL;
+      }
+    }
+    else {
+      if (r->fill_line_double)
+       r->fill_line_double = myrealloc(r->fill_line_double, sizeof(i_fcolor) * new_width);
+      else
+       r->fill_line_double = mymalloc(sizeof(i_fcolor) * new_width);
+      if (r->fill_line_8) {
+       myfree(r->fill_line_8);
+       r->fill_line_double = NULL;
+      }
+    }
+
+    r->fill_width = new_width;
+  }
+  else {
+    if (eight_bit) {
+      if (!r->fill_line_8)
+       r->fill_line_8 = mymalloc(sizeof(i_color) * r->fill_width);
+      if (r->fill_line_double) {
+       myfree(r->fill_line_double);
+       r->fill_line_double = NULL;
+      }
+    }
+    else {
+      if (!r->fill_line_double)
+       r->fill_line_double = mymalloc(sizeof(i_fcolor) * r->fill_width);
+      if (r->fill_line_8) {
+       myfree(r->fill_line_8);
+       r->fill_line_8 = NULL;
+      }
+    }
+  }
+}
+
 void
 i_render_color(i_render *r, int x, int y, int width, unsigned char const *src,
                i_color const *color) {
@@ -73,20 +189,192 @@ i_render_color(i_render *r, int x, int y, int width, unsigned char const *src,
   if (!width)
     return;
 
+#if 0
+  /* make sure our line buffer is big enough */
+  if (width > r->width) {
+    int new_width = r->width * 2;
+    if (new_width < width)
+      new_width = width;
+
+    if (r->im->bits <= 8) {
+      if (r->line_8)
+       r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width);
+      else
+       r->line_8 = mymalloc(sizeof(i_color) * new_width);
+      if (r->line_double) {
+       myfree(r->line_double);
+       r->line_double = NULL;
+      }
+    }
+    else {
+      if (r->line_double)
+       r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width);
+      else
+       r->line_double = mymalloc(sizeof(i_fcolor) * new_width);
+      if (r->line_8) {
+       myfree(r->line_8);
+       r->line_double = NULL;
+      }
+    }
+
+    r->width = new_width;
+  }
+#else
+  alloc_line(r, width, r->im->bits <= 8);
+#endif
+
+#code r->im->bits <= 8
+  /*if (r->IM_SUFFIX(line) == NULL)
+    r->IM_SUFFIX(line) = mymalloc(sizeof(IM_COLOR) * r->width);*/
+  (IM_SUFFIX(render_color_tab)[im->channels])(r, x, y, width, src, color);
+#/code
+}
+
+void
+i_render_fill(i_render *r, int x, int y, int width, unsigned char const *src,
+             i_fill_t *fill) {
+  i_img *im = r->im;
+  int fill_channels = im->channels;
+  
+  if (fill_channels == 1 || fill_channels == 3)
+    ++fill_channels;
+
+  if (y < 0 || y >= im->ysize)
+    return;
+  if (x < 0) {
+    width += x;
+    src -= x;
+    x = 0;
+  }
+  if (x + width > im->xsize) {
+    width = im->xsize - x;
+  }
+  if (x >= im->xsize || x + width <= 0 || width <= 0)
+    return;
+
+  if (src) {
+    /* avoid as much work as we can */
+    while (width > 0 && *src == 0) {
+      --width;
+      ++src;
+      ++x;
+    }
+    while (width > 0 && src[width-1] == 0) {
+      --width;
+    }
+  }
+  if (!width)
+    return;
+
+#if 0
+  if (r->im->bits <= 8 && fill->f_fill_with_color) {
+    if (!r->line_8)
+      r->line_8 = mymalloc(sizeof(i_color) * r->width);
+    if (!r->fill_line_8)
+      r->fill_line_8 = mymalloc(sizeof(i_color) * r->width);
+  }
+  else {
+    if (!r->line_double)
+      r->line_double = mymalloc(sizeof(i_fcolor) * r->width);
+    if (!r->fill_line_double)
+      r->fill_line_double = mymalloc(sizeof(i_fcolor) * r->width);
+  }
+
   /* make sure our line buffer is big enough */
   if (width > r->width) {
     int new_width = r->width * 2;
     if (new_width < width)
       new_width = width;
 
-    if (r->line_8)
+    if (r->im->bits <= 8 && fill->f_fill_with_color) {
       r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width);
-    else
+      r->fill_line_8 = myrealloc(r->fill_line_8, sizeof(i_color) * new_width);
+      if (r->line_double) {
+       myfree(r->line_double);
+       r->line_double = NULL;
+      }
+      if (r->fill_line_double) {
+       myfree(r->fill_line_double);
+       r->fill_line_double = NULL;
+      }
+    }
+    else {
       r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width);
+      r->fill_line_double = myrealloc(r->fill_line_double, sizeof(i_fcolor) * new_width);
+      if (r->line_8) {
+       myfree(r->line_8);
+       r->line_8 = NULL;
+      }
+      if (r->fill_line_8) {
+       myfree(r->fill_line_8);
+       r->fill_line_8 = NULL;
+      }
+    }
+    r->width = new_width;
   }
+#else
+  alloc_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
+  alloc_fill_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
+#endif
 
-#code r->im->bits <= 8
-    (IM_SUFFIX(render_color_tab)[im->channels])(r, x, y, width, src, color);
+#code r->im->bits <= 8 && fill->f_fill_with_color
+  if (IM_FILL_COMBINE(fill)) {
+    IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
+    IM_COLOR *destc = r->IM_SUFFIX(line);
+    IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
+    if (src) {
+      unsigned char const *srcc = src;
+      IM_COLOR *fillc = r->IM_SUFFIX(fill_line);
+      int work_width = width;
+      while (work_width) {
+       if (*srcc == 0) {
+         fillc->channel[fill_channels-1] = 0;
+       }
+       else if (*srcc != 255) {
+         fillc->channel[fill_channels-1] =
+           fillc->channel[fill_channels-1] * *srcc / 255;
+       }
+       --work_width;
+       ++srcc;
+       ++fillc;
+      }
+    }
+    IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
+    IM_FILL_COMBINE(fill)(destc, srcc, r->im->channels, width);
+  }
+  else {
+    if (src) {
+      int work_width = width;
+      IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
+      IM_COLOR *destc = r->IM_SUFFIX(line);
+      int ch;
+
+      IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
+      IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
+      while (work_width) {
+       if (*src == 255) {
+         /* just replace it */
+         *destc = *srcc;
+       }
+       else if (*src) {
+         for (ch = 0; ch < im->channels; ++ch) {
+           IM_WORK_T work = (destc->channel[ch] * (IM_SAMPLE_MAX - *src)
+                             + srcc->channel[ch] * *src) / IM_SAMPLE_MAX;
+           destc->channel[ch] = IM_LIMIT(work);
+         }
+       }
+       
+       ++srcc;
+       ++destc;
+       ++src;
+       --work_width;
+      }
+    }
+    else { /* if (src) */
+      IM_FILL_FILLER(fill)(fill, x, y, width, r->im->channels, r->IM_SUFFIX(line));
+    }
+  }
+  IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
 #/code
 }
 
@@ -102,6 +390,92 @@ dump_src(const char *note, unsigned char const *src, int width) {
 
 #code
 
+void
+IM_RENDER_LINE(i_render *r, int x, int y, int width, const IM_SAMPLE_T *src,
+             IM_COLOR *line, IM_FILL_COMBINE_F combine) {
+  i_img *im = r->im;
+  int src_chans = im->channels;
+
+  /* src must always have an alpha channel */
+  if (src_chans == 1 || src_chans == 3)
+    ++src_chans;
+
+  if (y < 0 || y >= im->ysize)
+    return;
+  if (x < 0) {
+    src -= x;
+    line -= x;
+    width += x;
+    x = 0;
+  }
+  if (x + width > im->xsize)
+    width = r->im->xsize - x;
+
+#ifdef IM_EIGHT_BIT
+  alloc_line(r, width, 1);
+#else
+  alloc_line(r, width, 0);
+#endif
+
+  if (combine) {
+    if (src) {
+      int work_width = width;
+      IM_COLOR *linep = line;
+      const IM_SAMPLE_T *srcp = src;
+      int alpha_chan = src_chans - 1;
+      
+      while (work_width) {
+       if (*srcp) {
+         if (*srcp != IM_SAMPLE_MAX) 
+           linep->channel[alpha_chan] = 
+             linep->channel[alpha_chan] * *srcp / IM_SAMPLE_MAX;
+       }
+       else {
+         linep->channel[alpha_chan] = 0;
+       }
+       --work_width;
+       ++srcp;
+       ++linep;
+      }
+    }
+    IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
+    combine(r->IM_SUFFIX(line), line, im->channels, width);
+    IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
+  }
+  else {
+    if (src) {
+      int work_width = width;
+      IM_COLOR *srcc = line;
+      IM_COLOR *destc = r->IM_SUFFIX(line);
+
+      IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
+      while (work_width) {
+       if (*src == 255) {
+         /* just replace it */
+         *destc = *srcc;
+       }
+       else if (*src) {
+         int ch;
+         for (ch = 0; ch < im->channels; ++ch) {
+           IM_WORK_T work = (destc->channel[ch] * (IM_SAMPLE_MAX - *src)
+                             + srcc->channel[ch] * *src) / IM_SAMPLE_MAX;
+           destc->channel[ch] = IM_LIMIT(work);
+         }
+       }
+       
+       ++srcc;
+       ++destc;
+       ++src;
+       --work_width;
+      }
+      IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
+    }
+    else {
+      IM_PLIN(im, x, x+width, y, line);
+    }
+  }
+}
+
 static
 void
 IM_SUFFIX(render_color_13)(i_render *r, int x, int y, int width, 
@@ -199,6 +573,733 @@ IM_SUFFIX(render_color_alpha)(i_render *r, int x, int y, int width,
     ++fetch_offset;
   }
   IM_PLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
+#undef STORE_COLOR
+}
+
+/* combine a line of image data with an output line, both the input
+   and output lines include an alpha channel.
+
+   Both input and output lines have I<channels> of data, channels
+   should be either 2 or 4.
+*/
+
+static void
+IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in, 
+                             int channels, int count) {
+  int ch;
+  int alpha_channel = channels - 1;
+  
+  while (count) {
+    IM_WORK_T src_alpha = in->channel[alpha_channel];
+      
+    if (src_alpha == IM_SAMPLE_MAX)
+      *out = *in;
+    else if (src_alpha) {
+      IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
+      IM_WORK_T orig_alpha = out->channel[alpha_channel];
+      IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
+       
+      for (ch = 0; ch < alpha_channel; ++ch) {
+       out->channel[ch] = ( src_alpha * in->channel[ch]
+                            + remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
+                            ) / dest_alpha;
+      }
+      out->channel[alpha_channel] = dest_alpha;
+    }
+
+    ++out;
+    ++in;
+    --count;
+  }
+}
+
+/* combine a line of image data with an output line.  The input line
+   includes an alpha channel, the output line has no alpha channel.
+   
+   The input line has I<channels>+1 of color data.  The output line
+   has I<channels> of color data.
+*/
+
+static void
+IM_SUFFIX(combine_line_noalpha)
+     (IM_COLOR *out, IM_COLOR const *in, int channels, int count) {
+  int ch;
+
+  while (count) {
+    IM_WORK_T src_alpha = in->channel[channels];
+    
+    if (src_alpha == IM_SAMPLE_MAX)
+      *out = *in;
+    else if (src_alpha) {
+      IM_WORK_T remains;
+      
+      remains = IM_SAMPLE_MAX - src_alpha;
+      for (ch = 0; ch < channels; ++ch) {
+       out->channel[ch] = ( in->channel[ch] * src_alpha
+                            + out->channel[ch] * remains) / IM_SAMPLE_MAX;
+      }
+    }
+    
+    ++out;
+    ++in;
+    --count;
+  }
+}
+
+/* combine a line of image data with an output line, both the input
+   and output lines include an alpha channel.
+
+   Both input and output lines have I<channels> of data, channels
+   should be either 2 or 4.
+
+   This variant does not modify the output alpha channel.
+*/
+
+static void
+IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in, 
+                                  int channels, int count) {
+  int ch;
+  int alpha_channel = channels - 1;
+  
+  while (count) {
+    IM_WORK_T src_alpha = in->channel[alpha_channel];
+      
+    if (src_alpha == IM_SAMPLE_MAX)
+      *out = *in;
+    else if (src_alpha) {
+      IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
+      IM_WORK_T orig_alpha = out->channel[alpha_channel];
+      IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
+       
+      for (ch = 0; ch < alpha_channel; ++ch) {
+       out->channel[ch] = ( src_alpha * in->channel[ch]
+                            + remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
+                            ) / dest_alpha;
+      }
+    }
+
+    ++out;
+    ++in;
+    --count;
+  }
+}
+
+static void
+IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, int count) {
+  if (channels == 2 || channels == 4)
+    IM_SUFFIX(combine_line_alpha)(out, in, channels, count);
+  else
+    IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
+}
+
+static void
+IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, int count) {
+  if (channels == 2 || channels == 4)
+    IM_SUFFIX(combine_line_alpha_na)(out, in, channels, count);
+  else
+    IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
+}
+
+static void IM_SUFFIX(combine_alphablend)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_mult)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_dissolve)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_add)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_subtract)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_diff)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_darken)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_lighten)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_hue)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_sat)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_value)(IM_COLOR *, IM_COLOR *, int, int);
+static void IM_SUFFIX(combine_color)(IM_COLOR *, IM_COLOR *, int, int);
+
+static const IM_FILL_COMBINE_F IM_SUFFIX(combines)[] =
+{
+  NULL,
+  IM_SUFFIX(combine_alphablend),
+  IM_SUFFIX(combine_mult),
+  IM_SUFFIX(combine_dissolve),
+  IM_SUFFIX(combine_add),
+  IM_SUFFIX(combine_subtract),
+  IM_SUFFIX(combine_diff),
+  IM_SUFFIX(combine_lighten),
+  IM_SUFFIX(combine_darken),
+  IM_SUFFIX(combine_hue),
+  IM_SUFFIX(combine_sat),
+  IM_SUFFIX(combine_value),
+  IM_SUFFIX(combine_color)
+};
+
+#/code
+
+/*
+=item i_get_combine(combine, color_func, fcolor_func)
+
+=cut
+*/
+
+void i_get_combine(int combine, i_fill_combine_f *color_func, 
+                   i_fill_combinef_f *fcolor_func) {
+  if (combine < 0 || combine > sizeof(combines_8) / sizeof(*combines_8))
+    combine = 0;
+
+  *color_func = combines_8[combine];
+  *fcolor_func = combines_double[combine];
+}
+
+#code
+
+/*
+  Three good references for implementing combining modes:
+
+  http://www.w3.org/TR/2004/WD-SVG12-20041027/rendering.html
+  referenced as [svg1.2]
+
+  http://gimp-savvy.com/BOOK/index.html?node55.html
+  ("The Blending Modes", if it changes)
+  referenced as [savvy]
+
+  http://www.pegtop.net/delphi/articles/blendmodes/
+  referenced as [pegtop]
+
+  Where differences exist, I follow the SVG practice, the gimp
+  practice, and lastly pegtop.
+*/
+
+
+static void 
+IM_SUFFIX(combine_alphablend)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  IM_SUFFIX(combine_line)(out, in, channels, count);
+}
+
+/*
+Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
+Da'  = Sa.Da + Sa.(1 - Da) + Da.(1 - Sa)
+     = Sa + Da - Sa.Da
+
+When Da=1
+
+Dc' = Sc.Sa.Dc + Dc.(1 - Sa)
+ */
+static void
+IM_SUFFIX(combine_mult)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int ch;
+  IM_COLOR *inp = in;
+  IM_COLOR *outp = out;
+  int work_count = count;
+  int color_channels = i_color_channels(channels);
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T orig_alpha = outp->channel[color_channels];
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      
+      if (src_alpha) {
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha 
+         - (src_alpha * orig_alpha) / IM_SAMPLE_MAX;
+       
+       for (ch = 0; ch < color_channels; ++ch) { 
+         outp->channel[ch] = 
+           (inp->channel[ch] * src_alpha * outp->channel[ch] / IM_SAMPLE_MAX
+            * orig_alpha
+            + inp->channel[ch] * src_alpha * (IM_SAMPLE_MAX - orig_alpha)
+            + outp->channel[ch] * orig_alpha * (IM_SAMPLE_MAX - src_alpha))
+           / IM_SAMPLE_MAX / dest_alpha;
+       }
+       outp->channel[color_channels] = dest_alpha;
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
+      
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) { 
+         outp->channel[ch] = 
+           (src_alpha * inp->channel[ch] * outp->channel[ch] / IM_SAMPLE_MAX
+            + outp->channel[ch] * remains) / IM_SAMPLE_MAX;
+       }
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+}
+
+static void 
+IM_SUFFIX(combine_dissolve)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int color_channels = i_color_channels(channels);
+  int ch;
+
+  if (i_has_alpha(channels)) {
+    while (count--) {
+      if (in->channel[channels-1] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
+       for (ch = 0; ch < color_channels; ++ch) {
+         out->channel[ch] = in->channel[ch];
+       }
+       out->channel[color_channels] = IM_SAMPLE_MAX;
+      }
+      ++out;
+      ++in;
+    }
+  }
+  else {
+    while (count--) {
+      if (in->channel[channels] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
+       for (ch = 0; ch < color_channels; ++ch) {
+         out->channel[ch] = in->channel[ch];
+       }
+      }
+      ++out;
+      ++in;
+    }
+  }
+}
+
+/*
+Dca' = Sca.Da + Dca.Sa + Sca.(1 - Da) + Dca.(1 - Sa)
+     = Sca + Dca
+Da'  = Sa.Da + Da.Sa + Sa.(1 - Da) + Da.(1 - Sa)
+     = Sa + Da
+*/
+
+static void
+IM_SUFFIX(combine_add)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int ch;
+  int color_channels = i_color_channels(channels);
+  int work_count = count;
+  IM_COLOR *inp = in;
+  IM_COLOR *outp = out;
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       IM_WORK_T orig_alpha = outp->channel[color_channels];
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha;
+       if (dest_alpha > IM_SAMPLE_MAX)
+         dest_alpha = IM_SAMPLE_MAX;
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T total = (outp->channel[ch] * orig_alpha + inp->channel[ch] * src_alpha) / dest_alpha;
+         if (total > IM_SAMPLE_MAX)
+           total = IM_SAMPLE_MAX;
+         outp->channel[ch] = total;
+       }
+       outp->channel[color_channels] = dest_alpha;
+      }
+      
+      ++outp;
+      ++inp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T total = outp->channel[ch] + inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
+         if (total > IM_SAMPLE_MAX)
+           total = IM_SAMPLE_MAX;
+         outp->channel[ch] = total;
+       } 
+      }
+      
+      ++outp;
+      ++inp;
+    }
+  }
+}
+
+/* 
+   [pegtop] documents this as max(A+B-256, 0) while [savvy] documents
+   it as max(A-B, 0). [svg1.2] doesn't cover it.
+
+   [savvy] doesn't document how it works with an alpha channel.  GIMP
+   actually seems to calculate the final value then use the alpha
+   channel to apply that to the target.
+ */
+static void
+IM_SUFFIX(combine_subtract)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int ch;
+  IM_COLOR const *inp = in;
+  IM_COLOR *outp = out;
+  int work_count = count;
+  int color_channels = i_color_channels(channels);
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       IM_WORK_T orig_alpha = outp->channel[color_channels];
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha;
+       if (dest_alpha > IM_SAMPLE_MAX)
+         dest_alpha = IM_SAMPLE_MAX;
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T total = 
+           (outp->channel[ch] * orig_alpha - inp->channel[ch] * src_alpha) 
+           / dest_alpha;
+         if (total < 0)
+           total = 0;
+         outp->channel[ch] = total;
+       }
+       outp->channel[color_channels] = dest_alpha;
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T total = outp->channel[ch] - inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
+         if (total < 0)
+           total = 0;
+         outp->channel[ch] = total;
+       } 
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+}
+
+#ifdef IM_EIGHT_BIT
+#define IM_abs(x) abs(x)
+#else
+#define IM_abs(x) fabs(x)
+#endif
+
+/*
+Dca' = abs(Dca.Sa - Sca.Da) + Sca.(1 - Da) + Dca.(1 - Sa)
+     = Sca + Dca - 2.min(Sca.Da, Dca.Sa)
+Da'  = Sa + Da - Sa.Da 
+*/
+static void
+IM_SUFFIX(combine_diff)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int ch;
+  IM_COLOR const *inp = in;
+  IM_COLOR *outp = out;
+  int work_count = count;
+  int color_channels = i_color_channels(channels);
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       IM_WORK_T orig_alpha = outp->channel[color_channels];
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha 
+         - src_alpha * orig_alpha / IM_SAMPLE_MAX;
+       for (ch = 0; ch < color_channels; ++ch) {
+         IM_WORK_T src = inp->channel[ch] * src_alpha;
+         IM_WORK_T orig = outp->channel[ch] * orig_alpha;
+         IM_WORK_T src_da = src * orig_alpha;
+         IM_WORK_T dest_sa = orig * src_alpha;
+         IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
+         outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / dest_alpha;
+       }
+       outp->channel[color_channels] = dest_alpha;
+      }
+      ++inp;
+      ++outp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) {
+         IM_WORK_T src = inp->channel[ch] * src_alpha;
+         IM_WORK_T orig = outp->channel[ch] * IM_SAMPLE_MAX;
+         IM_WORK_T src_da = src * IM_SAMPLE_MAX;
+         IM_WORK_T dest_sa = orig * src_alpha;
+         IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
+         outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / IM_SAMPLE_MAX;
+       }
+      }
+      ++inp;
+      ++outp;
+    }
+  }
+}
+
+#undef IM_abs
+
+/*
+  Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca(1 - Sa)
+  Da' = Sa + Da - Sa.Da
+
+  To hoist some code:
+
+  Dca' = min(Sc.Sa.Da, Dc.Da.Sa) + Sca - Sca.Da + Dca - Dca.Sa
+       = Sa.Da.min(Sc, Dc) + Sca - Sca.Da + Dca - Dca.Sa
+
+  When Da=1:
+
+  Dca' = min(Sca.1, Dc.1.Sa) + Sca.(1 - 1) + Dc.1(1 - Sa)
+       = min(Sca, Dc.Sa) + Dc(1-Sa)
+       = Sa.min(Sc, Dc) + Dc - Dc.Sa
+  Da'  = Sa + 1 - Sa.1
+       = 1
+ */
+static void 
+IM_SUFFIX(combine_darken)(IM_COLOR *out, IM_COLOR *in, int channels, 
+                         int count) {
+  int ch;
+  IM_COLOR const *inp = in;
+  IM_COLOR *outp = out;
+  int work_count = count;
+  int color_channels = i_color_channels(channels);
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+
+      if (src_alpha) {
+       IM_WORK_T orig_alpha = outp->channel[color_channels];
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha 
+         - src_alpha * orig_alpha / IM_SAMPLE_MAX;
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T Sca = inp->channel[ch] * src_alpha;
+         IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
+         IM_WORK_T ScaDa = Sca * orig_alpha;
+         IM_WORK_T DcaSa = Dca * src_alpha;
+         IM_WORK_T minc = ScaDa < DcaSa ? ScaDa : DcaSa;
+         outp->channel[ch] = 
+           ( 
+            minc + (Sca + Dca) * IM_SAMPLE_MAX
+            - ScaDa - DcaSa
+            ) / (IM_SAMPLE_MAX * dest_alpha);
+       } 
+       outp->channel[color_channels] = dest_alpha;
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T minc = outp->channel[ch] < inp->channel[ch]
+           ? outp->channel[ch] : inp->channel[ch];
+         outp->channel[ch] = 
+           ( 
+            src_alpha * minc + 
+            outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
+            ) / IM_SAMPLE_MAX;
+       } 
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+}
+
+static void 
+IM_SUFFIX(combine_lighten)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  int ch;
+  IM_COLOR const *inp = in;
+  IM_COLOR *outp = out;
+  int work_count = count;
+  int color_channels = i_color_channels(channels);
+
+  if (i_has_alpha(channels)) {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+
+      if (src_alpha) {
+       IM_WORK_T orig_alpha = outp->channel[color_channels];
+       IM_WORK_T dest_alpha = src_alpha + orig_alpha 
+         - src_alpha * orig_alpha / IM_SAMPLE_MAX;
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T Sca = inp->channel[ch] * src_alpha;
+         IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
+         IM_WORK_T ScaDa = Sca * orig_alpha;
+         IM_WORK_T DcaSa = Dca * src_alpha;
+         IM_WORK_T maxc = ScaDa > DcaSa ? ScaDa : DcaSa;
+         outp->channel[ch] = 
+           ( 
+            maxc + (Sca + Dca) * IM_SAMPLE_MAX
+            - ScaDa - DcaSa
+            ) / (IM_SAMPLE_MAX * dest_alpha);
+       } 
+       outp->channel[color_channels] = dest_alpha;
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+  else {
+    while (work_count--) {
+      IM_WORK_T src_alpha = inp->channel[color_channels];
+
+      if (src_alpha) {
+       for (ch = 0; ch < color_channels; ++ch) { 
+         IM_WORK_T maxc = outp->channel[ch] > inp->channel[ch]
+           ? outp->channel[ch] : inp->channel[ch];
+         outp->channel[ch] = 
+           ( 
+            src_alpha * maxc + 
+            outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
+            ) / IM_SAMPLE_MAX;
+       } 
+      }
+      ++outp;
+      ++inp;
+    }
+  }
+}
+
+#if IM_EIGHT_BIT
+#define IM_RGB_TO_HSV i_rgb_to_hsv
+#define IM_HSV_TO_RGB i_hsv_to_rgb
+#else
+#define IM_RGB_TO_HSV i_rgb_to_hsvf
+#define IM_HSV_TO_RGB i_hsv_to_rgbf
+#endif
+
+static void 
+IM_SUFFIX(combine_hue)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  if (channels > 2) {
+    IM_COLOR *inp = in;
+    IM_COLOR const *outp = out;
+    int work_count = count;
+
+    if (i_has_alpha(channels)) {
+      while (work_count--) {
+       IM_COLOR c = *inp;
+       IM_RGB_TO_HSV(&c);
+       /* only transfer hue if there's saturation */
+       if (c.channel[1] && inp->channel[3] && outp->channel[3]) {
+         *inp = *outp;
+         IM_RGB_TO_HSV(inp);
+         /* and no point in setting the target hue if the target has no sat */
+         if (inp->channel[1]) {
+           inp->channel[0] = c.channel[0];
+           IM_HSV_TO_RGB(inp);
+           inp->channel[3] = c.channel[3];
+         }
+         else {
+           inp->channel[3] = 0;
+         }
+       }
+       else {
+         inp->channel[3] = 0;
+       }
+       
+       ++outp;
+       ++inp;
+      }
+    }
+    else {
+      while (work_count--) {
+       IM_COLOR c = *inp;
+       IM_RGB_TO_HSV(&c);
+       /* only transfer hue if there's saturation */
+       if (c.channel[1] && inp->channel[3]) {
+         *inp = *outp;
+         IM_RGB_TO_HSV(inp);
+         /* and no point in setting the target hue if the target has no sat */
+         if (inp->channel[1]) {
+           inp->channel[0] = c.channel[0];
+           IM_HSV_TO_RGB(inp);
+           inp->channel[3] = c.channel[3];
+         }
+       }
+       else {
+         inp->channel[3] = 0;
+       }
+       
+       ++outp;
+       ++inp;
+      }
+    }
+
+    IM_SUFFIX(combine_line_na)(out, in, channels, count);
+  }
 }
 
+static void
+IM_SUFFIX(combine_sat)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  if (channels > 2) {
+    IM_COLOR *inp = in;
+    IM_COLOR const *outp = out;
+    int work_count = count;
+
+    while (work_count--) {
+      IM_COLOR c = *inp;
+      *inp = *outp;
+      IM_RGB_TO_HSV(&c);
+      IM_RGB_TO_HSV(inp);
+      inp->channel[1] = c.channel[1];
+      IM_HSV_TO_RGB(inp);
+      inp->channel[3] = c.channel[3];
+      ++outp;
+      ++inp;
+    }
+
+    IM_SUFFIX(combine_line_na)(out, in, channels, count);
+  }
+}
+
+static void 
+IM_SUFFIX(combine_value)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  if (channels > 2) {
+    IM_COLOR *inp = in;
+    IM_COLOR const *outp = out;
+    int work_count = count;
+
+    while (work_count--) {
+      IM_COLOR c = *inp;
+      *inp = *outp;
+      IM_RGB_TO_HSV(&c);
+      IM_RGB_TO_HSV(inp);
+      inp->channel[2] = c.channel[2];
+      IM_HSV_TO_RGB(inp);
+      inp->channel[3] = c.channel[3];
+      ++outp;
+      ++inp;
+    }
+  }
+
+  /* all images have a "value channel" - for greyscale it's the only
+     colour channel */
+  IM_SUFFIX(combine_line_na)(out, in, channels, count);
+}
+
+static void 
+IM_SUFFIX(combine_color)(IM_COLOR *out, IM_COLOR *in, int channels, int count) {
+  if (channels > 2) {
+    IM_COLOR *inp = in;
+    IM_COLOR const *outp = out;
+    int work_count = count;
+
+    while (work_count--) {
+      IM_COLOR c = *inp;
+      *inp = *outp;
+      IM_RGB_TO_HSV(&c);
+      IM_RGB_TO_HSV(inp);
+      inp->channel[0] = c.channel[0];
+      inp->channel[1] = c.channel[1];
+      IM_HSV_TO_RGB(inp);
+      inp->channel[3] = c.channel[3];
+      ++outp;
+      ++inp;
+    }
+
+    IM_SUFFIX(combine_line_na)(out, in, channels, count);
+  }
+}
+
+#undef IM_RGB_TO_HSV
+#undef IM_HSV_TO_RGB
+
 #/code
index 8b34a97..2eb79a2 100644 (file)
--- a/rendert.h
+++ b/rendert.h
@@ -4,9 +4,14 @@
 typedef struct {
   int magic;
   i_img *im;
+
+  int line_width;
   i_color *line_8;
   i_fcolor *line_double;
-  int width;
+
+  int fill_width;
+  i_color *fill_line_8;
+  i_fcolor *fill_line_double;
 } i_render;
 
 #endif
index fdc23d3..27a4da4 100644 (file)
@@ -6,54 +6,61 @@ rubthru_targ_noalpha(i_img *im, i_img *src,
                      int src_minx, int src_miny,
                      int src_maxx, int src_maxy) {
   int x, y, ttx, tty;
-  int chancount;
-  int chans[3];
   int alphachan;
   int ch;
+  int width = src_maxx - src_minx;
+  int want_channels;
 
   i_clear_error();
 
-  if (im->channels == 3 && src->channels == 4) {
-    chancount = 3;
-    chans[0] = 0; chans[1] = 1; chans[2] = 2;
+  if (im->channels == 3 && (src->channels == 4 || src->channels == 2)) {
+    want_channels = 4;
     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;
+  else if (im->channels == 1 && (src->channels == 4 || src->channels == 2)) {
+    want_channels = 2;
     alphachan = 1;
   }
   else {
-    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)");
-    return 0;
+    i_copyto(im, src, src_minx, src_miny, src_maxx, src_maxy, tx, ty);
+    return 1;
   }
 
 #code im->bits <= 8 && src->bits <= 8
   IM_WORK_T alpha;
-  IM_COLOR pv, orig, dest;
+  IM_COLOR *src_line, *dest_line;
+  
+  src_line = mymalloc(sizeof(IM_COLOR) * width);
+  dest_line = mymalloc(sizeof(IM_COLOR) * width);
 
   tty = ty;
   for(y = src_miny; y < src_maxy; y++) {
+    IM_COLOR *srcp = src_line;
+    IM_COLOR *destp = dest_line;
     ttx = tx;
+    IM_GLIN(src, src_minx, src_maxx, y, src_line);
+    IM_GLIN(im, tx, tx + width, tty, dest_line);
+    if (src->channels != want_channels)
+      IM_ADAPT_COLORS(want_channels, src->channels, src_line, width);
+
     for(x = src_minx; x < src_maxx; x++) {
-      IM_GPIX(src, x,   y,   &pv);
-      IM_GPIX(im,  ttx, tty, &orig);
-      alpha = pv.channel[alphachan];
-      for (ch = 0; ch < chancount; ++ch) {
-        dest.channel[ch] = (alpha * pv.channel[chans[ch]]
-                            + (IM_SAMPLE_MAX - alpha) * orig.channel[ch])/IM_SAMPLE_MAX;
+      alpha = srcp->channel[alphachan];
+      for (ch = 0; ch < im->channels; ++ch) {
+       IM_WORK_T samp = (alpha * srcp->channel[ch]
+                            + (IM_SAMPLE_MAX - alpha) * destp->channel[ch])/IM_SAMPLE_MAX;
+        destp->channel[ch] = IM_LIMIT(samp);
       }
-      IM_PPIX(im, ttx, tty, &dest);
-      ttx++;
+      ++srcp;
+      ++destp;
+      ttx;
     }
+    IM_PLIN(im, tx, tx + width, tty, dest_line);
     tty++;
   }
+  myfree(src_line);
+  myfree(dest_line);
 #/code
+
   return 1;
 }
 
@@ -62,64 +69,86 @@ rubthru_targ_alpha(i_img *im, i_img *src, int tx, int ty,
                    int src_minx, int src_miny,
                    int src_maxx, int src_maxy) {
   int x, y, ttx, tty;
-  int chancount;
-  int chans[3];
+  int want_channels;
   int alphachan;
   int ch;
   int targ_alpha_chan;
+  int width = src_maxx - src_minx;
   
-  if (im->channels == 4 && src->channels == 4) {
-    chancount = 3;
-    chans[0] = 0; chans[1] = 1; chans[2] = 2;
+  if (im->channels == 4 && (src->channels == 4 || src->channels == 2)) {
     alphachan = 3;
+    want_channels = 4;
   }
-  else if (im->channels == 4 && src->channels == 2) {
-    chancount = 3;
-    chans[0] = chans[1] = chans[2] = 0;
-    alphachan = 1;
-  }
-  else if (im->channels == 2 && src->channels == 2) {
-    chancount = 1;
-    chans[0] = 0;
+  else if (im->channels == 2 && (src->channels == 4 || src->channels == 2)) {
     alphachan = 1;
+    want_channels = 2;
   }
   else {
-    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)");
-    return 0;
+    i_copyto(im, src, src_minx, src_miny, src_maxx, src_maxy, tx, ty);
+    return 1;
   }
 
   targ_alpha_chan = im->channels - 1;
 
 #code im->bits <= 8 && src->bits <= 8
   IM_WORK_T src_alpha, orig_alpha, dest_alpha, remains;
-  IM_COLOR pv, orig, dest;
+  IM_COLOR *src_line, *dest_line;
+
+  src_line = mymalloc(sizeof(IM_COLOR) * width);
+  dest_line = mymalloc(sizeof(IM_COLOR) * width);
 
   tty = ty;
   for(y = src_miny; y < src_maxy; y++) {
-    ttx = tx;
-    for(x = src_minx; x < src_maxx; x++) {
-      IM_GPIX(src, x,   y,   &pv);
-      src_alpha = pv.channel[alphachan];
-      if (src_alpha) {
-        remains = IM_SAMPLE_MAX - src_alpha;
-        IM_GPIX(im,  ttx, tty, &orig);
-        orig_alpha = orig.channel[targ_alpha_chan];
-        dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
-
-        for (ch = 0; ch < chancount; ++ch) {
-          dest.channel[ch] = 
-            ( src_alpha * pv.channel[chans[ch]]
-              + remains * orig.channel[ch] * orig_alpha / IM_SAMPLE_MAX 
-              ) / dest_alpha;
-        }
-        /* dest's alpha */
-        dest.channel[targ_alpha_chan] = dest_alpha;
-        IM_PPIX(im, ttx, tty, &dest);
+    int min_x, max_x;
+    IM_COLOR *srcp = src_line;
+    IM_COLOR *destp = dest_line;
+    IM_GLIN(src, src_minx, src_maxx, y, src_line);
+    if (src->channels != want_channels)
+      IM_ADAPT_COLORS(want_channels, src->channels, src_line, width);
+    min_x = src_minx;
+    max_x = src_maxx;
+
+    while (min_x < max_x && srcp->channel[alphachan] == 0) {
+      ++min_x;
+      ++srcp;
+    }
+    while (max_x > min_x && src_line[max_x-1].channel[alphachan] == 0) {
+      --max_x;
+    }
+
+    if (max_x > min_x) {
+      int work_left = tx + min_x - src_minx;
+      int work_width = max_x - min_x;
+      ttx = work_left;
+      IM_GLIN(im, work_left, work_left + work_width, tty, dest_line);
+      
+      for(x = src_minx; x < src_maxx; x++) {
+       src_alpha = srcp->channel[alphachan];
+       if (src_alpha) {
+         remains = IM_SAMPLE_MAX - src_alpha;
+         orig_alpha = destp->channel[targ_alpha_chan];
+         dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
+         
+         for (ch = 0; ch < im->channels-1; ++ch) {
+           IM_WORK_T samp = 
+             ( src_alpha * srcp->channel[ch]
+               + remains * destp->channel[ch] * orig_alpha / IM_SAMPLE_MAX 
+               ) / dest_alpha;
+           destp->channel[ch] = IM_LIMIT(samp);
+         }
+         /* dest's alpha */
+         destp->channel[targ_alpha_chan] = dest_alpha;
+       }
+       ++srcp;
+       ++destp;
+       ttx++;
       }
-      ttx++;
+      IM_PLIN(im, work_left, work_left + work_width, tty, dest_line);
     }
     tty++;
   }
+  myfree(dest_line);
+  myfree(src_line);
 #/code
   return 1;
 }
@@ -143,6 +172,35 @@ unmodified.
 int
 i_rubthru(i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny,
          int src_maxx, int src_maxy) {
+  if (src_minx < 0) {
+    tx -= src_minx;
+    src_minx = 0;
+  }
+  if (src_miny < 0) {
+    ty -= src_miny;
+    src_miny = 0;
+  }
+  if (tx < 0) {
+    src_minx -= tx;
+    tx = 0;
+  }
+  if (ty < 0) {
+    src_miny -= ty;
+    ty = 0;
+  }
+  if (src_maxx > src->xsize) {
+    src_maxx = src->xsize;
+  }
+  if (src_maxy > src->ysize) {
+    src_maxy = src->ysize;
+  }
+  if (tx >= im->xsize || ty >= im->ysize
+      || src_minx >= src_maxx || src_miny >= src_maxy) {
+    i_clear_error();
+    i_push_error(0, "rubthrough: nothing to do");
+    return 0;
+  }
+
   if (im->channels == 1 || im->channels == 3)
     return rubthru_targ_noalpha(im, src, tx, ty, src_minx, src_miny, 
                                 src_maxx, src_maxy);
index fcff736..5cc253e 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-use Test::More tests => 56;
+use Test::More tests => 121;
 
 use Imager ':handy';
 use Imager::Fill;
@@ -193,43 +193,124 @@ is(Imager::i_img_diff($ffim->{IMG}, $copy->{IMG}), 0,
 # test combining modes
 my $fill = NC(192, 128, 128, 128);
 my $target = NC(64, 32, 64);
+my $trans_target = NC(64, 32, 64, 128);
 my %comb_tests =
   (
-   none=>{ result=>$fill },
-   normal=>{ result=>NC(128, 80, 96) },
-   multiply => { result=>NC(56, 24, 48) },
-   dissolve => { result=>[ $target, NC(128, 80, 96) ] },
-   add => { result=>NC(159, 96, 128) },
-   subtract => { result=>NC(31, 15, 31) }, # 31.87, 15.9, 31.87
-   diff => { result=>NC(96, 64, 64) },
-   lighten => { result=>NC(128, 80, 96) },
-   darken => { result=>$target },
+   none=>
+   { 
+    opaque => $fill,
+    trans => $fill,
+   },
+   normal=>
+   { 
+    opaque => NC(128, 80, 96),
+    trans => NC(150, 96, 107, 191),
+   },
+   multiply => 
+   { 
+    opaque => NC(56, 24, 48),
+    trans => NC(101, 58, 74, 192),
+   },
+   dissolve => 
+   { 
+    opaque => [ $target, NC(192, 128, 128, 255) ],
+    trans => [ $trans_target, NC(192, 128, 128, 255) ],
+   },
+   add => 
+   { 
+    opaque => NC(159, 96, 128),
+    trans => NC(128, 80, 96, 255),
+   },
+   subtract => 
+   { 
+    opaque => NC(0, 0, 0),
+    trans => NC(0, 0, 0, 255),
+   },
+   diff => 
+   { 
+    opaque => NC(96, 64, 64),
+    trans => NC(127, 85, 85, 192),
+   },
+   lighten => 
+   { 
+    opaque => NC(128, 80, 96), 
+    trans => NC(149, 95, 106, 192), 
+   },
+   darken => 
+   { 
+    opaque => $target,
+    trans => NC(106, 63, 85, 192),
+   },
    # the following results are based on the results of the tests and
    # are suspect for that reason (and were broken at one point <sigh>)
    # but trying to work them out manually just makes my head hurt - TC
-   hue => { result=>NC(64, 32, 47) },
-   saturation => { result=>NC(63, 37, 64) },
-   value => { result=>NC(127, 64, 128) },
-   color => { result=>NC(64, 37, 52) },
+   hue => 
+   { 
+    opaque => NC(64, 32, 47),
+    trans => NC(64, 32, 42, 128),
+   },
+   saturation => 
+   { 
+    opaque => NC(63, 37, 64),
+    trans => NC(64, 39, 64, 128),
+   },
+   value => 
+   { 
+    opaque => NC(127, 64, 128),
+    trans => NC(149, 75, 150, 128),
+   },
+   color => 
+   { 
+    opaque => NC(64, 37, 52),
+    trans => NC(64, 39, 50, 128),
+   },
   );
 
 for my $comb (Imager::Fill->combines) {
   my $test = $comb_tests{$comb};
-  my $targim = Imager->new(xsize=>1, ysize=>1);
-  $targim->box(filled=>1, color=>$target);
   my $fillobj = Imager::Fill->new(solid=>$fill, combine=>$comb);
-  $targim->box(fill=>$fillobj);
-  my $c = Imager::i_get_pixel($targim->{IMG}, 0, 0);
-  if ($test->{result} =~ /ARRAY/) {
-    ok(scalar grep(color_close($_, $c), @{$test->{result}}), 
-       "combine '$comb'")
-      or print "# got:",join(",", $c->rgba),"  allowed: ", 
-        join("|", map { join(",", $_->rgba) } @{$test->{result}}),"\n";
-  }
-  else {
-    ok(color_close($c, $test->{result}), "combine '$comb'")
-      or print "# got: ",join(",", $c->rgba),
-        "  allowed: ",join(",", $test->{result}->rgba),"\n";
+
+  for my $bits (qw(8 double)) {
+    {
+      my $targim = Imager->new(xsize=>4, ysize=>4, bits => $bits);
+      $targim->box(filled=>1, color=>$target);
+      $targim->box(fill=>$fillobj);
+      my $c = Imager::i_get_pixel($targim->{IMG}, 1, 1);
+      my $allowed = $test->{opaque};
+      $allowed =~ /ARRAY/ or $allowed = [ $allowed ];
+      ok(scalar grep(color_close($_, $c), @$allowed), 
+        "opaque '$comb' $bits bits")
+       or print "# got:",join(",", $c->rgba),"  allowed: ", 
+         join("|", map { join(",", $_->rgba) } @$allowed),"\n";
+    }
+    
+    {
+      # make sure the alpha path in the combine function produces the same
+      # or at least as sane a result as the non-alpha path
+      my $targim = Imager->new(xsize=>4, ysize=>4, channels => 4, bits => $bits);
+      $targim->box(filled=>1, color=>$target);
+      $targim->box(fill=>$fillobj);
+      my $c = Imager::i_get_pixel($targim->{IMG}, 1, 1);
+      my $allowed = $test->{opaque};
+      $allowed =~ /ARRAY/ or $allowed = [ $allowed ];
+      ok(scalar grep(color_close4($_, $c), @$allowed), 
+        "opaque '$comb' 4-channel $bits bits")
+       or print "# got:",join(",", $c->rgba),"  allowed: ", 
+         join("|", map { join(",", $_->rgba) } @$allowed),"\n";
+    }
+    
+    {
+      my $transim = Imager->new(xsize => 4, ysize => 4, channels => 4, bits => $bits);
+      $transim->box(filled=>1, color=>$trans_target);
+      $transim->box(fill => $fillobj);
+      my $c = $transim->getpixel(x => 1, 'y' => 1);
+      my $allowed = $test->{trans};
+      $allowed =~ /ARRAY/ or $allowed = [ $allowed ];
+      ok(scalar grep(color_close4($_, $c), @$allowed), 
+        "translucent '$comb' $bits bits")
+       or print "# got:",join(",", $c->rgba),"  allowed: ", 
+         join("|", map { join(",", $_->rgba) } @$allowed),"\n";
+    }
   }
 }
 
@@ -346,6 +427,20 @@ sub color_close {
   return 1;
 }
 
+sub color_close4 {
+  my ($c1, $c2) = @_;
+
+  my @c1 = $c1->rgba;
+  my @c2 = $c2->rgba;
+
+  for my $i (0..3) {
+    if (abs($c1[$i]-$c2[$i]) > 2) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
 # for use during testing
 sub save {
   my ($im, $name) = @_;
index 41161f2..160a514 100644 (file)
@@ -1,8 +1,9 @@
 #!perl -w
 use strict;
-use Test::More tests => 23;
+use Test::More tests => 54;
 
-BEGIN { use_ok("Imager") }
+use Imager;
+use Imager::Test qw(is_image);
 
 #$Imager::DEBUG=1;
 
@@ -135,3 +136,164 @@ ok($img->write(type=>'pnm',file=>'testout/t66.ppm'), "save it")
        "check pasted correctly");
   }
 }
+
+{ # https://rt.cpan.org/Ticket/Display.html?id=30908
+  # we now adapt the source channels to the target
+  # check each combination works as expected
+
+  # various source images
+  my $src1 = Imager->new(xsize => 50, ysize => 50, channels => 1);
+  my $g_grey_full = Imager::Color->new(128, 255, 0, 0);
+  my $g_white_50 = Imager::Color->new(255, 128, 0, 0);
+  $src1->box(filled => 1, xmax => 24, color => $g_grey_full);
+
+  my $src2 = Imager->new(xsize => 50, ysize => 50, channels => 2);
+  $src2->box(filled => 1, xmax => 24, color => $g_grey_full);
+  $src2->box(filled => 1, xmin => 25, color => $g_white_50);
+
+  my $c_red_full = Imager::Color->new(255, 0, 0);
+  my $c_blue_full = Imager::Color->new(0, 0, 255);
+  my $src3 = Imager->new(xsize => 50, ysize => 50, channels => 3);
+  $src3->box(filled => 1, xmax => 24, color => $c_red_full);
+  $src3->box(filled => 1, xmin => 25, color => $c_blue_full);
+
+  my $c_green_50 = Imager::Color->new(0, 255, 0, 127);
+  my $src4 = Imager->new(xsize => 50, ysize => 50, channels => 4);
+  $src4->box(filled => 1, xmax => 24, color => $c_blue_full);
+  $src4->box(filled => 1, xmin => 25, color => $c_green_50);
+
+  my @left_box = ( box => [ 25, 25, 49, 74 ] );
+  my @right_box = ( box => [ 50, 25, 74, 74 ] );
+
+  { # 1 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 1);
+    $base->box(filled => 1, color => Imager::Color->new(64, 255, 0, 0));
+
+    my $work = $base->copy;
+    ok($work->paste(left => 25, top => 25, src => $src1), "paste 1 to 1");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(left => 25, top => 25, src => $src2), "paste 2 to 1");
+    $comp = $base->copy;
+    $comp->box(filled => 1, @left_box, color => $g_grey_full);
+    $comp->box(filled => 1, @right_box, color => [ 128, 0, 0, 0 ]);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(left => 25, top => 25, src => $src3), "paste 3 to 1");
+     $comp = $base->copy;
+    $comp->box(filled => 1, @left_box, color => [ 57, 255, 0, 0 ]);
+    $comp->box(filled => 1, @right_box, color => [ 18, 255, 0, 0 ]);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(left => 25, top => 25, src => $src4), "paste 4 to 1");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 90, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+  }
+
+  { # 2 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 2);
+    $base->box(filled => 1, color => [ 128, 128, 0, 0 ]);
+    
+    my $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src1), "paste 1 to 2");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => [ 0, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src2), "paste 2 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => $g_white_50, @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src3), "paste 3 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 57, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src4), "paste 4 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 180, 127, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+  }
+
+  { # 3 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 3);
+    $base->box(filled => 1, color => [ 128, 255, 0, 0 ]);
+    
+    my $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src1), "paste 1 to 3");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src2), "paste 2 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src3), "paste 3 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src4), "paste 4 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 127, 0 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+  }
+
+  { # 4 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 4);
+    $base->box(filled => 1, color => [ 128, 255, 64, 128 ]);
+    
+    my $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src1), "paste 1 to 4");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src2), "paste 2 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 255, 255, 255, 128 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src3), "paste 3 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+
+    $work = $base->copy;
+    ok($work->paste(top => 25, left => 25, src => $src4), "paste 4 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => $c_blue_full, @left_box);
+    $comp->box(filled => 1, color => $c_green_50, @right_box);
+    is_image($work, $comp, "compare paste target to expected");
+  }
+}
index 2bd7642..447c3eb 100644 (file)
@@ -1,7 +1,8 @@
 #!perl -w
 use strict;
-use Test::More tests => 38;
-BEGIN { use_ok(Imager => qw(:all :handy)); }
+use Test::More tests => 76;
+use Imager qw(:all :handy);
+use Imager::Test qw(is_image);
 
 init_log("testout/t69rubthru.log", 1);
 
@@ -47,10 +48,6 @@ is(($c->rgba)[0], 0, "check grey level");
 is((Imager::i_get_pixel($gtarg, 30, 30)->rgba)[0], 128,
    "check grey level at 30, 30");
 
-# an attempt rub a 4 channel image over 1 channel should fail
-ok(!i_rubthru($gtarg, $src, 10, 10, 0, 0, $src_width, $src_height),
-   "check failure of 4 channel over 1 channel image");
-
 # simple test for 16-bit/sample images
 my $targ16 = Imager::i_img_16_new(100, 100, 3);
 ok(i_rubthru($targ16, $src, 10, 10, 0, 0, $src_width, $src_height),
@@ -71,13 +68,7 @@ ok(color_cmp(Imager::i_get_pixel($ootarg->{IMG}, 10, 10), NC(0, 0, 0)) == 0,
 ok(color_cmp(Imager::i_get_pixel($ootarg->{IMG}, 30, 30), NC(128, 0, 0)) == 0,
    "check pixel at 30, 30");
 
-# make sure we fail as expected
 my $oogtarg = Imager->new(xsize=>100, ysize=>100, channels=>1);
-ok(!$oogtarg->rubthrough(src=>$oosrc), "check oo fails correctly");
-
-is($oogtarg->errstr, 
-   'rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)',
-   "check error message");
 
 { # check empty image errors
   my $empty = Imager->new;
@@ -90,56 +81,220 @@ is($oogtarg->errstr,
 
 {
   # alpha source and target
-  my $src = Imager->new(xsize => 10, ysize => 1, channels => 4);
-  my $targ = Imager->new(xsize => 10, ysize => 2, channels => 4);
-
-  # simple initialization
-  $targ->setscanline('y' => 1, x => 1,
-                     pixels =>
-                     [
-                      NC(255, 128, 0, 255),
-                      NC(255, 128, 0, 128),
-                      NC(255, 128, 0, 0),
-                      NC(255, 128, 0, 255),
-                      NC(255, 128, 0, 128),
-                      NC(255, 128, 0, 0),
-                      NC(255, 128, 0, 255),
-                      NC(255, 128, 0, 128),
-                      NC(255, 128, 0, 0),
-                     ]);
-  $src->setscanline('y' => 0,
-                    pixels =>
-                    [
-                     NC(0, 128, 255, 0),
-                     NC(0, 128, 255, 0),
-                     NC(0, 128, 255, 0),
-                     NC(0, 128, 255, 128),
-                     NC(0, 128, 255, 128),
-                     NC(0, 128, 255, 128),
-                     NC(0, 128, 255, 255),
-                     NC(0, 128, 255, 255),
-                     NC(0, 128, 255, 255),
-                    ]);
-  ok($targ->rubthrough(src => $src,
-                       tx => 1, ty => 1), "do 4 on 4 rubthrough");
-  iscolora($targ->getpixel(x => 1, y => 1), NC(255, 128, 0, 255),
-           "check at zero source coverage on full targ coverage");
-  iscolora($targ->getpixel(x => 2, y => 1), NC(255, 128, 0, 128),
-           "check at zero source coverage on half targ coverage");
-  iscolora($targ->getpixel(x => 3, y => 1), NC(255, 128, 0, 0),
-           "check at zero source coverage on zero targ coverage");
-  iscolora($targ->getpixel(x => 4, y => 1), NC(127, 128, 128, 255),
-           "check at half source_coverage on full targ coverage");
-  iscolora($targ->getpixel(x => 5, y => 1), NC(85, 128, 170, 191),
-           "check at half source coverage on half targ coverage");
-  iscolora($targ->getpixel(x => 6, y => 1), NC(0, 128, 255, 128),
-           "check at half source coverage on zero targ coverage");
-  iscolora($targ->getpixel(x => 7, y => 1), NC(0, 128, 255, 255),
-           "check at full source_coverage on full targ coverage");
-  iscolora($targ->getpixel(x => 8, y => 1), NC(0, 128, 255, 255),
-           "check at full source coverage on half targ coverage");
-  iscolora($targ->getpixel(x => 9, y => 1), NC(0, 128, 255, 255),
-           "check at full source coverage on zero targ coverage");
+  for my $method (qw/rubthrough compose/) {
+
+    my $src = Imager->new(xsize => 10, ysize => 1, channels => 4);
+    my $targ = Imager->new(xsize => 10, ysize => 2, channels => 4);
+
+    # simple initialization
+    $targ->setscanline('y' => 1, x => 1,
+                      pixels =>
+                      [
+                       NC(255, 128, 0, 255),
+                       NC(255, 128, 0, 128),
+                       NC(255, 128, 0, 0),
+                       NC(255, 128, 0, 255),
+                       NC(255, 128, 0, 128),
+                       NC(255, 128, 0, 0),
+                       NC(255, 128, 0, 255),
+                       NC(255, 128, 0, 128),
+                       NC(255, 128, 0, 0),
+                      ]);
+    $src->setscanline('y' => 0,
+                     pixels =>
+                     [
+                      NC(0, 128, 255, 0),
+                      NC(0, 128, 255, 0),
+                      NC(0, 128, 255, 0),
+                      NC(0, 128, 255, 128),
+                      NC(0, 128, 255, 128),
+                      NC(0, 128, 255, 128),
+                      NC(0, 128, 255, 255),
+                      NC(0, 128, 255, 255),
+                      NC(0, 128, 255, 255),
+                     ]);
+    ok($targ->$method(src => $src, combine => 'normal',
+                     tx => 1, ty => 1), "do 4 on 4 $method");
+    iscolora($targ->getpixel(x => 1, 'y' => 1), NC(255, 128, 0, 255),
+            "check at zero source coverage on full targ coverage");
+    iscolora($targ->getpixel(x => 2, 'y' => 1), NC(255, 128, 0, 128),
+            "check at zero source coverage on half targ coverage");
+    iscolora($targ->getpixel(x => 3, 'y' => 1), NC(255, 128, 0, 0),
+            "check at zero source coverage on zero targ coverage");
+    iscolora($targ->getpixel(x => 4, 'y' => 1), NC(127, 128, 128, 255),
+            "check at half source_coverage on full targ coverage");
+    iscolora($targ->getpixel(x => 5, 'y' => 1), NC(85, 128, 170, 191),
+            "check at half source coverage on half targ coverage");
+    iscolora($targ->getpixel(x => 6, 'y' => 1), NC(0, 128, 255, 128),
+            "check at half source coverage on zero targ coverage");
+    iscolora($targ->getpixel(x => 7, 'y' => 1), NC(0, 128, 255, 255),
+            "check at full source_coverage on full targ coverage");
+    iscolora($targ->getpixel(x => 8, 'y' => 1), NC(0, 128, 255, 255),
+            "check at full source coverage on half targ coverage");
+    iscolora($targ->getpixel(x => 9, 'y' => 1), NC(0, 128, 255, 255),
+            "check at full source coverage on zero targ coverage");
+  }
+}
+
+{ # https://rt.cpan.org/Ticket/Display.html?id=30908
+  # we now adapt the source channels to the target
+  # check each combination works as expected
+
+  # various source images
+  my $src1 = Imager->new(xsize => 50, ysize => 50, channels => 1);
+  my $g_grey_full = Imager::Color->new(128, 255, 0, 0);
+  my $g_white_50 = Imager::Color->new(255, 128, 0, 0);
+  $src1->box(filled => 1, xmax => 24, color => $g_grey_full);
+
+  my $src2 = Imager->new(xsize => 50, ysize => 50, channels => 2);
+  $src2->box(filled => 1, xmax => 24, color => $g_grey_full);
+  $src2->box(filled => 1, xmin => 25, color => $g_white_50);
+
+  my $c_red_full = Imager::Color->new(255, 0, 0);
+  my $c_blue_full = Imager::Color->new(0, 0, 255);
+  my $src3 = Imager->new(xsize => 50, ysize => 50, channels => 3);
+  $src3->box(filled => 1, xmax => 24, color => $c_red_full);
+  $src3->box(filled => 1, xmin => 25, color => $c_blue_full);
+
+  my $c_green_50 = Imager::Color->new(0, 255, 0, 127);
+  my $src4 = Imager->new(xsize => 50, ysize => 50, channels => 4);
+  $src4->box(filled => 1, xmax => 24, color => $c_blue_full);
+  $src4->box(filled => 1, xmin => 25, color => $c_green_50);
+
+  my @left_box = ( box => [ 25, 25, 49, 74 ] );
+  my @right_box = ( box => [ 50, 25, 74, 74 ] );
+
+  { # 1 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 1);
+    $base->box(filled => 1, color => Imager::Color->new(64, 255, 0, 0));
+
+    my $work = $base->copy;
+    ok($work->rubthrough(left => 25, top => 25, src => $src1), "rubthrough 1 to 1");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(left => 25, top => 25, src => $src2), "rubthrough 2 to 1");
+    $comp = $base->copy;
+    $comp->box(filled => 1, @left_box, color => $g_grey_full);
+    $comp->box(filled => 1, @right_box, color => [ 159, 0, 0, 0 ]);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(left => 25, top => 25, src => $src3), "rubthrough 3 to 1");
+     $comp = $base->copy;
+    $comp->box(filled => 1, @left_box, color => [ 57, 255, 0, 0 ]);
+    $comp->box(filled => 1, @right_box, color => [ 18, 255, 0, 0 ]);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(left => 25, top => 25, src => $src4), "rubthrough 4 to 1");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 121, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+  }
+
+  { # 2 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 2);
+    $base->box(filled => 1, color => [ 128, 128, 0, 0 ]);
+    
+    my $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src1), "rubthrough 1 to 2");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => [ 0, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src2), "rubthrough 2 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => $g_grey_full, @left_box);
+    $comp->box(filled => 1, color => [ 213, 191, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src3), "rubthrough 3 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 57, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src4), "rubthrough 4 to 2");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 18, 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 162, 191, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+  }
+
+  { # 3 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 3);
+    $base->box(filled => 1, color => [ 128, 255, 0, 0 ]);
+    
+    my $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src1), "rubthrough 1 to 3");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src2), "rubthrough 2 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 191, 255, 128, 255 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src3), "rubthrough 3 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src4), "rubthrough 4 to 3");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 64, 255, 0 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+  }
+
+  { # 4 channel output
+    my $base = Imager->new(xsize => 100, ysize => 100, channels => 4);
+    $base->box(filled => 1, color => [ 128, 255, 64, 128 ]);
+    
+    my $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src1), "rubthrough 1 to 4");
+    my $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src2), "rubthrough 2 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 128, 128, 128, 255 ], @left_box);
+    $comp->box(filled => 1, color => [ 213, 255, 192, 191 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src3), "rubthrough 3 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => [ 255, 0, 0 ], @left_box);
+    $comp->box(filled => 1, color => [ 0, 0, 255 ], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+
+    $work = $base->copy;
+    ok($work->rubthrough(top => 25, left => 25, src => $src4), "rubthrough 4 to 4");
+    $comp = $base->copy;
+    $comp->box(filled => 1, color => $c_blue_full, @left_box);
+    $comp->box(filled => 1, color => [ 43, 255, 21, 191], @right_box);
+    is_image($work, $comp, "compare rubthrough target to expected");
+  }
 }
 
 sub color_cmp {
diff --git a/t/x11rubthru.t b/t/x11rubthru.t
new file mode 100644 (file)
index 0000000..7254cb4
--- /dev/null
@@ -0,0 +1,14 @@
+#!perl -w
+use strict;
+use Imager;
+use Imager::Test qw(is_image);
+use Test::More tests => 2;
+
+my $dest = Imager->new(xsize => 100, ysize => 100, channels => 4);
+$dest->box(filled => 1, color => '0000FF');
+my $src = Imager->new(xsize => 100, ysize => 100, channels => 4);
+$src->circle(color => 'FF0000', x => 50, y => 60, r => 40, aa => 1);
+ok($dest->rubthrough(src => $src, src_minx => 10, src_miny => 20, src_maxx => 90,
+              tx => 10, ty => 10), "rubthrough");
+ok($dest->write(file => "testout/x11rubthru.tif"), "save it");
+