]> git.imager.perl.org - imager.git/commitdiff
minor error handling in bmp.c
authorTony Cook <tony@develop=help.com>
Sun, 9 Sep 2001 14:48:00 +0000 (14:48 +0000)
committerTony Cook <tony@develop=help.com>
Sun, 9 Sep 2001 14:48:00 +0000 (14:48 +0000)
more complex fill combining types

15 files changed:
Imager.pm
Imager.xs
MANIFEST
bmp.c
color.c
draw.c
fills.c
filters.c
image.h
imagei.h
lib/Imager/Fill.pm
t/t15color.t
t/t20fill.t
t/t61filters.t
testimg/gimpgrad

index ad053a6eb123dc8ef83b3b0ac6379d9b18bffae1..9469fa81f2fbf01ccdfe00d5e410da1338820140 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -290,6 +290,21 @@ BEGIN {
                                    random  => 2,
                                    circle  => 3,
                                   },
                                    random  => 2,
                                    circle  => 3,
                                   },
+                  combine => {
+                              none      => 0,
+                              normal    => 1,
+                              multiply  => 2, mult => 2,
+                              dissolve  => 3,
+                              add       => 4,
+                              subtract  => 5, sub => 5,
+                              diff      => 6,
+                              lighten   => 7,
+                              darken    => 8,
+                              hue       => 9,
+                              sat       => 10,
+                              value     => 11,
+                              color     => 12,
+                             },
                  },
      defaults => { ftype => 0, repeat => 0, combine => 0,
                    super_sample => 0, ssample_param => 4,
                  },
      defaults => { ftype => 0, repeat => 0, combine => 0,
                    super_sample => 0, ssample_param => 4,
@@ -2960,7 +2975,7 @@ source.
   bumpmap         bump elevation(0) lightx lighty st(2)
   contrast        intensity
   conv            coef
   bumpmap         bump elevation(0) lightx lighty st(2)
   contrast        intensity
   conv            coef
-  fountain        xa ya xb yb ftype(linear) repeat(none) combine(0)
+  fountain        xa ya xb yb ftype(linear) repeat(none) combine(none)
                   super_sample(none) ssample_param(4) segments(see below)
   gaussian        stddev
   gradgen         xo yo colors dist
                   super_sample(none) ssample_param(4) segments(see below)
   gaussian        stddev
   gradgen         xo yo colors dist
@@ -3080,7 +3095,8 @@ for a linear fill).
 By default the fill simply overwrites the whole image (unless you have
 parts of the range 0 through 1 that aren't covered by a segment), if
 any segments of your fill have any transparency, you can set the
 By default the fill simply overwrites the whole image (unless you have
 parts of the range 0 through 1 that aren't covered by a segment), if
 any segments of your fill have any transparency, you can set the
-I<combine> option to 1 to have the fill combined with the existing pixels.
+I<combine> option to 'normal' to have the fill combined with the
+existing pixels.  See the description of I<combine> in L<Imager/Fill>.
 
 If your fill has sharp edges, for example between steps if you use
 repeat set to 'triangle', you may see some aliased or ragged edges.
 
 If your fill has sharp edges, for example between steps if you use
 repeat set to 'triangle', you may see some aliased or ragged edges.
index 955cbc2f860c197f761d73a23062fc8e8a5c7e6d..3299e07d5f04927b7d37dce568ca5443c06c9788 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -543,7 +543,26 @@ ICL_rgba(cl)
                PUSHs(sv_2mortal(newSVnv(cl->rgba.b)));
                PUSHs(sv_2mortal(newSVnv(cl->rgba.a)));
 
                PUSHs(sv_2mortal(newSVnv(cl->rgba.b)));
                PUSHs(sv_2mortal(newSVnv(cl->rgba.a)));
 
-
+Imager::Color
+i_hsv_to_rgb(c)
+        Imager::Color c
+      CODE:
+        RETVAL = mymalloc(sizeof(i_color));
+        *RETVAL = *c;
+        i_hsv_to_rgb(RETVAL);
+      OUTPUT:
+        RETVAL
+        
+Imager::Color
+i_rgb_to_hsv(c)
+        Imager::Color c
+      CODE:
+        RETVAL = mymalloc(sizeof(i_color));
+        *RETVAL = *c;
+        i_rgb_to_hsv(RETVAL);
+      OUTPUT:
+        RETVAL
+        
 
 
 MODULE = Imager        PACKAGE = Imager::Color::Float  PREFIX=ICLF_
 
 
 MODULE = Imager        PACKAGE = Imager::Color::Float  PREFIX=ICLF_
@@ -586,6 +605,27 @@ ICLF_set_internal(cl,r,g,b,a)
         EXTEND(SP, 1);
         PUSHs(ST(0));
 
         EXTEND(SP, 1);
         PUSHs(ST(0));
 
+Imager::Color::Float
+i_hsv_to_rgb(c)
+        Imager::Color::Float c
+      CODE:
+        RETVAL = mymalloc(sizeof(i_fcolor));
+        *RETVAL = *c;
+        i_hsv_to_rgbf(RETVAL);
+      OUTPUT:
+        RETVAL
+        
+Imager::Color::Float
+i_rgb_to_hsv(c)
+        Imager::Color::Float c
+      CODE:
+        RETVAL = mymalloc(sizeof(i_fcolor));
+        *RETVAL = *c;
+        i_rgb_to_hsvf(RETVAL);
+      OUTPUT:
+        RETVAL
+        
+
 MODULE = Imager                PACKAGE = Imager::ImgRaw        PREFIX = IIM_
 
 Imager::ImgRaw
 MODULE = Imager                PACKAGE = Imager::ImgRaw        PREFIX = IIM_
 
 Imager::ImgRaw
@@ -3095,3 +3135,24 @@ i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy)
       OUTPUT:
         RETVAL
 
       OUTPUT:
         RETVAL
 
+Imager::FillHandle
+i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy)
+        Imager::Color::Float fg
+        Imager::Color::Float bg
+        int combine
+        int hatch
+        int dx
+        int dy
+      PREINIT:
+        unsigned char *cust_hatch;
+        STRLEN len;
+      CODE:
+        if (SvOK(ST(4))) {
+          cust_hatch = SvPV(ST(4), len);
+        }
+        else
+          cust_hatch = NULL;
+        RETVAL = i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy);
+      OUTPUT:
+        RETVAL
+
index d6eb6ffc4d57b0edc4c81f50af49e74b00898f6f..4121d7abab0ad1e95bc187a36f1fadda203e55d2 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -4,7 +4,7 @@ Imager.xs
 MANIFEST
 README
 Makefile.PL
 MANIFEST
 README
 Makefile.PL
-bmp.c
+bmp.c           Reading and writing Windows BMP files
 color.c         Color translation and handling
 conv.c
 convert.c
 color.c         Color translation and handling
 conv.c
 convert.c
@@ -55,6 +55,7 @@ lib/Imager/Color.pm
 lib/Imager/Color/Float.pm
 lib/Imager/Expr.pm
 lib/Imager/Expr/Assem.pm
 lib/Imager/Color/Float.pm
 lib/Imager/Expr.pm
 lib/Imager/Expr/Assem.pm
