improved polygon support
authorTony Cook <tony@develop-help.com>
Sun, 25 Jan 2015 09:51:46 +0000 (20:51 +1100)
committerTony Cook <tony@develop-help.com>
Sun, 25 Jan 2015 09:51:46 +0000 (20:51 +1100)
18 files changed:
Imager.pm
Imager.xs
MANIFEST
apidocs.perl
imager.h
imdatatypes.h
imext.c
imext.h
imexttypes.h
lib/Imager/API.pod
lib/Imager/APIRef.pod
lib/Imager/Draw.pod
polygon.c
t/250-draw/050-polyaa.t
t/250-draw/060-polypoly.t [new file with mode: 0644]
t/450-api/100-inline.t
typemap.local
xt/x20spell.t

index 4ff2d74..4db2139 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -3064,6 +3064,8 @@ sub polygon {
     $self->{ERRSTR} = 'no points array, or x and y arrays.'; return undef;
   }
 
+  my $mode = _first($opts{mode}, 0);
+
   if ($opts{'fill'}) {
     unless (UNIVERSAL::isa($opts{'fill'}, 'Imager::Fill')) {
       # assume it's a hash ref
@@ -3073,8 +3075,8 @@ sub polygon {
         return undef;
       }
     }
-    i_poly_aa_cfill($self->{IMG}, $opts{'x'}, $opts{'y'}, 
-                    $opts{'fill'}{'fill'});
+    i_poly_aa_cfill_m($self->{IMG}, $opts{'x'}, $opts{'y'}, 
+                    $mode, $opts{'fill'}{'fill'});
   }
   else {
     my $color = _color($opts{'color'});
@@ -3082,12 +3084,73 @@ sub polygon {
       $self->{ERRSTR} = $Imager::ERRSTR; 
       return; 
     }
-    i_poly_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $color);
+    i_poly_aa_m($self->{IMG}, $opts{'x'}, $opts{'y'}, $mode, $color);
   }
 
   return $self;
 }
 
