- arc(..., fill=> ...) wasn't handling concave areas correctly
authorTony Cook <tony@develop=help.com>
Sat, 26 Nov 2005 02:52:56 +0000 (02:52 +0000)
committerTony Cook <tony@develop=help.com>
Sat, 26 Nov 2005 02:52:56 +0000 (02:52 +0000)
- arc(..., color=>...) wasn't properly filling it's area
- added experimental antialiased support to arc()

Changes
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
draw.c
image.h
imagei.h
t/t21draw.t
t/t81hlines.t [new file with mode: 0644]
typemap

diff --git a/Changes b/Changes
index 9adb2ec..d6cf78a 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1180,6 +1180,9 @@ Revision history for Perl extension Imager.
 - fixes to verbose mode in Makefile.PL, also added a -v switch so you 
   can enable it via the command-line
   Resolves: http://rt.cpan.org/NoAuth/Bug.html?id=16094
+- arc(..., fill=> ...) wasn't handling concave areas correctly
+- arc(..., color=>...) wasn't properly filling it's area
+- added experimental antialiased support to arc()
 
 =================================================================
 
index 03ba25a..1b5e036 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -2084,8 +2084,6 @@ sub box {
   return $self;
 }
 
-# Draws an arc - this routine SUCKS and is buggy - it sometimes doesn't work when the arc is a convex polygon
-
 sub arc {
   my $self=shift;
   unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; }
@@ -2095,38 +2093,61 @@ sub arc {
            'x'=>$self->getwidth()/2,
            'y'=>$self->getheight()/2,
            'd1'=>0, 'd2'=>361, @_);
-  if ($opts{fill}) {
-    unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
-      # assume it's a hash ref
-      require 'Imager/Fill.pm';
-      unless ($opts{fill} = Imager::Fill->new(%{$opts{fill}})) {
-        $self->{ERRSTR} = $Imager::ERRSTR;
-        return;
+  if ($opts{aa}) {
+    if ($opts{fill}) {
+      unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+       # assume it's a hash ref
+       require 'Imager/Fill.pm';
+       unless ($opts{fill} = Imager::Fill->new(%{$opts{fill}})) {
+         $self->{ERRSTR} = $Imager::ERRSTR;
+         return;
+       }
+      }
+      i_arc_aa_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+                    $opts{'d2'}, $opts{fill}{fill});
+    }
+    else {
+      my $color = _color($opts{'color'});
+      unless ($color) { 
+       $self->{ERRSTR} = $Imager::ERRSTR; 
+       return; 
+      }
+      if ($opts{d1} == 0 && $opts{d2} == 361 && $opts{aa}) {
+       i_circle_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, 
+                   $color);
+      }
+      else {
+       i_arc_aa($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},
+                $opts{'d1'}, $opts{'d2'}, $color); 
       }
     }
-    i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
-                $opts{'d2'}, $opts{fill}{fill});
   }
   else {
-    my $color = _color($opts{'color'});
-    unless ($color) { 
-      $self->{ERRSTR} = $Imager::ERRSTR; 
-      return; 
-    }
-    if ($opts{d1} == 0 && $opts{d2} == 361 && $opts{aa}) {
-      i_circle_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, 
-                  $color);
+    if ($opts{fill}) {
+      unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+       # assume it's a hash ref
+       require 'Imager/Fill.pm';
+       unless ($opts{fill} = Imager::Fill->new(%{$opts{fill}})) {
+         $self->{ERRSTR} = $Imager::ERRSTR;
+         return;
+       }
+      }
+      i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+                 $opts{'d2'}, $opts{fill}{fill});
     }
     else {
-      if ($opts{'d1'} <= $opts{'d2'}) { 
-        i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},
-              $opts{'d1'}, $opts{'d2'}, $color); 
+      my $color = _color($opts{'color'});
+      unless ($color) { 
+       $self->{ERRSTR} = $Imager::ERRSTR; 
+       return; 
+      }
+      if ($opts{d1} == 0 && $opts{d2} == 361 && $opts{aa}) {
+       i_circle_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, 
+                   $color);
       }
       else {
-        i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},
-              $opts{'d1'}, 361,         $color);
-        i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},
-              0,           $opts{'d2'}, $color); 
+       i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},
+             $opts{'d1'}, $opts{'d2'}, $color); 
       }
     }
   }
