#include "imager.h" #include "imageri.h" /* =head1 NAME fills.c - implements the basic general fills =head1 SYNOPSIS i_fill_t *fill; i_color c1, c2; i_fcolor fc1, fc2; int combine; fill = i_new_fill_solidf(&fc1, combine); fill = i_new_fill_solid(&c1, combine); fill = i_new_fill_hatchf(&fc1, &fc2, combine, hatch, cust_hash, dx, dy); fill = i_new_fill_hatch(&c1, &c2, combine, hatch, cust_hash, dx, dy); fill = i_new_fill_image(im, matrix, xoff, yoff, combine); i_fill_destroy(fill); =head1 DESCRIPTION Implements the basic general fills, which can be used for filling some shapes and for flood fills. Each fill can implement up to 3 functions: =over =item fill_with_color called for fills on 8-bit images. This can be NULL in which case the fill_with_colorf function is called. =item fill_with_fcolor called for fills on non-8-bit images or when fill_with_color is NULL. =item destroy called by i_fill_destroy() if non-NULL, to release any extra resources that the fill may need. =back fill_with_color and fill_with_fcolor are basically the same function except that the first works with lines of i_color and the second with lines of i_fcolor. If the combines member if non-zero the line data is populated from the target image before calling fill_with_*color. fill_with_color needs to fill the I parameter with the fill pixels. If combines is non-zero it the fill pixels should be combined with the existing data. The current fills are: =over =item * solid fill =item * hatched fill =item * fountain fill =back 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 */ static i_color fcolor_to_color(const i_fcolor *c) { int ch; i_color out; for (ch = 0; ch < MAXCHANNELS; ++ch) out.channel[ch] = SampleFTo8(c->channel[ch]); return out; } static i_fcolor color_to_fcolor(const i_color *c) { int ch; i_fcolor out; for (ch = 0; ch < MAXCHANNELS; ++ch) out.channel[ch] = Sample8ToF(c->channel[ch]); return out; } /* alpha combine in with out */ #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; \ } \ } /* 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; \ for (ch = 0; ch < (channels); ++ch) { \ (out).channel[ch] = (out).channel[ch] * (1.0 - (in).channel[3]) \ + (in).channel[ch] * (in).channel[3]; \ } \ } 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 *); static void fill_solidf(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, NULL, NULL, }, }; /* =item i_fill_destroy(fill) =category Fills =synopsis i_fill_destroy(fill); Call to destroy any fill object. =cut */ void i_fill_destroy(i_fill_t *fill) { if (fill->destroy) (fill->destroy)(fill); myfree(fill); } /* =item i_new_fill_solidf(color, combine) =category Fills =synopsis i_fill_t *fill = i_new_fill_solidf(&fcolor, combine); Create a solid fill based on a float color. If combine is non-zero then alpha values will be combined. =cut */ i_fill_t * i_new_fill_solidf(const i_fcolor *c, int combine) { int ch; i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */ *fill = base_solid_fill; if (combine) { i_get_combine(combine, &fill->base.combine, &fill->base.combinef); } fill->fc = *c; for (ch = 0; ch < MAXCHANNELS; ++ch) { fill->c.channel[ch] = SampleFTo8(c->channel[ch]); } return &fill->base; } /* =item i_new_fill_solid(color, combine) =category Fills =synopsis i_fill_t *fill = i_new_fill_solid(&color, combine); Create a solid fill based on an 8-bit color. If combine is non-zero then alpha values will be combined. =cut */ i_fill_t * i_new_fill_solid(const i_color *c, int combine) { int ch; i_fill_solid_t *fill = mymalloc(sizeof(i_fill_solid_t)); /* checked 14jul05 tonyc */ *fill = base_solid_fill; if (combine) { i_get_combine(combine, &fill->base.combine, &fill->base.combinef); } fill->c = *c; for (ch = 0; ch < MAXCHANNELS; ++ch) { fill->fc.channel[ch] = Sample8ToF(c->channel[ch]); } return &fill->base; } 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 */ 0x80, 0x80, 0x41, 0x3E, 0x08, 0x08, 0x14, 0xE3, }, { /* scales overlapping upwards */ 0xC7, 0x28, 0x10, 0x10, 0x7C, 0x82, 0x01, 0x01, }, { /* scales overlapping leftwards */ 0x83, 0x84, 0x88, 0x48, 0x38, 0x48, 0x88, 0x84, }, { /* scales overlapping rightwards */ 0x21, 0x11, 0x12, 0x1C, 0x12, 0x11, 0x21, 0xC1, }, { /* denser stipple */ 0x44, 0x88, 0x22, 0x11, 0x44, 0x88, 0x22, 0x11, }, { /* L-shaped tiles */ 0xFF, 0x84, 0x84, 0x9C, 0x94, 0x9C, 0x90, 0x90, }, { /* wider stipple */ 0x80, 0x40, 0x20, 0x00, 0x02, 0x04, 0x08, 0x00, }, }; 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(const i_color *fg, const i_color *bg, const i_fcolor *ffg, const i_fcolor *fbg, int combine, int hatch, const unsigned char *cust_hatch, int dx, int dy); /* =item i_new_fill_hatch(fg, bg, combine, hatch, cust_hatch, dx, dy) =category Fills =synopsis i_fill_t *fill = i_new_fill_hatch(&fg_color, &bg_color, combine, hatch, custom_hatch, dx, dy); Creates a new hatched fill with the fg color used for the 1 bits in the hatch and bg for the 0 bits. If combine is non-zero alpha values will be combined. If cust_hatch is non-NULL it should be a pointer to 8 bytes of the hash definition, with the high-bits to the left. If cust_hatch is NULL then one of the standard hatches is used. (dx, dy) are an offset into the hatch which can be used to unalign adjoining areas, or to align the origin of a hatch with the the side of a filled area. =cut */ i_fill_t * i_new_fill_hatch(const i_color *fg, const i_color *bg, int combine, int hatch, const unsigned char *cust_hatch, int dx, int dy) { return i_new_hatch_low(fg, bg, NULL, NULL, combine, hatch, cust_hatch, dx, dy); } /* =item i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy) =category Fills =synopsis i_fill_t *fill = i_new_fill_hatchf(&fg_fcolor, &bg_fcolor, combine, hatch, custom_hatch, dx, dy); Creates a new hatched fill with the fg color used for the 1 bits in the hatch and bg for the 0 bits. If combine is non-zero alpha values will be combined. If cust_hatch is non-NULL it should be a pointer to 8 bytes of the hash definition, with the high-bits to the left. If cust_hatch is NULL then one of the standard hatches is used. (dx, dy) are an offset into the hatch which can be used to unalign adjoining areas, or to align the origin of a hatch with the the side of a filled area. =cut */ i_fill_t * i_new_fill_hatchf(const i_fcolor *fg, const i_fcolor *bg, int combine, int hatch, const 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_image(i_fill_t *fill, int x, int y, int width, int channels, i_color *data); static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels, i_fcolor *data); struct i_fill_image_t { i_fill_t base; i_img *src; int xoff, yoff; int has_matrix; double matrix[9]; }; static struct i_fill_image_t image_fill_proto = { { fill_image, fill_imagef, NULL } }; /* =item i_new_fill_image(im, matrix, xoff, yoff, combine) =category Fills =synopsis i_fill_t *fill = i_new_fill_image(src_img, matrix, x_offset, y_offset, combine); Create an image based fill. matrix is an array of 9 doubles representing a transformation matrix. xoff and yoff are the offset into the image to start filling from. =cut */ i_fill_t * i_new_fill_image(i_img *im, const double *matrix, int xoff, int yoff, int combine) { struct i_fill_image_t *fill = mymalloc(sizeof(*fill)); /* checked 14jul05 tonyc */ *fill = image_fill_proto; if (combine) { i_get_combine(combine, &fill->base.combine, &fill->base.combinef); } else { fill->base.combine = NULL; fill->base.combinef = NULL; } fill->src = im; if (xoff < 0) xoff += im->xsize; fill->xoff = xoff; if (yoff < 0) yoff += im->ysize; fill->yoff = yoff; if (matrix) { fill->has_matrix = 1; memcpy(fill->matrix, matrix, sizeof(fill->matrix)); } else fill->has_matrix = 0; return &fill->base; } #define T_SOLID_FILL(fill) ((i_fill_solid_t *)(fill)) /* =back =head1 INTERNAL FUNCTIONS =over =item fill_solid(fill, x, y, width, channels, data) The 8-bit sample fill function for non-combining solid fills. =cut */ static void fill_solid(i_fill_t *fill, int x, int y, int width, int channels, i_color *data) { i_color c = T_SOLID_FILL(fill)->c; i_adapt_colors(channels > 2 ? 4 : 2, 4, &c, 1); while (width-- > 0) { *data++ = c; } } /* =item fill_solid(fill, x, y, width, channels, data) The floating sample fill function for non-combining solid fills. =cut */ static void fill_solidf(i_fill_t *fill, int x, int y, int width, int channels, i_fcolor *data) { i_fcolor c = T_SOLID_FILL(fill)->fc; i_adapt_fcolors(channels > 2 ? 4 : 2, 4, &c, 1); while (width-- > 0) { *data++ = c; } } static i_fill_hatch_t hatch_fill_proto = { { fill_hatch, fill_hatchf, NULL } }; /* =item i_new_hatch_low(fg, bg, ffg, fbg, combine, hatch, cust_hatch, dx, dy) Implements creation of hatch fill objects. =cut */ static i_fill_t * i_new_hatch_low(const i_color *fg, const i_color *bg, const i_fcolor *ffg, const i_fcolor *fbg, int combine, int hatch, const unsigned char *cust_hatch, int dx, int dy) { i_fill_hatch_t *fill = mymalloc(sizeof(i_fill_hatch_t)); /* checked 14jul05 tonyc */ *fill = hatch_fill_proto; /* Some Sun C didn't like the condition expressions that were here. See https://rt.cpan.org/Ticket/Display.html?id=21944 */ if (fg) fill->fg = *fg; else fill->fg = fcolor_to_color(ffg); if (bg) fill->bg = *bg; else fill->bg = fcolor_to_color(fbg); if (ffg) fill->ffg = *ffg; else fill->ffg = color_to_fcolor(fg); if (fbg) fill->fbg = *fbg; else fill->fbg = color_to_fcolor(bg); if (combine) { 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); } 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; } /* =item fill_hatch(fill, x, y, width, channels, data) The 8-bit sample fill function for hatched fills. =cut */ 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; i_color fg = f->fg; i_color bg = f->bg; int want_channels = channels > 2 ? 4 : 2; if (channels < 3) { i_adapt_colors(2, 4, &fg, 1); i_adapt_colors(2, 4, &bg, 1); } while (width-- > 0) { if (byte & mask) *data++ = fg; else *data++ = bg; if ((mask >>= 1) == 0) mask = 128; } } /* =item fill_hatchf(fill, x, y, width, channels, data) 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_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; i_fcolor fg = f->ffg; i_fcolor bg = f->fbg; if (channels < 3) { i_adapt_fcolors(2, 4, &fg, 1); i_adapt_fcolors(2, 4, &bg, 1); } while (width-- > 0) { if (byte & mask) *data++ = fg; else *data++ = bg; if ((mask >>= 1) == 0) mask = 128; } } /* hopefully this will be inlined (it is with -O3 with gcc 2.95.4) */ /* linear interpolation */ static i_color interp_i_color(i_color before, i_color after, double pos, int channels) { i_color out; int ch; pos -= floor(pos); for (ch = 0; ch < channels; ++ch) out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch]; if (channels > 3 && out.channel[3]) for (ch = 0; ch < channels; ++ch) if (ch != 3) { int temp = out.channel[ch] * 255 / out.channel[3]; if (temp > 255) temp = 255; out.channel[ch] = temp; } return out; } /* hopefully this will be inlined (it is with -O3 with gcc 2.95.4) */ /* linear interpolation */ static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos, int channels) { i_fcolor out; int ch; pos -= floor(pos); for (ch = 0; ch < channels; ++ch) out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch]; if (out.channel[3]) for (ch = 0; ch < channels; ++ch) if (ch != 3) { int temp = out.channel[ch] / out.channel[3]; if (temp > 1.0) temp = 1.0; out.channel[ch] = temp; } return out; } /* =item fill_image(fill, x, y, width, channels, data, work) =cut */ static void fill_image(i_fill_t *fill, int x, int y, int width, int channels, i_color *data) { struct i_fill_image_t *f = (struct i_fill_image_t *)fill; int i = 0; i_color *out = data; int want_channels = channels > 2 ? 4 : 2; if (f->has_matrix) { /* the hard way */ while (i < width) { double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2]; double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5]; double ix = floor(rx / f->src->xsize); double iy = floor(ry / f->src->ysize); i_color c[2][2]; i_color c2[2]; int dy; if (f->xoff) { rx += iy * f->xoff; ix = floor(rx / f->src->xsize); } else if (f->yoff) { ry += ix * f->yoff; iy = floor(ry / f->src->ysize); } rx -= ix * f->src->xsize; ry -= iy * f->src->ysize; for (dy = 0; dy < 2; ++dy) { if ((int)rx == f->src->xsize-1) { i_gpix(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]); i_gpix(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]); } else { i_glin(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, c[dy]); } c2[dy] = interp_i_color(c[dy][0], c[dy][1], rx, f->src->channels); } *out++ = interp_i_color(c2[0], c2[1], ry, f->src->channels); ++i; } } else { /* the easy way */ /* this should be possible to optimize to use i_glin() */ while (i < width) { int rx = x+i; int ry = y; int ix = rx / f->src->xsize; int iy = ry / f->src->ysize; if (f->xoff) { rx += iy * f->xoff; ix = rx / f->src->xsize; } else if (f->yoff) { ry += ix * f->yoff; iy = ry / f->src->xsize; } rx -= ix * f->src->xsize; ry -= iy * f->src->ysize; i_gpix(f->src, rx, ry, out); ++out; ++i; } } if (f->src->channels != want_channels) i_adapt_colors(want_channels, f->src->channels, data, width); } /* =item fill_imagef(fill, x, y, width, channels, data, work) =cut */ static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels, i_fcolor *data) { struct i_fill_image_t *f = (struct i_fill_image_t *)fill; int i = 0; int want_channels = channels > 2 ? 4 : 2; if (f->has_matrix) { i_fcolor *work_data = data; /* the hard way */ while (i < width) { double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2]; double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5]; double ix = floor(rx / f->src->xsize); double iy = floor(ry / f->src->ysize); i_fcolor c[2][2]; i_fcolor c2[2]; int dy; if (f->xoff) { rx += iy * f->xoff; ix = floor(rx / f->src->xsize); } else if (f->yoff) { ry += ix * f->yoff; iy = floor(ry / f->src->ysize); } rx -= ix * f->src->xsize; ry -= iy * f->src->ysize; for (dy = 0; dy < 2; ++dy) { if ((int)rx == f->src->xsize-1) { i_gpixf(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]); i_gpixf(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]); } else { i_glinf(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, c[dy]); } c2[dy] = interp_i_fcolor(c[dy][0], c[dy][1], rx, f->src->channels); } *work_data++ = interp_i_fcolor(c2[0], c2[1], ry, f->src->channels); ++i; } } else { i_fcolor *work_data = data; /* the easy way */ /* this should be possible to optimize to use i_glin() */ while (i < width) { int rx = x+i; int ry = y; int ix = rx / f->src->xsize; int iy = ry / f->src->ysize; if (f->xoff) { rx += iy * f->xoff; ix = rx / f->src->xsize; } else if (f->yoff) { ry += ix * f->yoff; iy = ry / f->src->xsize; } rx -= ix * f->src->xsize; ry -= iy * f->src->ysize; i_gpixf(f->src, rx, ry, work_data); ++work_data; ++i; } } if (f->src->channels != want_channels) i_adapt_fcolors(want_channels, f->src->channels, data, width); } /* =back =head1 AUTHOR Tony Cook =head1 SEE ALSO Imager(3) =cut */