support for generic fills for box and arc, with solid, hatched
authorTony Cook <tony@develop=help.com>
Sat, 1 Sep 2001 16:06:33 +0000 (16:06 +0000)
committerTony Cook <tony@develop=help.com>
Sat, 1 Sep 2001 16:06:33 +0000 (16:06 +0000)
and fountain fills implemented

16 files changed:
Changes
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
TODO
draw.c
fills.c [new file with mode: 0644]
filters.c
image.h
io.c
lib/Imager/Fill.pm [new file with mode: 0644]
samples/hatches.pl [new file with mode: 0644]
t/t20fill.t [new file with mode: 0644]
t/t61filters.t
typemap

diff --git a/Changes b/Changes
index 1ed0604..745eee1 100644 (file)
--- a/Changes
+++ b/Changes
@@ -491,6 +491,13 @@ Revision history for Perl extension Imager.
           - OO interface and documentation
           - Imager::Fountain for building/loading fill definitions
           - named value translation for filters
+        - added a generic fill mechanism
+          - created versions of i_box() and i_arc() that can fill using
+            generic fills
+          - solid generic fills (with alpha blending if asked for)
+          - hatched generic fills (with some options)
+          - fountain generic fills
+          - sample code to generate an examples page
 
 =================================================================
 
index 46b636f..ca368ae 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1602,8 +1602,22 @@ sub box {
     $opts{'ymax'} = max($opts{'box'}->[1],$opts{'box'}->[3]);
   }
 
-  if ($opts{filled}) { i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
-  else { i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
+  if ($opts{filled}) { 
+    i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+                 $opts{ymax},$opts{color}); 
+  }
+  elsif ($opts{fill}) {
+    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+      # assume it's a hash ref
+      require 'Imager/Fill.pm';
+      $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+    }
+    i_box_cfill($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+                $opts{ymax},$opts{fill}{fill});
+  }
+  else { 
+    i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color});
+  }
   return $self;
 }
 
@@ -1618,7 +1632,20 @@ sub arc {
            'x'=>$self->getwidth()/2,
            'y'=>$self->getheight()/2,
            'd1'=>0, 'd2'=>361, @_);
-  i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},$opts{'d2'},$opts{'color'}); 
+  if ($opts{fill}) {
+    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+      # assume it's a hash ref
+      require 'Imager/Fill.pm';
+      $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+    }
+    i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+                $opts{'d2'}, $opts{fill}{fill});
+  }
+  else {
+    i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+          $opts{'d2'},$opts{'color'}); 
+  }
+
   return $self;
 }
 
@@ -2731,6 +2758,18 @@ Arc:
 This creates a filled red arc with a 'center' at (200, 100) and spans
 10 degrees and the slice has a radius of 20. SEE section on BUGS.
 
+Both the arc() and box() methods can take a C<fill> parameter which
+can either be an Imager::Fill object, or a reference to a hash
+containing the parameters used to create the fill:
+
+  $img->box(xmin=>10, ymin=>30, xmax=>150, ymax=>60,
+            fill => { hatch=>'cross2' });
+  use Imager::Fill;
+  my $fill = Imager::Fill->new(hatch=>'stipple');
+  $img->box(fill=>$fill);
+
+See L<Imager::Fill> for the type of fills you can use.
+
 Circle:
   $img->circle(color=>$green, r=50, x=>200, y=>100);
 
index 0217966..2a48bfa 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -312,7 +312,6 @@ static void handle_quant_opts(i_quantize *quant, HV *hv)
 /* look through the hash for options to add to opts */
 static void handle_gif_opts(i_gif_opts *opts, HV *hv)
 {
-  /*** FIXME: POSSIBLY BROKEN: do I need to unref the SV from hv_fetch? ***/
   SV **sv;
   int i;
   /**((char *)0) = '\0';*/
@@ -413,6 +412,82 @@ static void copy_colors_back(HV *hv, i_quantize *quant) {
   }
 }
 
+/* loads the segments of a fountain fill into an array */
+i_fountain_seg *load_fount_segs(AV *asegs, int *count) {
+  /* Each element of segs must contain:
+     [ start, middle, end, c0, c1, segtype, colortrans ]
+     start, middle, end are doubles from 0 to 1
+     c0, c1 are Imager::Color::Float or Imager::Color objects
+     segtype, colortrans are ints
+  */
+  int i, j;
+  AV *aseg;
+  SV *sv;
+  i_fountain_seg *segs;
+  double work[3];
+  int worki[2];
+
+  *count = av_len(asegs)+1;
+  if (*count < 1) 
+    croak("i_fountain must have at least one segment");
+  segs = mymalloc(sizeof(i_fountain_seg) * *count);
+  for(i = 0; i < *count; i++) {
+    SV **sv1 = av_fetch(asegs, i, 0);
+    if (!sv1 || !*sv1 || !SvROK(*sv1) 
+        || SvTYPE(SvRV(*sv1)) != SVt_PVAV) {
+      myfree(segs);
+      croak("i_fountain: segs must be an arrayref of arrayrefs");
+    }
+    aseg = (AV *)SvRV(*sv1);
+    if (av_len(aseg) != 7-1) {
+      myfree(segs);
+      croak("i_fountain: a segment must have 7 members");
+    }
+    for (j = 0; j < 3; ++j) {
+      SV **sv2 = av_fetch(aseg, j, 0);
+      if (!sv2 || !*sv2) {
+        myfree(segs);
+        croak("i_fountain: XS error");
+      }
+      work[j] = SvNV(*sv2);
+    }
+    segs[i].start  = work[0];
+    segs[i].middle = work[1];
+    segs[i].end    = work[2];
+    for (j = 0; j < 2; ++j) {
+      SV **sv3 = av_fetch(aseg, 3+j, 0);
+      if (!sv3 || !*sv3 || !SvROK(*sv3) ||
+          (!sv_derived_from(*sv3, "Imager::Color")
+           && !sv_derived_from(*sv3, "Imager::Color::Float"))) {
+        myfree(segs);
+        croak("i_fountain: segs must contain colors in elements 3 and 4");
+      }
+      if (sv_derived_from(*sv3, "Imager::Color::Float")) {
+        segs[i].c[j] = *(i_fcolor *)SvIV((SV *)SvRV(*sv3));
+      }
+      else {
+        i_color c = *(i_color *)SvIV((SV *)SvRV(*sv3));
+        int ch;
+        for (ch = 0; ch < MAXCHANNELS; ++ch) {
+          segs[i].c[j].channel[ch] = c.channel[ch] / 255.0;
+        }
+      }
+    }
+    for (j = 0; j < 2; ++j) {
+      SV **sv2 = av_fetch(aseg, j+5, 0);
+      if (!sv2 || !*sv2) {
+        myfree(segs);
+        croak("i_fountain: XS error");
+      }
+      worki[j] = SvIV(*sv2);
+    }
+    segs[i].type = worki[0];
+    segs[i].color = worki[1];
+  }
+
+  return segs;
+}
+
 /* I don't think ICLF_* names belong at the C interface
    this makes the XS code think we have them, to let us avoid 
    putting function bodies in the XS code
@@ -420,6 +495,13 @@ static void copy_colors_back(HV *hv, i_quantize *quant) {
 #define ICLF_new_internal(r, g, b, a) i_fcolor_new((r), (g), (b), (a))
 #define ICLF_DESTROY(cl) i_fcolor_destroy(cl)
 
+/* for the fill objects
+   Since a fill object may later have dependent images, (or fills!)
+   we need perl wrappers - oh well
+*/
+#define IFILL_DESTROY(fill) i_fill_destroy(fill);
+typedef i_fill_t* Imager__FillHandle;
+
 MODULE = Imager                PACKAGE = Imager::Color PREFIX = ICL_
 
 Imager::Color
@@ -667,6 +749,15 @@ i_box_filled(im,x1,y1,x2,y2,val)
               int     y2
           Imager::Color    val
 