@@ -3208,18 +3229,24 @@ blur - L<Imager::Filters/guassian>, L<Imager::Filters/conv>
 
 boxes, drawing - L<Imager::Draw/box>
 
+changes between image - L<Imager::Filter/"Image Difference">
+
 color - L<Imager::Color>
 
 color names - L<Imager::Color>, L<Imager::Color::Table>
 
 combine modes - L<Imager::Fill/combine>
 
+compare images - L<Imager::Filter/"Image Difference">
+
 contrast - L<Imager::Filter/contrast>, L<Imager::Filter/autolevels>
 
 convolution - L<Imager::Filter/conv>
 
 cropping - L<Imager::Transformations/crop>
 
+C<diff> images - L<Imager::Filter/"Image Difference">
+
 dpi - L<Imager::ImageTypes/i_xres>
 
 drawing boxes - L<Imager::Draw/box>
index 3fafbe8..13db3ec 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -9,11 +9,17 @@ extern "C" {
 
 #endif
 
+#define i_int_hlines_testing() 1
+
 #include "image.h"
 #include "feat.h"
 #include "dynaload.h"
 #include "regmach.h"
 
+#if i_int_hlines_testing()
+#include "imagei.h"
+#endif
+
 typedef io_glue* Imager__IO;
 typedef i_color* Imager__Color;
 typedef i_fcolor* Imager__Color__Float;
@@ -821,6 +827,67 @@ typedef i_fill_t* Imager__FillHandle;
     potential naming conflicts */
 #define init_log m_init_log
 
+#if i_int_hlines_testing()
+
+typedef i_int_hlines *Imager__Internal__Hlines;
+
+static i_int_hlines *
+i_int_hlines_new(int start_y, int count_y, int start_x, int count_x) {
+  i_int_hlines *result = mymalloc(sizeof(i_int_hlines));
+  i_int_init_hlines(result, start_y, count_y, start_x, count_x);
+
+  return result;
+}
+
+static i_int_hlines *
+i_int_hlines_new_img(i_img *im) {
+  i_int_hlines *result = mymalloc(sizeof(i_int_hlines));
+  i_int_init_hlines_img(result, im);
+
+  return result;
+}
+
+static void
+i_int_hlines_DESTROY(i_int_hlines *hlines) {
+  i_int_hlines_destroy(hlines);
+  myfree(hlines);
+}
+
+static int seg_compare(const void *vleft, const void *vright) {
+  const i_int_hline_seg *left = vleft;
+  const i_int_hline_seg *right = vright;
+
+  return left->minx - right->minx;
+}
+
+static SV *
+i_int_hlines_dump(i_int_hlines *hlines) {
+  SV *dump = newSVpvf("start_y: %d limit_y: %d start_x: %d limit_x: %d\n",
+       hlines->start_y, hlines->limit_y, hlines->start_x, hlines->limit_x);
+  int y;
+  
+  for (y = hlines->start_y; y < hlines->limit_y; ++y) {
+    i_int_hline_entry *entry = hlines->entries[y-hlines->start_y];
+    if (entry) {
+      int i;
+      /* sort the segments, if any */
+      if (entry->count)
+        qsort(entry->segs, entry->count, sizeof(i_int_hline_seg), seg_compare);
+
+      sv_catpvf(dump, " %d (%d):", y, entry->count);
+      for (i = 0; i < entry->count; ++i) {
+        sv_catpvf(dump, " [%d, %d)", entry->segs[i].minx, 
+                  entry->segs[i].x_limit);
+      }
+      sv_catpv(dump, "\n");
+    }
+  }
+
+  return dump;
+}
+
+#endif
+
 MODULE = Imager                PACKAGE = Imager::Color PREFIX = ICL_
 
 Imager::Color
@@ -1216,6 +1283,16 @@ i_arc(im,x,y,rad,d1,d2,val)
              float     d2
           Imager::Color    val
 
+void
+i_arc_aa(im,x,y,rad,d1,d2,val)
+    Imager::ImgRaw     im
+           double     x
+           double     y
+            double     rad
+            double     d1
+            double     d2
+          Imager::Color    val
+
 void
 i_arc_cfill(im,x,y,rad,d1,d2,fill)
     Imager::ImgRaw     im
@@ -1226,6 +1303,15 @@ i_arc_cfill(im,x,y,rad,d1,d2,fill)
              float     d2
           Imager::FillHandle    fill
 
+void
+i_arc_aa_cfill(im,x,y,rad,d1,d2,fill)
+    Imager::ImgRaw     im
+           double     x
+           double     y
+            double     rad
+            double     d1
+            double     d2
+          Imager::FillHandle   fill
 
 
 void
@@ -4430,3 +4516,39 @@ i_new_fill_image(src, matrix, xoff, yoff, combine)
       OUTPUT:
         RETVAL
 
+MODULE = Imager  PACKAGE = Imager::Internal::Hlines  PREFIX=i_int_hlines_
+
+# this class is only exposed for testing
+
+int
+i_int_hlines_testing()
+
+#if i_int_hlines_testing()
+
+Imager::Internal::Hlines
+i_int_hlines_new(start_y, count_y, start_x, count_x)
+       int start_y
+       int count_y
+       int start_x
+       int count_x
+
+Imager::Internal::Hlines
+i_int_hlines_new_img(im)
+       Imager::ImgRaw im
+
+void
+i_int_hlines_add(hlines, y, minx, width)
+       Imager::Internal::Hlines hlines
+       int y
+       int minx
+       int width
+
+void
+i_int_hlines_DESTROY(hlines)
+       Imager::Internal::Hlines hlines
+
+SV *
+i_int_hlines_dump(hlines)
+       Imager::Internal::Hlines hlines
+
+#endif
index 4658835..bc8d60b 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -49,6 +49,7 @@ fontfiles/dodge.ttf
 freetyp2.c     Implements freetype2 font support
 gaussian.c
 gif.c
+hlines.c       Manage sets of horizontal line segments
 image.c
 image.h
 imagei.h
@@ -164,6 +165,7 @@ t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
 t/t80texttools.t        Test text wrapping
+t/t81hlines.t          Test hlines.c
 t/t90cc.t
 t/t91pod.t             Test POD with Test::Pod
 t/testtools.pl
index 89c124d..96af5bb 100644 (file)
@@ -110,7 +110,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 tga.o rgb.o color.o fills.o imgdouble.o limits.o);
+           bmp.o tga.o rgb.o color.o fills.o imgdouble.o limits.o hlines.o);
 
 %opts=(
        'NAME'         => 'Imager',
diff --git a/draw.c b/draw.c
index 05d69c8..c52d5a0 100644 (file)
--- a/draw.c
+++ b/draw.c
@@ -1,6 +1,7 @@
 #include "image.h"
 #include "draw.h"
 #include "log.h"
+#include "imagei.h"
 
 #include <limits.h>
 
@@ -143,16 +144,15 @@ i_mmarray_info(i_mmarray *ar) {
   if (ar->data[i].max!=-1) printf("line %d: min=%d, max=%d.\n",i,ar->data[i].min,ar->data[i].max);
 }
 
-
-void
-i_arc(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val) {
+static void
+i_arc_minmax(i_int_hlines *hlines,int x,int y,float rad,float d1,float d2) {
   i_mmarray dot;
   float f,fx,fy;
   int x1,y1;
 
-  mm_log((1,"i_arc(im* 0x%x,x %d,y %d,rad %.2f,d1 %.2f,d2 %.2f,val 0x%x)\n",im,x,y,rad,d1,d2,val));
+  /*mm_log((1,"i_arc(im* 0x%x,x %d,y %d,rad %.2f,d1 %.2f,d2 %.2f,val 0x%x)\n",im,x,y,rad,d1,d2,val));*/
 
-  i_mmarray_cr(&dot,im->ysize);
+  i_mmarray_cr(&dot, hlines->limit_y);
 
   x1=(int)(x+0.5+rad*cos(d1*PI/180.0));
   y1=(int)(y+0.5+rad*sin(d1*PI/180.0));
@@ -169,42 +169,158 @@ i_arc(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val) {
   /*  printf("x1: %d.\ny1: %d.\n",x1,y1); */
   i_arcdraw(x, y, x1, y1, &dot);
 
+  /* render the minmax values onto the hlines */
+  for (y = 0; y < dot.lines; y++) {
+    if (dot.data[y].max!=-1) {
+      int minx, width;
+      minx = dot.data[y].min;
+      width = dot.data[y].max - dot.data[y].min + 1;
+      i_int_hlines_add(hlines, y, minx, width);
+    }
+  }
+
   /*  dot.info(); */
-  i_mmarray_render(im,&dot,val);
   i_mmarray_dst(&dot);
 }
 
+static void
+i_arc_hlines(i_int_hlines *hlines,int x,int y,float rad,float d1,float d2) {
+  if (d1 <= d2) {
+    i_arc_minmax(hlines, x, y, rad, d1, d2);
+  }
+  else {
+    i_arc_minmax(hlines, x, y, rad, d1, 360);
+    i_arc_minmax(hlines, x, y, rad, 0, d2);
+  }
+}
+
+void
+i_arc(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val) {
+  i_int_hlines hlines;
+
+  i_int_init_hlines_img(&hlines, im);
+
+  i_arc_hlines(&hlines, x, y, rad, d1, d2);
+
+  i_int_hlines_fill_color(im, &hlines, val);
+
+  i_int_hlines_destroy(&hlines);
+}
+
+#define MIN_CIRCLE_STEPS 8
+#define MAX_CIRCLE_STEPS 360
+
 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;
+  i_int_hlines hlines;
 
-  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_int_init_hlines_img(&hlines, im);
 
-  i_mmarray_cr(&dot,im->ysize);
+  i_arc_hlines(&hlines, x, y, rad, d1, d2);
 
-  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;
+  i_int_hlines_fill_fill(im, &hlines, fill);
 
-  /*  printf("x1: %d.\ny1: %d.\n",x1,y1); */
-  i_arcdraw(x, y, x1, y1, &dot);
+  i_int_hlines_destroy(&hlines);
+}
 
-  x1=(int)(x+0.5+rad*cos(d2*PI/180.0));
-  y1=(int)(y+0.5+rad*sin(d2*PI/180.0));
+static void
+arc_poly(int *count, double **xvals, double **yvals,
+        double x, double y, double rad, double d1, double d2) {
+  double d1_rad, d2_rad;
+  double circum;
+  int steps, point_count;
+  double angle_inc;
+
+  /* normalize the angles */
+  d1 = fmod(d1, 360);
+  if (d1 == 0) {
+    if (d2 >= 360) { /* default is 361 */
+      d2 = 360;
+    }
+    else {
+      d2 = fmod(d2, 360);
+      if (d2 < d1)
+       d2 += 360;
+    }
+  }
+  else {
+    d2 = fmod(d2, 360);
+    if (d2 < d1)
+      d2 += 360;
+  }
+  d1_rad = d1 * PI / 180;
+  d2_rad = d2 * PI / 180;
 
-  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);
+  /* how many segments for the curved part? 
+     we do a maximum of one per degree, with a minimum of 8/circle
+     we try to aim at having about one segment per 2 pixels
+     Work it out per circle to get a step size.
 
-  /*  dot.info(); */
-  i_mmarray_render_fill(im,&dot,fill);
-  i_mmarray_dst(&dot);
+     I was originally making steps = circum/2 but that looked horrible.
+
+     I think there might be an issue in the polygon filler.
+  */
+  circum = 2 * PI * rad;
+  steps = circum;
+  if (steps > MAX_CIRCLE_STEPS)
+    steps = MAX_CIRCLE_STEPS;
+  else if (steps < MIN_CIRCLE_STEPS)
+    steps = MIN_CIRCLE_STEPS;
+
+  angle_inc = 2 * PI / steps;
+
+  point_count = steps + 5; /* rough */
+  *xvals = mymalloc(point_count * sizeof(double));
+  *yvals = mymalloc(point_count * sizeof(double));
+
+  /* from centre to edge at d1 */
+  (*xvals)[0] = x;
+  (*yvals)[0] = y;
+  (*xvals)[1] = x + rad * cos(d1_rad);
+  (*yvals)[1] = y + rad * sin(d1_rad);
+  *count = 2;
+
+  /* step around the curve */
+  while (d1_rad < d2_rad) {
+    (*xvals)[*count] = x + rad * cos(d1_rad);
+    (*yvals)[*count] = y + rad * sin(d1_rad);
+    ++*count;
+    d1_rad += angle_inc;
+  }
+
+  /* finish off the curve */
+  (*xvals)[*count] = x + rad * cos(d2_rad);
+  (*yvals)[*count] = y + rad * sin(d2_rad);
+  ++*count;
+}
+
+void
+i_arc_aa(i_img *im, double x, double y, double rad, double d1, double d2,
+        i_color *val) {
+  double *xvals, *yvals;
+  int count;
+
+  arc_poly(&count, &xvals, &yvals, x, y, rad, d1, d2);
+
+  i_poly_aa(im, count, xvals, yvals, val);
+
+  myfree(xvals);
+  myfree(yvals);
 }
 
+void
+i_arc_aa_cfill(i_img *im, double x, double y, double rad, double d1, double d2,
+              i_fill_t *fill) {
+  double *xvals, *yvals;
+  int count;
 
+  arc_poly(&count, &xvals, &yvals, x, y, rad, d1, d2);
+
+  i_poly_aa_cfill(im, count, xvals, yvals, fill);
+
+  myfree(xvals);
+  myfree(yvals);
+}
 
 /* Temporary AA HACK */
 
diff --git a/image.h b/image.h
index b64477e..20bfabf 100644 (file)
--- a/image.h
+++ b/image.h
@@ -207,7 +207,9 @@ void i_box_cfill(i_img *im, int x1, int y1, int x2, int y2, i_fill_t *fill);
 void i_line        (i_img *im,int x1,int y1,int x2,int y2,i_color *val, int endp);
 void i_line_aa     (i_img *im,int x1,int y1,int x2,int y2,i_color *val, int endp);
 void i_arc         (i_img *im,int x,int y,float rad,float d1,float d2,i_color *val);
+void i_arc_aa         (i_img *im, double x, double y, double rad, double d1, double 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_arc_aa_cfill(i_img *im,double x,double y,double rad,double d1,double 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);
index fb6a5a8..ce58e50 100644 (file)
--- a/imagei.h
+++ b/imagei.h
@@ -44,6 +44,9 @@ extern void i_get_combine(int combine, i_fill_combine_f *, i_fill_combinef_f *);
 extern int
 i_int_check_image_file_limits(int width, int height, int channels, int sample_size);
 
+#define im_min(a, b) ((a) < (b) ? (a) : (b))
+#define im_max(a, b) ((a) > (b) ? (a) : (b))
+
 #include "ext.h"
 
 extern UTIL_table_t i_UTIL_table;
@@ -57,4 +60,36 @@ extern UTIL_table_t i_UTIL_table;
 /* test if all channels are writable */
 #define I_ALL_CHANNELS_WRITABLE(im) (((im)->ch_mask & 0xF) == 0xf)
 
+typedef struct i_int_hline_seg_tag {
+  int minx, x_limit;
+} i_int_hline_seg;
+
+typedef struct i_int_hline_entry_tag {
+  int count;
+  int alloc;
+  i_int_hline_seg segs[1];
+} i_int_hline_entry;
+
+/* represents a set of horizontal line segments to be filled in later */
+typedef struct i_int_hlines_tag {
+  int start_y, limit_y;
+  int start_x, limit_x;
+  i_int_hline_entry **entries;
+} i_int_hlines;
+
+extern void 
+i_int_init_hlines(
+                 i_int_hlines *hlines, 
+                 int start_y,
+                 int count_y, 
+                 int start_x, 
+                 int width_x
+                 );
+extern void i_int_init_hlines_img(i_int_hlines *hlines, i_img *img);
+extern void i_int_hlines_add(i_int_hlines *hlines, int y, int minx, int width);
+extern void i_int_hlines_destroy(i_int_hlines *hlines);
+
+extern void i_int_hlines_fill_color(i_img *im, i_int_hlines *hlines, i_color *val);
+extern void i_int_hlines_fill_fill(i_img *im, i_int_hlines *hlines, i_fill_t *fill);
+
 #endif
index f40d5da..96ee18c 100644 (file)
@@ -1,3 +1,4 @@
+#!perl -w
 # Before `make install' is performed this script should be runnable with
 # `make test'. After `make install' it should work as `perl test.pl'
 
@@ -6,14 +7,10 @@
 # Change 1..1 below to 1..last_test_to_print .
 # (It may become useful if the test is moved to ./t subdirectory.)
 use strict;
+use Test::More tests => 38;
 my $loaded;
 
-BEGIN { $| = 1; print "1..29\n"; }
-END {print "not ok 1\n" unless $loaded;}
-use Imager qw/:all/;
-$loaded = 1;
-print "ok 1\n";
-
+BEGIN { use_ok(Imager=>':all'); }
 init_log("testout/t21draw.log",1);
 
 my $redobj = NC(255, 0, 0);
@@ -24,9 +21,7 @@ my $blueobj = NC(0, 0, 255);
 my $blue = { hue=>240, saturation=>1, value=>1 };
 my $white = '#FFFFFF';
 
-my $testnum = 2;
-
-my $img = Imager->new(xsize=>100, ysize=>100);
+my $img = Imager->new(xsize=>100, ysize=>300);
 
 ok($img->box(color=>$blueobj, xmin=>10, ymin=>10, xmax=>48, ymax=>18),
    "box with color obj");
@@ -54,8 +49,8 @@ ok($img->line(color=>$blueobj, x1=>5, y1=>55, x2=>35, y2=>95),
    "line with colorobj");
 
 # FIXME - neither the start nor end-point is set for a non-aa line
-#my $c = Imager::i_get_pixel($img->{IMG}, 5, 55);
-#ok(color_cmp($c, $blueobj) == 0, "# TODO start point not set");
+my $c = Imager::i_get_pixel($img->{IMG}, 5, 55);
+ok(color_cmp($c, $blueobj) == 0, "# TODO start point not set");
 
 ok($img->line(color=>$red, x1=>10, y1=>55, x2=>40, y2=>95, aa=>1),
    "aa line with color");
@@ -98,22 +93,34 @@ $gp = $img->getpixel('x'=>39, 'y'=>55, type=>'float');
 ok($gp->isa('Imager::Color::Float'), "check scalar float getpixel type");
 ok(color_cmp($gp, $flgreen) == 0, "check scalar float getpixel color");
 
+# more complete arc tests
+ok($img->arc(x=>25, 'y'=>125, r=>20, d1=>315, d2=>45, color=>$greenobj),
+   "color arc through angle 0");
+# use diff combine here to make sure double writing is noticable
+ok($img->arc(x=>75, 'y'=>125, r=>20, d1=>315, d2=>45,
+            fill => { solid=>$blueobj, combine => 'diff' }),
+   "fill arc through angle 0");
+ok($img->arc(x=>25, 'y'=>175, r=>20, d1=>315, d2=>225, color=>$redobj),
+   "concave color arc");
+ok($img->arc(x=>75, 'y'=>175, r=>20, d1=>315, d2=>225,
+            fill => { solid=>$greenobj, combine=>'diff' }),
+   "concave fill arc");
+ok($img->arc(x=>25, y=>225, r=>20, d1=>135, d2=>45, color=>$redobj),
+   "another concave color arc");
+ok($img->arc(x=>75, y=>225, r=>20, d1=>135, d2=>45, 
+            fille => { solid=>$blueobj, combine=>'diff' }),
+   "another concave fillarc");
+ok($img->arc(x=>25, y=>275, r=>20, d1=>135, d2=>45, color=>$redobj, aa=>1),
+   "concave color arc aa");
+ok($img->arc(x=>75, y=>275, r=>20, d1=>135, d2=>45, 
+            fille => { solid=>$blueobj, combine=>'diff' }, aa=>1),
+   "concave fill arc aa");
+
 ok($img->write(file=>'testout/t21draw.ppm'),
    "saving output");
 
 malloc_state();
 
-sub ok {
-  my ($ok, $msg) = @_;
-
-  if ($ok) {
-    print "ok ",$testnum++,"\n";
-  }
-  else {
-    print "not ok ",$testnum++," # $msg\n";
-  }
-}
-
 sub color_cmp {
   my ($l, $r) = @_;
   my @l = $l->rgba;
diff --git a/t/t81hlines.t b/t/t81hlines.t
new file mode 100644 (file)
index 0000000..594f010
--- /dev/null
@@ -0,0 +1,103 @@
+#!perl -w
+use strict;
+use Test::More;
+use Imager;
+
+# this script tests an internal set of functions for Imager, they 
+# aren't intended to be used at the perl level.
+# these functions aren't present in all Imager builds
+
+unless (Imager::Internal::Hlines::testing()) {
+  plan skip_all => 'Imager not built to run this test';
+}
+
+plan tests => 15;
+
+my $hline = Imager::Internal::Hlines::new(0, 100, 0, 100);
+my $base_text = 'start_y: 0 limit_y: 100 start_x: 0 limit_x: 100';
+ok($hline, "made hline");
+is($hline->dump, "$base_text\n", "check values");
+$hline->add(5, -5, 7);
+is($hline->dump, <<EOS, "check (-5, 7) added");
+$base_text
+ 5 (1): [0, 2)
+EOS
+$hline->add(5, 8, 4);
+is($hline->dump, <<EOS, "check (8, 4) added");
+$base_text
+ 5 (2): [0, 2) [8, 12)
+EOS
+$hline->add(5, 3, 3);
+is($hline->dump, <<EOS, "check (3, 3) added");
+$base_text
+ 5 (3): [0, 2) [3, 6) [8, 12)
+EOS
+$hline->add(5, 2, 6);
+is($hline->dump, <<EOS, "check (2, 6) added");
+$base_text
+ 5 (1): [0, 12)
+EOS
+# adding out of range should do nothing
+my $current = <<EOS;
+$base_text
+ 5 (1): [0, 12)
+EOS
+$hline->add(6, -5, 5);
+is($hline->dump, $current, "check (6, -5, 5) not added");
+$hline->add(6, 100, 5);
+is($hline->dump, $current, "check (6, 100, 5) not added");
+$hline->add(-1, 5, 2);
+is($hline->dump, $current, "check (-1, 5, 2) not added");
+$hline->add(100, 5, 2);
+is($hline->dump, $current, "check (10, 5, 2) not added");
+
+# overlapped add check
+$hline->add(6, 2, 6);
+$hline->add(6, 3, 4);
+is($hline->dump, <<EOS, "check internal overlap merged");
+$base_text
+ 5 (1): [0, 12)
+ 6 (1): [2, 8)
+EOS
+
+# white box test: try to force reallocation of an entry
+for my $i (0..20) {
+  $hline->add(7, $i*2, 1);
+}
+is($hline->dump, <<EOS, "lots of segments");
+$base_text
+ 5 (1): [0, 12)
+ 6 (1): [2, 8)
+ 7 (21): [0, 1) [2, 3) [4, 5) [6, 7) [8, 9) [10, 11) [12, 13) [14, 15) [16, 17) [18, 19) [20, 21) [22, 23) [24, 25) [26, 27) [28, 29) [30, 31) [32, 33) [34, 35) [36, 37) [38, 39) [40, 41)
+EOS
+# now merge them
+$hline->add(7, 1, 39);
+is($hline->dump, <<EOS, "merge lots of segments");
+$base_text
+ 5 (1): [0, 12)
+ 6 (1): [2, 8)
+ 7 (1): [0, 41)
+EOS
+
+# clean object
+$hline = Imager::Internal::Hlines::new(50, 50, 50, 50);
+$base_text = 'start_y: 50 limit_y: 100 start_x: 50 limit_x: 100';
+
+# left merge
+$hline->add(51, 45, 10);
+$hline->add(51, 55, 4);
+is($hline->dump, <<EOS, "left merge");
+$base_text
+ 51 (1): [50, 59)
+EOS
+
+# right merge
+$hline->add(52, 90, 5);
+$hline->add(52, 87, 5);
+is($hline->dump, <<EOS, "right merge");
+$base_text
+ 51 (1): [50, 59)
+ 52 (1): [87, 95)
+EOS
+
+undef $hline;
diff --git a/typemap b/typemap
index 1312b02..82d8a4a 100644 (file)
--- a/typemap
+++ b/typemap
@@ -6,6 +6,7 @@ Imager::Font::TT        T_PTROBJ
 Imager::IO              T_PTROBJ
 Imager::Font::FT2       T_PTROBJ
 Imager::FillHandle      T_PTROBJ
+Imager::Internal::Hlines T_PTROBJ
 const char *           T_PV
 float                  T_FLOAT
 float*                 T_ARRAY