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 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 <colopresso.h>
13 :
14 : #include <errno.h>
15 : #include <limits.h>
16 : #include <math.h>
17 : #include <stdbool.h>
18 : #include <stdint.h>
19 : #include <stdlib.h>
20 : #include <string.h>
21 :
22 : #include <png.h>
23 : #include <zlib.h>
24 :
25 : #include "internal/log.h"
26 : #include "internal/png.h"
27 : #include "internal/pngx_common.h"
28 :
29 : #define PNGX_LUMA_R 0.2126f
30 : #define PNGX_LUMA_G 0.7152f
31 : #define PNGX_LUMA_B 0.0722f
32 :
33 : #define PNGX_ANCHOR_SCALE_DIVISOR 8192
34 : #define PNGX_ANCHOR_SCALE_MIN 12
35 : #define PNGX_ANCHOR_AUTO_LIMIT_DEFAULT 16
36 : #define PNGX_ANCHOR_IMPORTANCE_FACTOR 0.45f
37 : #define PNGX_ANCHOR_IMPORTANCE_THRESHOLD 0.4f
38 : #define PNGX_ANCHOR_IMPORTANCE_BOOST_BASE 0.4f
39 : #define PNGX_ANCHOR_IMPORTANCE_BOOST_SCALE 0.5f
40 : #define PNGX_ANCHOR_SCORE_THRESHOLD 0.35f
41 : #define PNGX_ANCHOR_LOW_COUNT_PENALTY 0.5f
42 : #define PNGX_ANCHOR_LOW_COUNT_THRESHOLD 4
43 : #define PNGX_ANCHOR_DISTANCE_SQ_THRESHOLD 625
44 :
45 : #define PNGX_DITHER_OPAQUE_THRESHOLD 248
46 : #define PNGX_DITHER_TRANSLUCENT_THRESHOLD 32
47 : #define PNGX_DITHER_GRADIENT_MIN 0.02f
48 : #define PNGX_DITHER_BASE_LEVEL 0.62f
49 : #define PNGX_DITHER_HIGH_GRADIENT_BOOST 0.12f
50 : #define PNGX_DITHER_MID_GRADIENT_BOOST 0.05f
51 : #define PNGX_DITHER_LOW_GRADIENT_CUT 0.12f
52 : #define PNGX_DITHER_MID_LOW_GRADIENT_CUT 0.05f
53 : #define PNGX_DITHER_OPAQUE_LOW_CUT 0.08f
54 : #define PNGX_DITHER_OPAQUE_HIGH_BOOST 0.05f
55 : #define PNGX_DITHER_TRANSLUCENT_CUT 0.05f
56 : #define PNGX_DITHER_COVERAGE_THRESHOLD 0.35f
57 : #define PNGX_DITHER_SPAN_THRESHOLD 2.0f
58 : #define PNGX_DITHER_TARGET_CAP 0.9f
59 : #define PNGX_DITHER_TARGET_CAP_LOW_BIT 0.96f
60 : #define PNGX_DITHER_LOW_BIT_BOOST 0.05f
61 : #define PNGX_DITHER_LOW_BIT_GRADIENT_BOOST 0.05f
62 : #define PNGX_DITHER_MIN 0.2f
63 : #define PNGX_DITHER_MAX 0.95f
64 :
65 : #define PNGX_FIXED_PALETTE_DISTANCE_SQ 400
66 : #define PNGX_FIXED_PALETTE_MAX 256
67 :
68 : #define PNGX_RESOLVE_DEFAULT_GRADIENT 0.2f
69 : #define PNGX_RESOLVE_DEFAULT_SATURATION 0.2f
70 : #define PNGX_RESOLVE_DEFAULT_OPAQUE 1.0f
71 : #define PNGX_RESOLVE_DEFAULT_VIBRANT 0.05f
72 : #define PNGX_RESOLVE_AUTO_BASE 0.35f
73 : #define PNGX_RESOLVE_AUTO_GRADIENT_WEIGHT 0.35f
74 : #define PNGX_RESOLVE_AUTO_SATURATION_WEIGHT 0.15f
75 : #define PNGX_RESOLVE_AUTO_OPAQUE_CUT 0.06f
76 : #define PNGX_RESOLVE_ADAPTIVE_FLAT_CUT 0.12f
77 : #define PNGX_RESOLVE_ADAPTIVE_GRADIENT_BOOST 0.06f
78 : #define PNGX_RESOLVE_ADAPTIVE_VIBRANT_CUT 0.05f
79 : #define PNGX_RESOLVE_ADAPTIVE_SATURATION_BOOST 0.03f
80 : #define PNGX_RESOLVE_ADAPTIVE_SATURATION_CUT 0.02f
81 : #define PNGX_RESOLVE_MIN 0.02f
82 : #define PNGX_RESOLVE_MAX 0.90f
83 :
84 : #define PNGX_PREPARE_GRADIENT_SCALE 0.5f
85 : #define PNGX_PREPARE_VIBRANT_SATURATION 0.55f
86 : #define PNGX_PREPARE_VIBRANT_GRADIENT 0.05f
87 : #define PNGX_PREPARE_VIBRANT_ALPHA 127
88 : #define PNGX_PREPARE_CHROMA_WEIGHT 0.35f
89 : #define PNGX_PREPARE_BOOST_THRESHOLD 0.25f
90 : #define PNGX_PREPARE_BOOST_BASE 0.08f
91 : #define PNGX_PREPARE_BOOST_FACTOR 0.3f
92 : #define PNGX_PREPARE_CUT_THRESHOLD 0.08f
93 : #define PNGX_PREPARE_CUT_FACTOR 0.65f
94 : #define PNGX_PREPARE_ALPHA_THRESHOLD 0.85f
95 : #define PNGX_PREPARE_ALPHA_BASE 0.4f
96 : #define PNGX_PREPARE_ALPHA_MULTIPLIER 0.6f
97 : #define PNGX_PREPARE_BUCKET_SATURATION 0.35f
98 : #define PNGX_PREPARE_BUCKET_IMPORTANCE 0.55f
99 : #define PNGX_PREPARE_BUCKET_ALPHA 170
100 : #define PNGX_PREPARE_MIX_IMPORTANCE 0.6f
101 : #define PNGX_PREPARE_MIX_GRADIENT 0.3f
102 : #define PNGX_PREPARE_ANCHOR_SATURATION 0.45f
103 : #define PNGX_PREPARE_ANCHOR_MIX 0.55f
104 : #define PNGX_PREPARE_ANCHOR_IMP_THRESHOLD 0.75f
105 : #define PNGX_PREPARE_ANCHOR_IMP_BONUS 0.05f
106 : #define PNGX_PREPARE_ANCHOR_SCORE_THRESHOLD 0.35f
107 : #define PNGX_PREPARE_MAP_MIN_VALUE 4
108 :
109 : #define PNGX_FS_COEFF_7 (7.0f / 16.0f)
110 : #define PNGX_FS_COEFF_3 (3.0f / 16.0f)
111 : #define PNGX_FS_COEFF_5 (5.0f / 16.0f)
112 : #define PNGX_FS_COEFF_1 (1.0f / 16.0f)
113 :
114 : typedef struct {
115 : uint64_t r_sum;
116 : uint64_t g_sum;
117 : uint64_t b_sum;
118 : uint64_t a_sum;
119 : uint32_t count;
120 : float score;
121 : float importance_accum;
122 : } pngx_chroma_bucket_t;
123 :
124 26301176 : static inline float absf(float value) { return (value < 0.0f) ? -value : value; }
125 :
126 1965600 : static inline uint32_t chroma_bucket_index(uint8_t r, uint8_t g, uint8_t b) {
127 3931200 : return (((uint32_t)r >> PNGX_CHROMA_BUCKET_SHIFT) * PNGX_CHROMA_BUCKET_DIM * PNGX_CHROMA_BUCKET_DIM) + (((uint32_t)g >> PNGX_CHROMA_BUCKET_SHIFT) * PNGX_CHROMA_BUCKET_DIM) +
128 1965600 : ((uint32_t)b >> PNGX_CHROMA_BUCKET_SHIFT);
129 : }
130 :
131 13167242 : static inline float calc_saturation(uint8_t r, uint8_t g, uint8_t b) {
132 13167242 : uint8_t max_v = r, min_v = r;
133 :
134 13167242 : if (g > max_v) {
135 6736834 : max_v = g;
136 : }
137 13167242 : if (b > max_v) {
138 3283905 : max_v = b;
139 : }
140 13167242 : if (g < min_v) {
141 6217084 : min_v = g;
142 : }
143 13167242 : if (b < min_v) {
144 4943059 : min_v = b;
145 : }
146 :
147 13167242 : if (max_v == 0) {
148 36961 : return 0.0f;
149 : }
150 :
151 13130281 : return (float)(max_v - min_v) / (float)max_v;
152 : }
153 :
154 13167250 : static inline float calc_luma(uint8_t r, uint8_t g, uint8_t b) { return PNGX_LUMA_R * (float)r + PNGX_LUMA_G * (float)g + PNGX_LUMA_B * (float)b; }
155 :
156 36 : static inline bool decode_png_rgba(const uint8_t *png_data, size_t png_size, uint8_t **rgba, png_uint_32 *width, png_uint_32 *height) {
157 : cpres_error_t status;
158 :
159 36 : if (!png_data || png_size == 0 || !rgba || !width || !height) {
160 0 : return false;
161 : }
162 :
163 36 : status = cpres_png_decode_from_memory(png_data, png_size, rgba, width, height);
164 36 : if (status != CPRES_OK) {
165 0 : cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Failed to decode PNG (%d)", (int)status);
166 0 : return false;
167 : }
168 :
169 36 : return true;
170 : }
171 :
172 28 : static inline void extract_chroma_anchors(pngx_quant_support_t *support, pngx_chroma_bucket_t *buckets, size_t bucket_count, size_t pixel_count) {
173 : cpres_rgba_color_t chosen[PNGX_MAX_DERIVED_COLORS];
174 : pngx_chroma_bucket_t *bucket;
175 : size_t selected, best_index, i, dst, auto_limit, scaled, check;
176 : float best_score, score, importance_boost;
177 : bool duplicate;
178 :
179 28 : if (!support || !buckets || bucket_count == 0) {
180 4 : return;
181 : }
182 :
183 28 : if (pixel_count > 0) {
184 28 : scaled = pixel_count / PNGX_ANCHOR_SCALE_DIVISOR;
185 28 : if (scaled < PNGX_ANCHOR_SCALE_MIN) {
186 7 : scaled = PNGX_ANCHOR_SCALE_MIN;
187 : }
188 28 : if (scaled > PNGX_MAX_DERIVED_COLORS) {
189 18 : scaled = PNGX_MAX_DERIVED_COLORS;
190 : }
191 28 : auto_limit = scaled;
192 : } else {
193 0 : auto_limit = PNGX_ANCHOR_AUTO_LIMIT_DEFAULT;
194 : }
195 :
196 28 : selected = 0;
197 1002 : while (selected < auto_limit) {
198 980 : best_score = 0.0f;
199 980 : best_index = SIZE_MAX;
200 4015060 : for (i = 0; i < bucket_count; ++i) {
201 4014080 : bucket = &buckets[i];
202 4014080 : if (!bucket->count || bucket->score <= 0.0f) {
203 3215205 : continue;
204 : }
205 798875 : importance_boost = bucket->importance_accum * PNGX_ANCHOR_IMPORTANCE_FACTOR;
206 798875 : if (importance_boost > PNGX_ANCHOR_IMPORTANCE_THRESHOLD) {
207 677899 : importance_boost = PNGX_ANCHOR_IMPORTANCE_BOOST_BASE + (importance_boost - PNGX_ANCHOR_IMPORTANCE_THRESHOLD) * PNGX_ANCHOR_IMPORTANCE_BOOST_SCALE;
208 : }
209 798875 : score = bucket->score + importance_boost;
210 798875 : if (bucket->count < PNGX_ANCHOR_LOW_COUNT_THRESHOLD) {
211 171664 : score *= PNGX_ANCHOR_LOW_COUNT_PENALTY;
212 : }
213 798875 : if (score > best_score) {
214 15517 : best_score = score;
215 15517 : best_index = i;
216 : }
217 : }
218 980 : if (best_index == SIZE_MAX || best_score < PNGX_ANCHOR_SCORE_THRESHOLD) {
219 : break;
220 : }
221 :
222 974 : chosen[selected].r = (uint8_t)(buckets[best_index].r_sum / buckets[best_index].count);
223 974 : chosen[selected].g = (uint8_t)(buckets[best_index].g_sum / buckets[best_index].count);
224 974 : chosen[selected].b = (uint8_t)(buckets[best_index].b_sum / buckets[best_index].count);
225 974 : chosen[selected].a = (uint8_t)(buckets[best_index].a_sum / buckets[best_index].count);
226 974 : buckets[best_index].score = 0.0f;
227 974 : ++selected;
228 : }
229 :
230 28 : if (!selected) {
231 4 : return;
232 : }
233 :
234 24 : dst = 0;
235 998 : for (i = 0; i < selected; ++i) {
236 974 : duplicate = false;
237 7237 : for (check = 0; check < dst; ++check) {
238 6835 : if (color_distance_sq(&chosen[i], &chosen[check]) < PNGX_ANCHOR_DISTANCE_SQ_THRESHOLD) {
239 572 : duplicate = true;
240 572 : break;
241 : }
242 : }
243 974 : if (duplicate) {
244 572 : continue;
245 : }
246 402 : chosen[dst] = chosen[i];
247 402 : ++dst;
248 : }
249 :
250 24 : if (!dst) {
251 0 : return;
252 : }
253 :
254 24 : support->derived_colors = (cpres_rgba_color_t *)malloc(sizeof(cpres_rgba_color_t) * dst);
255 24 : if (!support->derived_colors) {
256 0 : return;
257 : }
258 24 : memcpy(support->derived_colors, chosen, sizeof(cpres_rgba_color_t) * dst);
259 24 : support->derived_colors_len = dst;
260 : }
261 :
262 : /* Call from libpng on setjmp */
263 : /* LCOV_EXCL_START */
264 : void memory_buffer_reset(png_memory_buffer_t *buffer) {
265 : if (!buffer) {
266 : return;
267 : }
268 :
269 : free(buffer->data);
270 : buffer->data = NULL;
271 : buffer->size = 0;
272 : buffer->capacity = 0;
273 : }
274 : /* LCOV_EXCL_STOP */
275 :
276 33 : void image_stats_reset(pngx_image_stats_t *stats) {
277 33 : if (!stats) {
278 0 : return;
279 : }
280 :
281 33 : stats->gradient_mean = 0.0f;
282 33 : stats->gradient_max = 0.0f;
283 33 : stats->saturation_mean = 0.0f;
284 33 : stats->opaque_ratio = 0.0f;
285 33 : stats->translucent_ratio = 0.0f;
286 33 : stats->vibrant_ratio = 0.0f;
287 : }
288 :
289 33 : void quant_support_reset(pngx_quant_support_t *support) {
290 33 : if (!support) {
291 0 : return;
292 : }
293 :
294 33 : free(support->importance_map);
295 33 : support->importance_map = NULL;
296 33 : support->importance_map_len = 0;
297 :
298 33 : free(support->derived_colors);
299 33 : support->derived_colors = NULL;
300 33 : support->derived_colors_len = 0;
301 :
302 33 : free(support->combined_fixed_colors);
303 33 : support->combined_fixed_colors = NULL;
304 33 : support->combined_fixed_len = 0;
305 :
306 33 : free(support->bit_hint_map);
307 33 : support->bit_hint_map = NULL;
308 33 : support->bit_hint_len = 0;
309 : }
310 :
311 8 : const char *lossy_type_label(uint8_t lossy_type) {
312 8 : switch (lossy_type) {
313 3 : case PNGX_LOSSY_TYPE_LIMITED_RGBA4444:
314 3 : return "Limited RGBA4444";
315 5 : case PNGX_LOSSY_TYPE_REDUCED_RGBA32:
316 5 : return "Reduced RGBA32";
317 0 : default:
318 0 : return "Palette256";
319 : }
320 : }
321 :
322 36 : void rgba_image_reset(pngx_rgba_image_t *image) {
323 36 : if (!image) {
324 0 : return;
325 : }
326 :
327 36 : free(image->rgba);
328 36 : image->rgba = NULL;
329 36 : image->width = 0;
330 36 : image->height = 0;
331 36 : image->pixel_count = 0;
332 : }
333 :
334 36 : bool load_rgba_image(const uint8_t *png_data, size_t png_size, pngx_rgba_image_t *image) {
335 36 : if (!png_data || png_size == 0 || !image) {
336 0 : return false;
337 : }
338 :
339 36 : image->rgba = NULL;
340 36 : image->width = 0;
341 36 : image->height = 0;
342 36 : image->pixel_count = 0;
343 :
344 36 : if (!decode_png_rgba(png_data, png_size, &image->rgba, &image->width, &image->height)) {
345 0 : rgba_image_reset(image);
346 0 : return false;
347 : }
348 :
349 36 : if (!image->rgba || image->width == 0 || image->height == 0) {
350 0 : rgba_image_reset(image);
351 0 : return false;
352 : }
353 :
354 36 : if ((size_t)image->width > SIZE_MAX / (size_t)image->height) {
355 0 : rgba_image_reset(image);
356 0 : return false;
357 : }
358 :
359 36 : image->pixel_count = (size_t)image->width * (size_t)image->height;
360 :
361 36 : return true;
362 : }
363 :
364 7411761 : uint8_t clamp_reduced_bits(uint8_t bits) {
365 7411761 : const uint8_t min_bits = (uint8_t)COLOPRESSO_PNGX_REDUCED_BITS_MIN, max_bits = (uint8_t)COLOPRESSO_PNGX_REDUCED_BITS_MAX;
366 :
367 7411761 : if (bits < min_bits) {
368 0 : return min_bits;
369 : }
370 7411761 : if (bits > max_bits) {
371 0 : return max_bits;
372 : }
373 7411761 : return bits;
374 : }
375 :
376 30516067 : uint8_t quantize_channel_value(float value, uint8_t bits_per_channel) {
377 : uint32_t levels;
378 : float clamped, scaled, rounded, quantized;
379 :
380 30516067 : if (bits_per_channel >= 8) {
381 0 : if (value < 0.0f) {
382 0 : return 0;
383 : }
384 0 : if (value > 255.0f) {
385 0 : return 255;
386 : }
387 0 : return (uint8_t)(value + 0.5f);
388 : }
389 :
390 30516067 : if (bits_per_channel < 1) {
391 0 : bits_per_channel = 1;
392 : }
393 :
394 30516067 : levels = 1 << bits_per_channel;
395 :
396 30516067 : clamped = value;
397 30516067 : if (clamped < 0.0f) {
398 539 : clamped = 0.0f;
399 30515528 : } else if (clamped > 255.0f) {
400 1243 : clamped = 255.0f;
401 : }
402 :
403 30516067 : if (levels <= 1) {
404 0 : return 0;
405 : }
406 :
407 30516067 : scaled = clamped * (float)(levels - 1) / 255.0f;
408 30516067 : if (scaled < 0.0f) {
409 0 : scaled = 0.0f;
410 : }
411 30516067 : if (scaled > (float)(levels - 1)) {
412 0 : scaled = (float)(levels - 1);
413 : }
414 :
415 30516067 : rounded = (float)((int32_t)(scaled + 0.5f));
416 :
417 30516067 : quantized = rounded * 255.0f / (float)(levels - 1);
418 30516067 : if (quantized < 0.0f) {
419 0 : quantized = 0.0f;
420 30516067 : } else if (quantized > 255.0f) {
421 0 : quantized = 255.0f;
422 : }
423 :
424 30516067 : return (uint8_t)(quantized + 0.5f);
425 : }
426 :
427 14622960 : uint8_t quantize_bits(uint8_t value, uint8_t bits) {
428 14622960 : if (bits >= 8) {
429 407949 : return value;
430 : }
431 :
432 14215011 : return quantize_channel_value((float)value, bits);
433 : }
434 :
435 1197400 : void snap_rgba_to_bits(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a, uint8_t bits_rgb, uint8_t bits_alpha) {
436 1197400 : bits_rgb = clamp_reduced_bits(bits_rgb);
437 1197400 : bits_alpha = clamp_reduced_bits(bits_alpha);
438 :
439 1197400 : if (r) {
440 1197400 : *r = quantize_bits(*r, bits_rgb);
441 : }
442 1197400 : if (g) {
443 1197400 : *g = quantize_bits(*g, bits_rgb);
444 : }
445 1197400 : if (b) {
446 1197400 : *b = quantize_bits(*b, bits_rgb);
447 : }
448 1197400 : if (a) {
449 1197400 : *a = quantize_bits(*a, bits_alpha);
450 : }
451 1197400 : }
452 :
453 10 : void snap_rgba_image_to_bits(uint8_t *rgba, size_t pixel_count, uint8_t bits_rgb, uint8_t bits_alpha) {
454 : uint8_t r, g, b, a;
455 : size_t base, i;
456 :
457 10 : if (!rgba || pixel_count == 0) {
458 0 : return;
459 : }
460 :
461 10 : bits_rgb = clamp_reduced_bits(bits_rgb);
462 10 : bits_alpha = clamp_reduced_bits(bits_alpha);
463 10 : if (bits_rgb >= 8 && bits_alpha >= 8) {
464 0 : return;
465 : }
466 :
467 1181966 : for (i = 0; i < pixel_count; ++i) {
468 1181956 : base = i * 4;
469 :
470 1181956 : r = rgba[base + 0];
471 1181956 : g = rgba[base + 1];
472 1181956 : b = rgba[base + 2];
473 1181956 : a = rgba[base + 3];
474 :
475 1181956 : snap_rgba_to_bits(&r, &g, &b, &a, bits_rgb, bits_alpha);
476 :
477 1181956 : rgba[base + 0] = r;
478 1181956 : rgba[base + 1] = g;
479 1181956 : rgba[base + 2] = b;
480 1181956 : rgba[base + 3] = a;
481 : }
482 : }
483 :
484 10021 : uint32_t color_distance_sq(const cpres_rgba_color_t *lhs, const cpres_rgba_color_t *rhs) {
485 : int32_t dr, dg, db, da;
486 :
487 10021 : if (!lhs || !rhs) {
488 0 : return 0;
489 : }
490 :
491 10021 : dr = (int32_t)lhs->r - (int32_t)rhs->r;
492 10021 : dg = (int32_t)lhs->g - (int32_t)rhs->g;
493 10021 : db = (int32_t)lhs->b - (int32_t)rhs->b;
494 10021 : da = (int32_t)lhs->a - (int32_t)rhs->a;
495 :
496 10021 : return (uint32_t)(dr * dr + dg * dg + db * db + da * da);
497 : }
498 :
499 1 : float estimate_bitdepth_dither_level(const uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_per_channel) {
500 : png_uint_32 y, x;
501 : uint8_t r, g, b, a;
502 : size_t pixel_count, gradient_samples, opaque_pixels, translucent_pixels, base, right, below;
503 : double gradient_accum;
504 : float base_level, target, right_luma, below_luma, luma, normalized_gradient, opaque_ratio, translucent_ratio, min_luma, max_luma, coverage, gradient_span;
505 :
506 1 : if (!rgba || width == 0 || height == 0) {
507 0 : return clamp_float(COLOPRESSO_PNGX_DEFAULT_LOSSY_DITHER_LEVEL, 0.0f, 1.0f);
508 : }
509 :
510 1 : pixel_count = (size_t)width * (size_t)height;
511 1 : gradient_accum = 0.0;
512 1 : gradient_samples = 0;
513 1 : opaque_pixels = 0;
514 1 : translucent_pixels = 0;
515 1 : min_luma = 255.0f;
516 1 : max_luma = 0.0f;
517 :
518 3 : for (y = 0; y < height; ++y) {
519 6 : for (x = 0; x < width; ++x) {
520 4 : base = (((size_t)y * (size_t)width) + (size_t)x) * 4;
521 4 : r = rgba[base + 0];
522 4 : g = rgba[base + 1];
523 4 : b = rgba[base + 2];
524 4 : a = rgba[base + 3];
525 4 : luma = calc_luma(r, g, b);
526 :
527 4 : if (luma < min_luma) {
528 1 : min_luma = luma;
529 : }
530 4 : if (luma > max_luma) {
531 0 : max_luma = luma;
532 : }
533 :
534 4 : if (a > PNGX_DITHER_OPAQUE_THRESHOLD) {
535 4 : ++opaque_pixels;
536 0 : } else if (a > PNGX_DITHER_TRANSLUCENT_THRESHOLD) {
537 0 : ++translucent_pixels;
538 : }
539 :
540 4 : if (x + 1 < width) {
541 2 : right = base + 4;
542 2 : right_luma = calc_luma(rgba[right + 0], rgba[right + 1], rgba[right + 2]);
543 2 : gradient_accum += absf(right_luma - luma);
544 2 : ++gradient_samples;
545 : }
546 :
547 4 : if (y + 1 < height) {
548 2 : below = (((size_t)(y + 1) * (size_t)width) + (size_t)x) * 4;
549 2 : below_luma = calc_luma(rgba[below + 0], rgba[below + 1], rgba[below + 2]);
550 2 : gradient_accum += absf(below_luma - luma);
551 2 : ++gradient_samples;
552 : }
553 : }
554 : }
555 :
556 1 : if (gradient_samples == 0) {
557 0 : gradient_samples = 1;
558 : }
559 :
560 1 : normalized_gradient = (float)(gradient_accum / ((double)gradient_samples * 255.0));
561 1 : opaque_ratio = (pixel_count > 0) ? (float)((double)opaque_pixels / (double)pixel_count) : 0.0f;
562 1 : translucent_ratio = (pixel_count > 0) ? (float)((double)translucent_pixels / (double)pixel_count) : 0.0f;
563 :
564 1 : coverage = (max_luma - min_luma) / 255.0f;
565 1 : if (coverage < 0.0f) {
566 0 : coverage = 0.0f;
567 1 : } else if (coverage > 1.0f) {
568 0 : coverage = 1.0f;
569 : }
570 :
571 1 : gradient_span = coverage / ((normalized_gradient > PNGX_DITHER_GRADIENT_MIN) ? normalized_gradient : PNGX_DITHER_GRADIENT_MIN);
572 :
573 1 : base_level = PNGX_DITHER_BASE_LEVEL;
574 1 : target = base_level;
575 :
576 1 : if (normalized_gradient > 0.35f) {
577 0 : target += PNGX_DITHER_HIGH_GRADIENT_BOOST;
578 1 : } else if (normalized_gradient > 0.2f) {
579 0 : target += PNGX_DITHER_MID_GRADIENT_BOOST;
580 1 : } else if (normalized_gradient < 0.08f) {
581 1 : target -= PNGX_DITHER_LOW_GRADIENT_CUT;
582 0 : } else if (normalized_gradient < 0.15f) {
583 0 : target -= PNGX_DITHER_MID_LOW_GRADIENT_CUT;
584 : }
585 :
586 1 : if (opaque_ratio < 0.35f) {
587 0 : target -= PNGX_DITHER_OPAQUE_LOW_CUT;
588 1 : } else if (opaque_ratio > 0.9f) {
589 1 : target += PNGX_DITHER_OPAQUE_HIGH_BOOST;
590 : }
591 :
592 1 : if (translucent_ratio > 0.3f) {
593 0 : target -= PNGX_DITHER_TRANSLUCENT_CUT;
594 : }
595 :
596 1 : if (coverage > PNGX_DITHER_COVERAGE_THRESHOLD && gradient_span > PNGX_DITHER_SPAN_THRESHOLD) {
597 0 : if (target < PNGX_DITHER_TARGET_CAP) {
598 0 : target = PNGX_DITHER_TARGET_CAP;
599 : }
600 0 : if (bits_per_channel <= 4 && target < PNGX_DITHER_TARGET_CAP_LOW_BIT) {
601 0 : target = PNGX_DITHER_TARGET_CAP_LOW_BIT;
602 : }
603 : }
604 :
605 1 : if (bits_per_channel <= 2) {
606 0 : target += PNGX_DITHER_LOW_BIT_BOOST;
607 0 : if (normalized_gradient > 0.25f) {
608 0 : target += PNGX_DITHER_LOW_BIT_GRADIENT_BOOST;
609 : }
610 : }
611 :
612 1 : return clamp_float(target, PNGX_DITHER_MIN, PNGX_DITHER_MAX);
613 : }
614 :
615 22 : void build_fixed_palette(const pngx_options_t *source_opts, pngx_quant_support_t *support, pngx_options_t *patched_opts) {
616 : size_t user_count, derived_count, total_cap, i, j;
617 : bool duplicate;
618 :
619 22 : if (!source_opts || !support || !patched_opts) {
620 0 : return;
621 : }
622 :
623 22 : *patched_opts = *source_opts;
624 22 : user_count = (source_opts->protected_colors && source_opts->protected_colors_count > 0) ? (size_t)source_opts->protected_colors_count : 0;
625 22 : derived_count = support->derived_colors_len;
626 22 : if (derived_count == 0) {
627 3 : return;
628 : }
629 :
630 19 : total_cap = user_count + derived_count;
631 19 : support->combined_fixed_colors = (cpres_rgba_color_t *)malloc(sizeof(cpres_rgba_color_t) * total_cap);
632 19 : if (!support->combined_fixed_colors) {
633 0 : return;
634 : }
635 :
636 19 : if (user_count > 0 && source_opts->protected_colors) {
637 1 : memcpy(support->combined_fixed_colors, source_opts->protected_colors, sizeof(cpres_rgba_color_t) * user_count);
638 : }
639 19 : support->combined_fixed_len = user_count;
640 348 : for (i = 0; i < derived_count; ++i) {
641 329 : duplicate = false;
642 3514 : for (j = 0; j < support->combined_fixed_len; ++j) {
643 3185 : if (color_distance_sq(&support->derived_colors[i], &support->combined_fixed_colors[j]) < PNGX_FIXED_PALETTE_DISTANCE_SQ) {
644 0 : duplicate = true;
645 0 : break;
646 : }
647 : }
648 329 : if (duplicate) {
649 0 : continue;
650 : }
651 329 : if (support->combined_fixed_len < total_cap) {
652 329 : support->combined_fixed_colors[support->combined_fixed_len] = support->derived_colors[i];
653 329 : ++support->combined_fixed_len;
654 : }
655 329 : if (support->combined_fixed_len >= PNGX_FIXED_PALETTE_MAX) {
656 0 : break;
657 : }
658 : }
659 19 : if (support->combined_fixed_len > user_count) {
660 19 : patched_opts->protected_colors = support->combined_fixed_colors;
661 19 : patched_opts->protected_colors_count = (int32_t)support->combined_fixed_len;
662 : }
663 : }
664 :
665 25 : float resolve_quant_dither(const pngx_options_t *opts, const pngx_image_stats_t *stats) {
666 : float resolved, gradient_mean, saturation_mean, opaque_ratio, vibrant_ratio, gradient_max;
667 :
668 25 : if (!opts) {
669 0 : return 0.5f;
670 : }
671 :
672 25 : if (stats) {
673 25 : gradient_mean = stats->gradient_mean;
674 25 : saturation_mean = stats->saturation_mean;
675 25 : opaque_ratio = stats->opaque_ratio;
676 25 : vibrant_ratio = stats->vibrant_ratio;
677 25 : gradient_max = stats->gradient_max;
678 : } else {
679 0 : gradient_mean = PNGX_RESOLVE_DEFAULT_GRADIENT;
680 0 : saturation_mean = PNGX_RESOLVE_DEFAULT_SATURATION;
681 0 : opaque_ratio = PNGX_RESOLVE_DEFAULT_OPAQUE;
682 0 : vibrant_ratio = PNGX_RESOLVE_DEFAULT_VIBRANT;
683 0 : gradient_max = PNGX_RESOLVE_DEFAULT_GRADIENT;
684 : }
685 :
686 25 : resolved = opts->lossy_dither_level;
687 :
688 25 : if (opts->lossy_dither_auto) {
689 1 : resolved = PNGX_RESOLVE_AUTO_BASE + gradient_mean * PNGX_RESOLVE_AUTO_GRADIENT_WEIGHT + saturation_mean * PNGX_RESOLVE_AUTO_SATURATION_WEIGHT;
690 1 : if (opaque_ratio < 0.7f) {
691 1 : resolved -= PNGX_RESOLVE_AUTO_OPAQUE_CUT;
692 : }
693 : }
694 :
695 25 : if (opts->adaptive_dither_enable) {
696 24 : if (gradient_mean < 0.10f) {
697 24 : resolved -= PNGX_RESOLVE_ADAPTIVE_FLAT_CUT;
698 0 : } else if (gradient_mean > 0.30f) {
699 0 : resolved += PNGX_RESOLVE_ADAPTIVE_GRADIENT_BOOST;
700 : }
701 24 : if (gradient_max > 0.5f && vibrant_ratio > 0.12f) {
702 0 : resolved -= PNGX_RESOLVE_ADAPTIVE_VIBRANT_CUT;
703 : }
704 24 : if (saturation_mean > 0.38f) {
705 7 : resolved += PNGX_RESOLVE_ADAPTIVE_SATURATION_BOOST;
706 17 : } else if (saturation_mean < 0.12f) {
707 3 : resolved -= PNGX_RESOLVE_ADAPTIVE_SATURATION_CUT;
708 : }
709 : }
710 :
711 25 : if (resolved < PNGX_RESOLVE_MIN) {
712 7 : resolved = PNGX_RESOLVE_MIN;
713 18 : } else if (resolved > PNGX_RESOLVE_MAX) {
714 0 : resolved = PNGX_RESOLVE_MAX;
715 : }
716 :
717 25 : return resolved;
718 : }
719 :
720 33 : bool prepare_quant_support(const pngx_rgba_image_t *image, const pngx_options_t *opts, pngx_quant_support_t *support, pngx_image_stats_t *stats) {
721 33 : pngx_chroma_bucket_t *buckets = NULL, *bucket_entry;
722 : uint32_t x, y, range, sample;
723 33 : uint16_t *importance_work = NULL, raw_min, raw_max;
724 : uint8_t r, g, b, a, value;
725 : size_t pixel_index, base, next_row_base, opaque_pixels, translucent_pixels, vibrant_pixels;
726 33 : float gradient_sum, saturation_sum, luma, gradient, saturation, importance, alpha_factor, anchor_score, right_luma, below_luma, importance_mix, *luma_row_curr = NULL, *luma_row_next = NULL,
727 : *luma_row_tmp;
728 : bool need_map, need_buckets;
729 :
730 33 : if (!image || !opts || !support || !stats || image->pixel_count == 0) {
731 0 : return false;
732 : }
733 :
734 33 : raw_min = UINT16_MAX;
735 33 : raw_max = 0;
736 33 : need_map = opts->saliency_map_enable || opts->postprocess_smooth_enable;
737 33 : need_buckets = opts->chroma_anchor_enable;
738 33 : gradient_sum = 0.0f;
739 33 : saturation_sum = 0.0f;
740 33 : opaque_pixels = 0;
741 33 : translucent_pixels = 0;
742 33 : vibrant_pixels = 0;
743 33 : if (need_map) {
744 28 : importance_work = (uint16_t *)malloc(sizeof(uint16_t) * image->pixel_count);
745 28 : if (!importance_work) {
746 0 : return false;
747 : }
748 : }
749 :
750 33 : if (need_buckets) {
751 28 : buckets = (pngx_chroma_bucket_t *)calloc(PNGX_CHROMA_BUCKET_COUNT, sizeof(pngx_chroma_bucket_t));
752 28 : if (!buckets) {
753 0 : if (importance_work) {
754 0 : free(importance_work);
755 : }
756 :
757 0 : return false;
758 : }
759 : }
760 :
761 33 : luma_row_curr = (float *)malloc(sizeof(float) * image->width);
762 33 : luma_row_next = (float *)malloc(sizeof(float) * image->width);
763 33 : if (!luma_row_curr || !luma_row_next) {
764 0 : free(importance_work);
765 0 : free(buckets);
766 0 : free(luma_row_curr);
767 0 : free(luma_row_next);
768 :
769 0 : return false;
770 : }
771 :
772 19309 : for (x = 0; x < image->width; ++x) {
773 19276 : base = (size_t)x * 4;
774 19276 : luma_row_curr[x] = calc_luma(image->rgba[base + 0], image->rgba[base + 1], image->rgba[base + 2]) / 255.0f;
775 : }
776 :
777 14069 : for (y = 0; y < image->height; ++y) {
778 14036 : if (y + 1 < image->height) {
779 14003 : next_row_base = ((size_t)(y + 1) * (size_t)image->width) * 4;
780 13161969 : for (x = 0; x < image->width; ++x) {
781 13147966 : base = next_row_base + (size_t)x * 4;
782 13147966 : luma_row_next[x] = calc_luma(image->rgba[base + 0], image->rgba[base + 1], image->rgba[base + 2]) / 255.0f;
783 : }
784 : }
785 :
786 13181278 : for (x = 0; x < image->width; ++x) {
787 13167242 : base = (((size_t)y * (size_t)image->width) + (size_t)x) * 4;
788 13167242 : r = image->rgba[base + 0];
789 13167242 : g = image->rgba[base + 1];
790 13167242 : b = image->rgba[base + 2];
791 13167242 : a = image->rgba[base + 3];
792 13167242 : luma = luma_row_curr[x];
793 13167242 : gradient = 0.0f;
794 13167242 : saturation = calc_saturation(r, g, b);
795 13167242 : alpha_factor = (float)a / 255.0f;
796 :
797 13167242 : if (x + 1 < image->width) {
798 13153206 : right_luma = luma_row_curr[x + 1];
799 13153206 : gradient += absf(right_luma - luma);
800 : }
801 :
802 13167242 : if (y + 1 < image->height) {
803 13147966 : below_luma = luma_row_next[x];
804 13147966 : gradient += absf(below_luma - luma);
805 : }
806 :
807 13167242 : gradient *= PNGX_PREPARE_GRADIENT_SCALE;
808 13167242 : if (gradient > 1.0f) {
809 0 : gradient = 1.0f;
810 : }
811 :
812 13167242 : gradient_sum += gradient;
813 13167242 : if (gradient > stats->gradient_max) {
814 702 : stats->gradient_max = gradient;
815 : }
816 :
817 13167242 : saturation_sum += saturation;
818 :
819 13167242 : if (a > PNGX_DITHER_OPAQUE_THRESHOLD) {
820 176859 : ++opaque_pixels;
821 12990383 : } else if (a > PNGX_DITHER_TRANSLUCENT_THRESHOLD) {
822 12884846 : ++translucent_pixels;
823 : }
824 :
825 13167242 : if (saturation > PNGX_PREPARE_VIBRANT_SATURATION && gradient > PNGX_PREPARE_VIBRANT_GRADIENT && a > PNGX_PREPARE_VIBRANT_ALPHA) {
826 545479 : ++vibrant_pixels;
827 : }
828 :
829 13167242 : importance = gradient;
830 :
831 13167242 : if (opts->chroma_weight_enable) {
832 13096322 : importance += saturation * PNGX_PREPARE_CHROMA_WEIGHT;
833 : }
834 :
835 13167242 : if (opts->gradient_boost_enable) {
836 13096322 : if (gradient > PNGX_PREPARE_BOOST_THRESHOLD) {
837 29792 : importance += PNGX_PREPARE_BOOST_BASE + (gradient * PNGX_PREPARE_BOOST_FACTOR);
838 13066530 : } else if (gradient < PNGX_PREPARE_CUT_THRESHOLD) {
839 9940372 : importance *= PNGX_PREPARE_CUT_FACTOR;
840 : }
841 : }
842 :
843 13167242 : if (alpha_factor < PNGX_PREPARE_ALPHA_THRESHOLD) {
844 11234189 : importance *= (PNGX_PREPARE_ALPHA_BASE + alpha_factor * PNGX_PREPARE_ALPHA_MULTIPLIER);
845 : }
846 :
847 13167242 : if (importance < 0.0f) {
848 0 : importance = 0.0f;
849 13167242 : } else if (importance > 1.0f) {
850 0 : importance = 1.0f;
851 : }
852 :
853 13167242 : if (need_map && importance_work) {
854 13100418 : pixel_index = ((size_t)y * (size_t)image->width) + (size_t)x;
855 13100418 : importance_work[pixel_index] = (uint16_t)(importance * PNGX_IMPORTANCE_SCALE + 0.5f);
856 :
857 13100418 : if (importance_work[pixel_index] < raw_min) {
858 942 : raw_min = importance_work[pixel_index];
859 : }
860 :
861 13100418 : if (importance_work[pixel_index] > raw_max) {
862 1114 : raw_max = importance_work[pixel_index];
863 : }
864 : }
865 :
866 13167242 : if (need_buckets && buckets) {
867 13096326 : if ((saturation > PNGX_PREPARE_BUCKET_SATURATION || importance > PNGX_PREPARE_BUCKET_IMPORTANCE) && a > PNGX_PREPARE_BUCKET_ALPHA) {
868 3794649 : importance_mix = (importance * PNGX_PREPARE_MIX_IMPORTANCE) + (gradient * PNGX_PREPARE_MIX_GRADIENT);
869 3794649 : anchor_score = (saturation * PNGX_PREPARE_ANCHOR_SATURATION) + (importance_mix * PNGX_PREPARE_ANCHOR_MIX);
870 3794649 : if (importance > PNGX_PREPARE_ANCHOR_IMP_THRESHOLD) {
871 0 : anchor_score += PNGX_PREPARE_ANCHOR_IMP_BONUS;
872 : }
873 3794649 : if (anchor_score > PNGX_PREPARE_ANCHOR_SCORE_THRESHOLD) {
874 1965600 : bucket_entry = &buckets[chroma_bucket_index(r, g, b)];
875 1965600 : bucket_entry->r_sum += (uint64_t)r;
876 1965600 : bucket_entry->g_sum += (uint64_t)g;
877 1965600 : bucket_entry->b_sum += (uint64_t)b;
878 1965600 : bucket_entry->a_sum += (uint64_t)a;
879 1965600 : bucket_entry->count += 1;
880 1965600 : bucket_entry->score += anchor_score;
881 1965600 : bucket_entry->importance_accum += importance;
882 : }
883 : }
884 : }
885 : }
886 :
887 14036 : luma_row_tmp = luma_row_curr;
888 14036 : luma_row_curr = luma_row_next;
889 14036 : luma_row_next = luma_row_tmp;
890 : }
891 :
892 33 : free(luma_row_curr);
893 33 : free(luma_row_next);
894 :
895 33 : if (image->pixel_count > 0) {
896 33 : stats->gradient_mean = gradient_sum / (float)image->pixel_count;
897 33 : stats->saturation_mean = saturation_sum / (float)image->pixel_count;
898 33 : stats->opaque_ratio = (float)opaque_pixels / (float)image->pixel_count;
899 33 : stats->translucent_ratio = (float)translucent_pixels / (float)image->pixel_count;
900 33 : stats->vibrant_ratio = (float)vibrant_pixels / (float)image->pixel_count;
901 : }
902 33 : if (need_map && importance_work && image->pixel_count > 0) {
903 28 : range = (uint32_t)(raw_max - raw_min);
904 28 : if (range == 0) {
905 3 : range = 1;
906 : }
907 :
908 28 : support->importance_map = (uint8_t *)malloc(image->pixel_count);
909 28 : if (support->importance_map) {
910 13100446 : for (pixel_index = 0; pixel_index < image->pixel_count; ++pixel_index) {
911 13100418 : sample = (uint32_t)(importance_work[pixel_index] - raw_min);
912 13100418 : value = (uint8_t)((sample * 255) / range);
913 13100418 : if (value < 4) {
914 84309 : value = 4;
915 : }
916 13100418 : support->importance_map[pixel_index] = value;
917 : }
918 28 : support->importance_map_len = image->pixel_count;
919 : }
920 : }
921 :
922 33 : free(importance_work);
923 :
924 33 : if (need_buckets && buckets) {
925 28 : extract_chroma_anchors(support, buckets, PNGX_CHROMA_BUCKET_COUNT, image->pixel_count);
926 : }
927 :
928 33 : free(buckets);
929 :
930 33 : return true;
931 : }
|