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