]> git.imager.perl.org - imager.git/blobdiff - fills.c
allow Imager to be loaded on Windows 98
[imager.git] / fills.c
diff --git a/fills.c b/fills.c
index d47f491c89140c4fbb0586275fb9ecdbe34573c4..6e929199dac9d1247173a6c5dd6d24c13a33aa1c 100644 (file)
--- a/fills.c
+++ b/fills.c
-#include "image.h"
-#include "imagei.h"
+#include "imager.h"
+#include "imageri.h"
 
 /*
+=head1 NAME
 
-Possible fill types:
- - solid colour
- - hatched (pattern, fg, bg)
- - tiled image
- - regmach
- - tiling?
- - generic?
+fills.c - implements the basic general fills
 
+=head1 SYNOPSIS
+
+  i_fill_t *fill;
+  i_color c1, c2;
+  i_fcolor fc1, fc2;
+  int combine;
+  fill = i_new_fill_solidf(&fc1, combine);
+  fill = i_new_fill_solid(&c1, combine);
+  fill = i_new_fill_hatchf(&fc1, &fc2, combine, hatch, cust_hash, dx, dy);
+  fill = i_new_fill_hatch(&c1, &c2, combine, hatch, cust_hash, dx, dy);
+  fill = i_new_fill_image(im, matrix, xoff, yoff, combine);
+  i_fill_destroy(fill);
+
+=head1 DESCRIPTION
+
+Implements the basic general fills, which can be used for filling some
+shapes and for flood fills.
+
+Each fill can implement up to 3 functions:
+
+=over
+
+=item fill_with_color
+
+called for fills on 8-bit images.  This can be NULL in which case the
+fill_with_colorf function is called.
+
+=item fill_with_fcolor
+
+called for fills on non-8-bit images or when fill_with_color is NULL.
+
+=item destroy
+
+called by i_fill_destroy() if non-NULL, to release any extra resources
+that the fill may need.
+
+=back
+
+fill_with_color and fill_with_fcolor are basically the same function
+except that the first works with lines of i_color and the second with
+lines of i_fcolor.
+
+If the combines member if non-zero the line data is populated from the
+target image before calling fill_with_*color.
+
+fill_with_color needs to fill the I<data> parameter with the fill
+pixels.  If combines is non-zero it the fill pixels should be combined
+with the existing data.
+
+The current fills are:
+
+=over
+
+=item *
+
+solid fill
+
+=item *
+
+hatched fill
+
+=item *
+
+fountain fill
+
+=back
+
+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
 */
 
-static i_color fcolor_to_color(i_fcolor *c) {
+static i_color fcolor_to_color(const i_fcolor *c) {
   int ch;
   i_color out;
 
   for (ch = 0; ch < MAXCHANNELS; ++ch)
     out.channel[ch] = SampleFTo8(c->channel[ch]);
+
+  return out;
 }
 
-static i_fcolor color_to_fcolor(i_color *c) {
+static i_fcolor color_to_fcolor(const i_color *c) {
   int ch;
-  i_color out;
+  i_fcolor out;
 
   for (ch = 0; ch < MAXCHANNELS; ++ch)
     out.channel[ch] = Sample8ToF(c->channel[ch]);
-}
 
-typedef struct
-{
-  i_fill_t base;
-  i_color c;
-  i_fcolor fc;
-} i_fill_solid_t;
+  return out;
+}
 
+/* alpha combine in with out */
 #define COMBINE(out, in, channels) \
   { \
     int ch; \
@@ -45,6 +134,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; \
@@ -54,6 +155,13 @@ 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 *);
 static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, 
@@ -69,7 +177,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 =
@@ -78,10 +187,21 @@ static i_fill_solid_t base_solid_fill_comb =
     fill_solid_comb,
     fill_solidf_comb,
     NULL,
-    1
+    NULL,
+    NULL,
   },
 };
 
