From: Tony Cook Date: Sun, 9 Sep 2001 14:48:00 +0000 (+0000) Subject: minor error handling in bmp.c X-Git-Tag: Imager-0.48^2~584 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/efdc2568ee6be1b2922fd89f9d0d371ddcd7aec4 minor error handling in bmp.c more complex fill combining types --- diff --git a/Imager.pm b/Imager.pm index ad053a6e..9469fa81 100644 --- a/Imager.pm +++ b/Imager.pm @@ -290,6 +290,21 @@ BEGIN { random => 2, circle => 3, }, + combine => { + none => 0, + normal => 1, + multiply => 2, mult => 2, + dissolve => 3, + add => 4, + subtract => 5, sub => 5, + diff => 6, + lighten => 7, + darken => 8, + hue => 9, + sat => 10, + value => 11, + color => 12, + }, }, defaults => { ftype => 0, repeat => 0, combine => 0, super_sample => 0, ssample_param => 4, @@ -2960,7 +2975,7 @@ source. bumpmap bump elevation(0) lightx lighty st(2) contrast intensity conv coef - fountain xa ya xb yb ftype(linear) repeat(none) combine(0) + fountain xa ya xb yb ftype(linear) repeat(none) combine(none) super_sample(none) ssample_param(4) segments(see below) gaussian stddev gradgen xo yo colors dist @@ -3080,7 +3095,8 @@ for a linear fill). By default the fill simply overwrites the whole image (unless you have parts of the range 0 through 1 that aren't covered by a segment), if any segments of your fill have any transparency, you can set the -I option to 1 to have the fill combined with the existing pixels. +I option to 'normal' to have the fill combined with the +existing pixels. See the description of I in L. If your fill has sharp edges, for example between steps if you use repeat set to 'triangle', you may see some aliased or ragged edges. diff --git a/Imager.xs b/Imager.xs index 955cbc2f..3299e07d 100644 --- a/Imager.xs +++ b/Imager.xs @@ -543,7 +543,26 @@ ICL_rgba(cl) PUSHs(sv_2mortal(newSVnv(cl->rgba.b))); PUSHs(sv_2mortal(newSVnv(cl->rgba.a))); - +Imager::Color +i_hsv_to_rgb(c) + Imager::Color c + CODE: + RETVAL = mymalloc(sizeof(i_color)); + *RETVAL = *c; + i_hsv_to_rgb(RETVAL); + OUTPUT: + RETVAL + +Imager::Color +i_rgb_to_hsv(c) + Imager::Color c + CODE: + RETVAL = mymalloc(sizeof(i_color)); + *RETVAL = *c; + i_rgb_to_hsv(RETVAL); + OUTPUT: + RETVAL + MODULE = Imager PACKAGE = Imager::Color::Float PREFIX=ICLF_ @@ -586,6 +605,27 @@ ICLF_set_internal(cl,r,g,b,a) EXTEND(SP, 1); PUSHs(ST(0)); +Imager::Color::Float +i_hsv_to_rgb(c) + Imager::Color::Float c + CODE: + RETVAL = mymalloc(sizeof(i_fcolor)); + *RETVAL = *c; + i_hsv_to_rgbf(RETVAL); + OUTPUT: + RETVAL + +Imager::Color::Float +i_rgb_to_hsv(c) + Imager::Color::Float c + CODE: + RETVAL = mymalloc(sizeof(i_fcolor)); + *RETVAL = *c; + i_rgb_to_hsvf(RETVAL); + OUTPUT: + RETVAL + + MODULE = Imager PACKAGE = Imager::ImgRaw PREFIX = IIM_ Imager::ImgRaw @@ -3095,3 +3135,24 @@ i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy) OUTPUT: RETVAL +Imager::FillHandle +i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy) + Imager::Color::Float fg + Imager::Color::Float 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_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy); + OUTPUT: + RETVAL + diff --git a/MANIFEST b/MANIFEST index d6eb6ffc..4121d7ab 100644 --- a/MANIFEST +++ b/MANIFEST @@ -4,7 +4,7 @@ Imager.xs MANIFEST README Makefile.PL -bmp.c +bmp.c Reading and writing Windows BMP files color.c Color translation and handling conv.c convert.c @@ -55,6 +55,7 @@ lib/Imager/Color.pm lib/Imager/Color/Float.pm lib/Imager/Expr.pm lib/Imager/Expr/Assem.pm +lib/Imager/Fill.pm lib/Imager/Font.pm lib/Imager/Font/Type1.pm lib/Imager/Font/Truetype.pm @@ -78,6 +79,7 @@ t/t105gif.t t/t106tiff.t t/t107bmp.t t/t15color.t +t/t20fill.t Tests fills t/t30t1font.t t/t35ttfont.t t/t36oofont.t @@ -94,6 +96,8 @@ t/t60dyntest.t t/t61filters.t t/t65crop.t t/t66paste.t +t/t67convert.t +t/t68map.t t/t69rubthru.t t/t70newgif.t t/t75polyaa.t diff --git a/bmp.c b/bmp.c index 7fc83d23..fbc09236 100644 --- a/bmp.c +++ b/bmp.c @@ -142,6 +142,10 @@ i_readbmp_wiol(io_glue *ig) { case 16: im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression); break; + + default: + i_push_errorf(0, "unknown bit count for BMP file (%d)", bit_count); + return NULL; } /* store the resolution */ diff --git a/color.c b/color.c index 6097cc95..ab33bfc1 100644 --- a/color.c +++ b/color.c @@ -1,4 +1,5 @@ #include "image.h" +#include /* =head1 NAME @@ -19,6 +20,9 @@ A collection of utility functions for converting between color spaces. #define EPSILON (1e-8) +#define my_max(a, b) ((a) < (b) ? (b) : (a)) +#define my_min(a, b) ((a) > (b) ? (b) : (a)) + /* =item i_rgb2hsvf(&color) @@ -33,8 +37,8 @@ void i_rgb_to_hsvf(i_fcolor *color) { double temp; double Cr, Cg, Cb; - v = max(max(color->rgb.r, color->rgb.g), color->rgb.b); - temp = min(min(color->rgb.r, color->rgb.g), color->rgb.b); + v = my_max(my_max(color->rgb.r, color->rgb.g), color->rgb.b); + temp = my_min(my_min(color->rgb.r, color->rgb.g), color->rgb.b); if (v < EPSILON) s = 0; else @@ -60,6 +64,47 @@ void i_rgb_to_hsvf(i_fcolor *color) { color->channel[2] = v; } +/* +=item i_rgb2hsv(&color) + +Converts the first 3 channels of color into hue, saturation and value. + +Each value is scaled into the range 0 to 255. + +=cut +*/ +void i_rgb_to_hsv(i_color *color) { + double h, s, v; + double temp; + double Cr, Cg, Cb; + + v = my_max(my_max(color->rgb.r, color->rgb.g), color->rgb.b); + temp = my_min(my_min(color->rgb.r, color->rgb.g), color->rgb.b); + if (v == 0) + s = 0; + else + s = (v-temp)*255/v; + if (s == 0) + h = 0; + else { + Cr = (v - color->rgb.r)/(v-temp); + Cg = (v - color->rgb.g)/(v-temp); + Cb = (v - color->rgb.b)/(v-temp); + if (color->rgb.r == v) + h = Cb - Cg; + else if (color->rgb.g == v) + h = 2 + Cr - Cb; + else if (color->rgb.b == v) + h = 4 + Cg - Cr; + h = h * 60.0; + if (h < 0) + h += 360; + } + color->channel[0] = h * 255 / 360.0; + color->channel[1] = s; + color->channel[2] = v; +} + /* =item i_hsv_to_rgbf(&color) @@ -81,7 +126,7 @@ void i_hsv_to_rgbf(i_fcolor *color) { int i; double f, m, n, k; h = fmod(h, 1.0) * 6; - i = h; + i = floor(h); f = h - i; m = v * (1 - s); n = v * (1 - s * f); @@ -109,6 +154,55 @@ void i_hsv_to_rgbf(i_fcolor *color) { } } +/* +=item i_hsv_to_rgb(&color) + +Convert a HSV value to an RGB value, each value ranges from 0 to 1. + +=cut +*/ + +void i_hsv_to_rgb(i_color *color) { + double h = color->channel[0]; + double s = color->channel[1]; + double v = color->channel[2]; + + if (color->channel[1] == 0) { + /* ignore h in this case */ + color->rgb.r = color->rgb.g = color->rgb.b = v; + } + else { + int i; + double f, m, n, k; + h = h / 255.0 * 6; + i = h; + f = h - i; + m = v * (255 - s) / 255; + n = v * (255 - s * f) / 255; + k = v * (255 - s * (1 - f)) / 255; + switch (i) { + case 0: + color->rgb.r = v; color->rgb.g = k; color->rgb.b = m; + break; + case 1: + color->rgb.r = n; color->rgb.g = v; color->rgb.b = m; + break; + case 2: + color->rgb.r = m; color->rgb.g = v; color->rgb.b = k; + break; + case 3: + color->rgb.r = m; color->rgb.g = n; color->rgb.b = v; + break; + case 4: + color->rgb.r = k; color->rgb.g = m; color->rgb.b = v; + break; + case 5: + color->rgb.r = v; color->rgb.g = m; color->rgb.b = n; + break; + } + } +} + /* =back diff --git a/draw.c b/draw.c index ecf0bad3..d7248aba 100644 --- a/draw.c +++ b/draw.c @@ -51,37 +51,47 @@ 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); + i_color *work = NULL; + if (fill->combine) + work = 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) + if (fill->combine) i_glin(im, x, x+w, y, line); - (fill->fill_with_color)(fill, x, y, w, im->channels, line); + (fill->fill_with_color)(fill, x, y, w, im->channels, line, work); i_plin(im, x, x+w, y, line); } } myfree(line); + if (work) + myfree(work); } else { i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize); + i_fcolor *work = NULL; + if (fill->combinef) + work = 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) + if (fill->combinef) i_glinf(im, x, x+w, y, line); - (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line); + (fill->fill_with_fcolor)(fill, x, y, w, im->channels, line, work); i_plinf(im, x, x+w, y, line); } } myfree(line); + if (work) + myfree(work); } } @@ -358,27 +368,37 @@ i_box_cfill(i_img *im,int x1,int y1,int x2,int y2,i_fill_t *fill) { ++x2; if (im->bits == i_8_bits && fill->fill_with_color) { i_color *line = mymalloc(sizeof(i_color) * (x2 - x1)); + i_color *work = NULL; + if (fill->combine) + work = mymalloc(sizeof(i_color) * (x2-x1)); while (y1 <= y2) { - if (fill->combines) + if (fill->combine) i_glin(im, x1, x2, y1, line); - (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line); + (fill->fill_with_color)(fill, x1, y1, x2-x1, im->channels, line, work); i_plin(im, x1, x2, y1, line); ++y1; } myfree(line); + if (work) + myfree(work); } else { i_fcolor *line = mymalloc(sizeof(i_fcolor) * (x2 - x1)); + i_fcolor *work; + work = mymalloc(sizeof(i_fcolor) * (x2 - x1)); + while (y1 <= y2) { - if (fill->combines) + if (fill->combinef) i_glinf(im, x1, x2, y1, line); - (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line); + (fill->fill_with_fcolor)(fill, x1, y1, x2-x1, im->channels, line, work); i_plinf(im, x1, x2, y1, line); ++y1; } myfree(line); + if (work) + myfree(work); } } @@ -1231,6 +1251,9 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) { if (im->bits == i_8_bits && fill->fill_with_color) { i_color *line = mymalloc(sizeof(i_color) * (bxmax - bxmin)); + i_color *work = NULL; + if (fill->combine) + work = mymalloc(sizeof(i_color) * (bxmax - bxmin)); for(y=bymin;y<=bymax;y++) { x = bxmin; @@ -1243,17 +1266,23 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) { while (x < bxmax && btm_test(btm, x, y)) { ++x; } - if (fill->combines) + if (fill->combine) i_glin(im, start, x, y, line); - (fill->fill_with_color)(fill, start, y, x-start, im->channels, line); + (fill->fill_with_color)(fill, start, y, x-start, im->channels, + line, work); i_plin(im, start, x, y, line); } } } myfree(line); + if (work) + myfree(work); } else { i_fcolor *line = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin)); + i_fcolor *work = NULL; + if (fill->combinef) + work = mymalloc(sizeof(i_fcolor) * (bxmax - bxmin)); for(y=bymin;y<=bymax;y++) { x = bxmin; @@ -1266,14 +1295,17 @@ i_flood_cfill(i_img *im, int seedx, int seedy, i_fill_t *fill) { while (x < bxmax && btm_test(btm, x, y)) { ++x; } - if (fill->combines) + if (fill->combinef) i_glinf(im, start, x, y, line); - (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, line); + (fill->fill_with_fcolor)(fill, start, y, x-start, im->channels, + line, work); i_plinf(im, start, x, y, line); } } } myfree(line); + if (work) + myfree(work); } btm_destroy(btm); diff --git a/fills.c b/fills.c index 7466f8dc..e7d3bc49 100644 --- a/fills.c +++ b/fills.c @@ -74,6 +74,30 @@ fountain fill Fountain fill is implemented by L. +Other fills that could be implemented include: + +=over + +=item * + +image - an image tiled over the fill area, with an offset either +horizontally or vertically. + +=item * + +checkerboard - combine 2 fills in a checkerboard + +=item * + +combine - combine the levels of 2 other fills based in the levels of +an image + +=item * + +regmach - use the register machine to generate colors + +=back + =over =cut @@ -95,13 +119,7 @@ static i_fcolor color_to_fcolor(i_color *c) { out.channel[ch] = Sample8ToF(c->channel[ch]); } -typedef struct -{ - i_fill_t base; - i_color c; - i_fcolor fc; -} i_fill_solid_t; - +/* alpha combine in with out */ #define COMBINE(out, in, channels) \ { \ int ch; \ @@ -111,6 +129,18 @@ typedef struct } \ } +/* alpha combine in with out, in this case in is a simple array of + samples, potentially not integers - the mult combiner uses doubles + for accuracy */ +#define COMBINEA(out, in, channels) \ + { \ + int ch; \ + for (ch = 0; ch < (channels); ++ch) { \ + (out).channel[ch] = ((out).channel[ch] * (255 - (in)[3]) \ + + (in)[ch] * (in)[3]) / 255; \ + } \ + } + #define COMBINEF(out, in, channels) \ { \ int ch; \ @@ -120,14 +150,21 @@ typedef struct } \ } +typedef struct +{ + i_fill_t base; + i_color c; + i_fcolor fc; +} i_fill_solid_t; + static void fill_solid(i_fill_t *, int x, int y, int width, int channels, - i_color *); + i_color *, i_color *); static void fill_solidf(i_fill_t *, int x, int y, int width, int channels, - i_fcolor *); + i_fcolor *, i_fcolor *); static void fill_solid_comb(i_fill_t *, int x, int y, int width, int channels, - i_color *); + i_color *, i_color *); static void fill_solidf_comb(i_fill_t *, int x, int y, int width, - int channels, i_fcolor *); + int channels, i_fcolor *, i_fcolor *); static i_fill_solid_t base_solid_fill = { @@ -135,7 +172,8 @@ static i_fill_solid_t base_solid_fill = fill_solid, fill_solidf, NULL, - 0 + NULL, + NULL, }, }; static i_fill_solid_t base_solid_fill_comb = @@ -144,7 +182,8 @@ static i_fill_solid_t base_solid_fill_comb = fill_solid_comb, fill_solidf_comb, NULL, - 1 + NULL, + NULL, }, }; @@ -178,8 +217,10 @@ 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) + if (combine && c->channel[3] < 1.0) { *fill = base_solid_fill_comb; + i_get_combine(combine, &fill->base.combine, &fill->base.combinef); + } else *fill = base_solid_fill; fill->fc = *c; @@ -205,8 +246,10 @@ 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) + if (combine && c->channel[3] < 255) { *fill = base_solid_fill_comb; + i_get_combine(combine, &fill->base.combine, &fill->base.combinef); + } else *fill = base_solid_fill; fill->c = *c; @@ -360,9 +403,9 @@ typedef struct } i_fill_hatch_t; static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, - i_color *data); + i_color *data, i_color *work); static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data); + i_fcolor *data, i_fcolor *work); static i_fill_t * i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, @@ -432,7 +475,7 @@ The 8-bit sample fill function for non-combining solid fills. */ static void fill_solid(i_fill_t *fill, int x, int y, int width, int channels, - i_color *data) { + i_color *data, i_color *work) { while (width-- > 0) { *data++ = T_SOLID_FILL(fill)->c; } @@ -447,7 +490,7 @@ The floating sample fill function for non-combining solid fills. */ static void fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data) { + i_fcolor *data, i_fcolor *work) { while (width-- > 0) { *data++ = T_SOLID_FILL(fill)->fc; } @@ -462,13 +505,15 @@ The 8-bit sample fill function for combining solid fills. */ static void fill_solid_comb(i_fill_t *fill, int x, int y, int width, int channels, - i_color *data) { + i_color *data, i_color *work) { i_color c = T_SOLID_FILL(fill)->c; + int count = width; + i_color *wstart = work; while (width-- > 0) { - COMBINE(*data, c, channels); - ++data; + *work++ = c; } + (fill->combine)(data, wstart, channels, count); } /* @@ -480,13 +525,15 @@ The floating sample fill function for combining solid fills. */ static void fill_solidf_comb(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data) { + i_fcolor *data, i_fcolor *work) { i_fcolor c = T_SOLID_FILL(fill)->fc; + int count = width; + i_fcolor *wstart = work; while (width-- > 0) { - COMBINEF(*data, c, channels); - ++data; + *work++ = c; } + (fill->combinef)(data, wstart, channels, count); } /* @@ -510,8 +557,13 @@ i_new_hatch_low(i_color *fg, i_color *bg, i_fcolor *ffg, i_fcolor *fbg, 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 (combine && (fill->ffg.channel[0] < 1 || fill->fbg.channel[0] < 1)) { + i_get_combine(combine, &fill->base.combine, &fill->base.combinef); + } + else { + fill->base.combine = NULL; + fill->base.combinef = NULL; + } if (cust_hatch) { memcpy(fill->hatch, cust_hatch, 8); } @@ -534,24 +586,31 @@ The 8-bit sample fill function for hatched fills. =back */ static void fill_hatch(i_fill_t *fill, int x, int y, int width, int channels, - i_color *data) { + i_color *data, i_color *work) { 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 (fill->combine) { + int count = width; + i_color *wstart = work; - if (f->base.combines) { - COMBINE(*data, c, channels); + while (count-- > 0) { + *work++ = (byte & mask) ? f->fg : f->bg; + + if ((mask >>= 1) == 0) + mask = 128; } - else { - *data = c; + (fill->combine)(data, wstart, channels, width); + } + else { + while (width-- > 0) { + *data++ = (byte & mask) ? f->fg : f->bg; + + if ((mask >>= 1) == 0) + mask = 128; } - ++data; - if ((mask >>= 1) == 0) - mask = 128; } } @@ -563,27 +622,500 @@ The floating sample fill function for hatched fills. =back */ static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data) { + i_fcolor *data, i_fcolor *work) { 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 (fill->combinef) { + int count = width; + i_fcolor *wstart = work; + + while (count-- > 0) { + *work++ = (byte & mask) ? f->ffg : f->fbg; + + if ((mask >>= 1) == 0) + mask = 128; + } + (fill->combinef)(data, wstart, channels, width); + } + else { + while (width-- > 0) { + *data++ = (byte & mask) ? f->ffg : f->fbg; - if (f->base.combines) { - COMBINE(*data, c, channels); + if ((mask >>= 1) == 0) + mask = 128; } - else { - *data = c; + } +} + +static void combine_replace(i_color *, i_color *, int, int); +static void combine_replacef(i_fcolor *, i_fcolor *, int, int); +static void combine_alphablend(i_color *, i_color *, int, int); +static void combine_alphablendf(i_fcolor *, i_fcolor *, int, int); +static void combine_mult(i_color *, i_color *, int, int); +static void combine_multf(i_fcolor *, i_fcolor *, int, int); +static void combine_dissolve(i_color *, i_color *, int, int); +static void combine_dissolvef(i_fcolor *, i_fcolor *, int, int); +static void combine_add(i_color *, i_color *, int, int); +static void combine_addf(i_fcolor *, i_fcolor *, int, int); +static void combine_subtract(i_color *, i_color *, int, int); +static void combine_subtractf(i_fcolor *, i_fcolor *, int, int); +static void combine_diff(i_color *, i_color *, int, int); +static void combine_difff(i_fcolor *, i_fcolor *, int, int); +static void combine_darken(i_color *, i_color *, int, int); +static void combine_darkenf(i_fcolor *, i_fcolor *, int, int); +static void combine_lighten(i_color *, i_color *, int, int); +static void combine_lightenf(i_fcolor *, i_fcolor *, int, int); +static void combine_hue(i_color *, i_color *, int, int); +static void combine_huef(i_fcolor *, i_fcolor *, int, int); +static void combine_sat(i_color *, i_color *, int, int); +static void combine_satf(i_fcolor *, i_fcolor *, int, int); +static void combine_value(i_color *, i_color *, int, int); +static void combine_valuef(i_fcolor *, i_fcolor *, int, int); +static void combine_color(i_color *, i_color *, int, int); +static void combine_colorf(i_fcolor *, i_fcolor *, int, int); + +struct i_combines { + i_fill_combine_f combine; + i_fill_combinef_f combinef; +} combines[] = +{ + { /* replace */ + combine_replace, + combine_replacef, + }, + { /* alpha blend */ + combine_alphablend, + combine_alphablendf, + }, + { + /* multiply */ + combine_mult, + combine_multf, + }, + { + /* dissolve */ + combine_dissolve, + combine_dissolvef, + }, + { + /* add */ + combine_add, + combine_addf, + }, + { + /* subtract */ + combine_subtract, + combine_subtractf, + }, + { + /* diff */ + combine_diff, + combine_difff, + }, + { + combine_lighten, + combine_lightenf, + }, + { + combine_darken, + combine_darkenf, + }, + { + combine_hue, + combine_huef, + }, + { + combine_sat, + combine_satf, + }, + { + combine_value, + combine_valuef, + }, + { + combine_color, + combine_colorf, + }, +}; + +/* +=item i_get_combine(combine, color_func, fcolor_func) + +=cut +*/ + +void i_get_combine(int combine, i_fill_combine_f *color_func, + i_fill_combinef_f *fcolor_func) { + if (combine < 0 || combine > sizeof(combines) / sizeof(*combines)) + combine = 0; + + *color_func = combines[combine].combine; + *fcolor_func = combines[combine].combinef; +} + +static void combine_replace(i_color *out, i_color *in, int channels, int count) { + while (count--) { + *out++ = *in++; + } +} + +static void combine_replacef(i_fcolor *out, i_fcolor *in, int channels, int count) { + while (count--) { + *out++ = *in++; + } +} + +static void combine_alphablend(i_color *out, i_color *in, int channels, int count) { + while (count--) { + COMBINE(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_alphablendf(i_fcolor *out, i_fcolor *in, int channels, int count) { + while (count--) { + COMBINEF(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_mult(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + i_color c = *in; + double mult[MAXCHANNELS]; + mult[3] = in->channel[3]; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) + mult[ch] = (out->channel[ch] * in->channel[ch]) * (1.0 / 255); + } + COMBINEA(*out, mult, channels); + ++out; + ++in; + } +} + +static void combine_multf(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + i_fcolor c = *in; + for (ch = 0; ch < channels; ++ch) { + if (ch != 3) + c.channel[ch] = out->channel[ch] * in->channel[ch]; + } + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_dissolve(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + if (in->channel[3] > rand() * (255.0 / RAND_MAX)) + COMBINE(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_dissolvef(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + if (in->channel[3] > rand() * (1.0 / RAND_MAX)) + COMBINEF(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_add(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + i_color c = *in; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) { + int total = out->channel[ch] + in->channel[ch]; + if (total > 255) + total = 255; + c.channel[ch] = total; + } + } + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_addf(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + i_fcolor c = *in; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) { + double total = out->channel[ch] + in->channel[ch]; + if (total > 1.0) + total = 1.0; + out->channel[ch] = total; + } + } + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_subtract(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + i_color c = *in; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) { + int total = out->channel[ch] - in->channel[ch]; + if (total < 0) + total = 0; + c.channel[ch] = total; + } + } + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_subtractf(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + i_fcolor c = *in; + for (ch = 0; ch < channels; ++ch) { + if (ch != 3) { + double total = out->channel[ch] - in->channel[ch]; + if (total < 0) + total = 0; + c.channel[ch] = total; + } + } + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_diff(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + i_color c = *in; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) + c.channel[ch] = abs(out->channel[ch] - in->channel[ch]); + } + COMBINE(*out, c, channels) + ++out; + ++in; + } +} + +static void combine_difff(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + i_fcolor c = *in; + for (ch = 0; ch < (channels); ++ch) { + if (ch != 3) + c.channel[ch] = fabs(out->channel[ch] - in->channel[ch]); } - ++data; - if ((mask >>= 1) == 0) - mask = 128; + COMBINEF(*out, c, channels); + ++out; + ++in; } } +static void combine_darken(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + for (ch = 0; ch < channels; ++ch) { + if (ch != 3 && out->channel[ch] < in->channel[ch]) + in->channel[ch] = out->channel[ch]; + } + COMBINE(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_darkenf(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + for (ch = 0; ch < channels; ++ch) { + if (ch != 3 && out->channel[ch] < in->channel[ch]) + in->channel[ch] = out->channel[ch]; + } + COMBINEF(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_lighten(i_color *out, i_color *in, int channels, int count) { + int ch; + + while (count--) { + for (ch = 0; ch < channels; ++ch) { + if (ch != 3 && out->channel[ch] > in->channel[ch]) + in->channel[ch] = out->channel[ch]; + } + COMBINE(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_lightenf(i_fcolor *out, i_fcolor *in, int channels, int count) { + int ch; + + while (count--) { + for (ch = 0; ch < channels; ++ch) { + if (ch != 3 && out->channel[ch] > in->channel[ch]) + in->channel[ch] = out->channel[ch]; + } + COMBINEF(*out, *in, channels); + ++out; + ++in; + } +} + +static void combine_hue(i_color *out, i_color *in, int channels, int count) { + while (count--) { + i_color c = *out; + i_rgb_to_hsv(&c); + i_rgb_to_hsv(in); + c.channel[0] = in->channel[0]; + i_hsv_to_rgb(&c); + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_huef(i_fcolor *out, i_fcolor *in, int channels, int count) { + while (count--) { + i_fcolor c = *out; + i_rgb_to_hsvf(&c); + i_rgb_to_hsvf(in); + c.channel[0] = in->channel[0]; + i_hsv_to_rgbf(&c); + c.channel[3] = in->channel[3]; + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_sat(i_color *out, i_color *in, int channels, int count) { + while (count--) { + i_color c = *out; + i_rgb_to_hsv(&c); + i_rgb_to_hsv(in); + c.channel[1] = in->channel[1]; + i_hsv_to_rgb(&c); + c.channel[3] = in->channel[3]; + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_satf(i_fcolor *out, i_fcolor *in, int channels, int count) { + while (count--) { + i_fcolor c = *out; + i_rgb_to_hsvf(&c); + i_rgb_to_hsvf(in); + c.channel[1] = in->channel[1]; + i_hsv_to_rgbf(&c); + c.channel[3] = in->channel[3]; + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_value(i_color *out, i_color *in, int channels, int count) { + while (count--) { + i_color c = *out; + i_rgb_to_hsv(&c); + i_rgb_to_hsv(in); + c.channel[2] = in->channel[2]; + i_hsv_to_rgb(&c); + c.channel[3] = in->channel[3]; + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_valuef(i_fcolor *out, i_fcolor *in, int channels, + int count) { + while (count--) { + i_fcolor c = *out; + i_rgb_to_hsvf(&c); + i_rgb_to_hsvf(in); + c.channel[2] = in->channel[2]; + i_hsv_to_rgbf(&c); + c.channel[3] = in->channel[3]; + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_color(i_color *out, i_color *in, int channels, int count) { + while (count--) { + i_color c = *out; + i_rgb_to_hsv(&c); + i_rgb_to_hsv(in); + c.channel[0] = in->channel[0]; + c.channel[1] = in->channel[1]; + i_hsv_to_rgb(&c); + c.channel[3] = in->channel[3]; + COMBINE(*out, c, channels); + ++out; + ++in; + } +} + +static void combine_colorf(i_fcolor *out, i_fcolor *in, int channels, + int count) { + while (count--) { + i_fcolor c = *out; + i_rgb_to_hsvf(&c); + i_rgb_to_hsvf(in); + c.channel[0] = in->channel[0]; + c.channel[1] = in->channel[1]; + i_hsv_to_rgbf(&c); + c.channel[3] = in->channel[3]; + COMBINEF(*out, c, channels); + ++out; + ++in; + } +} + + /* =back diff --git a/filters.c b/filters.c index 3eeb259c..473176db 100644 --- a/filters.c +++ b/filters.c @@ -1143,8 +1143,15 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb, struct fount_state state; int x, y; i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize); + i_fcolor *work = NULL; int ch; i_fountain_seg *my_segs; + i_fill_combine_f combine_func = NULL; + i_fill_combinef_f combinef_func = NULL; + + i_get_combine(combine, &combine_func, &combinef_func); + if (combinef_func) + work = mymalloc(sizeof(i_fcolor) * im->xsize); fount_init_state(&state, xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs); @@ -1161,16 +1168,14 @@ i_fountain(i_img *im, double xa, double ya, double xb, double yb, 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]; - } - } + if (combine) + work[x] = c; else line[x] = c; } } + if (combine) + combinef_func(line, work, im->channels, im->xsize); i_plinf(im, 0, im->xsize, y, line); } fount_finish_state(&state); @@ -1184,7 +1189,7 @@ typedef struct { static void fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data); + i_fcolor *data, i_fcolor *work); static void fount_fill_destroy(i_fill_t *fill); @@ -1206,7 +1211,12 @@ i_new_fill_fount(double xa, double ya, double xb, double yb, fill->base.fill_with_color = NULL; fill->base.fill_with_fcolor = fill_fountf; fill->base.destroy = fount_fill_destroy; - fill->base.combines = combine; + if (combine) + i_get_combine(combine, &fill->base.combine, &fill->base.combinef); + else { + fill->base.combine = NULL; + fill->base.combinef = NULL; + } fount_init_state(&fill->state, xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs); @@ -1791,32 +1801,42 @@ The fill function for fountain fills. */ static void fill_fountf(i_fill_t *fill, int x, int y, int width, int channels, - i_fcolor *data) { + i_fcolor *data, i_fcolor *work) { i_fill_fountain_t *f = (i_fill_fountain_t *)fill; - int ch; + + if (fill->combinef) { + i_fcolor *wstart = work; + int count = width; - 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; + 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); + + *work++ = c; + + ++x; + } + (fill->combinef)(data, wstart, channels, count); + } + else { + 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); + + *data++ = c; + + ++x; } - - ++x; - ++data; } } diff --git a/image.h b/image.h index d11436d3..16686c9f 100644 --- a/image.h +++ b/image.h @@ -44,6 +44,8 @@ extern void i_fcolor_destroy(i_fcolor *cl); extern void i_rgb_to_hsvf(i_fcolor *color); extern void i_hsv_to_rgbf(i_fcolor *color); +extern void i_rgb_to_hsv(i_color *color); +extern void i_hsv_to_rgb(i_color *color); i_img *IIM_new(int x,int y,int ch); void IIM_DESTROY(i_img *im); @@ -125,11 +127,16 @@ 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); + i_color *data, i_color *work); typedef void (*i_fill_with_fcolor_f) (struct i_fill_tag *fill, int x, int y, int width, int channels, - i_fcolor *data); + i_fcolor *data, i_fcolor *work); typedef void (*i_fill_destroy_f)(struct i_fill_tag *fill); +typedef void (*i_fill_combine_f)(i_color *out, i_color *in, int channels, + int count); +typedef void (*i_fill_combinef_f)(i_fcolor *out, i_fcolor *in, int channels, + int count); + typedef struct i_fill_tag { @@ -146,9 +153,26 @@ typedef struct i_fill_tag /* if non-zero the caller will fill data with the original data from the image */ - int combines; + i_fill_combine_f combine; + i_fill_combinef_f combinef; } i_fill_t; +typedef enum { + ic_none, + ic_normal, + ic_multiply, + ic_dissolve, + ic_add, + ic_subtract, + ic_diff, + ic_lighten, + ic_darken, + ic_hue, + ic_sat, + ic_value, + ic_color +} i_combine_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 * diff --git a/imagei.h b/imagei.h index d12680b7..fb865010 100644 --- a/imagei.h +++ b/imagei.h @@ -39,4 +39,6 @@ extern int i_setcolors_forward(i_img *im, int index, i_color *colors, #define Sample16To8(num) ((num) / 257) #define Sample8To16(num) ((num) * 257) +extern void i_get_combine(int combine, i_fill_combine_f *, i_fill_combinef_f *); + #endif diff --git a/lib/Imager/Fill.pm b/lib/Imager/Fill.pm index c727052c..229fbd2c 100644 --- a/lib/Imager/Fill.pm +++ b/lib/Imager/Fill.pm @@ -1,5 +1,5 @@ package Imager::Fill; - +use strict; # 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 @@ -10,17 +10,74 @@ my @hatch_types = my %hatch_types; @hatch_types{@hatch_types} = 0..$#hatch_types; +my @combine_types = + qw/none normal multiply dissolve add subtract diff lighten darken + hue saturation value color/; +my %combine_types; +@combine_types{@combine_types} = 0 .. $#combine_types; +$combine_types{mult} = $combine_types{multiply}; +$combine_types{sub} = $combine_types{subtract}; +$combine_types{sat} = $combine_types{saturation}; + +# this function tries to DWIM for color parameters +# color objects are used as is +# simple scalars are simply treated as single parameters to Imager::Color->new +# hashrefs are treated as named argument lists to Imager::Color->new +# arrayrefs are treated as list arguments to Imager::Color->new iff any +# parameter is > 1 +# other arrayrefs are treated as list arguments to Imager::Color::Float + +sub _color { + my $arg = shift; + my $result; + + if (ref $arg) { + if (UNIVERSAL::isa($arg, "Imager::Color") + || UNIVERSAL::isa($arg, "Imager::Color::Float")) { + $result = $arg; + } + else { + if ($arg =~ /^HASH\(/) { + $result = Imager::Color->new(%$arg); + } + elsif ($arg =~ /^ARRAY\(/) { + if (grep $_ > 1, @$arg) { + $result = Imager::Color->new(@$arg); + } + else { + $result = Imager::Color::Float->new(@$arg); + } + } + else { + $Imager::ERRSTR = "Not a color"; + } + } + } + else { + # assume Imager::Color::new knows how to handle it + $result = Imager::Color->new($arg); + } + + return $result; +} + sub new { my ($class, %hsh) = @_; my $self = bless { }, $class; $hsh{combine} ||= 0; + if (exists $combine_types{$hsh{combine}}) { + $hsh{combine} = $combine_types{$hsh{combine}}; + } if ($hsh{solid}) { - if (UNIVERSAL::isa($hsh{solid}, 'Imager::Color')) { - $self->{fill} = Imager::i_new_fill_solid($hsh{solid}, $hsh{combine}); + my $solid = _color($hsh{solid}); + if (UNIVERSAL::isa($solid, 'Imager::Color')) { + $self->{fill} = + Imager::i_new_fill_solid($solid, $hsh{combine}); } - elsif (UNIVERSAL::isa($hsh{colid}, 'Imager::Color::Float')) { - $self->{fill} = Imager::i_new_fill_solidf($hsh{solid}, $hsh{combine}); + elsif (UNIVERSAL::isa($solid, 'Imager::Color::Float')) { + $self->{fill} = + Imager::i_new_fill_solidf($solid, $hsh{combine}); } else { $Imager::ERRSTR = "solid isn't a color"; @@ -42,17 +99,18 @@ sub new { } $hsh{hatch} = $hatch_types{$hsh{hatch}}; } - if (UNIVERSAL::isa($hsh{fg}, 'Imager::Color')) { - $hsh{bg} ||= Imager::Color->new(255, 255, 255); + my $fg = _color($hsh{fg}); + if (UNIVERSAL::isa($fg, 'Imager::Color')) { + my $bg = _color($hsh{bg} || Imager::Color->new(255, 255, 255)); $self->{fill} = - Imager::i_new_fill_hatch($hsh{fg}, $hsh{bg}, $hsh{combine}, + Imager::i_new_fill_hatch($fg, $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); + elsif (UNIVERSAL::isa($fg, 'Imager::Color::Float')) { + my $bg = _color($hsh{bg} || Imager::Color::Float->new(1, 1, 1)); $self->{fill} = - Imager::i_new_fill_hatchf($hsh{fg}, $hsh{bg}, $hsh{combine}, + Imager::i_new_fill_hatchf($fg, $bg, $hsh{combine}, $hsh{hatch}, $hsh{cust_hatch}, $hsh{dx}, $hsh{dy}); } @@ -104,6 +162,10 @@ sub hatches { return @hatch_types; } +sub combines { + return @combine_types; +} + 1; =head1 NAME @@ -145,11 +207,78 @@ fountain (similar to gradients in paint software) =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. +The way in which the fill data is combined with the underlying image, +possible values include: + +=over + +=item none + +The fill pixel replaces the target pixel. + +=item normal + +The fill pixels alpha value is used to combine it with the target pixel. + +=item multiply + +=item mult + +Each channel of fill and target is multiplied, and the result is +combined using the alpha channel of the fill pixel. + +=item dissolve + +If the alpha of the fill pixel is greater than a random number, the +fill pixel is alpha combined with the target pixel. -This this is missing or zero then the target image pixels are simply -overwritten. +=item add + +The channels of the fill and target are added together, clamped to the range of the samples and alpha combined with the target. + +=item subtract + +The channels of the fill are subtracted from the target, clamped to be +>= 0, and alpha combined with the target. + +=item diff + +The channels of the fill are subtracted from the target and the +absolute value taken this is alpha combined with the target. + +=item lighten + +The higher value is taken from each channel of the fill and target pixels, which is then alpha combined with the target. + +=item darken + +The higher value is taken from each channel of the fill and target pixels, which is then alpha combined with the target. + +=item hue + +The combination of the saturation and value of the target is combined +with the hue of the fill pixel, and is then alpha combined with the +target. + +=item sat + +The combination of the hue and value of the target is combined +with the saturation of the fill pixel, and is then alpha combined with the +target. + +=item value + +The combination of the hue and value of the target is combined +with the value of the fill pixel, and is then alpha combined with the +target. + +=item color + +The combination of the value of the target is combined with the hue +and saturation of the fill pixel, and is then alpha combined with the +target. + +=back =back @@ -273,6 +402,20 @@ 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 OTHER METHODS + +=over + +=item Imager::Fill->hatches + +A list of all defined hatch names. + +=item Imager::Fill->combines + +A list of all combine types. + +=back + =head1 FUTURE PLANS I'm planning on adding the following types of fills: diff --git a/t/t15color.t b/t/t15color.t index d7f316c1..65b98ee1 100644 --- a/t/t15color.t +++ b/t/t15color.t @@ -6,7 +6,7 @@ # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) -BEGIN { $| = 1; print "1..22\n"; } +BEGIN { $| = 1; print "1..42\n"; } END {print "not ok 1\n" unless $loaded;} use Imager; $loaded = 1; @@ -65,6 +65,47 @@ color_ok(21, 200, 201, 203, 204, Imager::Color->new(channels=>[ 200, 201, 203, 204 ])); color_ok(22, 255, 250, 250, 255, Imager::Color->new(name=>'snow', palette=>'testimg/test_gimp_pal')); + +# test the internal HSV <=> RGB conversions +# these values were generated using the GIMP +# all but hue is 0..360, saturation and value from 0 to 1 +# rgb from 0 to 255 +my @hsv_vs_rgb = + ( + { hsv => [ 0, 0.2, 0.1 ], rgb=> [ 25, 20, 20 ] }, + { hsv => [ 0, 0.5, 1.0 ], rgb => [ 255, 127, 127 ] }, + { hsv => [ 100, 0.5, 1.0 ], rgb => [ 170, 255, 127 ] }, + { hsv => [ 100, 1.0, 1.0 ], rgb=> [ 85, 255, 0 ] }, + { hsv => [ 335, 0.5, 0.5 ], rgb=> [127, 63, 90 ] }, + ); + +use Imager::Color::Float; +my $test_num = 23; +my $index = 0; +for my $entry (@hsv_vs_rgb) { + print "# color index $index\n"; + my $hsv = $entry->{hsv}; + my $rgb = $entry->{rgb}; + my $fhsvo = Imager::Color::Float->new($hsv->[0]/360.0, $hsv->[1], $hsv->[2]); + my $fc = Imager::Color::Float::i_hsv_to_rgb($fhsvo); + fcolor_close_enough($test_num++, $rgb->[0]/255, $rgb->[1]/255, + $rgb->[2]/255, $fc); + my $fc2 = Imager::Color::Float::i_rgb_to_hsv($fc); + fcolor_close_enough($test_num++, $hsv->[0]/360.0, $hsv->[1], $hsv->[2], + $fc2); + + my $hsvo = Imager::Color->new($hsv->[0]*255/360.0, $hsv->[1] * 255, + $hsv->[2] * 255); + my $c = Imager::Color::i_hsv_to_rgb($hsvo); + color_close_enough($test_num++, @$rgb, $c); + my $c2 = Imager::Color::i_rgb_to_hsv($c); + color_close_enough_hsv($test_num++, $hsv->[0]*255/360.0, $hsv->[1] * 255, + $hsv->[2] * 255, $c2); + ++$index; +} + + + sub test_col { my ($c, $r, $g, $b, $a) = @_; unless ($c) { @@ -75,6 +116,48 @@ sub test_col { return $r == $cr && $g == $cg && $b == $cb && $a == $ca; } +sub color_close_enough { + my ($test_num, $r, $g, $b, $c) = @_; + + my ($cr, $cg, $cb) = $c->rgba; + if (abs($cr-$r) <= 5 && abs($cg-$g) <= 5 && abs($cb-$b) <= 5) { + print "ok $test_num\n"; + } + else { + print "not ok $test_num # ($cr, $cg, $cb) <=> ($r, $g, $b)\n"; + } +} + +sub color_close_enough_hsv { + my ($test_num, $h, $s, $v, $c) = @_; + + my ($ch, $cs, $cv) = $c->rgba; + if ($ch < 5 && $h > 250) { + $ch += 255; + } + elsif ($ch > 250 && $h < 5) { + $h += 255; + } + if (abs($ch-$h) <= 5 && abs($cs-$s) <= 5 && abs($cv-$v) <= 5) { + print "ok $test_num\n"; + } + else { + print "not ok $test_num # ($ch, $cs, $cv) <=> ($h, $s, $v)\n"; + } +} + +sub fcolor_close_enough { + my ($test_num, $r, $g, $b, $c) = @_; + + my ($cr, $cg, $cb) = $c->rgba; + if (abs($cr-$r) <= 0.01 && abs($cg-$g) <= 0.01 && abs($cb-$b) <= 0.01) { + print "ok $test_num\n"; + } + else { + print "not ok $test_num # ($cr, $cg, $cb) <=> ($r, $g, $b)\n"; + } +} + sub color_ok { my ($test_num, $r, $g, $b, $a, $c) = @_; diff --git a/t/t20fill.t b/t/t20fill.t index cfeba5a7..4f56d819 100644 --- a/t/t20fill.t +++ b/t/t20fill.t @@ -1,13 +1,15 @@ #!perl -w use strict; +print "1..34\n"; + use Imager ':handy'; use Imager::Fill; use Imager::Color::Float; -Imager::init_log("testout/t20fill.log", 1); +sub ok ($$$); -print "1..21\n"; +Imager::init_log("testout/t20fill.log", 1); my $blue = NC(0,0,255); my $red = NC(255, 0, 0); @@ -72,7 +74,7 @@ ok(12, !$diff, "custom hatch mismatch"); my $im1 = Imager->new(xsize=>100, ysize=>100); my $im2 = Imager->new(xsize=>100, ysize=>100); -my $solid = Imager::Fill->new(solid=>$red); +my $solid = Imager::Fill->new(solid=>'#FF0000'); ok(13, $solid, "creating oo solid fill"); ok(14, $solid->{fill}, "bad oo solid fill"); $im1->box(fill=>$solid); @@ -108,8 +110,8 @@ $im->box(xmin=>10, ymin=>10, xmax=>190, ymax=>190, $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) }); + fg=>[ 0, 0, 0, 255 ], + bg=>{ rgba=>[255,255,255,160] } }); $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'); @@ -149,7 +151,51 @@ $ffim->flood_fill(x=>50, 'y'=>50, # }); $ffim->write(file=>'testout/t20_ooflood.ppm'); -sub ok { +# test combining modes +my $fill = NC(192, 128, 128, 128); +my $target = NC(64, 32, 64); +my %comb_tests = + ( + none=>{ result=>$fill }, + normal=>{ result=>NC(128, 80, 96) }, + multiply => { result=>NC(56, 24, 48) }, + dissolve => { result=>[ $target, NC(128, 80, 96) ] }, + add => { result=>NC(159, 96, 128) }, + subtract => { result=>NC(31, 15, 31) }, # 31.87, 15.9, 31.87 + diff => { result=>NC(96, 64, 64) }, + lighten => { result=>NC(128, 80, 96) }, + darken => { result=>$target }, + # the following results are based on the results of the tests and + # are suspect for that reason (and were broken at one point ) + # but trying to work them out manually just makes my head hurt - TC + hue => { result=>NC(64, 32, 55) }, + saturation => { result=>NC(63, 37, 64) }, + value => { result=>NC(127, 64, 128) }, + color => { result=>NC(64, 37, 52) }, + ); + +my $testnum = 22; # from 22 to 34 +for my $comb (Imager::Fill->combines) { + my $test = $comb_tests{$comb}; + my $targim = Imager->new(xsize=>1, ysize=>1); + $targim->box(filled=>1, color=>$target); + my $fillobj = Imager::Fill->new(solid=>$fill, combine=>$comb); + $targim->box(fill=>$fillobj); + my $c = Imager::i_get_pixel($targim->{IMG}, 0, 0); + if ($test->{result} =~ /ARRAY/) { + ok($testnum++, scalar grep(color_close($_, $c), @{$test->{result}}), + "combine '$comb'") + or print "# got:",join(",", $c->rgba)," allowed: ", + join("|", map { join(",", $_->rgba) } @{$test->{result}}),"\n"; + } + else { + ok($testnum++, color_close($c, $test->{result}), "combine '$comb'") + or print "# got: ",join(",", $c->rgba), + " allowed: ",join(",", $test->{result}->rgba),"\n"; + } +} + +sub ok ($$$) { my ($num, $test, $desc) = @_; if ($test) { @@ -158,6 +204,21 @@ sub ok { else { print "not ok $num # $desc\n"; } + $test; +} + +sub color_close { + my ($c1, $c2) = @_; + + my @c1 = $c1->rgba; + my @c2 = $c2->rgba; + + for my $i (0..2) { + if (abs($c1[$i]-$c2[$i]) > 2) { + return 0; + } + } + return 1; } # for use during testing diff --git a/t/t61filters.t b/t/t61filters.t index 6aca7e1f..136b7230 100644 --- a/t/t61filters.t +++ b/t/t61filters.t @@ -73,9 +73,9 @@ test($imbase, 31, { type=>'fountain', xa=>20, ya=>130, xb=>130, yb=>20, my $f3 = Imager::Fountain->read(gimp=>'testimg/gimpgrad') or print "not "; print "ok 33\n"; -test($imbase, 34, { type=>'fountain', xa=>75, ya=>75, xb=>90, yb=>10, +test($imbase, 34, { type=>'fountain', xa=>75, ya=>75, xb=>90, yb=>15, segments=>$f3, super_sample=>'grid', - ftype=>'radial_square', combine=>1 }, + ftype=>'radial_square', combine=>'color' }, 'testout/t61_fount_gimp.ppm'); sub test { diff --git a/testimg/gimpgrad b/testimg/gimpgrad index 563cd779..58f3aacd 100644 --- a/testimg/gimpgrad +++ b/testimg/gimpgrad @@ -1,4 +1,4 @@ GIMP Gradient 2 -0.000000 0.130000 0.546377 0.000000 0.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 4 1 -0.546377 0.773188 1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 0 +0.000000 0.130000 0.546377 0.900000 0.900000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 4 1 +0.546377 0.773188 1.000000 0.000000 1.000000 0.000000 1.000000 0.200000 0.200000 0.200000 0.000000 0 0