+lib/Imager/Fill.pm
 lib/Imager/Font.pm
 lib/Imager/Font/Type1.pm
 lib/Imager/Font/Truetype.pm
 lib/Imager/Font.pm
 lib/Imager/Font/Type1.pm
 lib/Imager/Font/Truetype.pm
@@ -78,6 +79,7 @@ t/t105gif.t
 t/t106tiff.t
 t/t107bmp.t
 t/t15color.t
 t/t106tiff.t
 t/t107bmp.t
 t/t15color.t
+t/t20fill.t             Tests fills
 t/t30t1font.t
 t/t35ttfont.t
 t/t36oofont.t
 t/t30t1font.t
 t/t35ttfont.t
 t/t36oofont.t
@@ -94,6 +96,8 @@ t/t60dyntest.t
 t/t61filters.t
 t/t65crop.t
 t/t66paste.t
 t/t61filters.t
 t/t65crop.t
 t/t66paste.t
+t/t67convert.t
+t/t68map.t
 t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
 t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
diff --git a/bmp.c b/bmp.c
index 7fc83d23a39f51629cd8bb9c85d357f6eed0a458..fbc092361dff34349104aaf31e6bedb388cfd6b6 100644 (file)
--- a/bmp.c
+++ b/bmp.c
@@ -142,6 +142,10 @@ i_readbmp_wiol(io_glue *ig) {
   case 16:
     im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression);
     break;
   case 16:
     im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression);
     break;
+
+  default:
+    i_push_errorf(0, "unknown bit count for BMP file (%d)", bit_count);
+    return NULL;
   }
 
   /* store the resolution */
   }
 
   /* store the resolution */
diff --git a/color.c b/color.c
index 6097cc95eb17292a39e12b7cff312c277ac7bada..ab33bfc194c7c3b297171a27d4aaa33e2fd4ca9e 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,4 +1,5 @@
 #include "image.h"
 #include "image.h"