+/*
+=item i_fill_destroy(fill)
+
+=category Fills
+
+Call to destroy any fill object.
+
+=cut
+*/
+
 void
 i_fill_destroy(i_fill_t *fill) {
   if (fill->destroy)
@@ -89,13 +209,27 @@ i_fill_destroy(i_fill_t *fill) {
   myfree(fill);
 }
 
+/*
+=item i_new_fill_solidf(color, combine)
+
+=category Fills
+
+Create a solid fill based on a float color.
+
+If combine is non-zero then alpha values will be combined.
+
+=cut
+*/
+
 i_fill_t *
-i_new_fill_solidf(i_fcolor *c, int combine) {
+i_new_fill_solidf(const i_fcolor *c, int combine) {
   int ch;
-  i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
+  i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */
   
-  if (combine && c->channel[3] < 1.0)
+  if (combine) {
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   else
     *fill = base_solid_fill;
   fill->fc = *c;
@@ -106,13 +240,27 @@ i_new_fill_solidf(i_fcolor *c, int combine) {
   return &fill->base;
 }
 
+/*
+=item i_new_fill_solid(color, combine)
+
+=category Fills
+
+Create a solid fill based on an 8-bit color.
+
+If combine is non-zero then alpha values will be combined.
+
+=cut
+*/
+
 i_fill_t *
-i_new_fill_solid(i_color *c, int combine) {
+i_new_fill_solid(const i_color *c, int combine) {
   int ch;
-  i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t));
+  i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */
 
-  if (combine && c->channel[3] < 255)
+  if (combine) {
     *fill = base_solid_fill_comb;
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
   else
     *fill = base_solid_fill;
   fill->c = *c;
@@ -123,46 +271,6 @@ i_new_fill_solid(i_color *c, int combine) {
   return &fill->base;
 }
 
-#define T_SOLID_FILL(fill) ((i_fill_solid_t *)(fill))
-
-static void
-fill_solid(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_color *data) {
-  while (width-- > 0) {
-    *data++ = T_SOLID_FILL(fill)->c;
-  }
-}
-
-static void
-fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_fcolor *data) {
-  while (width-- > 0) {
-    *data++ = T_SOLID_FILL(fill)->fc;
-  }
-}
-
-static void
-fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_color *data) {
-  i_color c = T_SOLID_FILL(fill)->c;
-
-  while (width-- > 0) {
-    COMBINE(*data, c, channels);
-    ++data;
-  }
-}
-
-static void
-fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, 
-           i_fcolor *data) {
-  i_fcolor c = T_SOLID_FILL(fill)->fc;
-
-  while (width-- > 0) {
-    COMBINEF(*data, c, channels);
-    ++data;
-  }
-}
-
 static unsigned char
 builtin_hatches[][8] =
 {
@@ -268,19 +376,19 @@ builtin_hatches[][8] =
   },
   {
     /* scales overlapping downwards */
-    0x77, 0x22, 0x22, 0x22, 0xDD, 0x88, 0x88, 0x88,
+    0x80, 0x80, 0x41, 0x3E, 0x08, 0x08, 0x14, 0xE3,
   },
   {
     /* scales overlapping upwards */
-    0x22, 0x22, 0x22, 0x77, 0x88, 0x88, 0x88, 0xDD,
+    0xC7, 0x28, 0x10, 0x10, 0x7C, 0x82, 0x01, 0x01,
   },
   {
     /* scales overlapping leftwards */
-    0xF0, 0x11, 0x0F, 0x11, 0xF0, 0x11, 0x0F, 0x11,
+    0x83, 0x84, 0x88, 0x48, 0x38, 0x48, 0x88, 0x84,
   },
   {
     /* scales overlapping rightwards */
-    0x88, 0xF0, 0x88, 0x0F, 0x88, 0xF0, 0x88, 0x0F,
+    0x21, 0x11, 0x12, 0x1C, 0x12, 0x11, 0x21, 0xC1,
   },
   {
     /* denser stipple */
@@ -290,6 +398,10 @@ builtin_hatches[][8] =
     /* L-shaped tiles */
     0xFF, 0x84, 0x84, 0x9C, 0x94, 0x9C, 0x90, 0x90,
   },
+  {
+    /* wider stipple */
+    0x80, 0x40, 0x20, 0x00, 0x02, 0x04, 0x08, 0x00,
+  },
 };
 
 typedef struct
@@ -305,23 +417,236 @@ static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels,
                        i_color *data);
 static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, 
                         i_fcolor *data);
