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