+sub polypolygon {
+  my ($self, %opts) = @_;
+
+  $self->_valid_image("polypolygon")
+    or return;
+
+  my $points = $opts{points};
+  $points
+    or return $self->_set_error("polypolygon: missing required points");
+
+  my $mode = _first($opts{mode}, "evenodd");
+
+  if ($opts{filled}) {
+    my $color = _color(_first($opts{color}, [ 0, 0, 0, 0 ]))
+      or return $self->_set_error($Imager::ERRSTR);
+
+    i_poly_poly_aa($self->{IMG}, $points, $mode, $color)
+      or return $self->_set_error($self->_error_as_msg);
+  }
+  elsif ($opts{fill}) {
+    my $fill = $opts{fill};
+    $self->_valid_fill($fill, "polypolygon")
+      or return;
+
+    i_poly_poly_aa_cfill($self->{IMG}, $points, $mode, $fill->{fill})
+      or return $self->_set_error($self->_error_as_msg);
+  }
+  else {
+    my $color = _color(_first($opts{color}, [ 0, 0, 0, 255 ]))
+      or return $self->_set_error($Imager::ERRSTR);
+
+    my $rimg = $self->{IMG};
+
+    if (_first($opts{aa}, 1)) {
+      for my $poly (@$points) {
+       my $xp = $poly->[0];
+       my $yp = $poly->[1];
+       for my $i (0 .. $#$xp - 1) {
+         i_line_aa($rimg, $xp->[$i], $yp->[$i], $xp->[$i+1], $yp->[$i+1],
+                   $color, 0);
+       }
+       i_line_aa($rimg, $xp->[$#$xp], $yp->[$#$yp], $xp->[0], $yp->[0],
+                 $color, 0);
+      }
+    }
+    else {
+      for my $poly (@$points) {
+       my $xp = $poly->[0];
+       my $yp = $poly->[1];
+       for my $i (0 .. $#$xp - 1) {
+         i_line($rimg, $xp->[$i], $yp->[$i], $xp->[$i+1], $yp->[$i+1],
+                $color, 0);
+       }
+       i_line($rimg, $xp->[$#$xp], $yp->[$#$yp], $xp->[0], $yp->[0],
+              $color, 0);
+      }
+    }
+  }
+
+  return $self;
+}
 
 # this the multipoint bezier curve
 # this is here more for testing that actual usage since
@@ -4753,6 +4816,8 @@ polygon() - L<Imager::Draw/polygon()>
 
 polyline() - L<Imager::Draw/polyline()>
 
+polypolygon() - L<Imager::Draw/polypolygon()>
+
 preload() - L<Imager::Files/preload()>
 
 read() - L<Imager::Files/read()> - read a single image from an image file
index 6847b02..5fe6b4d 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -30,6 +30,8 @@ extern "C" {
 
 #include "imperl.h"
 
+#define ARRAY_COUNT(array) (sizeof(array)/sizeof(*array))
+
 /*
 
 Context object management
@@ -109,6 +111,11 @@ typedef struct {
   const i_fsample_t *samples;
 } i_fsample_list;
 
+typedef struct {
+  size_t count;
+  const i_polygon_t *polygons;
+} i_polygon_list;
+
 /*
 
 Allocate memory that will be discarded when mortals are discarded.
@@ -539,7 +546,7 @@ struct value_name {
   char *name;
   int value;
 };
-static int lookup_name(struct value_name *names, int count, char *name, int def_value)
+static int lookup_name(const struct value_name *names, int count, char *name, int def_value)
 {
   int i;
   for (i = 0; i < count; ++i)
@@ -767,6 +774,95 @@ ip_copy_colors_back(pTHX_ HV *hv, i_quantize *quant) {
   }
 }
 
+static struct value_name
+poly_fill_mode_names[] =
+{
+  { "evenodd", i_pfm_evenodd },
+  { "nonzero", i_pfm_nonzero }
+};
+
+static i_poly_fill_mode_t
+S_get_poly_fill_mode(pTHX_ SV *sv) {   
+  if (looks_like_number(sv)) {
+    IV work = SvIV(sv);
+    if (work < (IV)i_pfm_evenodd || work > (IV)i_pfm_nonzero)
+      work = (IV)i_pfm_evenodd;
+    return (i_poly_fill_mode_t)work;
+  }
+  else {
+    return (i_poly_fill_mode_t)lookup_name
+    (poly_fill_mode_names, ARRAY_COUNT(poly_fill_mode_names),
+     SvPV_nolen(sv), i_pfm_evenodd);
+  }
+}
+
+static void
+S_get_polygon_list(pTHX_ i_polygon_list *polys, SV *sv) {
+  AV *av;
+  int i;
+  i_polygon_t *s;
+
+  SvGETMAGIC(sv);
+  if (!SvOK(sv) || !SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV) 
+    croak("polys must be an arrayref");
+
+  av = (AV*)SvRV(sv);
+  polys->count = av_len(av) + 1;
+  if (polys->count < 1)
+    croak("polypolygon: no polygons provided");
+  s = malloc_temp(aTHX_ sizeof(i_polygon_t) * polys->count);
+  for (i = 0; i < polys->count; ++i) {
+    SV **poly_sv = av_fetch(av, i, 0);
+    AV *poly_av;
+    SV **x_sv, **y_sv;
+    AV *x_av, *y_av;
+    double *x_data, *y_data;
+    ssize_t j;
+    ssize_t point_count;
+
+    if (!poly_sv)
+      croak("poly_polygon: nothing found for polygon %d", i);
+    /* needs to be another av */
+    SvGETMAGIC(*poly_sv);
+    if (!SvOK(*poly_sv) || !SvROK(*poly_sv) || SvTYPE(SvRV(*poly_sv)) != SVt_PVAV)
+      croak("poly_polygon: polygon %d isn't an arrayref", i);
+    poly_av = (AV*)SvRV(*poly_sv);
+    /* with two elements */
+    if (av_len(poly_av) != 1)
+      croak("poly_polygon: polygon %d should contain two arrays", i);
+    x_sv = av_fetch(poly_av, 0, 0);
+    y_sv = av_fetch(poly_av, 1, 0);
+    if (!x_sv)
+      croak("poly_polygon: polygon %d has no x elements", i);
+    if (!y_sv)
+      croak("poly_polygon: polygon %d has no y elements", i);
+    SvGETMAGIC(*x_sv);
+    SvGETMAGIC(*y_sv);
+    if (!SvOK(*x_sv) || !SvROK(*x_sv) || SvTYPE(SvRV(*x_sv)) != SVt_PVAV)
+      croak("poly_polygon: polygon %d x elements isn't an array", i);
+    if (!SvOK(*y_sv) || !SvROK(*y_sv) || SvTYPE(SvRV(*y_sv)) != SVt_PVAV)
+      croak("poly_polygon: polygon %d y elements isn't an array", i);
+    x_av = (AV*)SvRV(*x_sv);
+    y_av = (AV*)SvRV(*y_sv);
+    if (av_len(x_av) != av_len(y_av))
+      croak("poly_polygon: polygon %d x and y arrays different lengths", i+1);
+    point_count = av_len(x_av)+1;
+    x_data = malloc_temp(aTHX_ sizeof(double) * point_count * 2);
+    y_data = x_data + point_count;
+
+    for (j = 0; j < point_count; ++j) {
+      SV **x_item_sv = av_fetch(x_av, j, 0);
+      SV **y_item_sv = av_fetch(y_av, j, 0);
+      x_data[j] = x_item_sv ? SvNV(*x_item_sv) : 0;
+      y_data[j] = y_item_sv ? SvNV(*y_item_sv) : 0;
+    }
+    s[i].x = x_data;
+    s[i].y = y_data;
+    s[i].count = point_count;
+  }
+  polys->polygons = s;
+}
+
 /* loads the segments of a fountain fill into an array */
 static i_fountain_seg *
 load_fount_segs(pTHX_ AV *asegs, int *count) {
@@ -1810,10 +1906,11 @@ i_bezier_multi(im,x,y,val)
     i_bezier_multi(im,size_x,x,y,val);
 
 int
-i_poly_aa(im,x,y,val)
+i_poly_aa_m(im,x,y,mode,val)
     Imager::ImgRaw     im
     double *x
     double *y
+    i_poly_fill_mode_t mode
     Imager::Color  val
   PREINIT:
     STRLEN   size_x;
@@ -1821,15 +1918,16 @@ i_poly_aa(im,x,y,val)
   CODE:
     if (size_x != size_y)
       croak("Imager: x and y arrays to i_poly_aa must be equal length\n");
-    RETVAL = i_poly_aa(im, size_x, x, y, val);
+    RETVAL = i_poly_aa_m(im, size_x, x, y, mode, val);
   OUTPUT:
     RETVAL
 
 int
-i_poly_aa_cfill(im, x, y, fill)
+i_poly_aa_cfill_m(im, x, y, mode, fill)
     Imager::ImgRaw     im
     double *x
     double *y
+    i_poly_fill_mode_t mode
     Imager::FillHandle     fill
   PREINIT:
     STRLEN size_x;
@@ -1837,10 +1935,32 @@ i_poly_aa_cfill(im, x, y, fill)
   CODE:
     if (size_x != size_y)
       croak("Imager: x and y arrays to i_poly_aa_cfill must be equal length\n");
-    RETVAL = i_poly_aa_cfill(im, size_x, x, y, fill);
+    RETVAL = i_poly_aa_cfill_m(im, size_x, x, y, mode, fill);
   OUTPUT:
     RETVAL
 
+int
+i_poly_poly_aa(im, polys, mode, color)
+    Imager::ImgRaw     im
+         i_polygon_list polys
+        i_poly_fill_mode_t mode
+        Imager::Color color
+    CODE:
+        RETVAL = i_poly_poly_aa(im, polys.count, polys.polygons, mode, color);
+    OUTPUT:
+        RETVAL
+
+int
+i_poly_poly_aa_cfill(im, polys, mode, fill)
+    Imager::ImgRaw     im
+         i_polygon_list polys
+        i_poly_fill_mode_t mode
+        Imager::FillHandle fill
+    CODE:
+        RETVAL = i_poly_poly_aa_cfill(im, polys.count, polys.polygons, mode, fill);
+    OUTPUT:
+        RETVAL
+
 undef_int
 i_flood_fill(im,seedx,seedy,dcol)
     Imager::ImgRaw     im
index f832eed..c91279a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -339,6 +339,7 @@ t/250-draw/020-flood.t              Flood fill tests
 t/250-draw/030-paste.t         Test the paste() method
 t/250-draw/040-rubthru.t       Test the rubthrough() method
 t/250-draw/050-polyaa.t                polygon()
+t/250-draw/060-polypoly.t      polypolygon()
 t/250-draw/100-fill.t          fills
 t/250-draw/200-compose.t       compose()
 t/300-transform/010-scale.t    scale(), scaleX() and scaleY()
index 1a6ed07..55804e0 100644 (file)
@@ -102,6 +102,12 @@ Imager::APIRef - Imager's C API - reference.
 
   i_color color;
   color.rgba.r = 255; color.rgba.g = 0; color.rgba.b = 255;
+  double x[] = { ... };
+  double y[] = { ... };
+  i_polygon_t poly;
+  poly.count = sizeof(x) / sizeof(*x);
+  poly.x = x;
+  poly.y = y;
 
 EOS
 
index 0d42826..2780286 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -158,6 +158,14 @@ extern i_img *i_matrix_transform_bg(i_img *im, i_img_dim xsize, i_img_dim ysize,
 void i_bezier_multi(i_img *im,int l,const double *x,const double *y,const i_color *val);
 int i_poly_aa     (i_img *im,int l,const double *x,const double *y,const i_color *val);
 int i_poly_aa_cfill(i_img *im,int l,const double *x,const double *y,i_fill_t *fill);
+int i_poly_aa_m     (i_img *im,int l,const double *x,const double *y, i_poly_fill_mode_t mode, const i_color *val);
+int i_poly_aa_cfill_m(i_img *im,int l,const double *x,const double *y, i_poly_fill_mode_t mode, i_fill_t *fill);
+extern int
+i_poly_poly_aa(i_img *im, int count, const i_polygon_t *polys,
+              i_poly_fill_mode_t mode, const i_color *val);
+extern int
+i_poly_poly_aa_cfill(i_img *im, int count, const i_polygon_t *polys,
+                    i_poly_fill_mode_t mode, i_fill_t *fill);
 
 undef_int i_flood_fill  (i_img *im,i_img_dim seedx,i_img_dim seedy, const i_color *dcol);
 undef_int i_flood_cfill(i_img *im, i_img_dim seedx, i_img_dim seedy, i_fill_t *fill);
index 9d5cf74..30590ce 100644 (file)
@@ -442,6 +442,17 @@ enum bounding_box_index_t {
   BOUNDING_BOX_COUNT
 };
 
+typedef struct i_polygon_tag {
+  const double *x;
+  const double *y;
+  size_t count;
+} i_polygon_t;
+
+typedef enum i_poly_fill_mode_tag {
+  i_pfm_evenodd,
+  i_pfm_nonzero
+} i_poly_fill_mode_t;
+
 /* Generic fills */
 struct i_fill_tag;
 
diff --git a/imext.c b/imext.c
index 4471afe..98f2c45 100644 (file)
--- a/imext.c
+++ b/imext.c
@@ -204,7 +204,13 @@ im_ext_funcs imager_function_table =
     i_mutex_unlock,
     im_context_slot_new,
     im_context_slot_set,
-    im_context_slot_get
+    im_context_slot_get,
+
+    /* level 9 */
+    i_poly_poly_aa,
+    i_poly_poly_aa_cfill,
+    i_poly_aa_m,
+    i_poly_aa_cfill_m
   };
 
 /* in general these functions aren't called by Imager internally, but
diff --git a/imext.h b/imext.h
index 6e834b2..6561e9a 100644 (file)
--- a/imext.h
+++ b/imext.h
@@ -155,6 +155,11 @@ extern im_ext_funcs *imager_function_ext_table;
 #define i_flood_fill_border(im, seedx, seedy, dcol, border) ((im_extt->f_i_flood_fill_border)((im), (seedx), (seedy), (dcol), (border)))
 #define i_flood_cfill_border(im, seedx, seedy, fill, border) ((im_extt->f_i_flood_cfill_border)((im), (seedx), (seedy), (fill), (border)))
 
+#define i_poly_aa_m(im, count, x, y, mode, c) ((im_extt->f_i_poly_aa_m)((im), (count), (x), (y), (mode), (c)))
+#define i_poly_aa_cfill_m(im, count, x, y, mode, fill) ((im_extt->f_i_poly_aa_m)((im), (count), (x), (y), (mode), (fill)))
+#define i_poly_poly_aa(im, count, polys, mode, c) ((im_extt->f_i_poly_poly_aa)((im), (count), (polys), (mode), (c)))
+#define i_poly_poly_aa_cfill(im, count, polys, mode, fill) ((im_extt->f_i_poly_poly_aa_cfill)((im), (count), (polys), (mode), (fill)))
+
 #define i_copyto(im, src, x1, y1, x2, y2, tx, ty) \
   ((im_extt->f_i_copyto)((im), (src), (x1), (y1), (x2), (y2), (tx), (ty)))
 #define i_copyto_trans(im, src, x1, y1, x2, y2, tx, ty, trans) \
index b2db7fe..354b211 100644 (file)
@@ -34,7 +34,7 @@
  will result in an increment of IMAGER_API_LEVEL.
 */
 
-#define IMAGER_API_LEVEL 8
+#define IMAGER_API_LEVEL 9
 
 typedef struct {
   int version;
@@ -217,7 +217,7 @@ typedef struct {
   size_t (*f_io_slurp)(i_io_glue_t *ig, unsigned char **c);
   void (*f_io_glue_destroy)(i_io_glue_t *ig);
 
-  /* IMAGER_API_LEVEL 8 functions will be added here */
+  /* IMAGER_API_LEVEL 8 */
   i_img *(*f_im_img_8_new)(im_context_t ctx, i_img_dim xsize, i_img_dim ysize, int channels);
   i_img *(*f_im_img_16_new)(im_context_t ctx, i_img_dim xsize, i_img_dim ysize, int channels);
   i_img *(*f_im_img_double_new)(im_context_t ctx, i_img_dim xsize, i_img_dim ysize, int channels);
@@ -254,6 +254,21 @@ typedef struct {
   im_slot_t (*f_im_context_slot_new)(im_slot_destroy_t);
   int (*f_im_context_slot_set)(im_context_t, im_slot_t, void *);
   void *(*f_im_context_slot_get)(im_context_t, im_slot_t);
+
+  /* IMAGER_API_LEVEL 9 */
+  int (*f_i_poly_poly_aa)(i_img *im, int count, const i_polygon_t *polys,
+                         i_poly_fill_mode_t mode, const i_color *val);
+  int (*f_i_poly_poly_aa_cfill)(i_img *im, int count, const i_polygon_t *polys,
+                               i_poly_fill_mode_t mode, i_fill_t *fill);
+  int (*f_i_poly_aa_m)(i_img *im, int l, const double *x, const double *y,
+                      i_poly_fill_mode_t mode, const i_color *val);
+  int (*f_i_poly_aa_cfill_m)(i_img *im, int l, const double *x, 
+                            const double *y, i_poly_fill_mode_t mode,
+                            i_fill_t *fill);
+
+  /* IMAGER_API_LEVEL 10 functions will be added here */
+
+
 } im_ext_funcs;
 
 #define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
index 15abf65..19f36e3 100644 (file)
@@ -181,6 +181,45 @@ type back to perl.
 
 See L</Context objects> for more information.
 
+=head2 i_polygon_t
+
+Represents a single polygon supplied to i_poly_poly_aa() and
+i_poly_poly_aa_cfill().
+
+This is a structure with 3 members:
+
+=over
+
+=item *
+
+C<x>, C<y> - pointers to the first elements of arrays of doubles that define
+the vertices of the polygon.
+
+=item *
+
+C<count> - the number of values in each of the C<x> and C<y> arrays.
+
+=back
+
+=head2 i_poly_fill_mode_t
+
+An enumerated type of the possible fill modes for polygons:
+
+=over
+
+=item *
+
+C<i_pfm_evenodd> - if areas overlap an odd number of times, they
+are filled, and are otherwise unfilled.
+
+=item *
+
+C<i_pfm_nonzero> - areas that have an unbalanced clockwise and
+anti-clockwise boundary are filled.  This is the same as
+C<WindingRule> for X and C<WINDING> for Win32 GDI.
+
+=back
+
 =head1 Create an XS module using the Imager API
 
 =head2 Foo.pm
index a607dde..89554be 100644 (file)
@@ -12,6 +12,12 @@ Imager::APIRef - Imager's C API - reference.
 
   i_color color;
   color.rgba.r = 255; color.rgba.g = 0; color.rgba.b = 255;
+  double x[] = { ... };
+  double y[] = { ... };
+  i_polygon_t poly;
+  poly.count = sizeof(x) / sizeof(*x);
+  poly.x = x;
+  poly.y = y;
 
 
   # Blit tools
@@ -38,6 +44,10 @@ Imager::APIRef - Imager's C API - reference.
   i_flood_cfill(im, 50, 50, fill);
   i_flood_fill_border(im, 50, 50, &color, &border);
   i_flood_cfill_border(im, 50, 50, fill, border);
+  i_poly_poly_aa(im, 1, &poly, mode, color);
+  i_poly_aa_m(im, count, x, y, mode, color);
+  i_poly_poly_aa_cfill(im, 1, &poly, mode, fill);
+  i_poly_aa_cfill(im, count, x, y, mode, fill);
 
   # Error handling
   im_clear_error(aIMCTX);
@@ -770,6 +780,58 @@ Returns the number of pixels set.
 =for comment
 From: File imext.c
 
+=item i_poly_aa_cfill_m(im, count, x, y, mode, fill)
+
+  i_poly_aa_cfill(im, count, x, y, mode, fill);
+
+Fill a polygon defined by the points specified by the x and y arrays with
+the fill specified by C<fill>.
+
+
+=for comment
+From: File polygon.c
+
+=item i_poly_aa_m(im, count, x, y, mode, color)
+
+  i_poly_aa_m(im, count, x, y, mode, color);
+
+Fill a polygon defined by the points specified by the x and y arrays with
+the color specified by C<color>.
+
+
+=for comment
+From: File polygon.c
+
+=item i_poly_poly_aa(im, count, polys, mode, color)
+
+  i_poly_poly_aa(im, 1, &poly, mode, color);
+
+Fill the C<count> polygons defined by C<polys> the color specified by
+C<color>.
+
+At least one polygon must be supplied.
+
+All polygons must have at least 3 points.
+
+
+=for comment
+From: File polygon.c
+
+=item i_poly_poly_aa_cfill(im, count, polys, mode, fill)
+
+  i_poly_poly_aa_cfill(im, 1, &poly, mode, fill);
+
+Fill the C<count> polygons defined by C<polys> the fill specified by
+C<fill>.
+
+At least one polygon must be supplied.
+
+All polygons must have at least 3 points.
+
+
+=for comment
+From: File polygon.c
+
 =item i_ppal(im, left, right, y, indexes)
 
 
index 057a129..c9b19e5 100644 (file)
@@ -121,6 +121,35 @@ Currently you can create opaque or transparent plain color fills,
 hatched fills, image based fills and fountain fills.  See
 L<Imager::Fill> for more information.
 
+=head2 Polygon Fill Modes
+
+When filling a polygon that overlaps itself, or when filling several
+polygons with polypolygon() that overlap each other, you can supply a
+C<mode> parameter that controls how the overlap is resolved.  This can
+have one of two possible values:
+
+=over
+
+=item *
+
+C<evenodd> - if areas overlap an odd number of times, they are filled,
+and are otherwise unfilled.  This is the default and the historical
+Imager polygon fill mode.
+
+=item *
+
+C<nonzero> - areas that have an unbalanced clockwise and
+anti-clockwise boundary are filled.  This is the same as
+C<WindingRule> for X and C<WINDING> for Win32 GDI.
+
+=back
+
+C<nonzero> allows polygons to overlap, either with itself, or with
+another polygon in the same polypolygon() call, without producing
+unfilled area in the overlap, and also allows areas to be cut out of
+the area by specifying the points making up a cut-out in the opposite
+order.
+
 =head2 List of primitives
 
 =over
@@ -434,8 +463,62 @@ Default: black.  Overridden by C<fill>.
 
 C<fill> - the fill for the filled circle.  See L</"Fill Parameters">
 
+=item *
+
+C<mode> - fill mode for the polygon.  See L</"Polygon Fill Modes">
+
+=back
+
+Note: the points specified are as offsets from the top-left of the
+image, I<not> as pixel locations.  This means that:
+
+  $img->polygon(points => [ [ 0, 0 ], [ 1, 0 ], [ 1, 1 ], [ 0, 1 ] ]);
+
+fills only a single pixel at C<(0, 0)>, not four.
+
+=item polypolygon()
+X<polypolygon() method>X<methods, polypolygon>
+
+  $img->polypolygon(points => $points, color => $color);
+
+Draw multiple polygons, either filled or unfilled.
+
+=over
+
+=item *
+
+C<points> - is an array reference containing polygon definitions, each
+polygon definition is a reference to an array containing two arrays,
+one each for the C<x> and C<y> co-ordinates.
+
+=item *
+
+C<filled> - if true, fill the polygons with the color defined by
+C<color>.
+
+=item *
+
+C<color> - the color to draw the polygons with if C<fill> is not
+supplied.
+
+=item *
+
+C<fill> - fill the polygons with this fill if supplied.
+
+=item *
+
+C<mode> - fill mode for the polygon.  See L</"Polygon Fill Modes">
+
 =back
 
+Note: the points specified are as offsets from the top-left of the
+image, I<not> as pixel locations.  This means that:
+
+  $img->polypolygon(points => [ [ [ 0, 1, 1, 0 ], [ 0, 0, 1, 1 ] ] ],
+                    filled => 1);
+
+fills only a single pixel at C<(0, 0)>, not four.
+
 =item flood_fill()
 
 X<flood_fill>You can fill a region that all has the same color using
index 38c8f5c..58ac8ec 100644 (file)
--- a/polygon.c
+++ b/polygon.c
@@ -1,3 +1,4 @@
+#define IMAGER_NO_CONTEXT
 #include "imager.h"
 #include "draw.h"
 #include "log.h"
 typedef i_img_dim pcord;
 
 typedef struct {
-  int n;
+  size_t n;
   pcord x,y;
 } p_point;
 
 typedef struct {
-  int n;
+  size_t n;
   pcord x1,y1;
   pcord x2,y2;
   pcord miny,maxy;
   pcord minx,maxx;
   int updown; /* -1 means down, 0 vertical, 1 up */
+  int dir; /* 1 for down, -1 for up */
 } p_line;
 
 typedef struct {
-  int n;
+  size_t n;
   double x;
 } p_slice;
 
@@ -81,34 +83,68 @@ p_eval_atx(p_line *l, pcord x) {
 
 static
 p_line *
-line_set_new(const double *x, const double *y, int l) {
-  int i;
-  p_line *lset = mymalloc(sizeof(p_line) * l);
-
-  for(i=0; i<l; i++) {
-    lset[i].n=i;
-    lset[i].x1 = IMTRUNC(x[i]);
-    lset[i].y1 = IMTRUNC(y[i]);
-    lset[i].x2 = IMTRUNC(x[(i+1)%l]);
-    lset[i].y2 = IMTRUNC(y[(i+1)%l]);
-    lset[i].miny=i_min(lset[i].y1,lset[i].y2);
-    lset[i].maxy=i_max(lset[i].y1,lset[i].y2);
-    lset[i].minx=i_min(lset[i].x1,lset[i].x2);
-    lset[i].maxx=i_max(lset[i].x1,lset[i].x2);
+line_set_new(const i_polygon_t *polys, size_t count, size_t *line_count) {
+  size_t i, j, n;
+  p_line *lset, *line;
+  size_t lines = 0;
+  
+  for (i = 0; i < count; ++i)
+    lines += polys[i].count;
+
+  line = lset = mymalloc(sizeof(p_line) * lines);
+
+  n = 0;
+  for (i = 0; i < count; ++i) {
+    const i_polygon_t *p = polys + i;
+
+    for(j = 0; j < p->count; j++) {
+      line->x1 = IMTRUNC(p->x[j]);
+      line->y1 = IMTRUNC(p->y[j]);
+      line->x2 = IMTRUNC(p->x[(j + 1) % p->count]);
+      line->y2 = IMTRUNC(p->y[(j + 1) % p->count]);
+
+      /* don't include purely horizontal lines, we don't usefully
+        intersect with them. */
+      if (line->y1 == line->y2)
+       continue;
+
+      line->miny = i_min(line->y1, line->y2);
+      line->maxy = i_max(line->y1, line->y2);
+      line->minx = i_min(line->x1, line->x2);
+      line->maxx = i_max(line->x1, line->x2);
+      line->n = n++;
+      ++line;
+    }
   }
+  *line_count = n;
+
   return lset;
 }
 
 static
 p_point *
-point_set_new(const double *x, const double *y, int l) {
-  int i;
-  p_point *pset = mymalloc(sizeof(p_point) * l);
+point_set_new(const i_polygon_t *polys, size_t count, size_t *point_count) {
+  size_t i, j, n;
+  p_point *pset, *pt;
+  size_t points = 0;
   
-  for(i=0; i<l; i++) {
-    pset[i].n=i;
-    pset[i].x=IMTRUNC(x[i]);
-    pset[i].y=IMTRUNC(y[i]);
+  for (i = 0; i < count; ++i)
+    points += polys[i].count;
+
+  *point_count = points;
+
+  pt = pset = mymalloc(sizeof(p_point) * points);
+
+  n = 0;
+  for (i = 0; i < count; ++i) {
+    const i_polygon_t *p = polys + i;
+
+    for(j = 0; j < p->count; j++) {
+      pt->n = n++;
+      pt->x = IMTRUNC(p->x[j]);
+      pt->y = IMTRUNC(p->y[j]);
+      ++pt;
+    }
   }
   return pset;
 }
@@ -161,14 +197,14 @@ lines_in_interval(p_line *lset, int l, p_slice *tllist, pcord minc, pcord maxc)
 
 static
 void
-mark_updown_slices(p_line *lset, p_slice *tllist, int count) {
+mark_updown_slices(pIMCTX, p_line *lset, p_slice *tllist, int count) {
   p_line *l, *r;
   int k;
   for(k=0; k<count; k+=2) {
     l = lset + tllist[k].n;
 
     if (l->y1 == l->y2) {
-      mm_log((1, "mark_updown_slices: horizontal line being marked: internal error!\n"));
+      im_log((aIMCTX,1, "mark_updown_slices: horizontal line being marked: internal error!\n"));
       exit(3);
     }
 
@@ -179,19 +215,20 @@ mark_updown_slices(p_line *lset, p_slice *tllist, int count) {
       (l->y1 > l->y2) ? -1 : 1
       : 
       (l->y1 > l->y2) ? 1 : -1;
+    l->dir = l->y1 < l->y2 ? 1 : -1;
 
     POLY_DEB( printf("marking left line %d as %s(%d)\n", l->n,
                     l->updown ?  l->updown == 1 ? "up" : "down" : "vert", l->updown, l->updown)
              );
 
     if (k+1 >= count) {
-      mm_log((1, "Invalid polygon spec, odd number of line crossings.\n"));
+      im_log((aIMCTX, 1, "Invalid polygon spec, odd number of line crossings.\n"));
       return;
     }
 
     r = lset + tllist[k+1].n;
     if (r->y1 == r->y2) {
-      mm_log((1, "mark_updown_slices: horizontal line being marked: internal error!\n"));
+      im_log((aIMCTX, 1, "mark_updown_slices: horizontal line being marked: internal error!\n"));
       exit(3);
     }
 
@@ -202,6 +239,7 @@ mark_updown_slices(p_line *lset, p_slice *tllist, int count) {
       (r->y1 > r->y2) ? -1 : 1
       : 
       (r->y1 > r->y2) ? 1 : -1;
+    r->dir = r->y1 < r->y2 ? 1 : -1;
     
     POLY_DEB( printf("marking right line %d as %s(%d)\n", r->n,
                     r->updown ?  r->updown == 1 ? "up" : "down" : "vert", r->updown, r->updown)
@@ -209,8 +247,6 @@ mark_updown_slices(p_line *lset, p_slice *tllist, int count) {
   }
 }
 
-
-
 static
 unsigned char
 saturate(int in) {
@@ -404,8 +440,10 @@ render_slice_scanline(ss_scanline *ss, int y, p_line *l, p_line *r, pcord miny,
  */
 
 
-static void
-i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, scanline_flusher flusher) {
+static int
+i_poly_poly_aa_low(i_img *im, int count, const i_polygon_t *polys,
+                  i_poly_fill_mode_t mode, void *ctx,
+                  scanline_flusher flusher) {
   int i ,k;                    /* Index variables */
   i_img_dim clc;               /* Lines inside current interval */
   /* initialize to avoid compiler warnings */
@@ -416,11 +454,31 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
   p_point *pset;               /* List of points in polygon */
   p_line  *lset;               /* List of lines in polygon */
   p_slice *tllist;             /* List of slices */
+  size_t pcount, lcount;
+  dIMCTX;
+
+  im_log((aIMCTX, 1, "i_poly_poly_aa_low(im %p, count %d, polys %p, ctx %p, flusher %p)\n", im, count, polys, ctx, flusher));
+
+  i_clear_error();
+
+  if (count < 1) {
+    i_push_error(0, "no polygons to draw");
+    return 0;
+  }
 
-  mm_log((1, "i_poly_aa(im %p, l %d, x %p, y %p, ctx %p, flusher %p)\n", im, l, x, y, ctx, flusher));
+  for (k = 0; k < count; ++k) {
+    if (polys[k].count < 3) {
+      i_push_errorf(0, "polygons must have at least 3 points");
+      return 0;
+    }
+  }
 
-  for(i=0; i<l; i++) {
-    mm_log((2, "(%.2f, %.2f)\n", x[i], y[i]));
+  for (k = 0; k < count; ++k) {
+    const i_polygon_t *p = polys + k;
+    im_log((aIMCTX, 2, "poly %d\n", k));
+    for(i = 0; i < p->count; i++) {
+      im_log((aIMCTX, 2, " (%.2f, %.2f)\n", p->x[i], p->y[i]));
+    }
   }
 
 
@@ -429,18 +487,17 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
           setbuf(stdout, NULL);
           );
 
-  tllist   = mymalloc(sizeof(p_slice)*l);
-  
-  ss_scanline_init(&templine, im->xsize, l);
-
-  pset     = point_set_new(x, y, l);
-  lset     = line_set_new(x, y, l);
+  pset     = point_set_new(polys, count, &pcount);
+  lset     = line_set_new(polys, count, &lcount);
 
+  ss_scanline_init(&templine, im->xsize, lcount);
 
-  qsort(pset, l, sizeof(p_point), (int(*)(const void *,const void *))p_compy);
+  tllist   = mymalloc(sizeof(p_slice) * lcount);
+  
+  qsort(pset, pcount, sizeof(p_point), (int(*)(const void *,const void *))p_compy);
   
   POLY_DEB(
-          for(i=0;i<l;i++) {
+          for(i=0;i<lcount;i++) {
             printf("%d [ %d ] (%d , %d) -> (%d , %d) yspan ( %d , %d )\n",
                    i, lset[i].n, lset[i].x1, lset[i].y1, lset[i].x2, lset[i].y2, lset[i].miny, lset[i].maxy);
           }
@@ -449,7 +506,7 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
   
 
   /* loop on intervals */
-  for(i=0; i<l-1; i++) {
+  for(i=0; i<pcount-1; i++) {
     i_img_dim startscan = i_max( coarse(pset[i].y), 0);
     i_img_dim stopscan = i_min( coarse(pset[i+1].y+15), im->ysize);
     pcord miny, maxy;  /* y bounds in fine coordinates */
@@ -466,10 +523,10 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
       continue;
     }
 
-    clc = lines_in_interval(lset, l, tllist, pset[i].y, pset[i+1].y);
+    clc = lines_in_interval(lset, lcount, tllist, pset[i].y, pset[i+1].y);
     qsort(tllist, clc, sizeof(p_slice), (int(*)(const void *,const void *))p_compx);
 
-    mark_updown_slices(lset, tllist, clc);
+    mark_updown_slices(aIMCTX, lset, tllist, clc);
 
     POLY_DEB
       (
@@ -505,9 +562,28 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
       
       tempy = i_min(cscl*16+16, pset[i+1].y);
       POLY_DEB( printf("evaluating scan line %d \n", cscl) );
-      for(k=0; k<clc-1; k+=2) {
-       POLY_DEB( printf("evaluating slice %d\n", k) );
-       render_slice_scanline(&templine, cscl, lset+tllist[k].n, lset+tllist[k+1].n, scan_miny, scan_maxy);
+      if (mode == i_pfm_evenodd) {
+       for(k=0; k<clc-1; k+=2) {
+         POLY_DEB( printf("evaluating slice %d\n", k) );
+         render_slice_scanline(&templine, cscl, lset+tllist[k].n, lset+tllist[k+1].n, scan_miny, scan_maxy);
+       }
+      }
+      else {
+       k = 0;
+       while (k < clc) {
+         p_line *left = lset + tllist[k++].n;
+         p_line *current;
+         int acc = left->dir;
+
+         while (k < clc && acc) {
+           current = lset + tllist[k++].n;
+           acc += current->dir;
+         }
+         if (acc == 0) {
+           render_slice_scanline(&templine, cscl, left, current,
+                                 scan_miny, scan_maxy);
+         }
+       }
       }
       if (16*coarse(tempy) == tempy) {
        POLY_DEB( printf("flushing scan line %d\n", cscl) );
@@ -530,14 +606,62 @@ i_poly_aa_low(i_img *im, int l, const double *x, const double *y, void *ctx, sca
   myfree(pset);
   myfree(lset);
   myfree(tllist);
-  
-} /* Function */
+
+  return 1;
+}
+
+/*
+=item i_poly_poly_aa(im, count, polys, mode, color)
+=synopsis i_poly_poly_aa(im, 1, &poly, mode, color);
+=category Drawing
+
+Fill the C<count> polygons defined by C<polys> the color specified by
+C<color>.
+
+At least one polygon must be supplied.
+
+All polygons must have at least 3 points.
+
+=cut
+*/
 
 int
-i_poly_aa(i_img *im, int l, const double *x, const double *y, const i_color *val) {
+i_poly_poly_aa(i_img *im, int count, const i_polygon_t *polys,
+              i_poly_fill_mode_t mode, const i_color *val) {
   i_color c = *val;
-  i_poly_aa_low(im, l, x, y, &c, scanline_flush);
-  return 1;
+  return i_poly_poly_aa_low(im, count, polys, mode, &c, scanline_flush);
+}
+
+/*
+=item i_poly_aa_m(im, count, x, y, mode, color)
+=synopsis i_poly_aa_m(im, count, x, y, mode, color);
+=category Drawing
+
+Fill a polygon defined by the points specified by the x and y arrays with
+the color specified by C<color>.
+
+=cut
+*/
+
+int
+i_poly_aa_m(i_img *im, int l, const double *x, const double *y,
+           i_poly_fill_mode_t mode, const i_color *val) {
+  i_polygon_t poly;
+
+  poly.count = l;
+  poly.x = x;
+  poly.y = y;
+  return i_poly_poly_aa(im, 1, &poly, mode, val);
+}
+
+int
+i_poly_aa(i_img *im, int l, const double *x, const double *y, const i_color *val) {
+  i_polygon_t poly;
+
+  poly.count = l;
+  poly.x = x;
+  poly.y = y;
+  return i_poly_poly_aa(im, 1, &poly, i_pfm_evenodd, val);
 }
 
 struct poly_render_state {
@@ -571,16 +695,71 @@ scanline_flush_render(i_img *im, ss_scanline *ss, int y, void *ctx) {
   }
 }
 
+/*
+=item i_poly_poly_aa_cfill(im, count, polys, mode, fill)
+=synopsis i_poly_poly_aa_cfill(im, 1, &poly, mode, fill);
+=category Drawing
+
+Fill the C<count> polygons defined by C<polys> the fill specified by
+C<fill>.
+
+At least one polygon must be supplied.
+
+All polygons must have at least 3 points.
+
+=cut
+*/
+
 int
-i_poly_aa_cfill(i_img *im, int l, const double *x, const double *y, 
-               i_fill_t *fill) {
+i_poly_poly_aa_cfill(i_img *im, int count, const i_polygon_t *polys,
+                    i_poly_fill_mode_t mode, i_fill_t *fill) {
   struct poly_render_state ctx;
+  int result;
 
   i_render_init(&ctx.render, im, im->xsize);
   ctx.fill = fill;
   ctx.cover = mymalloc(im->xsize);
-  i_poly_aa_low(im, l, x, y, &ctx, scanline_flush_render);
+
+  result = i_poly_poly_aa_low(im, count, polys, mode, &ctx,
+                             scanline_flush_render);
+
   myfree(ctx.cover);
   i_render_done(&ctx.render);
-  return 1;
+
+  return result;
+}
+
+/*
+=item i_poly_aa_cfill_m(im, count, x, y, mode, fill)
+=synopsis i_poly_aa_cfill(im, count, x, y, mode, fill);
+=category Drawing
+
+Fill a polygon defined by the points specified by the x and y arrays with
+the fill specified by C<fill>.
+
+=cut
+*/
+
+int
+i_poly_aa_cfill_m(i_img *im, int l, const double *x, const double *y, 
+               i_poly_fill_mode_t mode, i_fill_t *fill) {
+  i_polygon_t poly;
+
+  poly.count = l;
+  poly.x = x;
+  poly.y = y;
+
+  return i_poly_poly_aa_cfill(im, 1, &poly, mode, fill);
+}
+
+int
+i_poly_aa_cfill(i_img *im, int l, const double *x, const double *y, 
+               i_fill_t *fill) {
+  i_polygon_t poly;
+
+  poly.count = l;
+  poly.x = x;
+  poly.y = y;
+
+  return i_poly_poly_aa_cfill(im, 1, &poly, i_pfm_evenodd, fill);
 }
index ace2e64..5992b54 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 
 use strict;
-use Test::More tests => 18;
+use Test::More tests => 24;
 
 use Imager qw/NC/;
 use Imager::Test qw(is_image is_color3);
@@ -10,12 +10,23 @@ sub PI () { 3.14159265358979323846 }
 
 -d "testout" or mkdir "testout";
 
-Imager::init_log("testout/t75aapolyaa.log",1);
+my @out_files;
+
+END {
+  unless ($ENV{IMAGER_KEEP_FILES}) {
+    unlink @out_files;
+    rmdir "testout";
+  }
+}
+
+Imager->open_log(log => "testout/250-polyaa.log");
+push @out_files, "testout/250-polyaa.log";
 
 my $red   = Imager::Color->new(255,0,0);
 my $green = Imager::Color->new(0,255,0);
 my $blue  = Imager::Color->new(0,0,255);
 my $white = Imager::Color->new(255,255,255);
+my $black = Imager::Color->new(0, 0, 0);
 
 { # artifacts with multiple vertical lobes
   # https://rt.cpan.org/Ticket/Display.html?id=43518
@@ -38,7 +49,8 @@ my $white = Imager::Color->new(255,255,255);
   ok($im->polygon(points => \@pts,
                  color => $white),
      "draw with inside point");
-  ok($im->write(file => "testout/t75inside.ppm"), "save to file");
+  ok($im->write(file => "testout/250-poly-inside.ppm"), "save to file");
+  push @out_files, "testout/250-poly-inside.ppm";
   # both scanlines should be the same
   my $line0 = $im->crop(top => 0, height => 1);
   my $line1 = $im->crop(top => 1, height => 1);
@@ -90,14 +102,16 @@ my $white = Imager::Color->new(255,255,255);
   
   
   my ($x, $y) = array_to_refpair(@data);
-  ok(Imager::i_poly_aa($img->{IMG}, $x, $y, $white), "primitive poly");
+  ok(Imager::i_poly_aa_m($img->{IMG}, $x, $y, 0, $white), "primitive poly");
 
-  ok($img->write(file=>"testout/t75.ppm"), "write to file")
+  ok($img->write(file=>"testout/250-poly.ppm"), "write to file")
     or diag $img->errstr;
+  push @out_files, "testout/250-poly.ppm";
 
   my $zoom = make_zoom($img, 8, \@data, $red);
   ok($zoom, "make zoom of primitive");
-  $zoom->write(file=>"testout/t75zoom.ppm") or die $zoom->errstr;
+  $zoom->write(file=>"testout/250-poly-zoom.ppm") or die $zoom->errstr;
+  push @out_files, "testout/250-poly-zoom.ppm";
 }
 
 {
@@ -113,13 +127,14 @@ my $white = Imager::Color->new(255,255,255);
                               )
                        );
     my ($x, $y) = array_to_refpair(@data);
-    Imager::i_poly_aa($img->{IMG}, $x, $y, NC(rand(255), rand(255), rand(255)))
+    Imager::i_poly_aa_m($img->{IMG}, $x, $y, 0, NC(rand(255), rand(255), rand(255)))
        or $good = 0;
   }
   
-  $img->write(file=>"testout/t75big.ppm") or die $img->errstr;
+  $img->write(file=>"testout/250-poly-big.ppm") or die $img->errstr;
 
   ok($good, "primitive squares");
+  push @out_files, "testout/250-poly-big.ppm";
 }
 
 {
@@ -134,7 +149,8 @@ my $white = Imager::Color->new(255,255,255);
                 ), "method call")
     or diag $img->errstr();
 
-  $img->write(file=>"testout/t75wave.ppm") or die $img->errstr;
+  $img->write(file=>"testout/250-poly-wave.ppm") or die $img->errstr;
+  push @out_files, "testout/250-poly-wave.ppm";
 }
 
 {
@@ -152,8 +168,9 @@ my $white = Imager::Color->new(255,255,255);
                 ), "bug check")
     or diag $img->errstr();
 
-  make_zoom($img,20,\@data, $blue)->write(file=>"testout/t75wavebug.ppm") or die $img->errstr;
+  make_zoom($img,20,\@data, $blue)->write(file=>"testout/250-poly-wavebug.ppm") or die $img->errstr;
 
+  push @out_files, "testout/250-poly-wavebug.ppm";
 }
 
 {
@@ -166,7 +183,8 @@ my $white = Imager::Color->new(255,255,255);
                         ],
              ), "poly filled with hatch")
     or diag $img->errstr();
-  $img->write(file=>"testout/t75wave_fill.ppm") or die $img->errstr;
+  $img->write(file=>"testout/250-poly-wave_fill.ppm") or die $img->errstr;
+  push @out_files, "testout/250-poly-wave_fill.ppm";
 }
 
 {
@@ -179,11 +197,61 @@ my $white = Imager::Color->new(255,255,255);
                         ],
              ), "hatched to 16-bit image")
     or diag $img->errstr();
-  $img->write(file=>"testout/t75wave_fill16.ppm") or die $img->errstr;
+  $img->write(file=>"testout/250-poly-wave_fill16.ppm") or die $img->errstr;
+  push @out_files, "testout/250-poly-wave_fill16.ppm";
 }
 
-Imager::malloc_state();
+{
+  my $img = Imager->new(xsize => 100, ysize => 100);
+  my $poly =
+    [
+     [
+      [ 10, 90, 90, 10 ],
+      [ 10, 10, 90, 90 ],
+     ],
+     [
+      [ 20, 45, 45, 20 ],
+      [ 20, 20, 80, 80 ],
+     ],
+     [
+      [ 55, 55, 80, 80 ],
+      [ 20, 80, 80, 20 ],
+     ],
+    ];
+  ok($img->polypolygon
+     (
+      points => $poly,
+      filled => 1,
+      color => $white,
+     ), "default polypolygon");
+  push @out_files, "testout/250-poly-ppeo.ppm";
+  ok($img->write(file => "testout/250-poly-ppeo.ppm"),
+     "save to file");
+  my $cmp_eo = Imager->new(xsize => 100, ysize => 100);
+  $cmp_eo->box(filled => 1, color => $white, box => [ 10, 10, 89, 89 ]);
+  $cmp_eo->box(filled => 1, color => $black, box => [ 20, 20, 44, 79 ]);
+  $cmp_eo->box(filled => 1, color => $black, box => [ 55, 20, 79, 79 ]);
+  is_image($img, $cmp_eo, "check even/odd matches");
+  $img = Imager->new(xsize => 100, ysize => 100);
+  ok($img->polypolygon
+     (
+      points => $poly,
+      filled => 1,
+      color => $white,
+      mode => "nonzero",
+     ), "default polypolygon");
+  my $cmp_nz = Imager->new(xsize => 100, ysize => 100);
+  $cmp_nz->box(filled => 1, color => $white, box => [ 10, 10, 89, 89 ]);
+  $cmp_nz->box(filled => 1, color => $black, box => [ 55, 20, 79, 79 ]);
+  is_image($img, $cmp_nz, "check non-zero matches");
+  push @out_files, "testout/250-poly-ppnz.ppm";
+  ok($img->write(file => "testout/250-poly-ppnz.ppm"),
+     "save to file");
+}
 
+Imager->close_log;
+
+Imager::malloc_state();
 
 #initialized in a BEGIN, later
 my %primitives;
diff --git a/t/250-draw/060-polypoly.t b/t/250-draw/060-polypoly.t
new file mode 100644 (file)
index 0000000..9ef776a
--- /dev/null
@@ -0,0 +1,126 @@
+#!perl -w
+
+use strict;
+use Test::More tests => 12;
+
+use Imager qw/NC/;
+use Imager::Test qw(is_image is_color3);
+
+sub PI () { 3.14159265358979323846 }
+
+-d "testout" or mkdir "testout";
+
+my @cleanup;
+push @cleanup, "testout/060-polypoly.log";
+Imager->open_log(log => "testout/060-polypoly.log");
+
+my $red   = NC(255,0,0);
+my $green = NC(0,255,0);
+my $blue  = NC(0,0,255);
+my $white = NC(255,255,255);
+my $black = NC(0, 0, 0);
+
+END {
+  unlink @cleanup unless $ENV{IMAGER_KEEP_FILES};
+  rmdir "testout";
+}
+
+{
+  my $out = "testout/060-ppsimple.ppm";
+  my $im = Imager->new(xsize => 100, ysize => 100);
+  ok($im->polypolygon
+     (
+      filled => 1,
+      color => $red,
+      points =>
+      [
+       [
+       [ 20, 20, 40, 40 ],
+       [ 20, 80, 80, 20 ],
+       ],
+       [
+       [ 60, 60, 80, 80 ],
+       [ 20, 80, 80, 20 ],
+       ],
+      ]
+     ), "simple filled polypolygon");
+  ok($im->write(file => $out), "save to $out");
+  my $cmp = Imager->new(xsize => 100, ysize => 100);
+  $cmp->box(filled => 1, color => $red, box => [ 20, 20, 39, 79 ]);
+  $cmp->box(filled => 1, color => $red, box => [ 60, 20, 79, 79 ]);
+  is_image($im, $cmp, "check expected output");
+  push @cleanup, $out;
+}
+
+{
+  my $im = Imager->new(xsize => 100, ysize => 100);
+  my $cross_cw =
+    [
+     [
+      [ 10, 90, 90, 10 ],
+      [ 40, 40, 60, 60 ],
+     ],
+     [
+      [ 40, 60, 60, 40 ],
+      [ 10, 10, 90, 90 ],
+     ],
+    ];
+  ok($im->polypolygon
+     (
+      filled => 1,
+      color => $red,
+      points =>$cross_cw,
+      mode => "nonzero",
+     ), "cross polypolygon nz");
+  save($im, "060-ppcrossnz.ppm");
+  my $cmp = Imager->new(xsize => 100, ysize => 100);
+  $cmp->box(filled => 1, color => $red, box => [ 10, 40, 89, 59 ]);
+  $cmp->box(filled => 1, color => $red, box => [ 40, 10, 59, 89 ]);
+  is_image($im, $cmp, "check expected output");
+
+  my $im2 = Imager->new(xsize => 100, ysize => 100);
+  ok($im2->polypolygon
+     (
+      filled => 1,
+      color => $red,
+      points =>$cross_cw,
+      #mode => "nonzero", # default to evenodd
+     ), "cross polypolygon eo");
+  save($im2, "060-ppcrosseo.ppm");
+
+  $cmp->box(filled => 1, color => $black, box => [ 40, 40, 59, 59 ]);
+  is_image($im2, $cmp, "check expected output");
+
+  # same as cross_cw except that the second box is in reversed order
+  my $cross_diff =
+    [
+     [
+      [ 10, 90, 90, 10 ],
+      [ 40, 40, 60, 60 ],
+     ],
+     [
+      [ 40, 60, 60, 40 ],
+      [ 90, 90, 10, 10 ],
+     ],
+    ];
+  my $im3 = Imager->new(xsize => 100, ysize => 100);
+  ok($im3->polypolygon
+     (
+      filled => 1,
+      color => $red,
+      points => $cross_diff,
+      mode => "nonzero",
+     ), "cross polypolygon diff");
+  is_image($im3, $cmp, "check expected output");
+  save($im3, "060-ppcrossdiff.ppm");
+}
+
+Imager->close_log;
+
+sub save {
+  my ($im, $file) = @_;
+
+  $file = "testout/" . $file;
+  push @cleanup, $file;
+  ok($im->write(file => $file), "save to $file");
+}
index 16d231a..c9a1f93 100644 (file)
@@ -158,6 +158,28 @@ Imager do_lots(Imager src) {
   i_box_filled(im, 11, 25, 19, 29, &blue);
   i_flood_cfill_border(im, 15, 25, hatch, &black);
 
+  {
+     double x[3];
+     double y[3];
+     i_polygon_t poly;
+     x[0] = 55;
+     y[0] = 25;
+     x[1] = 55;
+     y[1] = 50;
+     x[2] = 70;
+     y[2] = 50;
+     i_poly_aa_m(im, 3, x, y, i_pfm_evenodd, &red);
+     x[2] = 40;
+     i_poly_aa_cfill_m(im, 3, x, y, i_pfm_evenodd, hatch);
+     y[0] = 65;
+     poly.x = x;
+     poly.y = y;
+     poly.count = 3;
+     i_poly_poly_aa(im, 1, &poly, i_pfm_nonzero, &green);
+     x[2] = 70;
+     i_poly_poly_aa_cfill(im, 1, &poly, i_pfm_nonzero, hatch);
+  }
+
   i_fill_destroy(fount_fill);
   i_fill_destroy(fhatch_fill);
   i_fill_destroy(solid_fill);
@@ -233,7 +255,7 @@ Imager do_lots(Imager src) {
     i_img_destroy(im);
     return NULL;
   }
-    
+
   return im;
 }
 
index bd15d4a..a3ea2d2 100644 (file)
@@ -5,6 +5,7 @@
 i_channel_list         T_IM_CHANNEL_LIST
 i_sample_list          T_IM_SAMPLE_LIST
 i_fsample_list         T_IM_FSAMPLE_LIST
+i_polygon_list         T_IM_POLYGON_LIST
 
 off_t                  T_OFF_T
 
@@ -18,6 +19,7 @@ int *                         T_AVARRAY
 i_img_dim *            T_AVARRAY
 i_color *              T_AVARRAY
 
+i_poly_fill_mode_t     T_I_POLY_FILL_MODE_T
 
 #############################################################################
 INPUT
@@ -109,6 +111,9 @@ T_IM_FSAMPLE_LIST
            croak(\"$pname: no samples provided in $var\");
        }
 
+T_IM_POLYGON_LIST
+        S_get_polygon_list(aTHX_ &$var, $arg);
+
 T_AVARRAY
        STMT_START {
                SV* const xsub_tmp_sv = $arg;
@@ -132,6 +137,11 @@ T_AVARRAY
                }
        } STMT_END
 
+T_I_POLY_FILL_MODE_T
+       $var = S_get_poly_fill_mode(aTHX_ $arg);
+
+
+
 #############################################################################
 OUTPUT
 
index 1f268d5..9661e0e 100644 (file)
@@ -19,6 +19,7 @@ CGI
 CMYK
 CPAN
 FreeType
+GDI
 GIF
 HSV
 Hrafnkelsson