+static
+i_fill_t *
+i_new_hatch_low(const i_color *fg, const i_color *bg, const i_fcolor *ffg, const i_fcolor *fbg, 
+                int combine, int hatch, const unsigned char *cust_hatch,
+                int dx, int dy);
+
+/*
+=item i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy)
+
+=category Fills
+
+Creates a new hatched fill with the fg color used for the 1 bits in
+the hatch and bg for the 0 bits.  If combine is non-zero alpha values
+will be combined.
+
+If cust_hatch is non-NULL it should be a pointer to 8 bytes of the
+hash definition, with the high-bits to the left.
+
+If cust_hatch is NULL then one of the standard hatches is used.
+
+(dx, dy) are an offset into the hatch which can be used to unalign adjoining areas, or to align the origin of a hatch with the the side of a filled area.
+
+=cut
+*/
+i_fill_t *
+i_new_fill_hatch(const i_color *fg, const i_color *bg, int combine, int hatch, 
+            const unsigned char *cust_hatch, int dx, int dy) {
+  return i_new_hatch_low(fg, bg, NULL, NULL, combine, hatch, cust_hatch, 
+                         dx, dy);
+}
+
+/*
+=item i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy)
+
+=category Fills
+
+Creates a new hatched fill with the fg color used for the 1 bits in
+the hatch and bg for the 0 bits.  If combine is non-zero alpha values
+will be combined.
+
+If cust_hatch is non-NULL it should be a pointer to 8 bytes of the
+hash definition, with the high-bits to the left.
+
+If cust_hatch is NULL then one of the standard hatches is used.
+
+(dx, dy) are an offset into the hatch which can be used to unalign adjoining areas, or to align the origin of a hatch with the the side of a filled area.
+
+=cut
+*/
+i_fill_t *
+i_new_fill_hatchf(const i_fcolor *fg, const i_fcolor *bg, int combine, int hatch, 
+                 const unsigned char *cust_hatch, int dx, int dy) {
+  return i_new_hatch_low(NULL, NULL, fg, bg, combine, hatch, cust_hatch, 
+                         dx, dy);
+}
+
+static void fill_image(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_color *data);
+static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_fcolor *data);
+struct i_fill_image_t {
+  i_fill_t base;
+  i_img *src;
+  int xoff, yoff;
+  int has_matrix;
+  double matrix[9];
+};
+
+/*
+=item i_new_fill_image(im, matrix, xoff, yoff, combine)
+
+=category Fills
+
+Create an image based fill.
+
+matrix is an array of 9 doubles representing a transformation matrix.
+
+xoff and yoff are the offset into the image to start filling from.
+
+=cut
+*/
+i_fill_t *
+i_new_fill_image(i_img *im, const double *matrix, int xoff, int yoff, int combine) {
+  struct i_fill_image_t *fill = mymalloc(sizeof(*fill)); /* checked 14jul05 tonyc */
+
+  fill->base.fill_with_color = fill_image;
+  fill->base.fill_with_fcolor = fill_imagef;
+  fill->base.destroy = NULL;
+
+  if (combine) {
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
+  else {
+    fill->base.combine = NULL;
+    fill->base.combinef = NULL;
+  }
+  fill->src = im;
+  if (xoff < 0)
+    xoff += im->xsize;
+  fill->xoff = xoff;
+  if (yoff < 0)
+    yoff += im->ysize;
+  fill->yoff = yoff;
+  if (matrix) {
+    fill->has_matrix = 1;
+    memcpy(fill->matrix, matrix, sizeof(fill->matrix));
+  }
+  else
+    fill->has_matrix = 0;
+
+  return &fill->base;
+}
+
+
+#define T_SOLID_FILL(fill) ((i_fill_solid_t *)(fill))
+
+/*
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=over
+
+=item fill_solid(fill, x, y, width, channels, data)
+
+The 8-bit sample fill function for non-combining solid fills.
+
+=cut
+*/
+static void
+fill_solid(i_fill_t *fill, int x, int y, int width, int channels, 
+           i_color *data) {
+  while (width-- > 0) {
+    *data++ = T_SOLID_FILL(fill)->c;
+  }
+}
+
+/*
+=item fill_solid(fill, x, y, width, channels, data)
+
+The floating sample fill function for non-combining solid fills.
+
+=cut
+*/
+static void
+fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, 
+           i_fcolor *data) {
+  while (width-- > 0) {
+    *data++ = T_SOLID_FILL(fill)->fc;
+  }
+}
+
+/*
+=item fill_solid_comb(fill, x, y, width, channels, data)
+
+The 8-bit sample fill function for combining solid fills.
+
+=cut
+*/
+static void
+fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, 
+                i_color *data) {
+  i_color c = T_SOLID_FILL(fill)->c;
+
+  while (width-- > 0) {
+    *data++ = c;
+  }
+}
+
+/*
+=item fill_solidf_comb(fill, x, y, width, channels, data)
+
+The floating sample fill function for combining solid fills.
+
+=cut
+*/
+static void
+fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, 
+           i_fcolor *data) {
+  i_fcolor c = T_SOLID_FILL(fill)->fc;
+
+  while (width-- > 0) {
+    *data++ = c;
+  }
+}
+
+/*
+=item i_new_hatch_low(fg, bg, ffg, fbg, combine, hatch, cust_hatch, dx, dy)
 
+Implements creation of hatch fill objects.
+
+=cut
+*/
 static
 i_fill_t *
