don't pass a ssize_t to a %d format string
[imager.git] / render.im
CommitLineData
9c106321
TC
1/*
2Render utilities
3*/
4#include "imager.h"
5
6#define RENDER_MAGIC 0x765AE
7
50c75381 8typedef void (*render_color_f)(i_render *, i_img_dim, i_img_dim, i_img_dim, unsigned char const *src, i_color const *color);
9c106321 9
9b1ec2b8
TC
10#define i_has_alpha(channels) ((channels) == 2 || (channels) == 4)
11
12#define i_color_channels(channels) (i_has_alpha(channels) ? (channels)-1 : (channels))
13
9c106321
TC
14#code
15
50c75381
TC
16static 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);
17static 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);
9c106321
TC
18
19static render_color_f IM_SUFFIX(render_color_tab)[] =
20 {
21 NULL,
22 IM_SUFFIX(render_color_13),
23 IM_SUFFIX(render_color_alpha),
24 IM_SUFFIX(render_color_13),
25 IM_SUFFIX(render_color_alpha),
26 };
27
50c75381
TC
28static void IM_SUFFIX(combine_line_noalpha)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
29static void IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
9b1ec2b8 30/* the copy variant copies the source alpha to the the output alpha channel */
50c75381 31static void IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
9b1ec2b8 32
50c75381
TC
33static void IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
34static void IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count);
9b1ec2b8 35
9c106321
TC
36#/code
37
50c75381
TC
38/*
39=item i_render_new(im, width)
40=category Blit tools
41
42Allocate a new C<i_render> object and initialize it.
43
44=cut
45*/
46
47i_render *
48i_render_new(i_img *im, i_img_dim width) {
49 i_render *r = mymalloc(sizeof(i_render));
50
51 i_render_init(r, im, width);
52
53 return r;
54}
55
56/*
57=item i_render_delete(r)
58=category Blit tools
59
60Release an C<i_render> object.
61
62=cut
63*/
64
9c106321 65void
50c75381
TC
66i_render_delete(i_render *r) {
67 i_render_done(r);
68 myfree(r);
69}
70
71void
72i_render_init(i_render *r, i_img *im, i_img_dim width) {
9c106321
TC
73 r->magic = RENDER_MAGIC;
74 r->im = im;
9b1ec2b8 75 r->line_width = width;
9c106321
TC
76 r->line_8 = NULL;
77 r->line_double = NULL;
9b1ec2b8
TC
78 r->fill_width = width;
79 r->fill_line_8 = NULL;
80 r->fill_line_double = NULL;
9c106321
TC
81}
82
83void
84i_render_done(i_render *r) {
85 if (r->line_8)
86 myfree(r->line_8);
9b1ec2b8 87 if (r->line_double)
9c106321 88 myfree(r->line_double);
9b1ec2b8
TC
89 if (r->fill_line_8)
90 myfree(r->fill_line_8);
91 if (r->fill_line_double)
92 myfree(r->fill_line_double);
9c106321
TC
93 r->magic = 0;
94}
95
9b1ec2b8 96static void
50c75381 97alloc_line(i_render *r, i_img_dim width, i_img_dim eight_bit) {
9b1ec2b8 98 if (width > r->line_width) {
50c75381 99 i_img_dim new_width = r->line_width * 2;
9b1ec2b8
TC
100 if (new_width < width)
101 new_width = width;
102
103 if (eight_bit) {
104 if (r->line_8)
105 r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width);
106 else
107 r->line_8 = mymalloc(sizeof(i_color) * new_width);
108 if (r->line_double) {
109 myfree(r->line_double);
110 r->line_double = NULL;
111 }
112 }
113 else {
114 if (r->line_double)
115 r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width);
116 else
117 r->line_double = mymalloc(sizeof(i_fcolor) * new_width);
118 if (r->line_8) {
119 myfree(r->line_8);
120 r->line_double = NULL;
121 }
122 }
123
124 r->line_width = new_width;
125 }
126 else {
127 if (eight_bit) {
128 if (!r->line_8)
129 r->line_8 = mymalloc(sizeof(i_color) * r->line_width);
130 if (r->line_double) {
131 myfree(r->line_double);
132 r->line_double = NULL;
133 }
134 }
135 else {
136 if (!r->line_double)
137 r->line_double = mymalloc(sizeof(i_fcolor) * r->line_width);
138 if (r->line_8) {
139 myfree(r->line_8);
140 r->line_8 = NULL;
141 }
142 }
143 }
144}
145
146static void
50c75381 147alloc_fill_line(i_render *r, i_img_dim width, int eight_bit) {
9b1ec2b8 148 if (width > r->fill_width) {
50c75381 149 i_img_dim new_width = r->fill_width * 2;
9b1ec2b8
TC
150 if (new_width < width)
151 new_width = width;
152
153 if (eight_bit) {
154 if (r->line_8)
155 r->fill_line_8 = myrealloc(r->fill_line_8, sizeof(i_color) * new_width);
156 else
157 r->fill_line_8 = mymalloc(sizeof(i_color) * new_width);
158 if (r->fill_line_double) {
159 myfree(r->fill_line_double);
160 r->fill_line_double = NULL;
161 }
162 }
163 else {
164 if (r->fill_line_double)
165 r->fill_line_double = myrealloc(r->fill_line_double, sizeof(i_fcolor) * new_width);
166 else
167 r->fill_line_double = mymalloc(sizeof(i_fcolor) * new_width);
168 if (r->fill_line_8) {
169 myfree(r->fill_line_8);
170 r->fill_line_double = NULL;
171 }
172 }
173
174 r->fill_width = new_width;
175 }
176 else {
177 if (eight_bit) {
178 if (!r->fill_line_8)
179 r->fill_line_8 = mymalloc(sizeof(i_color) * r->fill_width);
180 if (r->fill_line_double) {
181 myfree(r->fill_line_double);
182 r->fill_line_double = NULL;
183 }
184 }
185 else {
186 if (!r->fill_line_double)
187 r->fill_line_double = mymalloc(sizeof(i_fcolor) * r->fill_width);
188 if (r->fill_line_8) {
189 myfree(r->fill_line_8);
190 r->fill_line_8 = NULL;
191 }
192 }
193 }
194}
195
50c75381
TC
196/*
197=item i_render_color(r, x, y, width, source, color)
198=category Blit tools
199
200Render the given color with the coverage specified by C<source[0]> to
201C<source[width-1]>.
202
203Renders in normal combine mode.
204
205=cut
206*/
207
9c106321 208void
50c75381
TC
209i_render_color(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
210 unsigned char const *src, i_color const *color) {
9c106321
TC
211 i_img *im = r->im;
212 if (y < 0 || y >= im->ysize)
213 return;
214 if (x < 0) {
215 width += x;
216 src -= x;
217 x = 0;
218 }
219 if (x + width > im->xsize) {
220 width = im->xsize - x;
221 }
222 if (x >= im->xsize || x + width <= 0 || width <= 0)
223 return;
224
225 /* avoid as much work as we can */
226 while (width > 0 && *src == 0) {
227 --width;
228 ++src;
229 ++x;
230 }
231 while (width > 0 && src[width-1] == 0) {
232 --width;
233 }
234 if (!width)
235 return;
236
9b1ec2b8 237 alloc_line(r, width, r->im->bits <= 8);
9b1ec2b8
TC
238
239#code r->im->bits <= 8
240 /*if (r->IM_SUFFIX(line) == NULL)
241 r->IM_SUFFIX(line) = mymalloc(sizeof(IM_COLOR) * r->width);*/
242 (IM_SUFFIX(render_color_tab)[im->channels])(r, x, y, width, src, color);
243#/code
244}
245
50c75381
TC
246/*
247=item i_render_fill(r, x, y, width, source, fill)
248=category Blit tools
249
250Render the given fill with the coverage in C<source[0]> through
251C<source[width-1]>.
252
253=cut
254*/
255
9b1ec2b8 256void
50c75381
TC
257i_render_fill(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
258 unsigned char const *src, i_fill_t *fill) {
9b1ec2b8
TC
259 i_img *im = r->im;
260 int fill_channels = im->channels;
261
262 if (fill_channels == 1 || fill_channels == 3)
263 ++fill_channels;
264
265 if (y < 0 || y >= im->ysize)
266 return;
267 if (x < 0) {
268 width += x;
269 src -= x;
270 x = 0;
271 }
272 if (x + width > im->xsize) {
273 width = im->xsize - x;
274 }
275 if (x >= im->xsize || x + width <= 0 || width <= 0)
276 return;
277
278 if (src) {
279 /* avoid as much work as we can */
280 while (width > 0 && *src == 0) {
281 --width;
282 ++src;
283 ++x;
284 }
285 while (width > 0 && src[width-1] == 0) {
286 --width;
287 }
288 }
289 if (!width)
290 return;
291
9b1ec2b8
TC
292 alloc_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
293 alloc_fill_line(r, width, r->im->bits <= 8 && fill->f_fill_with_color != NULL);
9c106321 294
9b1ec2b8
TC
295#code r->im->bits <= 8 && fill->f_fill_with_color
296 if (IM_FILL_COMBINE(fill)) {
297 IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
298 IM_COLOR *destc = r->IM_SUFFIX(line);
299 IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
300 if (src) {
301 unsigned char const *srcc = src;
302 IM_COLOR *fillc = r->IM_SUFFIX(fill_line);
50c75381 303 i_img_dim work_width = width;
9b1ec2b8
TC
304 while (work_width) {
305 if (*srcc == 0) {
306 fillc->channel[fill_channels-1] = 0;
307 }
308 else if (*srcc != 255) {
309 fillc->channel[fill_channels-1] =
310 fillc->channel[fill_channels-1] * *srcc / 255;
311 }
312 --work_width;
313 ++srcc;
314 ++fillc;
315 }
316 }
317 IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
318 IM_FILL_COMBINE(fill)(destc, srcc, r->im->channels, width);
319 }
320 else {
321 if (src) {
50c75381 322 i_img_dim work_width = width;
9b1ec2b8
TC
323 IM_COLOR *srcc = r->IM_SUFFIX(fill_line);
324 IM_COLOR *destc = r->IM_SUFFIX(line);
325 int ch;
326
327 IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(fill_line));
328 IM_GLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
329 while (work_width) {
330 if (*src == 255) {
331 /* just replace it */
332 *destc = *srcc;
333 }
334 else if (*src) {
335 for (ch = 0; ch < im->channels; ++ch) {
7a98c442
TC
336 IM_WORK_T work = (destc->channel[ch] * (255 - *src)
337 + srcc->channel[ch] * *src) / 255.0;
9b1ec2b8
TC
338 destc->channel[ch] = IM_LIMIT(work);
339 }
340 }
341
342 ++srcc;
343 ++destc;
344 ++src;
345 --work_width;
346 }
347 }
348 else { /* if (src) */
e958b64e 349 IM_FILL_FILLER(fill)(fill, x, y, width, fill_channels, r->IM_SUFFIX(line));
9b1ec2b8
TC
350 }
351 }
352 IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
9c106321
TC
353#/code
354}
355
8d14daab
TC
356#if 0
357
358/* for debuggin */
359
9c106321 360static void
50c75381
TC
361dump_src(const char *note, unsigned char const *src, i_img_dim width) {
362 i_img_dim i;
8d14daab 363 printf("%s - %p/%" i_DF "\n", note, src, i_DFc(width));
9c106321
TC
364 for (i = 0; i < width; ++i) {
365 printf("%02x ", src[i]);
366 }
367 putchar('\n');
368}
369
8d14daab
TC
370#endif
371
9c106321
TC
372#code
373
50c75381
TC
374/*
375=item i_render_line(r, x, y, width, source, fill)
376=category Blit tools
377
378Render the given fill with the coverage in C<source[0]> through
379C<source[width-1]>.
380
381=cut
382
383=item i_render_linef(r, x, y, width, source, fill)
384=category Blit tools
385
386Render the given fill with the coverage in C<source[0]> through
387C<source[width-1]>.
388
389=cut
390*/
391
9b1ec2b8 392void
50c75381
TC
393IM_RENDER_LINE(i_render *r, i_img_dim x, i_img_dim y, i_img_dim width,
394 const IM_SAMPLE_T *src, IM_COLOR *line,
395 IM_FILL_COMBINE_F combine) {
9b1ec2b8
TC
396 i_img *im = r->im;
397 int src_chans = im->channels;
398
399 /* src must always have an alpha channel */
400 if (src_chans == 1 || src_chans == 3)
401 ++src_chans;
402
403 if (y < 0 || y >= im->ysize)
404 return;
405 if (x < 0) {
406 src -= x;
407 line -= x;
408 width += x;
409 x = 0;
410 }
411 if (x + width > im->xsize)
412 width = r->im->xsize - x;
413
414#ifdef IM_EIGHT_BIT
415 alloc_line(r, width, 1);
416#else
417 alloc_line(r, width, 0);
418#endif
419
420 if (combine) {
421 if (src) {
50c75381 422 i_img_dim work_width = width;
9b1ec2b8
TC
423 IM_COLOR *linep = line;
424 const IM_SAMPLE_T *srcp = src;
425 int alpha_chan = src_chans - 1;
426
427 while (work_width) {
428 if (*srcp) {
429 if (*srcp != IM_SAMPLE_MAX)
430 linep->channel[alpha_chan] =
431 linep->channel[alpha_chan] * *srcp / IM_SAMPLE_MAX;
432 }
433 else {
434 linep->channel[alpha_chan] = 0;
435 }
436 --work_width;
437 ++srcp;
438 ++linep;
439 }
440 }
441 IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
442 combine(r->IM_SUFFIX(line), line, im->channels, width);
443 IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
444 }
445 else {
446 if (src) {
50c75381 447 i_img_dim work_width = width;
9b1ec2b8
TC
448 IM_COLOR *srcc = line;
449 IM_COLOR *destc = r->IM_SUFFIX(line);
450
451 IM_GLIN(im, x, x+width, y, r->IM_SUFFIX(line));
452 while (work_width) {
453 if (*src == 255) {
454 /* just replace it */
455 *destc = *srcc;
456 }
457 else if (*src) {
458 int ch;
459 for (ch = 0; ch < im->channels; ++ch) {
460 IM_WORK_T work = (destc->channel[ch] * (IM_SAMPLE_MAX - *src)
461 + srcc->channel[ch] * *src) / IM_SAMPLE_MAX;
462 destc->channel[ch] = IM_LIMIT(work);
463 }
464 }
465
466 ++srcc;
467 ++destc;
468 ++src;
469 --work_width;
470 }
471 IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
472 }
473 else {
474 IM_PLIN(im, x, x+width, y, line);
475 }
476 }
477}
478
9c106321
TC
479static
480void
50c75381
TC
481IM_SUFFIX(render_color_13)(i_render *r, i_img_dim x, i_img_dim y,
482 i_img_dim width, unsigned char const *src,
483 i_color const *color) {
9c106321
TC
484 i_img *im = r->im;
485 IM_COLOR *linep = r->IM_SUFFIX(line);
486 int ch, channels = im->channels;
50c75381 487 i_img_dim fetch_offset;
9c106321
TC
488#undef STORE_COLOR
489#ifdef IM_EIGHT_BIT
490#define STORE_COLOR (*color)
491#else
492 i_fcolor fcolor;
493
494 for (ch = 0; ch < channels; ++ch) {
495 fcolor.channel[ch] = color->channel[ch] / 255.0;
496 }
497#define STORE_COLOR fcolor
498#endif
499
500 fetch_offset = 0;
501 while (fetch_offset < width && *src == 0xFF) {
502 *linep++ = STORE_COLOR;
503 ++src;
504 ++fetch_offset;
505 }
506 IM_GLIN(im, x+fetch_offset, x+width, y, linep);
507 while (fetch_offset < width) {
508#ifdef IM_EIGHT_BIT
509 IM_WORK_T alpha = *src++;
510#else
511 IM_WORK_T alpha = *src++ / 255.0;
512#endif
513 if (alpha == IM_SAMPLE_MAX)
514 *linep = STORE_COLOR;
515 else if (alpha) {
516 for (ch = 0; ch < channels; ++ch) {
517 linep->channel[ch] = (linep->channel[ch] * (IM_SAMPLE_MAX - alpha)
518 + STORE_COLOR.channel[ch] * alpha) / IM_SAMPLE_MAX;
519 }
520 }
521 ++linep;
522 ++fetch_offset;
523 }
524 IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line));
525}
526
527static
528void
50c75381
TC
529IM_SUFFIX(render_color_alpha)(i_render *r, i_img_dim x, i_img_dim y,
530 i_img_dim width, unsigned char const *src,
531 i_color const *color) {
9c106321
TC
532 IM_COLOR *linep = r->IM_SUFFIX(line);
533 int ch;
534 int alpha_channel = r->im->channels - 1;
50c75381 535 i_img_dim fetch_offset;
9c106321
TC
536#undef STORE_COLOR
537#ifdef IM_EIGHT_BIT
538#define STORE_COLOR (*color)
539#else
540 i_fcolor fcolor;
541
542 for (ch = 0; ch < r->im->channels; ++ch) {
543 fcolor.channel[ch] = color->channel[ch] / 255.0;
544 }
545#define STORE_COLOR fcolor
546#endif
547
548 fetch_offset = 0;
549 while (fetch_offset < width && *src == 0xFF) {
550 *linep++ = STORE_COLOR;
551 ++src;
552 ++fetch_offset;
553 }
554 IM_GLIN(r->im, x+fetch_offset, x+width, y, linep);
555 while (fetch_offset < width) {
556#ifdef IM_EIGHT_BIT
557 IM_WORK_T src_alpha = *src++;
558#else
559 IM_WORK_T src_alpha = *src++ / 255.0;
560#endif
561 if (src_alpha == IM_SAMPLE_MAX)
562 *linep = STORE_COLOR;
563 else if (src_alpha) {
e6e94ab0 564 IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
9c106321
TC
565 IM_WORK_T orig_alpha = linep->channel[alpha_channel];
566 IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
567 for (ch = 0; ch < alpha_channel; ++ch) {
568 linep->channel[ch] = ( src_alpha * STORE_COLOR.channel[ch]
569 + remains * linep->channel[ch] * orig_alpha / IM_SAMPLE_MAX
570 ) / dest_alpha;
571 }
572 linep->channel[alpha_channel] = dest_alpha;
573 }
574 ++linep;
575 ++fetch_offset;
576 }
577 IM_PLIN(r->im, x, x+width, y, r->IM_SUFFIX(line));
9b1ec2b8
TC
578#undef STORE_COLOR
579}
580
581/* combine a line of image data with an output line, both the input
582 and output lines include an alpha channel.
583
584 Both input and output lines have I<channels> of data, channels
585 should be either 2 or 4.
586*/
587
588static void
589IM_SUFFIX(combine_line_alpha)(IM_COLOR *out, IM_COLOR const *in,
50c75381 590 int channels, i_img_dim count) {
9b1ec2b8
TC
591 int ch;
592 int alpha_channel = channels - 1;
593
594 while (count) {
595 IM_WORK_T src_alpha = in->channel[alpha_channel];
596
597 if (src_alpha == IM_SAMPLE_MAX)
598 *out = *in;
599 else if (src_alpha) {
600 IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
601 IM_WORK_T orig_alpha = out->channel[alpha_channel];
602 IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
603
604 for (ch = 0; ch < alpha_channel; ++ch) {
605 out->channel[ch] = ( src_alpha * in->channel[ch]
606 + remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
607 ) / dest_alpha;
608 }
609 out->channel[alpha_channel] = dest_alpha;
610 }
611
612 ++out;
613 ++in;
614 --count;
615 }
616}
617
618/* combine a line of image data with an output line. The input line
619 includes an alpha channel, the output line has no alpha channel.
620
621 The input line has I<channels>+1 of color data. The output line
622 has I<channels> of color data.
623*/
624
625static void
626IM_SUFFIX(combine_line_noalpha)
50c75381 627 (IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
9b1ec2b8
TC
628 int ch;
629
630 while (count) {
631 IM_WORK_T src_alpha = in->channel[channels];
632
633 if (src_alpha == IM_SAMPLE_MAX)
634 *out = *in;
635 else if (src_alpha) {
636 IM_WORK_T remains;
637
638 remains = IM_SAMPLE_MAX - src_alpha;
639 for (ch = 0; ch < channels; ++ch) {
640 out->channel[ch] = ( in->channel[ch] * src_alpha
641 + out->channel[ch] * remains) / IM_SAMPLE_MAX;
642 }
643 }
644
645 ++out;
646 ++in;
647 --count;
648 }
649}
650
651/* combine a line of image data with an output line, both the input
652 and output lines include an alpha channel.
653
654 Both input and output lines have I<channels> of data, channels
655 should be either 2 or 4.
656
657 This variant does not modify the output alpha channel.
658*/
659
660static void
661IM_SUFFIX(combine_line_alpha_na)(IM_COLOR *out, IM_COLOR const *in,
50c75381 662 int channels, i_img_dim count) {
9b1ec2b8
TC
663 int ch;
664 int alpha_channel = channels - 1;
665
666 while (count) {
667 IM_WORK_T src_alpha = in->channel[alpha_channel];
668
669 if (src_alpha == IM_SAMPLE_MAX)
670 *out = *in;
671 else if (src_alpha) {
672 IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
673 IM_WORK_T orig_alpha = out->channel[alpha_channel];
674 IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
675
676 for (ch = 0; ch < alpha_channel; ++ch) {
677 out->channel[ch] = ( src_alpha * in->channel[ch]
678 + remains * out->channel[ch] * orig_alpha / IM_SAMPLE_MAX
679 ) / dest_alpha;
680 }
681 }
682
683 ++out;
684 ++in;
685 --count;
686 }
687}
688
689static void
50c75381 690IM_SUFFIX(combine_line)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
9b1ec2b8
TC
691 if (channels == 2 || channels == 4)
692 IM_SUFFIX(combine_line_alpha)(out, in, channels, count);
693 else
694 IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
695}
696
697static void
50c75381 698IM_SUFFIX(combine_line_na)(IM_COLOR *out, IM_COLOR const *in, int channels, i_img_dim count) {
9b1ec2b8
TC
699 if (channels == 2 || channels == 4)
700 IM_SUFFIX(combine_line_alpha_na)(out, in, channels, count);
701 else
702 IM_SUFFIX(combine_line_noalpha)(out, in, channels, count);
703}
704
50c75381
TC
705static void IM_SUFFIX(combine_alphablend)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
706static void IM_SUFFIX(combine_mult)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
707static void IM_SUFFIX(combine_dissolve)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
708static void IM_SUFFIX(combine_add)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
709static void IM_SUFFIX(combine_subtract)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
710static void IM_SUFFIX(combine_diff)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
711static void IM_SUFFIX(combine_darken)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
712static void IM_SUFFIX(combine_lighten)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
713static void IM_SUFFIX(combine_hue)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
714static void IM_SUFFIX(combine_sat)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
715static void IM_SUFFIX(combine_value)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
716static void IM_SUFFIX(combine_color)(IM_COLOR *, IM_COLOR *, int, i_img_dim);
9b1ec2b8
TC
717
718static const IM_FILL_COMBINE_F IM_SUFFIX(combines)[] =
719{
720 NULL,
721 IM_SUFFIX(combine_alphablend),
722 IM_SUFFIX(combine_mult),
723 IM_SUFFIX(combine_dissolve),
724 IM_SUFFIX(combine_add),
725 IM_SUFFIX(combine_subtract),
726 IM_SUFFIX(combine_diff),
727 IM_SUFFIX(combine_lighten),
728 IM_SUFFIX(combine_darken),
729 IM_SUFFIX(combine_hue),
730 IM_SUFFIX(combine_sat),
731 IM_SUFFIX(combine_value),
732 IM_SUFFIX(combine_color)
733};
734
735#/code
736
737/*
738=item i_get_combine(combine, color_func, fcolor_func)
739
740=cut
741*/
742
743void i_get_combine(int combine, i_fill_combine_f *color_func,
744 i_fill_combinef_f *fcolor_func) {
745 if (combine < 0 || combine > sizeof(combines_8) / sizeof(*combines_8))
746 combine = 0;
747
748 *color_func = combines_8[combine];
749 *fcolor_func = combines_double[combine];
750}
751
752#code
753
754/*
755 Three good references for implementing combining modes:
756
757 http://www.w3.org/TR/2004/WD-SVG12-20041027/rendering.html
758 referenced as [svg1.2]
759
760 http://gimp-savvy.com/BOOK/index.html?node55.html
761 ("The Blending Modes", if it changes)
762 referenced as [savvy]
763
764 http://www.pegtop.net/delphi/articles/blendmodes/
765 referenced as [pegtop]
766
767 Where differences exist, I follow the SVG practice, the gimp
768 practice, and lastly pegtop.
769*/
770
771
772static void
50c75381 773IM_SUFFIX(combine_alphablend)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
774 IM_SUFFIX(combine_line)(out, in, channels, count);
775}
776
777/*
778Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
779Da' = Sa.Da + Sa.(1 - Da) + Da.(1 - Sa)
780 = Sa + Da - Sa.Da
781
782When Da=1
783
784Dc' = Sc.Sa.Dc + Dc.(1 - Sa)
785 */
786static void
50c75381 787IM_SUFFIX(combine_mult)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
788 int ch;
789 IM_COLOR *inp = in;
790 IM_COLOR *outp = out;
50c75381 791 i_img_dim work_count = count;
9b1ec2b8
TC
792 int color_channels = i_color_channels(channels);
793
794 if (i_has_alpha(channels)) {
795 while (work_count--) {
796 IM_WORK_T orig_alpha = outp->channel[color_channels];
797 IM_WORK_T src_alpha = inp->channel[color_channels];
798
799 if (src_alpha) {
800 IM_WORK_T dest_alpha = src_alpha + orig_alpha
801 - (src_alpha * orig_alpha) / IM_SAMPLE_MAX;
802
803 for (ch = 0; ch < color_channels; ++ch) {
804 outp->channel[ch] =
805 (inp->channel[ch] * src_alpha * outp->channel[ch] / IM_SAMPLE_MAX
806 * orig_alpha
807 + inp->channel[ch] * src_alpha * (IM_SAMPLE_MAX - orig_alpha)
808 + outp->channel[ch] * orig_alpha * (IM_SAMPLE_MAX - src_alpha))
809 / IM_SAMPLE_MAX / dest_alpha;
810 }
811 outp->channel[color_channels] = dest_alpha;
812 }
813 ++outp;
814 ++inp;
815 }
816 }
817 else {
818 while (work_count--) {
819 IM_WORK_T src_alpha = inp->channel[color_channels];
820 IM_WORK_T remains = IM_SAMPLE_MAX - src_alpha;
821
822 if (src_alpha) {
823 for (ch = 0; ch < color_channels; ++ch) {
824 outp->channel[ch] =
825 (src_alpha * inp->channel[ch] * outp->channel[ch] / IM_SAMPLE_MAX
826 + outp->channel[ch] * remains) / IM_SAMPLE_MAX;
827 }
828 }
829 ++outp;
830 ++inp;
831 }
832 }
833}
834
835static void
50c75381 836IM_SUFFIX(combine_dissolve)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
837 int color_channels = i_color_channels(channels);
838 int ch;
839
840 if (i_has_alpha(channels)) {
841 while (count--) {
842 if (in->channel[channels-1] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
843 for (ch = 0; ch < color_channels; ++ch) {
844 out->channel[ch] = in->channel[ch];
845 }
846 out->channel[color_channels] = IM_SAMPLE_MAX;
847 }
848 ++out;
849 ++in;
850 }
851 }
852 else {
853 while (count--) {
854 if (in->channel[channels] > rand() * ((double)IM_SAMPLE_MAX / RAND_MAX)) {
855 for (ch = 0; ch < color_channels; ++ch) {
856 out->channel[ch] = in->channel[ch];
857 }
858 }
859 ++out;
860 ++in;
861 }
862 }
863}
864
865/*
866Dca' = Sca.Da + Dca.Sa + Sca.(1 - Da) + Dca.(1 - Sa)
867 = Sca + Dca
868Da' = Sa.Da + Da.Sa + Sa.(1 - Da) + Da.(1 - Sa)
869 = Sa + Da
870*/
871
872static void
50c75381 873IM_SUFFIX(combine_add)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
874 int ch;
875 int color_channels = i_color_channels(channels);
50c75381 876 i_img_dim work_count = count;
9b1ec2b8
TC
877 IM_COLOR *inp = in;
878 IM_COLOR *outp = out;
879
880 if (i_has_alpha(channels)) {
881 while (work_count--) {
882 IM_WORK_T src_alpha = inp->channel[color_channels];
883 if (src_alpha) {
884 IM_WORK_T orig_alpha = outp->channel[color_channels];
885 IM_WORK_T dest_alpha = src_alpha + orig_alpha;
886 if (dest_alpha > IM_SAMPLE_MAX)
887 dest_alpha = IM_SAMPLE_MAX;
888 for (ch = 0; ch < color_channels; ++ch) {
889 IM_WORK_T total = (outp->channel[ch] * orig_alpha + inp->channel[ch] * src_alpha) / dest_alpha;
890 if (total > IM_SAMPLE_MAX)
891 total = IM_SAMPLE_MAX;
892 outp->channel[ch] = total;
893 }
894 outp->channel[color_channels] = dest_alpha;
895 }
896
897 ++outp;
898 ++inp;
899 }
900 }
901 else {
902 while (work_count--) {
903 IM_WORK_T src_alpha = inp->channel[color_channels];
904 if (src_alpha) {
905 for (ch = 0; ch < color_channels; ++ch) {
906 IM_WORK_T total = outp->channel[ch] + inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
907 if (total > IM_SAMPLE_MAX)
908 total = IM_SAMPLE_MAX;
909 outp->channel[ch] = total;
910 }
911 }
912
913 ++outp;
914 ++inp;
915 }
916 }
917}
918
919/*
920 [pegtop] documents this as max(A+B-256, 0) while [savvy] documents
921 it as max(A-B, 0). [svg1.2] doesn't cover it.
922
923 [savvy] doesn't document how it works with an alpha channel. GIMP
924 actually seems to calculate the final value then use the alpha
925 channel to apply that to the target.
926 */
927static void
50c75381 928IM_SUFFIX(combine_subtract)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
929 int ch;
930 IM_COLOR const *inp = in;
931 IM_COLOR *outp = out;
50c75381 932 i_img_dim work_count = count;
9b1ec2b8
TC
933 int color_channels = i_color_channels(channels);
934
935 if (i_has_alpha(channels)) {
936 while (work_count--) {
937 IM_WORK_T src_alpha = inp->channel[color_channels];
938 if (src_alpha) {
939 IM_WORK_T orig_alpha = outp->channel[color_channels];
940 IM_WORK_T dest_alpha = src_alpha + orig_alpha;
941 if (dest_alpha > IM_SAMPLE_MAX)
942 dest_alpha = IM_SAMPLE_MAX;
943 for (ch = 0; ch < color_channels; ++ch) {
944 IM_WORK_T total =
945 (outp->channel[ch] * orig_alpha - inp->channel[ch] * src_alpha)
946 / dest_alpha;
947 if (total < 0)
948 total = 0;
949 outp->channel[ch] = total;
950 }
951 outp->channel[color_channels] = dest_alpha;
952 }
953 ++outp;
954 ++inp;
955 }
956 }
957 else {
958 while (work_count--) {
959 IM_WORK_T src_alpha = inp->channel[color_channels];
960 if (src_alpha) {
961 for (ch = 0; ch < color_channels; ++ch) {
962 IM_WORK_T total = outp->channel[ch] - inp->channel[ch] * src_alpha / IM_SAMPLE_MAX;
963 if (total < 0)
964 total = 0;
965 outp->channel[ch] = total;
966 }
967 }
968 ++outp;
969 ++inp;
970 }
971 }
972}
973
974#ifdef IM_EIGHT_BIT
975#define IM_abs(x) abs(x)
976#else
977#define IM_abs(x) fabs(x)
978#endif
979
980/*
981Dca' = abs(Dca.Sa - Sca.Da) + Sca.(1 - Da) + Dca.(1 - Sa)
982 = Sca + Dca - 2.min(Sca.Da, Dca.Sa)
983Da' = Sa + Da - Sa.Da
984*/
985static void
50c75381 986IM_SUFFIX(combine_diff)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
987 int ch;
988 IM_COLOR const *inp = in;
989 IM_COLOR *outp = out;
50c75381 990 i_img_dim work_count = count;
9b1ec2b8
TC
991 int color_channels = i_color_channels(channels);
992
993 if (i_has_alpha(channels)) {
994 while (work_count--) {
995 IM_WORK_T src_alpha = inp->channel[color_channels];
996 if (src_alpha) {
997 IM_WORK_T orig_alpha = outp->channel[color_channels];
998 IM_WORK_T dest_alpha = src_alpha + orig_alpha
999 - src_alpha * orig_alpha / IM_SAMPLE_MAX;
1000 for (ch = 0; ch < color_channels; ++ch) {
1001 IM_WORK_T src = inp->channel[ch] * src_alpha;
1002 IM_WORK_T orig = outp->channel[ch] * orig_alpha;
1003 IM_WORK_T src_da = src * orig_alpha;
1004 IM_WORK_T dest_sa = orig * src_alpha;
1005 IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
1006 outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / dest_alpha;
1007 }
1008 outp->channel[color_channels] = dest_alpha;
1009 }
1010 ++inp;
1011 ++outp;
1012 }
1013 }
1014 else {
1015 while (work_count--) {
1016 IM_WORK_T src_alpha = inp->channel[color_channels];
1017 if (src_alpha) {
1018 for (ch = 0; ch < color_channels; ++ch) {
1019 IM_WORK_T src = inp->channel[ch] * src_alpha;
1020 IM_WORK_T orig = outp->channel[ch] * IM_SAMPLE_MAX;
1021 IM_WORK_T src_da = src * IM_SAMPLE_MAX;
1022 IM_WORK_T dest_sa = orig * src_alpha;
1023 IM_WORK_T diff = src_da < dest_sa ? src_da : dest_sa;
1024 outp->channel[ch] = (src + orig - 2 * diff / IM_SAMPLE_MAX) / IM_SAMPLE_MAX;
1025 }
1026 }
1027 ++inp;
1028 ++outp;
1029 }
1030 }
1031}
1032
1033#undef IM_abs
1034
1035/*
1036 Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca(1 - Sa)
1037 Da' = Sa + Da - Sa.Da
1038
1039 To hoist some code:
1040
1041 Dca' = min(Sc.Sa.Da, Dc.Da.Sa) + Sca - Sca.Da + Dca - Dca.Sa
1042 = Sa.Da.min(Sc, Dc) + Sca - Sca.Da + Dca - Dca.Sa
1043
1044 When Da=1:
1045
1046 Dca' = min(Sca.1, Dc.1.Sa) + Sca.(1 - 1) + Dc.1(1 - Sa)
1047 = min(Sca, Dc.Sa) + Dc(1-Sa)
1048 = Sa.min(Sc, Dc) + Dc - Dc.Sa
1049 Da' = Sa + 1 - Sa.1
1050 = 1
1051 */
1052static void
1053IM_SUFFIX(combine_darken)(IM_COLOR *out, IM_COLOR *in, int channels,
50c75381 1054 i_img_dim count) {
9b1ec2b8
TC
1055 int ch;
1056 IM_COLOR const *inp = in;
1057 IM_COLOR *outp = out;
50c75381 1058 i_img_dim work_count = count;
9b1ec2b8
TC
1059 int color_channels = i_color_channels(channels);
1060
1061 if (i_has_alpha(channels)) {
1062 while (work_count--) {
1063 IM_WORK_T src_alpha = inp->channel[color_channels];
1064
1065 if (src_alpha) {
1066 IM_WORK_T orig_alpha = outp->channel[color_channels];
1067 IM_WORK_T dest_alpha = src_alpha + orig_alpha
1068 - src_alpha * orig_alpha / IM_SAMPLE_MAX;
1069 for (ch = 0; ch < color_channels; ++ch) {
1070 IM_WORK_T Sca = inp->channel[ch] * src_alpha;
1071 IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
1072 IM_WORK_T ScaDa = Sca * orig_alpha;
1073 IM_WORK_T DcaSa = Dca * src_alpha;
1074 IM_WORK_T minc = ScaDa < DcaSa ? ScaDa : DcaSa;
1075 outp->channel[ch] =
1076 (
1077 minc + (Sca + Dca) * IM_SAMPLE_MAX
1078 - ScaDa - DcaSa
1079 ) / (IM_SAMPLE_MAX * dest_alpha);
1080 }
1081 outp->channel[color_channels] = dest_alpha;
1082 }
1083 ++outp;
1084 ++inp;
1085 }
1086 }
1087 else {
1088 while (work_count--) {
1089 IM_WORK_T src_alpha = inp->channel[color_channels];
1090
1091 if (src_alpha) {
1092 for (ch = 0; ch < color_channels; ++ch) {
1093 IM_WORK_T minc = outp->channel[ch] < inp->channel[ch]
1094 ? outp->channel[ch] : inp->channel[ch];
1095 outp->channel[ch] =
1096 (
1097 src_alpha * minc +
1098 outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
1099 ) / IM_SAMPLE_MAX;
1100 }
1101 }
1102 ++outp;
1103 ++inp;
1104 }
1105 }
1106}
1107
1108static void
50c75381 1109IM_SUFFIX(combine_lighten)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
1110 int ch;
1111 IM_COLOR const *inp = in;
1112 IM_COLOR *outp = out;
50c75381 1113 i_img_dim work_count = count;
9b1ec2b8
TC
1114 int color_channels = i_color_channels(channels);
1115
1116 if (i_has_alpha(channels)) {
1117 while (work_count--) {
1118 IM_WORK_T src_alpha = inp->channel[color_channels];
1119
1120 if (src_alpha) {
1121 IM_WORK_T orig_alpha = outp->channel[color_channels];
1122 IM_WORK_T dest_alpha = src_alpha + orig_alpha
1123 - src_alpha * orig_alpha / IM_SAMPLE_MAX;
1124 for (ch = 0; ch < color_channels; ++ch) {
1125 IM_WORK_T Sca = inp->channel[ch] * src_alpha;
1126 IM_WORK_T Dca = outp->channel[ch] * orig_alpha;
1127 IM_WORK_T ScaDa = Sca * orig_alpha;
1128 IM_WORK_T DcaSa = Dca * src_alpha;
1129 IM_WORK_T maxc = ScaDa > DcaSa ? ScaDa : DcaSa;
1130 outp->channel[ch] =
1131 (
1132 maxc + (Sca + Dca) * IM_SAMPLE_MAX
1133 - ScaDa - DcaSa
1134 ) / (IM_SAMPLE_MAX * dest_alpha);
1135 }
1136 outp->channel[color_channels] = dest_alpha;
1137 }
1138 ++outp;
1139 ++inp;
1140 }
1141 }
1142 else {
1143 while (work_count--) {
1144 IM_WORK_T src_alpha = inp->channel[color_channels];
1145
1146 if (src_alpha) {
1147 for (ch = 0; ch < color_channels; ++ch) {
1148 IM_WORK_T maxc = outp->channel[ch] > inp->channel[ch]
1149 ? outp->channel[ch] : inp->channel[ch];
1150 outp->channel[ch] =
1151 (
1152 src_alpha * maxc +
1153 outp->channel[ch] * ( IM_SAMPLE_MAX - src_alpha )
1154 ) / IM_SAMPLE_MAX;
1155 }
1156 }
1157 ++outp;
1158 ++inp;
1159 }
1160 }
1161}
1162
1163#if IM_EIGHT_BIT
1164#define IM_RGB_TO_HSV i_rgb_to_hsv
1165#define IM_HSV_TO_RGB i_hsv_to_rgb
1166#else
1167#define IM_RGB_TO_HSV i_rgb_to_hsvf
1168#define IM_HSV_TO_RGB i_hsv_to_rgbf
1169#endif
1170
1171static void
50c75381 1172IM_SUFFIX(combine_hue)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
1173 if (channels > 2) {
1174 IM_COLOR *inp = in;
1175 IM_COLOR const *outp = out;
50c75381 1176 i_img_dim work_count = count;
9b1ec2b8
TC
1177
1178 if (i_has_alpha(channels)) {
1179 while (work_count--) {
1180 IM_COLOR c = *inp;
1181 IM_RGB_TO_HSV(&c);
1182 /* only transfer hue if there's saturation */
1183 if (c.channel[1] && inp->channel[3] && outp->channel[3]) {
1184 *inp = *outp;
1185 IM_RGB_TO_HSV(inp);
1186 /* and no point in setting the target hue if the target has no sat */
1187 if (inp->channel[1]) {
1188 inp->channel[0] = c.channel[0];
1189 IM_HSV_TO_RGB(inp);
1190 inp->channel[3] = c.channel[3];
1191 }
1192 else {
1193 inp->channel[3] = 0;
1194 }
1195 }
1196 else {
1197 inp->channel[3] = 0;
1198 }
1199
1200 ++outp;
1201 ++inp;
1202 }
1203 }
1204 else {
1205 while (work_count--) {
1206 IM_COLOR c = *inp;
1207 IM_RGB_TO_HSV(&c);
1208 /* only transfer hue if there's saturation */
1209 if (c.channel[1] && inp->channel[3]) {
1210 *inp = *outp;
1211 IM_RGB_TO_HSV(inp);
1212 /* and no point in setting the target hue if the target has no sat */
1213 if (inp->channel[1]) {
1214 inp->channel[0] = c.channel[0];
1215 IM_HSV_TO_RGB(inp);
1216 inp->channel[3] = c.channel[3];
1217 }
1218 }
1219 else {
1220 inp->channel[3] = 0;
1221 }
1222
1223 ++outp;
1224 ++inp;
1225 }
1226 }
1227
1228 IM_SUFFIX(combine_line_na)(out, in, channels, count);
1229 }
9c106321
TC
1230}
1231
9b1ec2b8 1232static void
50c75381 1233IM_SUFFIX(combine_sat)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
1234 if (channels > 2) {
1235 IM_COLOR *inp = in;
1236 IM_COLOR const *outp = out;
50c75381 1237 i_img_dim work_count = count;
9b1ec2b8
TC
1238
1239 while (work_count--) {
1240 IM_COLOR c = *inp;
1241 *inp = *outp;
1242 IM_RGB_TO_HSV(&c);
1243 IM_RGB_TO_HSV(inp);
1244 inp->channel[1] = c.channel[1];
1245 IM_HSV_TO_RGB(inp);
1246 inp->channel[3] = c.channel[3];
1247 ++outp;
1248 ++inp;
1249 }
1250
1251 IM_SUFFIX(combine_line_na)(out, in, channels, count);
1252 }
1253}
1254
1255static void
50c75381 1256IM_SUFFIX(combine_value)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
1257 if (channels > 2) {
1258 IM_COLOR *inp = in;
1259 IM_COLOR const *outp = out;
50c75381 1260 i_img_dim work_count = count;
9b1ec2b8
TC
1261
1262 while (work_count--) {
1263 IM_COLOR c = *inp;
1264 *inp = *outp;
1265 IM_RGB_TO_HSV(&c);
1266 IM_RGB_TO_HSV(inp);
1267 inp->channel[2] = c.channel[2];
1268 IM_HSV_TO_RGB(inp);
1269 inp->channel[3] = c.channel[3];
1270 ++outp;
1271 ++inp;
1272 }
1273 }
1274
1275 /* all images have a "value channel" - for greyscale it's the only
1276 colour channel */
1277 IM_SUFFIX(combine_line_na)(out, in, channels, count);
1278}
1279
1280static void
50c75381 1281IM_SUFFIX(combine_color)(IM_COLOR *out, IM_COLOR *in, int channels, i_img_dim count) {
9b1ec2b8
TC
1282 if (channels > 2) {
1283 IM_COLOR *inp = in;
1284 IM_COLOR const *outp = out;
50c75381 1285 i_img_dim work_count = count;
9b1ec2b8
TC
1286
1287 while (work_count--) {
1288 IM_COLOR c = *inp;
1289 *inp = *outp;
1290 IM_RGB_TO_HSV(&c);
1291 IM_RGB_TO_HSV(inp);
1292 inp->channel[0] = c.channel[0];
1293 inp->channel[1] = c.channel[1];
1294 IM_HSV_TO_RGB(inp);
1295 inp->channel[3] = c.channel[3];
1296 ++outp;
1297 ++inp;
1298 }
1299
1300 IM_SUFFIX(combine_line_na)(out, in, channels, count);
1301 }
1302}
1303
1304#undef IM_RGB_TO_HSV
1305#undef IM_HSV_TO_RGB
1306
9c106321 1307#/code