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 <stdlib.h>
13 : #include <string.h>
14 :
15 : #include <png.h>
16 : #include <zlib.h>
17 :
18 : #include "internal/log.h"
19 : #include "internal/pngx_common.h"
20 : #include "internal/threads.h"
21 :
22 : typedef struct {
23 : pngx_rgba_image_t image;
24 : pngx_quant_support_t support;
25 : pngx_image_stats_t stats;
26 : pngx_options_t tuned_opts;
27 : float resolved_dither;
28 : bool prefer_uniform;
29 : bool initialized;
30 : } palette256_context_t;
31 :
32 : typedef struct {
33 : uint8_t *indices;
34 : const uint8_t *reference;
35 : uint32_t width;
36 : uint32_t height;
37 : const cpres_rgba_color_t *palette;
38 : size_t palette_len;
39 : const uint8_t *importance_map;
40 : float cutoff;
41 : } postprocess_indices_parallel_ctx_t;
42 :
43 : /* Global context for multi-step processing. Not thread-safe; must be called sequentially. */
44 : static palette256_context_t g_palette256_ctx = {0};
45 :
46 24 : static inline void alpha_bleed_rgb_from_opaque(uint8_t *rgba, uint32_t width, uint32_t height, const pngx_options_t *opts) {
47 : uint32_t *seed_rgb, x, y, best_rgb;
48 : uint16_t *dist, best, d, max_distance;
49 : uint8_t opaque_threshold, soft_limit;
50 : size_t pixel_count, i, base, idx;
51 : bool has_seed;
52 :
53 24 : if (!rgba || width == 0 || height == 0) {
54 0 : return;
55 : }
56 :
57 24 : if (!opts || !opts->palette256_alpha_bleed_enable) {
58 22 : return;
59 : }
60 :
61 2 : max_distance = opts->palette256_alpha_bleed_max_distance;
62 2 : opaque_threshold = opts->palette256_alpha_bleed_opaque_threshold;
63 2 : soft_limit = opts->palette256_alpha_bleed_soft_limit;
64 :
65 2 : if ((size_t)width > SIZE_MAX / (size_t)height) {
66 0 : return;
67 : }
68 2 : pixel_count = (size_t)width * (size_t)height;
69 :
70 2 : dist = (uint16_t *)malloc(sizeof(uint16_t) * pixel_count);
71 2 : seed_rgb = (uint32_t *)malloc(sizeof(uint32_t) * pixel_count);
72 2 : if (!dist || !seed_rgb) {
73 0 : free(dist);
74 0 : free(seed_rgb);
75 0 : return;
76 : }
77 :
78 2 : has_seed = false;
79 4100 : for (i = 0; i < pixel_count; ++i) {
80 4098 : base = i * 4;
81 4098 : if (rgba[base + 3] == 0) {
82 4096 : rgba[base + 0] = 0;
83 4096 : rgba[base + 1] = 0;
84 4096 : rgba[base + 2] = 0;
85 : }
86 :
87 4098 : if (rgba[base + 3] >= opaque_threshold) {
88 1 : dist[i] = 0;
89 1 : seed_rgb[i] = ((uint32_t)rgba[base + 0] << 16) | ((uint32_t)rgba[base + 1] << 8) | (uint32_t)rgba[base + 2];
90 1 : has_seed = true;
91 : } else {
92 4097 : dist[i] = UINT16_MAX;
93 4097 : seed_rgb[i] = 0;
94 : }
95 : }
96 :
97 2 : if (!has_seed) {
98 1 : free(dist);
99 1 : free(seed_rgb);
100 1 : return;
101 : }
102 :
103 4 : for (i = 0; i < 3; ++i) {
104 6 : for (y = 0; y < height; ++y) {
105 9 : for (x = 0; x < width; ++x) {
106 6 : idx = (size_t)y * (size_t)width + (size_t)x;
107 6 : best = dist[idx];
108 6 : best_rgb = seed_rgb[idx];
109 :
110 6 : if (x > 0 && dist[idx - 1] != UINT16_MAX && (uint16_t)(dist[idx - 1] + 1) < best) {
111 1 : best = (uint16_t)(dist[idx - 1] + 1);
112 1 : best_rgb = seed_rgb[idx - 1];
113 : }
114 6 : if (y > 0 && dist[idx - (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width] + 1) < best) {
115 0 : best = (uint16_t)(dist[idx - (size_t)width] + 1);
116 0 : best_rgb = seed_rgb[idx - (size_t)width];
117 : }
118 6 : if (x > 0 && y > 0 && dist[idx - (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width - 1] + 1) < best) {
119 0 : best = (uint16_t)(dist[idx - (size_t)width - 1] + 1);
120 0 : best_rgb = seed_rgb[idx - (size_t)width - 1];
121 : }
122 6 : if (x + 1 < width && y > 0 && dist[idx - (size_t)width + 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width + 1] + 1) < best) {
123 0 : best = (uint16_t)(dist[idx - (size_t)width + 1] + 1);
124 0 : best_rgb = seed_rgb[idx - (size_t)width + 1];
125 : }
126 :
127 6 : dist[idx] = best;
128 6 : seed_rgb[idx] = best_rgb;
129 : }
130 : }
131 :
132 6 : for (y = height; y-- > 0;) {
133 9 : for (x = width; x-- > 0;) {
134 6 : idx = (size_t)y * (size_t)width + (size_t)x;
135 6 : best = dist[idx];
136 6 : best_rgb = seed_rgb[idx];
137 :
138 6 : if (x + 1 < width && dist[idx + 1] != UINT16_MAX && (uint16_t)(dist[idx + 1] + 1) < best) {
139 0 : best = (uint16_t)(dist[idx + 1] + 1);
140 0 : best_rgb = seed_rgb[idx + 1];
141 : }
142 6 : if (y + 1 < height && dist[idx + (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width] + 1) < best) {
143 0 : best = (uint16_t)(dist[idx + (size_t)width] + 1);
144 0 : best_rgb = seed_rgb[idx + (size_t)width];
145 : }
146 6 : if (x + 1 < width && y + 1 < height && dist[idx + (size_t)width + 1] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width + 1] + 1) < best) {
147 0 : best = (uint16_t)(dist[idx + (size_t)width + 1] + 1);
148 0 : best_rgb = seed_rgb[idx + (size_t)width + 1];
149 : }
150 6 : if (x > 0 && y + 1 < height && dist[idx + (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width - 1] + 1) < best) {
151 0 : best = (uint16_t)(dist[idx + (size_t)width - 1] + 1);
152 0 : best_rgb = seed_rgb[idx + (size_t)width - 1];
153 : }
154 :
155 6 : dist[idx] = best;
156 6 : seed_rgb[idx] = best_rgb;
157 : }
158 : }
159 : }
160 :
161 2 : for (y = 0; y < height; ++y) {
162 3 : for (x = 0; x < width; ++x) {
163 2 : idx = (size_t)y * (size_t)width + (size_t)x;
164 2 : d = dist[idx];
165 2 : base = idx * 4;
166 :
167 2 : if (rgba[base + 3] <= soft_limit && d != UINT16_MAX && d <= max_distance) {
168 1 : rgba[base + 0] = (uint8_t)((seed_rgb[idx] >> 16) & 0xFFu);
169 1 : rgba[base + 1] = (uint8_t)((seed_rgb[idx] >> 8) & 0xFFu);
170 1 : rgba[base + 2] = (uint8_t)(seed_rgb[idx] & 0xFFu);
171 : }
172 : }
173 : }
174 :
175 1 : free(dist);
176 1 : free(seed_rgb);
177 : }
178 :
179 23 : static inline void sanitize_transparent_palette(cpres_rgba_color_t *palette, size_t palette_len) {
180 : size_t i;
181 :
182 23 : if (!palette || palette_len == 0) {
183 0 : return;
184 : }
185 :
186 1576 : for (i = 0; i < palette_len; ++i) {
187 1553 : if (palette[i].a == 0) {
188 12 : palette[i].r = 0;
189 12 : palette[i].g = 0;
190 12 : palette[i].b = 0;
191 : }
192 : }
193 : }
194 :
195 21 : static inline bool is_smooth_gradient_profile(const pngx_image_stats_t *stats, const pngx_options_t *opts) {
196 : float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
197 :
198 21 : if (!stats) {
199 0 : return false;
200 : }
201 :
202 21 : opaque_ratio_threshold = (opts ? opts->palette256_profile_opaque_ratio_threshold : -1.0f);
203 21 : if (opaque_ratio_threshold < 0.0f) {
204 0 : opaque_ratio_threshold = PNGX_PALETTE256_GRADIENT_PROFILE_OPAQUE_RATIO_THRESHOLD;
205 : }
206 :
207 21 : gradient_mean_max = (opts ? opts->palette256_profile_gradient_mean_max : -1.0f);
208 21 : if (gradient_mean_max < 0.0f) {
209 0 : gradient_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_GRADIENT_MEAN_MAX;
210 : }
211 :
212 21 : saturation_mean_max = (opts ? opts->palette256_profile_saturation_mean_max : -1.0f);
213 21 : if (saturation_mean_max < 0.0f) {
214 0 : saturation_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_SATURATION_MEAN_MAX;
215 : }
216 :
217 21 : if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
218 2 : return true;
219 : }
220 :
221 19 : return false;
222 : }
223 :
224 24 : static inline void tune_quant_params_for_image(PngxBridgeQuantParams *params, const pngx_options_t *opts, const pngx_image_stats_t *stats) {
225 : uint8_t quality_min, quality_max;
226 : int32_t speed_max, quality_min_floor, quality_max_target;
227 : float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
228 :
229 24 : if (!params || !opts || !stats) {
230 0 : return;
231 : }
232 :
233 24 : opaque_ratio_threshold = opts->palette256_tune_opaque_ratio_threshold;
234 24 : if (opaque_ratio_threshold < 0.0f) {
235 0 : opaque_ratio_threshold = PNGX_PALETTE256_TUNE_OPAQUE_RATIO_THRESHOLD;
236 : }
237 24 : gradient_mean_max = opts->palette256_tune_gradient_mean_max;
238 24 : if (gradient_mean_max < 0.0f) {
239 0 : gradient_mean_max = PNGX_PALETTE256_TUNE_GRADIENT_MEAN_MAX;
240 : }
241 24 : saturation_mean_max = opts->palette256_tune_saturation_mean_max;
242 24 : if (saturation_mean_max < 0.0f) {
243 0 : saturation_mean_max = PNGX_PALETTE256_TUNE_SATURATION_MEAN_MAX;
244 : }
245 :
246 24 : speed_max = opts->palette256_tune_speed_max;
247 24 : if (speed_max < 0) {
248 0 : speed_max = PNGX_PALETTE256_TUNE_SPEED_MAX;
249 : }
250 24 : quality_min_floor = opts->palette256_tune_quality_min_floor;
251 24 : if (quality_min_floor < 0) {
252 0 : quality_min_floor = PNGX_PALETTE256_TUNE_QUALITY_MIN_FLOOR;
253 : }
254 24 : quality_max_target = opts->palette256_tune_quality_max_target;
255 24 : if (quality_max_target < 0) {
256 0 : quality_max_target = PNGX_PALETTE256_TUNE_QUALITY_MAX_TARGET;
257 : }
258 :
259 24 : if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
260 4 : if (params->speed > speed_max) {
261 3 : params->speed = speed_max;
262 : }
263 :
264 4 : quality_min = params->quality_min;
265 4 : quality_max = params->quality_max;
266 :
267 4 : if (quality_max < (uint8_t)quality_max_target) {
268 3 : quality_max = (uint8_t)quality_max_target;
269 : }
270 4 : if (quality_min < (uint8_t)quality_min_floor) {
271 3 : quality_min = (uint8_t)quality_min_floor;
272 : }
273 4 : if (quality_min > quality_max) {
274 1 : quality_min = quality_max;
275 : }
276 :
277 4 : params->quality_min = quality_min;
278 4 : params->quality_max = quality_max;
279 : }
280 : }
281 :
282 4 : static void postprocess_indices_parallel_worker(void *context, uint32_t start, uint32_t end) {
283 4 : postprocess_indices_parallel_ctx_t *ctx = (postprocess_indices_parallel_ctx_t *)context;
284 : uint32_t x, y, dist_sq, idx_start_y, idx_end_y;
285 : uint8_t base_color, importance, neighbor_colors[4], neighbor_used, candidate;
286 : size_t idx;
287 : bool neighbor_has_base;
288 :
289 4 : if (!ctx || !ctx->indices || !ctx->reference || !ctx->importance_map) {
290 0 : return;
291 : }
292 :
293 4 : idx_start_y = start;
294 4 : idx_end_y = end;
295 4 : if (idx_end_y > ctx->height) {
296 0 : idx_end_y = ctx->height;
297 : }
298 :
299 165 : for (y = idx_start_y; y < idx_end_y; ++y) {
300 9379 : for (x = 0; x < ctx->width; ++x) {
301 9218 : idx = (size_t)y * (size_t)ctx->width + (size_t)x;
302 9218 : base_color = ctx->reference[idx];
303 9218 : importance = ctx->importance_map[idx];
304 9218 : neighbor_used = 0;
305 9218 : neighbor_has_base = false;
306 :
307 9218 : if (ctx->cutoff >= 0.0f && ((float)importance / 255.0f) >= ctx->cutoff) {
308 2 : continue;
309 : }
310 :
311 9216 : if (x > 0) {
312 9056 : neighbor_colors[neighbor_used] = ctx->reference[idx - 1];
313 9056 : if (neighbor_colors[neighbor_used] == base_color) {
314 8918 : neighbor_has_base = true;
315 : }
316 9056 : ++neighbor_used;
317 : }
318 9216 : if (x + 1 < ctx->width) {
319 9055 : neighbor_colors[neighbor_used] = ctx->reference[idx + 1];
320 9055 : if (neighbor_colors[neighbor_used] == base_color) {
321 8918 : neighbor_has_base = true;
322 : }
323 9055 : ++neighbor_used;
324 : }
325 9216 : if (y > 0) {
326 9055 : neighbor_colors[neighbor_used] = ctx->reference[idx - (size_t)ctx->width];
327 9055 : if (neighbor_colors[neighbor_used] == base_color) {
328 8977 : neighbor_has_base = true;
329 : }
330 9055 : ++neighbor_used;
331 : }
332 9216 : if (y + 1 < ctx->height) {
333 9055 : neighbor_colors[neighbor_used] = ctx->reference[idx + (size_t)ctx->width];
334 9055 : if (neighbor_colors[neighbor_used] == base_color) {
335 8977 : neighbor_has_base = true;
336 : }
337 9055 : ++neighbor_used;
338 : }
339 :
340 9216 : if (neighbor_used < 3) {
341 13 : continue;
342 : }
343 :
344 9203 : candidate = neighbor_colors[0];
345 9203 : if (neighbor_used == 3) {
346 616 : if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate)) {
347 20 : continue;
348 : }
349 : } else {
350 8587 : if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate && neighbor_colors[3] == candidate)) {
351 354 : continue;
352 : }
353 : }
354 :
355 8829 : if (candidate == base_color) {
356 8828 : continue;
357 : }
358 :
359 1 : if (neighbor_has_base) {
360 0 : continue;
361 : }
362 :
363 1 : if (ctx->palette && ctx->palette_len > 0 && (size_t)base_color < ctx->palette_len && (size_t)candidate < ctx->palette_len) {
364 1 : dist_sq = color_distance_sq(&ctx->palette[base_color], &ctx->palette[candidate]);
365 1 : if (dist_sq > PNGX_POSTPROCESS_MAX_COLOR_DISTANCE_SQ) {
366 0 : continue;
367 : }
368 : }
369 :
370 1 : ctx->indices[idx] = candidate;
371 : }
372 : }
373 : }
374 :
375 23 : static inline void postprocess_indices(uint32_t thread_count, uint8_t *indices, uint32_t width, uint32_t height, const cpres_rgba_color_t *palette, size_t palette_len,
376 : const pngx_quant_support_t *support, const pngx_options_t *opts) {
377 : uint8_t *reference;
378 : size_t pixel_count;
379 : float cutoff;
380 : postprocess_indices_parallel_ctx_t ctx;
381 :
382 23 : if (!indices || !support || !opts || width == 0 || height == 0) {
383 19 : return;
384 : }
385 :
386 23 : if (!opts->postprocess_smooth_enable) {
387 2 : return;
388 : }
389 :
390 21 : if (opts->lossy_dither_level >= PNGX_POSTPROCESS_DISABLE_DITHER_THRESHOLD) {
391 17 : return;
392 : }
393 :
394 4 : if (!support->importance_map || support->importance_map_len < (size_t)width * (size_t)height) {
395 0 : return;
396 : }
397 :
398 4 : cutoff = opts->postprocess_smooth_importance_cutoff;
399 4 : if (cutoff >= 0.0f) {
400 3 : if (cutoff > 1.0f) {
401 0 : cutoff = 1.0f;
402 : }
403 : } else {
404 1 : cutoff = -1.0f;
405 : }
406 :
407 4 : pixel_count = (size_t)width * (size_t)height;
408 :
409 4 : reference = (uint8_t *)malloc(pixel_count);
410 4 : if (!reference) {
411 0 : return;
412 : }
413 :
414 4 : memcpy(reference, indices, pixel_count);
415 :
416 4 : ctx.indices = indices;
417 4 : ctx.reference = reference;
418 4 : ctx.width = width;
419 4 : ctx.height = height;
420 4 : ctx.palette = palette;
421 4 : ctx.palette_len = palette_len;
422 4 : ctx.importance_map = support->importance_map;
423 4 : ctx.cutoff = cutoff;
424 :
425 : #if COLOPRESSO_ENABLE_THREADS
426 4 : colopresso_parallel_for(thread_count, height, postprocess_indices_parallel_worker, &ctx);
427 : #else
428 : postprocess_indices_parallel_worker(&ctx, 0, height);
429 : #endif
430 :
431 4 : free(reference);
432 : }
433 :
434 24 : static inline void fill_quant_params(PngxBridgeQuantParams *params, const pngx_options_t *opts, const uint8_t *importance_map, size_t importance_map_len) {
435 : uint8_t quality_min, quality_max;
436 :
437 24 : if (!params || !opts) {
438 0 : return;
439 : }
440 :
441 24 : quality_min = clamp_uint8_t(opts->lossy_quality_min, 0, 100);
442 24 : quality_max = clamp_uint8_t(opts->lossy_quality_max, quality_min, 100);
443 24 : params->speed = clamp_int32_t((int32_t)opts->lossy_speed, 1, 10);
444 24 : params->quality_min = quality_min;
445 24 : params->quality_max = quality_max;
446 24 : params->max_colors = clamp_uint32_t((uint32_t)opts->lossy_max_colors, 2, 256);
447 24 : params->min_posterization = -1;
448 24 : params->dithering_level = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
449 24 : params->importance_map = importance_map;
450 24 : params->importance_map_len = importance_map_len;
451 24 : params->fixed_colors = opts->protected_colors;
452 24 : params->fixed_colors_len = (size_t)((opts->protected_colors_count > 0) ? opts->protected_colors_count : 0);
453 24 : params->remap = true;
454 : }
455 :
456 24 : static inline void free_quant_output(PngxBridgeQuantOutput *output) {
457 24 : if (!output) {
458 0 : return;
459 : }
460 :
461 24 : if (output->palette) {
462 23 : pngx_bridge_free((uint8_t *)output->palette);
463 23 : output->palette = NULL;
464 : }
465 24 : output->palette_len = 0;
466 :
467 24 : if (output->indices) {
468 23 : pngx_bridge_free(output->indices);
469 23 : output->indices = NULL;
470 : }
471 :
472 24 : output->indices_len = 0;
473 : }
474 :
475 40 : static inline bool init_write_struct(png_structp *png_ptr, png_infop *info_ptr) {
476 : png_structp tmp_png;
477 : png_infop tmp_info;
478 :
479 40 : if (!png_ptr || !info_ptr) {
480 0 : return false;
481 : }
482 :
483 40 : tmp_png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
484 40 : if (!tmp_png) {
485 0 : return false;
486 : }
487 :
488 40 : tmp_info = png_create_info_struct(tmp_png);
489 40 : if (!tmp_info) {
490 0 : png_destroy_write_struct(&tmp_png, NULL);
491 0 : return false;
492 : }
493 :
494 40 : *png_ptr = tmp_png;
495 40 : *info_ptr = tmp_info;
496 :
497 40 : return true;
498 : }
499 :
500 3618 : static inline bool memory_buffer_reserve(png_memory_buffer_t *buffer, size_t additional) {
501 : uint8_t *new_data;
502 : size_t required, capacity;
503 :
504 3618 : if (!buffer || additional > SIZE_MAX - buffer->size) {
505 0 : return false;
506 : }
507 :
508 3618 : required = buffer->size + additional;
509 3618 : capacity = buffer->capacity ? buffer->capacity : 4096;
510 :
511 3704 : while (capacity < required) {
512 86 : if (capacity > SIZE_MAX / 2) {
513 0 : capacity = required;
514 0 : break;
515 : }
516 :
517 86 : capacity *= 2;
518 : }
519 :
520 3618 : new_data = (uint8_t *)realloc(buffer->data, capacity);
521 3618 : if (!new_data) {
522 0 : return false;
523 : }
524 :
525 3618 : buffer->data = new_data;
526 3618 : buffer->capacity = capacity;
527 :
528 3618 : return true;
529 : }
530 :
531 : /* Excluded from coverage as this is only called in rare cases such as malloc failure. */
532 : /* LCOV_EXCL_START */
533 : static inline void memory_buffer_reset(png_memory_buffer_t *buffer) {
534 : if (!buffer) {
535 : return;
536 : }
537 :
538 : free(buffer->data);
539 : buffer->data = NULL;
540 : buffer->size = 0;
541 : buffer->capacity = 0;
542 : }
543 : /* LCOV_EXCL_STOP */
544 :
545 3618 : static inline void memory_write(png_structp png_ptr, png_bytep data, png_size_t length) {
546 3618 : png_memory_buffer_t *buffer = (png_memory_buffer_t *)png_get_io_ptr(png_ptr);
547 :
548 3618 : if (!buffer || !memory_buffer_reserve(buffer, length)) {
549 0 : png_error(png_ptr, "png write failure");
550 : return;
551 : }
552 :
553 3618 : memcpy(buffer->data + buffer->size, data, length);
554 :
555 3618 : buffer->size += length;
556 : }
557 :
558 40 : static inline bool finalize_memory_png(png_memory_buffer_t *buffer, uint8_t **out_data, size_t *out_size) {
559 : uint8_t *shrunk;
560 :
561 40 : if (!buffer || !out_data || !out_size) {
562 0 : return false;
563 : }
564 :
565 40 : if (buffer->data) {
566 40 : shrunk = (uint8_t *)realloc(buffer->data, buffer->size);
567 40 : if (shrunk) {
568 40 : buffer->data = shrunk;
569 40 : buffer->capacity = buffer->size;
570 : }
571 : }
572 :
573 40 : *out_data = buffer->data;
574 40 : *out_size = buffer->size;
575 40 : if (!buffer->data || buffer->size == 0) {
576 0 : memory_buffer_reset(buffer);
577 0 : return false;
578 : }
579 :
580 40 : return true;
581 : }
582 :
583 40 : static inline png_bytep *build_row_pointers(const uint8_t *data, size_t row_stride, uint32_t height) {
584 : png_bytep *row_pointers;
585 : uint32_t y;
586 :
587 40 : if (!data || row_stride == 0 || height == 0) {
588 0 : return NULL;
589 : }
590 :
591 40 : row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
592 40 : if (!row_pointers) {
593 0 : return NULL;
594 : }
595 :
596 11983 : for (y = 0; y < height; ++y) {
597 11943 : row_pointers[y] = (png_bytep)(data + (size_t)y * row_stride);
598 : }
599 :
600 40 : return row_pointers;
601 : }
602 :
603 23 : extern bool pngx_create_palette_png(const uint8_t *indices, size_t indices_len, const cpres_rgba_color_t *palette, size_t palette_len, uint32_t width, uint32_t height, uint8_t **out_data,
604 : size_t *out_size) {
605 : png_color palette_data[256];
606 : png_byte alpha_data[256];
607 : png_structp png_ptr;
608 : png_infop info_ptr;
609 : png_bytep *row_pointers;
610 : png_memory_buffer_t buffer;
611 : size_t expected_len, i;
612 : int num_trans;
613 :
614 23 : if (!indices || !palette || !out_data || !out_size || width == 0 || height == 0) {
615 0 : return false;
616 : }
617 :
618 23 : expected_len = (size_t)width * (size_t)height;
619 23 : if (indices_len != expected_len || palette_len == 0 || palette_len > 256) {
620 0 : return false;
621 : }
622 :
623 23 : if (!init_write_struct(&png_ptr, &info_ptr)) {
624 0 : return false;
625 : }
626 :
627 23 : buffer.data = NULL;
628 23 : buffer.size = 0;
629 23 : buffer.capacity = 0;
630 23 : row_pointers = NULL;
631 :
632 23 : if (setjmp(png_jmpbuf(png_ptr)) != 0) {
633 0 : memory_buffer_reset(&buffer);
634 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
635 0 : free(row_pointers);
636 0 : return false;
637 : }
638 :
639 23 : png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
640 23 : png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
641 23 : png_set_compression_strategy(png_ptr, Z_FILTERED);
642 23 : png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
643 23 : png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
644 :
645 23 : num_trans = 0;
646 1576 : for (i = 0; i < palette_len; ++i) {
647 1553 : palette_data[i].red = palette[i].r;
648 1553 : palette_data[i].green = palette[i].g;
649 1553 : palette_data[i].blue = palette[i].b;
650 1553 : alpha_data[i] = palette[i].a;
651 1553 : if (palette[i].a != 255) {
652 1435 : num_trans = (int)(i + 1);
653 : }
654 : }
655 :
656 23 : png_set_PLTE(png_ptr, info_ptr, palette_data, (int)palette_len);
657 23 : if (num_trans > 0) {
658 19 : png_set_tRNS(png_ptr, info_ptr, alpha_data, num_trans, NULL);
659 : }
660 :
661 23 : png_write_info(png_ptr, info_ptr);
662 :
663 23 : row_pointers = build_row_pointers(indices, (size_t)width, height);
664 23 : if (!row_pointers) {
665 0 : png_error(png_ptr, "png row allocation failed");
666 : return false;
667 : }
668 :
669 23 : png_write_image(png_ptr, row_pointers);
670 23 : png_write_end(png_ptr, NULL);
671 :
672 23 : free(row_pointers);
673 23 : png_destroy_write_struct(&png_ptr, &info_ptr);
674 :
675 23 : return finalize_memory_png(&buffer, out_data, out_size);
676 : }
677 :
678 17 : extern bool create_rgba_png(const uint8_t *rgba, size_t pixel_count, uint32_t width, uint32_t height, uint8_t **out_data, size_t *out_size) {
679 : png_structp png_ptr;
680 : png_infop info_ptr;
681 : png_bytep *row_pointers;
682 : png_memory_buffer_t buffer;
683 : size_t expected_pixels;
684 :
685 17 : if (!rgba || !out_data || !out_size || width == 0 || height == 0) {
686 0 : return false;
687 : }
688 :
689 17 : expected_pixels = (size_t)width * (size_t)height;
690 17 : if (pixel_count != expected_pixels) {
691 0 : return false;
692 : }
693 :
694 17 : if (!init_write_struct(&png_ptr, &info_ptr)) {
695 0 : return false;
696 : }
697 :
698 17 : buffer.data = NULL;
699 17 : buffer.size = 0;
700 17 : buffer.capacity = 0;
701 17 : row_pointers = NULL;
702 :
703 17 : if (setjmp(png_jmpbuf(png_ptr)) != 0) {
704 0 : memory_buffer_reset(&buffer);
705 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
706 0 : free(row_pointers);
707 0 : return false;
708 : }
709 :
710 17 : png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
711 17 : png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
712 17 : png_set_compression_strategy(png_ptr, Z_FILTERED);
713 17 : png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
714 17 : png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
715 17 : png_write_info(png_ptr, info_ptr);
716 :
717 17 : row_pointers = build_row_pointers(rgba, (size_t)width * PNGX_RGBA_CHANNELS, height);
718 17 : if (!row_pointers) {
719 0 : png_error(png_ptr, "png row allocation failed");
720 :
721 : return false;
722 : }
723 :
724 17 : png_write_image(png_ptr, row_pointers);
725 17 : png_write_end(png_ptr, NULL);
726 :
727 17 : free(row_pointers);
728 17 : png_destroy_write_struct(&png_ptr, &info_ptr);
729 :
730 17 : return finalize_memory_png(&buffer, out_data, out_size);
731 : }
732 :
733 24 : bool pngx_quantize_palette256(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint8_t **out_data, size_t *out_size, int *quant_quality) {
734 24 : PngxBridgeQuantParams params = {0}, fallback_params = {0};
735 24 : PngxBridgeQuantOutput output = {0};
736 : PngxBridgeQuantStatus status;
737 : uint8_t *rgba, *importance_map, *fixed_colors, quality_min, quality_max;
738 : uint32_t width, height, max_colors;
739 : int32_t speed;
740 : size_t pixel_count, importance_map_len, fixed_colors_len;
741 : float dither_level;
742 : bool relaxed_quality, success;
743 :
744 24 : if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
745 0 : return false;
746 : }
747 :
748 24 : if (quant_quality) {
749 24 : *quant_quality = -1;
750 : }
751 :
752 24 : if (!pngx_palette256_prepare(png_data, png_size, opts, &rgba, &width, &height, &importance_map, &importance_map_len, &speed, &quality_min, &quality_max, &max_colors, &dither_level, &fixed_colors,
753 : &fixed_colors_len)) {
754 0 : return false;
755 : }
756 :
757 24 : pixel_count = (size_t)width * (size_t)height;
758 :
759 24 : params.speed = speed;
760 24 : params.quality_min = quality_min;
761 24 : params.quality_max = quality_max;
762 24 : params.max_colors = max_colors;
763 24 : params.min_posterization = -1;
764 24 : params.dithering_level = dither_level;
765 24 : params.importance_map = importance_map;
766 24 : params.importance_map_len = importance_map_len;
767 24 : params.fixed_colors = (const cpres_rgba_color_t *)fixed_colors;
768 24 : params.fixed_colors_len = fixed_colors_len;
769 24 : params.remap = true;
770 :
771 24 : output.quality = -1;
772 24 : relaxed_quality = false;
773 :
774 24 : status = pngx_bridge_quantize((const cpres_rgba_color_t *)rgba, pixel_count, width, height, ¶ms, &output);
775 24 : pngx_set_last_error((int)status);
776 :
777 24 : if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW && params.quality_min > 0) {
778 1 : fallback_params = params;
779 1 : fallback_params.quality_min = 0;
780 :
781 1 : if (fallback_params.quality_max < fallback_params.quality_min) {
782 0 : fallback_params.quality_max = fallback_params.quality_min;
783 : }
784 :
785 1 : output.palette = NULL;
786 1 : output.palette_len = 0;
787 1 : output.indices = NULL;
788 1 : output.indices_len = 0;
789 1 : output.quality = -1;
790 :
791 1 : status = pngx_bridge_quantize((const cpres_rgba_color_t *)rgba, pixel_count, width, height, &fallback_params, &output);
792 1 : pngx_set_last_error((int)status);
793 1 : if (status == PNGX_BRIDGE_QUANT_STATUS_OK) {
794 1 : relaxed_quality = true;
795 : }
796 : }
797 :
798 24 : if (status != PNGX_BRIDGE_QUANT_STATUS_OK) {
799 1 : free_quant_output(&output);
800 1 : pngx_palette256_cleanup();
801 1 : if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW) {
802 0 : colopresso_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Quantization quality too low");
803 : }
804 :
805 1 : return false;
806 : }
807 :
808 23 : if (relaxed_quality) {
809 1 : colopresso_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Relaxed quantization quality floor");
810 : }
811 :
812 23 : if (quant_quality) {
813 23 : *quant_quality = output.quality;
814 : }
815 :
816 23 : success = false;
817 23 : if (output.indices && output.indices_len == pixel_count && output.palette && output.palette_len > 0 && output.palette_len <= 256) {
818 23 : success = pngx_palette256_finalize(output.indices, output.indices_len, output.palette, output.palette_len, out_data, out_size);
819 : } else {
820 0 : pngx_palette256_cleanup();
821 : }
822 :
823 23 : free_quant_output(&output);
824 :
825 23 : return success;
826 : }
827 :
828 24 : bool pngx_palette256_prepare(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint8_t **out_rgba, uint32_t *out_width, uint32_t *out_height, uint8_t **out_importance_map,
829 : size_t *out_importance_map_len, int32_t *out_speed, uint8_t *out_quality_min, uint8_t *out_quality_max, uint32_t *out_max_colors, float *out_dither_level,
830 : uint8_t **out_fixed_colors, size_t *out_fixed_colors_len) {
831 : float estimated_dither, gradient_dither_floor;
832 24 : PngxBridgeQuantParams params = {0};
833 :
834 24 : if (!png_data || png_size == 0 || !opts || !out_rgba || !out_width || !out_height) {
835 0 : return false;
836 : }
837 :
838 24 : if (g_palette256_ctx.initialized) {
839 0 : rgba_image_reset(&g_palette256_ctx.image);
840 0 : quant_support_reset(&g_palette256_ctx.support);
841 0 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
842 : }
843 :
844 24 : if (!load_rgba_image(png_data, png_size, &g_palette256_ctx.image)) {
845 0 : return false;
846 : }
847 :
848 24 : alpha_bleed_rgb_from_opaque(g_palette256_ctx.image.rgba, g_palette256_ctx.image.width, g_palette256_ctx.image.height, opts);
849 :
850 24 : image_stats_reset(&g_palette256_ctx.stats);
851 24 : if (!prepare_quant_support(&g_palette256_ctx.image, opts, &g_palette256_ctx.support, &g_palette256_ctx.stats)) {
852 0 : rgba_image_reset(&g_palette256_ctx.image);
853 0 : quant_support_reset(&g_palette256_ctx.support);
854 0 : return false;
855 : }
856 :
857 24 : g_palette256_ctx.tuned_opts = *opts;
858 24 : g_palette256_ctx.prefer_uniform = opts->palette256_gradient_profile_enable ? is_smooth_gradient_profile(&g_palette256_ctx.stats, &g_palette256_ctx.tuned_opts) : false;
859 :
860 24 : if (g_palette256_ctx.prefer_uniform) {
861 2 : g_palette256_ctx.tuned_opts.saliency_map_enable = false;
862 2 : g_palette256_ctx.tuned_opts.chroma_anchor_enable = false;
863 2 : g_palette256_ctx.tuned_opts.postprocess_smooth_enable = false;
864 : } else {
865 22 : build_fixed_palette(opts, &g_palette256_ctx.support, &g_palette256_ctx.tuned_opts);
866 : }
867 :
868 24 : g_palette256_ctx.resolved_dither = resolve_quant_dither(opts, &g_palette256_ctx.stats);
869 :
870 24 : if (opts->lossy_dither_auto) {
871 0 : estimated_dither = estimate_bitdepth_dither_level(g_palette256_ctx.image.rgba, g_palette256_ctx.image.width, g_palette256_ctx.image.height, 8);
872 0 : if (estimated_dither > g_palette256_ctx.resolved_dither) {
873 0 : g_palette256_ctx.resolved_dither = estimated_dither;
874 : }
875 : }
876 :
877 24 : gradient_dither_floor = g_palette256_ctx.tuned_opts.palette256_gradient_profile_dither_floor;
878 24 : if (gradient_dither_floor < 0.0f) {
879 0 : gradient_dither_floor = PNGX_PALETTE256_GRADIENT_PROFILE_DITHER_FLOOR;
880 : }
881 :
882 24 : if (g_palette256_ctx.prefer_uniform && g_palette256_ctx.resolved_dither < gradient_dither_floor) {
883 2 : g_palette256_ctx.resolved_dither = gradient_dither_floor;
884 : }
885 :
886 24 : g_palette256_ctx.tuned_opts.lossy_dither_level = g_palette256_ctx.resolved_dither;
887 :
888 24 : fill_quant_params(¶ms, &g_palette256_ctx.tuned_opts, g_palette256_ctx.prefer_uniform ? NULL : g_palette256_ctx.support.importance_map,
889 24 : g_palette256_ctx.prefer_uniform ? 0 : g_palette256_ctx.support.importance_map_len);
890 24 : params.dithering_level = g_palette256_ctx.resolved_dither;
891 24 : tune_quant_params_for_image(¶ms, &g_palette256_ctx.tuned_opts, &g_palette256_ctx.stats);
892 :
893 24 : *out_rgba = g_palette256_ctx.image.rgba;
894 24 : *out_width = g_palette256_ctx.image.width;
895 24 : *out_height = g_palette256_ctx.image.height;
896 :
897 24 : if (out_importance_map && out_importance_map_len) {
898 24 : if (!g_palette256_ctx.prefer_uniform && g_palette256_ctx.support.importance_map) {
899 22 : *out_importance_map = g_palette256_ctx.support.importance_map;
900 22 : *out_importance_map_len = g_palette256_ctx.support.importance_map_len;
901 : } else {
902 2 : *out_importance_map = NULL;
903 2 : *out_importance_map_len = 0;
904 : }
905 : }
906 :
907 24 : if (out_speed)
908 24 : *out_speed = params.speed;
909 24 : if (out_quality_min)
910 24 : *out_quality_min = params.quality_min;
911 24 : if (out_quality_max)
912 24 : *out_quality_max = params.quality_max;
913 24 : if (out_max_colors)
914 24 : *out_max_colors = params.max_colors;
915 24 : if (out_dither_level)
916 24 : *out_dither_level = params.dithering_level;
917 :
918 24 : if (out_fixed_colors && out_fixed_colors_len) {
919 24 : if (params.fixed_colors && params.fixed_colors_len > 0) {
920 19 : *out_fixed_colors = (uint8_t *)params.fixed_colors;
921 19 : *out_fixed_colors_len = params.fixed_colors_len;
922 : } else {
923 5 : *out_fixed_colors = NULL;
924 5 : *out_fixed_colors_len = 0;
925 : }
926 : }
927 :
928 24 : g_palette256_ctx.initialized = true;
929 24 : return true;
930 : }
931 :
932 23 : bool pngx_palette256_finalize(const uint8_t *indices, size_t indices_len, const cpres_rgba_color_t *palette, size_t palette_len, uint8_t **out_data, size_t *out_size) {
933 : uint8_t *mutable_indices;
934 : cpres_rgba_color_t *mutable_palette;
935 : bool success;
936 :
937 23 : if (!g_palette256_ctx.initialized) {
938 0 : return false;
939 : }
940 :
941 23 : if (!indices || indices_len == 0 || !palette || palette_len == 0 || palette_len > 256 || !out_data || !out_size) {
942 0 : rgba_image_reset(&g_palette256_ctx.image);
943 0 : quant_support_reset(&g_palette256_ctx.support);
944 0 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
945 :
946 0 : return false;
947 : }
948 :
949 23 : if (indices_len != g_palette256_ctx.image.pixel_count) {
950 0 : rgba_image_reset(&g_palette256_ctx.image);
951 0 : quant_support_reset(&g_palette256_ctx.support);
952 0 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
953 :
954 0 : return false;
955 : }
956 :
957 23 : mutable_palette = (cpres_rgba_color_t *)malloc(sizeof(cpres_rgba_color_t) * palette_len);
958 23 : if (!mutable_palette) {
959 0 : rgba_image_reset(&g_palette256_ctx.image);
960 0 : quant_support_reset(&g_palette256_ctx.support);
961 0 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
962 :
963 0 : return false;
964 : }
965 23 : memcpy(mutable_palette, palette, sizeof(cpres_rgba_color_t) * palette_len);
966 23 : sanitize_transparent_palette(mutable_palette, palette_len);
967 :
968 23 : mutable_indices = (uint8_t *)malloc(indices_len);
969 23 : if (!mutable_indices) {
970 0 : free(mutable_palette);
971 0 : rgba_image_reset(&g_palette256_ctx.image);
972 0 : quant_support_reset(&g_palette256_ctx.support);
973 0 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
974 0 : return false;
975 : }
976 :
977 23 : memcpy(mutable_indices, indices, indices_len);
978 :
979 23 : postprocess_indices(g_palette256_ctx.tuned_opts.thread_count, mutable_indices, g_palette256_ctx.image.width, g_palette256_ctx.image.height, mutable_palette, palette_len, &g_palette256_ctx.support,
980 : &g_palette256_ctx.tuned_opts);
981 :
982 23 : success = pngx_create_palette_png(mutable_indices, indices_len, mutable_palette, palette_len, g_palette256_ctx.image.width, g_palette256_ctx.image.height, out_data, out_size);
983 :
984 23 : free(mutable_indices);
985 23 : free(mutable_palette);
986 :
987 23 : rgba_image_reset(&g_palette256_ctx.image);
988 23 : quant_support_reset(&g_palette256_ctx.support);
989 23 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
990 :
991 23 : return success;
992 : }
993 :
994 1 : void pngx_palette256_cleanup(void) {
995 1 : if (g_palette256_ctx.initialized) {
996 1 : rgba_image_reset(&g_palette256_ctx.image);
997 1 : quant_support_reset(&g_palette256_ctx.support);
998 1 : memset(&g_palette256_ctx, 0, sizeof(g_palette256_ctx));
999 : }
1000 1 : }
|