+#include <math.h>
 
 /*
 =head1 NAME
 
 /*
 =head1 NAME
@@ -19,6 +20,9 @@ A collection of utility functions for converting between color spaces.
 
 #define EPSILON (1e-8)
 
 
 #define EPSILON (1e-8)
 
+#define my_max(a, b) ((a) < (b) ? (b) : (a))
+#define my_min(a, b) ((a) > (b) ? (b) : (a))
+
 /*
 =item i_rgb2hsvf(&color)
 
 /*
 =item i_rgb2hsvf(&color)
 
@@ -33,8 +37,8 @@ void i_rgb_to_hsvf(i_fcolor *color) {
   double temp;
   double Cr, Cg, Cb;
 
   double temp;
   double Cr, Cg, Cb;
 
-  v = max(max(color->rgb.r, color->rgb.g), color->rgb.b);
-  temp = min(min(color->rgb.r, color->rgb.g), color->rgb.b);
+  v = my_max(my_max(color->rgb.r, color->rgb.g), color->rgb.b);
+  temp = my_min(my_min(color->rgb.r, color->rgb.g), color->rgb.b);
   if (v < EPSILON)
     s = 0;
   else
   if (v < EPSILON)
     s = 0;
   else
@@ -60,6 +64,47 @@ void i_rgb_to_hsvf(i_fcolor *color) {
   color->channel[2] = v;
 }
 
   color->channel[2] = v;
 }
 
+/*
+=item i_rgb2hsv(&color)
+
+Converts the first 3 channels of color into hue, saturation and value.
+
+Each value is scaled into the range 0 to 255.
+
+=cut
+*/
+void i_rgb_to_hsv(i_color *color) {
+  double h, s, v;
+  double temp;
+  double Cr, Cg, Cb;
+
+  v = my_max(my_max(color->rgb.r, color->rgb.g), color->rgb.b);
+  temp = my_min(my_min(color->rgb.r, color->rgb.g), color->rgb.b);
+  if (v == 0)
+    s = 0;
+  else
+    s = (v-temp)*255/v;
+  if (s == 0)
+    h = 0;
+  else {
+    Cr = (v - color->rgb.r)/(v-temp);
+    Cg = (v - color->rgb.g)/(v-temp);
+    Cb = (v - color->rgb.b)/(v-temp);
+    if (color->rgb.r == v)
+      h = Cb - Cg;
+    else if (color->rgb.g == v)
+      h = 2 + Cr - Cb;
+    else if (color->rgb.b == v)
+      h = 4 + Cg - Cr;
+    h = h * 60.0;
+    if (h < 0)
+      h += 360;
+  }
+  color->channel[0] = h * 255 / 360.0;
+  color->channel[1] = s;
+  color->channel[2] = v;
+}
+
 /*
 =item i_hsv_to_rgbf(&color)
 
 /*
 =item i_hsv_to_rgbf(&color)
 
@@ -81,7 +126,7 @@ void i_hsv_to_rgbf(i_fcolor *color) {
     int i;
     double f, m, n, k;
     h = fmod(h, 1.0) * 6;
     int i;
     double f, m, n, k;
     h = fmod(h, 1.0) * 6;
-    i = h;
+    i = floor(h);
     f = h - i;
     m = v * (1 - s);
     n = v * (1 - s * f);
     f = h - i;
     m = v * (1 - s);
     n = v * (1 - s * f);
@@ -109,6 +154,55 @@ void i_hsv_to_rgbf(i_fcolor *color) {
   }
 }
 
   }
 }
 
+/*
+=item i_hsv_to_rgb(&color)
+
+Convert a HSV value to an RGB value, each value ranges from 0 to 1.
+
+=cut
+*/
+
+void i_hsv_to_rgb(i_color *color) {
+  double h = color->channel[0];
+  double s = color->channel[1];
+  double v = color->channel[2];
+
+  if (color->channel[1] == 0) {
+    /* ignore h in this case */
+    color->rgb.r = color->rgb.g = color->rgb.b = v;
+  }
+  else {
+    int i;
+    double f, m, n, k;
+    h = h / 255.0 * 6;
+    i = h;
+    f = h - i;
+    m = v * (255 - s) / 255;
+    n = v * (255 - s * f) / 255;
+    k = v * (255 - s * (1 - f)) / 255;
+    switch (i) {
+    case 0:
+      color->rgb.r = v; color->rgb.g = k; color->rgb.b = m;
+      break;
+    case 1:
+      color->rgb.r = n; color->rgb.g = v; color->rgb.b = m;
+      break;
+    case 2:
+      color->rgb.r = m; color->rgb.g = v; color->rgb.b = k;
+      break;
+    case 3:
+      color->rgb.r = m; color->rgb.g = n; color->rgb.b = v;
+      break;
+    case 4:
+      color->rgb.r = k; color->rgb.g = m; color->rgb.b = v;
+      break;
+    case 5:
+      color->rgb.r = v; color->rgb.g = m; color->rgb.b = n;
+      break;
+    }
+  }
+}
+
 /*
 =back
 
 /*
 =back
 
diff --git a/draw.c b/draw.c
index ecf0bad352b10f1bdbb655afd512d656d15172b3..d7248aba9640854a3105ade0a1dd53a9f34f9afc 100644 (file)
--- a/draw.c
+++ b/draw.c
@@ -51,37 +51,47 @@ 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);
   int x, w, y;
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * im->xsize);
+    i_color *work = NULL;
+    if (fill->combine)
+      work = mymalloc(sizeof(i_color) * im->xsize);
     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;
 
     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->combines
+        if (fill->combine) 
           i_glin(im, x, x+w, y, line);
         
           i_glin(im, x, x+w, y, line);
         
-        (fill->fill_with_color)(fill, x, y, w, im->channels, line);
+        (fill->fill_with_color)(fill, x, y, w, im->channels, line, work);
         i_plin(im, x, x+w, y, line);
       }
     }
   
     myfree(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);
   }
   else {
     i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
+    i_fcolor *work = NULL;
+    if (fill->combinef)
+      work = mymalloc(sizeof(i_fcolor) * im->xsize);
     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;
 
     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->combines
+        if (fill->combinef
           i_glinf(im, x, x+w, y, line);
         
           i_glinf(im, x, x+w, y, line);
         
-        (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line);
+        (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line, work);
         i_plinf(im, x, x+w, y, line);
       }
     }
   
     myfree(line);
         i_plinf(im, x, x+w, y, line);
       }
     }
   
     myfree(line);
+    if (work)
+      myfree(work);
   }
 }
 
   }
 }
 
@@ -358,27 +368,37 @@ i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) {
   ++x2;
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * (x2 - x1));
   ++x2;
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * (x2 - x1));
+    i_color *work = NULL;
+    if (fill->combine)
+      work = mymalloc(sizeof(i_color) * (x2-x1));
     while (y1 <= y2) {
     while (y1 <= y2) {
-      if (fill->combines)
+      if (fill->combine)
         i_glin(im, x1, x2, y1, line);
 
         i_glin(im, x1, x2, y1, line);
 
-      (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line);
+      (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line, work);
       i_plin(im, x1, x2, y1, line);
       ++y1;
     }
     myfree(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));
   }
   else {
     i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+    i_fcolor *work;
+    work = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+
     while (y1 <= y2) {
     while (y1 <= y2) {
-      if (fill->combines)
+      if (fill->combinef)
         i_glinf(im, x1, x2, y1, line);
 
         i_glinf(im, x1, x2, y1, line);
 
-      (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line);
+      (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line, work);
       i_plinf(im, x1, x2, y1, line);
       ++y1;
     }
     myfree(line);
       i_plinf(im, x1, x2, y1, line);
       ++y1;
     }
     myfree(line);
+    if (work)
+      myfree(work);
   }
 }
 
   }
 }
 
@@ -1231,6 +1251,9 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) {
 
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * (bxmax - bxmin));
 
   if (im->bits == i_8_bits && fill->fill_with_color) {
     i_color *line = mymalloc(sizeof(i_color) * (bxmax - bxmin));
+    i_color *work = NULL;
+    if (fill->combine)
+      work = mymalloc(sizeof(i_color) * (bxmax - bxmin));
 
     for(y=bymin;y<=bymax;y++) {
       x = bxmin;
 
     for(y=bymin;y<=bymax;y++) {
       x = bxmin;
@@ -1243,17 +1266,23 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) {
           while (x < bxmax && btm_test(btm, x, y)) {
             ++x;
           }
           while (x < bxmax && btm_test(btm, x, y)) {
             ++x;
           }
-          if (fill->combines)
+          if (fill->combine)
             i_glin(im, start, x, y, line);
             i_glin(im, start, x, y, line);
-          (fill->fill_with_color)(fill, start, y, x-start, im->channels, line);
+          (fill->fill_with_color)(fill, start, y, x-start, im->channels, 
+                                  line, work);
           i_plin(im, start, x, y, line);
         }
       }
     }
     myfree(line);
           i_plin(im, start, x, y, line);
         }
       }
     }
     myfree(line);
+    if (work)
+      myfree(work);
   }
   else {
     i_fcolor *line = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin));
   }
   else {
     i_fcolor *line = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin));
+    i_fcolor *work = NULL;
+    if (fill->combinef)
+      work = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin));
     
     for(y=bymin;y<=bymax;y++) {
       x = bxmin;
     
     for(y=bymin;y<=bymax;y++) {
       x = bxmin;
@@ -1266,14 +1295,17 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) {
           while (x < bxmax && btm_test(btm, x, y)) {
             ++x;
           }
           while (x < bxmax && btm_test(btm, x, y)) {
             ++x;
           }
-          if (fill->combines)
+          if (fill->combinef)
             i_glinf(im, start, x, y, line);
             i_glinf(im, start, x, y, line);
-          (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, line);
+          (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, 
+                                   line, work);
           i_plinf(im, start, x, y, line);
         }
       }
     }
     myfree(line);
           i_plinf(im, start, x, y, line);
         }
       }
     }
     myfree(line);
+    if (work)
+      myfree(work);
   }
 
   btm_destroy(btm);
   }
 
   btm_destroy(btm);
diff --git a/fills.c b/fills.c
index 7466f8dcec80011fd3d6563037a5eb5d0030e72a..e7d3bc49c5b6b3573e2b681b657291fa37b60162 100644 (file)
--- a/fills.c
+++ b/fills.c
@@ -74,6 +74,30 @@ fountain fill
 
 Fountain fill is implemented by L<filters.c>.
 
 
 Fountain fill is implemented by L<filters.c>.
 
+Other fills that could be implemented include:
+
+=over
+
+=item *
+
+image - an image tiled over the fill area, with an offset either
+horizontally or vertically.
+
+=item *
+
+checkerboard - combine 2 fills in a checkerboard
+
+=item *
+
+combine - combine the levels of 2 other fills based in the levels of
+an image
+
+=item *
+
+regmach - use the register machine to generate colors
+
+=back
+
 =over
 
 =cut
 =over
 
 =cut
@@ -95,13 +119,7 @@ static i_fcolor color_to_fcolor(i_color *c) {
     out.channel[ch] = Sample8ToF(c->channel[ch]);
 }
 
     out.channel[ch] = Sample8ToF(c->channel[ch]);
 }
 
-typedef struct
-{
-  i_fill_t base;
-  i_color c;
-  i_fcolor fc;
-} i_fill_solid_t;
-
+/* alpha combine in with out */
 #define COMBINE(out, in, channels) \
   { \
     int ch; \
 #define COMBINE(out, in, channels) \
   { \
     int ch; \
@@ -111,6 +129,18 @@ typedef struct
     } \
   }
 
     } \
   }
 
+/* alpha combine in with out, in this case in is a simple array of
+   samples, potentially not integers - the mult combiner uses doubles
+   for accuracy */
+#define COMBINEA(out, in, channels) \
+  { \
+    int ch; \
+    for (ch = 0; ch < (channels); ++ch) { \
+      (out).channel[ch] = ((out).channel[ch] * (255 - (in)[3]) \
+        + (in)[ch] * (in)[3]) / 255; \
+    } \
+  }
+
 #define COMBINEF(out, in, channels) \
   { \
     int ch; \
 #define COMBINEF(out, in, channels) \
   { \
     int ch; \
@@ -120,14 +150,21 @@ typedef struct
     } \
   }
 
     } \
   }
 
+typedef struct
+{
+  i_fill_t base;
+  i_color c;
+  i_fcolor fc;
+} i_fill_solid_t;
+
 static void fill_solid(i_fill_t *, int x, int y, int width, int channels, 
 static void fill_solid(i_fill_t *, int x, int y, int width, int channels, 
-                       i_color *);
+                       i_color *, i_color *);
 static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, 
 static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, 