+void
+i_box_cfill(im,x1,y1,x2,y2,fill)
+    Imager::ImgRaw     im
+              int     x1
+              int     y1
+              int     x2
+              int     y2
+          Imager::FillHandle    fill
+
 void
 i_arc(im,x,y,rad,d1,d2,val)
     Imager::ImgRaw     im
@@ -677,6 +768,16 @@ i_arc(im,x,y,rad,d1,d2,val)
              float     d2
           Imager::Color    val
 
+void
+i_arc_cfill(im,x,y,rad,d1,d2,fill)
+    Imager::ImgRaw     im
+              int     x
+              int     y
+             float     rad
+             float     d1
+             float     d2
+          Imager::FillHandle    fill
+
 
 
 void
@@ -1896,87 +1997,46 @@ i_fountain(im, xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_para
                int     super_sample
             double     ssample_param
       PREINIT:
-       int i, j;
         AV *asegs;
-        AV *aseg;
-       SV *sv;
         int count;
         i_fountain_seg *segs;
-        double work[3];
-        int worki[2];
       CODE:
-        /* Each element of segs must contain:
-           [ start, middle, end, c0, c1, segtype, colortrans ]
-             start, middle, end are doubles from 0 to 1
-             c0, c1 are Imager::Color::Float or Imager::Color objects
-             segtype, colortrans are ints
-        */
        if (!SvROK(ST(10)) || ! SvTYPE(SvRV(ST(10))))
            croak("i_fountain: argument 11 must be an array ref");
         
        asegs = (AV *)SvRV(ST(10));
-       
-        count = av_len(asegs)+1;
-       if (count < 1) 
-          croak("i_fountain must have at least one segment");
-        segs = mymalloc(sizeof(i_fountain_seg) * count);
-       for(i = 0; i<count; i++) {
-          SV **sv1 = av_fetch(asegs, i, 0);
-          if (!sv1 || !*sv1 || !SvROK(*sv1) 
-              || SvTYPE(SvRV(*sv1)) != SVt_PVAV) {
-            myfree(segs);
-            croak("i_fountain: segs must be an arrayref of arrayrefs");
-          }
-          aseg = (AV *)SvRV(*sv1);
-          if (av_len(aseg) != 7-1) {
-            myfree(segs);
-            croak("i_fountain: a segment must have 7 members");
-          }
-          for (j = 0; j < 3; ++j) {
-            SV **sv2 = av_fetch(aseg, j, 0);
-            if (!sv2 || !*sv2) {
-              myfree(segs);
-              croak("i_fountain: XS error");
-            }
-            work[j] = SvNV(*sv2);
-          }
-          segs[i].start  = work[0];
-          segs[i].middle = work[1];
-          segs[i].end    = work[2];
-          for (j = 0; j < 2; ++j) {
-            SV **sv3 = av_fetch(aseg, 3+j, 0);
-            if (!sv3 || !*sv3 || !SvROK(*sv3) ||
-                (!sv_derived_from(*sv3, "Imager::Color")
-                 && !sv_derived_from(*sv3, "Imager::Color::Float"))) {
-              myfree(segs);
-              croak("i_fountain: segs must contain colors in elements 3 and 4");
-            }
-            if (sv_derived_from(*sv3, "Imager::Color::Float")) {
-              segs[i].c[j] = *(i_fcolor *)SvIV((SV *)SvRV(*sv3));
-            }
-            else {
-              i_color c = *(i_color *)SvIV((SV *)SvRV(*sv3));
-              int ch;
-              for (ch = 0; ch < MAXCHANNELS; ++ch) {
-                segs[i].c[j].channel[ch] = c.channel[ch] / 255.0;
-              }
-            }
-          }
-          for (j = 0; j < 2; ++j) {
-            SV **sv2 = av_fetch(aseg, j+5, 0);
-            if (!sv2 || !*sv2) {
-              myfree(segs);
-              croak("i_fountain: XS error");
-            }
-            worki[j] = SvIV(*sv2);
-          }
-          segs[i].type = worki[0];
-          segs[i].color = worki[1];
-       }
+        segs = load_fount_segs(asegs, &count);
         i_fountain(im, xa, ya, xb, yb, type, repeat, combine, super_sample, 
                    ssample_param, count, segs);
         myfree(segs);
 
+Imager::FillHandle
+i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, segs)
+            double     xa
+            double     ya
+            double     xb
+            double     yb
+               int     type
+               int     repeat
+               int     combine
+               int     super_sample
+            double     ssample_param
+      PREINIT:
+        AV *asegs;
+        int count;
+        i_fountain_seg *segs;
+      CODE:
+       if (!SvROK(ST(9)) || ! SvTYPE(SvRV(ST(9))))
+           croak("i_fountain: argument 11 must be an array ref");
+        
+       asegs = (AV *)SvRV(ST(9));
+        segs = load_fount_segs(asegs, &count);
+        RETVAL = i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine, 
+                                  super_sample, ssample_param, count, segs);
+        myfree(segs);        
+      OUTPUT:
+        RETVAL
+
 void
 i_errors()
       PREINIT:
@@ -2989,3 +3049,42 @@ ft2_transform_box(font, x0, x1, x2, x3)
         
 #endif
 
+MODULE = Imager         PACKAGE = Imager::FillHandle PREFIX=IFILL_
+
+void
+IFILL_DESTROY(fill)
+        Imager::FillHandle fill
+
+MODULE = Imager         PACKAGE = Imager
+
+Imager::FillHandle
+i_new_fill_solid(cl, combine)
+        Imager::Color cl
+        int combine
+
+Imager::FillHandle
+i_new_fill_solidf(cl, combine)
+        Imager::Color::Float cl
+        int combine
+
+Imager::FillHandle
+i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy)
+        Imager::Color fg
+        Imager::Color 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_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy);
+      OUTPUT:
+        RETVAL
+
index 1710a37..d6eb6ff 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,7 @@ conv.c
 convert.c
 draw.c
 draw.h
+fills.c         Generic fills
 map.c
 error.c
 gaussian.c
index 8b61d14..d144c6d 100644 (file)
@@ -63,7 +63,7 @@ if (defined $Config{'d_dlsymun'}) { $OSDEF  .= ' -DDLSYMUN'; }
           filters.o dynaload.o stackmach.o datatypes.o
           regmach.o trans2.o quant.o error.o convert.o
           map.o tags.o palimg.o maskimg.o img16.o rotate.o
-           bmp.o color.o);
+           bmp.o color.o fills.o);
 
 %opts=(
        'NAME'         => 'Imager',
diff --git a/TODO b/TODO
index d43e25b..9ea18bf 100644 (file)
--- a/TODO
+++ b/TODO
@@ -58,10 +58,7 @@ New Features:
 Clean up:
 - Make sure everything is doable with the OO interface
   i_flood_fill() for example.
-- Split the other classes into seperate files
-  Imager::Font::TT, Imager::Font::T1, currently 
-  an if statement is used to choose what code to
-  run.
+
 - Compile with memory debugging enabled and fix all leaks
 
 - dynaload.c is strongly tied to perl
diff --git a/draw.c b/draw.c
index 1eea3ad..654305b 100644 (file)
--- a/draw.c
+++ b/draw.c
@@ -46,6 +46,45 @@ i_mmarray_render(i_img *im,i_mmarray *ar,i_color *val) {
   for(i=0;i<ar->lines;i++) if (ar->data[i].max!=-1) for(x=ar->data[i].min;x<ar->data[i].max;x++) i_ppix(im,x,i,val);
 }
 
+void
+i_mmarray_render_fill(i_img *im,i_mmarray *ar,i_fill_t *fill) {
+  int x, w, y;
+  if (im->bits == i_8_bits && fill->fill_with_color) {
+    i_color *line = mymalloc(sizeof(i_color) * im->xsize);
+    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) 
+          i_glin(im, x, x+w, y, line);
+        
+        (fill->fill_with_color)(fill, x, y, w, im->channels, line);
+        i_plin(im, x, x+w, y, line);
+      }
+    }
+  
+    myfree(line);
+  }
+  else {
+    i_fcolor *line = 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) 
+          i_glinf(im, x, x+w, y, line);
+        
+        (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line);
+        i_plinf(im, x, x+w, y, line);
+      }
+    }
+  
+    myfree(line);
+  }
+}
+
 
 static
 void
