Skip to content
Browse files

support for generic fills for box and arc, with solid, hatched

and fountain fills implemented
  • Loading branch information...
1 parent a010a7d commit f1ac5027d2bc4e16dc38c54cf5360415bfb4376d Tony Cook committed Sep 1, 2001
Showing with 1,527 additions and 207 deletions.
  1. +7 −0 Changes
  2. +42 −3 Imager.pm
  3. +169 −70 Imager.xs
  4. +1 −0 MANIFEST
  5. +1 −1 Makefile.PL
  6. +1 −4 TODO
  7. +98 −0 draw.c
  8. +395 −0 fills.c
  9. +231 −127 filters.c
  10. +46 −0 image.h
  11. +2 −2 io.c
  12. +308 −0 lib/Imager/Fill.pm
  13. +86 −0 samples/hatches.pl
  14. +138 −0 t/t20fill.t
  15. +1 −0 t/t61filters.t
  16. +1 −0 typemap
View
7 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
=================================================================
View
45 Imager.pm
@@ -1602,8 +1602,22 @@ sub box {
$opts{'ymax'} = max($opts{'box'}->[1],$opts{'box'}->[3]);
}
- if ($opts{filled}) { i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
- else { i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); }
+ if ($opts{filled}) {
+ i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+ $opts{ymax},$opts{color});
+ }
+ elsif ($opts{fill}) {
+ unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+ # assume it's a hash ref
+ require 'Imager/Fill.pm';
+ $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+ }
+ i_box_cfill($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},
+ $opts{ymax},$opts{fill}{fill});
+ }
+ else {
+ i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color});
+ }
return $self;
}
@@ -1618,7 +1632,20 @@ sub arc {
'x'=>$self->getwidth()/2,
'y'=>$self->getheight()/2,
'd1'=>0, 'd2'=>361, @_);
- i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},$opts{'d2'},$opts{'color'});
+ if ($opts{fill}) {
+ unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) {
+ # assume it's a hash ref
+ require 'Imager/Fill.pm';
+ $opts{fill} = Imager::Fill->new(%{$opts{fill}});
+ }
+ i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+ $opts{'d2'}, $opts{fill}{fill});
+ }
+ else {
+ i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},
+ $opts{'d2'},$opts{'color'});
+ }
+
return $self;
}
@@ -2731,6 +2758,18 @@ Arc:
This creates a filled red arc with a 'center' at (200, 100) and spans
10 degrees and the slice has a radius of 20. SEE section on BUGS.
+Both the arc() and box() methods can take a C<fill> parameter which
+can either be an Imager::Fill object, or a reference to a hash
+containing the parameters used to create the fill:
+
+ $img->box(xmin=>10, ymin=>30, xmax=>150, ymax=>60,
+ fill => { hatch=>'cross2' });
+ use Imager::Fill;
+ my $fill = Imager::Fill->new(hatch=>'stipple');
+ $img->box(fill=>$fill);
+
+See L<Imager::Fill> for the type of fills you can use.
+
Circle:
$img->circle(color=>$green, r=50, x=>200, y=>100);
View
239 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,13 +412,96 @@ 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
*/
#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
@@ -668,6 +750,15 @@ i_box_filled(im,x1,y1,x2,y2,val)
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
int x
@@ -677,6 +768,16 @@ i_arc(im,x,y,rad,d1,d2,val)
float d2
Imager::Color val
+void
+i_arc_cfill(im,x,y,rad,d1,d2,fill)
+ Imager::ImgRaw im
+ int x
+ int y
+ float rad
+ float d1
+ float d2
+ Imager::FillHandle fill
+
void
@@ -1896,87 +1997,46 @@ i_fountain(im, xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_para
int super_sample
double ssample_param
PREINIT:
- int i, j;
AV *asegs;
- AV *aseg;
- SV *sv;
int count;
i_fountain_seg *segs;
- double work[3];
- int worki[2];
CODE:
- /* Each element of segs must contain:
- [ start, middle, end, c0, c1, segtype, colortrans ]
- start, middle, end are doubles from 0 to 1
- c0, c1 are Imager::Color::Float or Imager::Color objects
- segtype, colortrans are ints
- */
if (!SvROK(ST(10)) || ! SvTYPE(SvRV(ST(10))))
croak("i_fountain: argument 11 must be an array ref");
asegs = (AV *)SvRV(ST(10));
-
- count = av_len(asegs)+1;
- if (count < 1)
- croak("i_fountain must have at least one segment");
- segs = mymalloc(sizeof(i_fountain_seg) * count);
- for(i = 0; i<count; i++) {
- SV **sv1 = av_fetch(asegs, i, 0);
- if (!sv1 || !*sv1 || !SvROK(*sv1)
- || SvTYPE(SvRV(*sv1)) != SVt_PVAV) {
- myfree(segs);
- croak("i_fountain: segs must be an arrayref of arrayrefs");
- }
- aseg = (AV *)SvRV(*sv1);
- if (av_len(aseg) != 7-1) {
- myfree(segs);
- croak("i_fountain: a segment must have 7 members");
- }
- for (j = 0; j < 3; ++j) {
- SV **sv2 = av_fetch(aseg, j, 0);
- if (!sv2 || !*sv2) {
- myfree(segs);
- croak("i_fountain: XS error");
- }
- work[j] = SvNV(*sv2);
- }
- segs[i].start = work[0];
- segs[i].middle = work[1];
- segs[i].end = work[2];
- for (j = 0; j < 2; ++j) {
- SV **sv3 = av_fetch(aseg, 3+j, 0);
- if (!sv3 || !*sv3 || !SvROK(*sv3) ||
- (!sv_derived_from(*sv3, "Imager::Color")
- && !sv_derived_from(*sv3, "Imager::Color::Float"))) {
- myfree(segs);
- croak("i_fountain: segs must contain colors in elements 3 and 4");
- }
- if (sv_derived_from(*sv3, "Imager::Color::Float")) {
- segs[i].c[j] = *(i_fcolor *)SvIV((SV *)SvRV(*sv3));
- }
- else {
- i_color c = *(i_color *)SvIV((SV *)SvRV(*sv3));
- int ch;
- for (ch = 0; ch < MAXCHANNELS; ++ch) {
- segs[i].c[j].channel[ch] = c.channel[ch] / 255.0;
- }
- }
- }
- for (j = 0; j < 2; ++j) {
- SV **sv2 = av_fetch(aseg, j+5, 0);
- if (!sv2 || !*sv2) {
- myfree(segs);
- croak("i_fountain: XS error");
- }
- worki[j] = SvIV(*sv2);
- }
- segs[i].type = worki[0];
- segs[i].color = worki[1];
- }
+ segs = load_fount_segs(asegs, &count);
i_fountain(im, xa, ya, xb, yb, type, repeat, combine, super_sample,
ssample_param, count, segs);
myfree(segs);
+Imager::FillHandle
+i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, segs)
+ double xa
+ double ya
+ double xb
+ double yb
+ int type
+ int repeat
+ int combine
+ int super_sample
+ double ssample_param
+ PREINIT:
+ AV *asegs;
+ int count;
+ i_fountain_seg *segs;
+ CODE:
+ if (!SvROK(ST(9)) || ! SvTYPE(SvRV(ST(9))))
+ croak("i_fountain: argument 11 must be an array ref");
+
+ asegs = (AV *)SvRV(ST(9));
+ segs = load_fount_segs(asegs, &count);
+ RETVAL = i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine,
+ super_sample, ssample_param, count, segs);
+ myfree(segs);
+ OUTPUT:
+ RETVAL
+
void
i_errors()
PREINIT:
@@ -2989,3 +3049,42 @@ ft2_transform_box(font, x0, x1, x2, x3)
#endif
+MODULE = Imager PACKAGE = Imager::FillHandle PREFIX=IFILL_
+
+void
+IFILL_DESTROY(fill)
+ Imager::FillHandle fill
+
+MODULE = Imager PACKAGE = Imager
+
+Imager::FillHandle
+i_new_fill_solid(cl, combine)
+ Imager::Color cl
+ int combine
+
+Imager::FillHandle
+i_new_fill_solidf(cl, combine)
+ Imager::Color::Float cl
+ int combine
+
+Imager::FillHandle
+i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy)
+ Imager::Color fg
+ Imager::Color bg
+ int combine
+ int hatch
+ int dx
+ int dy
+ PREINIT:
+ unsigned char *cust_hatch;
+ STRLEN len;
+ CODE:
+ if (SvOK(ST(4))) {
+ cust_hatch = SvPV(ST(4), len);
+ }
+ else
+ cust_hatch = NULL;
+ RETVAL = i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy);
+ OUTPUT:
+ RETVAL
+
View
1 MANIFEST
@@ -10,6 +10,7 @@ conv.c
convert.c
draw.c
draw.h
+fills.c Generic fills
map.c
error.c
gaussian.c
View
2 Makefile.PL
@@ -63,7 +63,7 @@ if (defined $Config{'d_dlsymun'}) { $OSDEF .= ' -DDLSYMUN'; }
filters.o dynaload.o stackmach.o datatypes.o
regmach.o trans2.o quant.o error.o convert.o
map.o tags.o palimg.o maskimg.o img16.o rotate.o
- bmp.o color.o);
+ bmp.o color.o fills.o);
%opts=(
'NAME' => 'Imager',
View
5 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
View
98 draw.c
@@ -46,6 +46,45 @@ i_mmarray_render(i_img *im,i_mmarray *ar,i_color *val) {
for(i=0;i<ar->lines;i++) if (ar->data[i].max!=-1) for(x=ar->data[i].min;x<ar->data[i].max;x++) i_ppix(im,x,i,val);
}
+void
+i_mmarray_render_fill(i_img *im,i_mmarray *ar,i_fill_t *fill) {
+ int x, w, y;
+ if (im->bits == i_8_bits && fill->fill_with_color) {
+ i_color *line = mymalloc(sizeof(i_color) * im->xsize);
+ for(y=0;y<ar->lines;y++) {
+ if (ar->data[y].max!=-1) {
+ x = ar->data[y].min;
+ w = ar->data[y].max-ar->data[y].min;
+
+ if (fill->combines)
+ i_glin(im, x, x+w, y, line);
+
+ (fill->fill_with_color)(fill, x, y, w, im->channels, line);
+ i_plin(im, x, x+w, y, line);
+ }
+ }
+
+ myfree(line);
+ }
+ else {
+ i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
+ for(y=0;y<ar->lines;y++) {
+ if (ar->data[y].max!=-1) {
+ x = ar->data[y].min;
+ w = ar->data[y].max-ar->data[y].min;
+
+ if (fill->combines)
+ i_glinf(im, x, x+w, y, line);
+
+ (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line);
+ i_plinf(im, x, x+w, y, line);
+ }
+ }
+
+ myfree(line);
+ }
+}
+
static
void
@@ -116,6 +155,35 @@ i_arc(i_img *im,int x,int y,float rad,float d1,float d2,i_color *val) {
i_mmarray_render(im,&dot,val);
}
+void
+i_arc_cfill(i_img *im,int x,int y,float rad,float d1,float d2,i_fill_t *fill) {
+ i_mmarray dot;
+ float f,fx,fy;
+ int x1,y1;
+
+ mm_log((1,"i_arc_cfill(im* 0x%x,x %d,y %d,rad %.2f,d1 %.2f,d2 %.2f,fill 0x%x)\n",im,x,y,rad,d1,d2,fill));
+
+ i_mmarray_cr(&dot,im->ysize);
+
+ x1=(int)(x+0.5+rad*cos(d1*PI/180.0));
+ y1=(int)(y+0.5+rad*sin(d1*PI/180.0));
+ fx=(float)x1; fy=(float)y1;
+
+ /* printf("x1: %d.\ny1: %d.\n",x1,y1); */
+ i_arcdraw(x, y, x1, y1, &dot);
+
+ x1=(int)(x+0.5+rad*cos(d2*PI/180.0));
+ y1=(int)(y+0.5+rad*sin(d2*PI/180.0));
+
+ for(f=d1;f<=d2;f+=0.01) i_mmarray_add(&dot,(int)(x+0.5+rad*cos(f*PI/180.0)),(int)(y+0.5+rad*sin(f*PI/180.0)));
+
+ /* printf("x1: %d.\ny1: %d.\n",x1,y1); */
+ i_arcdraw(x, y, x1, y1, &dot);
+
+ /* dot.info(); */
+ i_mmarray_render_fill(im,&dot,fill);
+}
+
/* Temporary AA HACK */
@@ -283,6 +351,36 @@ i_box_filled(i_img *im,int x1,int y1,int x2,int y2,i_color *val) {
for(x=x1;x<x2+1;x++) for (y=y1;y<y2+1;y++) i_ppix(im,x,y,val);
}
+void
+i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) {
+ mm_log((1,"i_box_cfill(im* 0x%x,x1 %d,y1 %d,x2 %d,y2 %d,fill 0x%x)\n",im,x1,y1,x2,y2,fill));
+
+ ++x2;
+ if (im->bits == i_8_bits && fill->fill_with_color) {
+ i_color *line = mymalloc(sizeof(i_color) * (x2 - x1));
+ while (y1 <= y2) {
+ if (fill->combines)
+ i_glin(im, x1, x2, y1, line);
+
+ (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line);
+ i_plin(im, x1, x2, y1, line);
+ ++y1;
+ }
+ myfree(line);
+ }
+ else {
+ i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1));
+ while (y1 <= y2) {
+ if (fill->combines)
+ i_glinf(im, x1, x2, y1, line);
+
+ (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line);
+ i_plinf(im, x1, x2, y1, line);
+ ++y1;
+ }
+ myfree(line);
+ }
+}
void
i_draw(i_img *im,int x1,int y1,int x2,int y2,i_color *val) {
View
395 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;
+ }
+}
View
358 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;
}
}
@@ -1726,6 +1781,55 @@ fount_r_tri_both(double 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
=head1 AUTHOR
View
46 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 */
View
4 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;
}
View
308 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<solid> which
+should be the color to fill with.
+
+=head2 Hatched fills
+
+ my $fill = Imager::Fill->new(hatch=>$type, fg=>$fgcolor, bg=>$bgcolor,
+ dx=>$dx, $dy=>$dy);
+
+Creates a hatched fill. You can specify the following keywords:
+
+=over
+
+=item hatch
+
+The type of hatch to perform, this can either be the numeric index of
+the hatch (not recommended), the symbolic name of the hatch, or an
+array of 8 integers which specify the pattern of the hatch.
+
+Hatches are represented as cells 8x8 arrays of bits, which limits their
+complexity.
+
+Current hatch names are:
+
+=over
+
+=item check1x1, check2x2, check4x4
+
+checkerboards at varios sizes
+
+=item vline1, vline2, vline4
+
+1, 2, or 4 vertical lines per cell
+
+=item hline1, hline2, hline4
+
+1, 2, or 4 horizontal lines per cell
+
+=item slash1, slash2
+
+1 or 2 / lines per cell.
+
+=item slosh1, slosh2
+
+1 or 2 \ lines per cell
+
+=item grid1, grid2, grid4
+
+1, 2, or 4 vertical and horizontal lines per cell
+
+=item dots1, dots4, dots16
+
+1, 4 or 16 dots per cell
+
+=item stipple, stipple2
+
+see the samples
+
+=item weave
+
+I hope this one is obvious.
+
+=item cross1, cross2
+
+2 densities of crosshatch
+
+=item vlozenge, hlozenge
+
+something like lozenge tiles
+
+=item scalesdown, scalesup, scalesleft, scalesright
+
+Vaguely like fish scales in each direction.
+
+=item tile_L
+
+L-shaped tiles
+
+=back
+
+=item fg
+
+=item bg
+
+The fg color is rendered where bits are set in the hatch, and the bg
+where they are clear. If you use a transparent fg or bg, and set
+combine, you can overlay the hatch onto an existing image.
+
+fg defaults to black, bg to white.
+
+=item dx
+
+=item dy
+
+An offset into the hatch cell. Both default to zero.
+
+=back
+
+You can call Imager::Fill->hatches for a list of hatch names.
+
+=head2 Fountain fills
+
+ my $fill = Imager::Fill->new(fountain=>$ftype,
+ xa=>$xa, ya=>$ya, xb=>$xb, yb=>$yb,
+ segment=>$segments, repeat=>$repeat, combine=>$combine,
+ super_sample=>$super_sample, ssample_param=>$ssample_param);
+
+This fills the given region with a fountain fill. This is exactly the
+same fill as the C<fountain> filter, but is restricted to the shape
+you are drawing, and the fountain parameter supplies the fill type,
+and is required.
+
+=head1 FUTURE PLANS
+
+I'm planning on adding the following types of fills:
+
+=over
+
+=item image
+
+tiled image fill
+
+=item checkerboard
+
+combines 2 other fills in a checkerboard
+
+=item combine
+
+combines 2 other fills using the levels of an image
+
+=item regmach
+
+uses the transform2() register machine to create fills
+
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=cut
View
86 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 <<EOS;
+<HTML><HEAD><TITLE>Imager - Hatched Fills</TITLE></HEAD><BODY BGCOLOR="FFFFFF">
+
+<CENTER><FONT FACE="Helvetica, Arial" SIZE="6" COLOR="CC0000"><B>
+Hatched Fills
+</FONT></B></CENTER>
+<HR WIDTH="65%" NOSHADE>
+<TABLE><TR><TD WIDTH="70%">
+
+<TABLE>
+<TR><TH>Filled area</TH><TH>Close-up</TH><TH>Name</TH></TR>
+EOS
+
+my $red = NC(255, 0, 0);
+my $yellow = NC(255, 255, 0);
+
+# sort of a spiral
+my $custom = [ 0xFF, 0x01, 0x7D, 0x45, 0x5D, 0x41, 0x7F, 0x00 ];
+
+for my $hatch (Imager::Fill->hatches, $custom) {
+ my $area = Imager->new(xsize=>100, ysize=>100);
+ $area->box(xmax=>50, fill => { hatch => $hatch });
+ $area->box(xmin=>50,
+ fill => { hatch => $hatch,
+ fg=>$red,
+ bg=>$yellow });
+ my $name = ref($hatch) ? "custom" : $hatch;
+
+ $area->write(file=>"hatches/area_$name.png")
+ or die "Cannot save hatches/area_$name.png: ",$area->errstr;
+
+ my $subset = $area->crop(width=>20, height=>20);
+ # we use the HTML to zoom up
+ $subset->write(file=>"hatches/zoom_$name.png")
+ or die "Cannot save hatches/zoom_$name.png: ",$subset->errstr;
+
+ print HTML <<EOS;
+<TR>
+ <TD><IMG SRC="hatches/area_$name.png" WIDTH="100" HEIGHT="100" BORDER=1></TD>
+ <TD><IMG SRC="hatches/zoom_$name.png" WIDTH="100" HEIGHT="100" BORDER=1></TD>
+ <TD>$name</TD>
+</TR>
+EOS
+}
+
+print HTML <<EOS;
+</TABLE>
+
+<P>The following code was used to generate this page:</p>
+
+<PRE>
+EOS
+
+open SELF, "< $0"
+ or die "Can't open myself: $!";
+while (<SELF>) {
+ print HTML encode_entities($_);
+}
+close SELF;
+
+print HTML <<EOS;
+</PRE>
+
+<HR WIDTH="75%" NOSHADE ALIGN="LEFT">
+
+Send errors/fixes/suggestions to: <B>tony</B>_at_<B>develop-help.com</B>
+
+</TD></TR></TABLE>
+</BODY>
+</HTML>
+EOS
+
+close HTML;
View
138 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;
+}
View
1 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;
View
1 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

0 comments on commit f1ac502

Please sign in to comment.
Something went wrong with that request. Please try again.