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 <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 :
21 24 : static inline void alpha_bleed_rgb_from_opaque(uint8_t *rgba, uint32_t width, uint32_t height, const pngx_options_t *opts) {
22 : uint32_t *seed_rgb, x, y, best_rgb;
23 : uint16_t *dist, best, d;
24 : uint16_t max_distance;
25 : uint8_t opaque_threshold, soft_limit;
26 : size_t pixel_count, i, base, idx;
27 : bool has_seed;
28 :
29 24 : if (!rgba || width == 0 || height == 0) {
30 0 : return;
31 : }
32 :
33 24 : if (!opts || !opts->palette256_alpha_bleed_enable) {
34 1 : return;
35 : }
36 :
37 23 : max_distance = opts->palette256_alpha_bleed_max_distance;
38 23 : opaque_threshold = opts->palette256_alpha_bleed_opaque_threshold;
39 23 : soft_limit = opts->palette256_alpha_bleed_soft_limit;
40 :
41 23 : if ((size_t)width > SIZE_MAX / (size_t)height) {
42 0 : return;
43 : }
44 23 : pixel_count = (size_t)width * (size_t)height;
45 :
46 23 : dist = (uint16_t *)malloc(sizeof(uint16_t) * pixel_count);
47 23 : seed_rgb = (uint32_t *)malloc(sizeof(uint32_t) * pixel_count);
48 23 : if (!dist || !seed_rgb) {
49 0 : free(dist);
50 0 : free(seed_rgb);
51 0 : return;
52 : }
53 :
54 23 : has_seed = false;
55 10671513 : for (i = 0; i < pixel_count; ++i) {
56 10671490 : base = i * 4;
57 10671490 : if (rgba[base + 3] == 0) {
58 4096 : rgba[base + 0] = 0;
59 4096 : rgba[base + 1] = 0;
60 4096 : rgba[base + 2] = 0;
61 : }
62 :
63 10671490 : if (rgba[base + 3] >= opaque_threshold) {
64 84009 : dist[i] = 0;
65 84009 : seed_rgb[i] = ((uint32_t)rgba[base + 0] << 16) | ((uint32_t)rgba[base + 1] << 8) | (uint32_t)rgba[base + 2];
66 84009 : has_seed = true;
67 : } else {
68 10587481 : dist[i] = UINT16_MAX;
69 10587481 : seed_rgb[i] = 0;
70 : }
71 : }
72 :
73 23 : if (!has_seed) {
74 1 : free(dist);
75 1 : free(seed_rgb);
76 1 : return;
77 : }
78 :
79 88 : for (i = 0; i < 3; ++i) {
80 32421 : for (y = 0; y < height; ++y) {
81 32034537 : for (x = 0; x < width; ++x) {
82 32002182 : idx = (size_t)y * (size_t)width + (size_t)x;
83 32002182 : best = dist[idx];
84 32002182 : best_rgb = seed_rgb[idx];
85 :
86 32002182 : if (x > 0 && dist[idx - 1] != UINT16_MAX && (uint16_t)(dist[idx - 1] + 1) < best) {
87 10570172 : best = (uint16_t)(dist[idx - 1] + 1);
88 10570172 : best_rgb = seed_rgb[idx - 1];
89 : }
90 32002182 : if (y > 0 && dist[idx - (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width] + 1) < best) {
91 4105526 : best = (uint16_t)(dist[idx - (size_t)width] + 1);
92 4105526 : best_rgb = seed_rgb[idx - (size_t)width];
93 : }
94 32002182 : if (x > 0 && y > 0 && dist[idx - (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width - 1] + 1) < best) {
95 202756 : best = (uint16_t)(dist[idx - (size_t)width - 1] + 1);
96 202756 : best_rgb = seed_rgb[idx - (size_t)width - 1];
97 : }
98 32002182 : if (x + 1 < width && y > 0 && dist[idx - (size_t)width + 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width + 1] + 1) < best) {
99 258250 : best = (uint16_t)(dist[idx - (size_t)width + 1] + 1);
100 258250 : best_rgb = seed_rgb[idx - (size_t)width + 1];
101 : }
102 :
103 32002182 : dist[idx] = best;
104 32002182 : seed_rgb[idx] = best_rgb;
105 : }
106 : }
107 :
108 32421 : for (y = height; y-- > 0;) {
109 32034537 : for (x = width; x-- > 0;) {
110 32002182 : idx = (size_t)y * (size_t)width + (size_t)x;
111 32002182 : best = dist[idx];
112 32002182 : best_rgb = seed_rgb[idx];
113 :
114 32002182 : if (x + 1 < width && dist[idx + 1] != UINT16_MAX && (uint16_t)(dist[idx + 1] + 1) < best) {
115 4028774 : best = (uint16_t)(dist[idx + 1] + 1);
116 4028774 : best_rgb = seed_rgb[idx + 1];
117 : }
118 32002182 : if (y + 1 < height && dist[idx + (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width] + 1) < best) {
119 2899342 : best = (uint16_t)(dist[idx + (size_t)width] + 1);
120 2899342 : best_rgb = seed_rgb[idx + (size_t)width];
121 : }
122 32002182 : 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) {
123 76180 : best = (uint16_t)(dist[idx + (size_t)width + 1] + 1);
124 76180 : best_rgb = seed_rgb[idx + (size_t)width + 1];
125 : }
126 32002182 : if (x > 0 && y + 1 < height && dist[idx + (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width - 1] + 1) < best) {
127 1460099 : best = (uint16_t)(dist[idx + (size_t)width - 1] + 1);
128 1460099 : best_rgb = seed_rgb[idx + (size_t)width - 1];
129 : }
130 :
131 32002182 : dist[idx] = best;
132 32002182 : seed_rgb[idx] = best_rgb;
133 : }
134 : }
135 : }
136 :
137 10807 : for (y = 0; y < height; ++y) {
138 10678179 : for (x = 0; x < width; ++x) {
139 10667394 : idx = (size_t)y * (size_t)width + (size_t)x;
140 10667394 : d = dist[idx];
141 10667394 : base = idx * 4;
142 :
143 10667394 : if (rgba[base + 3] <= soft_limit && d != UINT16_MAX && d <= max_distance) {
144 1 : rgba[base + 0] = (uint8_t)((seed_rgb[idx] >> 16) & 0xFFu);
145 1 : rgba[base + 1] = (uint8_t)((seed_rgb[idx] >> 8) & 0xFFu);
146 1 : rgba[base + 2] = (uint8_t)(seed_rgb[idx] & 0xFFu);
147 : }
148 : }
149 : }
150 :
151 22 : free(dist);
152 22 : free(seed_rgb);
153 : }
154 :
155 24 : static inline void sanitize_transparent_palette(cpres_rgba_color_t *palette, size_t palette_len) {
156 : size_t i;
157 :
158 24 : if (!palette || palette_len == 0) {
159 0 : return;
160 : }
161 :
162 3145 : for (i = 0; i < palette_len; ++i) {
163 3121 : if (palette[i].a == 0) {
164 1 : palette[i].r = 0;
165 1 : palette[i].g = 0;
166 1 : palette[i].b = 0;
167 : }
168 : }
169 : }
170 :
171 21 : static inline bool is_smooth_gradient_profile(const pngx_image_stats_t *stats, const pngx_options_t *opts) {
172 : float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
173 :
174 21 : if (!stats) {
175 0 : return false;
176 : }
177 :
178 21 : opaque_ratio_threshold = (opts ? opts->palette256_profile_opaque_ratio_threshold : -1.0f);
179 21 : if (opaque_ratio_threshold < 0.0f) {
180 0 : opaque_ratio_threshold = PNGX_PALETTE256_GRADIENT_PROFILE_OPAQUE_RATIO_THRESHOLD;
181 : }
182 :
183 21 : gradient_mean_max = (opts ? opts->palette256_profile_gradient_mean_max : -1.0f);
184 21 : if (gradient_mean_max < 0.0f) {
185 0 : gradient_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_GRADIENT_MEAN_MAX;
186 : }
187 :
188 21 : saturation_mean_max = (opts ? opts->palette256_profile_saturation_mean_max : -1.0f);
189 21 : if (saturation_mean_max < 0.0f) {
190 0 : saturation_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_SATURATION_MEAN_MAX;
191 : }
192 :
193 21 : if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
194 2 : return true;
195 : }
196 :
197 19 : return false;
198 : }
199 :
200 24 : static inline void tune_quant_params_for_image(PngxBridgeQuantParams *params, const pngx_options_t *opts, const pngx_image_stats_t *stats) {
201 : uint8_t quality_min, quality_max;
202 : float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
203 : int32_t speed_max, quality_min_floor, quality_max_target;
204 :
205 24 : if (!params || !opts || !stats) {
206 0 : return;
207 : }
208 :
209 24 : opaque_ratio_threshold = opts->palette256_tune_opaque_ratio_threshold;
210 24 : if (opaque_ratio_threshold < 0.0f) {
211 0 : opaque_ratio_threshold = PNGX_PALETTE256_TUNE_OPAQUE_RATIO_THRESHOLD;
212 : }
213 24 : gradient_mean_max = opts->palette256_tune_gradient_mean_max;
214 24 : if (gradient_mean_max < 0.0f) {
215 0 : gradient_mean_max = PNGX_PALETTE256_TUNE_GRADIENT_MEAN_MAX;
216 : }
217 24 : saturation_mean_max = opts->palette256_tune_saturation_mean_max;
218 24 : if (saturation_mean_max < 0.0f) {
219 0 : saturation_mean_max = PNGX_PALETTE256_TUNE_SATURATION_MEAN_MAX;
220 : }
221 :
222 24 : speed_max = opts->palette256_tune_speed_max;
223 24 : if (speed_max < 0) {
224 0 : speed_max = PNGX_PALETTE256_TUNE_SPEED_MAX;
225 : }
226 24 : quality_min_floor = opts->palette256_tune_quality_min_floor;
227 24 : if (quality_min_floor < 0) {
228 0 : quality_min_floor = PNGX_PALETTE256_TUNE_QUALITY_MIN_FLOOR;
229 : }
230 24 : quality_max_target = opts->palette256_tune_quality_max_target;
231 24 : if (quality_max_target < 0) {
232 0 : quality_max_target = PNGX_PALETTE256_TUNE_QUALITY_MAX_TARGET;
233 : }
234 :
235 24 : if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
236 4 : if (params->speed > speed_max) {
237 3 : params->speed = speed_max;
238 : }
239 :
240 4 : quality_min = params->quality_min;
241 4 : quality_max = params->quality_max;
242 :
243 4 : if (quality_max < (uint8_t)quality_max_target) {
244 3 : quality_max = (uint8_t)quality_max_target;
245 : }
246 4 : if (quality_min < (uint8_t)quality_min_floor) {
247 3 : quality_min = (uint8_t)quality_min_floor;
248 : }
249 4 : if (quality_min > quality_max) {
250 1 : quality_min = quality_max;
251 : }
252 :
253 4 : params->quality_min = quality_min;
254 4 : params->quality_max = quality_max;
255 : }
256 : }
257 :
258 24 : static inline void postprocess_indices(uint8_t *indices, uint32_t width, uint32_t height, const cpres_rgba_color_t *palette, size_t palette_len, const pngx_quant_support_t *support,
259 : const pngx_options_t *opts) {
260 : uint32_t x, y, dist_sq;
261 : uint8_t base_color, importance, neighbor_colors[4], neighbor_used, *reference, candidate;
262 : size_t pixel_count, idx;
263 : float cutoff;
264 : bool neighbor_has_base;
265 :
266 24 : if (!indices || !support || !opts || width == 0 || height == 0) {
267 19 : return;
268 : }
269 :
270 24 : if (!opts->postprocess_smooth_enable) {
271 2 : return;
272 : }
273 :
274 22 : if (opts->lossy_dither_level >= PNGX_POSTPROCESS_DISABLE_DITHER_THRESHOLD) {
275 17 : return;
276 : }
277 :
278 5 : if (!support->importance_map || support->importance_map_len < (size_t)width * (size_t)height) {
279 0 : return;
280 : }
281 :
282 5 : cutoff = opts->postprocess_smooth_importance_cutoff;
283 5 : if (cutoff >= 0.0f) {
284 4 : if (cutoff > 1.0f) {
285 0 : cutoff = 1.0f;
286 : }
287 : } else {
288 1 : cutoff = -1.0f;
289 : }
290 :
291 5 : pixel_count = (size_t)width * (size_t)height;
292 :
293 5 : reference = (uint8_t *)malloc(pixel_count);
294 5 : if (!reference) {
295 0 : return;
296 : }
297 :
298 5 : memcpy(reference, indices, pixel_count);
299 :
300 230 : for (y = 0; y < height; ++y) {
301 13539 : for (x = 0; x < width; ++x) {
302 13314 : idx = (size_t)y * (size_t)width + (size_t)x;
303 13314 : base_color = reference[idx];
304 13314 : importance = support->importance_map[idx];
305 13314 : neighbor_used = 0;
306 13314 : neighbor_has_base = false;
307 :
308 13314 : if (cutoff >= 0.0f && ((float)importance / 255.0f) >= cutoff) {
309 2 : continue;
310 : }
311 :
312 13312 : if (x > 0) {
313 13088 : neighbor_colors[neighbor_used] = reference[idx - 1];
314 13088 : if (neighbor_colors[neighbor_used] == base_color) {
315 12950 : neighbor_has_base = true;
316 : }
317 13088 : ++neighbor_used;
318 : }
319 13312 : if (x + 1 < width) {
320 13087 : neighbor_colors[neighbor_used] = reference[idx + 1];
321 13087 : if (neighbor_colors[neighbor_used] == base_color) {
322 12950 : neighbor_has_base = true;
323 : }
324 13087 : ++neighbor_used;
325 : }
326 13312 : if (y > 0) {
327 13087 : neighbor_colors[neighbor_used] = reference[idx - (size_t)width];
328 13087 : if (neighbor_colors[neighbor_used] == base_color) {
329 13009 : neighbor_has_base = true;
330 : }
331 13087 : ++neighbor_used;
332 : }
333 13312 : if (y + 1 < height) {
334 13087 : neighbor_colors[neighbor_used] = reference[idx + (size_t)width];
335 13087 : if (neighbor_colors[neighbor_used] == base_color) {
336 13009 : neighbor_has_base = true;
337 : }
338 13087 : ++neighbor_used;
339 : }
340 :
341 13312 : if (neighbor_used < 3) {
342 17 : continue;
343 : }
344 :
345 13295 : candidate = neighbor_colors[0];
346 13295 : if (neighbor_used == 3) {
347 864 : if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate)) {
348 20 : continue;
349 : }
350 : } else {
351 12431 : if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate && neighbor_colors[3] == candidate)) {
352 354 : continue;
353 : }
354 : }
355 :
356 12921 : if (candidate == base_color) {
357 12920 : continue;
358 : }
359 :
360 1 : if (neighbor_has_base) {
361 0 : continue;
362 : }
363 :
364 1 : if (palette && palette_len > 0 && (size_t)base_color < palette_len && (size_t)candidate < palette_len) {
365 1 : dist_sq = color_distance_sq(&palette[base_color], &palette[candidate]);
366 1 : if (dist_sq > PNGX_POSTPROCESS_MAX_COLOR_DISTANCE_SQ) {
367 0 : continue;
368 : }
369 : }
370 :
371 1 : indices[idx] = candidate;
372 : }
373 : }
374 :
375 5 : free(reference);
376 : }
377 :
378 24 : static inline void fill_quant_params(PngxBridgeQuantParams *params, const pngx_options_t *opts, const uint8_t *importance_map, size_t importance_map_len) {
379 : uint8_t quality_min, quality_max;
380 :
381 24 : if (!params || !opts) {
382 0 : return;
383 : }
384 :
385 24 : quality_min = clamp_uint8_t(opts->lossy_quality_min, 0, 100);
386 24 : quality_max = clamp_uint8_t(opts->lossy_quality_max, quality_min, 100);
387 24 : params->speed = clamp_int32_t((int32_t)opts->lossy_speed, 1, 10);
388 24 : params->quality_min = quality_min;
389 24 : params->quality_max = quality_max;
390 24 : params->max_colors = clamp_uint32_t((uint32_t)opts->lossy_max_colors, 2, 256);
391 24 : params->min_posterization = -1;
392 24 : params->dithering_level = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
393 24 : params->importance_map = importance_map;
394 24 : params->importance_map_len = importance_map_len;
395 24 : params->fixed_colors = opts->protected_colors;
396 24 : params->fixed_colors_len = (size_t)((opts->protected_colors_count > 0) ? opts->protected_colors_count : 0);
397 24 : params->remap = true;
398 : }
399 :
400 24 : static inline void free_quant_output(PngxBridgeQuantOutput *output) {
401 24 : if (!output) {
402 0 : return;
403 : }
404 :
405 24 : if (output->palette) {
406 24 : pngx_bridge_free((uint8_t *)output->palette);
407 24 : output->palette = NULL;
408 : }
409 24 : output->palette_len = 0;
410 :
411 24 : if (output->indices) {
412 24 : pngx_bridge_free(output->indices);
413 24 : output->indices = NULL;
414 : }
415 :
416 24 : output->indices_len = 0;
417 : }
418 :
419 41 : static inline bool init_write_struct(png_structp *png_ptr, png_infop *info_ptr) {
420 : png_structp tmp_png;
421 : png_infop tmp_info;
422 :
423 41 : if (!png_ptr || !info_ptr) {
424 0 : return false;
425 : }
426 :
427 41 : tmp_png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
428 41 : if (!tmp_png) {
429 0 : return false;
430 : }
431 :
432 41 : tmp_info = png_create_info_struct(tmp_png);
433 41 : if (!tmp_info) {
434 0 : png_destroy_write_struct(&tmp_png, NULL);
435 0 : return false;
436 : }
437 :
438 41 : *png_ptr = tmp_png;
439 41 : *info_ptr = tmp_info;
440 :
441 41 : return true;
442 : }
443 :
444 6457 : static inline bool memory_buffer_reserve(png_memory_buffer_t *buffer, size_t additional) {
445 : size_t required, capacity;
446 : uint8_t *new_data;
447 :
448 6457 : if (!buffer || additional > SIZE_MAX - buffer->size) {
449 0 : return false;
450 : }
451 :
452 6457 : required = buffer->size + additional;
453 6457 : capacity = buffer->capacity ? buffer->capacity : 4096;
454 :
455 6609 : while (capacity < required) {
456 152 : if (capacity > SIZE_MAX / 2) {
457 0 : capacity = required;
458 0 : break;
459 : }
460 :
461 152 : capacity *= 2;
462 : }
463 :
464 6457 : new_data = (uint8_t *)realloc(buffer->data, capacity);
465 6457 : if (!new_data) {
466 0 : return false;
467 : }
468 :
469 6457 : buffer->data = new_data;
470 6457 : buffer->capacity = capacity;
471 :
472 6457 : return true;
473 : }
474 :
475 6457 : static inline void memory_write(png_structp png_ptr, png_bytep data, png_size_t length) {
476 6457 : png_memory_buffer_t *buffer = (png_memory_buffer_t *)png_get_io_ptr(png_ptr);
477 :
478 6457 : if (!buffer || !memory_buffer_reserve(buffer, length)) {
479 0 : png_error(png_ptr, "png write failure");
480 : return;
481 : }
482 :
483 6457 : memcpy(buffer->data + buffer->size, data, length);
484 :
485 6457 : buffer->size += length;
486 : }
487 :
488 41 : static inline bool finalize_memory_png(png_memory_buffer_t *buffer, uint8_t **out_data, size_t *out_size) {
489 : uint8_t *shrunk;
490 :
491 41 : if (!buffer || !out_data || !out_size) {
492 0 : return false;
493 : }
494 :
495 41 : if (buffer->data) {
496 41 : shrunk = (uint8_t *)realloc(buffer->data, buffer->size);
497 41 : if (shrunk) {
498 41 : buffer->data = shrunk;
499 41 : buffer->capacity = buffer->size;
500 : }
501 : }
502 :
503 41 : *out_data = buffer->data;
504 41 : *out_size = buffer->size;
505 41 : if (!buffer->data || buffer->size == 0) {
506 0 : memory_buffer_reset(buffer);
507 0 : return false;
508 : }
509 :
510 41 : return true;
511 : }
512 :
513 41 : static inline png_bytep *build_row_pointers(const uint8_t *data, size_t row_stride, uint32_t height) {
514 : png_bytep *row_pointers;
515 : uint32_t y;
516 :
517 41 : if (!data || row_stride == 0 || height == 0) {
518 0 : return NULL;
519 : }
520 :
521 41 : row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
522 41 : if (!row_pointers) {
523 0 : return NULL;
524 : }
525 :
526 15920 : for (y = 0; y < height; ++y) {
527 15879 : row_pointers[y] = (png_bytep)(data + (size_t)y * row_stride);
528 : }
529 :
530 41 : return row_pointers;
531 : }
532 :
533 24 : static inline 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,
534 : size_t *out_size) {
535 : png_color palette_data[256];
536 : png_byte alpha_data[256];
537 : png_structp png_ptr;
538 : png_infop info_ptr;
539 : png_bytep *row_pointers;
540 : png_memory_buffer_t buffer;
541 : size_t expected_len, i;
542 : int num_trans;
543 :
544 24 : if (!indices || !palette || !out_data || !out_size || width == 0 || height == 0) {
545 0 : return false;
546 : }
547 :
548 24 : expected_len = (size_t)width * (size_t)height;
549 24 : if (indices_len != expected_len || palette_len == 0 || palette_len > 256) {
550 0 : return false;
551 : }
552 :
553 24 : if (!init_write_struct(&png_ptr, &info_ptr)) {
554 0 : return false;
555 : }
556 :
557 24 : buffer.data = NULL;
558 24 : buffer.size = 0;
559 24 : buffer.capacity = 0;
560 24 : row_pointers = NULL;
561 :
562 24 : if (setjmp(png_jmpbuf(png_ptr)) != 0) {
563 0 : memory_buffer_reset(&buffer);
564 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
565 0 : free(row_pointers);
566 0 : return false;
567 : }
568 :
569 24 : png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
570 24 : png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
571 24 : png_set_compression_strategy(png_ptr, Z_FILTERED);
572 24 : png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
573 24 : 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);
574 :
575 24 : num_trans = 0;
576 3145 : for (i = 0; i < palette_len; ++i) {
577 3121 : palette_data[i].red = palette[i].r;
578 3121 : palette_data[i].green = palette[i].g;
579 3121 : palette_data[i].blue = palette[i].b;
580 3121 : alpha_data[i] = palette[i].a;
581 3121 : if (palette[i].a != 255) {
582 3043 : num_trans = (int)(i + 1);
583 : }
584 : }
585 :
586 24 : png_set_PLTE(png_ptr, info_ptr, palette_data, (int)palette_len);
587 24 : if (num_trans > 0) {
588 19 : png_set_tRNS(png_ptr, info_ptr, alpha_data, num_trans, NULL);
589 : }
590 :
591 24 : png_write_info(png_ptr, info_ptr);
592 :
593 24 : row_pointers = build_row_pointers(indices, (size_t)width, height);
594 24 : if (!row_pointers) {
595 0 : png_error(png_ptr, "png row allocation failed");
596 : return false;
597 : }
598 :
599 24 : png_write_image(png_ptr, row_pointers);
600 24 : png_write_end(png_ptr, NULL);
601 :
602 24 : free(row_pointers);
603 24 : png_destroy_write_struct(&png_ptr, &info_ptr);
604 :
605 24 : return finalize_memory_png(&buffer, out_data, out_size);
606 : }
607 :
608 17 : 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) {
609 : png_structp png_ptr;
610 : png_infop info_ptr;
611 : png_bytep *row_pointers;
612 : png_memory_buffer_t buffer;
613 : size_t expected_pixels;
614 :
615 17 : if (!rgba || !out_data || !out_size || width == 0 || height == 0) {
616 0 : return false;
617 : }
618 :
619 17 : expected_pixels = (size_t)width * (size_t)height;
620 17 : if (pixel_count != expected_pixels) {
621 0 : return false;
622 : }
623 :
624 17 : if (!init_write_struct(&png_ptr, &info_ptr)) {
625 0 : return false;
626 : }
627 :
628 17 : buffer.data = NULL;
629 17 : buffer.size = 0;
630 17 : buffer.capacity = 0;
631 17 : row_pointers = NULL;
632 :
633 17 : if (setjmp(png_jmpbuf(png_ptr)) != 0) {
634 0 : memory_buffer_reset(&buffer);
635 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
636 0 : free(row_pointers);
637 0 : return false;
638 : }
639 :
640 17 : png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
641 17 : png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
642 17 : png_set_compression_strategy(png_ptr, Z_FILTERED);
643 17 : png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
644 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);
645 17 : png_write_info(png_ptr, info_ptr);
646 :
647 17 : row_pointers = build_row_pointers(rgba, (size_t)width * PNGX_RGBA_CHANNELS, height);
648 17 : if (!row_pointers) {
649 0 : png_error(png_ptr, "png row allocation failed");
650 :
651 : return false;
652 : }
653 :
654 17 : png_write_image(png_ptr, row_pointers);
655 17 : png_write_end(png_ptr, NULL);
656 :
657 17 : free(row_pointers);
658 17 : png_destroy_write_struct(&png_ptr, &info_ptr);
659 :
660 17 : return finalize_memory_png(&buffer, out_data, out_size);
661 : }
662 :
663 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) {
664 : PngxBridgeQuantParams params, fallback_params;
665 : PngxBridgeQuantOutput output;
666 : PngxBridgeQuantStatus status;
667 : pngx_options_t tuned_opts;
668 : pngx_quant_support_t support;
669 : pngx_image_stats_t stats;
670 : pngx_rgba_image_t image;
671 : size_t pixel_count;
672 : float resolved_dither, estimated_dither, gradient_dither_floor;
673 : bool prefer_uniform, relaxed_quality, success;
674 :
675 24 : if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
676 0 : return false;
677 : }
678 :
679 24 : if (quant_quality) {
680 24 : *quant_quality = -1;
681 : }
682 :
683 24 : if (!load_rgba_image(png_data, png_size, &image)) {
684 0 : return false;
685 : }
686 :
687 24 : alpha_bleed_rgb_from_opaque(image.rgba, image.width, image.height, opts);
688 :
689 24 : pixel_count = image.pixel_count;
690 24 : image_stats_reset(&stats);
691 24 : memset(&support, 0, sizeof(support));
692 24 : if (!prepare_quant_support(&image, opts, &support, &stats)) {
693 0 : rgba_image_reset(&image);
694 0 : quant_support_reset(&support);
695 :
696 0 : return false;
697 : }
698 :
699 24 : tuned_opts = *opts;
700 24 : prefer_uniform = opts->palette256_gradient_profile_enable ? is_smooth_gradient_profile(&stats, &tuned_opts) : false;
701 24 : if (prefer_uniform) {
702 2 : tuned_opts.saliency_map_enable = false;
703 2 : tuned_opts.chroma_anchor_enable = false;
704 2 : tuned_opts.postprocess_smooth_enable = false;
705 : } else {
706 22 : build_fixed_palette(opts, &support, &tuned_opts);
707 : }
708 :
709 24 : resolved_dither = resolve_quant_dither(opts, &stats);
710 :
711 24 : if (opts->lossy_dither_auto) {
712 0 : estimated_dither = estimate_bitdepth_dither_level(image.rgba, image.width, image.height, 8);
713 0 : if (estimated_dither > resolved_dither) {
714 0 : resolved_dither = estimated_dither;
715 : }
716 : }
717 :
718 24 : gradient_dither_floor = tuned_opts.palette256_gradient_profile_dither_floor;
719 24 : if (gradient_dither_floor < 0.0f) {
720 0 : gradient_dither_floor = PNGX_PALETTE256_GRADIENT_PROFILE_DITHER_FLOOR;
721 : }
722 :
723 24 : if (prefer_uniform && resolved_dither < gradient_dither_floor) {
724 2 : resolved_dither = gradient_dither_floor;
725 : }
726 :
727 24 : tuned_opts.lossy_dither_level = resolved_dither;
728 :
729 24 : fill_quant_params(¶ms, &tuned_opts, prefer_uniform ? NULL : support.importance_map, prefer_uniform ? 0 : support.importance_map_len);
730 :
731 24 : params.dithering_level = resolved_dither;
732 :
733 24 : tune_quant_params_for_image(¶ms, &tuned_opts, &stats);
734 :
735 24 : output.palette = NULL;
736 24 : output.palette_len = 0;
737 24 : output.indices = NULL;
738 24 : output.indices_len = 0;
739 24 : output.quality = -1;
740 24 : relaxed_quality = false;
741 :
742 24 : status = pngx_bridge_quantize((const cpres_rgba_color_t *)image.rgba, pixel_count, image.width, image.height, ¶ms, &output);
743 24 : pngx_set_last_error((int)status);
744 :
745 24 : if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW && params.quality_min > 0) {
746 1 : fallback_params = params;
747 1 : fallback_params.quality_min = 0;
748 :
749 1 : if (fallback_params.quality_max < fallback_params.quality_min) {
750 0 : fallback_params.quality_max = fallback_params.quality_min;
751 : }
752 :
753 1 : output.palette = NULL;
754 1 : output.palette_len = 0;
755 1 : output.indices = NULL;
756 1 : output.indices_len = 0;
757 1 : output.quality = -1;
758 :
759 1 : status = pngx_bridge_quantize((const cpres_rgba_color_t *)image.rgba, pixel_count, image.width, image.height, &fallback_params, &output);
760 1 : pngx_set_last_error((int)status);
761 1 : if (status == PNGX_BRIDGE_QUANT_STATUS_OK) {
762 1 : relaxed_quality = true;
763 : }
764 : }
765 :
766 24 : if (status != PNGX_BRIDGE_QUANT_STATUS_OK) {
767 0 : free_quant_output(&output);
768 0 : rgba_image_reset(&image);
769 0 : quant_support_reset(&support);
770 0 : if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW) {
771 0 : cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Quantization quality too low");
772 : }
773 :
774 0 : return false;
775 : }
776 :
777 24 : if (relaxed_quality) {
778 1 : cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Relaxed quantization quality floor");
779 : }
780 :
781 24 : if (quant_quality) {
782 24 : *quant_quality = output.quality;
783 : }
784 :
785 24 : success = false;
786 24 : if (output.indices && output.indices_len == pixel_count && output.palette && output.palette_len > 0 && output.palette_len <= 256) {
787 24 : sanitize_transparent_palette(output.palette, output.palette_len);
788 24 : postprocess_indices(output.indices, image.width, image.height, output.palette, output.palette_len, &support, &tuned_opts);
789 24 : success = pngx_create_palette_png(output.indices, output.indices_len, output.palette, output.palette_len, image.width, image.height, out_data, out_size);
790 : }
791 :
792 24 : free_quant_output(&output);
793 24 : rgba_image_reset(&image);
794 24 : quant_support_reset(&support);
795 :
796 24 : return success;
797 : }
|