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 ad053a6..9469fa8 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -290,6 +290,21 @@ BEGIN {
                                    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,
@@ -2960,7 +2975,7 @@ source.
   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
@@ -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
-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.
index 955cbc2..3299e07 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)));
 
-
+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_
@@ -586,6 +605,27 @@ ICLF_set_internal(cl,r,g,b,a)
         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
@@ -3095,3 +3135,24 @@ i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy)
       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 d6eb6ff..4121d7a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -4,7 +4,7 @@ Imager.xs
 MANIFEST
 README
 Makefile.PL
-bmp.c
+bmp.c           Reading and writing Windows BMP files
 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/Fill.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/t20fill.t             Tests fills
 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/t67convert.t
+t/t68map.t
 t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
diff --git a/bmp.c b/bmp.c
index 7fc83d2..fbc0923 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;
+
+  default:
+    i_push_errorf(0, "unknown bit count for BMP file (%d)", bit_count);
+    return NULL;
   }
 
   /* store the resolution */
diff --git a/color.c b/color.c
index 6097cc9..ab33bfc 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,4 +1,5 @@
 #include "image.h"
+#include <math.h>
 
 /*
 =head1 NAME
@@ -19,6 +20,9 @@ A collection of utility functions for converting between color spaces.
 
 #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)
 
@@ -33,8 +37,8 @@ void i_rgb_to_hsvf(i_fcolor *color) {
   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
@@ -60,6 +64,47 @@ void i_rgb_to_hsvf(i_fcolor *color) {
   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)
 
@@ -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;
-    i = h;
+    i = floor(h);
     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
 
diff --git a/draw.c b/draw.c
index ecf0bad..d7248ab 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);
+    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;
 
-        if (fill->combines
+        if (fill->combine) 
           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);
+    if (work)
+      myfree(work);
   }
   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;
 
-        if (fill->combines
+        if (fill->combinef
           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);
+    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));
+    i_color *work = NULL;
+    if (fill->combine)
+      work = mymalloc(sizeof(i_color) * (x2-x1));
     while (y1 <= y2) {
-      if (fill->combines)
+      if (fill->combine)
         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);
+    if (work)
+      myfree(work);
   }
   else {
     i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+    i_fcolor *work;
+    work = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+
     while (y1 <= y2) {
-      if (fill->combines)
+      if (fill->combinef)
         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);
+    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));
+    i_color *work = NULL;
+    if (fill->combine)
+      work = mymalloc(sizeof(i_color) * (bxmax - 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;
           }
-          if (fill->combines)
+          if (fill->combine)
             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);
+    if (work)
+      myfree(work);
   }
   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;
@@ -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;
           }
-          if (fill->combines)
+          if (fill->combinef)
             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);
+    if (work)
+      myfree(work);
   }
 
   btm_destroy(btm);
diff --git a/fills.c b/fills.c
index 7466f8d..e7d3bc4 100644 (file)
--- a/fills.c
+++ b/fills.c
@@ -74,6 +74,30 @@ fountain fill
 
 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
@@ -95,13 +119,7 @@ static i_fcolor color_to_fcolor(i_color *c) {
     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; \
@@ -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; \
@@ -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, 
-                       i_color *);
+                       i_color *, i_color *);
 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, 
-                            i_color *);
+                            i_color *, i_color *);
 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 =
 {
@@ -135,7 +172,8 @@ static i_fill_solid_t base_solid_fill =
     fill_solid,
     fill_solidf,
     NULL,
-    0
+    NULL,
+    NULL,
   },
 };
 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,
-    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));
   
-  if (combine && c->channel[3] < 1.0)
+  if (combine && c->channel[3] < 1.0) {
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   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));
 
-  if (combine && c->channel[3] < 255)
+  if (combine && c->channel[3] < 255) {
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   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_color *data);
+                       i_color *data, i_color *work);
 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, 
@@ -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, 
-           i_color *data) {
+           i_color *data, i_color *work) {
   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, 
-           i_fcolor *data) {
+           i_fcolor *data, i_fcolor *work) {
   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, 
-           i_color *data) {
+                i_color *data, i_color *work) {
   i_color c = T_SOLID_FILL(fill)->c;
+  int count = width;
+  i_color *wstart = work;
 
   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, 
-           i_fcolor *data) {
+           i_fcolor *data, i_fcolor *work) {
   i_fcolor c = T_SOLID_FILL(fill)->fc;
+  int count = width;
+  i_fcolor *wstart = work;
 
   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->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);
   }
@@ -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, 
-                       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;
 
-  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, 
-                        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;
   
-  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
 
index 3eeb259..473176d 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);
+  i_fcolor *work = NULL;
   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);
@@ -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) {
-        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;
       }
     }
+    if (combine)
+      combinef_func(line, work, im->channels, im->xsize);
     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, 
-            i_fcolor *data);
+            i_fcolor *data, i_fcolor *work);
 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.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);
 
@@ -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, 
-            i_fcolor *data) {
+            i_fcolor *data, i_fcolor *work) {
   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 d11436d..16686c9 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_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);
@@ -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, 
-      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,
-      i_fcolor *data);
+      i_fcolor *data, i_fcolor *work);
 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
 {
@@ -146,9 +153,26 @@ typedef struct i_fill_tag
 
   /* 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;
 
+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 *
index d12680b..fb86501 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)
 
+extern void i_get_combine(int combine, i_fill_combine_f *, i_fill_combinef_f *);
+
 #endif
index c727052..229fbd2 100644 (file)
@@ -1,5 +1,5 @@
 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
@@ -10,17 +10,74 @@ 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};
+
+# 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;
+  if (exists $combine_types{$hsh{combine}}) {
+    $hsh{combine} = $combine_types{$hsh{combine}};
+  }
   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";
@@ -42,17 +99,18 @@ sub new {
       }
       $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} = 
-        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});
     }
-    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} = 
-        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});
     }
@@ -104,6 +162,10 @@ sub hatches {
   return @hatch_types;
 }
 
+sub combines {
+  return @combine_types;
+}
+
 1;
 
 =head1 NAME
@@ -145,11 +207,78 @@ fountain (similar to gradients in paint software)
 
 =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
 
@@ -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.
 
+=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:
index d7f316c..65b98ee 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.)
 
-BEGIN { $| = 1; print "1..22\n"; }
+BEGIN { $| = 1; print "1..42\n"; }
 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'));
+
+# 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) {
@@ -75,6 +116,48 @@ sub test_col {
   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) = @_;
 
index cfeba5a..4f56d81 100644 (file)
@@ -1,13 +1,15 @@
 #!perl -w
 use strict;
 
+print "1..34\n";
+
 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);
@@ -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 $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);
@@ -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,
-                   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');
@@ -149,7 +151,51 @@ $ffim->flood_fill(x=>50, 'y'=>50,
 #                        });
 $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) {
@@ -158,6 +204,21 @@ sub ok {
   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
index 6aca7e1..136b723 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";
-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',
-                    ftype=>'radial_square', combine=>1 },
+                    ftype=>'radial_square', combine=>'color' },
      'testout/t61_fount_gimp.ppm');
 
 sub test {
index 563cd77..58f3aac 100644 (file)
@@ -1,4 +1,4 @@
 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