From: Tony Cook Date: Thu, 25 Feb 2010 08:37:52 +0000 (+0000) Subject: merge circle outline branch X-Git-Tag: Imager-0.74~13 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/40068b33250554397ec32c371644ed27bbdd8d6a merge circle outline branch --- diff --git a/Changes b/Changes index 2e5dded1..e03569d1 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Imager release history. Older releases can be found in Changes.old +Imager 0.73 - unreleased +=========== + + - implement outline circles, both anti-aliased and not + https://rt.cpan.org/Ticket/Display.html?id=19755 + Imager 0.72 - 09 Dec 2009 =========== diff --git a/Imager.pm b/Imager.pm index b7d475cc..ab501b43 100644 --- a/Imager.pm +++ b/Imager.pm @@ -2791,12 +2791,18 @@ sub box { sub arc { my $self=shift; unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } - my $dflcl=i_color_new(255,255,255,255); - my %opts=(color=>$dflcl, - 'r'=>_min($self->getwidth(),$self->getheight())/3, - 'x'=>$self->getwidth()/2, - 'y'=>$self->getheight()/2, - 'd1'=>0, 'd2'=>361, @_); + my $dflcl= [ 255, 255, 255, 255]; + my $good = 1; + my %opts= + ( + color=>$dflcl, + 'r'=>_min($self->getwidth(),$self->getheight())/3, + 'x'=>$self->getwidth()/2, + 'y'=>$self->getheight()/2, + 'd1'=>0, 'd2'=>361, + filled => 1, + @_, + ); if ($opts{aa}) { if ($opts{fill}) { unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { @@ -2810,7 +2816,7 @@ sub arc { i_arc_aa_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'}, $opts{'d2'}, $opts{fill}{fill}); } - else { + elsif ($opts{filled}) { my $color = _color($opts{'color'}); unless ($color) { $self->{ERRSTR} = $Imager::ERRSTR; @@ -2825,6 +2831,15 @@ sub arc { $opts{'d1'}, $opts{'d2'}, $color); } } + else { + my $color = _color($opts{'color'}); + if ($opts{d2} - $opts{d1} >= 360) { + $good = i_circle_out_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, $color); + } + else { + $good = i_arc_out_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, $opts{'d1'}, $opts{'d2'}, $color); + } + } } else { if ($opts{fill}) { @@ -2843,12 +2858,26 @@ sub arc { my $color = _color($opts{'color'}); unless ($color) { $self->{ERRSTR} = $Imager::ERRSTR; - return; + return; + } + if ($opts{filled}) { + i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'}, + $opts{'d1'}, $opts{'d2'}, $color); + } + else { + if ($opts{d1} == 0 && $opts{d2} == 361) { + $good = i_circle_out($self->{IMG}, $opts{x}, $opts{y}, $opts{r}, $color); + } + else { + $good = i_arc_out($self->{IMG}, $opts{x}, $opts{y}, $opts{r}, $opts{d1}, $opts{d2}, $color); + } } - i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'}, - $opts{'d1'}, $opts{'d2'}, $color); } } + unless ($good) { + $self->_set_error($self->_error_as_msg); + return; + } return $self; } diff --git a/Imager.xs b/Imager.xs index 080be4e5..38210252 100644 --- a/Imager.xs +++ b/Imager.xs @@ -1468,6 +1468,41 @@ i_circle_aa(im,x,y,rad,val) float rad Imager::Color val +int +i_circle_out(im,x,y,rad,val) + Imager::ImgRaw im + i_img_dim x + i_img_dim y + i_img_dim rad + Imager::Color val + +int +i_circle_out_aa(im,x,y,rad,val) + Imager::ImgRaw im + i_img_dim x + i_img_dim y + i_img_dim rad + Imager::Color val + +int +i_arc_out(im,x,y,rad,d1,d2,val) + Imager::ImgRaw im + i_img_dim x + i_img_dim y + i_img_dim rad + float d1 + float d2 + Imager::Color val + +int +i_arc_out_aa(im,x,y,rad,d1,d2,val) + Imager::ImgRaw im + i_img_dim x + i_img_dim y + i_img_dim rad + float d1 + float d2 + Imager::Color val void diff --git a/draw.c b/draw.c index baeca5ca..1d84d956 100644 --- a/draw.c +++ b/draw.c @@ -5,6 +5,74 @@ #include "imrender.h" #include +int +i_ppix_norm(i_img *im, i_img_dim x, i_img_dim y, i_color const *col) { + i_color src; + i_color work; + int dest_alpha; + int remains; + + if (!col->channel[3]) + return 0; + + switch (im->channels) { + case 1: + work = *col; + i_adapt_colors(2, 4, &work, 1); + i_gpix(im, x, y, &src); + remains = 255 - work.channel[1]; + src.channel[0] = (src.channel[0] * remains + + work.channel[0] * work.channel[1]) / 255; + return i_ppix(im, x, y, &src); + + case 2: + work = *col; + i_adapt_colors(2, 4, &work, 1); + i_gpix(im, x, y, &src); + dest_alpha = work.channel[1] + remains * src.channel[1] / 255; + if (work.channel[1] == 255) { + return i_ppix(im, x, y, &work); + } + else { + src.channel[0] = (work.channel[1] * work.channel[0] + + remains * src.channel[0] * src.channel[1] / 255) / dest_alpha; + src.channel[1] = dest_alpha; + return i_ppix(im, x, y, &src); + } + + case 3: + work = *col; + i_gpix(im, x, y, &src); + remains = 255 - work.channel[3]; + src.channel[0] = (src.channel[0] * remains + + work.channel[0] * work.channel[3]) / 255; + src.channel[1] = (src.channel[1] * remains + + work.channel[1] * work.channel[3]) / 255; + src.channel[2] = (src.channel[2] * remains + + work.channel[2] * work.channel[3]) / 255; + return i_ppix(im, x, y, &src); + + case 4: + work = *col; + i_gpix(im, x, y, &src); + dest_alpha = work.channel[3] + remains * src.channel[3] / 255; + if (work.channel[3] == 255) { + return i_ppix(im, x, y, &work); + } + else { + src.channel[0] = (work.channel[3] * work.channel[0] + + remains * src.channel[0] * src.channel[3] / 255) / dest_alpha; + src.channel[1] = (work.channel[3] * work.channel[1] + + remains * src.channel[1] * src.channel[3] / 255) / dest_alpha; + src.channel[2] = (work.channel[3] * work.channel[2] + + remains * src.channel[2] * src.channel[3] / 255) / dest_alpha; + src.channel[3] = dest_alpha; + return i_ppix(im, x, y, &src); + } + } + return 0; +} + static void cfill_from_btm(i_img *im, i_fill_t *fill, struct i_bitmap *btm, int bxmin, int bxmax, int bymin, int bymax); @@ -290,7 +358,7 @@ i_arc_aa(i_img *im, double x, double y, double rad, double d1, double d2, double *xvals, *yvals; int count; - arc_poly(&count, &xvals, &yvals, x, y, rad, d1, d2); + arc_poly(&count, &xvals, &yvals, x+0.5, y+0.5, rad, d1, d2); i_poly_aa(im, count, xvals, yvals, val); @@ -458,6 +526,518 @@ i_circle_aa(i_img *im, float x, float y, float rad, const i_color *val) { i_mmarray_dst(&dot); } +/* +=item i_circle_out(im, x, y, r, col) + +=category Drawing +=synopsis i_circle_out(im, 50, 50, 45, &color); + +Draw a circle outline centered at (x,y) with radius r, +non-anti-aliased. + +Parameters: + +=over + +=item * + +(x, y) - the center of the circle + +=item * + +r - the radius of the circle in pixels, must be non-negative + +=back + +Returns non-zero on success. + +Implementation: + +=cut +*/ + +int +i_circle_out(i_img *im, i_img_dim xc, i_img_dim yc, i_img_dim r, + const i_color *col) { + i_img_dim x, y; + i_img_dim dx, dy; + int error; + + i_clear_error(); + + if (r < 0) { + i_push_error(0, "circle: radius must be non-negative"); + return 0; + } + + i_ppix(im, xc+r, yc, col); + i_ppix(im, xc-r, yc, col); + i_ppix(im, xc, yc+r, col); + i_ppix(im, xc, yc-r, col); + + x = 0; + y = r; + dx = 1; + dy = -2 * r; + error = 1 - r; + while (x < y) { + if (error >= 0) { + --y; + dy += 2; + error += dy; + } + ++x; + dx += 2; + error += dx; + + i_ppix(im, xc + x, yc + y, col); + i_ppix(im, xc + x, yc - y, col); + i_ppix(im, xc - x, yc + y, col); + i_ppix(im, xc - x, yc - y, col); + if (x != y) { + i_ppix(im, xc + y, yc + x, col); + i_ppix(im, xc + y, yc - x, col); + i_ppix(im, xc - y, yc + x, col); + i_ppix(im, xc - y, yc - x, col); + } + } + + return 1; +} + +/* +=item arc_seg(angle) + +Convert an angle in degrees into an angle measure we can generate +simply from the numbers we have when drawing the circle. + +=back +*/ + +static i_img_dim +arc_seg(double angle, int scale) { + i_img_dim seg = (angle + 45) / 90; + double remains = angle - seg * 90; /* should be in the range [-45,45] */ + int sign = remains < 0 ? -1 : remains ? 1 : 0; + + while (seg > 4) + seg -= 4; + if (seg == 4 && remains > 0) + seg = 0; + + return scale * (seg * 2 + sin(remains * PI/180)); +} + +/* +=item i_arc_out(im, x, y, r, d1, d2, col) + +=category Drawing +=synopsis i_arc_out(im, 50, 50, 45, 45, 135, &color); + +Draw an arc outline centered at (x,y) with radius r, non-anti-aliased +over the angle range d1 through d2 degrees. + +Parameters: + +=over + +=item * + +(x, y) - the center of the circle + +=item * + +r - the radius of the circle in pixels, must be non-negative + +=item * + +d1, d2 - the range of angles to draw the arc over, in degrees. + +=back + +Returns non-zero on success. + +Implementation: + +=cut +*/ + +int +i_arc_out(i_img *im, i_img_dim xc, i_img_dim yc, i_img_dim r, + float d1, float d2, const i_color *col) { + i_img_dim x, y; + i_img_dim dx, dy; + int error; + i_img_dim segs[2][2]; + int seg_count; + i_img_dim sin_th; + i_img_dim seg_d1, seg_d2; + int seg_num; + double inv_r; + i_img_dim scale = r + 1; + i_img_dim seg1 = scale * 2; + i_img_dim seg2 = scale * 4; + i_img_dim seg3 = scale * 6; + i_img_dim seg4 = scale * 8; + + i_clear_error(); + + if (r <= 0) { + i_push_error(0, "arc: radius must be non-negative"); + return 0; + } + if (d1 + 360 <= d2) + return i_circle_out(im, xc, yc, r, col); + + if (d1 < 0) + d1 += 360 * floor((-d1 + 359) / 360); + if (d2 < 0) + d2 += 360 * floor((-d2 + 359) / 360); + d1 = fmod(d1, 360); + d2 = fmod(d2, 360); + seg_d1 = arc_seg(d1, scale); + seg_d2 = arc_seg(d2, scale); + if (seg_d2 < seg_d1) { + /* split into two segments */ + segs[0][0] = 0; + segs[0][1] = seg_d2; + segs[1][0] = seg_d1; + segs[1][1] = seg4; + seg_count = 2; + } + else { + segs[0][0] = seg_d1; + segs[0][1] = seg_d2; + seg_count = 1; + } + + for (seg_num = 0; seg_num < seg_count; ++seg_num) { + i_img_dim seg_start = segs[seg_num][0]; + i_img_dim seg_end = segs[seg_num][1]; + if (seg_start == 0) + i_ppix(im, xc+r, yc, col); + if (seg_start <= seg1 && seg_end >= seg1) + i_ppix(im, xc, yc+r, col); + if (seg_start <= seg2 && seg_end >= seg2) + i_ppix(im, xc-r, yc, col); + if (seg_start <= seg3 && seg_end >= seg3) + i_ppix(im, xc, yc-r, col); + + y = 0; + x = r; + dy = 1; + dx = -2 * r; + error = 1 - r; + while (y < x) { + if (error >= 0) { + --x; + dx += 2; + error += dx; + } + ++y; + dy += 2; + error += dy; + + sin_th = y; + if (seg_start <= sin_th && seg_end >= sin_th) + i_ppix(im, xc + x, yc + y, col); + if (seg_start <= seg1 - sin_th && seg_end >= seg1 - sin_th) + i_ppix(im, xc + y, yc + x, col); + + if (seg_start <= seg1 + sin_th && seg_end >= seg1 + sin_th) + i_ppix(im, xc - y, yc + x, col); + if (seg_start <= seg2 - sin_th && seg_end >= seg2 - sin_th) + i_ppix(im, xc - x, yc + y, col); + + if (seg_start <= seg2 + sin_th && seg_end >= seg2 + sin_th) + i_ppix(im, xc - x, yc - y, col); + if (seg_start <= seg3 - sin_th && seg_end >= seg3 - sin_th) + i_ppix(im, xc - y, yc - x, col); + + if (seg_start <= seg3 + sin_th && seg_end >= seg3 + sin_th) + i_ppix(im, xc + y, yc - x, col); + if (seg_start <= seg4 - sin_th && seg_end >= seg4 - sin_th) + i_ppix(im, xc + x, yc - y, col); + } + } + + return 1; +} + +static double +cover(i_img_dim r, i_img_dim j) { + float rjsqrt = sqrt(r*r - j*j); + + return ceil(rjsqrt) - rjsqrt; +} + +/* +=item i_circle_out_aa(im, xc, yc, r, col) + +=synopsis i_circle_out_aa(im, 50, 50, 45, &color); + +Draw a circle outline centered at (x,y) with radius r, anti-aliased. + +Parameters: + +=over + +=item * + +(xc, yc) - the center of the circle + +=item * + +r - the radius of the circle in pixels, must be non-negative + +=item * + +col - an i_color for the color to draw in. + +=back + +Returns non-zero on success. + +=cut + +Based on "Fast Anti-Aliased Circle Generation", Xiaolin Wu, Graphics +Gems. + +I use floating point for I since for large circles the precision of +a [0,255] value isn't sufficient when approaching the end of the +octant. + +*/ + +int +i_circle_out_aa(i_img *im, i_img_dim xc, i_img_dim yc, i_img_dim r, const i_color *col) { + i_img_dim i, j; + double t; + i_color workc = *col; + int orig_alpha = col->channel[3]; + + i_clear_error(); + if (r <= 0) { + i_push_error(0, "arc: radius must be non-negative"); + return 0; + } + i = r; + j = 0; + t = 0; + i_ppix_norm(im, xc+i, yc+j, col); + i_ppix_norm(im, xc-i, yc+j, col); + i_ppix_norm(im, xc+j, yc+i, col); + i_ppix_norm(im, xc+j, yc-i, col); + + while (i > j+1) { + double d; + int cv, inv_cv; + i_color p; + int ch; + j++; + d = cover(r, j); + cv = (int)(d * 255 + 0.5); + inv_cv = 255-cv; + if (d < t) { + --i; + } + if (inv_cv) { + workc.channel[3] = orig_alpha * inv_cv / 255; + i_ppix_norm(im, xc+i, yc+j, &workc); + i_ppix_norm(im, xc-i, yc+j, &workc); + i_ppix_norm(im, xc+i, yc-j, &workc); + i_ppix_norm(im, xc-i, yc-j, &workc); + + if (i != j) { + i_ppix_norm(im, xc+j, yc+i, &workc); + i_ppix_norm(im, xc-j, yc+i, &workc); + i_ppix_norm(im, xc+j, yc-i, &workc); + i_ppix_norm(im, xc-j, yc-i, &workc); + } + } + if (cv && i > j) { + workc.channel[3] = orig_alpha * cv / 255; + i_ppix_norm(im, xc+i-1, yc+j, &workc); + i_ppix_norm(im, xc-i+1, yc+j, &workc); + i_ppix_norm(im, xc+i-1, yc-j, &workc); + i_ppix_norm(im, xc-i+1, yc-j, &workc); + + if (j != i-1) { + i_ppix_norm(im, xc+j, yc+i-1, &workc); + i_ppix_norm(im, xc-j, yc+i-1, &workc); + i_ppix_norm(im, xc+j, yc-i+1, &workc); + i_ppix_norm(im, xc-j, yc-i+1, &workc); + } + } + t = d; + } + + return 1; +} + +/* +=item i_arc_out_aa(im, xc, yc, r, d1, d2, col) + +=synopsis i_arc_out_aa(im, 50, 50, 45, 45, 125, &color); + +Draw a circle arc outline centered at (x,y) with radius r, from angle +d1 degrees through angle d2 degrees, anti-aliased. + +Parameters: + +=over + +=item * + +(xc, yc) - the center of the circle + +=item * + +r - the radius of the circle in pixels, must be non-negative + +=item * + +d1, d2 - the range of angle in degrees to draw the arc through. If +d2-d1 >= 360 a full circle is drawn. + +=back + +Returns non-zero on success. + +=cut + +Based on "Fast Anti-Aliased Circle Generation", Xiaolin Wu, Graphics +Gems. + +*/ + +int +i_arc_out_aa(i_img *im, i_img_dim xc, i_img_dim yc, i_img_dim r, float d1, float d2, const i_color *col) { + i_img_dim i, j; + double t; + i_color workc = *col; + i_img_dim segs[2][2]; + int seg_count; + i_img_dim sin_th; + i_img_dim seg_d1, seg_d2; + int seg_num; + int orig_alpha = col->channel[3]; + i_img_dim scale = r + 1; + i_img_dim seg1 = scale * 2; + i_img_dim seg2 = scale * 4; + i_img_dim seg3 = scale * 6; + i_img_dim seg4 = scale * 8; + + i_clear_error(); + if (r <= 0) { + i_push_error(0, "arc: radius must be non-negative"); + return 0; + } + if (d1 + 360 <= d2) + return i_circle_out_aa(im, xc, yc, r, col); + + if (d1 < 0) + d1 += 360 * floor((-d1 + 359) / 360); + if (d2 < 0) + d2 += 360 * floor((-d2 + 359) / 360); + d1 = fmod(d1, 360); + d2 = fmod(d2, 360); + seg_d1 = arc_seg(d1, scale); + seg_d2 = arc_seg(d2, scale); + if (seg_d2 < seg_d1) { + /* split into two segments */ + segs[0][0] = 0; + segs[0][1] = seg_d2; + segs[1][0] = seg_d1; + segs[1][1] = seg4; + seg_count = 2; + } + else { + segs[0][0] = seg_d1; + segs[0][1] = seg_d2; + seg_count = 1; + } + + for (seg_num = 0; seg_num < seg_count; ++seg_num) { + i_img_dim seg_start = segs[seg_num][0]; + i_img_dim seg_end = segs[seg_num][1]; + + i = r; + j = 0; + t = 0; + + if (seg_start == 0) + i_ppix_norm(im, xc+i, yc+j, col); + if (seg_start <= seg1 && seg_end >= seg1) + i_ppix_norm(im, xc+j, yc+i, col); + if (seg_start <= seg2 && seg_end >= seg2) + i_ppix_norm(im, xc-i, yc+j, col); + if (seg_start <= seg3 && seg_end >= seg3) + i_ppix_norm(im, xc+j, yc-i, col); + + while (i > j+1) { + int cv, inv_cv; + i_color p; + int ch; + double d; + j++; + d = cover(r, j); + cv = (int)(d * 255 + 0.5); + inv_cv = 255-cv; + if (d < t) { + --i; + } + sin_th = j; + if (inv_cv) { + workc.channel[3] = orig_alpha * inv_cv / 255; + + if (seg_start <= sin_th && seg_end >= sin_th) + i_ppix_norm(im, xc+i, yc+j, &workc); + if (seg_start <= seg2 - sin_th && seg_end >= seg2 - sin_th) + i_ppix_norm(im, xc-i, yc+j, &workc); + if (seg_start <= seg4 - sin_th && seg_end >= seg4 - sin_th) + i_ppix_norm(im, xc+i, yc-j, &workc); + if (seg_start <= seg2 + sin_th && seg_end >= seg2 + sin_th) + i_ppix_norm(im, xc-i, yc-j, &workc); + + if (i != j) { + if (seg_start <= seg1 - sin_th && seg_end >= seg1 - sin_th) + i_ppix_norm(im, xc+j, yc+i, &workc); + if (seg_start <= seg1 + sin_th && seg_end >= seg1 + sin_th) + i_ppix_norm(im, xc-j, yc+i, &workc); + if (seg_start <= seg3 + sin_th && seg_end >= seg3 + sin_th) + i_ppix_norm(im, xc+j, yc-i, &workc); + if (seg_start <= seg3 - sin_th && seg_end >= seg3 - sin_th) + i_ppix_norm(im, xc-j, yc-i, &workc); + } + } + if (cv && i > j) { + workc.channel[3] = orig_alpha * cv / 255; + if (seg_start <= sin_th && seg_end >= sin_th) + i_ppix_norm(im, xc+i-1, yc+j, &workc); + if (seg_start <= seg2 - sin_th && seg_end >= seg2 - sin_th) + i_ppix_norm(im, xc-i+1, yc+j, &workc); + if (seg_start <= seg4 - sin_th && seg_end >= seg4 - sin_th) + i_ppix_norm(im, xc+i-1, yc-j, &workc); + if (seg_start <= seg2 + sin_th && seg_end >= seg2 + sin_th) + i_ppix_norm(im, xc-i+1, yc-j, &workc); + + if (seg_start <= seg1 - sin_th && seg_end >= seg1 - sin_th) + i_ppix_norm(im, xc+j, yc+i-1, &workc); + if (seg_start <= seg1 + sin_th && seg_end >= seg1 + sin_th) + i_ppix_norm(im, xc-j, yc+i-1, &workc); + if (seg_start <= seg3 + sin_th && seg_end >= seg3 + sin_th) + i_ppix_norm(im, xc+j, yc-i+1, &workc); + if (seg_start <= seg3 - sin_th && seg_end >= seg3 - sin_th) + i_ppix_norm(im, xc-j, yc-i+1, &workc); + } + t = d; + } + } + + return 1; +} + /* =item i_box(im, x1, y1, x2, y2, color) diff --git a/imager.h b/imager.h index e44311cc..e8246cef 100644 --- a/imager.h +++ b/imager.h @@ -168,10 +168,14 @@ 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,const i_color *val, int endp); void i_line_aa (i_img *im,int x1,int y1,int x2,int y2,const i_color *val, int endp); void i_arc (i_img *im,int x,int y,float rad,float d1,float d2,const i_color *val); +int i_arc_out(i_img *im,i_img_dim x,i_img_dim y,i_img_dim rad,float d1,float d2,const i_color *val); +int i_arc_out_aa(i_img *im,i_img_dim x,i_img_dim y,i_img_dim rad,float d1,float d2,const i_color *val); void i_arc_aa (i_img *im, double x, double y, double rad, double d1, double d2, const 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,const i_color *val); +int i_circle_out (i_img *im,i_img_dim x, i_img_dim y, i_img_dim rad,const i_color *val); +int i_circle_out_aa (i_img *im,i_img_dim x, i_img_dim y, i_img_dim rad,const 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,const i_color *trans); i_img* i_copy (i_img *src); diff --git a/lib/Imager/Draw.pod b/lib/Imager/Draw.pod index c41f9c4a..139b9ea2 100644 --- a/lib/Imager/Draw.pod +++ b/lib/Imager/Draw.pod @@ -276,12 +276,17 @@ filled. See L<"Fill Parameters">. $img->arc(color=>$red, r=>20, x=>200, y=>100, d1=>10, d2=>20 ); This creates a filled red arc with a 'center' at (200, 100) and spans -10 degrees and the slice has a radius of 20. [NOTE: arc has a BUG in -it right now for large differences in angles.] +10 degrees and the slice has a radius of 20. + It's also possible to supply a C parameter. +To draw just an arc outline - just the curve, not the radius lines, +set filled to 0: + Parameters: + $img->arc(color=>$red, r=>20, x=>200, y=>100, d1=>10, d2=>20, filled=>0 ); + =over =item * @@ -316,6 +321,10 @@ aa - if true the filled arc is drawn anti-aliased. Default: false. Anti-aliased arc() is experimental for now, I'm not entirely happy with the results in some cases. +=item * + +filled - set to 0 to draw only an outline. + =back # arc going through angle zero: @@ -335,8 +344,9 @@ instead of a color parameter. $img->circle(r => 50, x=> 150, y => 150, fill=>{ hatch => 'stipple' }); -The circle is always filled but that might change, so always pass a -filled=>1 parameter if you want it to be filled. +To draw a circular outline, set C to 0: + + $img->circle(color=>$green, r=>50, x=>200, y=>100, aa=>1, filled=>0); =over @@ -361,6 +371,10 @@ fill - the fill for the filled circle. See L<"Fill Parameters"> aa - if true the filled circle is drawn anti-aliased. Default: false. +=item * + +filled - set to 0 to just draw an outline. + =back =item polygon diff --git a/t/t21draw.t b/t/t21draw.t index 318ff9c3..cf7f3581 100644 --- a/t/t21draw.t +++ b/t/t21draw.t @@ -1,16 +1,10 @@ #!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' - -######################### We start with some black magic to print on failure. - -# 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 => 43; -my $loaded; +use Test::More tests => 235; +use Imager ':all'; +use Imager::Test qw(is_color3); +use constant PI => 3.14159265358979; -BEGIN { use_ok(Imager=>':all'); } init_log("testout/t21draw.log",1); my $redobj = NC(255, 0, 0); @@ -21,126 +15,265 @@ my $blueobj = NC(0, 0, 255); my $blue = { hue=>240, saturation=>1, value=>1 }; my $white = '#FFFFFF'; -my $img = Imager->new(xsize=>100, ysize=>500); - -ok($img->box(color=>$blueobj, xmin=>10, ymin=>10, xmax=>48, ymax=>18), - "box with color obj"); -ok($img->box(color=>$blue, xmin=>10, ymin=>20, xmax=>48, ymax=>28), - "box with color"); -ok($img->box(color=>$redobj, xmin=>10, ymin=>30, xmax=>28, ymax=>48, filled=>1), - "filled box with color obj"); -ok($img->box(color=>$red, xmin=>30, ymin=>30, xmax=>48, ymax=>48, filled=>1), - "filled box with color"); - -ok($img->arc('x'=>75, 'y'=>25, r=>24, color=>$redobj), - "filled arc with colorobj"); - -ok($img->arc('x'=>75, 'y'=>25, r=>20, color=>$green), - "filled arc with colorobj"); -ok($img->arc('x'=>75, 'y'=>25, r=>18, color=>$white, d1=>325, d2=>225), - "filled arc with color"); - -ok($img->arc('x'=>75, 'y'=>25, r=>18, color=>$blue, d1=>225, d2=>325), - "filled arc with color"); -ok($img->arc('x'=>75, 'y'=>25, r=>15, color=>$green, aa=>1), - "filled arc with color"); - -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"); - -ok($img->line(color=>$red, x1=>10, y1=>55, x2=>40, y2=>95, aa=>1), - "aa line with color"); -ok($img->line(color=>$green, x1=>15, y1=>55, x2=>45, y2=>95, antialias=>1), - "antialias line with color"); - -ok($img->polyline(points=>[ [ 55, 55 ], [ 90, 60 ], [ 95, 95] ], - color=>$redobj), - "polyline points with color obj"); -ok($img->polyline('x'=>[ 55, 85, 90 ], 'y'=>[60, 65, 95], color=>$green, aa=>1), - "polyline xy with color aa"); -ok($img->polyline('x'=>[ 55, 80, 85 ], 'y'=>[65, 70, 95], color=>$green, - antialias=>1), - "polyline xy with color antialias"); - -ok($img->setpixel('x'=>[35, 37, 39], 'y'=>[55, 57, 59], color=>$red), - "set array of pixels"); -ok($img->setpixel('x'=>39, 'y'=>55, color=>$green), - "set single pixel"); -use Imager::Color::Float; -my $flred = Imager::Color::Float->new(1, 0, 0, 0); -my $flgreen = Imager::Color::Float->new(0, 1, 0, 0); -ok($img->setpixel('x'=>[41, 43, 45], 'y'=>[55, 57, 59], color=>$flred), - "set array of float pixels"); -ok($img->setpixel('x'=>45, 'y'=>55, color=>$flgreen), - "set single float pixel"); -my @gp = $img->getpixel('x'=>[41, 43, 45], 'y'=>[55, 57, 59]); -ok(grep($_->isa('Imager::Color'), @gp) == 3, "check getpixel result type"); -ok(grep(color_cmp($_, NC(255, 0, 0)) == 0, @gp) == 3, - "check getpixel result colors"); -my $gp = $img->getpixel('x'=>45, 'y'=>55); -ok($gp->isa('Imager::Color'), "check scalar getpixel type"); -ok(color_cmp($gp, NC(0, 255, 0)) == 0, "check scalar getpixel color"); -@gp = $img->getpixel('x'=>[35, 37, 39], 'y'=>[55, 57, 59], type=>'float'); -ok(grep($_->isa('Imager::Color::Float'), @gp) == 3, - "check getpixel float result type"); -ok(grep(color_cmp($_, $flred) == 0, @gp) == 3, - "check getpixel float result type"); -$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"); -angle_marker($img, 25, 175, 23, 315, 225); -ok($img->arc(x=>75, 'y'=>175, r=>20, d1=>315, d2=>225, - fill => { solid=>$greenobj, combine=>'diff' }), - "concave fill arc"); -angle_marker($img, 75, 175, 23, 315, 225); -ok($img->arc(x=>25, y=>225, r=>20, d1=>135, d2=>45, color=>$redobj), - "another concave color arc"); -angle_marker($img, 25, 225, 23, 45, 135); -ok($img->arc(x=>75, y=>225, r=>20, d1=>135, d2=>45, - fill => { solid=>$blueobj, combine=>'diff' }), - "another concave fillarc"); -angle_marker($img, 75, 225, 23, 45, 135); -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, - fill => { solid=>$blueobj, combine=>'diff' }, aa=>1), - "concave fill arc aa"); - -ok($img->circle(x=>25, y=>325, r=>20, color=>$redobj), - "color circle no aa"); -ok($img->circle(x=>75, y=>325, r=>20, color=>$redobj, aa=>1), - "color circle aa"); -ok($img->circle(x=>25, 'y'=>375, r=>20, - fill => { hatch=>'stipple', fg=>$blueobj, bg=>$redobj }), - "fill circle no aa"); -ok($img->circle(x=>75, 'y'=>375, r=>20, aa=>1, - fill => { hatch=>'stipple', fg=>$blueobj, bg=>$redobj }), - "fill circle aa"); - -ok($img->arc(x=>50, y=>450, r=>45, d1=>135, d2=>45, - fill => { solid=>$blueobj, combine=>'diff' }), - "another concave fillarc"); -angle_marker($img, 50, 450, 47, 45, 135); - -ok($img->write(file=>'testout/t21draw.ppm'), - "saving output"); +{ + my $img = Imager->new(xsize=>100, ysize=>500); + + ok($img->box(color=>$blueobj, xmin=>10, ymin=>10, xmax=>48, ymax=>18), + "box with color obj"); + ok($img->box(color=>$blue, xmin=>10, ymin=>20, xmax=>48, ymax=>28), + "box with color"); + ok($img->box(color=>$redobj, xmin=>10, ymin=>30, xmax=>28, ymax=>48, filled=>1), + "filled box with color obj"); + ok($img->box(color=>$red, xmin=>30, ymin=>30, xmax=>48, ymax=>48, filled=>1), + "filled box with color"); + + ok($img->arc('x'=>75, 'y'=>25, r=>24, color=>$redobj), + "filled arc with colorobj"); + + ok($img->arc('x'=>75, 'y'=>25, r=>20, color=>$green), + "filled arc with colorobj"); + ok($img->arc('x'=>75, 'y'=>25, r=>18, color=>$white, d1=>325, d2=>225), + "filled arc with color"); + + ok($img->arc('x'=>75, 'y'=>25, r=>18, color=>$blue, d1=>225, d2=>325), + "filled arc with color"); + ok($img->arc('x'=>75, 'y'=>25, r=>15, color=>$green, aa=>1), + "filled arc with color"); + + 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"); + + ok($img->line(color=>$red, x1=>10, y1=>55, x2=>40, y2=>95, aa=>1), + "aa line with color"); + ok($img->line(color=>$green, x1=>15, y1=>55, x2=>45, y2=>95, antialias=>1), + "antialias line with color"); + + ok($img->polyline(points=>[ [ 55, 55 ], [ 90, 60 ], [ 95, 95] ], + color=>$redobj), + "polyline points with color obj"); + ok($img->polyline('x'=>[ 55, 85, 90 ], 'y'=>[60, 65, 95], color=>$green, aa=>1), + "polyline xy with color aa"); + ok($img->polyline('x'=>[ 55, 80, 85 ], 'y'=>[65, 70, 95], color=>$green, + antialias=>1), + "polyline xy with color antialias"); + + ok($img->setpixel('x'=>[35, 37, 39], 'y'=>[55, 57, 59], color=>$red), + "set array of pixels"); + ok($img->setpixel('x'=>39, 'y'=>55, color=>$green), + "set single pixel"); + use Imager::Color::Float; + my $flred = Imager::Color::Float->new(1, 0, 0, 0); + my $flgreen = Imager::Color::Float->new(0, 1, 0, 0); + ok($img->setpixel('x'=>[41, 43, 45], 'y'=>[55, 57, 59], color=>$flred), + "set array of float pixels"); + ok($img->setpixel('x'=>45, 'y'=>55, color=>$flgreen), + "set single float pixel"); + my @gp = $img->getpixel('x'=>[41, 43, 45], 'y'=>[55, 57, 59]); + ok(grep($_->isa('Imager::Color'), @gp) == 3, "check getpixel result type"); + ok(grep(color_cmp($_, NC(255, 0, 0)) == 0, @gp) == 3, + "check getpixel result colors"); + my $gp = $img->getpixel('x'=>45, 'y'=>55); + ok($gp->isa('Imager::Color'), "check scalar getpixel type"); + ok(color_cmp($gp, NC(0, 255, 0)) == 0, "check scalar getpixel color"); + @gp = $img->getpixel('x'=>[35, 37, 39], 'y'=>[55, 57, 59], type=>'float'); + ok(grep($_->isa('Imager::Color::Float'), @gp) == 3, + "check getpixel float result type"); + ok(grep(color_cmp($_, $flred) == 0, @gp) == 3, + "check getpixel float result type"); + $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"); + angle_marker($img, 25, 175, 23, 315, 225); + ok($img->arc(x=>75, 'y'=>175, r=>20, d1=>315, d2=>225, + fill => { solid=>$greenobj, combine=>'diff' }), + "concave fill arc"); + angle_marker($img, 75, 175, 23, 315, 225); + ok($img->arc(x=>25, y=>225, r=>20, d1=>135, d2=>45, color=>$redobj), + "another concave color arc"); + angle_marker($img, 25, 225, 23, 45, 135); + ok($img->arc(x=>75, y=>225, r=>20, d1=>135, d2=>45, + fill => { solid=>$blueobj, combine=>'diff' }), + "another concave fillarc"); + angle_marker($img, 75, 225, 23, 45, 135); + 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, + fill => { solid=>$blueobj, combine=>'diff' }, aa=>1), + "concave fill arc aa"); + + ok($img->circle(x=>25, y=>325, r=>20, color=>$redobj), + "color circle no aa"); + ok($img->circle(x=>75, y=>325, r=>20, color=>$redobj, aa=>1), + "color circle aa"); + ok($img->circle(x=>25, 'y'=>375, r=>20, + fill => { hatch=>'stipple', fg=>$blueobj, bg=>$redobj }), + "fill circle no aa"); + ok($img->circle(x=>75, 'y'=>375, r=>20, aa=>1, + fill => { hatch=>'stipple', fg=>$blueobj, bg=>$redobj }), + "fill circle aa"); + + ok($img->arc(x=>50, y=>450, r=>45, d1=>135, d2=>45, + fill => { solid=>$blueobj, combine=>'diff' }), + "another concave fillarc"); + angle_marker($img, 50, 450, 47, 45, 135); + + ok($img->write(file=>'testout/t21draw.ppm'), + "saving output"); +} + +{ + my $im = Imager->new(xsize => 400, ysize => 400); + ok($im->arc(x => 200, y => 202, r => 10, filled => 0), + "draw circle outline"); + is_color3($im->getpixel(x => 200, y => 202), 0, 0, 0, + "check center not filled"); + ok($im->arc(x => 198, y => 200, r => 13, filled => 0, color => "#f88"), + "draw circle outline"); + is_color3($im->getpixel(x => 198, y => 200), 0, 0, 0, + "check center not filled"); + ok($im->arc(x => 200, y => 200, r => 24, filled => 0, color => "#0ff"), + "draw circle outline"); + my $r = 40; + while ($r < 180) { + ok($im->arc(x => 200, y => 200, r => $r, filled => 0, color => "#ff0"), + "draw circle outline r $r"); + $r += 15; + } + ok($im->write(file => "testout/t21circout.ppm"), + "save arc outline"); +} + +{ + my $im = Imager->new(xsize => 400, ysize => 400); + { + my $lc = Imager::Color->new(32, 32, 32); + my $an = 0; + while ($an < 360) { + my $an_r = $an * PI / 180; + my $ca = cos($an_r); + my $sa = sin($an_r); + $im->line(aa => 1, color => $lc, + x1 => 198 + 5 * $ca, y1 => 202 + 5 * $sa, + x2 => 198 + 190 * $ca, y2 => 202 + 190 * $sa); + $an += 5; + } + } + my $d1 = 0; + my $r = 20; + while ($d1 < 350) { + ok($im->arc(x => 198, y => 202, r => $r, d1 => $d1, d2 => $d1+300, filled => 0), + "draw arc outline r$r d1$d1 len 300"); + ok($im->arc(x => 198, y => 202, r => $r+3, d1 => $d1, d2 => $d1+40, filled => 0, color => '#FFFF00'), + "draw arc outline r$r d1$d1 len 40"); + $d1 += 15; + $r += 6; + } + is_color3($im->getpixel(x => 198, y => 202), 0, 0, 0, + "check center not filled"); + ok($im->write(file => "testout/t21arcout.ppm"), + "save arc outline"); +} + +{ + my $im = Imager->new(xsize => 400, ysize => 400); + ok($im->arc(x => 197, y => 201, r => 10, filled => 0, aa => 1, color => 'white'), + "draw circle outline"); + is_color3($im->getpixel(x => 197, y => 201), 0, 0, 0, + "check center not filled"); + ok($im->arc(x => 197, y => 205, r => 13, filled => 0, color => "#f88", aa => 1), + "draw circle outline"); + is_color3($im->getpixel(x => 197, y => 205), 0, 0, 0, + "check center not filled"); + ok($im->arc(x => 190, y => 215, r => 24, filled => 0, color => [0,0, 255, 128], aa => 1), + "draw circle outline"); + my $r = 40; + while ($r < 190) { + ok($im->arc(x => 197, y => 201, r => $r, filled => 0, aa => 1, color => '#ff0'), "draw aa circle rad $r"); + $r += 7; + } + ok($im->write(file => "testout/t21aacircout.ppm"), + "save arc outline"); +} + +{ + my $im = Imager->new(xsize => 400, ysize => 400); + { + my $lc = Imager::Color->new(32, 32, 32); + my $an = 0; + while ($an < 360) { + my $an_r = $an * PI / 180; + my $ca = cos($an_r); + my $sa = sin($an_r); + $im->line(aa => 1, color => $lc, + x1 => 198 + 5 * $ca, y1 => 202 + 5 * $sa, + x2 => 198 + 190 * $ca, y2 => 202 + 190 * $sa); + $an += 5; + } + } + my $d1 = 0; + my $r = 20; + while ($d1 < 350) { + ok($im->arc(x => 198, y => 202, r => $r, d1 => $d1, d2 => $d1+300, filled => 0, aa => 1), + "draw aa arc outline r$r d1$d1 len 300"); + ok($im->arc(x => 198, y => 202, r => $r+3, d1 => $d1, d2 => $d1+40, filled => 0, color => '#FFFF00', aa => 1), + "draw aa arc outline r$r d1$d1 len 40"); + $d1 += 15; + $r += 6; + } + is_color3($im->getpixel(x => 198, y => 202), 0, 0, 0, + "check center not filled"); + ok($im->write(file => "testout/t21aaarcout.ppm"), + "save arc outline"); +} + +{ + my $im = Imager->new(xsize => 400, ysize => 400); + + my $an = 0; + my $step = 15; + while ($an <= 360-$step) { + my $cx = int(200 + 20 * cos(($an+$step/2) * PI / 180)); + my $cy = int(200 + 20 * sin(($an+$step/2) * PI / 180)); + + ok($im->arc(x => $cx, y => $cy, aa => 1, color => "#fff", + d1 => $an, d2 => $an+$step, filled => 0, r => 170), + "angle starting from $an"); + ok($im->arc(x => $cx, y => $cy, aa => 1, color => "#ff0", + d1 => $an, d2 => $an+$step, r => 168), + "filled angle starting from $an"); + + $an += $step; + } + ok($im->write(file => "testout/t21aaarcs.ppm"), + "save arc outline"); +} + malloc_state(); +unless ($ENV{IMAGER_KEEP_FILES}) { + unlink "testout/t21draw.ppm"; + unlink "testout/t21circout.ppm"; + unlink "testout/t21aacircout.ppm"; + unlink "testout/t21arcout.ppm"; + unlink "testout/t21aaarcout.ppm"; + unlink "testout/t21aaarcs.ppm"; +} + sub color_cmp { my ($l, $r) = @_; my @l = $l->rgba; @@ -151,8 +284,6 @@ sub color_cmp { || $l[2] <=> $r[2]; } -use constant PI => 4 * atan2(1,1); - sub angle_marker { my ($img, $x, $y, $radius, @angles) = @_; diff --git a/typemap b/typemap index 4438d086..28159ad7 100644 --- a/typemap +++ b/typemap @@ -14,6 +14,7 @@ undef_int T_IV_U undef_neg_int T_IV_NEGU HASH T_HVREF utf8_str T_UTF8_STR +i_img_dim T_IV # these types are for use by Inline, which can't handle types containing :: Imager__Color T_PTROBJ_INV