-i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, 
-                int combine, int hatch, unsigned char *cust_hatch,
+i_new_hatch_low(const i_color *fg, const i_color *bg, 
+               const i_fcolor *ffg, const i_fcolor *fbg, 
+                int combine, int hatch, const unsigned char *cust_hatch,
                 int dx, int dy) {
-  i_fill_hatch_t *fill = mymalloc(sizeof(i_fill_hatch_t));
+  i_fill_hatch_t *fill = mymalloc(sizeof(i_fill_hatch_t)); /* checked 14jul05 tonyc */
 
   fill->base.fill_with_color = fill_hatch;
   fill->base.fill_with_fcolor = fill_hatchf;
   fill->base.destroy = NULL;
-  fill->fg = fg ? *fg : fcolor_to_color(ffg);
-  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);
+  /* Some Sun C didn't like the condition expressions that were here.
+     See https://rt.cpan.org/Ticket/Display.html?id=21944
+   */
+  if (fg)
+    fill->fg = *fg;
+  else
+    fill->fg = fcolor_to_color(ffg);
+  if (bg)
+    fill->bg = *bg;
+  else
+    fill->bg = fcolor_to_color(fbg);
+  if (ffg) 
+    fill->ffg = *ffg;
+  else
+    fill->ffg = color_to_fcolor(fg);
+  if (fbg)
+    fill->fbg = *fbg;
+  else
+    fill->fbg = color_to_fcolor(bg);
+  if (combine) {
+    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);
   }
@@ -336,20 +661,13 @@ i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg,
   return &fill->base;
 }
 
