1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 /* Copyright (C) 2019-2021 Hans Petter Jansson
4 *
5 * This file is part of Chafa, a program that turns images into character art.
6 *
7 * Chafa is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published
9 * by the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Chafa is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Chafa. If not, see <http://www.gnu.org/licenses/>. */
19
20 #include "config.h"
21
22 #include <stdlib.h> /* abs */
23 #include <math.h> /* pow, cbrt, log, sqrt, atan2, cos, sin */
24 #include "chafa.h"
25 #include "internal/chafa-private.h"
26
27 #define DEBUG(x)
28
29 /* FIXME: Refactor old color selection code into a common palette framework */
30
31 static gint
find_dominant_channel(gconstpointer pixels,gint n_pixels)32 find_dominant_channel (gconstpointer pixels, gint n_pixels)
33 {
34 const guint8 *p = pixels;
35 guint8 min [4] = { G_MAXUINT8, G_MAXUINT8, G_MAXUINT8, G_MAXUINT8 };
36 guint8 max [4] = { 0, 0, 0, 0 };
37 guint16 diff [4];
38 gint best;
39 gint i;
40
41 for (i = 0; i < n_pixels; i++)
42 {
43 /* This should yield branch-free code where possible */
44 min [0] = MIN (min [0], *p);
45 max [0] = MAX (max [0], *p);
46 p++;
47 min [1] = MIN (min [1], *p);
48 max [1] = MAX (max [1], *p);
49 p++;
50 min [2] = MIN (min [2], *p);
51 max [2] = MAX (max [2], *p);
52
53 /* Skip alpha */
54 p += 2;
55 }
56
57 #if 1
58 /* Multipliers for luminance */
59 diff [0] = (max [0] - min [0]) * 30;
60 diff [1] = (max [1] - min [1]) * 59;
61 diff [2] = (max [2] - min [2]) * 11;
62 #else
63 diff [0] = (max [0] - min [0]);
64 diff [1] = (max [1] - min [1]);
65 diff [2] = (max [2] - min [2]);
66 #endif
67
68 /* If there are ties, prioritize thusly: G, R, B */
69
70 best = 1;
71 if (diff [0] > diff [best])
72 best = 0;
73 if (diff [2] > diff [best])
74 best = 2;
75
76 return best;
77 }
78
79 static int
compare_rgba_0(gconstpointer a,gconstpointer b)80 compare_rgba_0 (gconstpointer a, gconstpointer b)
81 {
82 const guint8 *ab = a;
83 const guint8 *bb = b;
84 gint ai = ab [0];
85 gint bi = bb [0];
86
87 return ai - bi;
88 }
89
90 static int
compare_rgba_1(gconstpointer a,gconstpointer b)91 compare_rgba_1 (gconstpointer a, gconstpointer b)
92 {
93 const guint8 *ab = a;
94 const guint8 *bb = b;
95 gint ai = ab [1];
96 gint bi = bb [1];
97
98 return ai - bi;
99 }
100
101 static int
compare_rgba_2(gconstpointer a,gconstpointer b)102 compare_rgba_2 (gconstpointer a, gconstpointer b)
103 {
104 const guint8 *ab = a;
105 const guint8 *bb = b;
106 gint ai = ab [2];
107 gint bi = bb [2];
108
109 return ai - bi;
110 }
111
112 static int
compare_rgba_3(gconstpointer a,gconstpointer b)113 compare_rgba_3 (gconstpointer a, gconstpointer b)
114 {
115 const guint8 *ab = a;
116 const guint8 *bb = b;
117 gint ai = ab [3];
118 gint bi = bb [3];
119
120 return ai - bi;
121 }
122
123 static void
sort_by_channel(gpointer pixels,gint n_pixels,gint ch)124 sort_by_channel (gpointer pixels, gint n_pixels, gint ch)
125 {
126 switch (ch)
127 {
128 case 0:
129 qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_0);
130 break;
131 case 1:
132 qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_1);
133 break;
134 case 2:
135 qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_2);
136 break;
137 case 3:
138 qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_3);
139 break;
140 default:
141 g_assert_not_reached ();
142 }
143 }
144
145 #if 0
146
147 static void
148 average_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out)
149 {
150 guint8 *p = pixels + first_ofs * sizeof (guint32);
151 guint8 *pixels_end;
152 gint ch [3] = { 0 };
153
154 pixels_end = p + n_pixels * sizeof (guint32);
155
156 for ( ; p < pixels_end; p += 4)
157 {
158 ch [0] += p [0];
159 ch [1] += p [1];
160 ch [2] += p [2];
161 }
162
163 col_out->ch [0] = (ch [0] + n_pixels / 2) / n_pixels;
164 col_out->ch [1] = (ch [1] + n_pixels / 2) / n_pixels;
165 col_out->ch [2] = (ch [2] + n_pixels / 2) / n_pixels;
166 }
167
168 #endif
169
170 static void
median_pixels(guint8 * pixels,gint first_ofs,gint n_pixels,ChafaColor * col_out)171 median_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out)
172 {
173 guint8 *p = pixels + (first_ofs + n_pixels / 2) * sizeof (guint32);
174 col_out->ch [0] = p [0];
175 col_out->ch [1] = p [1];
176 col_out->ch [2] = p [2];
177 }
178
179 static void
average_pixels_weighted_by_deviation(guint8 * pixels,gint first_ofs,gint n_pixels,ChafaColor * col_out)180 average_pixels_weighted_by_deviation (guint8 *pixels, gint first_ofs, gint n_pixels,
181 ChafaColor *col_out)
182 {
183 guint8 *p = pixels + first_ofs * sizeof (guint32);
184 guint8 *pixels_end;
185 gint ch [3] = { 0 };
186 ChafaColor median;
187 guint sum = 0;
188
189 median_pixels (pixels, first_ofs, n_pixels, &median);
190
191 pixels_end = p + n_pixels * sizeof (guint32);
192
193 for ( ; p < pixels_end; p += 4)
194 {
195 ChafaColor t;
196 guint diff;
197
198 t.ch [0] = p [0];
199 t.ch [1] = p [1];
200 t.ch [2] = p [2];
201
202 diff = chafa_color_diff_fast (&median, &t);
203 diff /= 256;
204 diff += 1;
205
206 ch [0] += p [0] * diff;
207 ch [1] += p [1] * diff;
208 ch [2] += p [2] * diff;
209
210 sum += diff;
211 }
212
213 col_out->ch [0] = (ch [0] + sum / 2) / sum;
214 col_out->ch [1] = (ch [1] + sum / 2) / sum;
215 col_out->ch [2] = (ch [2] + sum / 2) / sum;
216 }
217
218 static void
pick_box_color(gpointer pixels,gint first_ofs,gint n_pixels,ChafaColor * color_out)219 pick_box_color (gpointer pixels, gint first_ofs, gint n_pixels, ChafaColor *color_out)
220 {
221 average_pixels_weighted_by_deviation (pixels, first_ofs, n_pixels, color_out);
222 }
223
224 static void
median_cut_once(gpointer pixels,gint first_ofs,gint n_pixels,ChafaColor * color_out)225 median_cut_once (gpointer pixels,
226 gint first_ofs, gint n_pixels,
227 ChafaColor *color_out)
228 {
229 guint8 *p = pixels;
230 gint dominant_ch;
231
232 g_assert (n_pixels > 0);
233
234 dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels);
235 sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch);
236
237 pick_box_color (pixels, first_ofs, n_pixels, color_out);
238 }
239
240 static void
median_cut(ChafaPalette * pal,gpointer pixels,gint first_ofs,gint n_pixels,gint first_col,gint n_cols)241 median_cut (ChafaPalette *pal, gpointer pixels,
242 gint first_ofs, gint n_pixels,
243 gint first_col, gint n_cols)
244 {
245 guint8 *p = pixels;
246 gint dominant_ch;
247
248 g_assert (n_pixels > 0);
249 g_assert (n_cols > 0);
250
251 dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels);
252 sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch);
253
254 if (n_cols == 1 || n_pixels < 2)
255 {
256 pick_box_color (pixels, first_ofs, n_pixels, &pal->colors [first_col].col [CHAFA_COLOR_SPACE_RGB]);
257 return;
258 }
259
260 median_cut (pal, pixels,
261 first_ofs,
262 n_pixels / 2,
263 first_col,
264 n_cols / 2);
265
266 median_cut (pal, pixels,
267 first_ofs + (n_pixels / 2),
268 n_pixels - (n_pixels / 2),
269 first_col + (n_cols / 2),
270 n_cols - (n_cols / 2));
271 }
272
273 static gint
dominant_diff(guint8 * p1,guint8 * p2)274 dominant_diff (guint8 *p1, guint8 *p2)
275 {
276 gint diff [3];
277
278 diff [0] = abs (p2 [0] - (gint) p1 [0]);
279 diff [1] = abs (p2 [1] - (gint) p1 [1]);
280 diff [2] = abs (p2 [2] - (gint) p1 [2]);
281
282 return MAX (diff [0], MAX (diff [1], diff [2]));
283 }
284
285 static void
diversity_pass(ChafaPalette * pal,gpointer pixels,gint n_pixels,gint first_col,gint n_cols)286 diversity_pass (ChafaPalette *pal, gpointer pixels,
287 gint n_pixels,
288 gint first_col, gint n_cols)
289 {
290 guint8 *p = pixels;
291 gint step = n_pixels / 128;
292 gint i, n, c;
293 guint8 done [128] = { 0 };
294
295 step = MAX (step, 1);
296
297 for (c = 0; c < n_cols; c++)
298 {
299 gint best_box = 0;
300 gint best_diff = 0;
301
302 for (i = 0, n = 0; i < 128 && i < n_pixels; i++)
303 {
304 gint diff = dominant_diff (p + 4 * n, p + 4 * (n + step - 1));
305
306 if (diff > best_diff && !done [i])
307 {
308 best_diff = diff;
309 best_box = i;
310 }
311
312 n += step;
313 }
314
315 median_cut_once (pixels, best_box * step, MAX (step / 2, 1),
316 &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]);
317 c++;
318 if (c >= n_cols)
319 break;
320
321 median_cut_once (pixels, best_box * step + step / 2, MAX (step / 2, 1),
322 &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]);
323
324 done [best_box] = 1;
325 }
326 }
327
328 static void
gen_din99d_color_space(ChafaPalette * palette)329 gen_din99d_color_space (ChafaPalette *palette)
330 {
331 gint i;
332
333 for (i = 0; i < palette->n_colors; i++)
334 {
335 chafa_color_rgb_to_din99d (&palette->colors [i].col [CHAFA_COLOR_SPACE_RGB],
336 &palette->colors [i].col [CHAFA_COLOR_SPACE_DIN99D]);
337 }
338 }
339
340 static void
gen_table(ChafaPalette * palette,ChafaColorSpace color_space)341 gen_table (ChafaPalette *palette, ChafaColorSpace color_space)
342 {
343 gint i;
344
345 for (i = 0; i < palette->n_colors; i++)
346 {
347 const ChafaColor *col;
348
349 if (i == palette->transparent_index)
350 continue;
351
352 col = &palette->colors [i].col [color_space];
353
354 chafa_color_table_set_pen_color (&palette->table [color_space], i,
355 col->ch [0] | (col->ch [1] << 8) | (col->ch [2] << 16));
356 }
357
358 chafa_color_table_sort (&palette->table [color_space]);
359 }
360
361 #define N_SAMPLES 32768
362
363 static gint
extract_samples(gconstpointer pixels,gpointer pixels_out,gint n_pixels,gint step,gint alpha_threshold)364 extract_samples (gconstpointer pixels, gpointer pixels_out, gint n_pixels, gint step,
365 gint alpha_threshold)
366 {
367 const guint32 *p = pixels;
368 guint32 *p_out = pixels_out;
369 gint i;
370
371 step = MAX (step, 1);
372
373 for (i = 0; i < n_pixels; i += step)
374 {
375 gint alpha = p [i] >> 24;
376 if (alpha < alpha_threshold)
377 continue;
378 *(p_out++) = p [i];
379 }
380
381 return ((ptrdiff_t) p_out - (ptrdiff_t) pixels_out) / 4;
382 }
383
384 static gint
extract_samples_dense(gconstpointer pixels,gpointer pixels_out,gint n_pixels,gint n_samples_max,gint alpha_threshold)385 extract_samples_dense (gconstpointer pixels, gpointer pixels_out, gint n_pixels,
386 gint n_samples_max, gint alpha_threshold)
387 {
388 const guint32 *p = pixels;
389 guint32 *p_out = pixels_out;
390 gint n_samples = 0;
391 gint i;
392
393 g_assert (n_samples_max > 0);
394
395 for (i = 0; i < n_pixels; i++)
396 {
397 gint alpha = p [i] >> 24;
398 if (alpha < alpha_threshold)
399 continue;
400
401 *(p_out++) = p [i];
402
403 n_samples++;
404 if (n_samples == n_samples_max)
405 break;
406 }
407
408 return n_samples;
409 }
410
411 static void
clean_up(ChafaPalette * palette_out)412 clean_up (ChafaPalette *palette_out)
413 {
414 gint i, j;
415 gint best_diff = G_MAXINT;
416 gint best_pair = 1;
417
418 /* Reserve 0th pen for transparency and move colors up.
419 * Eliminate duplicates and colors that would be the same in
420 * sixel representation (0..100). */
421
422 DEBUG (g_printerr ("Colors before: %d\n", palette_out->n_colors));
423
424 for (i = 1, j = 1; i < palette_out->n_colors; i++)
425 {
426 ChafaColor *a, *b;
427 gint diff, t;
428
429 a = &palette_out->colors [j - 1].col [CHAFA_COLOR_SPACE_RGB];
430 b = &palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB];
431
432 /* Dividing by 256 is strictly not correct, but it's close enough for
433 * comparison purposes, and a lot faster too. */
434 t = (gint) (a->ch [0] * 100) / 256 - (gint) (b->ch [0] * 100) / 256;
435 diff = t * t;
436 t = (gint) (a->ch [1] * 100) / 256 - (gint) (b->ch [1] * 100) / 256;
437 diff += t * t;
438 t = (gint) (a->ch [2] * 100) / 256 - (gint) (b->ch [2] * 100) / 256;
439 diff += t * t;
440
441 if (diff == 0)
442 {
443 DEBUG (g_printerr ("%d and %d are the same\n", j - 1, i));
444 continue;
445 }
446 else if (diff < best_diff)
447 {
448 best_pair = j - 1;
449 best_diff = diff;
450 }
451
452 palette_out->colors [j++] = palette_out->colors [i];
453 }
454
455 palette_out->n_colors = j;
456
457 DEBUG (g_printerr ("Colors after: %d\n", palette_out->n_colors));
458
459 g_assert (palette_out->n_colors >= 0 && palette_out->n_colors <= 256);
460
461 if (palette_out->transparent_index < 256)
462 {
463 if (palette_out->n_colors < 256)
464 {
465 DEBUG (g_printerr ("Color 0 moved to end (%d)\n", palette_out->n_colors));
466 palette_out->colors [palette_out->n_colors] = palette_out->colors [palette_out->transparent_index];
467 palette_out->n_colors++;
468 }
469 else
470 {
471 /* Delete one color to make room for transparency */
472 palette_out->colors [best_pair] = palette_out->colors [palette_out->transparent_index];
473 DEBUG (g_printerr ("Color 0 replaced %d\n", best_pair));
474 }
475 }
476 }
477
478 void
chafa_palette_init(ChafaPalette * palette_out,ChafaPaletteType type)479 chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type)
480 {
481 gint i;
482
483 chafa_init_palette ();
484 palette_out->type = type;
485
486 for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++)
487 {
488 palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB] = *chafa_get_palette_color_256 (i, CHAFA_COLOR_SPACE_RGB);
489 palette_out->colors [i].col [CHAFA_COLOR_SPACE_DIN99D] = *chafa_get_palette_color_256 (i, CHAFA_COLOR_SPACE_DIN99D);
490 }
491
492 palette_out->transparent_index = CHAFA_PALETTE_INDEX_TRANSPARENT;
493
494 palette_out->first_color = 0;
495 palette_out->n_colors = 256;
496
497 if (type == CHAFA_PALETTE_TYPE_FIXED_240)
498 {
499 palette_out->first_color = 16;
500 palette_out->n_colors = 240;
501 }
502 else if (type == CHAFA_PALETTE_TYPE_FIXED_16)
503 {
504 palette_out->n_colors = 16;
505 }
506 else if (type == CHAFA_PALETTE_TYPE_FIXED_8)
507 {
508 palette_out->n_colors = 8;
509 }
510 else if (type == CHAFA_PALETTE_TYPE_FIXED_FGBG)
511 {
512 palette_out->first_color = CHAFA_PALETTE_INDEX_FG;
513 palette_out->n_colors = 2;
514 }
515
516 if (palette_out->type == CHAFA_PALETTE_TYPE_DYNAMIC_256)
517 {
518 for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++)
519 chafa_color_table_init (&palette_out->table [i]);
520 }
521 }
522
523 void
chafa_palette_deinit(ChafaPalette * palette)524 chafa_palette_deinit (ChafaPalette *palette)
525 {
526 gint i;
527
528 if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256)
529 {
530 for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++)
531 chafa_color_table_deinit (&palette->table [i]);
532 }
533 }
534
535 gint
chafa_palette_get_first_color(const ChafaPalette * palette)536 chafa_palette_get_first_color (const ChafaPalette *palette)
537 {
538 return palette->first_color;
539 }
540
541 gint
chafa_palette_get_n_colors(const ChafaPalette * palette)542 chafa_palette_get_n_colors (const ChafaPalette *palette)
543 {
544 return palette->n_colors;
545 }
546
547 void
chafa_palette_copy(const ChafaPalette * src,ChafaPalette * dest)548 chafa_palette_copy (const ChafaPalette *src, ChafaPalette *dest)
549 {
550 memcpy (dest, src, sizeof (*dest));
551 }
552
553 /* pixels must point to RGBA8888 data to sample */
554 void
chafa_palette_generate(ChafaPalette * palette_out,gconstpointer pixels,gint n_pixels,ChafaColorSpace color_space)555 chafa_palette_generate (ChafaPalette *palette_out, gconstpointer pixels, gint n_pixels,
556 ChafaColorSpace color_space)
557 {
558 guint32 *pixels_copy;
559 gint step;
560 gint copy_n_pixels;
561
562 if (palette_out->type != CHAFA_PALETTE_TYPE_DYNAMIC_256)
563 return;
564
565 pixels_copy = g_malloc (N_SAMPLES * sizeof (guint32));
566
567 step = (n_pixels / N_SAMPLES) + 1;
568 copy_n_pixels = extract_samples (pixels, pixels_copy, n_pixels, step,
569 palette_out->alpha_threshold);
570
571 /* If we recovered very few (potentially zero) samples, it could be due to
572 * the image being mostly transparent. Resample at full density if so. */
573 if (copy_n_pixels < 256 && step != 1)
574 copy_n_pixels = extract_samples_dense (pixels, pixels_copy, n_pixels, N_SAMPLES,
575 palette_out->alpha_threshold);
576
577 DEBUG (g_printerr ("Extracted %d samples.\n", copy_n_pixels));
578
579 if (copy_n_pixels < 1)
580 {
581 palette_out->n_colors = 0;
582 goto out;
583 }
584
585 median_cut (palette_out, pixels_copy, 0, copy_n_pixels, 0, 128);
586 palette_out->n_colors = 128;
587 clean_up (palette_out);
588
589 diversity_pass (palette_out, pixels_copy, copy_n_pixels, palette_out->n_colors, 256 - palette_out->n_colors);
590 palette_out->n_colors = 256;
591 clean_up (palette_out);
592
593 gen_table (palette_out, CHAFA_COLOR_SPACE_RGB);
594
595 if (color_space == CHAFA_COLOR_SPACE_DIN99D)
596 {
597 gen_din99d_color_space (palette_out);
598 gen_table (palette_out, CHAFA_COLOR_SPACE_DIN99D);
599 }
600
601 out:
602 g_free (pixels_copy);
603 }
604
605 gint
chafa_palette_lookup_nearest(const ChafaPalette * palette,ChafaColorSpace color_space,const ChafaColor * color,ChafaColorCandidates * candidates)606 chafa_palette_lookup_nearest (const ChafaPalette *palette, ChafaColorSpace color_space,
607 const ChafaColor *color, ChafaColorCandidates *candidates)
608 {
609 if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256)
610 {
611 gint result;
612
613 /* Transparency */
614 if (color->ch [3] < palette->alpha_threshold)
615 return palette->transparent_index;
616
617 result = chafa_color_table_find_nearest_pen (&palette->table [color_space],
618 color->ch [0]
619 | (color->ch [1] << 8)
620 | (color->ch [2] << 16));
621
622 if (candidates)
623 {
624 /* The only consumer of multiple candidates is the cell canvas, and that
625 * supports fixed palettes only. Therefore, in practice we'll never end up here.
626 * Let's not leave a loose end, though... */
627
628 candidates->index [0] = result;
629 candidates->index [1] = result;
630 candidates->error [0] = 0;
631 candidates->error [1] = 0;
632 }
633
634 return result;
635 }
636 else
637 {
638 ChafaColorCandidates candidates_temp;
639
640 if (!candidates)
641 candidates = &candidates_temp;
642
643 #if 0
644 /* Transparency */
645
646 /* NOTE: Disabled because chafa_pick_color_*() deal
647 * with transparency */
648 if (color->ch [3] < palette->alpha_threshold)
649 return palette->transparent_index;
650 #endif
651
652 if (palette->type == CHAFA_PALETTE_TYPE_FIXED_256)
653 chafa_pick_color_256 (color, color_space, candidates);
654 else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_240)
655 chafa_pick_color_240 (color, color_space, candidates);
656 else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_16)
657 chafa_pick_color_16 (color, color_space, candidates);
658 else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_8)
659 chafa_pick_color_8 (color, color_space, candidates);
660 else /* CHAFA_PALETTE_TYPE_FIXED_FGBG */
661 chafa_pick_color_fgbg (color, color_space,
662 &palette->colors [CHAFA_PALETTE_INDEX_FG].col [color_space],
663 &palette->colors [CHAFA_PALETTE_INDEX_BG].col [color_space],
664 candidates);
665
666 if (palette->transparent_index < 256)
667 {
668 if (candidates->index [0] == palette->transparent_index)
669 {
670 candidates->index [0] = candidates->index [1];
671 candidates->error [0] = candidates->error [1];
672 }
673 else
674 {
675 if (candidates->index [0] == CHAFA_PALETTE_INDEX_TRANSPARENT)
676 candidates->index [0] = palette->transparent_index;
677 if (candidates->index [1] == CHAFA_PALETTE_INDEX_TRANSPARENT)
678 candidates->index [1] = palette->transparent_index;
679 }
680 }
681
682 return candidates->index [0];
683 }
684
685 g_assert_not_reached ();
686 }
687
688 gint
chafa_palette_lookup_with_error(const ChafaPalette * palette,ChafaColorSpace color_space,ChafaColor color,ChafaColorAccum * error_inout)689 chafa_palette_lookup_with_error (const ChafaPalette *palette, ChafaColorSpace color_space,
690 ChafaColor color, ChafaColorAccum *error_inout)
691 {
692 ChafaColorAccum compensated_color;
693 gint index;
694
695 if (error_inout)
696 {
697 compensated_color.ch [0] = ((gint16) color.ch [0]) + ((error_inout->ch [0] * 0.9) / 16);
698 compensated_color.ch [1] = ((gint16) color.ch [1]) + ((error_inout->ch [1] * 0.9) / 16);
699 compensated_color.ch [2] = ((gint16) color.ch [2]) + ((error_inout->ch [2] * 0.9) / 16);
700
701 color.ch [0] = CLAMP (compensated_color.ch [0], 0, 255);
702 color.ch [1] = CLAMP (compensated_color.ch [1], 0, 255);
703 color.ch [2] = CLAMP (compensated_color.ch [2], 0, 255);
704 }
705
706 index = chafa_palette_lookup_nearest (palette, color_space, &color, NULL);
707
708 if (error_inout)
709 {
710 if (index == palette->transparent_index)
711 {
712 memset (error_inout, 0, sizeof (*error_inout));
713 }
714 else
715 {
716 ChafaColor found_color = palette->colors [index].col [color_space];
717
718 error_inout->ch [0] = ((gint16) compensated_color.ch [0]) - ((gint16) found_color.ch [0]);
719 error_inout->ch [1] = ((gint16) compensated_color.ch [1]) - ((gint16) found_color.ch [1]);
720 error_inout->ch [2] = ((gint16) compensated_color.ch [2]) - ((gint16) found_color.ch [2]);
721 }
722 }
723
724 return index;
725 }
726
727 ChafaPaletteType
chafa_palette_get_type(const ChafaPalette * palette)728 chafa_palette_get_type (const ChafaPalette *palette)
729 {
730 return palette->type;
731 }
732
733 const ChafaColor *
chafa_palette_get_color(const ChafaPalette * palette,ChafaColorSpace color_space,gint index)734 chafa_palette_get_color (const ChafaPalette *palette, ChafaColorSpace color_space,
735 gint index)
736 {
737 return &palette->colors [index].col [color_space];
738 }
739
740 void
chafa_palette_set_color(ChafaPalette * palette,gint index,const ChafaColor * color)741 chafa_palette_set_color (ChafaPalette *palette, gint index, const ChafaColor *color)
742 {
743 palette->colors [index].col [CHAFA_COLOR_SPACE_RGB] = *color;
744 chafa_color_rgb_to_din99d (&palette->colors [index].col [CHAFA_COLOR_SPACE_RGB],
745 &palette->colors [index].col [CHAFA_COLOR_SPACE_DIN99D]);
746 }
747
748 gint
chafa_palette_get_alpha_threshold(const ChafaPalette * palette)749 chafa_palette_get_alpha_threshold (const ChafaPalette *palette)
750 {
751 return palette->alpha_threshold;
752 }
753
754 void
chafa_palette_set_alpha_threshold(ChafaPalette * palette,gint alpha_threshold)755 chafa_palette_set_alpha_threshold (ChafaPalette *palette, gint alpha_threshold)
756 {
757 palette->alpha_threshold = alpha_threshold;
758 }
759
760 gint
chafa_palette_get_transparent_index(const ChafaPalette * palette)761 chafa_palette_get_transparent_index (const ChafaPalette *palette)
762 {
763 return palette->transparent_index;
764 }
765
766 void
chafa_palette_set_transparent_index(ChafaPalette * palette,gint index)767 chafa_palette_set_transparent_index (ChafaPalette *palette, gint index)
768 {
769 palette->transparent_index = index;
770 }
771
772