-                        i_fcolor *);
+                        i_fcolor *, i_fcolor *);
 static void fill_solid_comb(i_fill_t *, int x, int y, int width, int channels, 
 static void fill_solid_comb(i_fill_t *, int x, int y, int width, int channels, 
-                            i_color *);
+                            i_color *, i_color *);
 static void fill_solidf_comb(i_fill_t *, int x, int y, int width, 
 static void fill_solidf_comb(i_fill_t *, int x, int y, int width, 
-                             int channels, i_fcolor *);
+                             int channels, i_fcolor *, i_fcolor *);
 
 static i_fill_solid_t base_solid_fill =
 {
 
 static i_fill_solid_t base_solid_fill =
 {
@@ -135,7 +172,8 @@ static i_fill_solid_t base_solid_fill =
     fill_solid,
     fill_solidf,
     NULL,
     fill_solid,
     fill_solidf,
     NULL,
-    0
+    NULL,
+    NULL,
   },
 };
 static i_fill_solid_t base_solid_fill_comb =
   },
 };
 static i_fill_solid_t base_solid_fill_comb =
@@ -144,7 +182,8 @@ static i_fill_solid_t base_solid_fill_comb =
     fill_solid_comb,
     fill_solidf_comb,
     NULL,
     fill_solid_comb,
     fill_solidf_comb,
     NULL,
-    1
+    NULL,
+    NULL,
   },
 };
 
   },
 };
 
@@ -178,8 +217,10 @@ i_new_fill_solidf(i_fcolor *c, int combine) {
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
   
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
   
-  if (combine && c->channel[3] < 1.0)
+  if (combine && c->channel[3] < 1.0) {
     *fill = base_solid_fill_comb;
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   else
     *fill = base_solid_fill;
   fill->fc = *c;
   else
     *fill = base_solid_fill;
   fill->fc = *c;
@@ -205,8 +246,10 @@ i_new_fill_solid(i_color *c, int combine) {
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
 
   int ch;
   i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
 
-  if (combine && c->channel[3] < 255)
+  if (combine && c->channel[3] < 255) {
     *fill = base_solid_fill_comb;
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   else
     *fill = base_solid_fill;
   fill->c = *c;
   else
     *fill = base_solid_fill;
   fill->c = *c;
@@ -360,9 +403,9 @@ typedef struct
 } i_fill_hatch_t;
 
 static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, 
 } i_fill_hatch_t;
 
 static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, 
-                       i_color *data);
+                       i_color *data, i_color *work);
 static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, 
 static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, 
-                        i_fcolor *data);
+                        i_fcolor *data, i_fcolor *work);
 static
 i_fill_t *
 i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, 
 static
 i_fill_t *
 i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, 
@@ -432,7 +475,7 @@ 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, 
 */
 static void
 fill_solid(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_color *data) {
+           i_color *data, i_color *work) {
   while (width-- > 0) {
     *data++ = T_SOLID_FILL(fill)->c;
   }
   while (width-- > 0) {
     *data++ = T_SOLID_FILL(fill)->c;
   }
@@ -447,7 +490,7 @@ 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, 
 */
 static void
 fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_fcolor *data) {
+           i_fcolor *data, i_fcolor *work) {
   while (width-- > 0) {
     *data++ = T_SOLID_FILL(fill)->fc;
   }
   while (width-- > 0) {
     *data++ = T_SOLID_FILL(fill)->fc;
   }
@@ -462,13 +505,15 @@ The 8-bit sample fill function for combining solid fills.
 */
 static void
 fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, 
 */
 static void
 fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_color *data) {
+                i_color *data, i_color *work) {
   i_color c = T_SOLID_FILL(fill)->c;
   i_color c = T_SOLID_FILL(fill)->c;
+  int count = width;
+  i_color *wstart = work;
 
   while (width-- > 0) {
 
   while (width-- > 0) {
-    COMBINE(*data, c, channels);
-    ++data;
+    *work++ = c;
   }
   }
+  (fill->combine)(data, wstart, channels, count);
 }
 
 /*
 }
 
 /*
@@ -480,13 +525,15 @@ The floating sample fill function for combining solid fills.
 */
 static void
 fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, 
 */
 static void
 fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_fcolor *data) {
+           i_fcolor *data, i_fcolor *work) {
   i_fcolor c = T_SOLID_FILL(fill)->fc;
   i_fcolor c = T_SOLID_FILL(fill)->fc;
+  int count = width;
+  i_fcolor *wstart = work;
 
   while (width-- > 0) {
 
   while (width-- > 0) {
-    COMBINEF(*data, c, channels);
-    ++data;
+    *work++ = c;
   }
   }
+  (fill->combinef)(data, wstart, channels, count);
 }
 
 /*
 }
 
 /*
@@ -510,8 +557,13 @@ i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg,
   fill->bg = bg ? *bg : fcolor_to_color(fbg);
   fill->ffg = ffg ? *ffg : color_to_fcolor(fg);
   fill->fbg = fbg ? *fbg : color_to_fcolor(bg);
   fill->bg = bg ? *bg : fcolor_to_color(fbg);
   fill->ffg = ffg ? *ffg : color_to_fcolor(fg);
   fill->fbg = fbg ? *fbg : color_to_fcolor(bg);
-  fill->base.combines = 
-    combine && (fill->ffg.channel[0] < 1 || fill->fbg.channel[0] < 1);
+  if (combine && (fill->ffg.channel[0] < 1 || fill->fbg.channel[0] < 1)) {
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
+  else {
+    fill->base.combine = NULL;
+    fill->base.combinef = NULL;
+  }
   if (cust_hatch) {
     memcpy(fill->hatch, cust_hatch, 8);
   }
   if (cust_hatch) {
     memcpy(fill->hatch, cust_hatch, 8);
   }
@@ -534,24 +586,31 @@ The 8-bit sample fill function for hatched fills.
 =back
 */
 static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, 
 =back
 */
 static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, 
-                       i_color *data) {
+                       i_color *data, i_color *work) {
   i_fill_hatch_t *f = (i_fill_hatch_t *)fill;
   int byte = f->hatch[(y + f->dy) & 7];
   int xpos = (x + f->dx) & 7;
   int mask = 128 >> xpos;
 
   i_fill_hatch_t *f = (i_fill_hatch_t *)fill;
   int byte = f->hatch[(y + f->dy) & 7];
   int xpos = (x + f->dx) & 7;
   int mask = 128 >> xpos;
 
-  while (width-- > 0) {
-    i_color c = (byte & mask) ? f->fg : f->bg;
+  if (fill->combine) {
+    int count = width;
+    i_color *wstart = work;
 
 
-    if (f->base.combines) {
-      COMBINE(*data, c, channels);
+    while (count-- > 0) {
+      *work++ = (byte & mask) ? f->fg : f->bg;
+      
+      if ((mask >>= 1) == 0)
+        mask = 128;
     }
     }
-    else {
-      *data = c;
+    (fill->combine)(data, wstart, channels, width);
+  }
+  else {
+    while (width-- > 0) {
+      *data++ = (byte & mask) ? f->fg : f->bg;
+
+      if ((mask >>= 1) == 0)
+        mask = 128;
     }
     }
-    ++data;
-    if ((mask >>= 1) == 0)
-      mask = 128;
   }
 }
 
   }
 }
 