-i_fill_t *
-i_new_fill_hatch(i_color *fg, i_color *bg, int combine, int hatch, 
-            unsigned char *cust_hatch, int dx, int dy) {
-  return i_new_hatch_low(fg, bg, NULL, NULL, combine, hatch, cust_hatch, 
-                         dx, dy);
-}
+/*
+=item fill_hatch(fill, x, y, width, channels, data)
 
-i_fill_t *
-i_new_fill_hatchf(i_fcolor *fg, i_fcolor *bg, int combine, int hatch, 
-            unsigned char *cust_hatch, int dx, int dy) {
-  return i_new_hatch_low(NULL, NULL, fg, bg, combine, hatch, cust_hatch, 
-                         dx, dy);
-}
+The 8-bit sample fill function for hatched fills.
 
+=cut
+*/
 static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, 
                        i_color *data) {
   i_fill_hatch_t *f = (i_fill_hatch_t *)fill;
@@ -358,20 +676,23 @@ static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels,
   int mask = 128 >> xpos;
 
   while (width-- > 0) {
-    i_color c = (byte & mask) ? f->fg : f->bg;
-
-    if (f->base.combines) {
-      COMBINE(*data, c, channels);
-    }
-    else {
-      *data = c;
-    }
-    ++data;
+    if (byte & mask)
+      *data++ = f->fg;
+    else
+      *data++ = f->bg;
+    
     if ((mask >>= 1) == 0)
       mask = 128;
   }
 }
 
+/*
+=item fill_hatchf(fill, x, y, width, channels, data)
+
+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_fill_hatch_t *f = (i_fill_hatch_t *)fill;
@@ -380,16 +701,725 @@ static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels,
   int mask = 128 >> xpos;
   
   while (width-- > 0) {
-    i_fcolor c = (byte & mask) ? f->ffg : f->fbg;
+    if (byte & mask)
+      *data++ = f->ffg;
+    else
+      *data++ = f->fbg;
+    
+    if ((mask >>= 1) == 0)
+      mask = 128;
+  }
+}
+
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_color interp_i_color(i_color before, i_color after, double pos,
+                              int channels) {
+  i_color out;
+  int ch;
 
-    if (f->base.combines) {
-      COMBINE(*data, c, channels);
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+  if (channels > 3 && out.channel[3])
+    for (ch = 0; ch < channels; ++ch)
+      if (ch != 3) {
+        int temp = out.channel[ch] * 255 / out.channel[3];
+        if (temp > 255)
+          temp = 255;
+        out.channel[ch] = temp;
+      }
+
+  return out;
+}
+
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos,
+                                int channels) {
+  i_fcolor out;
+  int ch;
+
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+  if (out.channel[3])
+    for (ch = 0; ch < channels; ++ch)
+      if (ch != 3) {
+        int temp = out.channel[ch] / out.channel[3];
+        if (temp > 1.0)
+          temp = 1.0;
+        out.channel[ch] = temp;
+      }
+
+  return out;
+}
+
+/*
+=item fill_image(fill, x, y, width, channels, data, work)
+
+=cut
+*/
+static void fill_image(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_color *data) {
+  struct i_fill_image_t *f = (struct i_fill_image_t *)fill;
+  int i = 0;
+  i_color *out = data;
+  
+  if (f->has_matrix) {
+    /* the hard way */
+    while (i < width) {
+      double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2];
+      double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5];
+      double ix = floor(rx / f->src->xsize);
+      double iy = floor(ry / f->src->ysize);
+      i_color c[2][2];
+      i_color c2[2];
+      int dy;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = floor(rx / f->src->xsize);
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = floor(ry / f->src->ysize);
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+
+      for (dy = 0; dy < 2; ++dy) {
+        if ((int)rx == f->src->xsize-1) {
+          i_gpix(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]);
+          i_gpix(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]);
+        }
+        else {
+          i_glin(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, 
+                 c[dy]);
+        }
+        c2[dy] = interp_i_color(c[dy][0], c[dy][1], rx, f->src->channels);
+      }
+      *out++ = interp_i_color(c2[0], c2[1], ry, f->src->channels);
+      ++i;
     }
