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

The following code was used to generate this page:

+ +
+EOS
+
+open SELF, "< $0"
+  or die "Can't open myself: $!";
+while () {
+  print HTML encode_entities($_);
+}
+close SELF;
+
+print HTML <
+
+
+ +Send errors/fixes/suggestions to: tony_at_develop-help.com + +
Filled areaClose-upName
$name
+ + +EOS + +close HTML; diff --git a/t/t20fill.t b/t/t20fill.t new file mode 100644 index 00000000..5def8d70 --- /dev/null +++ b/t/t20fill.t @@ -0,0 +1,138 @@ +#!perl -w +use strict; + +use Imager ':handy'; +use Imager::Fill; +use Imager::Color::Float; + +Imager::init_log("testout/t20fill.log", 1); + +print "1..18\n"; + +my $blue = NC(0,0,255); +my $red = NC(255, 0, 0); +my $redf = Imager::Color::Float->new(1, 0, 0); +my $rsolid = Imager::i_new_fill_solid($blue, 0); +ok(1, $rsolid, "building solid fill"); +my $raw1 = Imager::ImgRaw::new(100, 100, 3); +# use the normal filled box +Imager::i_box_filled($raw1, 0, 0, 99, 99, $blue); +my $raw2 = Imager::ImgRaw::new(100, 100, 3); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rsolid); +ok(2, 1, "drawing with solid fill"); +my $diff = Imager::i_img_diff($raw1, $raw2); +ok(3, $diff == 0, "solid fill doesn't match"); +Imager::i_box_filled($raw1, 0, 0, 99, 99, $red); +my $rsolid2 = Imager::i_new_fill_solidf($redf, 0); +ok(4, $rsolid2, "creating float solid fill"); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rsolid2); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(5, $diff == 0, "float solid fill doesn't match"); + +# ok solid still works, let's try a hatch +# hash1 is a 2x2 checkerboard +my $rhatcha = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 0, 0); +my $rhatchb = Imager::i_new_fill_hatch($blue, $red, 0, 1, undef, 2, 0); +ok(6, $rhatcha && $rhatchb, "can't build hatched fill"); + +# the offset should make these match +Imager::i_box_cfill($raw1, 0, 0, 99, 99, $rhatcha); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb); +ok(7, 1, "filling with hatch"); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(8, $diff == 0, "hatch images different"); +$rhatchb = Imager::i_new_fill_hatch($blue, $red, 0, 1, undef, 4, 6); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(9, $diff == 0, "hatch images different"); + +# I guess I was tired when I originally did this - make sure it keeps +# acting the way it's meant to +# I had originally expected these to match with the red and blue swapped +$rhatchb = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 2, 2); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(10, $diff == 0, "hatch images different"); + +# this shouldn't match +$rhatchb = Imager::i_new_fill_hatch($red, $blue, 0, 1, undef, 1, 1); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rhatchb); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(11, $diff, "hatch images the same!"); + +# custom hatch +# the inverse of the 2x2 checkerboard +my $hatch = pack("C8", 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC); +my $rcustom = Imager::i_new_fill_hatch($blue, $red, 0, 0, $hatch, 0, 0); +Imager::i_box_cfill($raw2, 0, 0, 99, 99, $rcustom); +$diff = Imager::i_img_diff($raw1, $raw2); +ok(12, !$diff, "custom hatch mismatch"); + +# test the oo interface +my $im1 = Imager->new(xsize=>100, ysize=>100); +my $im2 = Imager->new(xsize=>100, ysize=>100); + +my $solid = Imager::Fill->new(solid=>$red); +ok(13, $solid, "creating oo solid fill"); +ok(14, $solid->{fill}, "bad oo solid fill"); +$im1->box(fill=>$solid); +$im2->box(filled=>1, color=>$red); +$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG}); +ok(15, !$diff, "oo solid fill"); + +my $hatcha = Imager::Fill->new(hatch=>'check2x2'); +my $hatchb = Imager::Fill->new(hatch=>'check2x2', dx=>2); +$im1->box(fill=>$hatcha); +$im2->box(fill=>$hatchb); +# should be different +$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG}); +ok(16, $diff, "offset checks the same!"); +$hatchb = Imager::Fill->new(hatch=>'check2x2', dx=>2, dy=>2); +$im2->box(fill=>$hatchb); +$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG}); +ok(17, !$diff, "offset into similar check should be the same"); + +# test dymanic build of fill +$im2->box(fill=>{hatch=>'check2x2', dx=>2, fg=>NC(255,255,255), + bg=>NC(0,0,0)}); +$diff = Imager::i_img_diff($im1->{IMG}, $im2->{IMG}); +ok(18, !$diff, "offset and flipped should be the same"); + +# a simple demo +my $im = Imager->new(xsize=>200, ysize=>200); + +$im->box(xmin=>10, ymin=>10, xmax=>190, ymax=>190, + fill=>{ hatch=>'check4x4', + fg=>NC(128, 0, 0), + bg=>NC(128, 64, 0) }); +$im->arc(r=>80, d1=>45, d2=>75, + fill=>{ hatch=>'stipple2', + combine=>1, + fg=>NC(0, 0, 0, 255), + bg=>NC(255,255,255,192) }); +$im->arc(r=>80, d1=>75, d2=>135, + fill=>{ fountain=>'radial', xa=>100, ya=>100, xb=>20, yb=>100 }); +$im->write(file=>'testout/t20_sample.ppm'); + +sub ok { + my ($num, $test, $desc) = @_; + + if ($test) { + print "ok $num\n"; + } + else { + print "not ok $num # $desc\n"; + } +} + +# for use during testing +sub save { + my ($im, $name) = @_; + + open FH, "> $name" or die "Cannot create $name: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + Imager::i_writeppm_wiol($im, $io) or die "Cannot save to $name"; + undef $io; + close FH; +} diff --git a/t/t61filters.t b/t/t61filters.t index 6e6e3503..6aca7e1f 100644 --- a/t/t61filters.t +++ b/t/t61filters.t @@ -2,6 +2,7 @@ use strict; use Imager qw(:handy); +Imager::init_log("testout/t61filters.log", 1); # meant for testing the filters themselves my $imbase = Imager->new; $imbase->open(file=>'testout/t104.ppm') or die; diff --git a/typemap b/typemap index f8b7a5b9..f39ed30e 100644 --- a/typemap +++ b/typemap @@ -5,6 +5,7 @@ Imager::ImgRaw T_PTROBJ Imager::TTHandle T_PTROBJ Imager::IO T_PTROBJ Imager::Font::FT2 T_PTROBJ +Imager::FillHandle T_PTROBJ const char * T_PV float T_FLOAT float* T_ARRAY -- 2.39.5