@@ -563,27 +622,500 @@ The floating sample fill function for hatched fills.
 =back
 */
 static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, 
 =back
 */
 static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, 
-                        i_fcolor *data) {
+                        i_fcolor *data, i_fcolor *work) {
   i_fill_hatch_t *f = (i_fill_hatch_t *)fill;
   int byte = f->hatch[(y + f->dy) & 7];
   int xpos = (x + f->dx) & 7;
   int mask = 128 >> xpos;
   
   i_fill_hatch_t *f = (i_fill_hatch_t *)fill;
   int byte = f->hatch[(y + f->dy) & 7];
   int xpos = (x + f->dx) & 7;
   int mask = 128 >> xpos;
   
-  while (width-- > 0) {
-    i_fcolor c = (byte & mask) ? f->ffg : f->fbg;
+  if (fill->combinef) {
+    int count = width;
+    i_fcolor *wstart = work;
+
+    while (count-- > 0) {
+      *work++ = (byte & mask) ? f->ffg : f->fbg;
+      
+      if ((mask >>= 1) == 0)
+        mask = 128;
+    }
+    (fill->combinef)(data, wstart, channels, width);
+  }
+  else {
+    while (width-- > 0) {
+      *data++ = (byte & mask) ? f->ffg : f->fbg;
 
 
-    if (f->base.combines) {
-      COMBINE(*data, c, channels);
+      if ((mask >>= 1) == 0)
+        mask = 128;
     }
     }
-    else {
-      *data = c;
+  }
+}
+
+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);
+
+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--) {
+    i_color c = *in;
+    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) {
+  int ch;
+
+  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) {
+  int ch;
+
+  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]);
     }
     }
-    ++data;
-    if ((mask >>= 1) == 0)
-      mask = 128;
+    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);
+    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
 
 /*
 =back
 
index 3eeb259ce568c945f6b77c5f9344b572049b1b4c..473176dbcf2a182041cbb82c7b248e6f9f86a47e 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1143,8 +1143,15 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb,
   struct fount_state state;
   int x, y;
   i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
   struct fount_state state;
   int x, y;
   i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
+  i_fcolor *work = NULL;
   int ch;
   i_fountain_seg *my_segs;
   int ch;
   i_fountain_seg *my_segs;
+  i_fill_combine_f combine_func = NULL;
+  i_fill_combinef_f combinef_func = NULL;
+
+  i_get_combine(combine, &combine_func, &combinef_func);
+  if (combinef_func)
+    work = mymalloc(sizeof(i_fcolor) * im->xsize);
 
   fount_init_state(&state, xa, ya, xb, yb, type, repeat, combine, 
                    super_sample, ssample_param, count, segs);
 
   fount_init_state(&state, xa, ya, xb, yb, type, repeat, combine, 
                    super_sample, ssample_param, count, segs);
@@ -1161,16 +1168,14 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb,
       else
         got_one = state.ssfunc(&c, x, y, &state);
       if (got_one) {
       else
         got_one = state.ssfunc(&c, x, y, &state);
       if (got_one) {
-        if (combine) {
-          for (ch = 0; ch < im->channels; ++ch) {
-            line[x].channel[ch] = line[x].channel[ch] * (1.0 - c.channel[3])
-              + c.channel[ch] * c.channel[3];
-          }
-        }
+        if (combine)
+          work[x] = c;
         else 
           line[x] = c;
       }
     }
         else 
           line[x] = c;
       }
     }
+    if (combine)
+      combinef_func(line, work, im->channels, im->xsize);
     i_plinf(im, 0, im->xsize, y, line);
   }
   fount_finish_state(&state);
     i_plinf(im, 0, im->xsize, y, line);
   }
   fount_finish_state(&state);
@@ -1184,7 +1189,7 @@ typedef struct {
 
 static void
 fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
 
 static void
 fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
-            i_fcolor *data);
+            i_fcolor *data, i_fcolor *work);
 static void
 fount_fill_destroy(i_fill_t *fill);
 
 static void
 fount_fill_destroy(i_fill_t *fill);
 
@@ -1206,7 +1211,12 @@ i_new_fill_fount(double xa, double ya, double xb, double yb,
   fill->base.fill_with_color = NULL;
   fill->base.fill_with_fcolor = fill_fountf;
   fill->base.destroy = fount_fill_destroy;
   fill->base.fill_with_color = NULL;
   fill->base.fill_with_fcolor = fill_fountf;
   fill->base.destroy = fount_fill_destroy;
-  fill->base.combines = combine;
+  if (combine)
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  else {
+    fill->base.combine = NULL;
+    fill->base.combinef = NULL;
+  }
   fount_init_state(&fill->state, xa, ya, xb, yb, type, repeat, combine, 
                    super_sample, ssample_param, count, segs);
 
   fount_init_state(&fill->state, xa, ya, xb, yb, type, repeat, combine, 
                    super_sample, ssample_param, count, segs);
 
@@ -1791,32 +1801,42 @@ The fill function for fountain fills.
 */
 static void
 fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
 */
 static void
 fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
-            i_fcolor *data) {
+            i_fcolor *data, i_fcolor *work) {
   i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
   i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
-  int ch;
+  
+  if (fill->combinef) {
+    i_fcolor *wstart = work;
+    int count = width;
 
 
-  while (width--) {
-    i_fcolor c;
-    int got_one;
-    double v;
-    if (f->state.ssfunc)
-      got_one = f->state.ssfunc(&c, x, y, &f->state);
-    else
-      got_one = fount_getat(&c, x, y, &f->state);
-
-    if (got_one) {
-      if (f->base.combines) {
-        for (ch = 0; ch < channels; ++ch) {
-          data->channel[ch] = data->channel[ch] * (1.0 - c.channel[3])
-            + c.channel[ch] * c.channel[3];
-        }
-      }
-      else 
-        *data = c;
+    while (width--) {
+      i_fcolor c;
+      int got_one;
+      double v;
+      if (f->state.ssfunc)
+        got_one = f->state.ssfunc(&c, x, y, &f->state);
+      else
+        got_one = fount_getat(&c, x, y, &f->state);
+      
+      *work++ = c;
+      
+      ++x;
+    }
+    (fill->combinef)(data, wstart, channels, count);
+  }
+  else {
+    while (width--) {
+      i_fcolor c;
+      int got_one;
+      double v;
+      if (f->state.ssfunc)
+        got_one = f->state.ssfunc(&c, x, y, &f->state);
+      else
+        got_one = fount_getat(&c, x, y, &f->state);
+      
+      *data++ = c;
+      
+      ++x;
     }
     }
-
-    ++x;
-    ++data;
   }
 }
 
   }
 }
 
diff --git a/image.h b/image.h
index d11436d3e1fef6e9aceb588bfc06d30f415b3737..16686c9febc28fa96ab1e07d13a5b0ffc9181bac 100644 (file)
--- a/image.h
+++ b/image.h
@@ -44,6 +44,8 @@ extern void i_fcolor_destroy(i_fcolor *cl);
 
 extern void i_rgb_to_hsvf(i_fcolor *color);
 extern void i_hsv_to_rgbf(i_fcolor *color);
 
 extern void i_rgb_to_hsvf(i_fcolor *color);
 extern void i_hsv_to_rgbf(i_fcolor *color);
