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

Generated by: LCOV version 2.0-1