1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 /* Copyright (C) 2020-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 "internal/chafa-pixops.h"
23 #include "internal/smolscale/smolscale.h"
24 
25 /* Fixed point multiplier */
26 #define FIXED_MULT 16384
27 
28 /* See rgb_to_intensity_fast () */
29 #define INTENSITY_MAX (256 * 8)
30 
31 /* Normalization: Percentage of pixels to discard at extremes of histogram */
32 #define INDEXED_16_CROP_PCT 5
33 #define INDEXED_8_CROP_PCT  10
34 #define INDEXED_2_CROP_PCT  20
35 
36 typedef struct
37 {
38     gint32 c [INTENSITY_MAX];
39 
40     /* Transparent pixels are not sampled, so we must keep count */
41     gint n_samples;
42 
43     /* Lower and upper bounds */
44     gint32 min, max;
45 }
46 Histogram;
47 
48 typedef struct
49 {
50     ChafaPixelType src_pixel_type;
51     gconstpointer src_pixels;
52     gint src_width, src_height;
53     gint src_rowstride;
54 
55     ChafaPixel *dest_pixels;
56     gint dest_width, dest_height;
57 
58     const ChafaPalette *palette;
59     const ChafaDither *dither;
60     ChafaColorSpace color_space;
61     gboolean preprocessing_enabled;
62     gint work_factor_int;
63 
64     /* Cached to avoid repeatedly calling palette functions */
65     ChafaPaletteType palette_type;
66     ChafaColor bg_color_rgb;
67 
68     /* Result of alpha detection is stored here */
69     gint have_alpha_int;
70 
71     Histogram hist;
72     gint n_batches_pixels;
73     gint n_rows_per_batch_pixels;
74 
75     SmolScaleCtx *scale_ctx;
76 }
77 PrepareContext;
78 
79 typedef struct
80 {
81     gint first_row;
82     gint n_rows;
83     Histogram hist;
84 }
85 PreparePixelsBatch1;
86 
87 typedef struct
88 {
89     gint first_row;
90     gint n_rows;
91 }
92 PreparePixelsBatch2;
93 
94 static gint
rgb_to_intensity_fast(const ChafaColor * color)95 rgb_to_intensity_fast (const ChafaColor *color)
96 {
97     /* Sum to 8x so we can divide by shifting later */
98     return color->ch [0] * 3 + color->ch [1] * 4 + color->ch [2];
99 }
100 
101 static void
sum_histograms(const Histogram * hist_in,Histogram * hist_accum)102 sum_histograms (const Histogram *hist_in, Histogram *hist_accum)
103 {
104     gint i;
105 
106     hist_accum->n_samples += hist_in->n_samples;
107 
108     for (i = 0; i < INTENSITY_MAX; i++)
109     {
110         hist_accum->c [i] += hist_in->c [i];
111     }
112 }
113 
114 static void
histogram_calc_bounds(Histogram * hist,gint crop_pct)115 histogram_calc_bounds (Histogram *hist, gint crop_pct)
116 {
117     gint64 pixels_crop;
118     gint i;
119     gint t;
120 
121     pixels_crop = (hist->n_samples * (((gint64) crop_pct * 1024) / 100)) / 1024;
122 
123     /* Find lower bound */
124 
125     for (i = 0, t = pixels_crop; i < INTENSITY_MAX; i++)
126     {
127         t -= hist->c [i];
128         if (t <= 0)
129             break;
130     }
131 
132     hist->min = i;
133 
134     /* Find upper bound */
135 
136     for (i = INTENSITY_MAX - 1, t = pixels_crop; i >= 0; i--)
137     {
138         t -= hist->c [i];
139         if (t <= 0)
140             break;
141     }
142 
143     hist->max = i;
144 }
145 
146 static gint16
normalize_ch(guint8 v,gint min,gint factor)147 normalize_ch (guint8 v, gint min, gint factor)
148 {
149     gint vt = v;
150 
151     vt -= min;
152     vt *= factor;
153     vt /= FIXED_MULT;
154 
155     vt = CLAMP (vt, 0, 255);
156     return vt;
157 }
158 
159 static void
normalize_rgb(ChafaPixel * pixels,const Histogram * hist,gint width,gint dest_y,gint n_rows)160 normalize_rgb (ChafaPixel *pixels, const Histogram *hist, gint width, gint dest_y, gint n_rows)
161 {
162     ChafaPixel *p0, *p1;
163     gint factor;
164 
165     /* Make sure range is more or less sane */
166 
167     if (hist->min == hist->max)
168         return;
169 
170 #if 0
171     if (min > 512)
172         min = 512;
173     if (max < 1536)
174         max = 1536;
175 #endif
176 
177     /* Adjust intensities */
178 
179     factor = ((INTENSITY_MAX - 1) * FIXED_MULT) / (hist->max - hist->min);
180 
181 #if 0
182     g_printerr ("[%d-%d] * %d, crop=%d     \n", min, max, factor, pixels_crop);
183 #endif
184 
185     p0 = pixels + dest_y * width;
186     p1 = p0 + n_rows * width;
187 
188     for ( ; p0 < p1; p0++)
189     {
190         p0->col.ch [0] = normalize_ch (p0->col.ch [0], hist->min / 8, factor);
191         p0->col.ch [1] = normalize_ch (p0->col.ch [1], hist->min / 8, factor);
192         p0->col.ch [2] = normalize_ch (p0->col.ch [2], hist->min / 8, factor);
193     }
194 }
195 
196 static void
boost_saturation_rgb(ChafaColor * col)197 boost_saturation_rgb (ChafaColor *col)
198 {
199     gint ch [3];
200     gfloat P = sqrtf (col->ch [0] * (gfloat) col->ch [0] * .299f
201                       + col->ch [1] * (gfloat) col->ch [1] * .587f
202                       + col->ch [2] * (gfloat) col->ch [2] * .144f);
203 
204     ch [0] = P + ((gfloat) col->ch [0] - P) * 2;
205     ch [1] = P + ((gfloat) col->ch [1] - P) * 2;
206     ch [2] = P + ((gfloat) col->ch [2] - P) * 2;
207 
208     col->ch [0] = CLAMP (ch [0], 0, 255);
209     col->ch [1] = CLAMP (ch [1], 0, 255);
210     col->ch [2] = CLAMP (ch [2], 0, 255);
211 }
212 
213 /* pixel must point to top-left pixel of the grain to be dithered */
214 static void
fs_dither_grain(const ChafaDither * dither,const ChafaPalette * palette,ChafaColorSpace color_space,ChafaPixel * pixel,gint image_width,const ChafaColorAccum * error_in,ChafaColorAccum * error_out_0,ChafaColorAccum * error_out_1,ChafaColorAccum * error_out_2,ChafaColorAccum * error_out_3)215 fs_dither_grain (const ChafaDither *dither,
216                  const ChafaPalette *palette, ChafaColorSpace color_space,
217                  ChafaPixel *pixel, gint image_width,
218                  const ChafaColorAccum *error_in,
219                  ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1,
220                  ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3)
221 {
222     gint grain_width = 1 << dither->grain_width_shift;
223     gint grain_height = 1 << dither->grain_height_shift;
224     gint grain_shift = dither->grain_width_shift + dither->grain_height_shift;
225     ChafaColorAccum next_error = { 0 };
226     ChafaColorAccum accum = { 0 };
227     ChafaColorCandidates cand = { 0 };
228     ChafaPixel *p;
229     ChafaColor acol;
230     const ChafaColor *col;
231     gint x, y, i;
232 
233     p = pixel;
234 
235     for (y = 0; y < grain_height; y++)
236     {
237         for (x = 0; x < grain_width; x++, p++)
238         {
239             for (i = 0; i < 3; i++)
240             {
241                 gint16 ch = p->col.ch [i];
242                 ch += error_in->ch [i];
243 
244                 if (ch < 0)
245                 {
246                     next_error.ch [i] += ch;
247                     ch = 0;
248                 }
249                 else if (ch > 255)
250                 {
251                     next_error.ch [i] += ch - 255;
252                     ch = 255;
253                 }
254 
255                 p->col.ch [i] = ch;
256                 accum.ch [i] += ch;
257             }
258         }
259 
260         p += image_width - grain_width;
261     }
262 
263     for (i = 0; i < 3; i++)
264     {
265         accum.ch [i] >>= grain_shift;
266         acol.ch [i] = accum.ch [i];
267     }
268 
269     /* Don't try to dither alpha */
270     acol.ch [3] = 0xff;
271 
272     chafa_palette_lookup_nearest (palette, color_space, &acol, &cand);
273     col = chafa_palette_get_color (palette, color_space, cand.index [0]);
274 
275     for (i = 0; i < 3; i++)
276     {
277         /* FIXME: Floating point op is slow. Factor this out and make
278          * dither_intensity == 1.0 the fast path. */
279         next_error.ch [i] = ((next_error.ch [i] >> grain_shift) + (accum.ch [i] - (gint16) col->ch [i]) * dither->intensity);
280 
281         error_out_0->ch [i] += next_error.ch [i] * 7 / 16;
282         error_out_1->ch [i] += next_error.ch [i] * 1 / 16;
283         error_out_2->ch [i] += next_error.ch [i] * 5 / 16;
284         error_out_3->ch [i] += next_error.ch [i] * 3 / 16;
285     }
286 }
287 
288 static void
convert_rgb_to_din99d(ChafaPixel * pixels,gint width,gint dest_y,gint n_rows)289 convert_rgb_to_din99d (ChafaPixel *pixels, gint width, gint dest_y, gint n_rows)
290 {
291     ChafaPixel *pixel = pixels + dest_y * width;
292     ChafaPixel *pixel_max = pixel + n_rows * width;
293 
294     /* RGB -> DIN99d */
295 
296     for ( ; pixel < pixel_max; pixel++)
297     {
298         chafa_color_rgb_to_din99d (&pixel->col, &pixel->col);
299     }
300 }
301 
302 static void
bayer_dither(const ChafaDither * dither,ChafaPixel * pixels,gint width,gint dest_y,gint n_rows)303 bayer_dither (const ChafaDither *dither, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows)
304 {
305     ChafaPixel *pixel = pixels + dest_y * width;
306     ChafaPixel *pixel_max = pixel + n_rows * width;
307     gint x, y;
308 
309     for (y = dest_y; pixel < pixel_max; y++)
310     {
311         for (x = 0; x < width; x++)
312         {
313             pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y);
314             pixel++;
315         }
316     }
317 }
318 
319 static void
fs_dither(const ChafaDither * dither,const ChafaPalette * palette,ChafaColorSpace color_space,ChafaPixel * pixels,gint width,gint dest_y,gint n_rows)320 fs_dither (const ChafaDither *dither, const ChafaPalette *palette,
321            ChafaColorSpace color_space,
322            ChafaPixel *pixels, gint width, gint dest_y, gint n_rows)
323 {
324     ChafaPixel *pixel;
325     ChafaColorAccum *error_rows;
326     ChafaColorAccum *error_row [2];
327     ChafaColorAccum *pp;
328     gint grain_width = 1 << dither->grain_width_shift;
329     gint grain_height = 1 << dither->grain_height_shift;
330     gint width_grains = width >> dither->grain_width_shift;
331     gint x, y;
332 
333     g_assert (width % grain_width == 0);
334     g_assert (dest_y % grain_height == 0);
335     g_assert (n_rows % grain_height == 0);
336 
337     dest_y >>= dither->grain_height_shift;
338     n_rows >>= dither->grain_height_shift;
339 
340     error_rows = alloca (width_grains * 2 * sizeof (ChafaColorAccum));
341     error_row [0] = error_rows;
342     error_row [1] = error_rows + width_grains;
343 
344     memset (error_row [0], 0, width_grains * sizeof (ChafaColorAccum));
345 
346     for (y = dest_y; y < dest_y + n_rows; y++)
347     {
348         memset (error_row [1], 0, width_grains * sizeof (ChafaColorAccum));
349 
350         if (!(y & 1))
351         {
352             /* Forwards pass */
353             pixel = pixels + (y << dither->grain_height_shift) * width;
354 
355             fs_dither_grain (dither, palette, color_space, pixel, width,
356                              error_row [0],
357                              error_row [0] + 1,
358                              error_row [1] + 1,
359                              error_row [1],
360                              error_row [1] + 1);
361             pixel += grain_width;
362 
363             for (x = 1; ((x + 1) << dither->grain_width_shift) < width; x++)
364             {
365                 fs_dither_grain (dither, palette, color_space, pixel, width,
366                                  error_row [0] + x,
367                                  error_row [0] + x + 1,
368                                  error_row [1] + x + 1,
369                                  error_row [1] + x,
370                                  error_row [1] + x - 1);
371                 pixel += grain_width;
372             }
373 
374             fs_dither_grain (dither, palette, color_space, pixel, width,
375                              error_row [0] + x,
376                              error_row [1] + x,
377                              error_row [1] + x,
378                              error_row [1] + x - 1,
379                              error_row [1] + x - 1);
380         }
381         else
382         {
383             /* Backwards pass */
384             pixel = pixels + (y << dither->grain_height_shift) * (width + 1) - grain_width;
385 
386             fs_dither_grain (dither, palette, color_space, pixel, width,
387                              error_row [0] + width_grains - 1,
388                              error_row [0] + width_grains - 2,
389                              error_row [1] + width_grains - 2,
390                              error_row [1] + width_grains - 1,
391                              error_row [1] + width_grains - 2);
392 
393             pixel -= grain_width;
394 
395             for (x = width_grains - 2; x > 0; x--)
396             {
397                 fs_dither_grain (dither, palette, color_space, pixel, width,
398                                  error_row [0] + x,
399                                  error_row [0] + x - 1,
400                                  error_row [1] + x - 1,
401                                  error_row [1] + x,
402                                  error_row [1] + x + 1);
403                 pixel -= grain_width;
404             }
405 
406             fs_dither_grain (dither, palette, color_space, pixel, width,
407                              error_row [0],
408                              error_row [1],
409                              error_row [1],
410                              error_row [1] + 1,
411                              error_row [1] + 1);
412         }
413 
414         pp = error_row [0];
415         error_row [0] = error_row [1];
416         error_row [1] = pp;
417     }
418 }
419 
420 static void
bayer_and_convert_rgb_to_din99d(const ChafaDither * dither,ChafaPixel * pixels,gint width,gint dest_y,gint n_rows)421 bayer_and_convert_rgb_to_din99d (const ChafaDither *dither,
422                                  ChafaPixel *pixels, gint width, gint dest_y, gint n_rows)
423 {
424     ChafaPixel *pixel = pixels + dest_y * width;
425     ChafaPixel *pixel_max = pixel + n_rows * width;
426     gint x, y;
427 
428     for (y = dest_y; pixel < pixel_max; y++)
429     {
430         for (x = 0; x < width; x++)
431         {
432             pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y);
433             chafa_color_rgb_to_din99d (&pixel->col, &pixel->col);
434             pixel++;
435         }
436     }
437 }
438 
439 static void
fs_and_convert_rgb_to_din99d(const ChafaDither * dither,const ChafaPalette * palette,ChafaPixel * pixels,gint width,gint dest_y,gint n_rows)440 fs_and_convert_rgb_to_din99d (const ChafaDither *dither, const ChafaPalette *palette,
441                               ChafaPixel *pixels, gint width, gint dest_y, gint n_rows)
442 {
443     convert_rgb_to_din99d (pixels, width, dest_y, n_rows);
444     fs_dither (dither, palette, CHAFA_COLOR_SPACE_DIN99D, pixels, width, dest_y, n_rows);
445 }
446 
447 static void
prepare_pixels_1_inner(PreparePixelsBatch1 * work,PrepareContext * prep_ctx,const guint8 * data_p,ChafaPixel * pixel_out,gint * alpha_sum)448 prepare_pixels_1_inner (PreparePixelsBatch1 *work,
449                         PrepareContext *prep_ctx,
450                         const guint8 *data_p,
451                         ChafaPixel *pixel_out,
452                         gint *alpha_sum)
453 {
454     ChafaColor *col = &pixel_out->col;
455 
456     col->ch [0] = data_p [0];
457     col->ch [1] = data_p [1];
458     col->ch [2] = data_p [2];
459     col->ch [3] = data_p [3];
460 
461     *alpha_sum += (0xff - col->ch [3]);
462 
463     if (prep_ctx->preprocessing_enabled
464         && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16
465             || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8)
466         )
467     {
468         boost_saturation_rgb (col);
469     }
470 
471     /* Build histogram */
472     if (col->ch [3] > 127)
473     {
474         gint v = rgb_to_intensity_fast (col);
475         work->hist.c [v]++;
476         work->hist.n_samples++;
477     }
478 }
479 
480 static void
prepare_pixels_1_worker_nearest(PreparePixelsBatch1 * work,PrepareContext * prep_ctx)481 prepare_pixels_1_worker_nearest (PreparePixelsBatch1 *work, PrepareContext *prep_ctx)
482 {
483     ChafaPixel *pixel;
484     gint dest_y;
485     gint px, py;
486     gint x_inc, y_inc;
487     gint alpha_sum = 0;
488     const guint8 *data;
489     gint n_rows;
490     gint rowstride;
491 
492     dest_y = work->first_row;
493     data = prep_ctx->src_pixels;
494     n_rows = work->n_rows;
495     rowstride = prep_ctx->src_rowstride;
496 
497     x_inc = (prep_ctx->src_width * FIXED_MULT) / (prep_ctx->dest_width);
498     y_inc = (prep_ctx->src_height * FIXED_MULT) / (prep_ctx->dest_height);
499 
500     pixel = prep_ctx->dest_pixels + dest_y * prep_ctx->dest_width;
501 
502     for (py = dest_y; py < dest_y + n_rows; py++)
503     {
504         const guint8 *data_row_p;
505 
506         data_row_p = data + ((py * y_inc) / FIXED_MULT) * rowstride;
507 
508         for (px = 0; px < prep_ctx->dest_width; px++)
509         {
510             const guint8 *data_p = data_row_p + ((px * x_inc) / FIXED_MULT) * 4;
511             prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum);
512         }
513     }
514 
515     if (alpha_sum > 0)
516         g_atomic_int_set (&prep_ctx->have_alpha_int, 1);
517 }
518 
519 static void
prepare_pixels_1_worker_smooth(PreparePixelsBatch1 * work,PrepareContext * prep_ctx)520 prepare_pixels_1_worker_smooth (PreparePixelsBatch1 *work, PrepareContext *prep_ctx)
521 {
522     ChafaPixel *pixel, *pixel_max;
523     gint alpha_sum = 0;
524     guint8 *scaled_data;
525     const guint8 *data_p;
526 
527     scaled_data = g_malloc (prep_ctx->dest_width * work->n_rows * sizeof (guint32));
528     smol_scale_batch_full (prep_ctx->scale_ctx, scaled_data, work->first_row, work->n_rows);
529 
530     data_p = scaled_data;
531     pixel = prep_ctx->dest_pixels + work->first_row * prep_ctx->dest_width;
532     pixel_max = pixel + work->n_rows * prep_ctx->dest_width;
533 
534     while (pixel < pixel_max)
535     {
536         prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum);
537         data_p += 4;
538     }
539 
540     g_free (scaled_data);
541 
542     if (alpha_sum > 0)
543         g_atomic_int_set (&prep_ctx->have_alpha_int, 1);
544 }
545 
546 static void
prepare_pixels_pass_1(PrepareContext * prep_ctx)547 prepare_pixels_pass_1 (PrepareContext *prep_ctx)
548 {
549     GThreadPool *thread_pool;
550     PreparePixelsBatch1 *batches;
551     gint cy;
552     gint i;
553 
554     /* First pass
555      * ----------
556      *
557      * - Scale and convert pixel format
558      * - Apply local preprocessing like saturation boost (optional)
559      * - Generate histogram for later passes (e.g. for normalization)
560      * - Figure out if we have alpha transparency
561      */
562 
563     batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels);
564 
565     thread_pool = g_thread_pool_new ((GFunc) ((prep_ctx->work_factor_int < 3
566                                                && prep_ctx->src_pixel_type == CHAFA_PIXEL_RGBA8_UNASSOCIATED)
567                                               ? prepare_pixels_1_worker_nearest
568                                               : prepare_pixels_1_worker_smooth),
569                                      prep_ctx,
570                                      g_get_num_processors (),
571                                      FALSE,
572                                      NULL);
573 
574     for (cy = 0, i = 0;
575          cy < prep_ctx->dest_height;
576          cy += prep_ctx->n_rows_per_batch_pixels, i++)
577     {
578         PreparePixelsBatch1 *batch = &batches [i];
579 
580         batch->first_row = cy;
581         batch->n_rows = MIN (prep_ctx->dest_height - cy, prep_ctx->n_rows_per_batch_pixels);
582 
583         g_thread_pool_push (thread_pool, batch, NULL);
584     }
585 
586     /* Wait for threads to finish */
587     g_thread_pool_free (thread_pool, FALSE, TRUE);
588 
589     /* Generate final histogram */
590     if (prep_ctx->preprocessing_enabled)
591     {
592         for (i = 0; i < prep_ctx->n_batches_pixels; i++)
593             sum_histograms (&batches [i].hist, &prep_ctx->hist);
594 
595         switch (prep_ctx->palette_type)
596         {
597           case CHAFA_PALETTE_TYPE_FIXED_16:
598             histogram_calc_bounds (&prep_ctx->hist, INDEXED_16_CROP_PCT);
599             break;
600           case CHAFA_PALETTE_TYPE_FIXED_8:
601             histogram_calc_bounds (&prep_ctx->hist, INDEXED_8_CROP_PCT);
602             break;
603           default:
604             histogram_calc_bounds (&prep_ctx->hist, INDEXED_2_CROP_PCT);
605             break;
606         }
607     }
608 
609     g_free (batches);
610 }
611 
612 static void
composite_alpha_on_bg(ChafaColor bg_color,ChafaPixel * pixels,gint width,gint first_row,gint n_rows)613 composite_alpha_on_bg (ChafaColor bg_color,
614                        ChafaPixel *pixels, gint width, gint first_row, gint n_rows)
615 {
616     ChafaPixel *p0, *p1;
617 
618     p0 = pixels + first_row * width;
619     p1 = p0 + n_rows * width;
620 
621     for ( ; p0 < p1; p0++)
622     {
623         p0->col.ch [0] += (bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255;
624         p0->col.ch [1] += (bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255;
625         p0->col.ch [2] += (bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255;
626     }
627 }
628 
629 static void
prepare_pixels_2_worker(PreparePixelsBatch2 * work,PrepareContext * prep_ctx)630 prepare_pixels_2_worker (PreparePixelsBatch2 *work, PrepareContext *prep_ctx)
631 {
632     if (prep_ctx->preprocessing_enabled
633         && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16
634             ||prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8
635             || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG))
636         normalize_rgb (prep_ctx->dest_pixels, &prep_ctx->hist, prep_ctx->dest_width,
637                        work->first_row, work->n_rows);
638 
639     if (prep_ctx->have_alpha_int)
640         composite_alpha_on_bg (prep_ctx->bg_color_rgb,
641                                prep_ctx->dest_pixels, prep_ctx->dest_width,
642                                work->first_row, work->n_rows);
643 
644     if (prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D)
645     {
646         if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED)
647         {
648             bayer_and_convert_rgb_to_din99d (prep_ctx->dither,
649                                              prep_ctx->dest_pixels,
650                                              prep_ctx->dest_width,
651                                              work->first_row,
652                                              work->n_rows);
653         }
654         else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION)
655         {
656             fs_and_convert_rgb_to_din99d (prep_ctx->dither,
657                                           prep_ctx->palette,
658                                           prep_ctx->dest_pixels,
659                                           prep_ctx->dest_width,
660                                           work->first_row,
661                                           work->n_rows);
662         }
663         else
664         {
665             convert_rgb_to_din99d (prep_ctx->dest_pixels,
666                                    prep_ctx->dest_width,
667                                    work->first_row,
668                                    work->n_rows);
669         }
670     }
671     else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED)
672     {
673         bayer_dither (prep_ctx->dither,
674                       prep_ctx->dest_pixels,
675                       prep_ctx->dest_width,
676                       work->first_row,
677                       work->n_rows);
678     }
679     else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION)
680     {
681         fs_dither (prep_ctx->dither,
682                    prep_ctx->palette,
683                    prep_ctx->color_space,
684                    prep_ctx->dest_pixels,
685                    prep_ctx->dest_width,
686                    work->first_row,
687                    work->n_rows);
688     }
689 }
690 
691 static gboolean
need_pass_2(PrepareContext * prep_ctx)692 need_pass_2 (PrepareContext *prep_ctx)
693 {
694     if ((prep_ctx->preprocessing_enabled
695          && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16
696              || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8
697              || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG))
698         || prep_ctx->have_alpha_int
699         || prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D
700         || prep_ctx->dither->mode != CHAFA_DITHER_MODE_NONE)
701         return TRUE;
702 
703     return FALSE;
704 }
705 
706 static void
prepare_pixels_pass_2(PrepareContext * prep_ctx)707 prepare_pixels_pass_2 (PrepareContext *prep_ctx)
708 {
709     GThreadPool *thread_pool;
710     PreparePixelsBatch1 *batches;
711     gint cy;
712     gint i;
713 
714     /* Second pass
715      * -----------
716      *
717      * - Normalization (optional)
718      * - Dithering (optional)
719      * - Color space conversion; DIN99d (optional)
720      */
721 
722     if (!need_pass_2 (prep_ctx))
723         return;
724 
725     batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels);
726 
727     thread_pool = g_thread_pool_new ((GFunc) prepare_pixels_2_worker,
728                                      prep_ctx,
729                                      g_get_num_processors (),
730                                      FALSE,
731                                      NULL);
732 
733     for (cy = 0, i = 0;
734          cy < prep_ctx->dest_height;
735          cy += prep_ctx->n_rows_per_batch_pixels, i++)
736     {
737         PreparePixelsBatch1 *batch = &batches [i];
738 
739         batch->first_row = cy;
740         batch->n_rows = MIN (prep_ctx->dest_height - cy, prep_ctx->n_rows_per_batch_pixels);
741 
742         g_thread_pool_push (thread_pool, batch, NULL);
743     }
744 
745     /* Wait for threads to finish */
746     g_thread_pool_free (thread_pool, FALSE, TRUE);
747 
748     g_free (batches);
749 }
750 
751 void
chafa_prepare_pixel_data_for_symbols(const ChafaPalette * palette,const ChafaDither * dither,ChafaColorSpace color_space,gboolean preprocessing_enabled,gint work_factor,ChafaPixelType src_pixel_type,gconstpointer src_pixels,gint src_width,gint src_height,gint src_rowstride,ChafaPixel * dest_pixels,gint dest_width,gint dest_height)752 chafa_prepare_pixel_data_for_symbols (const ChafaPalette *palette,
753                                       const ChafaDither *dither,
754                                       ChafaColorSpace color_space,
755                                       gboolean preprocessing_enabled,
756                                       gint work_factor,
757                                       ChafaPixelType src_pixel_type,
758                                       gconstpointer src_pixels,
759                                       gint src_width,
760                                       gint src_height,
761                                       gint src_rowstride,
762                                       ChafaPixel *dest_pixels,
763                                       gint dest_width,
764                                       gint dest_height)
765 {
766     PrepareContext prep_ctx = { 0 };
767     guint n_cpus;
768 
769     n_cpus = g_get_num_processors ();
770 
771     prep_ctx.palette = palette;
772     prep_ctx.dither = dither;
773     prep_ctx.color_space = color_space;
774     prep_ctx.preprocessing_enabled = preprocessing_enabled;
775     prep_ctx.work_factor_int = work_factor;
776 
777     prep_ctx.palette_type = chafa_palette_get_type (palette);
778     prep_ctx.bg_color_rgb = *chafa_palette_get_color (palette,
779                                                       CHAFA_COLOR_SPACE_RGB,
780                                                       CHAFA_PALETTE_INDEX_BG);
781 
782     prep_ctx.src_pixel_type = src_pixel_type;
783     prep_ctx.src_pixels = src_pixels;
784     prep_ctx.src_width = src_width;
785     prep_ctx.src_height = src_height;
786     prep_ctx.src_rowstride = src_rowstride;
787 
788     prep_ctx.dest_pixels = dest_pixels;
789     prep_ctx.dest_width = dest_width;
790     prep_ctx.dest_height = dest_height;
791 
792     prep_ctx.n_batches_pixels = (prep_ctx.dest_height + n_cpus - 1) / n_cpus;
793     prep_ctx.n_rows_per_batch_pixels = (prep_ctx.dest_height + prep_ctx.n_batches_pixels - 1) / prep_ctx.n_batches_pixels;
794 
795     prep_ctx.scale_ctx = smol_scale_new ((SmolPixelType) prep_ctx.src_pixel_type,
796                                          (const guint32 *) prep_ctx.src_pixels,
797                                          prep_ctx.src_width,
798                                          prep_ctx.src_height,
799                                          prep_ctx.src_rowstride,
800                                          SMOL_PIXEL_RGBA8_PREMULTIPLIED,
801                                          NULL,
802                                          prep_ctx.dest_width,
803                                          prep_ctx.dest_height,
804                                          prep_ctx.dest_width * sizeof (guint32));
805 
806     prepare_pixels_pass_1 (&prep_ctx);
807     prepare_pixels_pass_2 (&prep_ctx);
808 
809     smol_scale_destroy (prep_ctx.scale_ctx);
810 }
811 
812 void
chafa_sort_pixel_index_by_channel(guint8 * index,const ChafaPixel * pixels,gint n_pixels,gint ch)813 chafa_sort_pixel_index_by_channel (guint8 *index, const ChafaPixel *pixels, gint n_pixels, gint ch)
814 {
815     const gint gaps [] = { 57, 23, 10, 4, 1 };
816     gint g, i, j;
817 
818     /* Since we don't care about stability and the number of elements
819      * is small and known in advance, use a simple in-place shellsort.
820      *
821      * Due to locality and callback overhead this is probably faster
822      * than qsort(), although admittedly I haven't benchmarked it.
823      *
824      * Another option is to use radix, but since we support multiple
825      * color spaces with fixed-point reals, we could get more buckets
826      * than is practical. */
827 
828     for (g = 0; ; g++)
829     {
830         gint gap = gaps [g];
831 
832         for (i = gap; i < n_pixels; i++)
833         {
834             guint8 ptemp = index [i];
835 
836             for (j = i; j >= gap && pixels [index [j - gap]].col.ch [ch]
837                                   > pixels [ptemp].col.ch [ch]; j -= gap)
838             {
839                 index [j] = index [j - gap];
840             }
841 
842             index [j] = ptemp;
843         }
844 
845         /* After gap == 1 the array is always left sorted */
846         if (gap == 1)
847             break;
848     }
849 }
850