@@ -116,6 +155,35 @@ i_arc(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val) {
   i_mmarray_render(im,&dot,val);
 }
 
+void
+i_arc_cfill(i_img *im,int x,int y,float rad,float d1,float d2,i_fill_t *fill) {
+  i_mmarray dot;
+  float f,fx,fy;
+  int x1,y1;
+
+  mm_log((1,"i_arc_cfill(im* 0x%x,x %d,y %d,rad %.2f,d1 %.2f,d2 %.2f,fill 0x%x)\n",im,x,y,rad,d1,d2,fill));
+
+  i_mmarray_cr(&dot,im->ysize);
+
+  x1=(int)(x+0.5+rad*cos(d1*PI/180.0));
+  y1=(int)(y+0.5+rad*sin(d1*PI/180.0));
+  fx=(float)x1; fy=(float)y1;
+
+  /*  printf("x1: %d.\ny1: %d.\n",x1,y1); */
+  i_arcdraw(x, y, x1, y1, &dot);
+
+  x1=(int)(x+0.5+rad*cos(d2*PI/180.0));
+  y1=(int)(y+0.5+rad*sin(d2*PI/180.0));
+
+  for(f=d1;f<=d2;f+=0.01) i_mmarray_add(&dot,(int)(x+0.5+rad*cos(f*PI/180.0)),(int)(y+0.5+rad*sin(f*PI/180.0)));
+  
+  /*  printf("x1: %d.\ny1: %d.\n",x1,y1); */
+  i_arcdraw(x, y, x1, y1, &dot);
+
+  /*  dot.info(); */
+  i_mmarray_render_fill(im,&dot,fill);
+}
+
 
 
 /* Temporary AA HACK */
@@ -283,6 +351,36 @@ i_box_filled(i_img *im,int x1,int y1,int x2,int y2,i_color *val) {
   for(x=x1;x<x2+1;x++) for (y=y1;y<y2+1;y++) i_ppix(im,x,y,val);
 }
 
+void
+i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) {
+  mm_log((1,"i_box_cfill(im* 0x%x,x1 %d,y1 %d,x2 %d,y2 %d,fill 0x%x)\n",im,x1,y1,x2,y2,fill));
+
+  ++x2;
+  if (im->bits == i_8_bits && fill->fill_with_color) {
+    i_color *line = mymalloc(sizeof(i_color) * (x2 - x1));
+    while (y1 <= y2) {
+      if (fill->combines)
+        i_glin(im, x1, x2, y1, line);
+
+      (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line);
+      i_plin(im, x1, x2, y1, line);
+      ++y1;
+    }
+    myfree(line);
+  }
+  else {
+    i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+    while (y1 <= y2) {
+      if (fill->combines)
+        i_glinf(im, x1, x2, y1, line);
+
+      (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line);
+      i_plinf(im, x1, x2, y1, line);
+      ++y1;
+    }
+    myfree(line);
+  }
+}
 
 void
 i_draw(i_img *im,int x1,int y1,int x2,int y2,i_color *val) {
diff --git a/fills.c b/fills.c
new file mode 100644 (file)
index 0000000..d47f491
--- /dev/null
+++ b/fills.c
@@ -0,0 +1,395 @@
+#include "image.h"
+#include "imagei.h"
+
+/*
+
+Possible fill types:
+ - solid colour
+ - hatched (pattern, fg, bg)
+ - tiled image
+ - regmach
+ - tiling?
+ - generic?
+
+*/
+
+static i_color fcolor_to_color(i_fcolor *c) {
+  int ch;
+  i_color out;
+
+  for (ch = 0; ch < MAXCHANNELS; ++ch)
+    out.channel[ch] = SampleFTo8(c->channel[ch]);
+}
+
+static i_fcolor color_to_fcolor(i_color *c) {
+  int ch;
+  i_color 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;
+
+#define COMBINE(out, in, channels) \
+  { \
+    int ch; \
+    for (ch = 0; ch < (channels); ++ch) { \
+      (out).channel[ch] = ((out).channel[ch] * (255 - (in).channel[3]) \
+        + (in).channel[ch] * (in).channel[3]) / 255; \
+    } \
+  }
+
+#define COMBINEF(out, in, channels) \
+  { \
+    int ch; \
+    for (ch = 0; ch < (channels); ++ch) { \
+      (out).channel[ch] = (out).channel[ch] * (1.0 - (in).channel[3]) \
+        + (in).channel[ch] * (in).channel[3]; \
+    } \
+  }
+
+static void fill_solid(i_fill_t *, int x, int y, int width, int channels, 
+                       i_color *);
+static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, 
+                        i_fcolor *);
+static void fill_solid_comb(i_fill_t *, int x, int y, int width, int channels, 
+                            i_color *);
+static void fill_solidf_comb(i_fill_t *, int x, int y, int width, 
+                             int channels, i_fcolor *);
+
+static i_fill_solid_t base_solid_fill =
+{
+  {
+    fill_solid,
+    fill_solidf,
+    NULL,
+    0
+  },
+};
+static i_fill_solid_t base_solid_fill_comb =
+{
+  {
+    fill_solid_comb,
+    fill_solidf_comb,
+    NULL,
+    1
+  },
+};
+
+void
+i_fill_destroy(i_fill_t *fill) {
+  if (fill->destroy)
+    (fill->destroy)(fill);
+  myfree(fill);
+}
+
+i_fill_t *
+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)
+    *fill = base_solid_fill_comb;
+  else
+    *fill = base_solid_fill;
+  fill->fc = *c;
+  for (ch = 0; ch < MAXCHANNELS; ++ch) {
+    fill->c.channel[ch] = SampleFTo8(c->channel[ch]);
+  }
+  
+  return &fill->base;
+}
+
+i_fill_t *
+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)
+    *fill = base_solid_fill_comb;
+  else
+    *fill = base_solid_fill;
+  fill->c = *c;
+  for (ch = 0; ch < MAXCHANNELS; ++ch) {
+    fill->fc.channel[ch] = Sample8ToF(c->channel[ch]);
+  }
+  
+  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] =
+{
+  {
+    /* 1x1 checkerboard */
+    0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55,
+  },
+  {
+    /* 2x2 checkerboard */
+    0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33,
+  },
+  {
+    /* 4 x 4 checkerboard */
+    0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F,
+  },
+  {
+    /* single vertical lines */
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+  },
+  {
+    /* double vertical lines */
+    0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 
+  },
+  {
+    /* quad vertical lines */
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+  },
+  {
+    /* single hlines */
+    0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  },
+  {
+    /* double hlines */
+    0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
+  },
+  {
+    /* quad hlines */
+    0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+  },
+  {
+    /* single / */
+    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+  },
+  {
+    /* single \ */
+    0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
+  },
+  {
+    /* double / */
+    0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88,
+  },
+  {
+    /* double \ */
+    0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11,
+  },
+  {
+    /* single grid */
+    0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+  },
+  {
+    /* double grid */
+    0xFF, 0x88, 0x88, 0x88, 0xFF, 0x88, 0x88, 0x88,
+  },
+  {
+    /* quad grid */
+    0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA,
+  },
+  {
+    /* single dots */
+    0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  },
+  {
+    /* 4 dots */
+    0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
+  },
+  {
+    /* 16 dots */
+    0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00,
+  },
+  {
+    /* simple stipple */
+    0x48, 0x84, 0x00, 0x00, 0x84, 0x48, 0x00, 0x00,
+  },
+  {
+    /* weave */
+    0x55, 0xFD, 0x05, 0xFD, 0x55, 0xDF, 0x50, 0xDF,
+  },
+  {
+    /* single cross hatch */
+    0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x01,
+  },
+  {
+    /* double cross hatch */
+    0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11,
+  },
+  {
+    /* vertical lozenge */
+    0x11, 0x11, 0x11, 0xAA, 0x44, 0x44, 0x44, 0xAA,
+  },
+  {
+    /* horizontal lozenge */
+    0x88, 0x70, 0x88, 0x07, 0x88, 0x70, 0x88, 0x07,
+  },
+  {
+    /* scales overlapping downwards */
+    0x77, 0x22, 0x22, 0x22, 0xDD, 0x88, 0x88, 0x88,
+  },
+  {
+    /* scales overlapping upwards */
+    0x22, 0x22, 0x22, 0x77, 0x88, 0x88, 0x88, 0xDD,
+  },
+  {
+    /* scales overlapping leftwards */
+    0xF0, 0x11, 0x0F, 0x11, 0xF0, 0x11, 0x0F, 0x11,
+  },
+  {
+    /* scales overlapping rightwards */
+    0x88, 0xF0, 0x88, 0x0F, 0x88, 0xF0, 0x88, 0x0F,
+  },
+  {
+    /* denser stipple */
+    0x44, 0x88, 0x22, 0x11, 0x44, 0x88, 0x22, 0x11,
+  },
+  {
+    /* L-shaped tiles */
+    0xFF, 0x84, 0x84, 0x9C, 0x94, 0x9C, 0x90, 0x90,
+  },
+};
+
+typedef struct
+{
+  i_fill_t base;
+  i_color fg, bg;
+  i_fcolor ffg, fbg;
+  unsigned char hatch[8];
+  int dx, dy;
+} i_fill_hatch_t;
+
+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(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, 
+                int combine, int hatch, unsigned char *cust_hatch,
+                int dx, int dy) {
+  i_fill_hatch_t *fill = mymalloc(sizeof(i_fill_hatch_t));
+
+  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);
+  if (cust_hatch) {
+    memcpy(fill->hatch, cust_hatch, 8);
+  }
+  else {
+    if (hatch > sizeof(builtin_hatches)/sizeof(*builtin_hatches)) 
+      hatch = 0;
+    memcpy(fill->hatch, builtin_hatches[hatch], 8);
+  }
+  fill->dx = dx & 7;
+  fill->dy = dy & 7;
+
+  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);
+}
+
+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);
+}
+
+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;
+  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 (f->base.combines) {
+      COMBINE(*data, c, channels);
+    }
+    else {
+      *data = c;
+    }
+    ++data;
+    if ((mask >>= 1) == 0)
+      mask = 128;
+  }
+}
+
+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;
+  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 (f->base.combines) {
+      COMBINE(*data, c, channels);
+    }
+    else {
+      *data = c;
+    }
+    ++data;
+    if ((mask >>= 1) == 0)
+      mask = 128;
+  }
+}
index 4ee3987..fc1a531 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -918,22 +918,7 @@ i_nearest_color(i_img *im, int num, int *xo, int *yo, i_color *oval, int dmeasur
   i_nearest_color_foo(im, num, xo, yo, ival, dmeasure);
 }
 