+extern void i_rgb_to_hsv(i_color *color);
+extern void i_hsv_to_rgb(i_color *color);
 
 i_img *IIM_new(int x,int y,int ch);
 void   IIM_DESTROY(i_img *im);
 
 i_img *IIM_new(int x,int y,int ch);
 void   IIM_DESTROY(i_img *im);
@@ -125,11 +127,16 @@ struct i_fill_tag;
 
 typedef void (*i_fill_with_color_f)
      (struct i_fill_tag *fill, int x, int y, int width, int channels, 
 
 typedef void (*i_fill_with_color_f)
      (struct i_fill_tag *fill, int x, int y, int width, int channels, 
-      i_color *data);
+      i_color *data, i_color *work);
 typedef void (*i_fill_with_fcolor_f)
      (struct i_fill_tag *fill, int x, int y, int width, int channels,
 typedef void (*i_fill_with_fcolor_f)
      (struct i_fill_tag *fill, int x, int y, int width, int channels,
-      i_fcolor *data);
+      i_fcolor *data, i_fcolor *work);
 typedef void (*i_fill_destroy_f)(struct i_fill_tag *fill);
 typedef void (*i_fill_destroy_f)(struct i_fill_tag *fill);
+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,
+                                  int count);
+
 
 typedef struct i_fill_tag
 {
 
 typedef struct i_fill_tag
 {
@@ -146,9 +153,26 @@ typedef struct i_fill_tag
 
   /* if non-zero the caller will fill data with the original data
      from the image */
 
   /* if non-zero the caller will fill data with the original data
      from the image */
-  int combines;
+  i_fill_combine_f combine;
+  i_fill_combinef_f combinef;
 } i_fill_t;
 
 } i_fill_t;
 
+typedef enum {
+  ic_none,
+  ic_normal,
+  ic_multiply,
+  ic_dissolve,
+  ic_add,
+  ic_subtract,
+  ic_diff,
+  ic_lighten,
+  ic_darken,
+  ic_hue,
+  ic_sat,
+  ic_value,
+  ic_color
+} i_combine_t;
+
 extern i_fill_t *i_new_fill_solidf(i_fcolor *c, int combine);
 extern i_fill_t *i_new_fill_solid(i_color *c, int combine);
 extern i_fill_t *
 extern i_fill_t *i_new_fill_solidf(i_fcolor *c, int combine);
 extern i_fill_t *i_new_fill_solid(i_color *c, int combine);
 extern i_fill_t *