-    else {
-      *data = c;
+  }
+  else {
+    /* the easy way */
+    /* this should be possible to optimize to use i_glin() */
+    while (i < width) {
+      int rx = x+i;
+      int ry = y;
+      int ix = rx / f->src->xsize;
+      int iy = ry / f->src->ysize;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = rx / f->src->xsize;
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = ry / f->src->xsize;
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+      i_gpix(f->src, rx, ry, out);
+      ++out;
+      ++i;
     }
-    ++data;
-    if ((mask >>= 1) == 0)
-      mask = 128;
+  }
+  if (f->src->channels == 3) {
+    /* just set the alpha */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = 255;
+      data++;
+    }
+  }
+  else if (f->src->channels == 2) {
+    /* copy the alpha to channel 3, duplicate the grey value */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = data->channel[1];
+      data->channel[1] = data->channel[2] = data->channel[0];
+      data++;
+    }
+  }
+  else if (f->src->channels == 1) {
+    /* set the alpha, duplicate grey */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = 255;
+      data->channel[1] = data->channel[2] = data->channel[0];
+      data++;
+    }
+  }
+}
+
+/*
+=item fill_image(fill, x, y, width, channels, data, work)
+
+=cut
+*/
+static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_fcolor *data) {
+  struct i_fill_image_t *f = (struct i_fill_image_t *)fill;
+  int i = 0;
+  
+  if (f->has_matrix) {
+    /* the hard way */
+    while (i < width) {
+      double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2];
+      double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5];
+      double ix = floor(rx / f->src->xsize);
+      double iy = floor(ry / f->src->ysize);
+      i_fcolor c[2][2];
+      i_fcolor c2[2];
+      int dy;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = floor(rx / f->src->xsize);
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = floor(ry / f->src->ysize);
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+
+      for (dy = 0; dy < 2; ++dy) {
+        if ((int)rx == f->src->xsize-1) {
+          i_gpixf(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]);
+          i_gpixf(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]);
+        }
+        else {
+          i_glinf(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, 
+                 c[dy]);
+        }
+        c2[dy] = interp_i_fcolor(c[dy][0], c[dy][1], rx, f->src->channels);
+      }
+      *data++ = interp_i_fcolor(c2[0], c2[1], ry, f->src->channels);
+      ++i;
+    }
+  }
+  else {
+    /* the easy way */
+    /* this should be possible to optimize to use i_glin() */
+    while (i < width) {
+      int rx = x+i;
+      int ry = y;
+      int ix = rx / f->src->xsize;
+      int iy = ry / f->src->ysize;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = rx / f->src->xsize;
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = ry / f->src->xsize;
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+      i_gpixf(f->src, rx, ry, data);
+      ++data;
+      ++i;
+    }
+  }
+  if (f->src->channels == 3) {
+    /* just set the alpha */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = 1.0;
+      data++;
+    }
+  }
+  else if (f->src->channels == 2) {
+    /* copy the alpha to channel 3, duplicate the grey value */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = data->channel[1];
+      data->channel[1] = data->channel[2] = data->channel[0];
+      data++;
+    }
+  }
+  else if (f->src->channels == 1) {
+    /* set the alpha, duplicate grey */
+    for (i = 0; i <  width; ++i) {
+      data->channel[3] = 1.0;
+      data->channel[1] = data->channel[2] = data->channel[0];
+      data++;
+    }
+  }
+}
+
+static void combine_replace(i_color *, i_color *, int, int);
+static void combine_replacef(i_fcolor *, i_fcolor *, int, int);
+static void combine_alphablend(i_color *, i_color *, int, int);
+static void combine_alphablendf(i_fcolor *, i_fcolor *, int, int);
+static void combine_mult(i_color *, i_color *, int, int);
+static void combine_multf(i_fcolor *, i_fcolor *, int, int);
+static void combine_dissolve(i_color *, i_color *, int, int);
+static void combine_dissolvef(i_fcolor *, i_fcolor *, int, int);
+static void combine_add(i_color *, i_color *, int, int);
+static void combine_addf(i_fcolor *, i_fcolor *, int, int);
+static void combine_subtract(i_color *, i_color *, int, int);
+static void combine_subtractf(i_fcolor *, i_fcolor *, int, int);
+static void combine_diff(i_color *, i_color *, int, int);
+static void combine_difff(i_fcolor *, i_fcolor *, int, int);
+static void combine_darken(i_color *, i_color *, int, int);
+static void combine_darkenf(i_fcolor *, i_fcolor *, int, int);
+static void combine_lighten(i_color *, i_color *, int, int);
+static void combine_lightenf(i_fcolor *, i_fcolor *, int, int);
+static void combine_hue(i_color *, i_color *, int, int);
+static void combine_huef(i_fcolor *, i_fcolor *, int, int);
+static void combine_sat(i_color *, i_color *, int, int);
+static void combine_satf(i_fcolor *, i_fcolor *, int, int);
+static void combine_value(i_color *, i_color *, int, int);
+static void combine_valuef(i_fcolor *, i_fcolor *, int, int);
+static void combine_color(i_color *, i_color *, int, int);
+static void combine_colorf(i_fcolor *, i_fcolor *, int, int);
+
+static struct i_combines {
+  i_fill_combine_f combine;
+  i_fill_combinef_f combinef;
+} combines[] =
+{
+  { /* replace */
+    combine_replace,
+    combine_replacef,
+  },
+  { /* alpha blend */
+    combine_alphablend,
+    combine_alphablendf,
+  },
+  {
+    /* multiply */
+    combine_mult,
+    combine_multf,
+  },
+  {
+    /* dissolve */
+    combine_dissolve,
+    combine_dissolvef,
+  },
+  {
+    /* add */
+    combine_add,
+    combine_addf,
+  },
+  {
+    /* subtract */
+    combine_subtract,
+    combine_subtractf,
+  },
+  {
+    /* diff */
+    combine_diff,
+    combine_difff,
+  },
+  {
+    combine_lighten,
+    combine_lightenf,
+  },
+  {
+    combine_darken,
+    combine_darkenf,
+  },
+  {
+    combine_hue,
+    combine_huef,
+  },
+  {
+    combine_sat,
+    combine_satf,
+  },
+  {
+    combine_value,
+    combine_valuef,
+  },
+  {
+    combine_color,
+    combine_colorf,
+  },
+};
+
+/*
+=item i_get_combine(combine, color_func, fcolor_func)
+
+=cut
+*/
+
+void i_get_combine(int combine, i_fill_combine_f *color_func, 
+                   i_fill_combinef_f *fcolor_func) {
+  if (combine < 0 || combine > sizeof(combines) / sizeof(*combines))
+    combine = 0;
+
+  *color_func = combines[combine].combine;
+  *fcolor_func = combines[combine].combinef;
+}
+
+static void combine_replace(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    *out++ = *in++;
+  }
+}
+
+static void combine_replacef(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  while (count--) {
+    *out++ = *in++;
+  }
+}
+
+static void combine_alphablend(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    COMBINE(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_alphablendf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  while (count--) {
+    COMBINEF(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_mult(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    double mult[MAXCHANNELS];
+    mult[3] = in->channel[3];
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3)
+        mult[ch] = (out->channel[ch] * in->channel[ch]) * (1.0 / 255);
+    } 
+    COMBINEA(*out, mult, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_multf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_fcolor c = *in;
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3)
+        c.channel[ch] = out->channel[ch] * in->channel[ch];
+    } 
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_dissolve(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    if (in->channel[3] > rand() * (255.0 / RAND_MAX))
+      COMBINE(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_dissolvef(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  while (count--) {
+    if (in->channel[3] > rand() * (1.0 / RAND_MAX))
+      COMBINEF(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_add(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_color c = *in;
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3) {
+        int total = out->channel[ch] + in->channel[ch];
+        if (total > 255)
+          total = 255;
+        c.channel[ch] = total;
+      }
+    } 
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
   }
 }
+
+static void combine_addf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_fcolor c = *in;
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3) {
+        double total = out->channel[ch] + in->channel[ch];
+        if (total > 1.0)
+          total = 1.0;
+        out->channel[ch] = total;
+      }
+    } 
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_subtract(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_color c = *in;
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3) {
+        int total = out->channel[ch] - in->channel[ch];
+        if (total < 0)
+          total = 0;
+        c.channel[ch] = total;
+      }
+    } 
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_subtractf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_fcolor c = *in;
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3) {
+        double total = out->channel[ch] - in->channel[ch];
+        if (total < 0)
+          total = 0;
+        c.channel[ch] = total;
+      }
+    } 
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_diff(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_color c = *in;
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3) 
+        c.channel[ch] = abs(out->channel[ch] - in->channel[ch]);
+    } 
+    COMBINE(*out, c, channels)
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_difff(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    i_fcolor c = *in;
+    for (ch = 0; ch < (channels); ++ch) { 
+      if (ch != 3)
+        c.channel[ch] = fabs(out->channel[ch] - in->channel[ch]);
+    }
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_darken(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3 && out->channel[ch] < in->channel[ch])
+        in->channel[ch] = out->channel[ch];
+    } 
+    COMBINE(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_darkenf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3 && out->channel[ch] < in->channel[ch])
+        in->channel[ch] = out->channel[ch];
+    } 
+    COMBINEF(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_lighten(i_color *out, i_color *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3 && out->channel[ch] > in->channel[ch])
+        in->channel[ch] = out->channel[ch];
+    } 
+    COMBINE(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_lightenf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  int ch;
+
+  while (count--) {
+    for (ch = 0; ch < channels; ++ch) { 
+      if (ch != 3 && out->channel[ch] > in->channel[ch])
+        in->channel[ch] = out->channel[ch];
+    } 
+    COMBINEF(*out, *in, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_hue(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    i_color c = *out;
+    i_rgb_to_hsv(&c);
+    i_rgb_to_hsv(in);
+    c.channel[0] = in->channel[0];
+    i_hsv_to_rgb(&c);
+    c.channel[3] = in->channel[3];
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_huef(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  while (count--) {
+    i_fcolor c = *out;
+    i_rgb_to_hsvf(&c);
+    i_rgb_to_hsvf(in);
+    c.channel[0] = in->channel[0];
+    i_hsv_to_rgbf(&c);
+    c.channel[3] = in->channel[3];
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_sat(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    i_color c = *out;
+    i_rgb_to_hsv(&c);
+    i_rgb_to_hsv(in);
+    c.channel[1] = in->channel[1];
+    i_hsv_to_rgb(&c);
+    c.channel[3] = in->channel[3];
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_satf(i_fcolor *out, i_fcolor *in, int channels, int count) {
+  while (count--) {
+    i_fcolor c = *out;
+    i_rgb_to_hsvf(&c);
+    i_rgb_to_hsvf(in);
+    c.channel[1] = in->channel[1];
+    i_hsv_to_rgbf(&c);
+    c.channel[3] = in->channel[3];
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_value(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    i_color c = *out;
+    i_rgb_to_hsv(&c);
+    i_rgb_to_hsv(in);
+    c.channel[2] = in->channel[2];
+    i_hsv_to_rgb(&c);
+    c.channel[3] = in->channel[3];
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_valuef(i_fcolor *out, i_fcolor *in, int channels, 
+                           int count) {
+  while (count--) {
+    i_fcolor c = *out;
+    i_rgb_to_hsvf(&c);
+    i_rgb_to_hsvf(in);
+    c.channel[2] = in->channel[2];
+    i_hsv_to_rgbf(&c);
+    c.channel[3] = in->channel[3];
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_color(i_color *out, i_color *in, int channels, int count) {
+  while (count--) {
+    i_color c = *out;
+    i_rgb_to_hsv(&c);
+    i_rgb_to_hsv(in);
+    c.channel[0] = in->channel[0];
+    c.channel[1] = in->channel[1];
+    i_hsv_to_rgb(&c);
+    c.channel[3] = in->channel[3];
+    COMBINE(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+static void combine_colorf(i_fcolor *out, i_fcolor *in, int channels, 
+                           int count) {
+  while (count--) {
+    i_fcolor c = *out;
+    i_rgb_to_hsvf(&c);
+    i_rgb_to_hsvf(in);
+    c.channel[0] = in->channel[0];
+    c.channel[1] = in->channel[1];
+    i_hsv_to_rgbf(&c);
+    c.channel[3] = in->channel[3];
+    COMBINEF(*out, c, channels);
+    ++out;
+    ++in;
+  }
+}
+
+
+/*
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=cut
+*/