LCOV - code coverage report
Current view: top level - src - pngx_palette256.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 82.5 % 560 462
Test Date: 2026-11-09 10:50:48 Functions: 95.8 % 24 23
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1