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 <stdbool.h>
15 : #include <stddef.h>
16 : #include <stdint.h>
17 : #include <string.h>
18 :
19 : #include "internal/log.h"
20 : #include "internal/pngx_common.h"
21 :
22 : static int g_pngx_last_error = 0;
23 :
24 1 : int pngx_get_last_error(void) { return g_pngx_last_error; }
25 :
26 81 : void pngx_set_last_error(int error_code) { g_pngx_last_error = error_code; }
27 :
28 31 : bool pngx_run_quantization(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) {
29 : const char *label;
30 31 : uint32_t resolved_colors = 0;
31 31 : uint32_t applied_colors = 0;
32 : bool success;
33 :
34 31 : if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
35 0 : return false;
36 : }
37 :
38 31 : *out_data = NULL;
39 31 : *out_size = 0;
40 31 : if (quant_quality) {
41 31 : *quant_quality = -1;
42 : }
43 :
44 31 : if (opts->lossy_type == PNGX_LOSSY_TYPE_REDUCED_RGBA32) {
45 5 : success = pngx_quantize_reduced_rgba32(png_data, png_size, opts, &resolved_colors, &applied_colors, out_data, out_size);
46 5 : if (success) {
47 5 : label = lossy_type_label(opts->lossy_type);
48 5 : cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: %s target %u colors -> %u unique", label, resolved_colors, applied_colors);
49 : }
50 5 : return success;
51 : }
52 :
53 26 : if (opts->lossy_type == PNGX_LOSSY_TYPE_LIMITED_RGBA4444) {
54 3 : return pngx_quantize_limited4444(png_data, png_size, opts, out_data, out_size);
55 : }
56 :
57 23 : return pngx_quantize_palette256(png_data, png_size, opts, out_data, out_size, quant_quality);
58 : }
59 :
60 37 : void pngx_fill_pngx_options(pngx_options_t *opts, const cpres_config_t *config) {
61 37 : uint16_t lossy_max_colors = COLOPRESSO_PNGX_DEFAULT_LOSSY_MAX_COLORS, palette256_alpha_bleed_max_distance = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_MAX_DISTANCE;
62 37 : uint8_t level = COLOPRESSO_PNGX_DEFAULT_LEVEL, lossy_quality_min = COLOPRESSO_PNGX_DEFAULT_LOSSY_QUALITY_MIN, lossy_quality_max = COLOPRESSO_PNGX_DEFAULT_LOSSY_QUALITY_MAX,
63 37 : lossy_speed = COLOPRESSO_PNGX_DEFAULT_LOSSY_SPEED, lossy_type = COLOPRESSO_PNGX_DEFAULT_LOSSY_TYPE, lossy_reduced_bits_rgb = COLOPRESSO_PNGX_DEFAULT_REDUCED_BITS_RGB,
64 37 : lossy_reduced_bits_alpha = COLOPRESSO_PNGX_DEFAULT_REDUCED_ALPHA_BITS, tmp, palette256_alpha_bleed_opaque_threshold = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_OPAQUE_THRESHOLD,
65 37 : palette256_alpha_bleed_soft_limit = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_SOFT_LIMIT;
66 37 : int32_t lossy_reduced_colors = COLOPRESSO_PNGX_DEFAULT_REDUCED_COLORS, clamped;
67 37 : int16_t palette256_tune_speed_max = PNGX_PALETTE256_TUNE_SPEED_MAX, palette256_tune_quality_min_floor = PNGX_PALETTE256_TUNE_QUALITY_MIN_FLOOR,
68 37 : palette256_tune_quality_max_target = PNGX_PALETTE256_TUNE_QUALITY_MAX_TARGET;
69 37 : bool strip_safe = COLOPRESSO_PNGX_DEFAULT_STRIP_SAFE, optimize_alpha = COLOPRESSO_PNGX_DEFAULT_OPTIMIZE_ALPHA, lossy_enable = COLOPRESSO_PNGX_DEFAULT_LOSSY_ENABLE, lossy_dither_auto = false,
70 37 : saliency_map_enable = COLOPRESSO_PNGX_DEFAULT_SALIENCY_MAP_ENABLE, chroma_anchor_enable = COLOPRESSO_PNGX_DEFAULT_CHROMA_ANCHOR_ENABLE,
71 37 : adaptive_dither_enable = COLOPRESSO_PNGX_DEFAULT_ADAPTIVE_DITHER_ENABLE, gradient_boost_enable = COLOPRESSO_PNGX_DEFAULT_GRADIENT_BOOST_ENABLE,
72 37 : chroma_weight_enable = COLOPRESSO_PNGX_DEFAULT_CHROMA_WEIGHT_ENABLE, postprocess_smooth_enable = COLOPRESSO_PNGX_DEFAULT_POSTPROCESS_SMOOTH_ENABLE,
73 37 : palette256_gradient_profile_enable = COLOPRESSO_PNGX_DEFAULT_PALETTE256_GRADIENT_PROFILE_ENABLE, palette256_alpha_bleed_enable = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_ENABLE;
74 37 : float lossy_dither_level = COLOPRESSO_PNGX_DEFAULT_LOSSY_DITHER_LEVEL, postprocess_smooth_importance_cutoff = COLOPRESSO_PNGX_DEFAULT_POSTPROCESS_SMOOTH_IMPORTANCE_CUTOFF,
75 37 : palette256_gradient_profile_dither_floor = PNGX_PALETTE256_GRADIENT_PROFILE_DITHER_FLOOR, palette256_profile_opaque_ratio_threshold = PNGX_PALETTE256_GRADIENT_PROFILE_OPAQUE_RATIO_THRESHOLD,
76 37 : palette256_profile_gradient_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_GRADIENT_MEAN_MAX, palette256_profile_saturation_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_SATURATION_MEAN_MAX,
77 37 : palette256_tune_opaque_ratio_threshold = PNGX_PALETTE256_TUNE_OPAQUE_RATIO_THRESHOLD, palette256_tune_gradient_mean_max = PNGX_PALETTE256_TUNE_GRADIENT_MEAN_MAX,
78 37 : palette256_tune_saturation_mean_max = PNGX_PALETTE256_TUNE_SATURATION_MEAN_MAX;
79 :
80 37 : if (!opts) {
81 0 : return;
82 : }
83 :
84 37 : if (config) {
85 37 : if (config->pngx_level >= 0 && config->pngx_level <= 6) {
86 37 : level = (uint8_t)config->pngx_level;
87 : }
88 37 : strip_safe = config->pngx_strip_safe;
89 37 : optimize_alpha = config->pngx_optimize_alpha;
90 37 : lossy_enable = config->pngx_lossy_enable;
91 37 : if (config->pngx_lossy_type >= CPRES_PNGX_LOSSY_TYPE_PALETTE256 && config->pngx_lossy_type <= CPRES_PNGX_LOSSY_TYPE_REDUCED_RGBA32) {
92 37 : lossy_type = (uint8_t)config->pngx_lossy_type;
93 : }
94 :
95 37 : if (config->pngx_lossy_max_colors > 0 && config->pngx_lossy_max_colors <= 256) {
96 36 : lossy_max_colors = (uint16_t)config->pngx_lossy_max_colors;
97 : }
98 37 : if (config->pngx_lossy_reduced_colors == COLOPRESSO_PNGX_DEFAULT_REDUCED_COLORS) {
99 32 : lossy_reduced_colors = COLOPRESSO_PNGX_DEFAULT_REDUCED_COLORS;
100 5 : } else if (config->pngx_lossy_reduced_colors >= (int32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
101 2 : clamped = config->pngx_lossy_reduced_colors;
102 2 : if (clamped > (int32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
103 0 : clamped = (int32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
104 : }
105 :
106 2 : lossy_reduced_colors = clamped;
107 : }
108 37 : if (config->pngx_lossy_reduced_bits_rgb >= COLOPRESSO_PNGX_REDUCED_BITS_MIN && config->pngx_lossy_reduced_bits_rgb <= COLOPRESSO_PNGX_REDUCED_BITS_MAX) {
109 37 : lossy_reduced_bits_rgb = (uint8_t)config->pngx_lossy_reduced_bits_rgb;
110 : }
111 37 : if (config->pngx_lossy_reduced_alpha_bits >= COLOPRESSO_PNGX_REDUCED_BITS_MIN && config->pngx_lossy_reduced_alpha_bits <= COLOPRESSO_PNGX_REDUCED_BITS_MAX) {
112 37 : lossy_reduced_bits_alpha = (uint8_t)config->pngx_lossy_reduced_alpha_bits;
113 : }
114 37 : if (config->pngx_lossy_quality_min >= 0 && config->pngx_lossy_quality_min <= 100) {
115 36 : lossy_quality_min = (uint8_t)config->pngx_lossy_quality_min;
116 : }
117 37 : if (config->pngx_lossy_quality_max >= 0 && config->pngx_lossy_quality_max <= 100) {
118 36 : lossy_quality_max = (uint8_t)config->pngx_lossy_quality_max;
119 : }
120 37 : if (lossy_quality_max < lossy_quality_min) {
121 1 : tmp = lossy_quality_min;
122 1 : lossy_quality_min = lossy_quality_max;
123 1 : lossy_quality_max = tmp;
124 : }
125 37 : if (config->pngx_lossy_speed >= 1 && config->pngx_lossy_speed <= 10) {
126 36 : lossy_speed = (uint8_t)config->pngx_lossy_speed;
127 : }
128 37 : if (config->pngx_lossy_dither_level < 0.0f) {
129 1 : lossy_dither_auto = true;
130 36 : } else if (config->pngx_lossy_dither_level <= 1.0f) {
131 35 : lossy_dither_level = config->pngx_lossy_dither_level;
132 : }
133 37 : if (config->pngx_protected_colors && config->pngx_protected_colors_count > 0) {
134 2 : opts->protected_colors = config->pngx_protected_colors;
135 2 : opts->protected_colors_count = config->pngx_protected_colors_count;
136 : } else {
137 35 : opts->protected_colors = NULL;
138 35 : opts->protected_colors_count = 0;
139 : }
140 :
141 37 : saliency_map_enable = config->pngx_saliency_map_enable;
142 37 : chroma_anchor_enable = config->pngx_chroma_anchor_enable;
143 37 : adaptive_dither_enable = config->pngx_adaptive_dither_enable;
144 37 : gradient_boost_enable = config->pngx_gradient_boost_enable;
145 37 : chroma_weight_enable = config->pngx_chroma_weight_enable;
146 37 : postprocess_smooth_enable = config->pngx_postprocess_smooth_enable;
147 37 : postprocess_smooth_importance_cutoff = config->pngx_postprocess_smooth_importance_cutoff;
148 :
149 37 : palette256_gradient_profile_enable = config->pngx_palette256_gradient_profile_enable;
150 37 : if (config->pngx_palette256_gradient_dither_floor >= 0.0f && config->pngx_palette256_gradient_dither_floor <= 1.0f) {
151 37 : palette256_gradient_profile_dither_floor = config->pngx_palette256_gradient_dither_floor;
152 : }
153 37 : palette256_alpha_bleed_enable = config->pngx_palette256_alpha_bleed_enable;
154 :
155 37 : if (config->pngx_palette256_alpha_bleed_max_distance >= 0 && config->pngx_palette256_alpha_bleed_max_distance <= 65535) {
156 37 : palette256_alpha_bleed_max_distance = (uint16_t)config->pngx_palette256_alpha_bleed_max_distance;
157 : }
158 37 : if (config->pngx_palette256_alpha_bleed_opaque_threshold >= 0 && config->pngx_palette256_alpha_bleed_opaque_threshold <= 255) {
159 37 : palette256_alpha_bleed_opaque_threshold = (uint8_t)config->pngx_palette256_alpha_bleed_opaque_threshold;
160 : }
161 37 : if (config->pngx_palette256_alpha_bleed_soft_limit >= 0 && config->pngx_palette256_alpha_bleed_soft_limit <= 255) {
162 37 : palette256_alpha_bleed_soft_limit = (uint8_t)config->pngx_palette256_alpha_bleed_soft_limit;
163 : }
164 :
165 37 : if (config->pngx_palette256_profile_opaque_ratio_threshold >= 0.0f && config->pngx_palette256_profile_opaque_ratio_threshold <= 1.0f) {
166 36 : palette256_profile_opaque_ratio_threshold = config->pngx_palette256_profile_opaque_ratio_threshold;
167 : }
168 37 : if (config->pngx_palette256_profile_gradient_mean_max >= 0.0f && config->pngx_palette256_profile_gradient_mean_max <= 1.0f) {
169 36 : palette256_profile_gradient_mean_max = config->pngx_palette256_profile_gradient_mean_max;
170 : }
171 37 : if (config->pngx_palette256_profile_saturation_mean_max >= 0.0f && config->pngx_palette256_profile_saturation_mean_max <= 1.0f) {
172 36 : palette256_profile_saturation_mean_max = config->pngx_palette256_profile_saturation_mean_max;
173 : }
174 :
175 37 : if (config->pngx_palette256_tune_opaque_ratio_threshold >= 0.0f && config->pngx_palette256_tune_opaque_ratio_threshold <= 1.0f) {
176 37 : palette256_tune_opaque_ratio_threshold = config->pngx_palette256_tune_opaque_ratio_threshold;
177 : }
178 37 : if (config->pngx_palette256_tune_gradient_mean_max >= 0.0f && config->pngx_palette256_tune_gradient_mean_max <= 1.0f) {
179 37 : palette256_tune_gradient_mean_max = config->pngx_palette256_tune_gradient_mean_max;
180 : }
181 37 : if (config->pngx_palette256_tune_saturation_mean_max >= 0.0f && config->pngx_palette256_tune_saturation_mean_max <= 1.0f) {
182 37 : palette256_tune_saturation_mean_max = config->pngx_palette256_tune_saturation_mean_max;
183 : }
184 :
185 37 : if (config->pngx_palette256_tune_speed_max >= 1 && config->pngx_palette256_tune_speed_max <= 10) {
186 37 : palette256_tune_speed_max = (int16_t)config->pngx_palette256_tune_speed_max;
187 : }
188 37 : if (config->pngx_palette256_tune_quality_min_floor >= 0 && config->pngx_palette256_tune_quality_min_floor <= 100) {
189 37 : palette256_tune_quality_min_floor = (int16_t)config->pngx_palette256_tune_quality_min_floor;
190 : }
191 37 : if (config->pngx_palette256_tune_quality_max_target >= 0 && config->pngx_palette256_tune_quality_max_target <= 100) {
192 37 : palette256_tune_quality_max_target = (int16_t)config->pngx_palette256_tune_quality_max_target;
193 : }
194 : } else {
195 0 : opts->protected_colors = NULL;
196 0 : opts->protected_colors_count = 0;
197 : }
198 :
199 37 : lossy_quality_min = clamp_uint8_t(lossy_quality_min, 0, 100);
200 37 : lossy_quality_max = clamp_uint8_t(lossy_quality_max, lossy_quality_min, 100);
201 37 : lossy_speed = clamp_uint8_t(lossy_speed, 1, 10);
202 37 : lossy_max_colors = clamp_uint16_t(lossy_max_colors, 2, 256);
203 37 : lossy_dither_level = clamp_float(lossy_dither_level, 0.0f, 1.0f);
204 37 : lossy_reduced_bits_rgb = clamp_reduced_bits(lossy_reduced_bits_rgb);
205 37 : lossy_reduced_bits_alpha = clamp_reduced_bits(lossy_reduced_bits_alpha);
206 37 : if (postprocess_smooth_importance_cutoff < 0.0f) {
207 1 : postprocess_smooth_importance_cutoff = -1.0f;
208 : } else {
209 36 : postprocess_smooth_importance_cutoff = clamp_float(postprocess_smooth_importance_cutoff, 0.0f, 1.0f);
210 : }
211 :
212 37 : if (palette256_gradient_profile_dither_floor < 0.0f) {
213 0 : palette256_gradient_profile_dither_floor = -1.0f;
214 : } else {
215 37 : palette256_gradient_profile_dither_floor = clamp_float(palette256_gradient_profile_dither_floor, 0.0f, 1.0f);
216 : }
217 :
218 37 : if (palette256_profile_opaque_ratio_threshold < 0.0f) {
219 0 : palette256_profile_opaque_ratio_threshold = -1.0f;
220 : } else {
221 37 : palette256_profile_opaque_ratio_threshold = clamp_float(palette256_profile_opaque_ratio_threshold, 0.0f, 1.0f);
222 : }
223 37 : if (palette256_profile_gradient_mean_max < 0.0f) {
224 0 : palette256_profile_gradient_mean_max = -1.0f;
225 : } else {
226 37 : palette256_profile_gradient_mean_max = clamp_float(palette256_profile_gradient_mean_max, 0.0f, 1.0f);
227 : }
228 37 : if (palette256_profile_saturation_mean_max < 0.0f) {
229 0 : palette256_profile_saturation_mean_max = -1.0f;
230 : } else {
231 37 : palette256_profile_saturation_mean_max = clamp_float(palette256_profile_saturation_mean_max, 0.0f, 1.0f);
232 : }
233 :
234 37 : if (palette256_tune_opaque_ratio_threshold < 0.0f) {
235 0 : palette256_tune_opaque_ratio_threshold = -1.0f;
236 : } else {
237 37 : palette256_tune_opaque_ratio_threshold = clamp_float(palette256_tune_opaque_ratio_threshold, 0.0f, 1.0f);
238 : }
239 37 : if (palette256_tune_gradient_mean_max < 0.0f) {
240 0 : palette256_tune_gradient_mean_max = -1.0f;
241 : } else {
242 37 : palette256_tune_gradient_mean_max = clamp_float(palette256_tune_gradient_mean_max, 0.0f, 1.0f);
243 : }
244 37 : if (palette256_tune_saturation_mean_max < 0.0f) {
245 0 : palette256_tune_saturation_mean_max = -1.0f;
246 : } else {
247 37 : palette256_tune_saturation_mean_max = clamp_float(palette256_tune_saturation_mean_max, 0.0f, 1.0f);
248 : }
249 :
250 37 : if (palette256_tune_speed_max < -1) {
251 0 : palette256_tune_speed_max = -1;
252 : }
253 37 : if (palette256_tune_quality_min_floor < -1) {
254 0 : palette256_tune_quality_min_floor = -1;
255 : }
256 37 : if (palette256_tune_quality_max_target < -1) {
257 0 : palette256_tune_quality_max_target = -1;
258 : }
259 :
260 37 : opts->bridge.optimization_level = level;
261 37 : opts->bridge.strip_safe = strip_safe;
262 37 : opts->bridge.optimize_alpha = optimize_alpha;
263 37 : opts->lossy_enable = lossy_enable;
264 37 : opts->lossy_type = lossy_type;
265 37 : opts->lossy_max_colors = lossy_max_colors;
266 37 : opts->lossy_reduced_colors = lossy_reduced_colors;
267 37 : opts->lossy_reduced_bits_rgb = lossy_reduced_bits_rgb;
268 37 : opts->lossy_reduced_alpha_bits = lossy_reduced_bits_alpha;
269 37 : opts->lossy_quality_min = lossy_quality_min;
270 37 : opts->lossy_quality_max = lossy_quality_max;
271 37 : opts->lossy_speed = lossy_speed;
272 37 : opts->lossy_dither_level = lossy_dither_level;
273 37 : opts->lossy_dither_auto = lossy_dither_auto;
274 37 : opts->saliency_map_enable = saliency_map_enable;
275 37 : opts->chroma_anchor_enable = chroma_anchor_enable;
276 37 : opts->adaptive_dither_enable = adaptive_dither_enable;
277 37 : opts->gradient_boost_enable = gradient_boost_enable;
278 37 : opts->chroma_weight_enable = chroma_weight_enable;
279 37 : opts->postprocess_smooth_enable = postprocess_smooth_enable;
280 37 : opts->postprocess_smooth_importance_cutoff = postprocess_smooth_importance_cutoff;
281 37 : opts->palette256_gradient_profile_enable = palette256_gradient_profile_enable;
282 37 : opts->palette256_gradient_profile_dither_floor = palette256_gradient_profile_dither_floor;
283 37 : opts->palette256_alpha_bleed_enable = palette256_alpha_bleed_enable;
284 37 : opts->palette256_alpha_bleed_max_distance = palette256_alpha_bleed_max_distance;
285 37 : opts->palette256_alpha_bleed_opaque_threshold = palette256_alpha_bleed_opaque_threshold;
286 37 : opts->palette256_alpha_bleed_soft_limit = palette256_alpha_bleed_soft_limit;
287 37 : opts->palette256_profile_opaque_ratio_threshold = palette256_profile_opaque_ratio_threshold;
288 37 : opts->palette256_profile_gradient_mean_max = palette256_profile_gradient_mean_max;
289 37 : opts->palette256_profile_saturation_mean_max = palette256_profile_saturation_mean_max;
290 37 : opts->palette256_tune_opaque_ratio_threshold = palette256_tune_opaque_ratio_threshold;
291 37 : opts->palette256_tune_gradient_mean_max = palette256_tune_gradient_mean_max;
292 37 : opts->palette256_tune_saturation_mean_max = palette256_tune_saturation_mean_max;
293 37 : opts->palette256_tune_speed_max = palette256_tune_speed_max;
294 37 : opts->palette256_tune_quality_min_floor = palette256_tune_quality_min_floor;
295 37 : opts->palette256_tune_quality_max_target = palette256_tune_quality_max_target;
296 : }
297 :
298 54 : bool pngx_run_lossless_optimization(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint8_t **out_data, size_t *out_size) {
299 : PngxBridgeLosslessOptions lossless;
300 : PngxBridgeResult result;
301 :
302 54 : if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
303 0 : return false;
304 : }
305 :
306 54 : *out_data = NULL;
307 54 : *out_size = 0;
308 :
309 54 : memset(&lossless, 0, sizeof(lossless));
310 54 : lossless.optimization_level = opts->bridge.optimization_level;
311 54 : lossless.strip_safe = opts->bridge.strip_safe;
312 54 : lossless.optimize_alpha = opts->bridge.optimize_alpha;
313 54 : result = pngx_bridge_optimize_lossless(png_data, png_size, out_data, out_size, &lossless);
314 54 : pngx_set_last_error((int)result);
315 :
316 54 : if (result != PNGX_BRIDGE_RESULT_SUCCESS) {
317 0 : return false;
318 : }
319 :
320 54 : return true;
321 : }
322 :
323 31 : bool pngx_should_attempt_quantization(const pngx_options_t *opts) {
324 31 : if (!opts) {
325 0 : return false;
326 : }
327 :
328 31 : return opts->lossy_enable;
329 : }
330 :
331 23 : bool pngx_quantization_better(size_t baseline_size, size_t candidate_size) { return candidate_size == 0 ? false : (baseline_size == 0 ? true : candidate_size < baseline_size); }
|