Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-or-later
3 : *
4 : * This file is part of colopresso
5 : *
6 : * Copyright (C) 2025-2026 COLOPL, Inc.
7 : *
8 : * Author: Go Kudo <g-kudo@colopl.co.jp>
9 : * Developed with AI (LLM) code assistance. See `NOTICE` for details.
10 : */
11 :
12 : #include <limits.h>
13 : #include <math.h>
14 : #include <stdio.h>
15 : #include <stdlib.h>
16 : #include <string.h>
17 :
18 : #include <png.h>
19 :
20 : #include "internal/log.h"
21 : #include "internal/pngx_common.h"
22 : #include "internal/simd.h"
23 : #include "internal/threads.h"
24 :
25 : typedef struct {
26 : uint32_t color;
27 : uint32_t count;
28 : uint32_t mapped_color;
29 : uint8_t detail_bits_rgb;
30 : uint8_t detail_bits_alpha;
31 : bool locked;
32 : } color_entry_t;
33 :
34 : typedef struct {
35 : color_entry_t *entries;
36 : size_t count;
37 : size_t unlocked_count;
38 : } color_histogram_t;
39 :
40 : typedef struct {
41 : uint32_t color;
42 : uint16_t weight;
43 : uint8_t rgb_bits;
44 : uint8_t alpha_bits;
45 : } histogram_sample_t;
46 :
47 : typedef struct {
48 : size_t start;
49 : size_t end;
50 : uint8_t min_r;
51 : uint8_t min_g;
52 : uint8_t min_b;
53 : uint8_t min_a;
54 : uint8_t max_r;
55 : uint8_t max_g;
56 : uint8_t max_b;
57 : uint8_t max_a;
58 : uint64_t total_weight;
59 : } color_box_t;
60 :
61 : typedef struct {
62 : uint32_t color;
63 : uint32_t mapped_color;
64 : } color_map_entry_t;
65 :
66 : typedef struct {
67 : uint32_t color;
68 : uint32_t count;
69 : } color_freq_t;
70 :
71 : typedef struct {
72 : size_t index;
73 : uint32_t count;
74 : uint32_t color;
75 : } freq_rank_t;
76 :
77 : typedef struct {
78 : uint8_t *rgba;
79 : size_t pixel_count;
80 : const uint8_t *importance_map;
81 : size_t importance_map_len;
82 : uint8_t bits_rgb;
83 : uint8_t bits_alpha;
84 : uint8_t boost_bits_rgb;
85 : uint8_t boost_bits_alpha;
86 : uint8_t *bit_hint_map;
87 : size_t bit_hint_len;
88 : } reduce_bitdepth_parallel_ctx_t;
89 :
90 : typedef struct {
91 : uint8_t *rgba;
92 : size_t pixel_count;
93 : const color_map_entry_t *map;
94 : size_t map_count;
95 : } color_remap_parallel_ctx_t;
96 :
97 : typedef struct {
98 : const uint8_t *rgba;
99 : uint32_t *packed;
100 : size_t pixel_count;
101 : } pack_rgba_parallel_ctx_t;
102 :
103 18 : static inline uint32_t compute_grid_capacity(uint8_t bits_rgb, uint8_t bits_alpha)
104 : {
105 : uint64_t capacity;
106 18 : uint32_t rgb_levels = 1, alpha_levels = 1;
107 :
108 18 : bits_rgb = clamp_reduced_bits(bits_rgb);
109 18 : bits_alpha = clamp_reduced_bits(bits_alpha);
110 :
111 18 : if (bits_rgb < 8) {
112 14 : rgb_levels = (uint32_t)1 << bits_rgb;
113 14 : rgb_levels = rgb_levels * rgb_levels * rgb_levels;
114 : } else {
115 4 : rgb_levels = UINT32_MAX;
116 : }
117 :
118 18 : if (bits_alpha < 8) {
119 18 : alpha_levels = (uint32_t)1 << bits_alpha;
120 : } else {
121 0 : alpha_levels = UINT32_MAX;
122 : }
123 :
124 18 : if (rgb_levels == UINT32_MAX || alpha_levels == UINT32_MAX) {
125 4 : return (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
126 : }
127 :
128 14 : capacity = (uint64_t)rgb_levels * (uint64_t)alpha_levels;
129 14 : if (capacity > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
130 8 : capacity = (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
131 : }
132 :
133 14 : return (uint32_t)capacity;
134 : }
135 :
136 9238467 : static inline uint32_t pack_rgba_u32(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
137 : {
138 9238467 : return ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
139 : }
140 :
141 : static inline uint8_t color_component(uint32_t color, uint8_t channel)
142 : {
143 : return (uint8_t)((color >> (channel * 8)) & 0xff);
144 : }
145 :
146 1037047 : static inline void unpack_rgba_u32(uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
147 : {
148 1037047 : if (r) {
149 1037047 : *r = (uint8_t)(color & 0xff);
150 : }
151 :
152 1037047 : if (g) {
153 1037047 : *g = (uint8_t)((color >> 8) & 0xff);
154 : }
155 :
156 1037047 : if (b) {
157 1037047 : *b = (uint8_t)((color >> 16) & 0xff);
158 : }
159 :
160 1037047 : if (a) {
161 1037047 : *a = (uint8_t)((color >> 24) & 0xff);
162 : }
163 1037047 : }
164 :
165 2424832 : static inline float importance_dither_scale(uint8_t importance_value)
166 : {
167 : float normalized, scale;
168 :
169 2424832 : normalized = (float)importance_value / PNGX_REDUCED_IMPORTANCE_SCALE_DENOM;
170 2424832 : scale = PNGX_REDUCED_IMPORTANCE_SCALE_MIN +
171 2424832 : (1.0f - normalized) * PNGX_REDUCED_IMPORTANCE_SCALE_RANGE;
172 :
173 2424832 : return scale < 0.0f ? 0.0f : (scale > 1.0f ? 1.0f : scale);
174 : }
175 :
176 8 : static inline void color_histogram_reset(color_histogram_t *hist)
177 : {
178 8 : if (!hist) {
179 0 : return;
180 : }
181 :
182 8 : free(hist->entries);
183 8 : hist->entries = NULL;
184 8 : hist->count = 0;
185 8 : hist->unlocked_count = 0;
186 : }
187 :
188 16937 : static inline bool is_protected_color(uint32_t color, const uint32_t *protected_colors,
189 : size_t protected_count)
190 : {
191 : size_t i;
192 :
193 16937 : if (!protected_colors || protected_count == 0) {
194 16933 : return false;
195 : }
196 :
197 10 : for (i = 0; i < protected_count; ++i) {
198 10 : if (protected_colors[i] == color) {
199 4 : return true;
200 : }
201 : }
202 :
203 0 : return false;
204 : }
205 :
206 2491400 : static inline uint16_t histogram_importance_weight(const pngx_quant_support_t *support,
207 : size_t pixel_index)
208 : {
209 : const uint8_t *map;
210 : uint16_t weight;
211 : uint8_t importance;
212 :
213 2491400 : if (!support || !support->importance_map || pixel_index >= support->importance_map_len) {
214 66568 : return 1;
215 : }
216 :
217 2424832 : map = support->importance_map;
218 2424832 : importance = map[pixel_index];
219 :
220 2424832 : weight = 1 + (uint16_t)(importance >> PNGX_REDUCED_IMPORTANCE_WEIGHT_SHIFT);
221 :
222 2424832 : if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_STRONG) {
223 23900 : weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_STRONG;
224 2400932 : } else if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_HIGH) {
225 545339 : weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_HIGH;
226 1855593 : } else if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_MEDIUM) {
227 1190152 : weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_MEDIUM;
228 : }
229 :
230 2424832 : if (weight > PNGX_REDUCED_IMPORTANCE_WEIGHT_CAP) {
231 0 : weight = PNGX_REDUCED_IMPORTANCE_WEIGHT_CAP;
232 : }
233 :
234 2424832 : return weight;
235 : }
236 :
237 39340728 : static inline int compare_histogram_sample(const void *lhs, const void *rhs)
238 : {
239 39340728 : const histogram_sample_t *a = (const histogram_sample_t *)lhs,
240 39340728 : *b = (const histogram_sample_t *)rhs;
241 :
242 39340728 : return a->color < b->color ? -1 : (a->color > b->color ? 1 : 0);
243 : }
244 :
245 9123 : static inline void color_box_refresh(const color_entry_t *entries, color_box_t *box)
246 : {
247 : uint32_t c;
248 : uint8_t r, g, b, a;
249 : size_t i;
250 :
251 9123 : if (!entries || !box || box->end <= box->start) {
252 0 : return;
253 : }
254 :
255 9123 : box->min_r = 255;
256 9123 : box->min_g = 255;
257 9123 : box->min_b = 255;
258 9123 : box->min_a = 255;
259 9123 : box->max_r = 0;
260 9123 : box->max_g = 0;
261 9123 : box->max_b = 0;
262 9123 : box->max_a = 0;
263 9123 : box->total_weight = 0;
264 167036 : for (i = box->start; i < box->end; ++i) {
265 157913 : c = entries[i].color;
266 157913 : r = (uint8_t)(c & 0xff);
267 157913 : g = (uint8_t)((c >> 8) & 0xff);
268 157913 : b = (uint8_t)((c >> 16) & 0xff);
269 157913 : a = (uint8_t)((c >> 24) & 0xff);
270 :
271 157913 : if (r < box->min_r) {
272 15732 : box->min_r = r;
273 : }
274 :
275 157913 : if (r > box->max_r) {
276 16254 : box->max_r = r;
277 : }
278 :
279 157913 : if (g < box->min_g) {
280 10480 : box->min_g = g;
281 : }
282 :
283 157913 : if (g > box->max_g) {
284 14455 : box->max_g = g;
285 : }
286 :
287 157913 : if (b < box->min_b) {
288 10909 : box->min_b = b;
289 : }
290 :
291 157913 : if (b > box->max_b) {
292 18039 : box->max_b = b;
293 : }
294 :
295 157913 : if (a < box->min_a) {
296 8445 : box->min_a = a;
297 : }
298 :
299 157913 : if (a > box->max_a) {
300 12590 : box->max_a = a;
301 : }
302 :
303 157913 : box->total_weight += (uint64_t)entries[i].count;
304 : }
305 : }
306 :
307 55863124 : static inline int compare_u32(const void *lhs, const void *rhs)
308 : {
309 55863124 : uint32_t a = *(const uint32_t *)lhs, b = *(const uint32_t *)rhs;
310 :
311 55863124 : if (a < b) {
312 14669971 : return -1;
313 : }
314 :
315 41193153 : if (a > b) {
316 24983192 : return 1;
317 : }
318 :
319 16209961 : return 0;
320 : }
321 :
322 3202653 : static inline bool color_box_splittable(const color_box_t *box)
323 : {
324 3202653 : return box && box->end > box->start + 1;
325 : }
326 :
327 952896 : static inline uint8_t color_box_max_span(const color_box_t *box)
328 : {
329 952896 : uint8_t span_r = box->max_r - box->min_r, span_g = box->max_g - box->min_g,
330 952896 : span_b = box->max_b - box->min_b, span_a = box->max_a - box->min_a, span = span_r;
331 :
332 952896 : return span_g > span ? span_g : (span_b > span ? span_b : (span_a > span ? span_a : span));
333 : }
334 :
335 952896 : static inline float color_box_detail_bias(const color_entry_t *entries, const color_box_t *box,
336 : uint8_t base_bits_rgb, uint8_t base_bits_alpha)
337 : {
338 : const color_entry_t *entry;
339 952896 : uint64_t weight = 0, w, accum_x2 = 0;
340 : uint32_t delta_rgb, delta_alpha, score_x2;
341 : size_t idx;
342 :
343 952896 : if (!entries || !box || box->end <= box->start) {
344 0 : return 0.0f;
345 : }
346 :
347 11931878 : for (idx = box->start; idx < box->end; ++idx) {
348 10978982 : entry = &entries[idx];
349 10978982 : w = entry->count ? entry->count : 1;
350 5900517 : delta_rgb = (entry->detail_bits_rgb > base_bits_rgb)
351 5078465 : ? (uint32_t)(entry->detail_bits_rgb - base_bits_rgb)
352 10978982 : : 0;
353 5900517 : delta_alpha = (entry->detail_bits_alpha > base_bits_alpha)
354 5078465 : ? (uint32_t)(entry->detail_bits_alpha - base_bits_alpha)
355 10978982 : : 0;
356 10978982 : score_x2 = (delta_rgb << 1) + delta_alpha;
357 :
358 10978982 : if (score_x2 > 0) {
359 5078465 : accum_x2 += (uint64_t)score_x2 * w;
360 : }
361 :
362 10978982 : weight += w;
363 : }
364 :
365 952896 : if (weight == 0) {
366 0 : return 0.0f;
367 : }
368 :
369 952896 : return (float)accum_x2 / (float)(weight * 2);
370 : }
371 :
372 4558 : static inline int select_box_to_split(const color_box_t *boxes, size_t box_count,
373 : const color_entry_t *entries, uint8_t base_bits_rgb,
374 : uint8_t base_bits_alpha)
375 : {
376 4558 : int best_index = -1;
377 : size_t i;
378 4558 : float best_priority = -1.0f, detail_bias, span_score, weight_score, priority;
379 :
380 4558 : if (!boxes || box_count == 0) {
381 0 : return -1;
382 : }
383 :
384 3202653 : for (i = 0; i < box_count; ++i) {
385 3198095 : if (!color_box_splittable(&boxes[i])) {
386 2245199 : continue;
387 : }
388 :
389 952896 : detail_bias = color_box_detail_bias(entries, &boxes[i], base_bits_rgb, base_bits_alpha);
390 952896 : span_score = (float)color_box_max_span(&boxes[i]) / 255.0f;
391 0 : weight_score =
392 952896 : (boxes[i].total_weight > 0) ? logf((float)boxes[i].total_weight + 1.0f) : 0.0f;
393 952896 : priority = (span_score * PNGX_REDUCED_PRIORITY_SPAN_WEIGHT) +
394 952896 : (detail_bias * PNGX_REDUCED_PRIORITY_DETAIL_WEIGHT) +
395 952896 : (weight_score * PNGX_REDUCED_PRIORITY_MASS_WEIGHT);
396 :
397 952896 : if (priority > best_priority) {
398 25538 : best_priority = priority;
399 25538 : best_index = (int)i;
400 : }
401 : }
402 :
403 4558 : return best_index;
404 : }
405 :
406 4558 : static inline size_t box_find_split_index(const color_entry_t *entries, const color_box_t *box)
407 : {
408 4558 : uint64_t half, accum = 0;
409 : size_t idx;
410 :
411 4558 : if (!entries || !box || box->end <= box->start) {
412 0 : return box ? box->start : 0;
413 : }
414 :
415 4558 : half = box->total_weight / 2;
416 4558 : if (half == 0) {
417 0 : half = 1;
418 : }
419 :
420 67505 : for (idx = box->start; idx < box->end; ++idx) {
421 67505 : accum += (uint64_t)entries[idx].count;
422 67505 : if (accum >= half) {
423 4558 : return idx;
424 : }
425 : }
426 :
427 0 : return box->start + ((box->end - box->start) / 2);
428 : }
429 :
430 290060 : static inline int compare_entries_r(const void *lhs, const void *rhs)
431 : {
432 290060 : const color_entry_t *a = (const color_entry_t *)lhs, *b = (const color_entry_t *)rhs;
433 290060 : uint8_t av = (uint8_t)(a->color & 0xff), bv = (uint8_t)(b->color & 0xff);
434 :
435 290060 : return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
436 : }
437 :
438 213350 : static inline int compare_entries_g(const void *lhs, const void *rhs)
439 : {
440 213350 : const color_entry_t *a = (const color_entry_t *)lhs, *b = (const color_entry_t *)rhs;
441 213350 : uint8_t av = (uint8_t)((a->color >> 8) & 0xff), bv = (uint8_t)((b->color >> 8) & 0xff);
442 :
443 213350 : return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
444 : }
445 :
446 103592 : static inline int compare_entries_b(const void *lhs, const void *rhs)
447 : {
448 103592 : const color_entry_t *a = (const color_entry_t *)lhs, *b = (const color_entry_t *)rhs;
449 103592 : uint8_t av = (uint8_t)((a->color >> 16) & 0xff), bv = (uint8_t)((b->color >> 16) & 0xff);
450 :
451 103592 : return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
452 : }
453 :
454 144554 : static inline int compare_entries_a(const void *lhs, const void *rhs)
455 : {
456 144554 : const color_entry_t *a = (const color_entry_t *)lhs, *b = (const color_entry_t *)rhs;
457 144554 : uint8_t av = (uint8_t)((a->color >> 24) & 0xff), bv = (uint8_t)((b->color >> 24) & 0xff);
458 :
459 144554 : return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
460 : }
461 :
462 4558 : static inline void sort_entries_by_axis(color_entry_t *entries, size_t count, int axis)
463 : {
464 4558 : if (!entries || count == 0) {
465 0 : return;
466 : }
467 :
468 4558 : switch (axis) {
469 1294 : case 0:
470 1294 : qsort(entries, count, sizeof(color_entry_t), compare_entries_r);
471 :
472 1294 : break;
473 1213 : case 1:
474 1213 : qsort(entries, count, sizeof(color_entry_t), compare_entries_g);
475 :
476 1213 : break;
477 1220 : case 2:
478 1220 : qsort(entries, count, sizeof(color_entry_t), compare_entries_b);
479 :
480 1220 : break;
481 831 : case 3:
482 831 : qsort(entries, count, sizeof(color_entry_t), compare_entries_a);
483 :
484 831 : break;
485 0 : default:
486 0 : qsort(entries, count, sizeof(color_entry_t), compare_entries_r);
487 :
488 0 : break;
489 : }
490 : }
491 :
492 4558 : static inline bool split_color_box(color_entry_t *entries, color_box_t *box, color_box_t *new_box)
493 : {
494 : int axis;
495 : size_t split_index;
496 :
497 4558 : if (!entries || !box || !new_box || !color_box_splittable(box)) {
498 0 : return false;
499 : }
500 :
501 4558 : axis = 0;
502 :
503 4558 : if ((box->max_g - box->min_g) > (box->max_r - box->min_r)) {
504 1500 : axis = 1;
505 : }
506 :
507 4558 : if ((box->max_b - box->min_b) > (box->max_g - box->min_g) &&
508 1424 : (box->max_b - box->min_b) >= (box->max_r - box->min_r)) {
509 1278 : axis = 2;
510 : }
511 :
512 4558 : if ((box->max_a - box->min_a) > (box->max_b - box->min_b) &&
513 1022 : (box->max_a - box->min_a) >= (box->max_g - box->min_g) &&
514 899 : (box->max_a - box->min_a) >= (box->max_r - box->min_r)) {
515 831 : axis = 3;
516 : }
517 :
518 4558 : sort_entries_by_axis(entries + box->start, box->end - box->start, axis);
519 :
520 4558 : split_index = box_find_split_index(entries, box);
521 4558 : if (split_index <= box->start) {
522 977 : split_index = box->start + 1;
523 : }
524 :
525 4558 : if (split_index >= box->end) {
526 0 : split_index = box->end - 1;
527 : }
528 :
529 4558 : new_box->start = split_index;
530 4558 : new_box->end = box->end;
531 4558 : box->end = split_index;
532 :
533 4558 : color_box_refresh(entries, box);
534 4558 : color_box_refresh(entries, new_box);
535 :
536 4558 : return true;
537 : }
538 :
539 152503 : static inline int compare_color_map_by_color(const void *lhs, const void *rhs)
540 : {
541 152503 : const color_map_entry_t *a = (const color_map_entry_t *)lhs,
542 152503 : *b = (const color_map_entry_t *)rhs;
543 :
544 152503 : return a->color < b->color ? -1 : (a->color > b->color ? 1 : 0);
545 : }
546 :
547 145518 : static inline int compare_color_map_by_mapped(const void *lhs, const void *rhs)
548 : {
549 145518 : const color_map_entry_t *a = (const color_map_entry_t *)lhs,
550 145518 : *b = (const color_map_entry_t *)rhs;
551 :
552 145518 : return a->mapped_color < b->mapped_color ? -1 : (a->mapped_color > b->mapped_color ? 1 : 0);
553 : }
554 :
555 2462854 : static inline const color_map_entry_t *find_color_mapping(const color_map_entry_t *map,
556 : size_t count, uint32_t color)
557 : {
558 2462854 : size_t left = 0, right = (count > 0) ? count - 1 : 0, mid;
559 :
560 2462854 : if (!map || count == 0) {
561 0 : return NULL;
562 : }
563 :
564 30603647 : while (left <= right) {
565 29094766 : mid = left + ((right - left) / 2);
566 :
567 29094766 : if (map[mid].color == color) {
568 878632 : return &map[mid];
569 : }
570 :
571 28216134 : if (map[mid].color < color) {
572 12347562 : left = mid + 1;
573 : } else {
574 15868572 : if (mid == 0) {
575 75341 : break;
576 : }
577 :
578 15793231 : right = mid - 1;
579 : }
580 : }
581 :
582 1584222 : return NULL;
583 : }
584 :
585 7 : static inline void refine_reduced_palette(color_entry_t *entries, size_t unlocked_count,
586 : const uint32_t *seed_palette,
587 : const uint8_t *palette_bits_rgb,
588 : const uint8_t *palette_bits_alpha, size_t palette_count)
589 : {
590 7 : const size_t max_iter = 3;
591 : uint64_t *sum_r, *sum_g, *sum_b, *sum_a, *sum_w, w;
592 : uint32_t *palette, color, best_dist, weight, dist, new_color;
593 : uint8_t r, g, b, a, snap_bits_rgb, snap_bits_alpha, cr, cg, cb, ca;
594 : size_t iter, best_index, candidate, i, p;
595 : bool palette_changed;
596 :
597 7 : if (!entries || unlocked_count == 0 || !seed_palette || palette_count == 0 ||
598 : palette_count > 4096) {
599 0 : return;
600 : }
601 :
602 7 : palette = (uint32_t *)malloc(sizeof(uint32_t) * palette_count);
603 7 : if (!palette) {
604 0 : return;
605 : }
606 :
607 7 : memcpy(palette, seed_palette, sizeof(uint32_t) * palette_count);
608 :
609 7 : sum_r = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
610 7 : sum_g = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
611 7 : sum_b = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
612 7 : sum_a = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
613 7 : sum_w = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
614 :
615 7 : if (!sum_r || !sum_g || !sum_b || !sum_a || !sum_w) {
616 0 : free(sum_r);
617 0 : free(sum_g);
618 0 : free(sum_b);
619 0 : free(sum_a);
620 0 : free(sum_w);
621 0 : free(palette);
622 :
623 0 : return;
624 : }
625 :
626 21 : for (iter = 0; iter < max_iter; ++iter) {
627 17 : palette_changed = false;
628 :
629 17 : memset(sum_r, 0, sizeof(uint64_t) * palette_count);
630 17 : memset(sum_g, 0, sizeof(uint64_t) * palette_count);
631 17 : memset(sum_b, 0, sizeof(uint64_t) * palette_count);
632 17 : memset(sum_a, 0, sizeof(uint64_t) * palette_count);
633 17 : memset(sum_w, 0, sizeof(uint64_t) * palette_count);
634 :
635 47374 : for (i = 0; i < unlocked_count; ++i) {
636 47357 : color = entries[i].color;
637 47357 : best_dist = UINT32_MAX;
638 47357 : best_index = 0;
639 47357 : candidate = 0;
640 47357 : weight = entries[i].count ? entries[i].count : 1;
641 :
642 33876129 : for (candidate = 0; candidate < palette_count; ++candidate) {
643 33828772 : dist = simd_color_distance_sq_u32(color, palette[candidate]);
644 33828772 : if (dist < best_dist) {
645 337174 : best_dist = dist;
646 337174 : best_index = candidate;
647 : }
648 : }
649 :
650 47357 : unpack_rgba_u32(color, &cr, &cg, &cb, &ca);
651 :
652 47357 : sum_r[best_index] += (uint64_t)cr * (uint64_t)weight;
653 47357 : sum_g[best_index] += (uint64_t)cg * (uint64_t)weight;
654 47357 : sum_b[best_index] += (uint64_t)cb * (uint64_t)weight;
655 47357 : sum_a[best_index] += (uint64_t)ca * (uint64_t)weight;
656 47357 : sum_w[best_index] += (uint64_t)weight;
657 : }
658 :
659 10974 : for (p = 0; p < palette_count; ++p) {
660 10957 : w = sum_w[p];
661 10957 : if (w == 0) {
662 78 : continue;
663 : }
664 :
665 10879 : r = (uint8_t)((sum_r[p] + (w / 2)) / w);
666 10879 : g = (uint8_t)((sum_g[p] + (w / 2)) / w);
667 10879 : b = (uint8_t)((sum_b[p] + (w / 2)) / w);
668 10879 : a = (uint8_t)((sum_a[p] + (w / 2)) / w);
669 10879 : snap_bits_rgb =
670 10879 : palette_bits_rgb ? palette_bits_rgb[p] : COLOPRESSO_PNGX_REDUCED_BITS_MAX;
671 10879 : snap_bits_alpha =
672 10879 : palette_bits_alpha ? palette_bits_alpha[p] : COLOPRESSO_PNGX_REDUCED_BITS_MAX;
673 10879 : snap_rgba_to_bits(&r, &g, &b, &a, snap_bits_rgb, snap_bits_alpha);
674 10879 : new_color = pack_rgba_u32(r, g, b, a);
675 10879 : if (new_color != palette[p]) {
676 831 : palette[p] = new_color;
677 831 : palette_changed = true;
678 : }
679 : }
680 :
681 17 : if (!palette_changed) {
682 3 : break;
683 : }
684 : }
685 :
686 16940 : for (i = 0; i < unlocked_count; ++i) {
687 16933 : color = entries[i].color;
688 16933 : best_dist = UINT32_MAX;
689 16933 : best_index = 0;
690 :
691 13258047 : for (candidate = 0; candidate < palette_count; ++candidate) {
692 13241114 : dist = simd_color_distance_sq_u32(color, palette[candidate]);
693 13241114 : if (dist < best_dist) {
694 124418 : best_dist = dist;
695 124418 : best_index = candidate;
696 : }
697 : }
698 :
699 16933 : entries[i].mapped_color = palette[best_index];
700 : }
701 :
702 7 : free(sum_r);
703 7 : free(sum_g);
704 7 : free(sum_b);
705 7 : free(sum_a);
706 7 : free(sum_w);
707 :
708 7 : free(palette);
709 : }
710 :
711 8 : static inline bool build_color_histogram(const pngx_rgba_image_t *image, const pngx_options_t *opts,
712 : const pngx_quant_support_t *support,
713 : color_histogram_t *hist)
714 : {
715 8 : const cpres_rgba_color_t *protected_colors =
716 8 : (opts && opts->protected_colors_count > 0) ? opts->protected_colors : NULL;
717 8 : histogram_sample_t *samples = NULL;
718 : color_entry_t tmp;
719 : uint64_t weight_sum;
720 : uint32_t protected_table[256], color;
721 8 : uint8_t bits_rgb = 8, bits_alpha = 8, r, g, b, a, sample_bits_rgb, sample_bits_alpha, hint,
722 : hint_rgb, hint_alpha, max_bits_rgb, max_bits_alpha;
723 : size_t pixel_count, i, unique_count, base, run, write,
724 8 : protected_count = (protected_colors && opts->protected_colors_count > 0)
725 1 : ? (size_t)opts->protected_colors_count
726 8 : : 0;
727 8 : bool quantize_rgb = false, quantize_alpha = false;
728 :
729 8 : if (!image || !image->rgba || !hist || image->pixel_count == 0) {
730 0 : return false;
731 : }
732 :
733 8 : if (opts) {
734 8 : bits_rgb = clamp_reduced_bits(opts->lossy_reduced_bits_rgb);
735 8 : bits_alpha = clamp_reduced_bits(opts->lossy_reduced_alpha_bits);
736 : }
737 :
738 8 : quantize_rgb = bits_rgb < 8;
739 8 : quantize_alpha = bits_alpha < 8;
740 :
741 8 : if (protected_count > 256) {
742 0 : protected_count = 256;
743 : }
744 :
745 12 : for (i = 0; i < protected_count; ++i) {
746 4 : r = protected_colors[i].r;
747 4 : g = protected_colors[i].g;
748 4 : b = protected_colors[i].b;
749 4 : a = protected_colors[i].a;
750 :
751 4 : if (quantize_rgb) {
752 4 : r = quantize_bits(r, bits_rgb);
753 4 : g = quantize_bits(g, bits_rgb);
754 4 : b = quantize_bits(b, bits_rgb);
755 : }
756 :
757 4 : if (quantize_alpha) {
758 4 : a = quantize_bits(a, bits_alpha);
759 : }
760 :
761 4 : protected_table[i] = pack_rgba_u32(r, g, b, a);
762 : }
763 :
764 8 : pixel_count = image->pixel_count;
765 :
766 8 : samples = (histogram_sample_t *)malloc(pixel_count * sizeof(histogram_sample_t));
767 8 : if (!samples) {
768 0 : return false;
769 : }
770 :
771 2491408 : for (i = 0; i < pixel_count; ++i) {
772 2491400 : base = i * 4;
773 2491400 : r = image->rgba[base + 0];
774 2491400 : g = image->rgba[base + 1];
775 2491400 : b = image->rgba[base + 2];
776 2491400 : a = image->rgba[base + 3];
777 2491400 : sample_bits_rgb = bits_rgb;
778 2491400 : sample_bits_alpha = bits_alpha;
779 :
780 2491400 : if (support && support->bit_hint_map && i < support->bit_hint_len) {
781 2491400 : hint = support->bit_hint_map[i];
782 2491400 : hint_rgb = hint >> 4;
783 2491400 : hint_alpha = hint & 0x0fu;
784 :
785 2491400 : if (hint_rgb >= COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
786 2491400 : sample_bits_rgb = clamp_reduced_bits(hint_rgb);
787 : }
788 :
789 2491400 : if (hint_alpha >= COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
790 2491400 : sample_bits_alpha = clamp_reduced_bits(hint_alpha);
791 : }
792 : }
793 :
794 2491400 : if (quantize_rgb) {
795 2424840 : r = quantize_bits(r, bits_rgb);
796 2424840 : g = quantize_bits(g, bits_rgb);
797 2424840 : b = quantize_bits(b, bits_rgb);
798 : }
799 :
800 2491400 : if (quantize_alpha) {
801 2491400 : a = quantize_bits(a, bits_alpha);
802 : }
803 :
804 2491400 : samples[i].color = pack_rgba_u32(r, g, b, a);
805 2491400 : samples[i].weight = histogram_importance_weight(support, i);
806 2491400 : samples[i].rgb_bits = sample_bits_rgb;
807 2491400 : samples[i].alpha_bits = sample_bits_alpha;
808 : }
809 :
810 8 : qsort(samples, pixel_count, sizeof(histogram_sample_t), compare_histogram_sample);
811 :
812 8 : unique_count = 0;
813 :
814 16945 : for (i = 0; i < pixel_count;) {
815 16937 : color = samples[i].color;
816 16937 : run = 1;
817 :
818 2491400 : while (i + run < pixel_count && samples[i + run].color == color) {
819 2474463 : ++run;
820 : }
821 :
822 16937 : ++unique_count;
823 16937 : i += run;
824 : }
825 :
826 8 : hist->entries = (color_entry_t *)malloc(unique_count * sizeof(color_entry_t));
827 8 : if (!hist->entries) {
828 0 : free(samples);
829 :
830 0 : return false;
831 : }
832 :
833 8 : hist->count = unique_count;
834 8 : hist->unlocked_count = 0;
835 :
836 16945 : for (i = 0, unique_count = 0; i < pixel_count;) {
837 16937 : color = samples[i].color;
838 16937 : run = 1;
839 16937 : weight_sum = samples[i].weight;
840 16937 : max_bits_rgb = samples[i].rgb_bits;
841 16937 : max_bits_alpha = samples[i].alpha_bits;
842 :
843 2491400 : while (i + run < pixel_count && samples[i + run].color == color) {
844 2474463 : weight_sum += samples[i + run].weight;
845 :
846 2474463 : if (samples[i + run].rgb_bits > max_bits_rgb) {
847 770 : max_bits_rgb = samples[i + run].rgb_bits;
848 : }
849 :
850 2474463 : if (samples[i + run].alpha_bits > max_bits_alpha) {
851 540 : max_bits_alpha = samples[i + run].alpha_bits;
852 : }
853 :
854 2474463 : ++run;
855 : }
856 :
857 16937 : hist->entries[unique_count].color = color;
858 16937 : hist->entries[unique_count].count =
859 16937 : (uint32_t)((weight_sum > UINT32_MAX) ? UINT32_MAX : weight_sum);
860 16937 : hist->entries[unique_count].mapped_color = color;
861 33874 : hist->entries[unique_count].locked =
862 16937 : is_protected_color(color, protected_table, protected_count);
863 16937 : hist->entries[unique_count].detail_bits_rgb = clamp_reduced_bits(max_bits_rgb);
864 16937 : hist->entries[unique_count].detail_bits_alpha = clamp_reduced_bits(max_bits_alpha);
865 16937 : ++unique_count;
866 16937 : i += run;
867 : }
868 :
869 8 : free(samples);
870 :
871 8 : if (unique_count == 0) {
872 0 : return true;
873 : }
874 :
875 8 : write = 0;
876 :
877 16945 : for (i = 0; i < unique_count; ++i) {
878 16937 : if (!hist->entries[i].locked) {
879 16933 : if (write != i) {
880 0 : tmp = hist->entries[write];
881 0 : hist->entries[write] = hist->entries[i];
882 0 : hist->entries[i] = tmp;
883 : }
884 :
885 16933 : ++write;
886 : }
887 : }
888 :
889 8 : hist->unlocked_count = write;
890 :
891 8 : return true;
892 : }
893 :
894 4565 : static inline uint32_t box_representative_color(const color_entry_t *entries,
895 : const color_box_t *box)
896 : {
897 4565 : uint64_t sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0, total = 0, weight;
898 : uint32_t c;
899 : size_t i;
900 :
901 4565 : if (!entries || !box) {
902 0 : return 0;
903 : }
904 :
905 21498 : for (i = box->start; i < box->end; ++i) {
906 16933 : c = entries[i].color;
907 16933 : weight = (uint64_t)entries[i].count;
908 16933 : sum_r += (uint64_t)(c & 0xff) * weight;
909 16933 : sum_g += (uint64_t)((c >> 8) & 0xff) * weight;
910 16933 : sum_b += (uint64_t)((c >> 16) & 0xff) * weight;
911 16933 : sum_a += (uint64_t)((c >> 24) & 0xff) * weight;
912 16933 : total += weight;
913 : }
914 :
915 4565 : if (total == 0) {
916 0 : return entries[box->start].color;
917 : }
918 :
919 4565 : sum_r = (sum_r + (total / 2)) / total;
920 4565 : sum_g = (sum_g + (total / 2)) / total;
921 4565 : sum_b = (sum_b + (total / 2)) / total;
922 4565 : sum_a = (sum_a + (total / 2)) / total;
923 :
924 4565 : return pack_rgba_u32((uint8_t)sum_r, (uint8_t)sum_g, (uint8_t)sum_b, (uint8_t)sum_a);
925 : }
926 :
927 7 : static inline void color_remap_parallel_worker(void *context, uint32_t start, uint32_t end)
928 : {
929 : const color_map_entry_t *mapping;
930 7 : color_remap_parallel_ctx_t *ctx = (color_remap_parallel_ctx_t *)context;
931 : uint32_t original;
932 : uint8_t *px;
933 : size_t i, base;
934 :
935 7 : if (!ctx || !ctx->rgba || !ctx->map || ctx->map_count == 0) {
936 0 : return;
937 : }
938 :
939 2491403 : for (i = (size_t)start; i < (size_t)end && i < ctx->pixel_count; ++i) {
940 2491396 : base = i * PNGX_RGBA_CHANNELS;
941 2491396 : px = &ctx->rgba[base];
942 2491396 : if (px[3] <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
943 28542 : continue;
944 : }
945 :
946 2462854 : original = pack_rgba_u32(px[0], px[1], px[2], px[3]);
947 2462854 : mapping = find_color_mapping(ctx->map, ctx->map_count, original);
948 :
949 2462854 : if (!mapping) {
950 1584222 : continue;
951 : }
952 :
953 878632 : unpack_rgba_u32(mapping->mapped_color, &px[0], &px[1], &px[2], &px[3]);
954 : }
955 : }
956 :
957 7 : static inline bool apply_reduced_rgba32_quantization(uint32_t thread_count, color_histogram_t *hist,
958 : pngx_rgba_image_t *image,
959 : uint32_t target_colors, uint8_t bits_rgb,
960 : uint8_t bits_alpha, uint32_t *applied_colors)
961 : {
962 7 : color_box_t *boxes = NULL, new_box;
963 7 : color_map_entry_t *map = NULL;
964 : color_remap_parallel_ctx_t remap_ctx;
965 7 : uint32_t *palette_seed = NULL, actual_colors = 0, mapped;
966 7 : uint8_t *palette_bits_rgb = NULL, *palette_bits_alpha = NULL, r, g, b, a;
967 : int32_t split_index;
968 7 : size_t box_count = 0, map_count, i, idx;
969 :
970 7 : if (!hist || !image || target_colors == 0) {
971 0 : if (applied_colors) {
972 0 : *applied_colors = (uint32_t)(hist ? hist->count : 0);
973 : }
974 :
975 0 : return true;
976 : }
977 :
978 7 : if (hist->unlocked_count == 0) {
979 0 : if (applied_colors) {
980 0 : *applied_colors = (uint32_t)hist->count;
981 : }
982 :
983 0 : return true;
984 : }
985 :
986 7 : if (target_colors > (uint32_t)hist->unlocked_count) {
987 0 : target_colors = (uint32_t)hist->unlocked_count;
988 : }
989 :
990 7 : if (target_colors == 0) {
991 0 : target_colors = 1;
992 : }
993 :
994 7 : boxes = (color_box_t *)calloc(target_colors, sizeof(color_box_t));
995 7 : if (!boxes) {
996 0 : return false;
997 : }
998 :
999 7 : boxes[0].start = 0;
1000 7 : boxes[0].end = hist->unlocked_count;
1001 7 : color_box_refresh(hist->entries, &boxes[0]);
1002 7 : box_count = (boxes[0].end > boxes[0].start) ? 1 : 0;
1003 :
1004 4565 : while (box_count < target_colors) {
1005 4558 : split_index = select_box_to_split(boxes, box_count, hist->entries, bits_rgb, bits_alpha);
1006 4558 : if (split_index < 0) {
1007 0 : break;
1008 : }
1009 :
1010 4558 : if (!split_color_box(hist->entries, &boxes[split_index], &new_box)) {
1011 0 : break;
1012 : }
1013 :
1014 4558 : boxes[box_count++] = new_box;
1015 : }
1016 :
1017 7 : if (box_count == 0) {
1018 0 : free(boxes);
1019 :
1020 0 : if (applied_colors) {
1021 0 : *applied_colors = (uint32_t)hist->count;
1022 : }
1023 :
1024 0 : return true;
1025 : }
1026 :
1027 7 : palette_seed = (uint32_t *)malloc(sizeof(uint32_t) * box_count);
1028 7 : palette_bits_rgb = (uint8_t *)calloc(box_count, sizeof(uint8_t));
1029 7 : palette_bits_alpha = (uint8_t *)calloc(box_count, sizeof(uint8_t));
1030 7 : if (!palette_seed || !palette_bits_rgb || !palette_bits_alpha) {
1031 0 : free(palette_seed);
1032 0 : free(palette_bits_rgb);
1033 0 : free(palette_bits_alpha);
1034 0 : free(boxes);
1035 :
1036 0 : return false;
1037 : }
1038 :
1039 4572 : for (i = 0; i < box_count; ++i) {
1040 4565 : mapped = box_representative_color(hist->entries, &boxes[i]);
1041 4565 : unpack_rgba_u32(mapped, &r, &g, &b, &a);
1042 4565 : snap_rgba_to_bits(&r, &g, &b, &a, bits_rgb, bits_alpha);
1043 4565 : mapped = pack_rgba_u32(r, g, b, a);
1044 4565 : palette_seed[i] = mapped;
1045 4565 : palette_bits_rgb[i] = bits_rgb;
1046 4565 : palette_bits_alpha[i] = bits_alpha;
1047 :
1048 21498 : for (idx = boxes[i].start; idx < boxes[i].end; ++idx) {
1049 16933 : hist->entries[idx].mapped_color = mapped;
1050 : }
1051 : }
1052 :
1053 7 : if (box_count > 0 && box_count <= 4096) {
1054 7 : refine_reduced_palette(hist->entries, hist->unlocked_count, palette_seed, palette_bits_rgb,
1055 : palette_bits_alpha, box_count);
1056 : }
1057 :
1058 7 : for (i = hist->unlocked_count; i < hist->count; ++i) {
1059 0 : hist->entries[i].mapped_color = hist->entries[i].color;
1060 : }
1061 :
1062 7 : map_count = hist->count;
1063 7 : if (map_count == 0) {
1064 0 : free(palette_seed);
1065 0 : free(palette_bits_rgb);
1066 0 : free(palette_bits_alpha);
1067 0 : free(boxes);
1068 :
1069 0 : if (applied_colors) {
1070 0 : *applied_colors = 0;
1071 : }
1072 :
1073 0 : return true;
1074 : }
1075 :
1076 7 : map = (color_map_entry_t *)malloc(map_count * sizeof(color_map_entry_t));
1077 7 : if (!map) {
1078 0 : free(palette_seed);
1079 0 : free(palette_bits_rgb);
1080 0 : free(palette_bits_alpha);
1081 0 : free(boxes);
1082 :
1083 0 : return false;
1084 : }
1085 :
1086 16940 : for (i = 0; i < map_count; ++i) {
1087 16933 : map[i].color = hist->entries[i].color;
1088 16933 : map[i].mapped_color = hist->entries[i].mapped_color;
1089 : }
1090 :
1091 7 : qsort(map, map_count, sizeof(*map), compare_color_map_by_color);
1092 :
1093 7 : remap_ctx.rgba = image->rgba;
1094 7 : remap_ctx.pixel_count = image->pixel_count;
1095 7 : remap_ctx.map = map;
1096 7 : remap_ctx.map_count = map_count;
1097 :
1098 : #if COLOPRESSO_ENABLE_THREADS
1099 7 : colopresso_parallel_for(thread_count, (uint32_t)image->pixel_count, color_remap_parallel_worker,
1100 : &remap_ctx);
1101 : #else
1102 : color_remap_parallel_worker(&remap_ctx, 0, (uint32_t)image->pixel_count);
1103 : #endif
1104 :
1105 7 : qsort(map, map_count, sizeof(*map), compare_color_map_by_mapped);
1106 :
1107 16940 : for (i = 0; i < map_count; ++i) {
1108 16933 : if (i == 0 || map[i].mapped_color != map[i - 1].mapped_color) {
1109 4537 : ++actual_colors;
1110 : }
1111 : }
1112 :
1113 7 : if (applied_colors) {
1114 7 : *applied_colors = actual_colors;
1115 : }
1116 :
1117 7 : free(map);
1118 7 : free(palette_seed);
1119 7 : free(palette_bits_rgb);
1120 7 : free(palette_bits_alpha);
1121 7 : free(boxes);
1122 :
1123 7 : return true;
1124 : }
1125 :
1126 34 : static inline void pack_rgba_parallel_worker(void *context, uint32_t start, uint32_t end)
1127 : {
1128 34 : pack_rgba_parallel_ctx_t *ctx = (pack_rgba_parallel_ctx_t *)context;
1129 : size_t i, base;
1130 :
1131 34 : if (!ctx || !ctx->rgba || !ctx->packed) {
1132 0 : return;
1133 : }
1134 :
1135 3673386 : for (i = (size_t)start; i < (size_t)end && i < ctx->pixel_count; ++i) {
1136 3673352 : base = i * 4;
1137 3673352 : ctx->packed[i] = pack_rgba_u32(ctx->rgba[base + 0], ctx->rgba[base + 1],
1138 3673352 : ctx->rgba[base + 2], ctx->rgba[base + 3]);
1139 : }
1140 : }
1141 :
1142 17 : static inline bool pack_sorted_rgba(const uint8_t *rgba, size_t pixel_count, uint32_t **out_sorted)
1143 : {
1144 : pack_rgba_parallel_ctx_t ctx;
1145 : uint32_t *packed;
1146 :
1147 17 : if (!rgba || pixel_count == 0 || !out_sorted) {
1148 0 : return false;
1149 : }
1150 :
1151 17 : if (pixel_count > SIZE_MAX / sizeof(uint32_t)) {
1152 0 : return false;
1153 : }
1154 :
1155 17 : packed = (uint32_t *)malloc(sizeof(uint32_t) * pixel_count);
1156 17 : if (!packed) {
1157 0 : return false;
1158 : }
1159 :
1160 17 : ctx.rgba = rgba;
1161 17 : ctx.packed = packed;
1162 17 : ctx.pixel_count = pixel_count;
1163 :
1164 : #if COLOPRESSO_ENABLE_THREADS
1165 17 : colopresso_parallel_for(0, (uint32_t)pixel_count, pack_rgba_parallel_worker, &ctx);
1166 : #else
1167 : pack_rgba_parallel_worker(&ctx, 0, (uint32_t)pixel_count);
1168 : #endif
1169 :
1170 17 : qsort(packed, pixel_count, sizeof(uint32_t), compare_u32);
1171 17 : *out_sorted = packed;
1172 :
1173 17 : return true;
1174 : }
1175 :
1176 13 : static inline size_t count_unique_rgba(const uint8_t *rgba, size_t pixel_count)
1177 : {
1178 : uint32_t *packed;
1179 13 : size_t i, unique = 0;
1180 :
1181 13 : if (!rgba || pixel_count == 0) {
1182 0 : return 0;
1183 : }
1184 :
1185 13 : if (!pack_sorted_rgba(rgba, pixel_count, &packed)) {
1186 0 : return 0;
1187 : }
1188 :
1189 3082517 : for (i = 0; i < pixel_count; ++i) {
1190 3082504 : if (i == 0 || packed[i] != packed[i - 1]) {
1191 74495 : ++unique;
1192 : }
1193 : }
1194 :
1195 13 : free(packed);
1196 :
1197 13 : return unique;
1198 : }
1199 :
1200 4 : static inline bool build_color_frequency(const uint8_t *rgba, size_t pixel_count,
1201 : color_freq_t **out_freq, size_t *out_count)
1202 : {
1203 : color_freq_t *freq, *shrunk;
1204 : uint32_t *packed;
1205 : size_t i, unique;
1206 :
1207 4 : if (!out_freq || !out_count) {
1208 0 : return false;
1209 : }
1210 :
1211 4 : if (!pack_sorted_rgba(rgba, pixel_count, &packed)) {
1212 0 : return false;
1213 : }
1214 :
1215 4 : freq = (color_freq_t *)malloc(sizeof(color_freq_t) * pixel_count);
1216 4 : if (!freq) {
1217 0 : free(packed);
1218 :
1219 0 : return false;
1220 : }
1221 :
1222 4 : unique = 0;
1223 590852 : for (i = 0; i < pixel_count; ++i) {
1224 590848 : if (i == 0 || packed[i] != packed[i - 1]) {
1225 4373 : freq[unique].color = packed[i];
1226 4373 : freq[unique].count = 1;
1227 4373 : ++unique;
1228 : } else {
1229 586475 : ++freq[unique - 1].count;
1230 : }
1231 : }
1232 :
1233 4 : free(packed);
1234 :
1235 4 : if (unique == 0) {
1236 0 : free(freq);
1237 0 : *out_freq = NULL;
1238 0 : *out_count = 0;
1239 :
1240 0 : return true;
1241 : }
1242 :
1243 4 : shrunk = (color_freq_t *)realloc(freq, sizeof(color_freq_t) * unique);
1244 4 : if (!shrunk) {
1245 0 : free(freq);
1246 :
1247 0 : return false;
1248 : }
1249 :
1250 4 : freq = shrunk;
1251 :
1252 4 : *out_freq = freq;
1253 4 : *out_count = unique;
1254 :
1255 4 : return true;
1256 : }
1257 :
1258 590848 : static inline bool find_freq_index(const color_freq_t *freq, size_t freq_count, uint32_t color,
1259 : size_t *index_out)
1260 : {
1261 590848 : size_t left = 0, right = freq_count, mid;
1262 :
1263 590848 : if (!freq || freq_count == 0) {
1264 0 : return false;
1265 : }
1266 :
1267 5599644 : while (left < right) {
1268 5599644 : mid = left + (right - left) / 2;
1269 5599644 : if (freq[mid].color == color) {
1270 590848 : if (index_out) {
1271 590848 : *index_out = mid;
1272 : }
1273 :
1274 590848 : return true;
1275 : }
1276 :
1277 5008796 : if (freq[mid].color < color) {
1278 2165167 : left = mid + 1;
1279 : } else {
1280 2843629 : right = mid;
1281 : }
1282 : }
1283 :
1284 0 : return false;
1285 : }
1286 :
1287 39216 : static inline int compare_freq_rank_desc(const void *lhs, const void *rhs)
1288 : {
1289 39216 : const freq_rank_t *a = (const freq_rank_t *)lhs, *b = (const freq_rank_t *)rhs;
1290 :
1291 39216 : if (a->count != b->count) {
1292 30328 : return a->count > b->count ? -1 : 1;
1293 : }
1294 :
1295 8888 : return a->color < b->color ? -1 : (a->color > b->color ? 1 : 0);
1296 : }
1297 :
1298 4 : static inline bool enforce_manual_reduced_limit(uint32_t thread_count, pngx_rgba_image_t *image,
1299 : uint32_t manual_limit, uint8_t bits_rgb,
1300 : uint8_t bits_alpha, uint32_t *applied_colors)
1301 : {
1302 4 : color_freq_t *freq = NULL;
1303 4 : freq_rank_t *rank = NULL;
1304 4 : uint32_t *mapped = NULL, source, best_color, best_dist, candidate_color, distance, original;
1305 4 : size_t *keep_indices = NULL, freq_count = 0, keep_count, current_unique, unique_after,
1306 : pixel_index, idx, best_keep, keep_idx, candidate_index, base, freq_index, i;
1307 4 : bool success = false;
1308 :
1309 4 : if (!image || !image->rgba || image->pixel_count == 0 || manual_limit == 0) {
1310 0 : if (applied_colors && image && image->rgba) {
1311 0 : current_unique = count_unique_rgba(image->rgba, image->pixel_count);
1312 :
1313 0 : if (current_unique > UINT32_MAX) {
1314 0 : current_unique = UINT32_MAX;
1315 : }
1316 :
1317 0 : *applied_colors = (uint32_t)current_unique;
1318 : }
1319 :
1320 0 : return true;
1321 : }
1322 :
1323 4 : snap_rgba_image_to_bits(thread_count, image->rgba, image->pixel_count, bits_rgb, bits_alpha);
1324 :
1325 4 : if (!build_color_frequency(image->rgba, image->pixel_count, &freq, &freq_count)) {
1326 0 : goto bailout;
1327 : }
1328 :
1329 4 : if (freq_count == 0) {
1330 0 : if (applied_colors) {
1331 0 : *applied_colors = 0;
1332 : }
1333 :
1334 0 : success = true;
1335 :
1336 0 : goto bailout;
1337 : }
1338 :
1339 4 : if (freq_count <= (size_t)manual_limit) {
1340 0 : if (applied_colors) {
1341 0 : *applied_colors = (uint32_t)freq_count;
1342 : }
1343 :
1344 0 : success = true;
1345 :
1346 0 : goto bailout;
1347 : }
1348 :
1349 4 : keep_count = (size_t)manual_limit;
1350 4 : if (keep_count > freq_count) {
1351 0 : keep_count = freq_count;
1352 : }
1353 :
1354 4 : rank = (freq_rank_t *)malloc(sizeof(freq_rank_t) * freq_count);
1355 4 : mapped = (uint32_t *)malloc(sizeof(uint32_t) * freq_count);
1356 4 : keep_indices = (size_t *)malloc(sizeof(size_t) * keep_count);
1357 4 : if (!rank || !mapped || !keep_indices) {
1358 0 : goto bailout;
1359 : }
1360 :
1361 4377 : for (i = 0; i < freq_count; ++i) {
1362 4373 : rank[i].index = i;
1363 4373 : rank[i].count = freq[i].count;
1364 4373 : rank[i].color = freq[i].color;
1365 4373 : mapped[i] = freq[i].color;
1366 : }
1367 :
1368 4 : qsort(rank, freq_count, sizeof(freq_rank_t), compare_freq_rank_desc);
1369 :
1370 2700 : for (i = 0; i < keep_count; ++i) {
1371 2696 : keep_indices[i] = rank[i].index;
1372 : }
1373 :
1374 1681 : for (i = keep_count; i < freq_count; ++i) {
1375 1677 : idx = rank[i].index;
1376 1677 : source = freq[idx].color;
1377 1677 : best_keep = keep_indices[0];
1378 1677 : best_color = freq[best_keep].color;
1379 1677 : best_dist = simd_color_distance_sq_u32(source, best_color);
1380 :
1381 601676 : for (keep_idx = 1; keep_idx < keep_count; ++keep_idx) {
1382 599999 : candidate_index = keep_indices[keep_idx];
1383 599999 : candidate_color = freq[candidate_index].color;
1384 599999 : distance = simd_color_distance_sq_u32(source, candidate_color);
1385 :
1386 599999 : if (distance < best_dist) {
1387 10265 : best_dist = distance;
1388 10265 : best_color = candidate_color;
1389 : }
1390 : }
1391 :
1392 1677 : mapped[idx] = best_color;
1393 : }
1394 :
1395 590852 : for (pixel_index = 0; pixel_index < image->pixel_count; ++pixel_index) {
1396 590848 : base = pixel_index * 4;
1397 590848 : original = pack_rgba_u32(image->rgba[base + 0], image->rgba[base + 1],
1398 590848 : image->rgba[base + 2], image->rgba[base + 3]);
1399 :
1400 590848 : if (!find_freq_index(freq, freq_count, original, &freq_index)) {
1401 0 : continue;
1402 : }
1403 :
1404 590848 : if (mapped[freq_index] == original) {
1405 484355 : continue;
1406 : }
1407 :
1408 106493 : unpack_rgba_u32(mapped[freq_index], &image->rgba[base + 0], &image->rgba[base + 1],
1409 106493 : &image->rgba[base + 2], &image->rgba[base + 3]);
1410 : }
1411 :
1412 4 : snap_rgba_image_to_bits(thread_count, image->rgba, image->pixel_count, bits_rgb, bits_alpha);
1413 :
1414 4 : unique_after = count_unique_rgba(image->rgba, image->pixel_count);
1415 :
1416 4 : if (applied_colors) {
1417 4 : if (unique_after > UINT32_MAX) {
1418 0 : unique_after = UINT32_MAX;
1419 : }
1420 :
1421 4 : *applied_colors = (uint32_t)unique_after;
1422 : }
1423 :
1424 4 : colopresso_log(CPRES_LOG_LEVEL_DEBUG,
1425 : "PNGX: Reduced RGBA32 manual target enforcement trimmed %zu -> %zu colors",
1426 : freq_count, unique_after);
1427 :
1428 4 : success = true;
1429 :
1430 4 : bailout:
1431 4 : free(freq);
1432 4 : free(rank);
1433 4 : free(mapped);
1434 4 : free(keep_indices);
1435 :
1436 4 : return success;
1437 : }
1438 :
1439 4983312 : static inline uint8_t resolve_pixel_bits(uint8_t importance, uint8_t base_bits, uint8_t boost_bits)
1440 : {
1441 4983312 : uint8_t resolved = base_bits, mid_bits;
1442 :
1443 4983312 : if (boost_bits <= base_bits) {
1444 133648 : return base_bits;
1445 : }
1446 :
1447 4849664 : if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_FULL) {
1448 30540 : resolved = boost_bits;
1449 4819124 : } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_HIGH) {
1450 311036 : mid_bits = (uint8_t)((base_bits + boost_bits + 1) / 2);
1451 311036 : resolved = mid_bits;
1452 4508088 : } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_MEDIUM) {
1453 1374458 : mid_bits = (uint8_t)((base_bits * 2 + boost_bits + 2) / 3);
1454 1374458 : resolved = mid_bits;
1455 3133630 : } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_LOW) {
1456 1456982 : resolved = (uint8_t)((base_bits * 3 + boost_bits + 3) / 4);
1457 : }
1458 :
1459 4849664 : if (resolved > boost_bits) {
1460 0 : resolved = boost_bits;
1461 : }
1462 :
1463 4849664 : if (resolved < base_bits) {
1464 0 : resolved = base_bits;
1465 : }
1466 :
1467 4849664 : return resolved;
1468 : }
1469 :
1470 5 : static inline void reduce_bitdepth_parallel_worker(void *context, uint32_t start, uint32_t end)
1471 : {
1472 5 : reduce_bitdepth_parallel_ctx_t *ctx = (reduce_bitdepth_parallel_ctx_t *)context;
1473 : uint8_t importance, pixel_bits_rgb, pixel_bits_alpha;
1474 : size_t i, base;
1475 :
1476 5 : if (!ctx || !ctx->rgba) {
1477 0 : return;
1478 : }
1479 :
1480 66829 : for (i = (size_t)start; i < (size_t)end && i < ctx->pixel_count; ++i) {
1481 66824 : base = i * PNGX_RGBA_CHANNELS;
1482 66824 : importance =
1483 66824 : (ctx->importance_map && i < ctx->importance_map_len) ? ctx->importance_map[i] : 0;
1484 66824 : pixel_bits_rgb = resolve_pixel_bits(importance, ctx->bits_rgb, ctx->boost_bits_rgb);
1485 66824 : pixel_bits_alpha = resolve_pixel_bits(importance, ctx->bits_alpha, ctx->boost_bits_alpha);
1486 :
1487 66824 : if (ctx->bit_hint_map && i < ctx->bit_hint_len) {
1488 66824 : ctx->bit_hint_map[i] = (uint8_t)((pixel_bits_rgb << 4) | (pixel_bits_alpha & 0x0fu));
1489 : }
1490 :
1491 66824 : if (ctx->rgba[base + 3] > PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
1492 66760 : if (pixel_bits_rgb < PNGX_FULL_CHANNEL_BITS) {
1493 200 : ctx->rgba[base + 0] = quantize_bits(ctx->rgba[base + 0], pixel_bits_rgb);
1494 200 : ctx->rgba[base + 1] = quantize_bits(ctx->rgba[base + 1], pixel_bits_rgb);
1495 200 : ctx->rgba[base + 2] = quantize_bits(ctx->rgba[base + 2], pixel_bits_rgb);
1496 : }
1497 : }
1498 :
1499 66824 : if (pixel_bits_alpha < PNGX_FULL_CHANNEL_BITS) {
1500 66824 : ctx->rgba[base + 3] = quantize_bits(ctx->rgba[base + 3], pixel_bits_alpha);
1501 : }
1502 : }
1503 : }
1504 :
1505 5 : static inline void reduce_rgba_custom_bitdepth_simple(
1506 : uint32_t thread_count, uint8_t *rgba, size_t pixel_count, uint8_t bits_rgb, uint8_t bits_alpha,
1507 : const uint8_t *importance_map, size_t importance_map_len, uint8_t boost_bits_rgb,
1508 : uint8_t boost_bits_alpha, uint8_t *bit_hint_map, size_t bit_hint_len)
1509 : {
1510 : reduce_bitdepth_parallel_ctx_t ctx;
1511 :
1512 5 : if (!rgba || pixel_count == 0) {
1513 0 : return;
1514 : }
1515 :
1516 5 : ctx.rgba = rgba;
1517 5 : ctx.pixel_count = pixel_count;
1518 5 : ctx.importance_map = importance_map;
1519 5 : ctx.importance_map_len = importance_map_len;
1520 5 : ctx.bits_rgb = clamp_reduced_bits(bits_rgb);
1521 5 : ctx.bits_alpha = clamp_reduced_bits(bits_alpha);
1522 5 : ctx.boost_bits_rgb = clamp_reduced_bits(boost_bits_rgb);
1523 5 : ctx.boost_bits_alpha = clamp_reduced_bits(boost_bits_alpha);
1524 5 : ctx.bit_hint_map = bit_hint_map;
1525 5 : ctx.bit_hint_len = bit_hint_len;
1526 :
1527 : #if COLOPRESSO_ENABLE_THREADS
1528 5 : colopresso_parallel_for(thread_count, (uint32_t)pixel_count, reduce_bitdepth_parallel_worker,
1529 : &ctx);
1530 : #else
1531 : reduce_bitdepth_parallel_worker(&ctx, 0, (uint32_t)pixel_count);
1532 : #endif
1533 : }
1534 :
1535 2424832 : static inline void process_custom_bitdepth_pixel(uint8_t *rgba, png_uint_32 width,
1536 : png_uint_32 height, uint32_t x, uint32_t y,
1537 : uint8_t base_bits_rgb, uint8_t base_bits_alpha,
1538 : uint8_t boost_bits_rgb, uint8_t boost_bits_alpha,
1539 : float base_dither, const uint8_t *importance_map,
1540 : size_t pixel_count, float *err_curr,
1541 : float *err_next, bool left_to_right,
1542 : uint8_t *bit_hint_map, size_t bit_hint_len)
1543 : {
1544 2424832 : uint8_t importance = 0, pixel_bits_rgb, pixel_bits_alpha, channel, bits, quantized;
1545 2424832 : size_t pixel_index = (size_t)y * (size_t)width + (size_t)x,
1546 2424832 : rgba_index = pixel_index * PNGX_RGBA_CHANNELS,
1547 2424832 : err_index = (size_t)x * PNGX_RGBA_CHANNELS;
1548 2424832 : float dither = base_dither, value, error, alpha_factor, dither_ch;
1549 :
1550 2424832 : if (importance_map && pixel_index < pixel_count) {
1551 2424832 : importance = importance_map[pixel_index];
1552 2424832 : dither *= importance_dither_scale(importance);
1553 : }
1554 :
1555 2424832 : alpha_factor = (float)rgba[rgba_index + 3] / 255.0f;
1556 :
1557 2424832 : pixel_bits_rgb = resolve_pixel_bits(importance, base_bits_rgb, boost_bits_rgb);
1558 2424832 : pixel_bits_alpha = resolve_pixel_bits(importance, base_bits_alpha, boost_bits_alpha);
1559 :
1560 2424832 : if (bit_hint_map && pixel_index < bit_hint_len) {
1561 2424832 : bit_hint_map[pixel_index] = (uint8_t)((pixel_bits_rgb << 4) | (pixel_bits_alpha & 0x0fu));
1562 : }
1563 :
1564 12124160 : for (channel = 0; channel < PNGX_RGBA_CHANNELS; ++channel) {
1565 9699328 : if (channel != 3 && rgba[rgba_index + 3] <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
1566 82944 : bits = PNGX_FULL_CHANNEL_BITS;
1567 : } else {
1568 9616384 : bits = (channel == 3) ? pixel_bits_alpha : pixel_bits_rgb;
1569 : }
1570 :
1571 9699328 : if (bits >= PNGX_FULL_CHANNEL_BITS) {
1572 82944 : err_curr[err_index + channel] = 0.0f;
1573 :
1574 82944 : continue;
1575 : }
1576 :
1577 9616384 : value = (float)rgba[rgba_index + channel] + err_curr[err_index + channel];
1578 9616384 : quantized = quantize_channel_value(value, bits);
1579 9616384 : error = (value - (float)quantized);
1580 :
1581 9616384 : if (channel == 3) {
1582 2424832 : dither_ch = 0.0f;
1583 : } else {
1584 7191552 : dither_ch = dither * alpha_factor;
1585 :
1586 7191552 : if (alpha_factor < PNGX_REDUCED_ALPHA_MIN_DITHER_FACTOR) {
1587 18432 : dither_ch = 0.0f;
1588 : }
1589 : }
1590 :
1591 9616384 : error *= dither_ch;
1592 :
1593 9616384 : rgba[rgba_index + channel] = quantized;
1594 9616384 : err_curr[err_index + channel] = 0.0f;
1595 :
1596 9616384 : if (dither_ch <= 0.0f || error == 0.0f) {
1597 2573894 : continue;
1598 : }
1599 :
1600 7042490 : if (left_to_right) {
1601 3520033 : if (x + 1 < width) {
1602 3515946 : err_curr[err_index + PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
1603 : }
1604 :
1605 3520033 : if (y + 1 < height) {
1606 3520033 : if (x > 0) {
1607 3515928 : err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
1608 : }
1609 :
1610 3520033 : err_next[err_index + channel] += error * (5.0f / 16.0f);
1611 :
1612 3520033 : if (x + 1 < width) {
1613 3515946 : err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
1614 : }
1615 : }
1616 : } else {
1617 3522457 : if (x > 0) {
1618 3518333 : err_curr[err_index - PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
1619 : }
1620 :
1621 3522457 : if (y + 1 < height) {
1622 3514481 : if (x + 1 < width) {
1623 3510412 : err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
1624 : }
1625 :
1626 3514481 : err_next[err_index + channel] += error * (5.0f / 16.0f);
1627 :
1628 3514481 : if (x > 0) {
1629 3510369 : err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
1630 : }
1631 : }
1632 : }
1633 : }
1634 2424832 : }
1635 :
1636 4 : static inline bool reduce_rgba_custom_bitdepth_dither(
1637 : uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_rgb, uint8_t bits_alpha,
1638 : uint8_t boost_bits_rgb, uint8_t boost_bits_alpha, float dither_level,
1639 : const uint8_t *importance_map, size_t pixel_count, uint8_t *bit_hint_map, size_t bit_hint_len)
1640 : {
1641 : uint32_t x, y;
1642 : size_t row_stride;
1643 : bool left_to_right;
1644 : float *err_curr, *err_next, *tmp;
1645 :
1646 4 : bits_rgb = clamp_reduced_bits(bits_rgb);
1647 4 : bits_alpha = clamp_reduced_bits(bits_alpha);
1648 4 : boost_bits_rgb = clamp_reduced_bits(boost_bits_rgb);
1649 4 : boost_bits_alpha = clamp_reduced_bits(boost_bits_alpha);
1650 :
1651 4 : if ((!rgba) || width == 0 || height == 0 ||
1652 0 : (bits_rgb >= PNGX_FULL_CHANNEL_BITS && bits_alpha >= PNGX_FULL_CHANNEL_BITS)) {
1653 0 : return true;
1654 : }
1655 :
1656 4 : row_stride = (size_t)width * PNGX_RGBA_CHANNELS;
1657 4 : err_curr = (float *)calloc(row_stride, sizeof(float));
1658 4 : err_next = (float *)calloc(row_stride, sizeof(float));
1659 4 : if (!err_curr || !err_next) {
1660 0 : free(err_curr);
1661 0 : free(err_next);
1662 0 : colopresso_log(CPRES_LOG_LEVEL_ERROR, "PNGX: Reduced RGBA32 dither allocation failed");
1663 :
1664 0 : return false;
1665 : }
1666 :
1667 2820 : for (y = 0; y < height; ++y) {
1668 2816 : left_to_right = ((y & 1) == 0);
1669 2816 : memset(err_next, 0, row_stride * sizeof(float));
1670 :
1671 2816 : if (left_to_right) {
1672 1213824 : for (x = 0; x < width; ++x) {
1673 1212416 : process_custom_bitdepth_pixel(rgba, width, height, x, y, bits_rgb, bits_alpha,
1674 : boost_bits_rgb, boost_bits_alpha, dither_level,
1675 : importance_map, pixel_count, err_curr, err_next, true,
1676 : bit_hint_map, bit_hint_len);
1677 : }
1678 : } else {
1679 1408 : x = width;
1680 1213824 : while (x-- > 0) {
1681 1212416 : process_custom_bitdepth_pixel(rgba, width, height, x, y, bits_rgb, bits_alpha,
1682 : boost_bits_rgb, boost_bits_alpha, dither_level,
1683 : importance_map, pixel_count, err_curr, err_next,
1684 : false, bit_hint_map, bit_hint_len);
1685 : }
1686 : }
1687 :
1688 2816 : tmp = err_curr;
1689 2816 : err_curr = err_next;
1690 2816 : err_next = tmp;
1691 : }
1692 :
1693 4 : free(err_curr);
1694 4 : free(err_next);
1695 :
1696 4 : return true;
1697 : }
1698 :
1699 9 : static inline bool reduce_rgba_custom_bitdepth(uint32_t thread_count, uint8_t *rgba,
1700 : png_uint_32 width, png_uint_32 height,
1701 : uint8_t bits_rgb, uint8_t bits_alpha,
1702 : float dither_level, const uint8_t *importance_map,
1703 : size_t importance_map_len,
1704 : pngx_quant_support_t *support)
1705 : {
1706 9 : uint8_t boost_bits_rgb, boost_bits_alpha, *bit_hint_map = NULL;
1707 9 : size_t bit_hint_len = 0, pixel_count, hint_len;
1708 : bool need_rgb, need_alpha;
1709 :
1710 9 : if (!rgba || width == 0 || height == 0) {
1711 0 : return true;
1712 : }
1713 :
1714 9 : bits_rgb = clamp_reduced_bits(bits_rgb);
1715 9 : bits_alpha = clamp_reduced_bits(bits_alpha);
1716 9 : need_rgb = bits_rgb < PNGX_FULL_CHANNEL_BITS;
1717 9 : need_alpha = bits_alpha < PNGX_FULL_CHANNEL_BITS;
1718 9 : if (!need_rgb && !need_alpha) {
1719 0 : return true;
1720 : }
1721 :
1722 9 : pixel_count = (size_t)width * (size_t)height;
1723 9 : if (!importance_map || importance_map_len < pixel_count) {
1724 5 : importance_map = NULL;
1725 : }
1726 :
1727 9 : boost_bits_rgb = (importance_map && bits_rgb < PNGX_FULL_CHANNEL_BITS)
1728 4 : ? clamp_reduced_bits((uint8_t)(bits_rgb + 3))
1729 : : bits_rgb;
1730 9 : boost_bits_alpha = (importance_map && bits_alpha < PNGX_FULL_CHANNEL_BITS)
1731 4 : ? clamp_reduced_bits((uint8_t)(bits_alpha + 2))
1732 : : bits_alpha;
1733 :
1734 9 : if (support) {
1735 9 : free(support->bit_hint_map);
1736 9 : support->bit_hint_map = NULL;
1737 9 : support->bit_hint_len = 0;
1738 :
1739 9 : if (pixel_count > 0) {
1740 9 : support->bit_hint_map = (uint8_t *)malloc(pixel_count);
1741 9 : if (support->bit_hint_map) {
1742 9 : support->bit_hint_len = pixel_count;
1743 9 : bit_hint_map = support->bit_hint_map;
1744 9 : bit_hint_len = support->bit_hint_len;
1745 : }
1746 : }
1747 : }
1748 :
1749 9 : if (dither_level > 0.0f) {
1750 4 : if (!reduce_rgba_custom_bitdepth_dither(
1751 : rgba, width, height, bits_rgb, bits_alpha, boost_bits_rgb, boost_bits_alpha,
1752 : dither_level, importance_map, pixel_count, bit_hint_map, bit_hint_len)) {
1753 0 : return false;
1754 : }
1755 : } else {
1756 5 : hint_len = importance_map ? pixel_count : 0;
1757 5 : reduce_rgba_custom_bitdepth_simple(thread_count, rgba, pixel_count, bits_rgb, bits_alpha,
1758 : importance_map, hint_len, boost_bits_rgb,
1759 : boost_bits_alpha, bit_hint_map, bit_hint_len);
1760 : }
1761 :
1762 9 : return true;
1763 : }
1764 :
1765 9 : static inline uint32_t collect_alpha_levels(const pngx_rgba_image_t *image, float *non_opaque_ratio)
1766 : {
1767 9 : uint32_t unique = 0, non_opaque = 0;
1768 : uint8_t alpha;
1769 : size_t i;
1770 9 : bool level_used[256] = {false};
1771 :
1772 9 : if (!image || !image->rgba || image->pixel_count == 0) {
1773 0 : if (non_opaque_ratio) {
1774 0 : *non_opaque_ratio = 0.0f;
1775 : }
1776 :
1777 0 : return 0;
1778 : }
1779 :
1780 2491665 : for (i = 0; i < image->pixel_count; ++i) {
1781 2491656 : alpha = image->rgba[i * 4 + 3];
1782 2491656 : if (!level_used[alpha]) {
1783 932 : level_used[alpha] = true;
1784 932 : ++unique;
1785 : }
1786 :
1787 2491656 : if (alpha < 255) {
1788 2420117 : ++non_opaque;
1789 : }
1790 : }
1791 :
1792 9 : if (non_opaque_ratio) {
1793 9 : *non_opaque_ratio =
1794 9 : (image->pixel_count > 0) ? ((float)non_opaque / (float)image->pixel_count) : 0.0f;
1795 : }
1796 :
1797 9 : return unique;
1798 : }
1799 :
1800 9 : static inline uint8_t bits_for_level_count(uint32_t levels)
1801 : {
1802 9 : uint32_t capacity = 1;
1803 9 : uint8_t bits = 1;
1804 :
1805 9 : if (levels == 0) {
1806 0 : return 1;
1807 : }
1808 :
1809 41 : while (capacity < levels && bits < COLOPRESSO_PNGX_REDUCED_BITS_MAX) {
1810 32 : ++bits;
1811 32 : capacity <<= 1;
1812 : }
1813 :
1814 9 : return clamp_reduced_bits(bits);
1815 : }
1816 :
1817 9 : static inline void tune_reduced_bitdepth(const pngx_rgba_image_t *image,
1818 : const pngx_image_stats_t *stats, uint8_t *bits_rgb,
1819 : uint8_t *bits_alpha)
1820 : {
1821 : uint32_t alpha_levels;
1822 : uint8_t tuned_rgb, tuned_alpha, level_bits, next_alpha;
1823 : float gradient, saturation, vibrant, opaque, translucent, non_opaque_ratio;
1824 :
1825 9 : if (!bits_rgb || !bits_alpha) {
1826 0 : return;
1827 : }
1828 :
1829 9 : tuned_rgb = clamp_reduced_bits(*bits_rgb);
1830 9 : tuned_alpha = clamp_reduced_bits(*bits_alpha);
1831 9 : gradient = stats ? stats->gradient_mean : PNGX_REDUCED_TUNE_DEFAULT_GRADIENT;
1832 9 : saturation = stats ? stats->saturation_mean : PNGX_REDUCED_TUNE_DEFAULT_SATURATION;
1833 9 : vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_TUNE_DEFAULT_VIBRANT;
1834 9 : opaque = stats ? stats->opaque_ratio : PNGX_REDUCED_TUNE_DEFAULT_OPAQUE;
1835 9 : translucent = stats ? stats->translucent_ratio : PNGX_REDUCED_TUNE_DEFAULT_TRANSLUCENT;
1836 :
1837 9 : if (gradient < PNGX_REDUCED_TUNE_FLAT_GRADIENT_THRESHOLD &&
1838 0 : saturation < PNGX_REDUCED_TUNE_FLAT_SATURATION_THRESHOLD &&
1839 0 : vibrant < PNGX_REDUCED_TUNE_FLAT_VIBRANT_THRESHOLD && tuned_rgb > 3) {
1840 0 : --tuned_rgb;
1841 0 : if (gradient < PNGX_REDUCED_TUNE_VERY_FLAT_GRADIENT &&
1842 0 : saturation < PNGX_REDUCED_TUNE_VERY_FLAT_SATURATION && tuned_rgb > 3) {
1843 0 : --tuned_rgb;
1844 : }
1845 :
1846 0 : if (tuned_rgb < 3) {
1847 0 : tuned_rgb = 3;
1848 : }
1849 : }
1850 :
1851 9 : non_opaque_ratio = 0.0f;
1852 9 : alpha_levels = collect_alpha_levels(image, &non_opaque_ratio);
1853 9 : if (alpha_levels > 0) {
1854 9 : level_bits = bits_for_level_count(alpha_levels);
1855 9 : if (alpha_levels <= PNGX_REDUCED_ALPHA_LEVEL_LIMIT_FEW &&
1856 5 : non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_FEW && tuned_alpha > level_bits) {
1857 3 : tuned_alpha = (level_bits < 2) ? 2 : level_bits;
1858 6 : } else if (alpha_levels <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT &&
1859 2 : non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_LOW &&
1860 0 : tuned_alpha > (uint8_t)(level_bits + 1)) {
1861 0 : tuned_alpha = (uint8_t)(level_bits + 1);
1862 6 : } else if (alpha_levels <= 16 && non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_MINIMAL &&
1863 0 : tuned_alpha > (uint8_t)(level_bits + 2)) {
1864 0 : tuned_alpha = (uint8_t)(level_bits + 2);
1865 : }
1866 :
1867 9 : if (opaque > PNGX_REDUCED_ALPHA_OPAQUE_LIMIT && level_bits <= 2 && tuned_alpha > 2) {
1868 0 : tuned_alpha = 2;
1869 9 : } else if (translucent < PNGX_REDUCED_ALPHA_TRANSLUCENT_LIMIT && tuned_alpha > level_bits) {
1870 3 : next_alpha = (uint8_t)(tuned_alpha - 1);
1871 3 : if (next_alpha < level_bits) {
1872 0 : next_alpha = level_bits;
1873 : }
1874 :
1875 3 : tuned_alpha = next_alpha;
1876 : }
1877 : }
1878 :
1879 9 : if (tuned_alpha < COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
1880 0 : tuned_alpha = COLOPRESSO_PNGX_REDUCED_BITS_MIN;
1881 : }
1882 :
1883 9 : *bits_rgb = tuned_rgb;
1884 9 : *bits_alpha = tuned_alpha;
1885 : }
1886 :
1887 9 : static inline bool apply_reduced_rgba32_prepass(pngx_rgba_image_t *image,
1888 : const pngx_options_t *opts,
1889 : pngx_quant_support_t *support,
1890 : pngx_image_stats_t *stats)
1891 : {
1892 9 : const uint8_t *importance = NULL;
1893 9 : uint8_t bits_rgb = COLOPRESSO_PNGX_DEFAULT_REDUCED_BITS_RGB,
1894 9 : bits_alpha = COLOPRESSO_PNGX_DEFAULT_REDUCED_ALPHA_BITS;
1895 9 : size_t importance_len = 0;
1896 9 : float dither = 0.0f;
1897 :
1898 9 : if (!image || !image->rgba || image->pixel_count == 0) {
1899 0 : return true;
1900 : }
1901 :
1902 9 : if (opts) {
1903 9 : bits_rgb = clamp_reduced_bits(opts->lossy_reduced_bits_rgb);
1904 9 : bits_alpha = clamp_reduced_bits(opts->lossy_reduced_alpha_bits);
1905 :
1906 9 : if (opts->lossy_dither_auto) {
1907 1 : dither = resolve_quant_dither(opts, stats);
1908 : } else {
1909 8 : dither = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
1910 : }
1911 :
1912 9 : if (support && support->importance_map &&
1913 4 : support->importance_map_len >= image->pixel_count) {
1914 4 : importance = support->importance_map;
1915 4 : importance_len = support->importance_map_len;
1916 : }
1917 : }
1918 :
1919 9 : return reduce_rgba_custom_bitdepth(opts->thread_count, image->rgba, image->width, image->height,
1920 : bits_rgb, bits_alpha, dither, importance, importance_len,
1921 : support);
1922 : }
1923 :
1924 552 : static inline void head_heap_sift_up(uint64_t *heap, size_t index)
1925 : {
1926 : uint64_t parent_value;
1927 : size_t parent;
1928 :
1929 868 : while (index > 0) {
1930 843 : parent = (index - 1) / 2;
1931 843 : parent_value = heap[parent];
1932 :
1933 843 : if (parent_value <= heap[index]) {
1934 527 : break;
1935 : }
1936 :
1937 316 : heap[parent] = heap[index];
1938 316 : heap[index] = parent_value;
1939 316 : index = parent;
1940 : }
1941 552 : }
1942 :
1943 1528 : static inline void head_heap_sift_down(uint64_t *heap, size_t size, size_t index)
1944 : {
1945 : uint64_t tmp;
1946 : size_t left, right, smallest;
1947 :
1948 : while (true) {
1949 7556 : left = index * 2 + 1;
1950 7556 : right = left + 1;
1951 7556 : smallest = index;
1952 :
1953 7556 : if (left < size && heap[left] < heap[smallest]) {
1954 5567 : smallest = left;
1955 : }
1956 :
1957 7556 : if (right < size && heap[right] < heap[smallest]) {
1958 2877 : smallest = right;
1959 : }
1960 :
1961 7556 : if (smallest == index) {
1962 1528 : break;
1963 : }
1964 :
1965 6028 : tmp = heap[index];
1966 6028 : heap[index] = heap[smallest];
1967 6028 : heap[smallest] = tmp;
1968 6028 : index = smallest;
1969 : }
1970 1528 : }
1971 :
1972 12 : static inline float histogram_head_dominance(const color_histogram_t *hist, size_t head_limit)
1973 : {
1974 12 : uint64_t heap[PNGX_REDUCED_HEAD_DOMINANCE_LIMIT], weight, total_weight = 0, head_sum = 0;
1975 12 : size_t heap_size = 0, capacity, i;
1976 :
1977 12 : if (!hist || hist->count == 0 || head_limit == 0) {
1978 0 : return 0.0f;
1979 : }
1980 :
1981 12 : capacity = head_limit;
1982 12 : if (capacity > PNGX_REDUCED_HEAD_DOMINANCE_LIMIT) {
1983 0 : capacity = PNGX_REDUCED_HEAD_DOMINANCE_LIMIT;
1984 : }
1985 :
1986 28800 : for (i = 0; i < hist->count; ++i) {
1987 28788 : weight = hist->entries[i].count ? hist->entries[i].count : 1;
1988 28788 : total_weight += weight;
1989 :
1990 28788 : if (capacity == 0) {
1991 0 : continue;
1992 : }
1993 :
1994 28788 : if (heap_size < capacity) {
1995 552 : heap[heap_size] = weight;
1996 552 : ++heap_size;
1997 552 : head_heap_sift_up(heap, heap_size - 1);
1998 28236 : } else if (weight > heap[0]) {
1999 1528 : heap[0] = weight;
2000 1528 : head_heap_sift_down(heap, heap_size, 0);
2001 : }
2002 : }
2003 :
2004 12 : if (total_weight == 0 || heap_size == 0) {
2005 0 : return 0.0f;
2006 : }
2007 :
2008 564 : for (i = 0; i < heap_size; ++i) {
2009 552 : head_sum += heap[i];
2010 : }
2011 :
2012 12 : return (float)head_sum / (float)total_weight;
2013 : }
2014 :
2015 12 : static inline float histogram_low_weight_ratio(const color_histogram_t *hist,
2016 : uint32_t weight_threshold)
2017 : {
2018 : const color_entry_t *entry;
2019 12 : uint64_t total_weight = 0, low_weight = 0, weight;
2020 : size_t i;
2021 :
2022 12 : if (!hist || hist->count == 0 || weight_threshold == 0) {
2023 0 : return 0.0f;
2024 : }
2025 :
2026 28800 : for (i = 0; i < hist->count; ++i) {
2027 28788 : entry = &hist->entries[i];
2028 28788 : weight = entry->count ? entry->count : 1;
2029 28788 : total_weight += weight;
2030 :
2031 28788 : if (entry->count <= weight_threshold) {
2032 17648 : low_weight += weight;
2033 : }
2034 : }
2035 :
2036 12 : if (total_weight == 0) {
2037 0 : return 0.0f;
2038 : }
2039 :
2040 12 : return (float)low_weight / (float)total_weight;
2041 : }
2042 :
2043 12 : static inline float histogram_detail_pressure(const color_histogram_t *hist, uint8_t base_bits_rgb,
2044 : uint8_t base_bits_alpha)
2045 : {
2046 : const color_entry_t *entry;
2047 12 : uint64_t total_weight = 0, detail_weight = 0, weight;
2048 : size_t i;
2049 :
2050 12 : if (!hist || hist->count == 0) {
2051 0 : return 0.0f;
2052 : }
2053 :
2054 28800 : for (i = 0; i < hist->count; ++i) {
2055 28788 : entry = &hist->entries[i];
2056 28788 : weight = entry->count ? entry->count : 1;
2057 28788 : total_weight += weight;
2058 :
2059 28788 : if (entry->detail_bits_rgb > base_bits_rgb || entry->detail_bits_alpha > base_bits_alpha) {
2060 13338 : detail_weight += weight;
2061 : }
2062 : }
2063 :
2064 12 : if (total_weight == 0) {
2065 0 : return 0.0f;
2066 : }
2067 :
2068 12 : return (float)detail_weight / (float)total_weight;
2069 : }
2070 :
2071 12 : static inline float stats_flatness_factor(const pngx_image_stats_t *stats)
2072 : {
2073 12 : float gradient = stats ? stats->gradient_mean : PNGX_REDUCED_STATS_FLAT_DEFAULT_GRADIENT,
2074 12 : saturation = stats ? stats->saturation_mean : PNGX_REDUCED_STATS_FLAT_DEFAULT_SATURATION,
2075 12 : vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_STATS_FLAT_DEFAULT_VIBRANT,
2076 12 : gradient_term = clamp_float((PNGX_REDUCED_STATS_FLAT_GRADIENT_REF - gradient) /
2077 : PNGX_REDUCED_STATS_FLAT_GRADIENT_REF,
2078 : 0.0f, 1.0f),
2079 12 : saturation_term = clamp_float((PNGX_REDUCED_STATS_FLAT_SATURATION_REF - saturation) /
2080 : PNGX_REDUCED_STATS_FLAT_SATURATION_REF,
2081 : 0.0f, 1.0f),
2082 12 : vibrant_term = clamp_float((PNGX_REDUCED_VIBRANT_RATIO_LOW - vibrant) /
2083 : PNGX_REDUCED_VIBRANT_RATIO_LOW,
2084 : 0.0f, 1.0f);
2085 :
2086 12 : return (gradient_term * PNGX_REDUCED_STATS_FLAT_GRADIENT_WEIGHT) +
2087 24 : (saturation_term * PNGX_REDUCED_STATS_FLAT_SATURATION_WEIGHT) +
2088 12 : (vibrant_term * PNGX_REDUCED_STATS_FLAT_VIBRANT_WEIGHT);
2089 : }
2090 :
2091 12 : static inline float stats_alpha_simplicity(const pngx_image_stats_t *stats)
2092 : {
2093 12 : float opaque = stats ? stats->opaque_ratio : PNGX_REDUCED_ALPHA_SIMPLE_DEFAULT_OPAQUE,
2094 12 : translucent =
2095 12 : stats ? stats->translucent_ratio : PNGX_REDUCED_ALPHA_SIMPLE_DEFAULT_TRANSLUCENT,
2096 12 : opaque_term = clamp_float((opaque - PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_REF) /
2097 : PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_RANGE,
2098 : 0.0f, 1.0f),
2099 12 : translucent_term = clamp_float((PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_REF - translucent) /
2100 : PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_RANGE,
2101 : 0.0f, 1.0f);
2102 :
2103 24 : return (opaque_term * PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_WEIGHT) +
2104 12 : (translucent_term * PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_WEIGHT);
2105 : }
2106 :
2107 9 : static inline uint32_t resolve_reduced_passthrough_threshold(uint32_t grid_cap,
2108 : const pngx_image_stats_t *stats)
2109 : {
2110 : uint32_t threshold;
2111 9 : float gradient = stats ? stats->gradient_mean : PNGX_REDUCED_PASSTHROUGH_DEFAULT_GRADIENT,
2112 9 : saturation = stats ? stats->saturation_mean : PNGX_REDUCED_PASSTHROUGH_DEFAULT_SATURATION,
2113 9 : vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_PASSTHROUGH_DEFAULT_VIBRANT,
2114 9 : ratio = PNGX_REDUCED_PASSTHROUGH_RATIO_BASE +
2115 9 : clamp_float((gradient * PNGX_REDUCED_PASSTHROUGH_GRADIENT_WEIGHT) +
2116 9 : (saturation * PNGX_REDUCED_PASSTHROUGH_SATURATION_WEIGHT) +
2117 9 : (vibrant * PNGX_REDUCED_PASSTHROUGH_VIBRANT_WEIGHT),
2118 9 : 0.0f, 1.0f) *
2119 : PNGX_REDUCED_PASSTHROUGH_RATIO_GAIN;
2120 :
2121 9 : if (ratio > PNGX_REDUCED_PASSTHROUGH_RATIO_CAP) {
2122 0 : ratio = PNGX_REDUCED_PASSTHROUGH_RATIO_CAP;
2123 : }
2124 :
2125 9 : threshold = (uint32_t)((float)grid_cap * ratio + PNGX_REDUCED_ROUNDING_OFFSET);
2126 9 : if (threshold < PNGX_REDUCED_RGBA32_PASSTHROUGH_MIN_COLORS) {
2127 1 : threshold = PNGX_REDUCED_RGBA32_PASSTHROUGH_MIN_COLORS;
2128 : }
2129 :
2130 9 : if (threshold > grid_cap) {
2131 1 : threshold = grid_cap;
2132 : }
2133 :
2134 9 : return threshold;
2135 : }
2136 :
2137 6 : static inline uint32_t compute_auto_trim_limit(const color_histogram_t *hist, size_t pixel_count,
2138 : uint32_t actual_colors, uint8_t bits_rgb,
2139 : uint8_t bits_alpha, const pngx_image_stats_t *stats)
2140 : {
2141 : uint32_t low_weight_threshold, limit;
2142 : double density;
2143 : float low_weight_ratio, detail_pressure, head_dominance, flatness, alpha_simple, density_f,
2144 6 : trim = 0.0f, head_term, tail_term;
2145 :
2146 6 : if (!hist || hist->count == 0 || pixel_count == 0 ||
2147 : actual_colors <=
2148 : (uint32_t)(COLOPRESSO_PNGX_REDUCED_COLORS_MIN + PNGX_REDUCED_TRIM_MIN_COLOR_MARGIN)) {
2149 2 : return 0;
2150 : }
2151 :
2152 4 : density = (double)hist->count / (double)pixel_count;
2153 4 : density_f = (float)density;
2154 4 : low_weight_threshold = (uint32_t)(pixel_count / PNGX_REDUCED_LOW_WEIGHT_DIVISOR);
2155 4 : if (low_weight_threshold < PNGX_REDUCED_LOW_WEIGHT_MIN) {
2156 1 : low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MIN;
2157 3 : } else if (low_weight_threshold > PNGX_REDUCED_LOW_WEIGHT_MAX) {
2158 0 : low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MAX;
2159 : }
2160 :
2161 4 : low_weight_ratio = histogram_low_weight_ratio(hist, low_weight_threshold);
2162 4 : detail_pressure = histogram_detail_pressure(hist, bits_rgb, bits_alpha);
2163 4 : head_dominance = histogram_head_dominance(hist, PNGX_REDUCED_HEAD_DOMINANCE_LIMIT);
2164 4 : flatness = stats_flatness_factor(stats);
2165 4 : alpha_simple = stats_alpha_simplicity(stats);
2166 :
2167 4 : if (head_dominance > PNGX_REDUCED_TRIM_HEAD_DOMINANCE_THRESHOLD &&
2168 : detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_HEAD_LIMIT) {
2169 0 : head_term = clamp_float(
2170 0 : (head_dominance - PNGX_REDUCED_TRIM_HEAD_DOMINANCE_THRESHOLD) *
2171 0 : (PNGX_REDUCED_TRIM_HEAD_WEIGHT + flatness * PNGX_REDUCED_TRIM_FLATNESS_WEIGHT),
2172 : 0.0f, PNGX_REDUCED_TRIM_HEAD_CLAMP);
2173 0 : trim += head_term;
2174 : }
2175 :
2176 4 : if (low_weight_ratio > PNGX_REDUCED_TRIM_TAIL_RATIO_THRESHOLD &&
2177 : detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_TAIL_LIMIT) {
2178 2 : tail_term = (low_weight_ratio - PNGX_REDUCED_TRIM_TAIL_RATIO_THRESHOLD) *
2179 2 : (PNGX_REDUCED_TRIM_TAIL_BASE_WEIGHT +
2180 2 : PNGX_REDUCED_TRIM_TAIL_DETAIL_WEIGHT * (1.0f - detail_pressure));
2181 2 : trim += clamp_float(tail_term, 0.0f, PNGX_REDUCED_TRIM_TAIL_CLAMP);
2182 : }
2183 :
2184 4 : if (density_f < PNGX_REDUCED_TRIM_DENSITY_THRESHOLD) {
2185 3 : trim += clamp_float((PNGX_REDUCED_TRIM_DENSITY_THRESHOLD - density_f) *
2186 : PNGX_REDUCED_TRIM_DENSITY_SCALE,
2187 : 0.0f, PNGX_REDUCED_TRIM_DENSITY_CLAMP);
2188 : }
2189 :
2190 4 : if (alpha_simple > PNGX_REDUCED_TRIM_ALPHA_SIMPLE_THRESHOLD &&
2191 : detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_HEAD_LIMIT) {
2192 2 : trim += clamp_float(alpha_simple * PNGX_REDUCED_TRIM_ALPHA_SIMPLE_SCALE, 0.0f,
2193 : PNGX_REDUCED_TRIM_ALPHA_SIMPLE_CLAMP);
2194 : }
2195 :
2196 4 : if (flatness > PNGX_REDUCED_TRIM_FLATNESS_THRESHOLD &&
2197 : detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_FLAT_LIMIT) {
2198 2 : trim += clamp_float((flatness - PNGX_REDUCED_TRIM_FLATNESS_THRESHOLD) *
2199 : PNGX_REDUCED_TRIM_FLATNESS_SCALE,
2200 : 0.0f, PNGX_REDUCED_TRIM_FLATNESS_CLAMP);
2201 : }
2202 :
2203 4 : trim = clamp_float(trim, 0.0f, PNGX_REDUCED_TRIM_TOTAL_CLAMP);
2204 4 : if (trim < PNGX_REDUCED_TRIM_MIN_TRIGGER) {
2205 0 : return 0;
2206 : }
2207 :
2208 4 : limit = (uint32_t)((float)actual_colors * (1.0f - trim) + PNGX_REDUCED_ROUNDING_OFFSET);
2209 4 : if (limit < (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
2210 0 : limit = (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
2211 : }
2212 :
2213 4 : if (actual_colors <= limit || (actual_colors - limit) < PNGX_REDUCED_TRIM_MIN_COLOR_DIFF) {
2214 2 : return 0;
2215 : }
2216 :
2217 2 : return limit;
2218 : }
2219 :
2220 8 : static inline uint32_t resolve_reduced_rgba32_target(const color_histogram_t *hist,
2221 : size_t pixel_count, int32_t hint,
2222 : uint8_t bits_rgb, uint8_t bits_alpha,
2223 : const pngx_image_stats_t *stats)
2224 : {
2225 8 : uint64_t rgb_cap = 0, alpha_cap = 0;
2226 8 : uint32_t target, grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX, low_weight_threshold;
2227 8 : size_t unique_colors = hist ? hist->count : 0, unlocked = hist ? hist->unlocked_count : 0;
2228 : double base, density;
2229 8 : float low_weight_ratio = 0.0f, detail_pressure = 0.0f, reduction_scale, boost_scale,
2230 8 : flatness_score = 0.0f, alpha_simple = 0.0f, head_dominance = 0.0f, tail_cut,
2231 : dominance_cut, gentle_cut, applied_cut, tail_gain, dominance_gain, relief,
2232 : gradient_relief, softness_relief, detail_relief, combined_cut, density_gap,
2233 : flatness_reduction, alpha_reduction, span_ratio;
2234 :
2235 8 : if (unique_colors == 0) {
2236 0 : return 0;
2237 : }
2238 :
2239 8 : density = (pixel_count > 0) ? ((double)unique_colors / (double)pixel_count) : 0.0;
2240 :
2241 8 : bits_rgb = clamp_reduced_bits(bits_rgb);
2242 8 : bits_alpha = clamp_reduced_bits(bits_alpha);
2243 :
2244 8 : if (bits_rgb < 8) {
2245 6 : rgb_cap = (uint64_t)1 << (bits_rgb * 3);
2246 : }
2247 :
2248 8 : if (bits_alpha < 8) {
2249 8 : alpha_cap = (uint64_t)1 << bits_alpha;
2250 : }
2251 :
2252 8 : if (rgb_cap == 0) {
2253 2 : rgb_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2254 : }
2255 :
2256 8 : if (alpha_cap == 0) {
2257 0 : alpha_cap = 1;
2258 : }
2259 :
2260 8 : if (rgb_cap > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
2261 0 : rgb_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2262 : }
2263 :
2264 8 : if (alpha_cap > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
2265 0 : alpha_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2266 : }
2267 :
2268 8 : if (rgb_cap * alpha_cap < (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
2269 2 : grid_cap = (uint32_t)(rgb_cap * alpha_cap);
2270 : }
2271 :
2272 8 : if (hint > 0) {
2273 2 : target = (uint32_t)hint;
2274 2 : if (target < COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
2275 0 : target = COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
2276 : }
2277 :
2278 2 : if (target > COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
2279 0 : target = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2280 : }
2281 : } else {
2282 6 : target = (uint32_t)unique_colors;
2283 :
2284 6 : if (unique_colors > PNGX_REDUCED_TARGET_UNIQUE_COLOR_THRESHOLD) {
2285 3 : base = sqrt((double)unique_colors) * PNGX_REDUCED_TARGET_UNIQUE_BASE_SCALE;
2286 :
2287 3 : if (density < PNGX_REDUCED_TARGET_DENSITY_LOW_THRESHOLD) {
2288 3 : base *= PNGX_REDUCED_TARGET_DENSITY_LOW_SCALE;
2289 0 : } else if (density > PNGX_REDUCED_TARGET_DENSITY_HIGH_THRESHOLD) {
2290 0 : base *= PNGX_REDUCED_TARGET_DENSITY_HIGH_SCALE;
2291 : }
2292 :
2293 3 : if (base < PNGX_REDUCED_TARGET_BASE_MIN) {
2294 1 : base = PNGX_REDUCED_TARGET_BASE_MIN;
2295 : }
2296 :
2297 3 : if (base > (double)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
2298 0 : base = (double)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2299 : }
2300 :
2301 3 : target = (uint32_t)(base + PNGX_REDUCED_ROUNDING_OFFSET);
2302 : }
2303 : }
2304 :
2305 8 : if (pixel_count > 0 && hist) {
2306 8 : low_weight_threshold = (uint32_t)(pixel_count / PNGX_REDUCED_LOW_WEIGHT_DIVISOR);
2307 8 : if (low_weight_threshold < PNGX_REDUCED_LOW_WEIGHT_MIN) {
2308 3 : low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MIN;
2309 5 : } else if (low_weight_threshold > PNGX_REDUCED_LOW_WEIGHT_MAX) {
2310 0 : low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MAX;
2311 : }
2312 :
2313 8 : low_weight_ratio = histogram_low_weight_ratio(hist, low_weight_threshold);
2314 8 : detail_pressure = histogram_detail_pressure(hist, bits_rgb, bits_alpha);
2315 8 : head_dominance = histogram_head_dominance(hist, PNGX_REDUCED_TARGET_HEAD_DOMINANCE_BUCKETS);
2316 :
2317 8 : if (low_weight_ratio > PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_START) {
2318 4 : reduction_scale =
2319 : 1.0f -
2320 4 : clamp_float((low_weight_ratio - PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_START) *
2321 4 : (PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_BASE +
2322 4 : (PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_DETAIL *
2323 4 : (1.0f - detail_pressure))),
2324 : 0.0f, PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_CLAMP);
2325 4 : target = (uint32_t)((float)target * reduction_scale + PNGX_REDUCED_ROUNDING_OFFSET);
2326 : }
2327 :
2328 8 : if (low_weight_ratio > PNGX_REDUCED_TARGET_TAIL_RATIO_THRESHOLD &&
2329 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_TAIL_LIMIT) {
2330 3 : tail_cut = clamp_float((low_weight_ratio - PNGX_REDUCED_TARGET_TAIL_RATIO_THRESHOLD) *
2331 3 : (PNGX_REDUCED_TARGET_TAIL_WIDTH_BASE - detail_pressure) *
2332 : PNGX_REDUCED_TARGET_TAIL_WIDTH_SCALE,
2333 : 0.0f, PNGX_REDUCED_TARGET_TAIL_CUT_CLAMP);
2334 3 : target = (uint32_t)((float)target * (1.0f - tail_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
2335 : }
2336 :
2337 8 : if (detail_pressure > PNGX_REDUCED_TARGET_DETAIL_PRESSURE_BOOST) {
2338 4 : boost_scale =
2339 4 : 1.0f + clamp_float((detail_pressure - PNGX_REDUCED_TARGET_DETAIL_PRESSURE_BOOST) *
2340 : PNGX_REDUCED_TARGET_DETAIL_BOOST_SCALE,
2341 : 0.0f, PNGX_REDUCED_TARGET_DETAIL_BOOST_CLAMP);
2342 4 : target = (uint32_t)((float)target * boost_scale + PNGX_REDUCED_ROUNDING_OFFSET);
2343 : }
2344 :
2345 8 : if (head_dominance > PNGX_REDUCED_TARGET_HEAD_DOMINANCE_THRESHOLD &&
2346 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_HEAD_LIMIT) {
2347 0 : gradient_relief =
2348 : stats
2349 2 : ? clamp_float((PNGX_REDUCED_TARGET_GRADIENT_RELIEF_REF - stats->gradient_mean) /
2350 : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_REF,
2351 : 0.0f, 1.0f)
2352 2 : : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_DEFAULT;
2353 : dominance_cut =
2354 2 : clamp_float((head_dominance - PNGX_REDUCED_TARGET_HEAD_DOMINANCE_THRESHOLD) *
2355 2 : (PNGX_REDUCED_TARGET_HEAD_CUT_BASE +
2356 2 : PNGX_REDUCED_TARGET_HEAD_CUT_RELIEF * gradient_relief),
2357 : 0.0f, PNGX_REDUCED_TARGET_HEAD_CUT_CLAMP);
2358 2 : target =
2359 2 : (uint32_t)((float)target * (1.0f - dominance_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
2360 : }
2361 :
2362 8 : if (head_dominance > PNGX_REDUCED_TARGET_HEAD_DOMINANCE_STRONG &&
2363 2 : low_weight_ratio > PNGX_REDUCED_TARGET_LOW_WEIGHT_RATIO_STRONG &&
2364 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_STRONG_LIMIT) {
2365 0 : gradient_relief =
2366 2 : stats ? clamp_float((PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_REF -
2367 2 : stats->gradient_mean) /
2368 : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_REF,
2369 : 0.0f, 1.0f)
2370 2 : : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_DEFAULT;
2371 0 : softness_relief = stats ? clamp_float((PNGX_REDUCED_TARGET_SATURATION_RELIEF_REF -
2372 2 : stats->saturation_mean) /
2373 : PNGX_REDUCED_TARGET_SATURATION_RELIEF_REF,
2374 : 0.0f, 1.0f)
2375 2 : : PNGX_REDUCED_TARGET_SATURATION_RELIEF_DEFAULT;
2376 2 : relief = clamp_float((gradient_relief * PNGX_REDUCED_TARGET_RELIEF_GRADIENT_WEIGHT +
2377 2 : softness_relief * PNGX_REDUCED_TARGET_RELIEF_SATURATION_WEIGHT) *
2378 : PNGX_REDUCED_TARGET_RELIEF_SCALE,
2379 : 0.0f, PNGX_REDUCED_TARGET_RELIEF_CLAMP);
2380 : dominance_gain =
2381 2 : clamp_float((head_dominance - PNGX_REDUCED_TARGET_HEAD_DOMINANCE_STRONG) *
2382 : PNGX_REDUCED_TARGET_DOMINANCE_GAIN_SCALE,
2383 : 0.0f, PNGX_REDUCED_TARGET_DOMINANCE_GAIN_CLAMP);
2384 2 : tail_gain = clamp_float(
2385 2 : (low_weight_ratio - PNGX_REDUCED_TARGET_LOW_WEIGHT_RATIO_STRONG) *
2386 2 : (PNGX_REDUCED_TARGET_TAIL_GAIN_BASE +
2387 2 : PNGX_REDUCED_TARGET_TAIL_GAIN_RELIEF * (gradient_relief + softness_relief)),
2388 : 0.0f, PNGX_REDUCED_TARGET_TAIL_GAIN_CLAMP);
2389 2 : detail_relief = clamp_float((PNGX_REDUCED_TARGET_DETAIL_RELIEF_BASE - detail_pressure) *
2390 : PNGX_REDUCED_TARGET_DETAIL_RELIEF_SCALE,
2391 : 0.0f, PNGX_REDUCED_TARGET_DETAIL_RELIEF_CLAMP);
2392 : combined_cut =
2393 2 : clamp_float((dominance_gain + tail_gain) *
2394 2 : (PNGX_REDUCED_TARGET_COMBINED_CUT_BASE + relief + detail_relief),
2395 : 0.0f, PNGX_REDUCED_TARGET_COMBINED_CUT_CLAMP);
2396 2 : target =
2397 2 : (uint32_t)((float)target * (1.0f - combined_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
2398 : }
2399 :
2400 8 : if (density < PNGX_REDUCED_TARGET_DENSITY_THRESHOLD &&
2401 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_DENSITY_LIMIT) {
2402 1 : density_gap = clamp_float((PNGX_REDUCED_TARGET_DENSITY_THRESHOLD - (float)density) *
2403 : PNGX_REDUCED_TARGET_DENSITY_GAP_SCALE,
2404 : 0.0f, PNGX_REDUCED_TARGET_DENSITY_GAP_CLAMP);
2405 1 : target =
2406 1 : (uint32_t)((float)target * (1.0f - density_gap) + PNGX_REDUCED_ROUNDING_OFFSET);
2407 : }
2408 : }
2409 :
2410 8 : if (stats) {
2411 8 : flatness_score = stats_flatness_factor(stats);
2412 8 : if (flatness_score > PNGX_REDUCED_TARGET_FLATNESS_THRESHOLD &&
2413 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_FLAT_LIMIT) {
2414 4 : flatness_reduction = clamp_float(flatness_score * PNGX_REDUCED_TARGET_FLATNESS_SCALE,
2415 : 0.0f, PNGX_REDUCED_TARGET_FLATNESS_CLAMP);
2416 4 : target = (uint32_t)((float)target * (1.0f - flatness_reduction) +
2417 : PNGX_REDUCED_ROUNDING_OFFSET);
2418 : }
2419 :
2420 8 : alpha_simple = stats_alpha_simplicity(stats);
2421 8 : if (alpha_simple > PNGX_REDUCED_TARGET_ALPHA_SIMPLE_THRESHOLD &&
2422 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_ALPHA_LIMIT) {
2423 3 : alpha_reduction = clamp_float(alpha_simple * PNGX_REDUCED_TARGET_ALPHA_SIMPLE_SCALE,
2424 : 0.0f, PNGX_REDUCED_TARGET_ALPHA_SIMPLE_CLAMP);
2425 3 : target =
2426 3 : (uint32_t)((float)target * (1.0f - alpha_reduction) + PNGX_REDUCED_ROUNDING_OFFSET);
2427 : }
2428 : }
2429 :
2430 8 : if (hint <= 0 && unique_colors > PNGX_REDUCED_TARGET_GENTLE_MIN_COLORS &&
2431 1 : unique_colors <= PNGX_REDUCED_TARGET_GENTLE_MAX_COLORS &&
2432 : detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_GENTLE_LIMIT) {
2433 1 : span_ratio = ((float)unique_colors - (float)PNGX_REDUCED_TARGET_GENTLE_MIN_COLORS) /
2434 : PNGX_REDUCED_TARGET_GENTLE_COLOR_RANGE;
2435 1 : if (span_ratio < 0.0f) {
2436 0 : span_ratio = 0.0f;
2437 1 : } else if (span_ratio > 1.0f) {
2438 0 : span_ratio = 1.0f;
2439 : }
2440 :
2441 1 : gentle_cut = clamp_float((1.0f - detail_pressure) * PNGX_REDUCED_TARGET_GENTLE_SCALE, 0.0f,
2442 : PNGX_REDUCED_TARGET_GENTLE_CLAMP);
2443 1 : applied_cut = span_ratio * gentle_cut;
2444 1 : target = (uint32_t)((float)target * (1.0f - applied_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
2445 : }
2446 :
2447 8 : if (target > unique_colors) {
2448 0 : target = (uint32_t)unique_colors;
2449 : }
2450 :
2451 8 : if (target < (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
2452 1 : target = COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
2453 : }
2454 :
2455 8 : if (target > grid_cap) {
2456 0 : target = grid_cap;
2457 : }
2458 :
2459 8 : if (unlocked > 0 && target > (uint32_t)unlocked) {
2460 0 : target = (uint32_t)unlocked;
2461 : }
2462 :
2463 8 : if (unlocked == 0) {
2464 1 : target = (uint32_t)unique_colors;
2465 : }
2466 :
2467 8 : return target;
2468 : }
2469 :
2470 9 : extern bool pngx_quantize_reduced_rgba32(const uint8_t *png_data, size_t png_size,
2471 : const pngx_options_t *opts, uint32_t *resolved_target,
2472 : uint32_t *applied_colors, uint8_t **out_data,
2473 : size_t *out_size)
2474 : {
2475 : pngx_rgba_image_t image;
2476 9 : pngx_quant_support_t support = {0};
2477 : pngx_image_stats_t stats;
2478 : pngx_options_t tuned_opts;
2479 : color_histogram_t histogram;
2480 9 : uint32_t target = 0, actual = 0, manual_limit = 0, auto_trim_limit = 0, grid_cap;
2481 : uint8_t bits_rgb, bits_alpha;
2482 : size_t grid_unique, passthrough_threshold;
2483 9 : bool wrote, manual_target = false, success, auto_target, grid_passthrough,
2484 9 : auto_trim_applied = false;
2485 :
2486 9 : if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
2487 0 : return false;
2488 : }
2489 :
2490 9 : if (!load_rgba_image(png_data, png_size, &image)) {
2491 0 : return false;
2492 : }
2493 :
2494 9 : histogram.entries = NULL;
2495 9 : histogram.count = 0;
2496 9 : histogram.unlocked_count = 0;
2497 9 : tuned_opts = *opts;
2498 :
2499 9 : image_stats_reset(&stats);
2500 :
2501 9 : if (!prepare_quant_support(&image, &tuned_opts, &support, &stats)) {
2502 0 : rgba_image_reset(&image);
2503 :
2504 0 : return false;
2505 : }
2506 :
2507 9 : tune_reduced_bitdepth(&image, &stats, &tuned_opts.lossy_reduced_bits_rgb,
2508 : &tuned_opts.lossy_reduced_alpha_bits);
2509 9 : if (!apply_reduced_rgba32_prepass(&image, &tuned_opts, &support, &stats)) {
2510 0 : color_histogram_reset(&histogram);
2511 0 : quant_support_reset(&support);
2512 0 : rgba_image_reset(&image);
2513 :
2514 0 : return false;
2515 : }
2516 :
2517 9 : bits_rgb = clamp_reduced_bits(tuned_opts.lossy_reduced_bits_rgb);
2518 9 : bits_alpha = clamp_reduced_bits(tuned_opts.lossy_reduced_alpha_bits);
2519 9 : grid_cap = compute_grid_capacity(bits_rgb, bits_alpha);
2520 9 : if (grid_cap == 0) {
2521 0 : grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2522 : }
2523 :
2524 9 : if (opts->lossy_reduced_colors >= (int32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
2525 2 : manual_target = true;
2526 2 : manual_limit = (uint32_t)opts->lossy_reduced_colors;
2527 2 : if (manual_limit > grid_cap) {
2528 0 : manual_limit = grid_cap;
2529 : }
2530 : }
2531 :
2532 9 : grid_unique = count_unique_rgba(image.rgba, image.pixel_count);
2533 9 : grid_cap = compute_grid_capacity(bits_rgb, bits_alpha);
2534 9 : auto_target = (opts->lossy_reduced_colors <= 0);
2535 9 : grid_passthrough = false;
2536 :
2537 9 : if (grid_cap == 0) {
2538 0 : grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
2539 : }
2540 :
2541 9 : passthrough_threshold = resolve_reduced_passthrough_threshold(grid_cap, &stats);
2542 :
2543 9 : if (grid_unique > (size_t)grid_cap) {
2544 0 : grid_unique = (size_t)grid_cap;
2545 : }
2546 :
2547 9 : if (auto_target && grid_unique >= passthrough_threshold) {
2548 1 : grid_passthrough = true;
2549 : }
2550 :
2551 9 : if (grid_passthrough) {
2552 1 : wrote = false;
2553 1 : snap_rgba_image_to_bits(opts->thread_count, image.rgba, image.pixel_count, bits_rgb,
2554 : bits_alpha);
2555 :
2556 1 : if (resolved_target) {
2557 1 : *resolved_target = (uint32_t)grid_unique;
2558 : }
2559 :
2560 1 : if (applied_colors) {
2561 1 : *applied_colors = (uint32_t)grid_unique;
2562 : }
2563 :
2564 1 : wrote = create_rgba_png(image.rgba, image.pixel_count, image.width, image.height, out_data,
2565 : out_size);
2566 1 : if (wrote) {
2567 1 : colopresso_log(CPRES_LOG_LEVEL_DEBUG,
2568 : "PNGX: Reduced RGBA32 grid passthrough kept %zu colors (capacity=%u)",
2569 : grid_unique, grid_cap);
2570 : }
2571 :
2572 1 : quant_support_reset(&support);
2573 1 : rgba_image_reset(&image);
2574 :
2575 1 : return wrote;
2576 : }
2577 :
2578 8 : if (!build_color_histogram(&image, &tuned_opts, &support, &histogram)) {
2579 0 : quant_support_reset(&support);
2580 0 : rgba_image_reset(&image);
2581 :
2582 0 : return false;
2583 : }
2584 :
2585 8 : target = resolve_reduced_rgba32_target(
2586 8 : &histogram, image.pixel_count, opts->lossy_reduced_colors, bits_rgb, bits_alpha, &stats);
2587 8 : if (histogram.unlocked_count == 0 || target == 0) {
2588 1 : actual = (uint32_t)histogram.count;
2589 1 : snap_rgba_image_to_bits(opts->thread_count, image.rgba, image.pixel_count, bits_rgb,
2590 : bits_alpha);
2591 : } else {
2592 7 : if (!apply_reduced_rgba32_quantization(opts->thread_count, &histogram, &image, target,
2593 : bits_rgb, bits_alpha, &actual)) {
2594 0 : color_histogram_reset(&histogram);
2595 0 : quant_support_reset(&support);
2596 0 : rgba_image_reset(&image);
2597 :
2598 0 : return false;
2599 : }
2600 : }
2601 :
2602 8 : if (!manual_target) {
2603 6 : auto_trim_limit = compute_auto_trim_limit(&histogram, image.pixel_count, actual, bits_rgb,
2604 : bits_alpha, &stats);
2605 6 : if (auto_trim_limit > 0 && auto_trim_limit < actual) {
2606 4 : if (enforce_manual_reduced_limit(opts->thread_count, &image, auto_trim_limit, bits_rgb,
2607 : bits_alpha, &actual)) {
2608 2 : auto_trim_applied = true;
2609 2 : colopresso_log(CPRES_LOG_LEVEL_DEBUG,
2610 : "PNGX: Reduced RGBA32 auto trim applied %u -> %u colors", target,
2611 : auto_trim_limit);
2612 : } else {
2613 0 : colopresso_log(CPRES_LOG_LEVEL_WARNING,
2614 : "PNGX: Reduced RGBA32 auto trim request failed (limit=%u)",
2615 : auto_trim_limit);
2616 0 : auto_trim_limit = 0;
2617 : }
2618 : } else {
2619 4 : auto_trim_limit = 0;
2620 : }
2621 : }
2622 :
2623 8 : if (manual_target) {
2624 2 : if (!enforce_manual_reduced_limit(opts->thread_count, &image, manual_limit, bits_rgb,
2625 : bits_alpha, &actual)) {
2626 0 : color_histogram_reset(&histogram);
2627 0 : quant_support_reset(&support);
2628 0 : rgba_image_reset(&image);
2629 :
2630 0 : return false;
2631 : }
2632 : }
2633 :
2634 8 : success = create_rgba_png(image.rgba, image.pixel_count, image.width, image.height, out_data,
2635 : out_size);
2636 :
2637 8 : if (resolved_target) {
2638 8 : if (manual_target) {
2639 2 : *resolved_target = manual_limit;
2640 6 : } else if (auto_trim_applied && auto_trim_limit > 0) {
2641 2 : *resolved_target = auto_trim_limit;
2642 4 : } else if (histogram.unlocked_count == 0 || target == 0) {
2643 1 : *resolved_target = (uint32_t)histogram.count;
2644 : } else {
2645 3 : *resolved_target = target;
2646 : }
2647 : }
2648 :
2649 8 : if (applied_colors) {
2650 8 : *applied_colors = actual;
2651 : }
2652 :
2653 8 : color_histogram_reset(&histogram);
2654 8 : quant_support_reset(&support);
2655 8 : rgba_image_reset(&image);
2656 :
2657 8 : return success;
2658 : }
|