-/*
-  Keep state information used by each type of fountain fill
-*/
-struct fount_state {
-  /* precalculated for the equation of the line perpendicular to the line AB */
-  double lA, lB, lC;
-  double AB;
-  double sqrtA2B2;
-  double mult;
-  double cos;
-  double sin;
-  double theta;
-  int xa, ya;
-  void *ssample_data;
-};
-
+struct fount_state;
 static double linear_fount_f(double x, double y, struct fount_state *state);
 static double bilinear_fount_f(double x, double y, struct fount_state *state);
 static double radial_fount_f(double x, double y, struct fount_state *state);
@@ -993,22 +978,14 @@ static fount_repeat fount_repeats[] =
   fount_r_tri_both,
 };
 
-static int simple_ssample(i_fcolor *out, double parm, double x, double y, 
-                           struct fount_state *state, 
-                           fount_func ffunc, fount_repeat rpfunc,
-                           i_fountain_seg *segs, int count);
-static int random_ssample(i_fcolor *out, double parm, double x, double y, 
-                           struct fount_state *state, 
-                           fount_func ffunc, fount_repeat rpfunc,
-                           i_fountain_seg *segs, int count);
-static int circle_ssample(i_fcolor *out, double parm, double x, double y, 
-                           struct fount_state *state, 
-                           fount_func ffunc, fount_repeat rpfunc,
-                           i_fountain_seg *segs, int count);
-typedef int (*fount_ssample)(i_fcolor *out, double parm, double x, double y, 
-                              struct fount_state *state,
-                              fount_func ffunc, fount_repeat rpfunc,
-                              i_fountain_seg *segs, int count);
+static int simple_ssample(i_fcolor *out, double x, double y, 
+                           struct fount_state *state);
+static int random_ssample(i_fcolor *out, double x, double y, 
+                           struct fount_state *state);
+static int circle_ssample(i_fcolor *out, double x, double y, 
+                           struct fount_state *state);
+typedef int (*fount_ssample)(i_fcolor *out, double x, double y, 
+                              struct fount_state *state);
 static fount_ssample fount_ssamples[] =
 {
   NULL,
@@ -1018,9 +995,38 @@ static fount_ssample fount_ssamples[] =
 };
 
 static int
-fount_getat(i_fcolor *out, double x, double y, fount_func ffunc, 
-            fount_repeat rpfunc, struct fount_state *state,
-            i_fountain_seg *segs, int count);
+fount_getat(i_fcolor *out, double x, double y, struct fount_state *state);
+
+/*
+  Keep state information used by each type of fountain fill
+*/
+struct fount_state {
+  /* precalculated for the equation of the line perpendicular to the line AB */
+  double lA, lB, lC;
+  double AB;
+  double sqrtA2B2;
+  double mult;
+  double cos;
+  double sin;
+  double theta;
+  int xa, ya;
+  void *ssample_data;
+  fount_func ffunc;
+  fount_repeat rpfunc;
+  fount_ssample ssfunc;
+  double parm;
+  i_fountain_seg *segs;
+  int count;
+};
+
+static void
+fount_init_state(struct fount_state *state, double xa, double ya, 
+                 double xb, double yb, i_fountain_type type, 
+                 i_fountain_repeat repeat, int combine, int super_sample, 
+                 double ssample_param, int count, i_fountain_seg *segs);
+
+static void
+fount_finish_state(struct fount_state *state);
 
 #define EPSILON (1e-6)
 
