/*
Render utilities
*/
#include "imager.h"
#define RENDER_MAGIC 0x765AE
typedef void (*render_color_f)(i_render *, i_img_dim, i_img_dim, i_img_dim, unsigned char const *src, i_color const *color);
#define i_has_alpha(channels) ((channels) == 2 || (channels) == 4)
#define i_color_channels(channels) (i_has_alpha(channels) ? (channels)-1 : (channels))
#code
static void IM_SUFFIX(render_color_alpha)(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width, unsigned char const *src, i_color const *color);
static void IM_SUFFIX(render_color_13)(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width, unsigned char const *src, i_color const *color);
static render_color_f IM_SUFFIX(render_color_tab)[] =
{
NULL,
IM_SUFFIX(render_color_13),
IM_SUFFIX(render_color_alpha),
IM_SUFFIX(render_color_13),
IM_SUFFIX(render_color_alpha),
};
static void IM_SUFFIX(combine_line_noalpha)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
static void IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
/* the copy variant copies the source alpha to the the output alpha channel */
static void IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
static void IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
static void IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
#/code
/*
=item i_render_new(im, width)
=category Blit tools
Allocate a new C<i_render> object and initialize it.
=cut
*/
i_render *
i_render_new(i_img *im, i_img_dim width) {
i_render *r = mymalloc(sizeof(i_render));
i_render_init(r, im, width);
return r;
}
/*
=item i_render_delete(r)
=category Blit tools
Release an C<i_render> object.
=cut
*/
void
i_render_delete(i_render *r) {
i_render_done(r);
myfree(r);
}
void
i_render_init(i_render *r, i_img *im, i_img_dim width) {
r->magic = RENDER_MAGIC;
r->im = im;
r->line_width = width;
r->line_8 = NULL;
r->line_double = NULL;
r->fill_width = width;
r->fill_line_8 = NULL;
r->fill_line_double = NULL;
}
void
i_render_done(i_render *r) {
if (r->line_8)
myfree(r->line_8);
if (r->line_double)
myfree(r->line_double);
if (r->fill_line_8)
myfree(r->fill_line_8);
if (r->fill_line_double)
myfree(r->fill_line_double);
r->magic = 0;
}
static void
alloc_line(i_render *r, i_img_dim width, i_img_dim eight_bit) {
if (width > r->line_width) {
i_img_dim new_width = r->line_width * 2;
if (new_width < width)
new_width = width;
if (eight_bit) {
if (r->line_8)
r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width);
else
r->line_8 = mymalloc(sizeof(i_color) * new_width);
if (r->line_double) {
myfree(r->line_double);
r->line_double = NULL;
}
}
else {
if (r->line_double)
r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width);
else
r->line_double = mymalloc(sizeof(i_fcolor) * new_width);
if (r->line_8) {
myfree(r->line_8);
r->line_double = NULL;
}
}
r->line_width = new_width;
}
else {
if (eight_bit) {
if (!r->line_8)
r->line_8 = mymalloc(sizeof(i_color) * r->line_width);
if (r->line_double) {
myfree(r->line_double);
r->line_double = NULL;
}
}
else {
if (!r->line_double)
r->line_double = mymalloc(sizeof(i_fcolor) * r->line_width);
if (r->line_8) {
myfree(r->line_8);
r->line_8 = NULL;
}
}
}
}
static void
alloc_fill_line(i_render *r, i_img_dim width, int eight_bit) {
if (width > r->fill_width) {
i_img_dim new_width = r->fill_width * 2;
if (new_width < width)
new_width = width;
if (eight_bit) {
if (r->line_8)
r->fill_line_8 = myrealloc(r->fill_line_8, sizeof(i_color) * new_width);
else
r->fill_line_8 = mymalloc(sizeof(i_color) * new_width);
if (r->fill_line_double) {
myfree(r->fill_line_double);
r->fill_line_double = NULL;
}
}
else {
if (r->fill_line_double)
r->fill_line_double = myrealloc(r->fill_line_double, sizeof(i_fcolor) * new_width);
else
r->fill_line_double = mymalloc(sizeof(i_fcolor) * new_width);
if (r->fill_line_8) {
myfree(r->fill_line_8);
r->fill_line_double = NULL;
}
}
r->fill_width = new_width;
}
else {
if (eight_bit) {
if (!r->fill_line_8)
r->fill_line_8 = mymalloc(sizeof(i_color) * r->fill_width);
if (r->fill_line_double) {
myfree(r->fill_line_double);
r->fill_line_double = NULL;
}
}
else {
if (!r->fill_line_double)
r->fill_line_double = mymalloc(sizeof(i_fcolor) * r->fill_width);
if (r->fill_line_8) {
myfree(r->fill_line_8);
r->fill_line_8 = NULL;
}
}
}
}
/*
=item i_render_color(r, x, y, width, source, color)
=category Blit tools
Render the given color with the coverage specified by C<source[0]> to
C<source[width-1]>.
Renders in normal combine mode.
=cut
*/
void
i_render_color(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
unsigned char const *src, i_color const *color) {
i_img *im = r->im;
if (y < 0 || y >= im->ysize)
return;
if (x < 0) {
width += x;
src -= x;
x = 0;
}
if (x + width > im->xsize) {
width = im->xsize - x;
}
if (x >= im->xsize || x + width <= 0 || width <= 0)
return;
/* avoid as much work as we can */
while (width > 0 && *src == 0) {
--width;
++src;
++x;
}
while (width > 0 && src[width-1] == 0) {
--width;
}
if (!width)
return;
alloc_line(r, width, r->im->bits <= 8);
#code r->im->bits <= 8
/*if (r->IM_SUFFIX(line) == NULL)
r->IM_SUFFIX(line) = mymalloc(sizeof(IM_COLOR) * r->width);*/
(IM_SUFFIX(render_color_tab)[im->channels])(r, x, y, width, src, color);
#/code
}
/*
=item i_render_fill(r, x, y, width, source, fill)
=category Blit tools
Render the given fill with the coverage in C<source[0]> through
C<source[width-1]>.
=cut
*/
void
i_render_fill(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
unsigned char const *src, i_fill_t *fill) {
i_img *im = r->im;
int fill_channels = im->channels;
if (fill_channels == 1 || fill_channels == 3)
++fill_channels;
if (y < 0 || y >= im->ysize)
return;
if (x < 0) {
width += x;
src -= x;
x = 0;
}
if (x + width > im->xsize) {
width = im->xsize - x;
}
if (x >= im->xsize || x + width <= 0 || width <= 0)
return;
if (src) {
/* avoid as much work as we can */
while (width > 0 && *src == 0) {
--width;
++src;
++x;
}
while (width > 0 && src[width-1] == 0) {
--width;
}
}
if (!width)
return;
alloc_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
alloc_fill_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
#code r->im->bits <= 8 && fill->f_fill_with_color
if (IM_FILL_COMBINE(fill)) {
IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
IM_COLOR *destc = r->IM_SUFFIX(line);
IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
if (src) {
unsigned char const *srcc = src;
IM_COLOR *fillc = r->IM_SUFFIX(fill_line);
i_img_dim work_width = width;
while (work_width) {
if (*srcc == 0) {
fillc->channel[fill_channels-1] = 0;
}
else if (*srcc != 255) {
fillc->channel[fill_channels-1] =
fillc->channel[fill_channels-1] * *srcc / 255;
}
--work_width;
++srcc;
++fillc;
}
}
IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
IM_FILL_COMBINE(fill)(destc, srcc, r->im->channels, width);
}
else {
if (src) {
i_img_dim work_width = width;
IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
IM_COLOR *destc = r->IM_SUFFIX(line);
int ch;
IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
while (work_width) {
if (*src == 255) {
/* just replace it */
*destc = *srcc;
}
else if (*src) {
for (ch = 0; ch < im->channels; ++ch) {
IM_WORK_T work = (destc->channel[ch] * (255 - *src)
+ srcc->channel[ch] * *src) / 255.0;
destc->channel[ch] = IM_LIMIT(work);
}
}
++srcc;
++destc;
++src;
--work_width;
}
}
else { /* if (src) */
IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(line));
}
}
IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
#/code
}
#if 0
/* for debuggin */
static void
dump_src(const char *note, unsigned char const *src, i_img_dim width) {
i_img_dim i;
printf("%s - %p/%" i_DF "\n", note, src, i_DFc(width));
for (i = 0; i < width; ++i) {
printf("%02x ", src[i]);
}
putchar('\n');
}
#endif
#code
/*
=item i_render_line(r, x, y, width, source, fill)
=category Blit tools
Render the given fill with the coverage in C<source[0]> through
C<source[width-1]>.
=cut
=item i_render_linef(r, x, y, width, source, fill)
=category Blit tools
Render the given fill with the coverage in C<source[0]> through
C<source[width-1]>.
=cut
*/
void
IM_RENDER_LINE(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
const IM_SAMPLE_T *src, IM_COLOR *line,
IM_FILL_COMBINE_F combine) {
i_img *im = r->im;
int src_chans = im->channels;
/* src must always have an alpha channel */
if (src_chans == 1 || src_chans == 3)
++src_chans;
if (y < 0 || y >= im->ysize)
return;
if (x < 0) {
src -= x;
line -= x;
width += x;
x = 0;
}
if (x + width > im->xsize)
width = r->im->xsize - x;
#ifdef IM_EIGHT_BIT
alloc_line(r, width, 1);
#else
alloc_line(r, width, 0);
#endif
if (combine) {
if (src) {
i_img_dim work_width = width;
IM_COLOR *linep = line;
const IM_SAMPLE_T *srcp = src;
int alpha_chan = src_chans - 1;
while (work_width) {
if (*srcp) {
if (*srcp != IM_SAMPLE_MAX)
linep->channel[alpha_chan] =
linep->channel[alpha_chan] * *srcp / IM_SAMPLE_MAX;
}
else {
linep->channel[alpha_chan] = 0;
}
--work_width;
++srcp;
++linep;
}
}
IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
combine(r->IM_SUFFIX(line), line, im->channels, width);
IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
}
else {
if (src) {
i_img_dim work_width = width;
IM_COLOR *srcc = line;
IM_COLOR *destc = r->IM_SUFFIX(line);
IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
while (work_width) {
if (*src == 255) {
/* just replace it */
*destc = *srcc;
}
else if (*src) {
int ch;
for (ch = 0; ch < im->channels; ++ch) {
IM_WORK_T work = (destc->channel[ch] * (IM_SAMPLE_MAX - *src)
+ srcc->channel[ch] * *src) / IM_SAMPLE_MAX;
destc->channel[ch] = IM_LIMIT(work);
}
}
++srcc;
++destc;
++src;
--work_width;
}
IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
}
else {
IM_PLIN(im, x, x+width, y, line);
}
}
}
static
void
IM_SUFFIX(render_color_13)(i_render *r, i_img_dim x, i_img_dim y,
i_img_dim width, unsigned char const *src,
i_color const *color) {
i_img *im = r->im;
IM_COLOR *linep = r->IM_SUFFIX(line);
int ch, channels = im->channels;
i_img_dim fetch_offset;
int color_alpha = color->channel[im->channels];
#undef STORE_COLOR
#ifdef IM_EIGHT_BIT
#define STORE_COLOR (*color)
#else
i_fcolor fcolor;
for (ch = 0; ch < channels; ++ch) {
fcolor.channel[ch] = color->channel[ch] / 255.0;
}
#define STORE_COLOR fcolor
#endif
fetch_offset = 0;
if (color_alpha == 0xFF) {
while (fetch_offset < width && *src == 0xFF) {
*linep++ = STORE_COLOR;
++src;
++fetch_offset;
}
}
IM_GLIN(im, x+fetch_offset, x+width, y, linep);
while (fetch_offset < width) {
#ifdef IM_EIGHT_BIT
IM_WORK_T alpha = *src++ * color_alpha / 255;
#else
IM_WORK_T alpha = *src++ * color_alpha / (255.0 * 255.0);
#endif
if (alpha == IM_SAMPLE_MAX)
*linep = STORE_COLOR;
else if (alpha) {
for (ch = 0; ch < channels; ++ch) {
linep->channel[ch] = (linep->channel[ch] * (IM_SAMPLE_MAX - alpha)
+ STORE_COLOR.channel[ch] * alpha) / IM_SAMPLE_MAX;
}
}
++linep;
++fetch_offset;
}
IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
}
static
void
IM_SUFFIX(render_color_alpha)(i_render *r, i_img_dim x, i_img_dim y,
i_img_dim width, unsigned char const *src,
i_color const *color) {
IM_COLOR *linep = r->IM_SUFFIX(line);
int ch;
int alpha_channel = r->im->channels - 1;
i_img_dim fetch_offset;
int color_alpha = color->channel[alpha_channel];
#undef STORE_COLOR
#ifdef IM_EIGHT_BIT
#define STORE_COLOR (*color)
#else
i_fcolor fcolor;
for (ch = 0; ch < r->im->channels; ++ch) {
fcolor.channel[ch] = color->channel[ch] / 255.0;
}
#define STORE_COLOR fcolor
#endif
fetch_offset = 0;
if (color->channel[alpha_channel] == 0xFF) {
while (fetch_offset < width && *src == 0xFF) {
*linep++ = STORE_COLOR;
++src;
++fetch_offset;
}
}
IM_GLIN(r->im, x+fetch_offset, x+width, y, linep);
while (fetch_offset < width) {
#ifdef IM_EIGHT_BIT
IM_WORK_T src_alpha = *src++ * color_alpha / 255;
#else
IM_WORK_T src_alpha = *src++ * color_alpha / (255.0 * 255.0);
#endif
if (src_alpha == IM_SAMPLE_MAX)
*linep = STORE_COLOR;
else if (src_alpha) {
IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
IM_WORK_T orig_alpha = linep->channel[alpha_channel];
IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
for (ch = 0; ch < alpha_channel; ++ch) {
linep->channel[ch] = ( src_alpha * STORE_COLOR.channel[ch]
+ remains * linep->channel[ch] * orig_alpha / IM_SAMPLE_MAX
) / dest_alpha;
}
linep->channel[alpha_channel] = dest_alpha;
}
++linep;
++fetch_offset;
}
IM_PLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
#undef STORE_COLOR
}
/* combine a line of image data with an output line, both the input
and output lines include an alpha channel.
Both input and output lines have I<channels> of data, channels
should be either 2 or 4.
*/
static void
IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in,
int channels, i_img_dim count) {
int ch;
int alpha_channel = channels - 1;
while (count) {
IM_WORK_T src_alpha = in->channel[alpha_channel];
if (src_alpha == IM_SAMPLE_MAX)
*out = *in;
else if (src_alpha) {
IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
IM_WORK_T orig_alpha = out->channel[alpha_channel];
IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
for (ch = 0; ch < alpha_channel; ++ch) {
out->channel[ch] = ( src_alpha * in->channel[ch]
+ remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
) / dest_alpha;
}
out->channel[alpha_channel] = dest_alpha;
}
++out;
++in;
--count;
}
}
/* combine a line of image data with an output line. The input line
includes an alpha channel, the output line has no alpha channel.
The input line has I<channels>+1 of color data. The output line
has I<channels> of color data.
*/
static void
IM_SUFFIX(combine_line_noalpha)
(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
int ch;
while (count) {
IM_WORK_T src_alpha = in->channel[channels];
if (src_alpha == IM_SAMPLE_MAX)
*out = *in;
else if (src_alpha) {
IM_WORK_T remains;
remains = IM_SAMPLE_MAX - src_alpha;
for (ch = 0; ch < channels; ++ch) {
out->channel[ch] = ( in->channel[ch] * src_alpha
+ out->channel[ch] * remains) / IM_SAMPLE_MAX;
}
}
++out;
++in;
--count;
}
}
/* combine a line of image data with an output line, both the input
and output lines include an alpha channel.
Both input and output lines have I<channels> of data, channels
should be either 2 or 4.
This variant does not modify the output alpha channel.
*/
static void
IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in,
int channels, i_img_dim count) {
int ch;
int alpha_channel = channels - 1;
while (count) {
IM_WORK_T src_alpha = in->channel[alpha_channel];
if (src_alpha == IM_SAMPLE_MAX)
*out = *in;
else if (src_alpha) {
IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
IM_WORK_T orig_alpha = out->channel[alpha_channel];
IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
for (ch = 0; ch < alpha_channel; ++ch) {
out->channel[ch] = ( src_alpha * in->channel[ch]
+ remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
) / dest_alpha;
}
}
++out;
++in;
--count;
}
}
static void
IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
if (channels == 2 || channels == 4)
IM_SUFFIX(combine_line_alpha)(out, in, channels, count);
else
IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
}
static void
IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
if (channels == 2 || channels == 4)
IM_SUFFIX(combine_line_alpha_na)(out, in, channels, count);
else
IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
}
static void IM_SUFFIX(combine_alphablend)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_mult)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_dissolve)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_add)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_subtract)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_diff)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_darken)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_lighten)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_hue)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_sat)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_value)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static void IM_SUFFIX(combine_color)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
static const IM_FILL_COMBINE_F IM_SUFFIX(combines)[] =
{
NULL,
IM_SUFFIX(combine_alphablend),
IM_SUFFIX(combine_mult),
IM_SUFFIX(combine_dissolve),
IM_SUFFIX(combine_add),
IM_SUFFIX(combine_subtract),
IM_SUFFIX(combine_diff),
IM_SUFFIX(combine_lighten),
IM_SUFFIX(combine_darken),
IM_SUFFIX(combine_hue),
IM_SUFFIX(combine_sat),
IM_SUFFIX(combine_value),
IM_SUFFIX(combine_color)
};
#/code
/*
=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_8) / sizeof(*combines_8))
combine = 0;
*color_func = combines_8[combine];
*fcolor_func = combines_double[combine];
}
#code
/*
Three good references for implementing combining modes:
http://www.w3.org/TR/2004/WD-SVG12-20041027/rendering.html
referenced as [svg1.2]
http://gimp-savvy.com/BOOK/index.html?node55.html
("The Blending Modes", if it changes)
referenced as [savvy]
http://www.pegtop.net/delphi/articles/blendmodes/
referenced as [pegtop]
Where differences exist, I follow the SVG practice, the gimp
practice, and lastly pegtop.
*/
static void
IM_SUFFIX(combine_alphablend)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
IM_SUFFIX(combine_line)(out, in, channels, count);
}
/*
Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
Da' = Sa.Da + Sa.(1 - Da) + Da.(1 - Sa)
= Sa + Da - Sa.Da
When Da=1
Dc' = Sc.Sa.Dc + Dc.(1 - Sa)
*/
static void
IM_SUFFIX(combine_mult)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int ch;
IM_COLOR *inp = in;
IM_COLOR *outp = out;
i_img_dim work_count = count;
int color_channels = i_color_channels(channels);
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T dest_alpha = src_alpha + orig_alpha
- (src_alpha * orig_alpha) / IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
outp->channel[ch] =
(inp->channel[ch] * src_alpha * outp->channel[ch] / IM_SAMPLE_MAX
* orig_alpha
+ inp->channel[ch] * src_alpha * (IM_SAMPLE_MAX - orig_alpha)
+ outp->channel[ch] * orig_alpha * (IM_SAMPLE_MAX - src_alpha))
/ IM_SAMPLE_MAX / dest_alpha;
}
outp->channel[color_channels] = dest_alpha;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
outp->channel[ch] =
(src_alpha * inp->channel[ch] * outp->channel[ch] / IM_SAMPLE_MAX
+ outp->channel[ch] * remains) / IM_SAMPLE_MAX;
}
}
++outp;
++inp;
}
}
}
static void
IM_SUFFIX(combine_dissolve)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int color_channels = i_color_channels(channels);
int ch;
if (i_has_alpha(channels)) {
while (count--) {
if (in->channel[channels-1] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
for (ch = 0; ch < color_channels; ++ch) {
out->channel[ch] = in->channel[ch];
}
out->channel[color_channels] = IM_SAMPLE_MAX;
}
++out;
++in;
}
}
else {
while (count--) {
if (in->channel[channels] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
for (ch = 0; ch < color_channels; ++ch) {
out->channel[ch] = in->channel[ch];
}
}
++out;
++in;
}
}
}
/*
Dca' = Sca.Da + Dca.Sa + Sca.(1 - Da) + Dca.(1 - Sa)
= Sca + Dca
Da' = Sa.Da + Da.Sa + Sa.(1 - Da) + Da.(1 - Sa)
= Sa + Da
*/
static void
IM_SUFFIX(combine_add)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int ch;
int color_channels = i_color_channels(channels);
i_img_dim work_count = count;
IM_COLOR *inp = in;
IM_COLOR *outp = out;
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T dest_alpha = src_alpha + orig_alpha;
if (dest_alpha > IM_SAMPLE_MAX)
dest_alpha = IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T total = (outp->channel[ch] * orig_alpha + inp->channel[ch] * src_alpha) / dest_alpha;
if (total > IM_SAMPLE_MAX)
total = IM_SAMPLE_MAX;
outp->channel[ch] = total;
}
outp->channel[color_channels] = dest_alpha;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T total = outp->channel[ch] + inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
if (total > IM_SAMPLE_MAX)
total = IM_SAMPLE_MAX;
outp->channel[ch] = total;
}
}
++outp;
++inp;
}
}
}
/*
[pegtop] documents this as max(A+B-256, 0) while [savvy] documents
it as max(A-B, 0). [svg1.2] doesn't cover it.
[savvy] doesn't document how it works with an alpha channel. GIMP
actually seems to calculate the final value then use the alpha
channel to apply that to the target.
*/
static void
IM_SUFFIX(combine_subtract)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int ch;
IM_COLOR const *inp = in;
IM_COLOR *outp = out;
i_img_dim work_count = count;
int color_channels = i_color_channels(channels);
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T dest_alpha = src_alpha + orig_alpha;
if (dest_alpha > IM_SAMPLE_MAX)
dest_alpha = IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T total =
(outp->channel[ch] * orig_alpha - inp->channel[ch] * src_alpha)
/ dest_alpha;
if (total < 0)
total = 0;
outp->channel[ch] = total;
}
outp->channel[color_channels] = dest_alpha;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T total = outp->channel[ch] - inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
if (total < 0)
total = 0;
outp->channel[ch] = total;
}
}
++outp;
++inp;
}
}
}
#ifdef IM_EIGHT_BIT
#define IM_abs(x) abs(x)
#else
#define IM_abs(x) fabs(x)
#endif
/*
Dca' = abs(Dca.Sa - Sca.Da) + Sca.(1 - Da) + Dca.(1 - Sa)
= Sca + Dca - 2.min(Sca.Da, Dca.Sa)
Da' = Sa + Da - Sa.Da
*/
static void
IM_SUFFIX(combine_diff)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int ch;
IM_COLOR const *inp = in;
IM_COLOR *outp = out;
i_img_dim work_count = count;
int color_channels = i_color_channels(channels);
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T dest_alpha = src_alpha + orig_alpha
- src_alpha * orig_alpha / IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T src = inp->channel[ch] * src_alpha;
IM_WORK_T orig = outp->channel[ch] * orig_alpha;
IM_WORK_T src_da = src * orig_alpha;
IM_WORK_T dest_sa = orig * src_alpha;
IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / dest_alpha;
}
outp->channel[color_channels] = dest_alpha;
}
++inp;
++outp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T src = inp->channel[ch] * src_alpha;
IM_WORK_T orig = outp->channel[ch] * IM_SAMPLE_MAX;
IM_WORK_T src_da = src * IM_SAMPLE_MAX;
IM_WORK_T dest_sa = orig * src_alpha;
IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / IM_SAMPLE_MAX;
}
}
++inp;
++outp;
}
}
}
#undef IM_abs
/*
Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca(1 - Sa)
Da' = Sa + Da - Sa.Da
To hoist some code:
Dca' = min(Sc.Sa.Da, Dc.Da.Sa) + Sca - Sca.Da + Dca - Dca.Sa
= Sa.Da.min(Sc, Dc) + Sca - Sca.Da + Dca - Dca.Sa
When Da=1:
Dca' = min(Sca.1, Dc.1.Sa) + Sca.(1 - 1) + Dc.1(1 - Sa)
= min(Sca, Dc.Sa) + Dc(1-Sa)
= Sa.min(Sc, Dc) + Dc - Dc.Sa
Da' = Sa + 1 - Sa.1
= 1
*/
static void
IM_SUFFIX(combine_darken)(IM_COLOR *out, IM_COLOR *in, int channels,
i_img_dim count) {
int ch;
IM_COLOR const *inp = in;
IM_COLOR *outp = out;
i_img_dim work_count = count;
int color_channels = i_color_channels(channels);
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T dest_alpha = src_alpha + orig_alpha
- src_alpha * orig_alpha / IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T Sca = inp->channel[ch] * src_alpha;
IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
IM_WORK_T ScaDa = Sca * orig_alpha;
IM_WORK_T DcaSa = Dca * src_alpha;
IM_WORK_T minc = ScaDa < DcaSa ? ScaDa : DcaSa;
outp->channel[ch] =
(
minc + (Sca + Dca) * IM_SAMPLE_MAX
- ScaDa - DcaSa
) / (IM_SAMPLE_MAX * dest_alpha);
}
outp->channel[color_channels] = dest_alpha;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T minc = outp->channel[ch] < inp->channel[ch]
? outp->channel[ch] : inp->channel[ch];
outp->channel[ch] =
(
src_alpha * minc +
outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
) / IM_SAMPLE_MAX;
}
}
++outp;
++inp;
}
}
}
static void
IM_SUFFIX(combine_lighten)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
int ch;
IM_COLOR const *inp = in;
IM_COLOR *outp = out;
i_img_dim work_count = count;
int color_channels = i_color_channels(channels);
if (i_has_alpha(channels)) {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
IM_WORK_T orig_alpha = outp->channel[color_channels];
IM_WORK_T dest_alpha = src_alpha + orig_alpha
- src_alpha * orig_alpha / IM_SAMPLE_MAX;
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T Sca = inp->channel[ch] * src_alpha;
IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
IM_WORK_T ScaDa = Sca * orig_alpha;
IM_WORK_T DcaSa = Dca * src_alpha;
IM_WORK_T maxc = ScaDa > DcaSa ? ScaDa : DcaSa;
outp->channel[ch] =
(
maxc + (Sca + Dca) * IM_SAMPLE_MAX
- ScaDa - DcaSa
) / (IM_SAMPLE_MAX * dest_alpha);
}
outp->channel[color_channels] = dest_alpha;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_WORK_T src_alpha = inp->channel[color_channels];
if (src_alpha) {
for (ch = 0; ch < color_channels; ++ch) {
IM_WORK_T maxc = outp->channel[ch] > inp->channel[ch]
? outp->channel[ch] : inp->channel[ch];
outp->channel[ch] =
(
src_alpha * maxc +
outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
) / IM_SAMPLE_MAX;
}
}
++outp;
++inp;
}
}
}
#if IM_EIGHT_BIT
#define IM_RGB_TO_HSV i_rgb_to_hsv
#define IM_HSV_TO_RGB i_hsv_to_rgb
#else
#define IM_RGB_TO_HSV i_rgb_to_hsvf
#define IM_HSV_TO_RGB i_hsv_to_rgbf
#endif
static void
IM_SUFFIX(combine_hue)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
if (channels > 2) {
IM_COLOR *inp = in;
IM_COLOR const *outp = out;
i_img_dim work_count = count;
if (i_has_alpha(channels)) {
while (work_count--) {
IM_COLOR c = *inp;
IM_RGB_TO_HSV(&c);
/* only transfer hue if there's saturation */
if (c.channel[1] && inp->channel[3] && outp->channel[3]) {
*inp = *outp;
IM_RGB_TO_HSV(inp);
/* and no point in setting the target hue if the target has no sat */
if (inp->channel[1]) {
inp->channel[0] = c.channel[0];
IM_HSV_TO_RGB(inp);
inp->channel[3] = c.channel[3];
}
else {
inp->channel[3] = 0;
}
}
else {
inp->channel[3] = 0;
}
++outp;
++inp;
}
}
else {
while (work_count--) {
IM_COLOR c = *inp;
IM_RGB_TO_HSV(&c);
/* only transfer hue if there's saturation */
if (c.channel[1] && inp->channel[3]) {
*inp = *outp;
IM_RGB_TO_HSV(inp);
/* and no point in setting the target hue if the target has no sat */
if (inp->channel[1]) {
inp->channel[0] = c.channel[0];
IM_HSV_TO_RGB(inp);
inp->channel[3] = c.channel[3];
}
}
else {
inp->channel[3] = 0;
}
++outp;
++inp;
}
}
IM_SUFFIX(combine_line_na)(out, in, channels, count);
}
}
static void
IM_SUFFIX(combine_sat)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
if (channels > 2) {
IM_COLOR *inp = in;
IM_COLOR const *outp = out;
i_img_dim work_count = count;
while (work_count--) {
IM_COLOR c = *inp;
*inp = *outp;
IM_RGB_TO_HSV(&c);
IM_RGB_TO_HSV(inp);
inp->channel[1] = c.channel[1];
IM_HSV_TO_RGB(inp);
inp->channel[3] = c.channel[3];
++outp;
++inp;
}
IM_SUFFIX(combine_line_na)(out, in, channels, count);
}
}
static void
IM_SUFFIX(combine_value)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
if (channels > 2) {
IM_COLOR *inp = in;
IM_COLOR const *outp = out;
i_img_dim work_count = count;
while (work_count--) {
IM_COLOR c = *inp;
*inp = *outp;
IM_RGB_TO_HSV(&c);
IM_RGB_TO_HSV(inp);
inp->channel[2] = c.channel[2];
IM_HSV_TO_RGB(inp);
inp->channel[3] = c.channel[3];
++outp;
++inp;
}
}
/* all images have a "value channel" - for greyscale it's the only
colour channel */
IM_SUFFIX(combine_line_na)(out, in, channels, count);
}
static void
IM_SUFFIX(combine_color)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
if (channels > 2) {
IM_COLOR *inp = in;
IM_COLOR const *outp = out;
i_img_dim work_count = count;
while (work_count--) {
IM_COLOR c = *inp;
*inp = *outp;
IM_RGB_TO_HSV(&c);
IM_RGB_TO_HSV(inp);
inp->channel[0] = c.channel[0];
inp->channel[1] = c.channel[1];
IM_HSV_TO_RGB(inp);
inp->channel[3] = c.channel[3];
++outp;
++inp;
}
IM_SUFFIX(combine_line_na)(out, in, channels, count);
}
}
#undef IM_RGB_TO_HSV
#undef IM_HSV_TO_RGB
#/code