index d12680b7bc70b0123215673f0a2dbb7f95f6f14f..fb8650104aa57b2bd8569a07d4fdf84f3861091d 100644 (file)
--- a/imagei.h
+++ b/imagei.h
@@ -39,4 +39,6 @@ extern int i_setcolors_forward(i_img *im, int index, i_color *colors,
 #define Sample16To8(num) ((num) / 257)
 #define Sample8To16(num) ((num) * 257)
 
 #define Sample16To8(num) ((num) / 257)
 #define Sample8To16(num) ((num) * 257)
 
+extern void i_get_combine(int combine, i_fill_combine_f *, i_fill_combinef_f *);
+
 #endif
 #endif
index c727052c7918403a5c79348a9a08a74577c5c2e2..229fbd2c56a6b7628f5156d3dc1edaab974857c4 100644 (file)
@@ -1,5 +1,5 @@
 package Imager::Fill;
 package Imager::Fill;
-
+use strict;
 # this needs to be kept in sync with the array of hatches in fills.c
 my @hatch_types =
   qw/check1x1 check2x2 check4x4 vline1 vline2 vline4
 # this needs to be kept in sync with the array of hatches in fills.c
 my @hatch_types =
   qw/check1x1 check2x2 check4x4 vline1 vline2 vline4
@@ -10,17 +10,74 @@ my @hatch_types =
 my %hatch_types;
 @hatch_types{@hatch_types} = 0..$#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};
+
+# this function tries to DWIM for color parameters
+#  color objects are used as is
+#  simple scalars are simply treated as single parameters to Imager::Color->new
+#  hashrefs are treated as named argument lists to Imager::Color->new
+#  arrayrefs are treated as list arguments to Imager::Color->new iff any
+#    parameter is > 1
+#  other arrayrefs are treated as list arguments to Imager::Color::Float
+
+sub _color {
+  my $arg = shift;
+  my $result;
+
+  if (ref $arg) {
+    if (UNIVERSAL::isa($arg, "Imager::Color")
+        || UNIVERSAL::isa($arg, "Imager::Color::Float")) {
+      $result = $arg;
+    }
+    else {
+      if ($arg =~ /^HASH\(/) {
+        $result = Imager::Color->new(%$arg);
+      }
+      elsif ($arg =~ /^ARRAY\(/) {
+        if (grep $_ > 1, @$arg) {
+          $result = Imager::Color->new(@$arg);
+        }
+        else {
+          $result = Imager::Color::Float->new(@$arg);
+        }
+      }
+      else {
+        $Imager::ERRSTR = "Not a color";
+      }
+    }
+  }
+  else {
+    # assume Imager::Color::new knows how to handle it
+    $result = Imager::Color->new($arg);
+  }
+
+  return $result;
+}
+
 sub new {
   my ($class, %hsh) = @_;
 
   my $self = bless { }, $class;
   $hsh{combine} ||= 0;
 sub new {
   my ($class, %hsh) = @_;
 
   my $self = bless { }, $class;
   $hsh{combine} ||= 0;
+  if (exists $combine_types{$hsh{combine}}) {
+    $hsh{combine} = $combine_types{$hsh{combine}};
+  }
   if ($hsh{solid}) {
   if ($hsh{solid}) {
-    if (UNIVERSAL::isa($hsh{solid}, 'Imager::Color')) {
-      $self->{fill} = Imager::i_new_fill_solid($hsh{solid}, $hsh{combine});
+    my $solid = _color($hsh{solid});
+    if (UNIVERSAL::isa($solid, 'Imager::Color')) {
+      $self->{fill} = 
+        Imager::i_new_fill_solid($solid, $hsh{combine});
     }
     }
-    elsif (UNIVERSAL::isa($hsh{colid}, 'Imager::Color::Float')) {
-      $self->{fill} = Imager::i_new_fill_solidf($hsh{solid}, $hsh{combine});
+    elsif (UNIVERSAL::isa($solid, 'Imager::Color::Float')) {
+      $self->{fill} = 
+        Imager::i_new_fill_solidf($solid, $hsh{combine});
     }
     else {
       $Imager::ERRSTR = "solid isn't a color";
     }
     else {
       $Imager::ERRSTR = "solid isn't a color";
@@ -42,17 +99,18 @@ sub new {
       }
       $hsh{hatch} = $hatch_types{$hsh{hatch}};
     }
       }
       $hsh{hatch} = $hatch_types{$hsh{hatch}};
     }
-    if (UNIVERSAL::isa($hsh{fg}, 'Imager::Color')) {
-      $hsh{bg} ||= Imager::Color->new(255, 255, 255);
+    my $fg = _color($hsh{fg});
+    if (UNIVERSAL::isa($fg, 'Imager::Color')) {
+      my $bg = _color($hsh{bg} || Imager::Color->new(255, 255, 255));
       $self->{fill} = 
       $self->{fill} = 
-        Imager::i_new_fill_hatch($hsh{fg}, $hsh{bg}, $hsh{combine}, 
+        Imager::i_new_fill_hatch($fg, $bg, $hsh{combine}, 
                                  $hsh{hatch}, $hsh{cust_hatch}, 
                                  $hsh{dx}, $hsh{dy});
     }
                                  $hsh{hatch}, $hsh{cust_hatch}, 
                                  $hsh{dx}, $hsh{dy});
     }
-    elsif (UNIVERSAL::isa($hsh{bg}, 'Imager::Color::Float')) {
-      $hsh{bg} ||= Imager::Color::Float->new(1, 1, 1);
+    elsif (UNIVERSAL::isa($fg, 'Imager::Color::Float')) {
+      my $bg  = _color($hsh{bg} || Imager::Color::Float->new(1, 1, 1));
       $self->{fill} = 
       $self->{fill} = 
-        Imager::i_new_fill_hatchf($hsh{fg}, $hsh{bg}, $hsh{combine},
+        Imager::i_new_fill_hatchf($fg, $bg, $hsh{combine},
                                   $hsh{hatch}, $hsh{cust_hatch}, 
                                   $hsh{dx}, $hsh{dy});
     }
                                   $hsh{hatch}, $hsh{cust_hatch}, 
                                   $hsh{dx}, $hsh{dy});
     }
@@ -104,6 +162,10 @@ sub hatches {
   return @hatch_types;
 }
 
   return @hatch_types;
 }
 
+sub combines {
+  return @combine_types;
+}
+
 1;
 
 =head1 NAME
 1;
 
 =head1 NAME
@@ -145,11 +207,78 @@ fountain (similar to gradients in paint software)
 
 =item combine
 
 
 =item combine
 
-If this is non-zero the fill combines the given colors or samples (if
-the fill is an image) with the underlying image.
+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.
 
 
-This this is missing or zero then the target image pixels are simply
-overwritten.
+=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
 
 =back
 
 
 =back
 
@@ -273,6 +402,20 @@ same fill as the C<fountain> filter, but is restricted to the shape
 you are drawing, and the fountain parameter supplies the fill type,
 and is required.
 
 you are drawing, and the fountain parameter supplies the fill type,
 and is required.
 
+=head1 OTHER METHODS
+
+=over
+
+=item Imager::Fill->hatches
+
+A list of all defined hatch names.
+
+=item Imager::Fill->combines
+
+A list of all combine types.
+
+=back
+
 =head1 FUTURE PLANS
 
 I'm planning on adding the following types of fills:
 =head1 FUTURE PLANS
 
 I'm planning on adding the following types of fills:
index d7f316c1188309a0c1c4b2ad2f39616b8d6af3f7..65b98ee12e7a7178c536e5aeb61688ebade24994 100644 (file)
@@ -6,7 +6,7 @@
 # Change 1..1 below to 1..last_test_to_print .
 # (It may become useful if the test is moved to ./t subdirectory.)
 
 # Change 1..1 below to 1..last_test_to_print .
 # (It may become useful if the test is moved to ./t subdirectory.)
 
-BEGIN { $| = 1; print "1..22\n"; }
+BEGIN { $| = 1; print "1..42\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager;
 $loaded = 1;
 END {print "not ok 1\n" unless $loaded;}
 use Imager;
 $loaded = 1;
@@ -65,6 +65,47 @@ color_ok(21, 200, 201, 203, 204,
          Imager::Color->new(channels=>[ 200, 201, 203, 204 ]));
 color_ok(22, 255, 250, 250, 255, 
          Imager::Color->new(name=>'snow', palette=>'testimg/test_gimp_pal'));
          Imager::Color->new(channels=>[ 200, 201, 203, 204 ]));
 color_ok(22, 255, 250, 250, 255, 
          Imager::Color->new(name=>'snow', palette=>'testimg/test_gimp_pal'));
+
+# test the internal HSV <=> RGB conversions
+# these values were generated using the GIMP
+# all but hue is 0..360, saturation and value from 0 to 1
+# rgb from 0 to 255
+my @hsv_vs_rgb =
+  (
+   { hsv => [ 0, 0.2, 0.1 ], rgb=> [ 25, 20, 20 ] },
+   { hsv => [ 0, 0.5, 1.0 ], rgb => [ 255, 127, 127 ] },
+   { hsv => [ 100, 0.5, 1.0 ], rgb => [ 170, 255, 127 ] },
+   { hsv => [ 100, 1.0, 1.0 ], rgb=> [ 85, 255, 0 ] },
+   { hsv => [ 335, 0.5, 0.5 ], rgb=> [127, 63, 90 ] },
+  );
+
+use Imager::Color::Float;
+my $test_num = 23;
+my $index = 0;
+for my $entry (@hsv_vs_rgb) {
+  print "# color index $index\n";
+  my $hsv = $entry->{hsv};
+  my $rgb = $entry->{rgb};
+  my $fhsvo = Imager::Color::Float->new($hsv->[0]/360.0, $hsv->[1], $hsv->[2]);
+  my $fc = Imager::Color::Float::i_hsv_to_rgb($fhsvo);
+  fcolor_close_enough($test_num++, $rgb->[0]/255, $rgb->[1]/255, 
+                      $rgb->[2]/255, $fc);
+  my $fc2 = Imager::Color::Float::i_rgb_to_hsv($fc);
+  fcolor_close_enough($test_num++, $hsv->[0]/360.0, $hsv->[1], $hsv->[2], 
+                      $fc2);
+
+  my $hsvo = Imager::Color->new($hsv->[0]*255/360.0, $hsv->[1] * 255, 
+                                $hsv->[2] * 255);
+  my $c = Imager::Color::i_hsv_to_rgb($hsvo);
+  color_close_enough($test_num++, @$rgb, $c);
+  my $c2 = Imager::Color::i_rgb_to_hsv($c);
+  color_close_enough_hsv($test_num++, $hsv->[0]*255/360.0, $hsv->[1] * 255, 
+                     $hsv->[2] * 255, $c2);
+  ++$index;
+}
+
+
 sub test_col {
   my ($c, $r, $g, $b, $a) = @_;
   unless ($c) {
 sub test_col {
   my ($c, $r, $g, $b, $a) = @_;
   unless ($c) {
@@ -75,6 +116,48 @@ sub test_col {
   return $r == $cr && $g == $cg && $b == $cb && $a == $ca;
 }
 
   return $r == $cr && $g == $cg && $b == $cb && $a == $ca;
 }
 
+sub color_close_enough {
+  my ($test_num, $r, $g, $b, $c) = @_;
+
+  my ($cr, $cg, $cb) = $c->rgba;
+  if (abs($cr-$r) <= 5 && abs($cg-$g) <= 5 && abs($cb-$b) <= 5) {
+    print "ok $test_num\n";
+  }
+  else {
+    print "not ok $test_num # ($cr, $cg, $cb) <=> ($r, $g, $b)\n";
+  }
+}
+
+sub color_close_enough_hsv {
+  my ($test_num, $h, $s, $v, $c) = @_;
+
+  my ($ch, $cs, $cv) = $c->rgba;
+  if ($ch < 5 && $h > 250) {
+    $ch += 255;
+  }
+  elsif ($ch > 250 && $h < 5) {
+    $h += 255;
+  }
+  if (abs($ch-$h) <= 5 && abs($cs-$s) <= 5 && abs($cv-$v) <= 5) {
+    print "ok $test_num\n";
+  }
+  else {
+    print "not ok $test_num # ($ch, $cs, $cv) <=> ($h, $s, $v)\n";
+  }
+}
+
+sub fcolor_close_enough {
+  my ($test_num, $r, $g, $b, $c) = @_;
+
+  my ($cr, $cg, $cb) = $c->rgba;
+  if (abs($cr-$r) <= 0.01 && abs($cg-$g) <= 0.01 && abs($cb-$b) <= 0.01) {
+    print "ok $test_num\n";
+  }
+  else {
+    print "not ok $test_num # ($cr, $cg, $cb) <=> ($r, $g, $b)\n";
+  }
+}
+
 sub color_ok {
   my ($test_num, $r, $g, $b, $a, $c) = @_;
 
 sub color_ok {
   my ($test_num, $r, $g, $b, $a, $c) = @_;
 
index cfeba5a768431301ce58d9b8832371bf9ed90f77..4f56d8196cbbec4c084472b46946c8c72fe2399f 100644 (file)
@@ -1,13 +1,15 @@
 #!perl -w
 use strict;
 
 #!perl -w
 use strict;
 
+print "1..34\n";
+
 use Imager ':handy';
 use Imager::Fill;
 use Imager::Color::Float;
 
 use Imager ':handy';
 use Imager::Fill;
 use Imager::Color::Float;
 
-Imager::init_log("testout/t20fill.log", 1);
+sub ok ($$$);
 
 
-print "1..21\n";
+Imager::init_log("testout/t20fill.log", 1);
 
 my $blue = NC(0,0,255);
 my $red = NC(255, 0, 0);
 
 my $blue = NC(0,0,255);
 my $red = NC(255, 0, 0);
@@ -72,7 +74,7 @@ ok(12, !$diff, "custom hatch mismatch");
 my $im1 = Imager->new(xsize=>100, ysize=>100);
 my $im2 = Imager->new(xsize=>100, ysize=>100);
 
 my $im1 = Imager->new(xsize=>100, ysize=>100);
 my $im2 = Imager->new(xsize=>100, ysize=>100);
 
-my $solid = Imager::Fill->new(solid=>$red);
+my $solid = Imager::Fill->new(solid=>'#FF0000');
 ok(13, $solid, "creating oo solid fill");
 ok(14, $solid->{fill}, "bad oo solid fill");
 $im1->box(fill=>$solid);
 ok(13, $solid, "creating oo solid fill");
 ok(14, $solid->{fill}, "bad oo solid fill");
 $im1->box(fill=>$solid);
@@ -108,8 +110,8 @@ $im->box(xmin=>10, ymin=>10, xmax=>190, ymax=>190,
 $im->arc(r=>80, d1=>45, d2=>75, 
            fill=>{ hatch=>'stipple2',
                    combine=>1,
 $im->arc(r=>80, d1=>45, d2=>75, 
            fill=>{ hatch=>'stipple2',
                    combine=>1,
-                   fg=>NC(0, 0, 0, 255),
-                   bg=>NC(255,255,255,192) });
+                   fg=>[ 0, 0, 0, 255 ],
+                   bg=>{ rgba=>[255,255,255,160] } });
 $im->arc(r=>80, d1=>75, d2=>135,
          fill=>{ fountain=>'radial', xa=>100, ya=>100, xb=>20, yb=>100 });
 $im->write(file=>'testout/t20_sample.ppm');
 $im->arc(r=>80, d1=>75, d2=>135,
          fill=>{ fountain=>'radial', xa=>100, ya=>100, xb=>20, yb=>100 });
 $im->write(file=>'testout/t20_sample.ppm');
@@ -149,7 +151,51 @@ $ffim->flood_fill(x=>50, 'y'=>50,
 #                        });
 $ffim->write(file=>'testout/t20_ooflood.ppm');
 
 #                        });
 $ffim->write(file=>'testout/t20_ooflood.ppm');
 
-sub ok {
+# test combining modes
+my $fill = NC(192, 128, 128, 128);
+my $target = NC(64, 32, 64);
+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 },
+   # 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, 55) },
+   saturation => { result=>NC(63, 37, 64) },
+   value => { result=>NC(127, 64, 128) },
+   color => { result=>NC(64, 37, 52) },
+  );
+
+my $testnum = 22; # from 22 to 34
+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($testnum++, 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($testnum++, color_close($c, $test->{result}), "combine '$comb'")
+      or print "# got: ",join(",", $c->rgba),
+        "  allowed: ",join(",", $test->{result}->rgba),"\n";
+  }
+}
+
+sub ok ($$$) {
   my ($num, $test, $desc) = @_;
 
   if ($test) {
   my ($num, $test, $desc) = @_;
 
   if ($test) {
@@ -158,6 +204,21 @@ sub ok {
   else {
     print "not ok $num # $desc\n";
   }
   else {
     print "not ok $num # $desc\n";
   }
+  $test;
+}
+
+sub color_close {
+  my ($c1, $c2) = @_;
+
+  my @c1 = $c1->rgba;
+  my @c2 = $c2->rgba;
+
+  for my $i (0..2) {
+    if (abs($c1[$i]-$c2[$i]) > 2) {
+      return 0;
+    }
+  }
+  return 1;
 }
 
 # for use during testing
 }
 
 # for use during testing
index 6aca7e1fceb620cade9f2e9737ce08b95480eac5..136b72300e53731baebabb81ac6b2909bb3919d8 100644 (file)
@@ -73,9 +73,9 @@ test($imbase, 31, { type=>'fountain', xa=>20, ya=>130, xb=>130, yb=>20,
 my $f3 = Imager::Fountain->read(gimp=>'testimg/gimpgrad') 
   or print "not ";
 print "ok 33\n";
 my $f3 = Imager::Fountain->read(gimp=>'testimg/gimpgrad') 
   or print "not ";
 print "ok 33\n";
-test($imbase, 34, { type=>'fountain', xa=>75, ya=>75, xb=>90, yb=>10,
+test($imbase, 34, { type=>'fountain', xa=>75, ya=>75, xb=>90, yb=>15,
                     segments=>$f3, super_sample=>'grid',
                     segments=>$f3, super_sample=>'grid',
-                    ftype=>'radial_square', combine=>1 },
+                    ftype=>'radial_square', combine=>'color' },
      'testout/t61_fount_gimp.ppm');
 
 sub test {
      'testout/t61_fount_gimp.ppm');
 
 sub test {
index 563cd779c5866d7852f7ecb1996601a36fba65c3..58f3aacd1e3e78f9adaf4096fed134356ce33f00 100644 (file)
@@ -1,4 +1,4 @@
 GIMP Gradient
 2
 GIMP Gradient
 2
-0.000000 0.130000 0.546377 0.000000 0.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 4 1
-0.546377 0.773188 1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 0
+0.000000 0.130000 0.546377 0.900000 0.900000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 4 1
+0.546377 0.773188 1.000000 0.000000 1.000000 0.000000 1.000000 0.200000 0.200000 0.200000 0.000000 0 0