@@ -1135,16 +1141,101 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb,
            int combine, int super_sample, double ssample_param, 
            int count, i_fountain_seg *segs) {
   struct fount_state state;
-  fount_func ffunc;
-  fount_ssample ssfunc;
-  fount_repeat rpfunc;
   int x, y;
   i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
+  int ch;
+  i_fountain_seg *my_segs;
+
+  fount_init_state(&state, xa, ya, xb, yb, type, repeat, combine, 
+                   super_sample, ssample_param, count, segs);
+  my_segs = state.segs;
+
+  for (y = 0; y < im->ysize; ++y) {
+    i_glinf(im, 0, im->xsize, y, line);
+    for (x = 0; x < im->xsize; ++x) {
+      i_fcolor c;
+      int got_one;
+      double v;
+      if (super_sample == i_fts_none)
+        got_one = fount_getat(&c, x, y, &state);
+      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];
+          }
+        }
+        else 
+          line[x] = c;
+      }
+    }
+    i_plinf(im, 0, im->xsize, y, line);
+  }
+  fount_finish_state(&state);
+  myfree(line);
+}
+
+typedef struct {
+  i_fill_t base;
+  struct fount_state state;
+} i_fill_fountain_t;
+
+static void
+fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
+            i_fcolor *data);
+static void
+fount_fill_destroy(i_fill_t *fill);
+
+/*
+=item i_new_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs)
+
+=cut
+*/
+
+i_fill_t *
+i_new_fill_fount(double xa, double ya, double xb, double yb, 
+                 i_fountain_type type, i_fountain_repeat repeat, 
+                 int combine, int super_sample, double ssample_param, 
+                 int count, i_fountain_seg *segs) {
+  i_fill_fountain_t *fill = mymalloc(sizeof(i_fill_fountain_t));
+  
+  fill->base.fill_with_color = NULL;
+  fill->base.fill_with_fcolor = fill_fountf;
+  fill->base.destroy = fount_fill_destroy;
+  fill->base.combines = combine;
+  fount_init_state(&fill->state, xa, ya, xb, yb, type, repeat, combine, 
+                   super_sample, ssample_param, count, segs);
+
+  return &fill->base;
+}
+
+/*
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=over
+
+=item fount_init_state(...)
+
+Used by both the fountain fill filter and the fountain fill.
+
+=cut
+*/
+
+static void
+fount_init_state(struct fount_state *state, double xa, double ya, 
+                 double xb, double yb, i_fountain_type type, 
+                 i_fountain_repeat repeat, int combine, int super_sample, 
+                 double ssample_param, int count, i_fountain_seg *segs) {
   int i, j;
   i_fountain_seg *my_segs = mymalloc(sizeof(i_fountain_seg) * count);
-  int have_alpha = im->channels == 2 || im->channels == 4;
+  /*int have_alpha = im->channels == 2 || im->channels == 4;*/
   int ch;
-
+  
+  memset(state, 0, sizeof(*state));
   /* we keep a local copy that we can adjust for speed */
   for (i = 0; i < count; ++i) {
     i_fountain_seg *seg = my_segs + i;
@@ -1178,103 +1269,78 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb,
 
   /* initialize each engine */
   /* these are so common ... */
-  state.lA = xb - xa;
-  state.lB = yb - ya;
-  state.AB = sqrt(state.lA * state.lA + state.lB * state.lB);
-  state.xa = xa;
-  state.ya = ya;
+  state->lA = xb - xa;
+  state->lB = yb - ya;
+  state->AB = sqrt(state->lA * state->lA + state->lB * state->lB);
+  state->xa = xa;
+  state->ya = ya;
   switch (type) {
   default:
     type = i_ft_linear; /* make the invalid value valid */
   case i_ft_linear:
   case i_ft_bilinear:
-    state.lC = ya * ya - ya * yb + xa * xa - xa * xb;
-    state.mult = 1;
-    state.mult = 1/linear_fount_f(xb, yb, &state);
+    state->lC = ya * ya - ya * yb + xa * xa - xa * xb;
+    state->mult = 1;
+    state->mult = 1/linear_fount_f(xb, yb, state);
     break;
 
   case i_ft_radial:
-    state.mult = 1.0 / sqrt((double)(xb-xa)*(xb-xa) 
-                            + (double)(yb-ya)*(yb-ya));
+    state->mult = 1.0 / sqrt((double)(xb-xa)*(xb-xa) 
+                             + (double)(yb-ya)*(yb-ya));
     break;
 
   case i_ft_radial_square:
-    state.cos = state.lA / state.AB;
-    state.sin = state.lB / state.AB;
-    state.mult = 1.0 / state.AB;
+    state->cos = state->lA / state->AB;
+    state->sin = state->lB / state->AB;
+    state->mult = 1.0 / state->AB;
     break;
 
   case i_ft_revolution:
-    state.theta = atan2(yb-ya, xb-xa);
-    state.mult = 1.0 / (PI * 2);
+    state->theta = atan2(yb-ya, xb-xa);
+    state->mult = 1.0 / (PI * 2);
     break;
 
   case i_ft_conical:
-    state.theta = atan2(yb-ya, xb-xa);
-    state.mult = 1.0 / PI;
+    state->theta = atan2(yb-ya, xb-xa);
+    state->mult = 1.0 / PI;
     break;
   }
-  ffunc = fount_funcs[type];
+  state->ffunc = fount_funcs[type];
   if (super_sample < 0 
       || super_sample >= (sizeof(fount_ssamples)/sizeof(*fount_ssamples))) {
     super_sample = 0;
   }
-  state.ssample_data = NULL;
+  state->ssample_data = NULL;
   switch (super_sample) {
   case i_fts_grid:
     ssample_param = floor(0.5 + sqrt(ssample_param));
-    state.ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param * ssample_param);
+    state->ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param * ssample_param);
     break;
 
   case i_fts_random:
   case i_fts_circle:
     ssample_param = floor(0.5+ssample_param);
-    state.ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param);
+    state->ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param);
     break;
   }
-  ssfunc = fount_ssamples[super_sample];
+  state->parm = ssample_param;
+  state->ssfunc = fount_ssamples[super_sample];
   if (repeat < 0 || repeat >= (sizeof(fount_repeats)/sizeof(*fount_repeats)))
     repeat = 0;
-  rpfunc = fount_repeats[repeat];
-
-  for (y = 0; y < im->ysize; ++y) {
-    i_glinf(im, 0, im->xsize, y, line);
-    for (x = 0; x < im->xsize; ++x) {
-      i_fcolor c;
-      int got_one;
-      double v;
-      if (super_sample == i_fts_none)
-        got_one = fount_getat(&c, x, y, ffunc, rpfunc, &state, my_segs, count);
-      else
-        got_one = ssfunc(&c, ssample_param, x, y, &state, ffunc, rpfunc, 
-                         my_segs, count);
-      if (got_one) {
-        i_fountain_seg *seg = my_segs + i;
-        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];
-          }
-        }
-        else 
-          line[x] = c;
-      }
-    }
-    i_plinf(im, 0, im->xsize, y, line);
-  }
-  myfree(line);
-  myfree(my_segs);
-  if (state.ssample_data)
-    myfree(state.ssample_data);
+  state->rpfunc = fount_repeats[repeat];
+  state->segs = my_segs;
+  state->count = count;
 }
 
-/*
-=back
-
-=head1 INTERNAL FUNCTIONS
+static void
+fount_finish_state(struct fount_state *state) {
+  if (state->ssample_data)
+    myfree(state->ssample_data);
+  myfree(state->segs);
+}
 
-=over
 
+/*
 =item fount_getat(out, x, y, ffunc, rpfunc, state, segs, count)
 
 Evaluates the fountain fill at the given point.
@@ -1288,19 +1354,18 @@ instead, and combine those, but this breaks badly.
 */
 
 static int
-fount_getat(i_fcolor *out, double x, double y, fount_func ffunc, 
-            fount_repeat rpfunc, struct fount_state *state, 
-            i_fountain_seg *segs, int count) {
-  double v = rpfunc(ffunc(x, y, state));
+fount_getat(i_fcolor *out, double x, double y, struct fount_state *state) {
+  double v = (state->rpfunc)((state->ffunc)(x, y, state));
   int i;
 
   i = 0;
-  while (i < count && (v < segs[i].start || v > segs[i].end)) {
+  while (i < state->count 
+         && (v < state->segs[i].start || v > state->segs[i].end)) {
     ++i;
   }
-  if (i < count) {
-    v = (fount_interps[segs[i].type])(v, segs+i);
-    (fount_cinterps[segs[i].color])(out, v, segs+i);
+  if (i < state->count) {
+    v = (fount_interps[state->segs[i].type])(v, state->segs+i);
+    (fount_cinterps[state->segs[i].color])(out, v, state->segs+i);
     return 1;
   }
   else
@@ -1541,13 +1606,10 @@ Simple grid-based super-sampling.
 =cut
 */
 static int
-simple_ssample(i_fcolor *out, double parm, double x, double y, 
-               struct fount_state *state, 
-               fount_func ffunc, fount_repeat rpfunc, i_fountain_seg *segs,
-               int count) {
+simple_ssample(i_fcolor *out, double x, double y, struct fount_state *state) {
   i_fcolor *work = state->ssample_data;
   int dx, dy;
-  int grid = parm;
+  int grid = state->parm;
   double base = -0.5 + 0.5 / grid;
   double step = 1.0 / grid;
   int ch, i;
@@ -1556,8 +1618,7 @@ simple_ssample(i_fcolor *out, double parm, double x, double y,
   for (dx = 0; dx < grid; ++dx) {
     for (dy = 0; dy < grid; ++dy) {
       if (fount_getat(work+samp_count, x + base + step * dx, 
-                      y + base + step * dy, ffunc, rpfunc, state, 
-                      segs, count)) {
+                      y + base + step * dy, state)) {
         ++samp_count;
       }
     }
@@ -1582,19 +1643,16 @@ Random super-sampling.
 =cut
 */
 static int
-random_ssample(i_fcolor *out, double parm, double x, double y, 
-               struct fount_state *state, 
-               fount_func ffunc, fount_repeat rpfunc, i_fountain_seg *segs,
-               int count) {
+random_ssample(i_fcolor *out, double x, double y, 
+               struct fount_state *state) {
   i_fcolor *work = state->ssample_data;
   int i, ch;
-  int maxsamples = parm;
+  int maxsamples = state->parm;
   double rand_scale = 1.0 / RAND_MAX;
   int samp_count = 0;
   for (i = 0; i < maxsamples; ++i) {
     if (fount_getat(work+samp_count, x - 0.5 + rand() * rand_scale, 
-                    y - 0.5 + rand() * rand_scale, ffunc, rpfunc, state, 
-                    segs, count)) {
+                    y - 0.5 + rand() * rand_scale, state)) {
       ++samp_count;
     }
   }
@@ -1622,20 +1680,17 @@ much.
 =cut
  */
 static int
-circle_ssample(i_fcolor *out, double parm, double x, double y, 
-               struct fount_state *state, 
-               fount_func ffunc, fount_repeat rpfunc, i_fountain_seg *segs,
-               int count) {
+circle_ssample(i_fcolor *out, double x, double y, 
+               struct fount_state *state) {
   i_fcolor *work = state->ssample_data;
   int i, ch;
-  int maxsamples = parm;
+  int maxsamples = state->parm;
   double angle = 2 * PI / maxsamples;
   double radius = 0.3; /* semi-random */
   int samp_count = 0;
   for (i = 0; i < maxsamples; ++i) {
     if (fount_getat(work+samp_count, x + radius * cos(angle * i), 
-                    y + radius * sin(angle * i), ffunc, rpfunc, state, 
-                    segs, count)) {
+                    y + radius * sin(angle * i), state)) {
       ++samp_count;
     }
   }
@@ -1725,6 +1780,55 @@ fount_r_tri_both(double v) {
   return v > 1.0 ? 2.0 - v : v;
 }
 
+/*
+=item fill_fountf(fill, x, y, width, channels, data)
+
+The fill function for fountain fills.
+
+=cut
+*/
+static void
+fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, 
+            i_fcolor *data) {
+  i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
+  int ch;
+
+  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;
+    }
+
+    ++x;
+    ++data;
+  }
+}
+
+/*
+=item fount_fill_destroy(fill)
+
+=cut
+*/
+static void
+fount_fill_destroy(i_fill_t *fill) {
+  i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
+  fount_finish_state(&f->state);
+}
+
 /*
 =back
 
diff --git a/image.h b/image.h
index 1bb7cc8..d11436d 100644 (file)
--- a/image.h
+++ b/image.h
@@ -120,15 +120,56 @@ int i_glin_d(i_img *im,int l, int r, int y, i_color *val);
 #define i_img_type(im) ((im)->type)
 #define i_img_bits(im) ((im)->bits)
 
+/* Generic fills */
+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);
+typedef void (*i_fill_with_fcolor_f)
+     (struct i_fill_tag *fill, int x, int y, int width, int channels,
+      i_fcolor *data);
+typedef void (*i_fill_destroy_f)(struct i_fill_tag *fill);
+
+typedef struct i_fill_tag
+{
+  /* called for 8-bit/sample image (and maybe lower) */
+  /* this may be NULL, if so call fill_with_fcolor */
+  i_fill_with_color_f fill_with_color;
+
+  /* called for other sample sizes */
+  /* this must be non-NULL */
+  i_fill_with_fcolor_f fill_with_fcolor;
+
+  /* called if non-NULL to release any extra resources */
+  i_fill_destroy_f destroy;
+
+  /* if non-zero the caller will fill data with the original data
+     from the image */
+  int combines;
+} 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 *
+i_new_fill_hatch(i_color *fg, i_color *bg, int combine, int hatch, 
+                 unsigned char *cust_hatch, int dx, int dy);
+extern 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);
+extern void i_fill_destroy(i_fill_t *fill);
+
 float i_gpix_pch(i_img *im,int x,int y,int ch);
 
 /* functions for drawing primitives */
 
 void i_box         (i_img *im,int x1,int y1,int x2,int y2,i_color *val);
 void i_box_filled  (i_img *im,int x1,int y1,int x2,int y2,i_color *val);
+void i_box_cfill(i_img *im, int x1, int y1, int x2, int y2, i_fill_t *fill);
 void i_draw        (i_img *im,int x1,int y1,int x2,int y2,i_color *val);
 void i_line_aa     (i_img *im,int x1,int y1,int x2,int y2,i_color *val);
 void i_arc         (i_img *im,int x,int y,float rad,float d1,float d2,i_color *val);
+void i_arc_cfill(i_img *im,int x,int y,float rad,float d1,float d2,i_fill_t *fill);
 void i_circle_aa   (i_img *im,float x, float y,float rad,i_color *val);
 void i_copyto      (i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty);
 void i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_color *trans);
@@ -558,6 +599,11 @@ void i_fountain(i_img *im, double xa, double ya, double xb, double yb,
                 i_fountain_type type, i_fountain_repeat repeat, 
                 int combine, int super_sample, double ssample_param,
                 int count, i_fountain_seg *segs);
+extern i_fill_t *
+i_new_fill_fount(double xa, double ya, double xb, double yb, 
+                 i_fountain_type type, i_fountain_repeat repeat, 
+                 int combine, int super_sample, double ssample_param, 
+                 int count, i_fountain_seg *segs);
 
 /* Debug only functions */
 
diff --git a/io.c b/io.c
index 80c6618..bcc3f91 100644 (file)
--- a/io.c
+++ b/io.c
@@ -157,7 +157,7 @@ myfree_file_line(void *p, char *file, int line) {
   }
   
   if (match != 1) {
-    mm_log((1, "myfree_file_line: INCONSISTENT REFCOUNT %d\n", match));
+    mm_log((1, "myfree_file_line: INCONSISTENT REFCOUNT %d at %s (%i)\n", match, file, line));
   }
   
   mm_log((1, "myfree_file_line: freeing address %p\n", pp-UNDRRNVAL));
@@ -178,11 +178,11 @@ void*
 mymalloc(int size) {
   void *buf;
 
-  mm_log((1, "mymalloc(size %d)\n", size));
   if ( (buf = malloc(size)) == NULL ) {
     mm_log((1, "mymalloc: unable to malloc %d\n", size));
     fprintf(stderr,"Unable to malloc.\n"); exit(3);
   }
+  mm_log((1, "mymalloc(size %d) -> %p\n", size, buf));
   return buf;
 }
 
diff --git a/lib/Imager/Fill.pm b/lib/Imager/Fill.pm
new file mode 100644 (file)
index 0000000..bd6a291
--- /dev/null
@@ -0,0 +1,308 @@
+package Imager::Fill;
+
+# 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
+     hline1 hline2 hline4 slash1 slosh1 slash2 slosh2
+     grid1 grid2 grid4 dots1 dots4 dots16 stipple weave cross1 cross2
+     vlozenge hlozenge scalesdown scalesup scalesleft scalesright stipple2
+     tile_L/;
+my %hatch_types;
+@hatch_types{@hatch_types} = 0..$#hatch_types;
+
+sub new {
+  my ($class, %hsh) = @_;
+
+  my $self = bless { }, $class;
+  $hsh{combine} ||= 0;
+  if ($hsh{solid}) {
+    if (UNIVERSAL::isa($hsh{solid}, 'Imager::Color')) {
+      $self->{fill} = Imager::i_new_fill_solid($hsh{solid}, $hsh{combine});
+    }
+    elsif (UNIVERSAL::isa($hsh{colid}, 'Imager::Color::Float')) {
+      $self->{fill} = Imager::i_new_fill_solidf($hsh{solid}, $hsh{combine});
+    }
+    else {
+      $Imager::ERRSTR = "solid isn't a color";
+      return undef;
+    }
+  }
+  elsif (defined $hsh{hatch}) {
+    $hsh{dx} ||= 0;
+    $hsh{dy} ||= 0;
+    $hsh{fg} ||= Imager::Color->new(0, 0, 0);
+    if (ref $hsh{hatch}) {
+      $hsh{cust_hatch} = pack("C8", @{$hsh{hatch}});
+      $hsh{hatch} = 0;
+    }
+    elsif ($hsh{hatch} =~ /\D/) {
+      unless (exists($hatch_types{$hsh{hatch}})) {
+        $Imager::ERRSTR = "Unknown hatch type $hsh{hatch}";
+        return undef;
+      }
+      $hsh{hatch} = $hatch_types{$hsh{hatch}};
+    }
+    if (UNIVERSAL::isa($hsh{fg}, 'Imager::Color')) {
+      $hsh{bg} ||= Imager::Color->new(255, 255, 255);
+      $self->{fill} = 
+        Imager::i_new_fill_hatch($hsh{fg}, $hsh{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);
+      $self->{fill} = 
+        Imager::i_new_fill_hatchf($hsh{fg}, $hsh{bg}, $hsh{combine},
+                                  $hsh{hatch}, $hsh{cust_hatch}, 
+                                  $hsh{dx}, $hsh{dy});
+    }
+    else {
+      $Imager::ERRSTR = "fg isn't a color";
+      return undef;
+    }
+  }
+  elsif (defined $hsh{fountain}) {
+    # make sure we track the filter's defaults
+    my $fount = $Imager::filters{fountain};
+    my $def = $fount->{defaults};
+    my $names = $fount->{names};
+    
+    $hsh{ftype} = $hsh{fountain};
+    # process names of values
+    for my $name (keys %$names) {
+      if (defined $hsh{$name} && exists $names->{$name}{$hsh{$name}}) {
+        $hsh{$name} = $names->{$name}{$hsh{$name}};
+      }
+    }
+    # process defaults
+    %hsh = (%$def, %hsh);
+    my @parms = @{$fount->{callseq}};
+    shift @parms;
+    for my $name (@parms) {
+      unless (defined $hsh{$name}) {
+        $Imager::ERRSTR = 
+          "required parameter '$name' not set for fountain fill";
+        return undef;
+      }
+    }
+
+    $self->{fill} =
+      Imager::i_new_fill_fount($hsh{xa}, $hsh{ya}, $hsh{xb}, $hsh{yb},
+                  $hsh{ftype}, $hsh{repeat}, $hsh{combine}, $hsh{super_sample},
+                  $hsh{ssample_param}, $hsh{segments});
+  }
+  else {
+    $Imager::ERRSTR = "No fill type specified";
+    warn "No fill type!";
+    return undef;
+  }
+
+  $self;
+}
+
+sub hatches {
+  return @hatch_types;
+}
+
+1;
+
+=head1 NAME
+
+  Imager::Fill - general fill types
+
+=head1 SYNOPSIS
+
+  my $fill1 = Imager::Fill->new(solid=>$color, combine=>$combine);
+  my $fill2 = Imager::Fill->new(hatch=>'vline2', fg=>$color1, bg=>$color2,
+                                dx=>$dx, dy=>$dy);
+
+=head1 DESCRIPTION 
+
+Creates fill objects for use by some drawing functions, currently just
+the Imager box() method.
+
+The currently available fills are:
+
+=over
+
+=item *
+
+solid
+
+=item *
+
+hatch
+
+=item
+
+fountain (similar to gradients in paint software)
+
+=back
+
+=head1 Common options
+
+=over
+
+=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.
+
+This this is missing or zero then the target image pixels are simply
+overwritten.
+
+=back
+
+In general colors can be specified as Imager::Color or
+Imager::Color::Float objects.  The fill object will typically store
+both types and convert from one to the other.  If a fill takes 2 color
+objects they should have the same type.
+
+=head2 Solid fills
+
+  my $fill = Imager::Fill->new(solid=>$color, $combine =>$combine)
+
+Creates a solid fill, the only required parameter is C<solid> which
+should be the color to fill with.
+
+=head2 Hatched fills
+
+  my $fill = Imager::Fill->new(hatch=>$type, fg=>$fgcolor, bg=>$bgcolor,
+                               dx=>$dx, $dy=>$dy);
+
+Creates a hatched fill.  You can specify the following keywords:
+
+=over
+
+=item hatch
+
+The type of hatch to perform, this can either be the numeric index of
+the hatch (not recommended), the symbolic name of the hatch, or an
+array of 8 integers which specify the pattern of the hatch.
+
+Hatches are represented as cells 8x8 arrays of bits, which limits their
+complexity.
+
+Current hatch names are:
+
+=over
+
+=item check1x1, check2x2, check4x4
+
+checkerboards at varios sizes
+
+=item vline1, vline2, vline4
+
+1, 2, or 4 vertical lines per cell
+
+=item hline1, hline2, hline4
+
+1, 2, or 4 horizontal lines per cell
+
+=item slash1,  slash2
+
+1 or 2 / lines per cell.
+
+=item slosh1,  slosh2
+
+1 or 2 \ lines per cell
+
+=item grid1,  grid2,  grid4
+
+1, 2, or 4 vertical and horizontal lines per cell
+
+=item dots1, dots4, dots16
+
+1, 4 or 16 dots per cell
+
+=item stipple, stipple2
+
+see the samples
+
+=item weave
+
+I hope this one is obvious.
+
+=item cross1,  cross2
+
+2 densities of crosshatch
+
+=item vlozenge,  hlozenge
+
+something like lozenge tiles
+
+=item scalesdown,  scalesup,  scalesleft,  scalesright
+
+Vaguely like fish scales in each direction.
+
+=item tile_L
+
+L-shaped tiles
+
+=back
+
+=item fg
+
+=item bg
+
+The fg color is rendered where bits are set in the hatch, and the bg
+where they are clear.  If you use a transparent fg or bg, and set
+combine, you can overlay the hatch onto an existing image.
+
+fg defaults to black, bg to white.
+
+=item dx
+
+=item dy
+
+An offset into the hatch cell.  Both default to zero.
+
+=back
+
+You can call Imager::Fill->hatches for a list of hatch names.
+
+=head2 Fountain fills
+
+  my $fill = Imager::Fill->new(fountain=>$ftype, 
+       xa=>$xa, ya=>$ya, xb=>$xb, yb=>$yb, 
+       segment=>$segments, repeat=>$repeat, combine=>$combine, 
+       super_sample=>$super_sample, ssample_param=>$ssample_param);
+
+This fills the given region with a fountain fill.  This is exactly the
+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 FUTURE PLANS
+
+I'm planning on adding the following types of fills:
+
+=over
+
+=item image
+
+tiled image fill
+
+=item checkerboard
+
+combines 2 other fills in a checkerboard
+
+=item combine
+
+combines 2 other fills using the levels of an image
+
+=item regmach
+
+uses the transform2() register machine to create fills
+
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=cut
diff --git a/samples/hatches.pl b/samples/hatches.pl
new file mode 100644 (file)
index 0000000..3efbefa
--- /dev/null
@@ -0,0 +1,86 @@
+#!perl -w
+use strict;
+use Imager ':handy'; # handy functions like NC
+use Imager::Fill;
+use HTML::Entities;
+
+if (!-d 'hatches') {
+  mkdir 'hatches'
+    or die "hatches directory does not exist and could not be created: $!";
+}
+
+open HTML, "> hatches.html"
+  or die "Cannot create hatches.html: $!";
+print HTML <<EOS;
+<HTML><HEAD><TITLE>Imager - Hatched Fills</TITLE></HEAD><BODY BGCOLOR="FFFFFF">
+
+<CENTER><FONT FACE="Helvetica, Arial" SIZE="6" COLOR="CC0000"><B>
+Hatched Fills
+</FONT></B></CENTER>
+<HR WIDTH="65%" NOSHADE>
+<TABLE><TR><TD WIDTH="70%">
+
+<TABLE>
+<TR><TH>Filled area</TH><TH>Close-up</TH><TH>Name</TH></TR>
+EOS
+
+my $red = NC(255, 0, 0);
+my $yellow = NC(255, 255, 0);
+
+# sort of a spiral
+my $custom = [ 0xFF, 0x01, 0x7D, 0x45, 0x5D, 0x41, 0x7F, 0x00 ];
+
+for my $hatch (Imager::Fill->hatches, $custom) {
+  my $area = Imager->new(xsize=>100, ysize=>100);
+  $area->box(xmax=>50, fill => { hatch => $hatch });
+  $area->box(xmin=>50, 
+             fill => { hatch => $hatch,
+                       fg=>$red,
+                       bg=>$yellow });
+  my $name = ref($hatch) ? "custom" : $hatch;
+
+  $area->write(file=>"hatches/area_$name.png")
+    or die "Cannot save hatches/area_$name.png: ",$area->errstr;
+
+  my $subset = $area->crop(width=>20, height=>20);
+  # we use the HTML to zoom up
+  $subset->write(file=>"hatches/zoom_$name.png")
+    or die "Cannot save hatches/zoom_$name.png: ",$subset->errstr;
+
+  print HTML <<EOS;
+<TR>
+  <TD><IMG SRC="hatches/area_$name.png" WIDTH="100" HEIGHT="100" BORDER=1></TD>
+  <TD><IMG SRC="hatches/zoom_$name.png" WIDTH="100" HEIGHT="100" BORDER=1></TD>
+  <TD>$name</TD>
+</TR>
+EOS
+}
+
+print HTML <<EOS;
+</TABLE>
+
+<P>The following code was used to generate this page:</p>
+
+<PRE>
+EOS
+
+open SELF, "< $0"
+  or die "Can't open myself: $!";
+while (<SELF>) {
+  print HTML encode_entities($_);
+}
+close SELF;
+
+print HTML <<EOS;
+</PRE>
+
+<HR WIDTH="75%" NOSHADE ALIGN="LEFT">
+
+Send errors/fixes/suggestions to: <B>tony</B>_at_<B>develop-help.com</B>
+
+</TD></TR></TABLE>
+</BODY>
+</HTML>
+EOS
+
+close HTML;
diff --git a/t/t20fill.t b/t/t20fill.t
new file mode 100644 (file)
index 0000000..5def8d7
--- /dev/null
@@ -0,0 +1,138 @@
+#!perl -w
+use strict;
+
+use Imager ':handy';
+use Imager::Fill;
+use Imager::Color::Float;
+
+Imager::init_log("testout/t20fill.log", 1);
+
+print "1..18\n";
+
+my $blue = NC(0,0,255);
+my $red = NC(255, 0, 0);
+my $redf = Imager::Color::Float->new(1, 0, 0);
+my $rsolid = Imager::i_new_fill_solid($blue, 0);
+ok(1, $rsolid, "building solid fill");
+my $raw1 = Imager::ImgRaw::new(100, 100, 3);
+# use the normal filled box
+Imager::i_box_filled($raw1, 0, 0, 99, 99, $blue);
+my $raw2 = Imager::ImgRaw::new(100, 100, 3);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rsolid);
+ok(2, 1, "drawing with solid fill");
+my $diff = Imager::i_img_diff($raw1, $raw2);
+ok(3, $diff == 0, "solid fill doesn't match");
+Imager::i_box_filled($raw1, 0, 0, 99, 99, $red);
+my $rsolid2 = Imager::i_new_fill_solidf($redf, 0);
+ok(4, $rsolid2, "creating float solid fill");
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rsolid2);
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(5, $diff == 0, "float solid fill doesn't match");
+
+# ok solid still works, let's try a hatch
+# hash1 is a 2x2 checkerboard
+my $rhatcha = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 0, 0);
+my $rhatchb = Imager::i_new_fill_hatch($blue, $red, 0, 1, undef, 2, 0);
+ok(6, $rhatcha && $rhatchb, "can't build hatched fill");
+
+# the offset should make these match
+Imager::i_box_cfill($raw1, 0, 0, 99, 99, $rhatcha);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb);
+ok(7, 1, "filling with hatch");
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(8, $diff == 0, "hatch images different");
+$rhatchb = Imager::i_new_fill_hatch($blue, $red, 0, 1, undef, 4, 6);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb);
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(9, $diff == 0, "hatch images different");
+
+# I guess I was tired when I originally did this - make sure it keeps
+# acting the way it's meant to
+# I had originally expected these to match with the red and blue swapped
+$rhatchb = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 2, 2);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb);
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(10, $diff == 0, "hatch images different");
+
+# this shouldn't match
+$rhatchb = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 1, 1);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb);
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(11, $diff, "hatch images the same!");
+
+# custom hatch
+# the inverse of the 2x2 checkerboard
+my $hatch = pack("C8", 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC);
+my $rcustom = Imager::i_new_fill_hatch($blue, $red, 0, 0, $hatch, 0, 0);
+Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rcustom);
+$diff = Imager::i_img_diff($raw1, $raw2);
+ok(12, !$diff, "custom hatch mismatch");
+
+# test the oo interface
+my $im1 = Imager->new(xsize=>100, ysize=>100);
+my $im2 = Imager->new(xsize=>100, ysize=>100);
+
+my $solid = Imager::Fill->new(solid=>$red);
+ok(13, $solid, "creating oo solid fill");
+ok(14, $solid->{fill}, "bad oo solid fill");
+$im1->box(fill=>$solid);
+$im2->box(filled=>1, color=>$red);
+$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG});
+ok(15, !$diff, "oo solid fill");
+
+my $hatcha = Imager::Fill->new(hatch=>'check2x2');
+my $hatchb = Imager::Fill->new(hatch=>'check2x2', dx=>2);
+$im1->box(fill=>$hatcha);
+$im2->box(fill=>$hatchb);
+# should be different
+$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG});
+ok(16, $diff, "offset checks the same!");
+$hatchb = Imager::Fill->new(hatch=>'check2x2', dx=>2, dy=>2);
+$im2->box(fill=>$hatchb);
+$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG});
+ok(17, !$diff, "offset into similar check should be the same");
+
+# test dymanic build of fill
+$im2->box(fill=>{hatch=>'check2x2', dx=>2, fg=>NC(255,255,255), 
+                 bg=>NC(0,0,0)});
+$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG});
+ok(18, !$diff, "offset and flipped should be the same");
+
+# a simple demo
+my $im = Imager->new(xsize=>200, ysize=>200);
+
+$im->box(xmin=>10, ymin=>10, xmax=>190, ymax=>190,
+         fill=>{ hatch=>'check4x4',
+                 fg=>NC(128, 0, 0),
+                 bg=>NC(128, 64, 0) });
+$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) });
+$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');
+
+sub ok {
+  my ($num, $test, $desc) = @_;
+
+  if ($test) {
+    print "ok $num\n";
+  }
+  else {
+    print "not ok $num # $desc\n";
+  }
+}
+
+# for use during testing
+sub save {
+  my ($im, $name) = @_;
+
+  open FH, "> $name" or die "Cannot create $name: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  Imager::i_writeppm_wiol($im, $io) or die "Cannot save to $name";
+  undef $io;
+  close FH;
+}
index 6e6e350..6aca7e1 100644 (file)
@@ -2,6 +2,7 @@
 use strict;
 use Imager qw(:handy);
 
+Imager::init_log("testout/t61filters.log", 1);
 # meant for testing the filters themselves
 my $imbase = Imager->new;
 $imbase->open(file=>'testout/t104.ppm') or die;
diff --git a/typemap b/typemap
index f8b7a5b..f39ed30 100644 (file)
--- a/typemap
+++ b/typemap
@@ -5,6 +5,7 @@ Imager::ImgRaw          T_PTROBJ
 Imager::TTHandle       T_PTROBJ
 Imager::IO              T_PTROBJ
 Imager::Font::FT2       T_PTROBJ
+Imager::FillHandle      T_PTROBJ
 const char *           T_PV
 float                  T_FLOAT
 float*                 T_ARRAY