LCOV - code coverage report
Current view: top level - src - pngx_reduced.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 85.2 % 1270 1082
Test Date: 2025-12-13 15:41:21 Functions: 100.0 % 56 56
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 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 <limits.h>
      13              : #include <math.h>
      14              : #include <stdio.h>
      15              : #include <stdlib.h>
      16              : #include <string.h>
      17              : 
      18              : #include <png.h>
      19              : 
      20              : #include "internal/log.h"
      21              : #include "internal/pngx_common.h"
      22              : 
      23              : typedef struct {
      24              :   uint32_t color;
      25              :   uint32_t count;
      26              :   uint32_t mapped_color;
      27              :   uint8_t detail_bits_rgb;
      28              :   uint8_t detail_bits_alpha;
      29              :   bool locked;
      30              : } pngx_color_entry_t;
      31              : 
      32              : typedef struct {
      33              :   pngx_color_entry_t *entries;
      34              :   size_t count;
      35              :   size_t unlocked_count;
      36              : } pngx_color_histogram_t;
      37              : 
      38              : typedef struct {
      39              :   uint32_t color;
      40              :   uint16_t weight;
      41              :   uint8_t rgb_bits;
      42              :   uint8_t alpha_bits;
      43              : } pngx_histogram_sample_t;
      44              : 
      45              : typedef struct {
      46              :   size_t start;
      47              :   size_t end;
      48              :   uint8_t min_r;
      49              :   uint8_t min_g;
      50              :   uint8_t min_b;
      51              :   uint8_t min_a;
      52              :   uint8_t max_r;
      53              :   uint8_t max_g;
      54              :   uint8_t max_b;
      55              :   uint8_t max_a;
      56              :   uint64_t total_weight;
      57              : } pngx_color_box_t;
      58              : 
      59              : typedef struct {
      60              :   uint32_t color;
      61              :   uint32_t mapped_color;
      62              : } pngx_color_map_entry_t;
      63              : 
      64              : typedef struct {
      65              :   uint32_t color;
      66              :   uint32_t count;
      67              : } pngx_color_freq_t;
      68              : 
      69              : typedef struct {
      70              :   size_t index;
      71              :   uint32_t count;
      72              :   uint32_t color;
      73              : } pngx_freq_rank_t;
      74              : 
      75       290060 : static int compare_entries_r(const void *lhs, const void *rhs) {
      76       290060 :   const pngx_color_entry_t *a = (const pngx_color_entry_t *)lhs, *b = (const pngx_color_entry_t *)rhs;
      77       290060 :   uint8_t av = (uint8_t)(a->color & 0xff), bv = (uint8_t)(b->color & 0xff);
      78              : 
      79       290060 :   return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
      80              : }
      81              : 
      82       213350 : static int compare_entries_g(const void *lhs, const void *rhs) {
      83       213350 :   const pngx_color_entry_t *a = (const pngx_color_entry_t *)lhs, *b = (const pngx_color_entry_t *)rhs;
      84       213350 :   uint8_t av = (uint8_t)((a->color >> 8) & 0xff), bv = (uint8_t)((b->color >> 8) & 0xff);
      85              : 
      86       213350 :   return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
      87              : }
      88              : 
      89       103592 : static int compare_entries_b(const void *lhs, const void *rhs) {
      90       103592 :   const pngx_color_entry_t *a = (const pngx_color_entry_t *)lhs, *b = (const pngx_color_entry_t *)rhs;
      91       103592 :   uint8_t av = (uint8_t)((a->color >> 16) & 0xff), bv = (uint8_t)((b->color >> 16) & 0xff);
      92              : 
      93       103592 :   return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
      94              : }
      95              : 
      96       144554 : static int compare_entries_a(const void *lhs, const void *rhs) {
      97       144554 :   const pngx_color_entry_t *a = (const pngx_color_entry_t *)lhs, *b = (const pngx_color_entry_t *)rhs;
      98       144554 :   uint8_t av = (uint8_t)((a->color >> 24) & 0xff), bv = (uint8_t)((b->color >> 24) & 0xff);
      99              : 
     100       144554 :   return (av < bv) ? -1 : ((av > bv) ? 1 : 0);
     101              : }
     102              : 
     103           18 : static inline uint32_t compute_grid_capacity(uint8_t bits_rgb, uint8_t bits_alpha) {
     104              :   uint64_t capacity;
     105           18 :   uint32_t rgb_levels = 1, alpha_levels = 1;
     106              : 
     107           18 :   bits_rgb = clamp_reduced_bits(bits_rgb);
     108           18 :   bits_alpha = clamp_reduced_bits(bits_alpha);
     109              : 
     110           18 :   if (bits_rgb < 8) {
     111           14 :     rgb_levels = (uint32_t)1 << bits_rgb;
     112           14 :     rgb_levels = rgb_levels * rgb_levels * rgb_levels;
     113              :   } else {
     114            4 :     rgb_levels = UINT32_MAX;
     115              :   }
     116              : 
     117           18 :   if (bits_alpha < 8) {
     118           18 :     alpha_levels = (uint32_t)1 << bits_alpha;
     119              :   } else {
     120            0 :     alpha_levels = UINT32_MAX;
     121              :   }
     122              : 
     123           18 :   if (rgb_levels == UINT32_MAX || alpha_levels == UINT32_MAX) {
     124            4 :     return (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
     125              :   }
     126              : 
     127           14 :   capacity = (uint64_t)rgb_levels * (uint64_t)alpha_levels;
     128           14 :   if (capacity > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
     129            8 :     capacity = (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
     130              :   }
     131              : 
     132           14 :   return (uint32_t)capacity;
     133              : }
     134              : 
     135      9238467 : static inline uint32_t pack_rgba_u32(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r; }
     136              : 
     137         4565 : static uint32_t box_representative_color(const pngx_color_entry_t *entries, const pngx_color_box_t *box) {
     138              :   size_t i;
     139         4565 :   uint64_t sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0, total = 0, weight;
     140              :   uint32_t c;
     141              : 
     142         4565 :   if (!entries || !box) {
     143            0 :     return 0;
     144              :   }
     145        21498 :   for (i = box->start; i < box->end; ++i) {
     146        16933 :     c = entries[i].color;
     147        16933 :     weight = (uint64_t)entries[i].count;
     148        16933 :     sum_r += (uint64_t)(c & 0xff) * weight;
     149        16933 :     sum_g += (uint64_t)((c >> 8) & 0xff) * weight;
     150        16933 :     sum_b += (uint64_t)((c >> 16) & 0xff) * weight;
     151        16933 :     sum_a += (uint64_t)((c >> 24) & 0xff) * weight;
     152        16933 :     total += weight;
     153              :   }
     154              : 
     155         4565 :   if (total == 0) {
     156            0 :     return entries[box->start].color;
     157              :   }
     158              : 
     159         4565 :   sum_r = (sum_r + (total / 2)) / total;
     160         4565 :   sum_g = (sum_g + (total / 2)) / total;
     161         4565 :   sum_b = (sum_b + (total / 2)) / total;
     162         4565 :   sum_a = (sum_a + (total / 2)) / total;
     163              : 
     164         4565 :   return pack_rgba_u32((uint8_t)sum_r, (uint8_t)sum_g, (uint8_t)sum_b, (uint8_t)sum_a);
     165              : }
     166              : 
     167    381372496 : static inline uint8_t color_component(uint32_t color, uint8_t channel) { return (uint8_t)((color >> (channel * 8)) & 0xff); }
     168              : 
     169     47671562 : static inline uint32_t color_distance_sq_u32(uint32_t lhs, uint32_t rhs) {
     170     47671562 :   int32_t dr = (int32_t)color_component(lhs, 0) - (int32_t)color_component(rhs, 0), dg = (int32_t)color_component(lhs, 1) - (int32_t)color_component(rhs, 1),
     171     47671562 :           db = (int32_t)color_component(lhs, 2) - (int32_t)color_component(rhs, 2), da = (int32_t)color_component(lhs, 3) - (int32_t)color_component(rhs, 3);
     172              : 
     173     47671562 :   return (uint32_t)(dr * dr + dg * dg + db * db + da * da);
     174              : }
     175              : 
     176      1037047 : static inline void unpack_rgba_u32(uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a) {
     177      1037047 :   if (r) {
     178      1037047 :     *r = (uint8_t)(color & 0xff);
     179              :   }
     180              : 
     181      1037047 :   if (g) {
     182      1037047 :     *g = (uint8_t)((color >> 8) & 0xff);
     183              :   }
     184              : 
     185      1037047 :   if (b) {
     186      1037047 :     *b = (uint8_t)((color >> 16) & 0xff);
     187              :   }
     188              : 
     189      1037047 :   if (a) {
     190      1037047 :     *a = (uint8_t)((color >> 24) & 0xff);
     191              :   }
     192      1037047 : }
     193              : 
     194      2424832 : static inline float importance_dither_scale(uint8_t importance_value) {
     195              :   float normalized, scale;
     196              : 
     197      2424832 :   normalized = (float)importance_value / PNGX_REDUCED_IMPORTANCE_SCALE_DENOM;
     198      2424832 :   scale = PNGX_REDUCED_IMPORTANCE_SCALE_MIN + (1.0f - normalized) * PNGX_REDUCED_IMPORTANCE_SCALE_RANGE;
     199              : 
     200      2424832 :   return scale < 0.0f ? 0.0f : (scale > 1.0f ? 1.0f : scale);
     201              : }
     202              : 
     203            8 : static inline void color_histogram_reset(pngx_color_histogram_t *hist) {
     204            8 :   if (!hist) {
     205            0 :     return;
     206              :   }
     207              : 
     208            8 :   free(hist->entries);
     209            8 :   hist->entries = NULL;
     210            8 :   hist->count = 0;
     211            8 :   hist->unlocked_count = 0;
     212              : }
     213              : 
     214        16937 : static inline bool is_protected_color(uint32_t color, const uint32_t *protected_colors, size_t protected_count) {
     215              :   size_t i;
     216              : 
     217        16937 :   if (!protected_colors || protected_count == 0) {
     218        16933 :     return false;
     219              :   }
     220              : 
     221           10 :   for (i = 0; i < protected_count; ++i) {
     222           10 :     if (protected_colors[i] == color) {
     223            4 :       return true;
     224              :     }
     225              :   }
     226              : 
     227            0 :   return false;
     228              : }
     229              : 
     230      2491400 : static inline uint16_t histogram_importance_weight(const pngx_quant_support_t *support, size_t pixel_index) {
     231              :   const uint8_t *map;
     232              :   uint16_t weight;
     233              :   uint8_t importance;
     234              : 
     235      2491400 :   if (!support || !support->importance_map || pixel_index >= support->importance_map_len) {
     236        66568 :     return 1;
     237              :   }
     238              : 
     239      2424832 :   map = support->importance_map;
     240      2424832 :   importance = map[pixel_index];
     241              : 
     242      2424832 :   weight = 1 + (uint16_t)(importance >> PNGX_REDUCED_IMPORTANCE_WEIGHT_SHIFT);
     243      2424832 :   if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_STRONG) {
     244        23900 :     weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_STRONG;
     245      2400932 :   } else if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_HIGH) {
     246       545339 :     weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_HIGH;
     247      1855593 :   } else if (importance > PNGX_REDUCED_IMPORTANCE_WEIGHT_THRESHOLD_MEDIUM) {
     248      1190152 :     weight += PNGX_REDUCED_IMPORTANCE_WEIGHT_BONUS_MEDIUM;
     249              :   }
     250              : 
     251      2424832 :   if (weight > PNGX_REDUCED_IMPORTANCE_WEIGHT_CAP) {
     252            0 :     weight = PNGX_REDUCED_IMPORTANCE_WEIGHT_CAP;
     253              :   }
     254              : 
     255      2424832 :   return weight;
     256              : }
     257              : 
     258     39340728 : static inline int compare_histogram_sample(const void *lhs, const void *rhs) {
     259     39340728 :   const pngx_histogram_sample_t *a = (const pngx_histogram_sample_t *)lhs, *b = (const pngx_histogram_sample_t *)rhs;
     260              : 
     261     39340728 :   return a->color < b->color ? -1 : (a->color > b->color ? 1 : 0);
     262              : }
     263              : 
     264         9123 : static inline void color_box_refresh(const pngx_color_entry_t *entries, pngx_color_box_t *box) {
     265              :   size_t i;
     266              :   uint32_t c;
     267              :   uint8_t r, g, b, a;
     268              : 
     269         9123 :   if (!entries || !box || box->end <= box->start) {
     270            0 :     return;
     271              :   }
     272              : 
     273         9123 :   box->min_r = 255;
     274         9123 :   box->min_g = 255;
     275         9123 :   box->min_b = 255;
     276         9123 :   box->min_a = 255;
     277         9123 :   box->max_r = 0;
     278         9123 :   box->max_g = 0;
     279         9123 :   box->max_b = 0;
     280         9123 :   box->max_a = 0;
     281         9123 :   box->total_weight = 0;
     282       167036 :   for (i = box->start; i < box->end; ++i) {
     283       157913 :     c = entries[i].color;
     284       157913 :     r = (uint8_t)(c & 0xff);
     285       157913 :     g = (uint8_t)((c >> 8) & 0xff);
     286       157913 :     b = (uint8_t)((c >> 16) & 0xff);
     287       157913 :     a = (uint8_t)((c >> 24) & 0xff);
     288       157913 :     if (r < box->min_r) {
     289        15732 :       box->min_r = r;
     290              :     }
     291       157913 :     if (r > box->max_r) {
     292        16254 :       box->max_r = r;
     293              :     }
     294       157913 :     if (g < box->min_g) {
     295        10480 :       box->min_g = g;
     296              :     }
     297       157913 :     if (g > box->max_g) {
     298        14455 :       box->max_g = g;
     299              :     }
     300       157913 :     if (b < box->min_b) {
     301        10909 :       box->min_b = b;
     302              :     }
     303       157913 :     if (b > box->max_b) {
     304        18039 :       box->max_b = b;
     305              :     }
     306       157913 :     if (a < box->min_a) {
     307         8445 :       box->min_a = a;
     308              :     }
     309       157913 :     if (a > box->max_a) {
     310        12590 :       box->max_a = a;
     311              :     }
     312       157913 :     box->total_weight += (uint64_t)entries[i].count;
     313              :   }
     314              : }
     315              : 
     316     55863128 : static inline int compare_u32(const void *lhs, const void *rhs) {
     317     55863128 :   uint32_t a = *(const uint32_t *)lhs, b = *(const uint32_t *)rhs;
     318              : 
     319     55863128 :   if (a < b) {
     320     14669942 :     return -1;
     321              :   }
     322              : 
     323     41193186 :   if (a > b) {
     324     24983195 :     return 1;
     325              :   }
     326              : 
     327     16209991 :   return 0;
     328              : }
     329              : 
     330      3202653 : static inline bool color_box_splittable(const pngx_color_box_t *box) { return box && box->end > box->start + 1; }
     331              : 
     332       952896 : static inline uint8_t color_box_max_span(const pngx_color_box_t *box) {
     333       952896 :   uint8_t span_r = box->max_r - box->min_r, span_g = box->max_g - box->min_g, span_b = box->max_b - box->min_b, span_a = box->max_a - box->min_a, span = span_r;
     334              : 
     335       952896 :   return span_g > span ? span_g : (span_b > span ? span_b : (span_a > span ? span_a : span));
     336              : }
     337              : 
     338       952896 : static inline float color_box_detail_bias(const pngx_color_entry_t *entries, const pngx_color_box_t *box, uint8_t base_bits_rgb, uint8_t base_bits_alpha) {
     339              :   const pngx_color_entry_t *entry;
     340       952896 :   uint64_t weight = 0, w, accum_x2 = 0;
     341              :   uint32_t delta_rgb, delta_alpha, score_x2;
     342              :   size_t idx;
     343              : 
     344       952896 :   if (!entries || !box || box->end <= box->start) {
     345            0 :     return 0.0f;
     346              :   }
     347              : 
     348     11931878 :   for (idx = box->start; idx < box->end; ++idx) {
     349     10978982 :     entry = &entries[idx];
     350     10978982 :     w = entry->count ? entry->count : 1;
     351     10978982 :     delta_rgb = (entry->detail_bits_rgb > base_bits_rgb) ? (uint32_t)(entry->detail_bits_rgb - base_bits_rgb) : 0;
     352     10978982 :     delta_alpha = (entry->detail_bits_alpha > base_bits_alpha) ? (uint32_t)(entry->detail_bits_alpha - base_bits_alpha) : 0;
     353     10978982 :     score_x2 = (delta_rgb << 1) + delta_alpha;
     354              : 
     355     10978982 :     if (score_x2 > 0) {
     356      5078465 :       accum_x2 += (uint64_t)score_x2 * w;
     357              :     }
     358     10978982 :     weight += w;
     359              :   }
     360              : 
     361       952896 :   if (weight == 0) {
     362            0 :     return 0.0f;
     363              :   }
     364              : 
     365       952896 :   return (float)accum_x2 / (float)(weight * 2);
     366              : }
     367              : 
     368         4558 : static inline int select_box_to_split(const pngx_color_box_t *boxes, size_t box_count, const pngx_color_entry_t *entries, uint8_t base_bits_rgb, uint8_t base_bits_alpha) {
     369              :   size_t i;
     370         4558 :   float best_priority = -1.0f, detail_bias, span_score, weight_score, priority;
     371         4558 :   int best_index = -1;
     372              : 
     373         4558 :   if (!boxes || box_count == 0) {
     374            0 :     return -1;
     375              :   }
     376              : 
     377      3202653 :   for (i = 0; i < box_count; ++i) {
     378      3198095 :     if (!color_box_splittable(&boxes[i])) {
     379      2245199 :       continue;
     380              :     }
     381              : 
     382       952896 :     detail_bias = color_box_detail_bias(entries, &boxes[i], base_bits_rgb, base_bits_alpha);
     383       952896 :     span_score = (float)color_box_max_span(&boxes[i]) / 255.0f;
     384       952896 :     weight_score = (boxes[i].total_weight > 0) ? logf((float)boxes[i].total_weight + 1.0f) : 0.0f;
     385       952896 :     priority = (span_score * PNGX_REDUCED_PRIORITY_SPAN_WEIGHT) + (detail_bias * PNGX_REDUCED_PRIORITY_DETAIL_WEIGHT) + (weight_score * PNGX_REDUCED_PRIORITY_MASS_WEIGHT);
     386              : 
     387       952896 :     if (priority > best_priority) {
     388        25538 :       best_priority = priority;
     389        25538 :       best_index = (int)i;
     390              :     }
     391              :   }
     392              : 
     393         4558 :   return best_index;
     394              : }
     395              : 
     396         4558 : static inline size_t box_find_split_index(const pngx_color_entry_t *entries, const pngx_color_box_t *box) {
     397         4558 :   uint64_t half, accum = 0;
     398              :   size_t idx;
     399              : 
     400         4558 :   if (!entries || !box || box->end <= box->start) {
     401            0 :     return box ? box->start : 0;
     402              :   }
     403              : 
     404         4558 :   half = box->total_weight / 2;
     405         4558 :   if (half == 0) {
     406            0 :     half = 1;
     407              :   }
     408              : 
     409        67505 :   for (idx = box->start; idx < box->end; ++idx) {
     410        67505 :     accum += (uint64_t)entries[idx].count;
     411        67505 :     if (accum >= half) {
     412         4558 :       return idx;
     413              :     }
     414              :   }
     415              : 
     416            0 :   return box->start + ((box->end - box->start) / 2);
     417              : }
     418              : 
     419         4558 : static inline void sort_entries_by_axis(pngx_color_entry_t *entries, size_t count, int axis) {
     420         4558 :   if (!entries || count == 0) {
     421            0 :     return;
     422              :   }
     423              : 
     424         4558 :   switch (axis) {
     425         1294 :   case 0:
     426         1294 :     qsort(entries, count, sizeof(pngx_color_entry_t), compare_entries_r);
     427         1294 :     break;
     428         1213 :   case 1:
     429         1213 :     qsort(entries, count, sizeof(pngx_color_entry_t), compare_entries_g);
     430         1213 :     break;
     431         1220 :   case 2:
     432         1220 :     qsort(entries, count, sizeof(pngx_color_entry_t), compare_entries_b);
     433         1220 :     break;
     434          831 :   case 3:
     435          831 :     qsort(entries, count, sizeof(pngx_color_entry_t), compare_entries_a);
     436          831 :     break;
     437            0 :   default:
     438            0 :     qsort(entries, count, sizeof(pngx_color_entry_t), compare_entries_r);
     439            0 :     break;
     440              :   }
     441              : }
     442              : 
     443         4558 : static inline bool split_color_box(pngx_color_entry_t *entries, pngx_color_box_t *box, pngx_color_box_t *new_box) {
     444              :   size_t split_index;
     445              :   int axis;
     446              : 
     447         4558 :   if (!entries || !box || !new_box || !color_box_splittable(box)) {
     448            0 :     return false;
     449              :   }
     450              : 
     451         4558 :   axis = 0;
     452         4558 :   if ((box->max_g - box->min_g) > (box->max_r - box->min_r)) {
     453         1500 :     axis = 1;
     454              :   }
     455         4558 :   if ((box->max_b - box->min_b) > (box->max_g - box->min_g) && (box->max_b - box->min_b) >= (box->max_r - box->min_r)) {
     456         1278 :     axis = 2;
     457              :   }
     458         4558 :   if ((box->max_a - box->min_a) > (box->max_b - box->min_b) && (box->max_a - box->min_a) >= (box->max_g - box->min_g) && (box->max_a - box->min_a) >= (box->max_r - box->min_r)) {
     459          831 :     axis = 3;
     460              :   }
     461              : 
     462         4558 :   sort_entries_by_axis(entries + box->start, box->end - box->start, axis);
     463              : 
     464         4558 :   split_index = box_find_split_index(entries, box);
     465         4558 :   if (split_index <= box->start) {
     466          977 :     split_index = box->start + 1;
     467              :   }
     468         4558 :   if (split_index >= box->end) {
     469            0 :     split_index = box->end - 1;
     470              :   }
     471              : 
     472         4558 :   new_box->start = split_index;
     473         4558 :   new_box->end = box->end;
     474         4558 :   box->end = split_index;
     475              : 
     476         4558 :   color_box_refresh(entries, box);
     477         4558 :   color_box_refresh(entries, new_box);
     478              : 
     479         4558 :   return true;
     480              : }
     481              : 
     482       152503 : static inline int compare_color_map_by_color(const void *lhs, const void *rhs) {
     483       152503 :   const pngx_color_map_entry_t *a = (const pngx_color_map_entry_t *)lhs, *b = (const pngx_color_map_entry_t *)rhs;
     484              : 
     485       152503 :   return a->color < b->color ? -1 : (a->color > b->color ? 1 : 0);
     486              : }
     487              : 
     488       145518 : static inline int compare_color_map_by_mapped(const void *lhs, const void *rhs) {
     489       145518 :   const pngx_color_map_entry_t *a = (const pngx_color_map_entry_t *)lhs, *b = (const pngx_color_map_entry_t *)rhs;
     490              : 
     491       145518 :   return a->mapped_color < b->mapped_color ? -1 : (a->mapped_color > b->mapped_color ? 1 : 0);
     492              : }
     493              : 
     494      2462854 : static inline const pngx_color_map_entry_t *find_color_mapping(const pngx_color_map_entry_t *map, size_t count, uint32_t color) {
     495      2462854 :   size_t left = 0, right = (count > 0) ? count - 1 : 0, mid;
     496              : 
     497      2462854 :   if (!map || count == 0) {
     498            0 :     return NULL;
     499              :   }
     500              : 
     501     30603647 :   while (left <= right) {
     502     29094766 :     mid = left + ((right - left) / 2);
     503              : 
     504     29094766 :     if (map[mid].color == color) {
     505       878632 :       return &map[mid];
     506              :     }
     507              : 
     508     28216134 :     if (map[mid].color < color) {
     509     12347562 :       left = mid + 1;
     510              :     } else {
     511     15868572 :       if (mid == 0) {
     512        75341 :         break;
     513              :       }
     514     15793231 :       right = mid - 1;
     515              :     }
     516              :   }
     517              : 
     518      1584222 :   return NULL;
     519              : }
     520              : 
     521            7 : static inline void refine_reduced_palette(pngx_color_entry_t *entries, size_t unlocked_count, const uint32_t *seed_palette, const uint8_t *palette_bits_rgb, const uint8_t *palette_bits_alpha,
     522              :                                           size_t palette_count) {
     523            7 :   const size_t max_iter = 3;
     524              :   size_t iter, best_index, candidate, i, p;
     525              :   uint64_t *sum_r, *sum_g, *sum_b, *sum_a, *sum_w, w;
     526              :   uint32_t *palette, color, best_dist, weight, dist, new_color;
     527              :   uint8_t r, g, b, a, snap_bits_rgb, snap_bits_alpha, cr, cg, cb, ca;
     528              :   bool palette_changed;
     529              : 
     530            7 :   if (!entries || unlocked_count == 0 || !seed_palette || palette_count == 0 || palette_count > 4096) {
     531            0 :     return;
     532              :   }
     533              : 
     534            7 :   palette = (uint32_t *)malloc(sizeof(uint32_t) * palette_count);
     535            7 :   if (!palette) {
     536            0 :     return;
     537              :   }
     538              : 
     539            7 :   memcpy(palette, seed_palette, sizeof(uint32_t) * palette_count);
     540              : 
     541            7 :   sum_r = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
     542            7 :   sum_g = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
     543            7 :   sum_b = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
     544            7 :   sum_a = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
     545            7 :   sum_w = (uint64_t *)malloc(sizeof(uint64_t) * palette_count);
     546              : 
     547            7 :   if (!sum_r || !sum_g || !sum_b || !sum_a || !sum_w) {
     548            0 :     free(sum_r);
     549            0 :     free(sum_g);
     550            0 :     free(sum_b);
     551            0 :     free(sum_a);
     552            0 :     free(sum_w);
     553            0 :     free(palette);
     554              : 
     555            0 :     return;
     556              :   }
     557              : 
     558           21 :   for (iter = 0; iter < max_iter; ++iter) {
     559           17 :     palette_changed = false;
     560              : 
     561           17 :     memset(sum_r, 0, sizeof(uint64_t) * palette_count);
     562           17 :     memset(sum_g, 0, sizeof(uint64_t) * palette_count);
     563           17 :     memset(sum_b, 0, sizeof(uint64_t) * palette_count);
     564           17 :     memset(sum_a, 0, sizeof(uint64_t) * palette_count);
     565           17 :     memset(sum_w, 0, sizeof(uint64_t) * palette_count);
     566              : 
     567        47374 :     for (i = 0; i < unlocked_count; ++i) {
     568        47357 :       color = entries[i].color;
     569        47357 :       best_dist = UINT32_MAX;
     570        47357 :       best_index = 0;
     571        47357 :       candidate = 0;
     572        47357 :       weight = entries[i].count ? entries[i].count : 1;
     573              : 
     574     33876129 :       for (candidate = 0; candidate < palette_count; ++candidate) {
     575     33828772 :         dist = color_distance_sq_u32(color, palette[candidate]);
     576     33828772 :         if (dist < best_dist) {
     577       337174 :           best_dist = dist;
     578       337174 :           best_index = candidate;
     579              :         }
     580              :       }
     581              : 
     582        47357 :       unpack_rgba_u32(color, &cr, &cg, &cb, &ca);
     583              : 
     584        47357 :       sum_r[best_index] += (uint64_t)cr * (uint64_t)weight;
     585        47357 :       sum_g[best_index] += (uint64_t)cg * (uint64_t)weight;
     586        47357 :       sum_b[best_index] += (uint64_t)cb * (uint64_t)weight;
     587        47357 :       sum_a[best_index] += (uint64_t)ca * (uint64_t)weight;
     588        47357 :       sum_w[best_index] += (uint64_t)weight;
     589              :     }
     590              : 
     591        10974 :     for (p = 0; p < palette_count; ++p) {
     592        10957 :       w = sum_w[p];
     593        10957 :       if (w == 0) {
     594           78 :         continue;
     595              :       }
     596        10879 :       r = (uint8_t)((sum_r[p] + (w / 2)) / w);
     597        10879 :       g = (uint8_t)((sum_g[p] + (w / 2)) / w);
     598        10879 :       b = (uint8_t)((sum_b[p] + (w / 2)) / w);
     599        10879 :       a = (uint8_t)((sum_a[p] + (w / 2)) / w);
     600        10879 :       snap_bits_rgb = palette_bits_rgb ? palette_bits_rgb[p] : COLOPRESSO_PNGX_REDUCED_BITS_MAX;
     601        10879 :       snap_bits_alpha = palette_bits_alpha ? palette_bits_alpha[p] : COLOPRESSO_PNGX_REDUCED_BITS_MAX;
     602        10879 :       snap_rgba_to_bits(&r, &g, &b, &a, snap_bits_rgb, snap_bits_alpha);
     603        10879 :       new_color = pack_rgba_u32(r, g, b, a);
     604        10879 :       if (new_color != palette[p]) {
     605          831 :         palette[p] = new_color;
     606          831 :         palette_changed = true;
     607              :       }
     608              :     }
     609              : 
     610           17 :     if (!palette_changed) {
     611            3 :       break;
     612              :     }
     613              :   }
     614              : 
     615        16940 :   for (i = 0; i < unlocked_count; ++i) {
     616        16933 :     color = entries[i].color;
     617        16933 :     best_dist = UINT32_MAX;
     618        16933 :     best_index = 0;
     619              : 
     620     13258047 :     for (candidate = 0; candidate < palette_count; ++candidate) {
     621     13241114 :       dist = color_distance_sq_u32(color, palette[candidate]);
     622     13241114 :       if (dist < best_dist) {
     623       124418 :         best_dist = dist;
     624       124418 :         best_index = candidate;
     625              :       }
     626              :     }
     627              : 
     628        16933 :     entries[i].mapped_color = palette[best_index];
     629              :   }
     630              : 
     631            7 :   free(sum_r);
     632            7 :   free(sum_g);
     633            7 :   free(sum_b);
     634            7 :   free(sum_a);
     635            7 :   free(sum_w);
     636              : 
     637            7 :   free(palette);
     638              : }
     639              : 
     640            8 : static inline bool build_color_histogram(const pngx_rgba_image_t *image, const pngx_options_t *opts, const pngx_quant_support_t *support, pngx_color_histogram_t *hist) {
     641            8 :   const cpres_rgba_color_t *protected_colors = (opts && opts->protected_colors_count > 0) ? opts->protected_colors : NULL;
     642            8 :   pngx_histogram_sample_t *samples = NULL;
     643              :   pngx_color_entry_t tmp;
     644            8 :   size_t pixel_count, i, unique_count, base, run, write, protected_count = (protected_colors && opts->protected_colors_count > 0) ? (size_t)opts->protected_colors_count : 0;
     645              :   uint64_t weight_sum;
     646              :   uint32_t protected_table[256], color;
     647            8 :   uint8_t bits_rgb = 8, bits_alpha = 8, r, g, b, a, sample_bits_rgb, sample_bits_alpha, hint, hint_rgb, hint_alpha, max_bits_rgb, max_bits_alpha;
     648            8 :   bool quantize_rgb = false, quantize_alpha = false;
     649              : 
     650            8 :   if (!image || !image->rgba || !hist || image->pixel_count == 0) {
     651            0 :     return false;
     652              :   }
     653              : 
     654            8 :   if (opts) {
     655            8 :     bits_rgb = clamp_reduced_bits(opts->lossy_reduced_bits_rgb);
     656            8 :     bits_alpha = clamp_reduced_bits(opts->lossy_reduced_alpha_bits);
     657              :   }
     658              : 
     659            8 :   quantize_rgb = bits_rgb < 8;
     660            8 :   quantize_alpha = bits_alpha < 8;
     661              : 
     662            8 :   if (protected_count > 256) {
     663            0 :     protected_count = 256;
     664              :   }
     665              : 
     666           12 :   for (i = 0; i < protected_count; ++i) {
     667            4 :     r = protected_colors[i].r;
     668            4 :     g = protected_colors[i].g;
     669            4 :     b = protected_colors[i].b;
     670            4 :     a = protected_colors[i].a;
     671            4 :     if (quantize_rgb) {
     672            4 :       r = quantize_bits(r, bits_rgb);
     673            4 :       g = quantize_bits(g, bits_rgb);
     674            4 :       b = quantize_bits(b, bits_rgb);
     675              :     }
     676            4 :     if (quantize_alpha) {
     677            4 :       a = quantize_bits(a, bits_alpha);
     678              :     }
     679            4 :     protected_table[i] = pack_rgba_u32(r, g, b, a);
     680              :   }
     681              : 
     682            8 :   pixel_count = image->pixel_count;
     683              : 
     684            8 :   samples = (pngx_histogram_sample_t *)malloc(pixel_count * sizeof(pngx_histogram_sample_t));
     685            8 :   if (!samples) {
     686            0 :     return false;
     687              :   }
     688              : 
     689      2491408 :   for (i = 0; i < pixel_count; ++i) {
     690      2491400 :     base = i * 4;
     691      2491400 :     r = image->rgba[base + 0];
     692      2491400 :     g = image->rgba[base + 1];
     693      2491400 :     b = image->rgba[base + 2];
     694      2491400 :     a = image->rgba[base + 3];
     695      2491400 :     sample_bits_rgb = bits_rgb;
     696      2491400 :     sample_bits_alpha = bits_alpha;
     697              : 
     698      2491400 :     if (support && support->bit_hint_map && i < support->bit_hint_len) {
     699      2491400 :       hint = support->bit_hint_map[i];
     700      2491400 :       hint_rgb = hint >> 4;
     701      2491400 :       hint_alpha = hint & 0x0fu;
     702              : 
     703      2491400 :       if (hint_rgb >= COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
     704      2491400 :         sample_bits_rgb = clamp_reduced_bits(hint_rgb);
     705              :       }
     706              : 
     707      2491400 :       if (hint_alpha >= COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
     708      2491400 :         sample_bits_alpha = clamp_reduced_bits(hint_alpha);
     709              :       }
     710              :     }
     711              : 
     712      2491400 :     if (quantize_rgb) {
     713      2424840 :       r = quantize_bits(r, bits_rgb);
     714      2424840 :       g = quantize_bits(g, bits_rgb);
     715      2424840 :       b = quantize_bits(b, bits_rgb);
     716              :     }
     717              : 
     718      2491400 :     if (quantize_alpha) {
     719      2491400 :       a = quantize_bits(a, bits_alpha);
     720              :     }
     721              : 
     722      2491400 :     samples[i].color = pack_rgba_u32(r, g, b, a);
     723      2491400 :     samples[i].weight = histogram_importance_weight(support, i);
     724      2491400 :     samples[i].rgb_bits = sample_bits_rgb;
     725      2491400 :     samples[i].alpha_bits = sample_bits_alpha;
     726              :   }
     727              : 
     728            8 :   qsort(samples, pixel_count, sizeof(pngx_histogram_sample_t), compare_histogram_sample);
     729              : 
     730            8 :   unique_count = 0;
     731              : 
     732        16945 :   for (i = 0; i < pixel_count;) {
     733        16937 :     color = samples[i].color;
     734        16937 :     run = 1;
     735              : 
     736      2491400 :     while (i + run < pixel_count && samples[i + run].color == color) {
     737      2474463 :       ++run;
     738              :     }
     739              : 
     740        16937 :     ++unique_count;
     741        16937 :     i += run;
     742              :   }
     743              : 
     744            8 :   hist->entries = (pngx_color_entry_t *)malloc(unique_count * sizeof(pngx_color_entry_t));
     745            8 :   if (!hist->entries) {
     746            0 :     free(samples);
     747            0 :     return false;
     748              :   }
     749            8 :   hist->count = unique_count;
     750            8 :   hist->unlocked_count = 0;
     751              : 
     752        16945 :   for (i = 0, unique_count = 0; i < pixel_count;) {
     753        16937 :     color = samples[i].color;
     754        16937 :     run = 1;
     755        16937 :     weight_sum = samples[i].weight;
     756        16937 :     max_bits_rgb = samples[i].rgb_bits;
     757        16937 :     max_bits_alpha = samples[i].alpha_bits;
     758              : 
     759      2491400 :     while (i + run < pixel_count && samples[i + run].color == color) {
     760      2474463 :       weight_sum += samples[i + run].weight;
     761              : 
     762      2474463 :       if (samples[i + run].rgb_bits > max_bits_rgb) {
     763          770 :         max_bits_rgb = samples[i + run].rgb_bits;
     764              :       }
     765              : 
     766      2474463 :       if (samples[i + run].alpha_bits > max_bits_alpha) {
     767          540 :         max_bits_alpha = samples[i + run].alpha_bits;
     768              :       }
     769              : 
     770      2474463 :       ++run;
     771              :     }
     772              : 
     773        16937 :     hist->entries[unique_count].color = color;
     774        16937 :     hist->entries[unique_count].count = (uint32_t)((weight_sum > UINT32_MAX) ? UINT32_MAX : weight_sum);
     775        16937 :     hist->entries[unique_count].mapped_color = color;
     776        16937 :     hist->entries[unique_count].locked = is_protected_color(color, protected_table, protected_count);
     777        16937 :     hist->entries[unique_count].detail_bits_rgb = clamp_reduced_bits(max_bits_rgb);
     778        16937 :     hist->entries[unique_count].detail_bits_alpha = clamp_reduced_bits(max_bits_alpha);
     779        16937 :     ++unique_count;
     780        16937 :     i += run;
     781              :   }
     782              : 
     783            8 :   free(samples);
     784              : 
     785            8 :   if (unique_count == 0) {
     786            0 :     return true;
     787              :   }
     788              : 
     789            8 :   write = 0;
     790              : 
     791        16945 :   for (i = 0; i < unique_count; ++i) {
     792        16937 :     if (!hist->entries[i].locked) {
     793        16933 :       if (write != i) {
     794            0 :         tmp = hist->entries[write];
     795            0 :         hist->entries[write] = hist->entries[i];
     796            0 :         hist->entries[i] = tmp;
     797              :       }
     798        16933 :       ++write;
     799              :     }
     800              :   }
     801            8 :   hist->unlocked_count = write;
     802              : 
     803            8 :   return true;
     804              : }
     805              : 
     806            7 : static inline bool apply_reduced_rgba32_quantization(pngx_color_histogram_t *hist, pngx_rgba_image_t *image, uint32_t target_colors, uint8_t bits_rgb, uint8_t bits_alpha, uint32_t *applied_colors) {
     807              :   const pngx_color_map_entry_t *mapping;
     808            7 :   pngx_color_box_t *boxes = NULL, new_box;
     809            7 :   pngx_color_map_entry_t *map = NULL;
     810            7 :   uint32_t *palette_seed = NULL, actual_colors = 0, mapped, original;
     811            7 :   uint8_t *palette_bits_rgb = NULL, *palette_bits_alpha = NULL, r, g, b, a, *px;
     812            7 :   size_t box_count = 0, map_count, i, idx;
     813              :   int split_index;
     814              : 
     815            7 :   if (!hist || !image || target_colors == 0) {
     816            0 :     if (applied_colors) {
     817            0 :       *applied_colors = (uint32_t)(hist ? hist->count : 0);
     818              :     }
     819              : 
     820            0 :     return true;
     821              :   }
     822              : 
     823            7 :   if (hist->unlocked_count == 0) {
     824            0 :     if (applied_colors) {
     825            0 :       *applied_colors = (uint32_t)hist->count;
     826              :     }
     827              : 
     828            0 :     return true;
     829              :   }
     830              : 
     831            7 :   if (target_colors > (uint32_t)hist->unlocked_count) {
     832            0 :     target_colors = (uint32_t)hist->unlocked_count;
     833              :   }
     834              : 
     835            7 :   if (target_colors == 0) {
     836            0 :     target_colors = 1;
     837              :   }
     838              : 
     839            7 :   boxes = (pngx_color_box_t *)calloc(target_colors, sizeof(pngx_color_box_t));
     840            7 :   if (!boxes) {
     841            0 :     return false;
     842              :   }
     843              : 
     844            7 :   boxes[0].start = 0;
     845            7 :   boxes[0].end = hist->unlocked_count;
     846            7 :   color_box_refresh(hist->entries, &boxes[0]);
     847            7 :   box_count = (boxes[0].end > boxes[0].start) ? 1 : 0;
     848              : 
     849         4565 :   while (box_count < target_colors) {
     850         4558 :     split_index = select_box_to_split(boxes, box_count, hist->entries, bits_rgb, bits_alpha);
     851         4558 :     if (split_index < 0) {
     852            0 :       break;
     853              :     }
     854              : 
     855         4558 :     if (!split_color_box(hist->entries, &boxes[split_index], &new_box)) {
     856            0 :       break;
     857              :     }
     858              : 
     859         4558 :     boxes[box_count++] = new_box;
     860              :   }
     861              : 
     862            7 :   if (box_count == 0) {
     863            0 :     free(boxes);
     864              : 
     865            0 :     if (applied_colors) {
     866            0 :       *applied_colors = (uint32_t)hist->count;
     867              :     }
     868              : 
     869            0 :     return true;
     870              :   }
     871              : 
     872            7 :   palette_seed = (uint32_t *)malloc(sizeof(uint32_t) * box_count);
     873            7 :   palette_bits_rgb = (uint8_t *)calloc(box_count, sizeof(uint8_t));
     874            7 :   palette_bits_alpha = (uint8_t *)calloc(box_count, sizeof(uint8_t));
     875            7 :   if (!palette_seed || !palette_bits_rgb || !palette_bits_alpha) {
     876            0 :     free(palette_seed);
     877            0 :     free(palette_bits_rgb);
     878            0 :     free(palette_bits_alpha);
     879            0 :     free(boxes);
     880              : 
     881            0 :     return false;
     882              :   }
     883              : 
     884         4572 :   for (i = 0; i < box_count; ++i) {
     885         4565 :     mapped = box_representative_color(hist->entries, &boxes[i]);
     886         4565 :     unpack_rgba_u32(mapped, &r, &g, &b, &a);
     887         4565 :     snap_rgba_to_bits(&r, &g, &b, &a, bits_rgb, bits_alpha);
     888         4565 :     mapped = pack_rgba_u32(r, g, b, a);
     889         4565 :     palette_seed[i] = mapped;
     890         4565 :     palette_bits_rgb[i] = bits_rgb;
     891         4565 :     palette_bits_alpha[i] = bits_alpha;
     892              : 
     893        21498 :     for (idx = boxes[i].start; idx < boxes[i].end; ++idx) {
     894        16933 :       hist->entries[idx].mapped_color = mapped;
     895              :     }
     896              :   }
     897              : 
     898            7 :   if (box_count > 0 && box_count <= 4096) {
     899            7 :     refine_reduced_palette(hist->entries, hist->unlocked_count, palette_seed, palette_bits_rgb, palette_bits_alpha, box_count);
     900              :   }
     901              : 
     902            7 :   for (i = hist->unlocked_count; i < hist->count; ++i) {
     903            0 :     hist->entries[i].mapped_color = hist->entries[i].color;
     904              :   }
     905              : 
     906            7 :   map_count = hist->count;
     907            7 :   if (map_count == 0) {
     908            0 :     free(palette_seed);
     909            0 :     free(palette_bits_rgb);
     910            0 :     free(palette_bits_alpha);
     911            0 :     free(boxes);
     912              : 
     913            0 :     if (applied_colors) {
     914            0 :       *applied_colors = 0;
     915              :     }
     916              : 
     917            0 :     return true;
     918              :   }
     919              : 
     920            7 :   map = (pngx_color_map_entry_t *)malloc(map_count * sizeof(pngx_color_map_entry_t));
     921            7 :   if (!map) {
     922            0 :     free(palette_seed);
     923            0 :     free(palette_bits_rgb);
     924            0 :     free(palette_bits_alpha);
     925            0 :     free(boxes);
     926            0 :     return false;
     927              :   }
     928              : 
     929        16940 :   for (i = 0; i < map_count; ++i) {
     930        16933 :     map[i].color = hist->entries[i].color;
     931        16933 :     map[i].mapped_color = hist->entries[i].mapped_color;
     932              :   }
     933              : 
     934            7 :   qsort(map, map_count, sizeof(*map), compare_color_map_by_color);
     935              : 
     936      2491403 :   for (i = 0; i < image->pixel_count; ++i) {
     937      2491396 :     px = &image->rgba[i * PNGX_RGBA_CHANNELS];
     938      2491396 :     if (px[3] <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
     939        28542 :       continue;
     940              :     }
     941      2462854 :     original = pack_rgba_u32(px[0], px[1], px[2], px[3]);
     942      2462854 :     mapping = find_color_mapping(map, map_count, original);
     943              : 
     944      2462854 :     if (!mapping) {
     945      1584222 :       continue;
     946              :     }
     947              : 
     948       878632 :     unpack_rgba_u32(mapping->mapped_color, &px[0], &px[1], &px[2], &px[3]);
     949              :   }
     950              : 
     951            7 :   qsort(map, map_count, sizeof(*map), compare_color_map_by_mapped);
     952        16940 :   for (i = 0; i < map_count; ++i) {
     953        16933 :     if (i == 0 || map[i].mapped_color != map[i - 1].mapped_color) {
     954         4537 :       ++actual_colors;
     955              :     }
     956              :   }
     957              : 
     958            7 :   if (applied_colors) {
     959            7 :     *applied_colors = actual_colors;
     960              :   }
     961              : 
     962            7 :   free(map);
     963            7 :   free(palette_seed);
     964            7 :   free(palette_bits_rgb);
     965            7 :   free(palette_bits_alpha);
     966            7 :   free(boxes);
     967              : 
     968            7 :   return true;
     969              : }
     970              : 
     971           17 : static inline bool pack_sorted_rgba(const uint8_t *rgba, size_t pixel_count, uint32_t **out_sorted) {
     972              :   uint32_t *packed;
     973              :   size_t base, i;
     974              : 
     975           17 :   if (!rgba || pixel_count == 0 || !out_sorted) {
     976            0 :     return false;
     977              :   }
     978              : 
     979           17 :   if (pixel_count > SIZE_MAX / sizeof(uint32_t)) {
     980            0 :     return false;
     981              :   }
     982              : 
     983           17 :   packed = (uint32_t *)malloc(sizeof(uint32_t) * pixel_count);
     984           17 :   if (!packed) {
     985            0 :     return false;
     986              :   }
     987              : 
     988      3673369 :   for (i = 0; i < pixel_count; ++i) {
     989      3673352 :     base = i * 4;
     990      3673352 :     packed[i] = pack_rgba_u32(rgba[base + 0], rgba[base + 1], rgba[base + 2], rgba[base + 3]);
     991              :   }
     992              : 
     993           17 :   qsort(packed, pixel_count, sizeof(uint32_t), compare_u32);
     994           17 :   *out_sorted = packed;
     995              : 
     996           17 :   return true;
     997              : }
     998              : 
     999           13 : static inline size_t count_unique_rgba(const uint8_t *rgba, size_t pixel_count) {
    1000              :   uint32_t *packed;
    1001           13 :   size_t i, unique = 0;
    1002              : 
    1003           13 :   if (!rgba || pixel_count == 0) {
    1004            0 :     return 0;
    1005              :   }
    1006              : 
    1007           13 :   if (!pack_sorted_rgba(rgba, pixel_count, &packed)) {
    1008            0 :     return 0;
    1009              :   }
    1010              : 
    1011      3082517 :   for (i = 0; i < pixel_count; ++i) {
    1012      3082504 :     if (i == 0 || packed[i] != packed[i - 1]) {
    1013        74495 :       ++unique;
    1014              :     }
    1015              :   }
    1016              : 
    1017           13 :   free(packed);
    1018              : 
    1019           13 :   return unique;
    1020              : }
    1021              : 
    1022            4 : static inline bool build_color_frequency(const uint8_t *rgba, size_t pixel_count, pngx_color_freq_t **out_freq, size_t *out_count) {
    1023              :   pngx_color_freq_t *freq, *shrunk;
    1024              :   uint32_t *packed;
    1025              :   size_t i, unique;
    1026              : 
    1027            4 :   if (!out_freq || !out_count) {
    1028            0 :     return false;
    1029              :   }
    1030              : 
    1031            4 :   if (!pack_sorted_rgba(rgba, pixel_count, &packed)) {
    1032            0 :     return false;
    1033              :   }
    1034              : 
    1035            4 :   freq = (pngx_color_freq_t *)malloc(sizeof(pngx_color_freq_t) * pixel_count);
    1036            4 :   if (!freq) {
    1037            0 :     free(packed);
    1038            0 :     return false;
    1039              :   }
    1040              : 
    1041            4 :   unique = 0;
    1042       590852 :   for (i = 0; i < pixel_count; ++i) {
    1043       590848 :     if (i == 0 || packed[i] != packed[i - 1]) {
    1044         4373 :       freq[unique].color = packed[i];
    1045         4373 :       freq[unique].count = 1;
    1046         4373 :       ++unique;
    1047              :     } else {
    1048       586475 :       ++freq[unique - 1].count;
    1049              :     }
    1050              :   }
    1051              : 
    1052            4 :   free(packed);
    1053              : 
    1054            4 :   if (unique == 0) {
    1055            0 :     free(freq);
    1056            0 :     *out_freq = NULL;
    1057            0 :     *out_count = 0;
    1058            0 :     return true;
    1059              :   }
    1060              : 
    1061            4 :   shrunk = (pngx_color_freq_t *)realloc(freq, sizeof(pngx_color_freq_t) * unique);
    1062            4 :   if (!shrunk) {
    1063            0 :     free(freq);
    1064            0 :     return false;
    1065              :   }
    1066            4 :   freq = shrunk;
    1067              : 
    1068            4 :   *out_freq = freq;
    1069            4 :   *out_count = unique;
    1070              : 
    1071            4 :   return true;
    1072              : }
    1073              : 
    1074       590848 : static inline bool find_freq_index(const pngx_color_freq_t *freq, size_t freq_count, uint32_t color, size_t *index_out) {
    1075       590848 :   size_t left = 0, right = freq_count, mid;
    1076              : 
    1077       590848 :   if (!freq || freq_count == 0) {
    1078            0 :     return false;
    1079              :   }
    1080              : 
    1081      5599644 :   while (left < right) {
    1082      5599644 :     mid = left + (right - left) / 2;
    1083      5599644 :     if (freq[mid].color == color) {
    1084       590848 :       if (index_out) {
    1085       590848 :         *index_out = mid;
    1086              :       }
    1087              : 
    1088       590848 :       return true;
    1089              :     }
    1090              : 
    1091      5008796 :     if (freq[mid].color < color) {
    1092      2165167 :       left = mid + 1;
    1093              :     } else {
    1094      2843629 :       right = mid;
    1095              :     }
    1096              :   }
    1097              : 
    1098            0 :   return false;
    1099              : }
    1100              : 
    1101        39229 : static inline int compare_freq_rank_desc(const void *lhs, const void *rhs) {
    1102        39229 :   const pngx_freq_rank_t *a = (const pngx_freq_rank_t *)lhs, *b = (const pngx_freq_rank_t *)rhs;
    1103              : 
    1104        39229 :   return a->count != b->count ? (b->count - a->count) : (a->color != b->color ? (a->color - b->color) : 0);
    1105              : }
    1106              : 
    1107            4 : static inline bool enforce_manual_reduced_limit(pngx_rgba_image_t *image, uint32_t manual_limit, uint8_t bits_rgb, uint8_t bits_alpha, uint32_t *applied_colors) {
    1108            4 :   pngx_color_freq_t *freq = NULL;
    1109            4 :   pngx_freq_rank_t *rank = NULL;
    1110            4 :   uint32_t *mapped = NULL, source, best_color, best_dist, candidate_color, distance, original;
    1111            4 :   size_t *keep_indices = NULL, freq_count = 0, keep_count, current_unique, unique_after, pixel_index, idx, best_keep, keep_idx, candidate_index, base, freq_index, i;
    1112            4 :   bool success = false;
    1113              : 
    1114            4 :   if (!image || !image->rgba || image->pixel_count == 0 || manual_limit == 0) {
    1115            0 :     if (applied_colors && image && image->rgba) {
    1116            0 :       current_unique = count_unique_rgba(image->rgba, image->pixel_count);
    1117              : 
    1118            0 :       if (current_unique > UINT32_MAX) {
    1119            0 :         current_unique = UINT32_MAX;
    1120              :       }
    1121              : 
    1122            0 :       *applied_colors = (uint32_t)current_unique;
    1123              :     }
    1124              : 
    1125            0 :     return true;
    1126              :   }
    1127              : 
    1128            4 :   snap_rgba_image_to_bits(image->rgba, image->pixel_count, bits_rgb, bits_alpha);
    1129              : 
    1130            4 :   if (!build_color_frequency(image->rgba, image->pixel_count, &freq, &freq_count)) {
    1131            0 :     goto bailout;
    1132              :   }
    1133              : 
    1134            4 :   if (freq_count == 0) {
    1135            0 :     if (applied_colors) {
    1136            0 :       *applied_colors = 0;
    1137              :     }
    1138              : 
    1139            0 :     success = true;
    1140              : 
    1141            0 :     goto bailout;
    1142              :   }
    1143              : 
    1144            4 :   if (freq_count <= (size_t)manual_limit) {
    1145            0 :     if (applied_colors) {
    1146            0 :       *applied_colors = (uint32_t)freq_count;
    1147              :     }
    1148              : 
    1149            0 :     success = true;
    1150              : 
    1151            0 :     goto bailout;
    1152              :   }
    1153              : 
    1154            4 :   keep_count = (size_t)manual_limit;
    1155            4 :   if (keep_count > freq_count) {
    1156            0 :     keep_count = freq_count;
    1157              :   }
    1158              : 
    1159            4 :   rank = (pngx_freq_rank_t *)malloc(sizeof(pngx_freq_rank_t) * freq_count);
    1160            4 :   mapped = (uint32_t *)malloc(sizeof(uint32_t) * freq_count);
    1161            4 :   keep_indices = (size_t *)malloc(sizeof(size_t) * keep_count);
    1162            4 :   if (!rank || !mapped || !keep_indices) {
    1163            0 :     goto bailout;
    1164              :   }
    1165              : 
    1166         4377 :   for (i = 0; i < freq_count; ++i) {
    1167         4373 :     rank[i].index = i;
    1168         4373 :     rank[i].count = freq[i].count;
    1169         4373 :     rank[i].color = freq[i].color;
    1170         4373 :     mapped[i] = freq[i].color;
    1171              :   }
    1172              : 
    1173            4 :   qsort(rank, freq_count, sizeof(pngx_freq_rank_t), compare_freq_rank_desc);
    1174              : 
    1175         2700 :   for (i = 0; i < keep_count; ++i) {
    1176         2696 :     keep_indices[i] = rank[i].index;
    1177              :   }
    1178              : 
    1179         1681 :   for (i = keep_count; i < freq_count; ++i) {
    1180         1677 :     idx = rank[i].index;
    1181         1677 :     source = freq[idx].color;
    1182         1677 :     best_keep = keep_indices[0];
    1183         1677 :     best_color = freq[best_keep].color;
    1184         1677 :     best_dist = color_distance_sq_u32(source, best_color);
    1185              : 
    1186       601676 :     for (keep_idx = 1; keep_idx < keep_count; ++keep_idx) {
    1187       599999 :       candidate_index = keep_indices[keep_idx];
    1188       599999 :       candidate_color = freq[candidate_index].color;
    1189       599999 :       distance = color_distance_sq_u32(source, candidate_color);
    1190              : 
    1191       599999 :       if (distance < best_dist) {
    1192        10249 :         best_dist = distance;
    1193        10249 :         best_color = candidate_color;
    1194              :       }
    1195              :     }
    1196              : 
    1197         1677 :     mapped[idx] = best_color;
    1198              :   }
    1199              : 
    1200       590852 :   for (pixel_index = 0; pixel_index < image->pixel_count; ++pixel_index) {
    1201       590848 :     base = pixel_index * 4;
    1202       590848 :     original = pack_rgba_u32(image->rgba[base + 0], image->rgba[base + 1], image->rgba[base + 2], image->rgba[base + 3]);
    1203              : 
    1204       590848 :     if (!find_freq_index(freq, freq_count, original, &freq_index)) {
    1205            0 :       continue;
    1206              :     }
    1207              : 
    1208       590848 :     if (mapped[freq_index] == original) {
    1209       484355 :       continue;
    1210              :     }
    1211              : 
    1212       106493 :     unpack_rgba_u32(mapped[freq_index], &image->rgba[base + 0], &image->rgba[base + 1], &image->rgba[base + 2], &image->rgba[base + 3]);
    1213              :   }
    1214              : 
    1215            4 :   snap_rgba_image_to_bits(image->rgba, image->pixel_count, bits_rgb, bits_alpha);
    1216              : 
    1217            4 :   unique_after = count_unique_rgba(image->rgba, image->pixel_count);
    1218            4 :   if (applied_colors) {
    1219            4 :     if (unique_after > UINT32_MAX) {
    1220            0 :       unique_after = UINT32_MAX;
    1221              :     }
    1222            4 :     *applied_colors = (uint32_t)unique_after;
    1223              :   }
    1224              : 
    1225            4 :   cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Reduced RGBA32 manual target enforcement trimmed %zu -> %zu colors", freq_count, unique_after);
    1226              : 
    1227            4 :   success = true;
    1228              : 
    1229            4 : bailout:
    1230            4 :   free(freq);
    1231            4 :   free(rank);
    1232            4 :   free(mapped);
    1233            4 :   free(keep_indices);
    1234              : 
    1235            4 :   return success;
    1236              : }
    1237              : 
    1238      4983312 : static inline uint8_t resolve_pixel_bits(uint8_t importance, uint8_t base_bits, uint8_t boost_bits) {
    1239      4983312 :   uint8_t resolved = base_bits, mid_bits;
    1240              : 
    1241      4983312 :   if (boost_bits <= base_bits) {
    1242       133648 :     return base_bits;
    1243              :   }
    1244              : 
    1245      4849664 :   if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_FULL) {
    1246        30540 :     resolved = boost_bits;
    1247      4819124 :   } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_HIGH) {
    1248       311036 :     mid_bits = (uint8_t)((base_bits + boost_bits + 1) / 2);
    1249       311036 :     resolved = mid_bits;
    1250      4508088 :   } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_MEDIUM) {
    1251      1374458 :     mid_bits = (uint8_t)((base_bits * 2 + boost_bits + 2) / 3);
    1252      1374458 :     resolved = mid_bits;
    1253      3133630 :   } else if (importance >= PNGX_REDUCED_IMPORTANCE_LEVEL_LOW) {
    1254      1456982 :     resolved = (uint8_t)((base_bits * 3 + boost_bits + 3) / 4);
    1255              :   }
    1256              : 
    1257      4849664 :   if (resolved > boost_bits) {
    1258            0 :     resolved = boost_bits;
    1259              :   }
    1260      4849664 :   if (resolved < base_bits) {
    1261            0 :     resolved = base_bits;
    1262              :   }
    1263              : 
    1264      4849664 :   return resolved;
    1265              : }
    1266              : 
    1267            5 : static inline void reduce_rgba_custom_bitdepth_simple(uint8_t *rgba, size_t pixel_count, uint8_t bits_rgb, uint8_t bits_alpha, const uint8_t *importance_map, size_t importance_map_len,
    1268              :                                                       uint8_t boost_bits_rgb, uint8_t boost_bits_alpha, uint8_t *bit_hint_map, size_t bit_hint_len) {
    1269              :   size_t i, base;
    1270              :   uint8_t importance, pixel_bits_rgb, pixel_bits_alpha;
    1271              : 
    1272            5 :   if (!rgba || pixel_count == 0) {
    1273            0 :     return;
    1274              :   }
    1275              : 
    1276            5 :   bits_rgb = clamp_reduced_bits(bits_rgb);
    1277            5 :   bits_alpha = clamp_reduced_bits(bits_alpha);
    1278            5 :   boost_bits_rgb = clamp_reduced_bits(boost_bits_rgb);
    1279            5 :   boost_bits_alpha = clamp_reduced_bits(boost_bits_alpha);
    1280              : 
    1281        66829 :   for (i = 0; i < pixel_count; ++i) {
    1282        66824 :     base = i * PNGX_RGBA_CHANNELS;
    1283        66824 :     importance = (importance_map && i < importance_map_len) ? importance_map[i] : 0;
    1284        66824 :     pixel_bits_rgb = resolve_pixel_bits(importance, bits_rgb, boost_bits_rgb);
    1285        66824 :     pixel_bits_alpha = resolve_pixel_bits(importance, bits_alpha, boost_bits_alpha);
    1286              : 
    1287        66824 :     if (bit_hint_map && i < bit_hint_len) {
    1288        66824 :       bit_hint_map[i] = (uint8_t)((pixel_bits_rgb << 4) | (pixel_bits_alpha & 0x0fu));
    1289              :     }
    1290              : 
    1291        66824 :     if (rgba[base + 3] > PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
    1292        66760 :       if (pixel_bits_rgb < PNGX_FULL_CHANNEL_BITS) {
    1293          200 :         rgba[base + 0] = quantize_bits(rgba[base + 0], pixel_bits_rgb);
    1294          200 :         rgba[base + 1] = quantize_bits(rgba[base + 1], pixel_bits_rgb);
    1295          200 :         rgba[base + 2] = quantize_bits(rgba[base + 2], pixel_bits_rgb);
    1296              :       }
    1297              :     }
    1298        66824 :     if (pixel_bits_alpha < PNGX_FULL_CHANNEL_BITS) {
    1299        66824 :       rgba[base + 3] = quantize_bits(rgba[base + 3], pixel_bits_alpha);
    1300              :     }
    1301              :   }
    1302              : }
    1303              : 
    1304      2424832 : static inline void process_custom_bitdepth_pixel(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint32_t x, uint32_t y, uint8_t base_bits_rgb, uint8_t base_bits_alpha, uint8_t boost_bits_rgb,
    1305              :                                                  uint8_t boost_bits_alpha, float base_dither, const uint8_t *importance_map, size_t pixel_count, float *err_curr, float *err_next, bool left_to_right,
    1306              :                                                  uint8_t *bit_hint_map, size_t bit_hint_len) {
    1307      2424832 :   size_t pixel_index = (size_t)y * (size_t)width + (size_t)x, rgba_index = pixel_index * PNGX_RGBA_CHANNELS, err_index = (size_t)x * PNGX_RGBA_CHANNELS;
    1308      2424832 :   uint8_t importance = 0, pixel_bits_rgb, pixel_bits_alpha, channel, bits, quantized;
    1309      2424832 :   float dither = base_dither, value, error, alpha_factor, dither_ch;
    1310              : 
    1311      2424832 :   if (importance_map && pixel_index < pixel_count) {
    1312      2424832 :     importance = importance_map[pixel_index];
    1313      2424832 :     dither *= importance_dither_scale(importance);
    1314              :   }
    1315      2424832 :   alpha_factor = (float)rgba[rgba_index + 3] / 255.0f;
    1316              : 
    1317      2424832 :   pixel_bits_rgb = resolve_pixel_bits(importance, base_bits_rgb, boost_bits_rgb);
    1318      2424832 :   pixel_bits_alpha = resolve_pixel_bits(importance, base_bits_alpha, boost_bits_alpha);
    1319      2424832 :   if (bit_hint_map && pixel_index < bit_hint_len) {
    1320      2424832 :     bit_hint_map[pixel_index] = (uint8_t)((pixel_bits_rgb << 4) | (pixel_bits_alpha & 0x0fu));
    1321              :   }
    1322              : 
    1323     12124160 :   for (channel = 0; channel < PNGX_RGBA_CHANNELS; ++channel) {
    1324      9699328 :     if (channel != 3 && rgba[rgba_index + 3] <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT) {
    1325        82944 :       bits = PNGX_FULL_CHANNEL_BITS;
    1326              :     } else {
    1327      9616384 :       bits = (channel == 3) ? pixel_bits_alpha : pixel_bits_rgb;
    1328              :     }
    1329              : 
    1330      9699328 :     if (bits >= PNGX_FULL_CHANNEL_BITS) {
    1331        82944 :       err_curr[err_index + channel] = 0.0f;
    1332        82944 :       continue;
    1333              :     }
    1334              : 
    1335      9616384 :     value = (float)rgba[rgba_index + channel] + err_curr[err_index + channel];
    1336      9616384 :     quantized = quantize_channel_value(value, bits);
    1337      9616384 :     error = (value - (float)quantized);
    1338              : 
    1339      9616384 :     if (channel == 3) {
    1340      2424832 :       dither_ch = 0.0f;
    1341              :     } else {
    1342      7191552 :       dither_ch = dither * alpha_factor;
    1343      7191552 :       if (alpha_factor < PNGX_REDUCED_ALPHA_MIN_DITHER_FACTOR) {
    1344        18432 :         dither_ch = 0.0f;
    1345              :       }
    1346              :     }
    1347              : 
    1348      9616384 :     error *= dither_ch;
    1349              : 
    1350      9616384 :     rgba[rgba_index + channel] = quantized;
    1351      9616384 :     err_curr[err_index + channel] = 0.0f;
    1352              : 
    1353      9616384 :     if (dither_ch <= 0.0f || error == 0.0f) {
    1354      2573894 :       continue;
    1355              :     }
    1356              : 
    1357      7042490 :     if (left_to_right) {
    1358      3520033 :       if (x + 1 < width) {
    1359      3515946 :         err_curr[err_index + PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
    1360              :       }
    1361      3520033 :       if (y + 1 < height) {
    1362      3520033 :         if (x > 0) {
    1363      3515928 :           err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
    1364              :         }
    1365      3520033 :         err_next[err_index + channel] += error * (5.0f / 16.0f);
    1366      3520033 :         if (x + 1 < width) {
    1367      3515946 :           err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
    1368              :         }
    1369              :       }
    1370              :     } else {
    1371      3522457 :       if (x > 0) {
    1372      3518333 :         err_curr[err_index - PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
    1373              :       }
    1374      3522457 :       if (y + 1 < height) {
    1375      3514481 :         if (x + 1 < width) {
    1376      3510412 :           err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
    1377              :         }
    1378      3514481 :         err_next[err_index + channel] += error * (5.0f / 16.0f);
    1379      3514481 :         if (x > 0) {
    1380      3510369 :           err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
    1381              :         }
    1382              :       }
    1383              :     }
    1384              :   }
    1385      2424832 : }
    1386              : 
    1387            4 : static inline bool reduce_rgba_custom_bitdepth_dither(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_rgb, uint8_t bits_alpha, uint8_t boost_bits_rgb, uint8_t boost_bits_alpha,
    1388              :                                                       float dither_level, const uint8_t *importance_map, size_t pixel_count, uint8_t *bit_hint_map, size_t bit_hint_len) {
    1389              :   uint32_t x, y;
    1390              :   size_t row_stride;
    1391              :   float *err_curr, *err_next, *tmp;
    1392              :   bool left_to_right;
    1393              : 
    1394            4 :   bits_rgb = clamp_reduced_bits(bits_rgb);
    1395            4 :   bits_alpha = clamp_reduced_bits(bits_alpha);
    1396            4 :   boost_bits_rgb = clamp_reduced_bits(boost_bits_rgb);
    1397            4 :   boost_bits_alpha = clamp_reduced_bits(boost_bits_alpha);
    1398            4 :   if ((!rgba) || width == 0 || height == 0 || (bits_rgb >= PNGX_FULL_CHANNEL_BITS && bits_alpha >= PNGX_FULL_CHANNEL_BITS)) {
    1399            0 :     return true;
    1400              :   }
    1401              : 
    1402            4 :   row_stride = (size_t)width * PNGX_RGBA_CHANNELS;
    1403            4 :   err_curr = (float *)calloc(row_stride, sizeof(float));
    1404            4 :   err_next = (float *)calloc(row_stride, sizeof(float));
    1405            4 :   if (!err_curr || !err_next) {
    1406            0 :     free(err_curr);
    1407            0 :     free(err_next);
    1408            0 :     cpres_log(CPRES_LOG_LEVEL_ERROR, "PNGX: Reduced RGBA32 dither allocation failed");
    1409              : 
    1410            0 :     return false;
    1411              :   }
    1412              : 
    1413         2820 :   for (y = 0; y < height; ++y) {
    1414         2816 :     left_to_right = ((y & 1) == 0);
    1415         2816 :     memset(err_next, 0, row_stride * sizeof(float));
    1416         2816 :     if (left_to_right) {
    1417      1213824 :       for (x = 0; x < width; ++x) {
    1418      1212416 :         process_custom_bitdepth_pixel(rgba, width, height, x, y, bits_rgb, bits_alpha, boost_bits_rgb, boost_bits_alpha, dither_level, importance_map, pixel_count, err_curr, err_next, true,
    1419              :                                       bit_hint_map, bit_hint_len);
    1420              :       }
    1421              :     } else {
    1422         1408 :       x = width;
    1423      1213824 :       while (x-- > 0) {
    1424      1212416 :         process_custom_bitdepth_pixel(rgba, width, height, x, y, bits_rgb, bits_alpha, boost_bits_rgb, boost_bits_alpha, dither_level, importance_map, pixel_count, err_curr, err_next, false,
    1425              :                                       bit_hint_map, bit_hint_len);
    1426              :       }
    1427              :     }
    1428              : 
    1429         2816 :     tmp = err_curr;
    1430         2816 :     err_curr = err_next;
    1431         2816 :     err_next = tmp;
    1432              :   }
    1433              : 
    1434            4 :   free(err_curr);
    1435            4 :   free(err_next);
    1436              : 
    1437            4 :   return true;
    1438              : }
    1439              : 
    1440            9 : static inline bool reduce_rgba_custom_bitdepth(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_rgb, uint8_t bits_alpha, float dither_level, const uint8_t *importance_map,
    1441              :                                                size_t importance_map_len, pngx_quant_support_t *support) {
    1442            9 :   uint8_t boost_bits_rgb, boost_bits_alpha, *bit_hint_map = NULL;
    1443            9 :   size_t bit_hint_len = 0, pixel_count, hint_len;
    1444              :   bool need_rgb, need_alpha;
    1445              : 
    1446            9 :   if (!rgba || width == 0 || height == 0) {
    1447            0 :     return true;
    1448              :   }
    1449              : 
    1450            9 :   bits_rgb = clamp_reduced_bits(bits_rgb);
    1451            9 :   bits_alpha = clamp_reduced_bits(bits_alpha);
    1452            9 :   need_rgb = bits_rgb < PNGX_FULL_CHANNEL_BITS;
    1453            9 :   need_alpha = bits_alpha < PNGX_FULL_CHANNEL_BITS;
    1454            9 :   if (!need_rgb && !need_alpha) {
    1455            0 :     return true;
    1456              :   }
    1457              : 
    1458            9 :   pixel_count = (size_t)width * (size_t)height;
    1459            9 :   if (!importance_map || importance_map_len < pixel_count) {
    1460            5 :     importance_map = NULL;
    1461              :   }
    1462              : 
    1463            9 :   boost_bits_rgb = (importance_map && bits_rgb < PNGX_FULL_CHANNEL_BITS) ? clamp_reduced_bits((uint8_t)(bits_rgb + 3)) : bits_rgb;
    1464            9 :   boost_bits_alpha = (importance_map && bits_alpha < PNGX_FULL_CHANNEL_BITS) ? clamp_reduced_bits((uint8_t)(bits_alpha + 2)) : bits_alpha;
    1465              : 
    1466            9 :   if (support) {
    1467            9 :     free(support->bit_hint_map);
    1468            9 :     support->bit_hint_map = NULL;
    1469            9 :     support->bit_hint_len = 0;
    1470            9 :     if (pixel_count > 0) {
    1471            9 :       support->bit_hint_map = (uint8_t *)malloc(pixel_count);
    1472            9 :       if (support->bit_hint_map) {
    1473            9 :         support->bit_hint_len = pixel_count;
    1474            9 :         bit_hint_map = support->bit_hint_map;
    1475            9 :         bit_hint_len = support->bit_hint_len;
    1476              :       }
    1477              :     }
    1478              :   }
    1479              : 
    1480            9 :   if (dither_level > 0.0f) {
    1481            4 :     if (!reduce_rgba_custom_bitdepth_dither(rgba, width, height, bits_rgb, bits_alpha, boost_bits_rgb, boost_bits_alpha, dither_level, importance_map, pixel_count, bit_hint_map, bit_hint_len)) {
    1482            0 :       return false;
    1483              :     }
    1484              :   } else {
    1485            5 :     hint_len = importance_map ? pixel_count : 0;
    1486            5 :     reduce_rgba_custom_bitdepth_simple(rgba, pixel_count, bits_rgb, bits_alpha, importance_map, hint_len, boost_bits_rgb, boost_bits_alpha, bit_hint_map, bit_hint_len);
    1487              :   }
    1488              : 
    1489            9 :   return true;
    1490              : }
    1491              : 
    1492            9 : static inline uint32_t collect_alpha_levels(const pngx_rgba_image_t *image, float *non_opaque_ratio) {
    1493            9 :   uint32_t unique = 0, non_opaque = 0;
    1494              :   uint8_t alpha;
    1495              :   size_t i;
    1496            9 :   bool level_used[256] = {false};
    1497              : 
    1498            9 :   if (!image || !image->rgba || image->pixel_count == 0) {
    1499            0 :     if (non_opaque_ratio) {
    1500            0 :       *non_opaque_ratio = 0.0f;
    1501              :     }
    1502              : 
    1503            0 :     return 0;
    1504              :   }
    1505              : 
    1506      2491665 :   for (i = 0; i < image->pixel_count; ++i) {
    1507      2491656 :     alpha = image->rgba[i * 4 + 3];
    1508      2491656 :     if (!level_used[alpha]) {
    1509          932 :       level_used[alpha] = true;
    1510          932 :       ++unique;
    1511              :     }
    1512              : 
    1513      2491656 :     if (alpha < 255) {
    1514      2420117 :       ++non_opaque;
    1515              :     }
    1516              :   }
    1517              : 
    1518            9 :   if (non_opaque_ratio) {
    1519            9 :     *non_opaque_ratio = (image->pixel_count > 0) ? ((float)non_opaque / (float)image->pixel_count) : 0.0f;
    1520              :   }
    1521              : 
    1522            9 :   return unique;
    1523              : }
    1524              : 
    1525            9 : static inline uint8_t bits_for_level_count(uint32_t levels) {
    1526            9 :   uint32_t capacity = 1;
    1527            9 :   uint8_t bits = 1;
    1528              : 
    1529            9 :   if (levels == 0) {
    1530            0 :     return 1;
    1531              :   }
    1532              : 
    1533           41 :   while (capacity < levels && bits < COLOPRESSO_PNGX_REDUCED_BITS_MAX) {
    1534           32 :     ++bits;
    1535           32 :     capacity <<= 1;
    1536              :   }
    1537              : 
    1538            9 :   return clamp_reduced_bits(bits);
    1539              : }
    1540              : 
    1541            9 : static inline void tune_reduced_bitdepth(const pngx_rgba_image_t *image, const pngx_image_stats_t *stats, uint8_t *bits_rgb, uint8_t *bits_alpha) {
    1542              :   uint32_t alpha_levels;
    1543              :   uint8_t tuned_rgb, tuned_alpha, level_bits, next_alpha;
    1544              :   float gradient, saturation, vibrant, opaque, translucent, non_opaque_ratio;
    1545              : 
    1546            9 :   if (!bits_rgb || !bits_alpha) {
    1547            0 :     return;
    1548              :   }
    1549              : 
    1550            9 :   tuned_rgb = clamp_reduced_bits(*bits_rgb);
    1551            9 :   tuned_alpha = clamp_reduced_bits(*bits_alpha);
    1552            9 :   gradient = stats ? stats->gradient_mean : PNGX_REDUCED_TUNE_DEFAULT_GRADIENT;
    1553            9 :   saturation = stats ? stats->saturation_mean : PNGX_REDUCED_TUNE_DEFAULT_SATURATION;
    1554            9 :   vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_TUNE_DEFAULT_VIBRANT;
    1555            9 :   opaque = stats ? stats->opaque_ratio : PNGX_REDUCED_TUNE_DEFAULT_OPAQUE;
    1556            9 :   translucent = stats ? stats->translucent_ratio : PNGX_REDUCED_TUNE_DEFAULT_TRANSLUCENT;
    1557              : 
    1558            9 :   if (gradient < PNGX_REDUCED_TUNE_FLAT_GRADIENT_THRESHOLD && saturation < PNGX_REDUCED_TUNE_FLAT_SATURATION_THRESHOLD && vibrant < PNGX_REDUCED_TUNE_FLAT_VIBRANT_THRESHOLD && tuned_rgb > 3) {
    1559            0 :     --tuned_rgb;
    1560            0 :     if (gradient < PNGX_REDUCED_TUNE_VERY_FLAT_GRADIENT && saturation < PNGX_REDUCED_TUNE_VERY_FLAT_SATURATION && tuned_rgb > 3) {
    1561            0 :       --tuned_rgb;
    1562              :     }
    1563            0 :     if (tuned_rgb < 3) {
    1564            0 :       tuned_rgb = 3;
    1565              :     }
    1566              :   }
    1567              : 
    1568            9 :   non_opaque_ratio = 0.0f;
    1569            9 :   alpha_levels = collect_alpha_levels(image, &non_opaque_ratio);
    1570            9 :   if (alpha_levels > 0) {
    1571            9 :     level_bits = bits_for_level_count(alpha_levels);
    1572            9 :     if (alpha_levels <= PNGX_REDUCED_ALPHA_LEVEL_LIMIT_FEW && non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_FEW && tuned_alpha > level_bits) {
    1573            3 :       tuned_alpha = (level_bits < 2) ? 2 : level_bits;
    1574            6 :     } else if (alpha_levels <= PNGX_REDUCED_ALPHA_NEAR_TRANSPARENT && non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_LOW && tuned_alpha > (uint8_t)(level_bits + 1)) {
    1575            0 :       tuned_alpha = (uint8_t)(level_bits + 1);
    1576            6 :     } else if (alpha_levels <= 16 && non_opaque_ratio < PNGX_REDUCED_ALPHA_RATIO_MINIMAL && tuned_alpha > (uint8_t)(level_bits + 2)) {
    1577            0 :       tuned_alpha = (uint8_t)(level_bits + 2);
    1578              :     }
    1579              : 
    1580            9 :     if (opaque > PNGX_REDUCED_ALPHA_OPAQUE_LIMIT && level_bits <= 2 && tuned_alpha > 2) {
    1581            0 :       tuned_alpha = 2;
    1582            9 :     } else if (translucent < PNGX_REDUCED_ALPHA_TRANSLUCENT_LIMIT && tuned_alpha > level_bits) {
    1583            3 :       next_alpha = (uint8_t)(tuned_alpha - 1);
    1584            3 :       if (next_alpha < level_bits) {
    1585            0 :         next_alpha = level_bits;
    1586              :       }
    1587            3 :       tuned_alpha = next_alpha;
    1588              :     }
    1589              :   }
    1590              : 
    1591            9 :   if (tuned_alpha < COLOPRESSO_PNGX_REDUCED_BITS_MIN) {
    1592            0 :     tuned_alpha = COLOPRESSO_PNGX_REDUCED_BITS_MIN;
    1593              :   }
    1594              : 
    1595            9 :   *bits_rgb = tuned_rgb;
    1596            9 :   *bits_alpha = tuned_alpha;
    1597              : }
    1598              : 
    1599            9 : static inline bool apply_reduced_rgba32_prepass(pngx_rgba_image_t *image, const pngx_options_t *opts, pngx_quant_support_t *support, pngx_image_stats_t *stats) {
    1600            9 :   const uint8_t *importance = NULL;
    1601            9 :   uint8_t bits_rgb = COLOPRESSO_PNGX_DEFAULT_REDUCED_BITS_RGB, bits_alpha = COLOPRESSO_PNGX_DEFAULT_REDUCED_ALPHA_BITS;
    1602            9 :   size_t importance_len = 0;
    1603            9 :   float dither = 0.0f;
    1604              : 
    1605            9 :   if (!image || !image->rgba || image->pixel_count == 0) {
    1606            0 :     return true;
    1607              :   }
    1608              : 
    1609            9 :   if (opts) {
    1610            9 :     bits_rgb = clamp_reduced_bits(opts->lossy_reduced_bits_rgb);
    1611            9 :     bits_alpha = clamp_reduced_bits(opts->lossy_reduced_alpha_bits);
    1612            9 :     if (opts->lossy_dither_auto) {
    1613            1 :       dither = resolve_quant_dither(opts, stats);
    1614              :     } else {
    1615            8 :       dither = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
    1616              :     }
    1617            9 :     if (support && support->importance_map && support->importance_map_len >= image->pixel_count) {
    1618            4 :       importance = support->importance_map;
    1619            4 :       importance_len = support->importance_map_len;
    1620              :     }
    1621              :   }
    1622              : 
    1623            9 :   return reduce_rgba_custom_bitdepth(image->rgba, image->width, image->height, bits_rgb, bits_alpha, dither, importance, importance_len, support);
    1624              : }
    1625              : 
    1626          552 : static inline void head_heap_sift_up(uint64_t *heap, size_t index) {
    1627              :   uint64_t parent_value;
    1628              :   size_t parent;
    1629              : 
    1630          868 :   while (index > 0) {
    1631          843 :     parent = (index - 1) / 2;
    1632          843 :     parent_value = heap[parent];
    1633              : 
    1634          843 :     if (parent_value <= heap[index]) {
    1635          527 :       break;
    1636              :     }
    1637              : 
    1638          316 :     heap[parent] = heap[index];
    1639          316 :     heap[index] = parent_value;
    1640          316 :     index = parent;
    1641              :   }
    1642          552 : }
    1643              : 
    1644         1528 : static inline void head_heap_sift_down(uint64_t *heap, size_t size, size_t index) {
    1645              :   uint64_t tmp;
    1646              :   size_t left, right, smallest;
    1647              : 
    1648              :   while (true) {
    1649         7556 :     left = index * 2 + 1;
    1650         7556 :     right = left + 1;
    1651         7556 :     smallest = index;
    1652              : 
    1653         7556 :     if (left < size && heap[left] < heap[smallest]) {
    1654         5567 :       smallest = left;
    1655              :     }
    1656              : 
    1657         7556 :     if (right < size && heap[right] < heap[smallest]) {
    1658         2877 :       smallest = right;
    1659              :     }
    1660              : 
    1661         7556 :     if (smallest == index) {
    1662         1528 :       break;
    1663              :     }
    1664              : 
    1665         6028 :     tmp = heap[index];
    1666         6028 :     heap[index] = heap[smallest];
    1667         6028 :     heap[smallest] = tmp;
    1668         6028 :     index = smallest;
    1669              :   }
    1670         1528 : }
    1671              : 
    1672           12 : static inline float histogram_head_dominance(const pngx_color_histogram_t *hist, size_t head_limit) {
    1673           12 :   uint64_t heap[PNGX_REDUCED_HEAD_DOMINANCE_LIMIT], weight, total_weight = 0, head_sum = 0;
    1674           12 :   size_t heap_size = 0, capacity, i;
    1675              : 
    1676           12 :   if (!hist || hist->count == 0 || head_limit == 0) {
    1677            0 :     return 0.0f;
    1678              :   }
    1679              : 
    1680           12 :   capacity = head_limit;
    1681           12 :   if (capacity > PNGX_REDUCED_HEAD_DOMINANCE_LIMIT) {
    1682            0 :     capacity = PNGX_REDUCED_HEAD_DOMINANCE_LIMIT;
    1683              :   }
    1684              : 
    1685        28800 :   for (i = 0; i < hist->count; ++i) {
    1686        28788 :     weight = hist->entries[i].count ? hist->entries[i].count : 1;
    1687        28788 :     total_weight += weight;
    1688              : 
    1689        28788 :     if (capacity == 0) {
    1690            0 :       continue;
    1691              :     }
    1692              : 
    1693        28788 :     if (heap_size < capacity) {
    1694          552 :       heap[heap_size] = weight;
    1695          552 :       ++heap_size;
    1696          552 :       head_heap_sift_up(heap, heap_size - 1);
    1697        28236 :     } else if (weight > heap[0]) {
    1698         1528 :       heap[0] = weight;
    1699         1528 :       head_heap_sift_down(heap, heap_size, 0);
    1700              :     }
    1701              :   }
    1702              : 
    1703           12 :   if (total_weight == 0 || heap_size == 0) {
    1704            0 :     return 0.0f;
    1705              :   }
    1706              : 
    1707          564 :   for (i = 0; i < heap_size; ++i) {
    1708          552 :     head_sum += heap[i];
    1709              :   }
    1710              : 
    1711           12 :   return (float)head_sum / (float)total_weight;
    1712              : }
    1713              : 
    1714           12 : static inline float histogram_low_weight_ratio(const pngx_color_histogram_t *hist, uint32_t weight_threshold) {
    1715              :   const pngx_color_entry_t *entry;
    1716           12 :   uint64_t total_weight = 0, low_weight = 0, weight;
    1717              :   size_t i;
    1718              : 
    1719           12 :   if (!hist || hist->count == 0 || weight_threshold == 0) {
    1720            0 :     return 0.0f;
    1721              :   }
    1722              : 
    1723        28800 :   for (i = 0; i < hist->count; ++i) {
    1724        28788 :     entry = &hist->entries[i];
    1725        28788 :     weight = entry->count ? entry->count : 1;
    1726        28788 :     total_weight += weight;
    1727        28788 :     if (entry->count <= weight_threshold) {
    1728        17648 :       low_weight += weight;
    1729              :     }
    1730              :   }
    1731              : 
    1732           12 :   if (total_weight == 0) {
    1733            0 :     return 0.0f;
    1734              :   }
    1735              : 
    1736           12 :   return (float)low_weight / (float)total_weight;
    1737              : }
    1738              : 
    1739           12 : static inline float histogram_detail_pressure(const pngx_color_histogram_t *hist, uint8_t base_bits_rgb, uint8_t base_bits_alpha) {
    1740              :   const pngx_color_entry_t *entry;
    1741           12 :   uint64_t total_weight = 0, detail_weight = 0, weight;
    1742              :   size_t i;
    1743              : 
    1744           12 :   if (!hist || hist->count == 0) {
    1745            0 :     return 0.0f;
    1746              :   }
    1747              : 
    1748        28800 :   for (i = 0; i < hist->count; ++i) {
    1749        28788 :     entry = &hist->entries[i];
    1750        28788 :     weight = entry->count ? entry->count : 1;
    1751        28788 :     total_weight += weight;
    1752        28788 :     if (entry->detail_bits_rgb > base_bits_rgb || entry->detail_bits_alpha > base_bits_alpha) {
    1753        13338 :       detail_weight += weight;
    1754              :     }
    1755              :   }
    1756              : 
    1757           12 :   if (total_weight == 0) {
    1758            0 :     return 0.0f;
    1759              :   }
    1760              : 
    1761           12 :   return (float)detail_weight / (float)total_weight;
    1762              : }
    1763              : 
    1764           12 : static inline float stats_flatness_factor(const pngx_image_stats_t *stats) {
    1765           12 :   float gradient = stats ? stats->gradient_mean : PNGX_REDUCED_STATS_FLAT_DEFAULT_GRADIENT, saturation = stats ? stats->saturation_mean : PNGX_REDUCED_STATS_FLAT_DEFAULT_SATURATION,
    1766           12 :         vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_STATS_FLAT_DEFAULT_VIBRANT;
    1767           12 :   float gradient_term = clamp_float((PNGX_REDUCED_STATS_FLAT_GRADIENT_REF - gradient) / PNGX_REDUCED_STATS_FLAT_GRADIENT_REF, 0.0f, 1.0f),
    1768           12 :         saturation_term = clamp_float((PNGX_REDUCED_STATS_FLAT_SATURATION_REF - saturation) / PNGX_REDUCED_STATS_FLAT_SATURATION_REF, 0.0f, 1.0f),
    1769           12 :         vibrant_term = clamp_float((PNGX_REDUCED_VIBRANT_RATIO_LOW - vibrant) / PNGX_REDUCED_VIBRANT_RATIO_LOW, 0.0f, 1.0f);
    1770              : 
    1771           12 :   return (gradient_term * PNGX_REDUCED_STATS_FLAT_GRADIENT_WEIGHT) + (saturation_term * PNGX_REDUCED_STATS_FLAT_SATURATION_WEIGHT) + (vibrant_term * PNGX_REDUCED_STATS_FLAT_VIBRANT_WEIGHT);
    1772              : }
    1773              : 
    1774           12 : static inline float stats_alpha_simplicity(const pngx_image_stats_t *stats) {
    1775           12 :   float opaque = stats ? stats->opaque_ratio : PNGX_REDUCED_ALPHA_SIMPLE_DEFAULT_OPAQUE;
    1776           12 :   float translucent = stats ? stats->translucent_ratio : PNGX_REDUCED_ALPHA_SIMPLE_DEFAULT_TRANSLUCENT;
    1777           12 :   float opaque_term = clamp_float((opaque - PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_REF) / PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_RANGE, 0.0f, 1.0f);
    1778           12 :   float translucent_term = clamp_float((PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_REF - translucent) / PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_RANGE, 0.0f, 1.0f);
    1779              : 
    1780           12 :   return (opaque_term * PNGX_REDUCED_ALPHA_SIMPLE_OPAQUE_WEIGHT) + (translucent_term * PNGX_REDUCED_ALPHA_SIMPLE_TRANSLUCENT_WEIGHT);
    1781              : }
    1782              : 
    1783            9 : static inline uint32_t resolve_reduced_passthrough_threshold(uint32_t grid_cap, const pngx_image_stats_t *stats) {
    1784              :   uint32_t threshold;
    1785            9 :   float gradient = stats ? stats->gradient_mean : PNGX_REDUCED_PASSTHROUGH_DEFAULT_GRADIENT, saturation = stats ? stats->saturation_mean : PNGX_REDUCED_PASSTHROUGH_DEFAULT_SATURATION,
    1786            9 :         vibrant = stats ? stats->vibrant_ratio : PNGX_REDUCED_PASSTHROUGH_DEFAULT_VIBRANT;
    1787            9 :   float ratio =
    1788              :       PNGX_REDUCED_PASSTHROUGH_RATIO_BASE +
    1789            9 :       clamp_float((gradient * PNGX_REDUCED_PASSTHROUGH_GRADIENT_WEIGHT) + (saturation * PNGX_REDUCED_PASSTHROUGH_SATURATION_WEIGHT) + (vibrant * PNGX_REDUCED_PASSTHROUGH_VIBRANT_WEIGHT), 0.0f, 1.0f) *
    1790              :           PNGX_REDUCED_PASSTHROUGH_RATIO_GAIN;
    1791              : 
    1792            9 :   if (ratio > PNGX_REDUCED_PASSTHROUGH_RATIO_CAP) {
    1793            0 :     ratio = PNGX_REDUCED_PASSTHROUGH_RATIO_CAP;
    1794              :   }
    1795              : 
    1796            9 :   threshold = (uint32_t)((float)grid_cap * ratio + PNGX_REDUCED_ROUNDING_OFFSET);
    1797            9 :   if (threshold < PNGX_REDUCED_RGBA32_PASSTHROUGH_MIN_COLORS) {
    1798            1 :     threshold = PNGX_REDUCED_RGBA32_PASSTHROUGH_MIN_COLORS;
    1799              :   }
    1800            9 :   if (threshold > grid_cap) {
    1801            1 :     threshold = grid_cap;
    1802              :   }
    1803              : 
    1804            9 :   return threshold;
    1805              : }
    1806              : 
    1807            6 : static inline uint32_t compute_auto_trim_limit(const pngx_color_histogram_t *hist, size_t pixel_count, uint32_t actual_colors, uint8_t bits_rgb, uint8_t bits_alpha, const pngx_image_stats_t *stats) {
    1808              :   uint32_t low_weight_threshold, limit;
    1809            6 :   float low_weight_ratio, detail_pressure, head_dominance, flatness, alpha_simple, density_f, trim = 0.0f;
    1810              :   double density;
    1811              : 
    1812            6 :   if (!hist || hist->count == 0 || pixel_count == 0 || actual_colors <= (uint32_t)(COLOPRESSO_PNGX_REDUCED_COLORS_MIN + PNGX_REDUCED_TRIM_MIN_COLOR_MARGIN)) {
    1813            2 :     return 0;
    1814              :   }
    1815              : 
    1816            4 :   density = (double)hist->count / (double)pixel_count;
    1817            4 :   density_f = (float)density;
    1818            4 :   low_weight_threshold = (uint32_t)(pixel_count / PNGX_REDUCED_LOW_WEIGHT_DIVISOR);
    1819            4 :   if (low_weight_threshold < PNGX_REDUCED_LOW_WEIGHT_MIN) {
    1820            1 :     low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MIN;
    1821            3 :   } else if (low_weight_threshold > PNGX_REDUCED_LOW_WEIGHT_MAX) {
    1822            0 :     low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MAX;
    1823              :   }
    1824              : 
    1825            4 :   low_weight_ratio = histogram_low_weight_ratio(hist, low_weight_threshold);
    1826            4 :   detail_pressure = histogram_detail_pressure(hist, bits_rgb, bits_alpha);
    1827            4 :   head_dominance = histogram_head_dominance(hist, PNGX_REDUCED_HEAD_DOMINANCE_LIMIT);
    1828            4 :   flatness = stats_flatness_factor(stats);
    1829            4 :   alpha_simple = stats_alpha_simplicity(stats);
    1830              : 
    1831            4 :   if (head_dominance > PNGX_REDUCED_TRIM_HEAD_DOMINANCE_THRESHOLD && detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_HEAD_LIMIT) {
    1832              :     float head_term =
    1833            0 :         clamp_float((head_dominance - PNGX_REDUCED_TRIM_HEAD_DOMINANCE_THRESHOLD) * (PNGX_REDUCED_TRIM_HEAD_WEIGHT + flatness * PNGX_REDUCED_TRIM_FLATNESS_WEIGHT), 0.0f, PNGX_REDUCED_TRIM_HEAD_CLAMP);
    1834            0 :     trim += head_term;
    1835              :   }
    1836              : 
    1837            4 :   if (low_weight_ratio > PNGX_REDUCED_TRIM_TAIL_RATIO_THRESHOLD && detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_TAIL_LIMIT) {
    1838            2 :     float tail_term = (low_weight_ratio - PNGX_REDUCED_TRIM_TAIL_RATIO_THRESHOLD) * (PNGX_REDUCED_TRIM_TAIL_BASE_WEIGHT + PNGX_REDUCED_TRIM_TAIL_DETAIL_WEIGHT * (1.0f - detail_pressure));
    1839            2 :     trim += clamp_float(tail_term, 0.0f, PNGX_REDUCED_TRIM_TAIL_CLAMP);
    1840              :   }
    1841              : 
    1842            4 :   if (density_f < PNGX_REDUCED_TRIM_DENSITY_THRESHOLD) {
    1843            3 :     trim += clamp_float((PNGX_REDUCED_TRIM_DENSITY_THRESHOLD - density_f) * PNGX_REDUCED_TRIM_DENSITY_SCALE, 0.0f, PNGX_REDUCED_TRIM_DENSITY_CLAMP);
    1844              :   }
    1845              : 
    1846            4 :   if (alpha_simple > PNGX_REDUCED_TRIM_ALPHA_SIMPLE_THRESHOLD && detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_HEAD_LIMIT) {
    1847            2 :     trim += clamp_float(alpha_simple * PNGX_REDUCED_TRIM_ALPHA_SIMPLE_SCALE, 0.0f, PNGX_REDUCED_TRIM_ALPHA_SIMPLE_CLAMP);
    1848              :   }
    1849              : 
    1850            4 :   if (flatness > PNGX_REDUCED_TRIM_FLATNESS_THRESHOLD && detail_pressure < PNGX_REDUCED_TRIM_DETAIL_PRESSURE_FLAT_LIMIT) {
    1851            2 :     trim += clamp_float((flatness - PNGX_REDUCED_TRIM_FLATNESS_THRESHOLD) * PNGX_REDUCED_TRIM_FLATNESS_SCALE, 0.0f, PNGX_REDUCED_TRIM_FLATNESS_CLAMP);
    1852              :   }
    1853              : 
    1854            4 :   trim = clamp_float(trim, 0.0f, PNGX_REDUCED_TRIM_TOTAL_CLAMP);
    1855            4 :   if (trim < PNGX_REDUCED_TRIM_MIN_TRIGGER) {
    1856            0 :     return 0;
    1857              :   }
    1858              : 
    1859            4 :   limit = (uint32_t)((float)actual_colors * (1.0f - trim) + PNGX_REDUCED_ROUNDING_OFFSET);
    1860            4 :   if (limit < (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
    1861            0 :     limit = (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
    1862              :   }
    1863              : 
    1864            4 :   if (actual_colors <= limit || (actual_colors - limit) < PNGX_REDUCED_TRIM_MIN_COLOR_DIFF) {
    1865            2 :     return 0;
    1866              :   }
    1867              : 
    1868            2 :   return limit;
    1869              : }
    1870              : 
    1871            8 : static inline uint32_t resolve_reduced_rgba32_target(const pngx_color_histogram_t *hist, size_t pixel_count, int32_t hint, uint8_t bits_rgb, uint8_t bits_alpha, const pngx_image_stats_t *stats) {
    1872            8 :   uint64_t rgb_cap = 0, alpha_cap = 0;
    1873            8 :   uint32_t target, grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX, low_weight_threshold;
    1874            8 :   size_t unique_colors = hist ? hist->count : 0, unlocked = hist ? hist->unlocked_count : 0;
    1875              :   double base, density;
    1876            8 :   float low_weight_ratio = 0.0f, detail_pressure = 0.0f, reduction_scale, boost_scale, flatness_score = 0.0f, alpha_simple = 0.0f, head_dominance = 0.0f, tail_cut, dominance_cut, gentle_cut,
    1877              :         applied_cut, tail_gain, dominance_gain, relief, gradient_relief, softness_relief, detail_relief, combined_cut, density_gap, flatness_reduction, alpha_reduction, span_ratio;
    1878              : 
    1879            8 :   if (unique_colors == 0) {
    1880            0 :     return 0;
    1881              :   }
    1882              : 
    1883            8 :   density = (pixel_count > 0) ? ((double)unique_colors / (double)pixel_count) : 0.0;
    1884              : 
    1885            8 :   bits_rgb = clamp_reduced_bits(bits_rgb);
    1886            8 :   bits_alpha = clamp_reduced_bits(bits_alpha);
    1887            8 :   if (bits_rgb < 8) {
    1888            6 :     rgb_cap = (uint64_t)1 << (bits_rgb * 3);
    1889              :   }
    1890            8 :   if (bits_alpha < 8) {
    1891            8 :     alpha_cap = (uint64_t)1 << bits_alpha;
    1892              :   }
    1893            8 :   if (rgb_cap == 0) {
    1894            2 :     rgb_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    1895              :   }
    1896            8 :   if (alpha_cap == 0) {
    1897            0 :     alpha_cap = 1;
    1898              :   }
    1899            8 :   if (rgb_cap > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
    1900            0 :     rgb_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    1901              :   }
    1902            8 :   if (alpha_cap > (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
    1903            0 :     alpha_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    1904              :   }
    1905            8 :   if (rgb_cap * alpha_cap < (uint64_t)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
    1906            2 :     grid_cap = (uint32_t)(rgb_cap * alpha_cap);
    1907              :   }
    1908              : 
    1909            8 :   if (hint > 0) {
    1910            2 :     target = (uint32_t)hint;
    1911            2 :     if (target < COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
    1912            0 :       target = COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
    1913              :     }
    1914            2 :     if (target > COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
    1915            0 :       target = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    1916              :     }
    1917              :   } else {
    1918            6 :     target = (uint32_t)unique_colors;
    1919            6 :     if (unique_colors > PNGX_REDUCED_TARGET_UNIQUE_COLOR_THRESHOLD) {
    1920            3 :       base = sqrt((double)unique_colors) * PNGX_REDUCED_TARGET_UNIQUE_BASE_SCALE;
    1921            3 :       if (density < PNGX_REDUCED_TARGET_DENSITY_LOW_THRESHOLD) {
    1922            3 :         base *= PNGX_REDUCED_TARGET_DENSITY_LOW_SCALE;
    1923            0 :       } else if (density > PNGX_REDUCED_TARGET_DENSITY_HIGH_THRESHOLD) {
    1924            0 :         base *= PNGX_REDUCED_TARGET_DENSITY_HIGH_SCALE;
    1925              :       }
    1926              : 
    1927            3 :       if (base < PNGX_REDUCED_TARGET_BASE_MIN) {
    1928            1 :         base = PNGX_REDUCED_TARGET_BASE_MIN;
    1929              :       }
    1930              : 
    1931            3 :       if (base > (double)COLOPRESSO_PNGX_REDUCED_COLORS_MAX) {
    1932            0 :         base = (double)COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    1933              :       }
    1934              : 
    1935            3 :       target = (uint32_t)(base + PNGX_REDUCED_ROUNDING_OFFSET);
    1936              :     }
    1937              :   }
    1938              : 
    1939            8 :   if (pixel_count > 0 && hist) {
    1940            8 :     low_weight_threshold = (uint32_t)(pixel_count / PNGX_REDUCED_LOW_WEIGHT_DIVISOR);
    1941            8 :     if (low_weight_threshold < PNGX_REDUCED_LOW_WEIGHT_MIN) {
    1942            3 :       low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MIN;
    1943            5 :     } else if (low_weight_threshold > PNGX_REDUCED_LOW_WEIGHT_MAX) {
    1944            0 :       low_weight_threshold = PNGX_REDUCED_LOW_WEIGHT_MAX;
    1945              :     }
    1946              : 
    1947            8 :     low_weight_ratio = histogram_low_weight_ratio(hist, low_weight_threshold);
    1948            8 :     detail_pressure = histogram_detail_pressure(hist, bits_rgb, bits_alpha);
    1949            8 :     head_dominance = histogram_head_dominance(hist, PNGX_REDUCED_TARGET_HEAD_DOMINANCE_BUCKETS);
    1950              : 
    1951            8 :     if (low_weight_ratio > PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_START) {
    1952            8 :       reduction_scale = 1.0f - clamp_float((low_weight_ratio - PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_START) *
    1953            4 :                                                (PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_BASE + (PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_DETAIL * (1.0f - detail_pressure))),
    1954              :                                            0.0f, PNGX_REDUCED_TARGET_LOW_WEIGHT_REDUCTION_CLAMP);
    1955            4 :       target = (uint32_t)((float)target * reduction_scale + PNGX_REDUCED_ROUNDING_OFFSET);
    1956              :     }
    1957              : 
    1958            8 :     if (low_weight_ratio > PNGX_REDUCED_TARGET_TAIL_RATIO_THRESHOLD && detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_TAIL_LIMIT) {
    1959            3 :       tail_cut = clamp_float((low_weight_ratio - PNGX_REDUCED_TARGET_TAIL_RATIO_THRESHOLD) * (PNGX_REDUCED_TARGET_TAIL_WIDTH_BASE - detail_pressure) * PNGX_REDUCED_TARGET_TAIL_WIDTH_SCALE, 0.0f,
    1960              :                              PNGX_REDUCED_TARGET_TAIL_CUT_CLAMP);
    1961            3 :       target = (uint32_t)((float)target * (1.0f - tail_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
    1962              :     }
    1963              : 
    1964            8 :     if (detail_pressure > PNGX_REDUCED_TARGET_DETAIL_PRESSURE_BOOST) {
    1965            4 :       boost_scale = 1.0f + clamp_float((detail_pressure - PNGX_REDUCED_TARGET_DETAIL_PRESSURE_BOOST) * PNGX_REDUCED_TARGET_DETAIL_BOOST_SCALE, 0.0f, PNGX_REDUCED_TARGET_DETAIL_BOOST_CLAMP);
    1966            4 :       target = (uint32_t)((float)target * boost_scale + PNGX_REDUCED_ROUNDING_OFFSET);
    1967              :     }
    1968              : 
    1969            8 :     if (head_dominance > PNGX_REDUCED_TARGET_HEAD_DOMINANCE_THRESHOLD && detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_HEAD_LIMIT) {
    1970            2 :       gradient_relief =
    1971            2 :           stats ? clamp_float((PNGX_REDUCED_TARGET_GRADIENT_RELIEF_REF - stats->gradient_mean) / PNGX_REDUCED_TARGET_GRADIENT_RELIEF_REF, 0.0f, 1.0f) : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_DEFAULT;
    1972            2 :       dominance_cut = clamp_float((head_dominance - PNGX_REDUCED_TARGET_HEAD_DOMINANCE_THRESHOLD) * (PNGX_REDUCED_TARGET_HEAD_CUT_BASE + PNGX_REDUCED_TARGET_HEAD_CUT_RELIEF * gradient_relief), 0.0f,
    1973              :                                   PNGX_REDUCED_TARGET_HEAD_CUT_CLAMP);
    1974            2 :       target = (uint32_t)((float)target * (1.0f - dominance_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
    1975              :     }
    1976              : 
    1977            8 :     if (head_dominance > PNGX_REDUCED_TARGET_HEAD_DOMINANCE_STRONG && low_weight_ratio > PNGX_REDUCED_TARGET_LOW_WEIGHT_RATIO_STRONG &&
    1978              :         detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_STRONG_LIMIT) {
    1979            2 :       gradient_relief = stats ? clamp_float((PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_REF - stats->gradient_mean) / PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_REF, 0.0f, 1.0f)
    1980            2 :                               : PNGX_REDUCED_TARGET_GRADIENT_RELIEF_SECONDARY_DEFAULT;
    1981            2 :       softness_relief = stats ? clamp_float((PNGX_REDUCED_TARGET_SATURATION_RELIEF_REF - stats->saturation_mean) / PNGX_REDUCED_TARGET_SATURATION_RELIEF_REF, 0.0f, 1.0f)
    1982            2 :                               : PNGX_REDUCED_TARGET_SATURATION_RELIEF_DEFAULT;
    1983            2 :       relief = clamp_float((gradient_relief * PNGX_REDUCED_TARGET_RELIEF_GRADIENT_WEIGHT + softness_relief * PNGX_REDUCED_TARGET_RELIEF_SATURATION_WEIGHT) * PNGX_REDUCED_TARGET_RELIEF_SCALE, 0.0f,
    1984              :                            PNGX_REDUCED_TARGET_RELIEF_CLAMP);
    1985            2 :       dominance_gain = clamp_float((head_dominance - PNGX_REDUCED_TARGET_HEAD_DOMINANCE_STRONG) * PNGX_REDUCED_TARGET_DOMINANCE_GAIN_SCALE, 0.0f, PNGX_REDUCED_TARGET_DOMINANCE_GAIN_CLAMP);
    1986            2 :       tail_gain = clamp_float((low_weight_ratio - PNGX_REDUCED_TARGET_LOW_WEIGHT_RATIO_STRONG) *
    1987            2 :                                   (PNGX_REDUCED_TARGET_TAIL_GAIN_BASE + PNGX_REDUCED_TARGET_TAIL_GAIN_RELIEF * (gradient_relief + softness_relief)),
    1988              :                               0.0f, PNGX_REDUCED_TARGET_TAIL_GAIN_CLAMP);
    1989            2 :       detail_relief = clamp_float((PNGX_REDUCED_TARGET_DETAIL_RELIEF_BASE - detail_pressure) * PNGX_REDUCED_TARGET_DETAIL_RELIEF_SCALE, 0.0f, PNGX_REDUCED_TARGET_DETAIL_RELIEF_CLAMP);
    1990            2 :       combined_cut = clamp_float((dominance_gain + tail_gain) * (PNGX_REDUCED_TARGET_COMBINED_CUT_BASE + relief + detail_relief), 0.0f, PNGX_REDUCED_TARGET_COMBINED_CUT_CLAMP);
    1991            2 :       target = (uint32_t)((float)target * (1.0f - combined_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
    1992              :     }
    1993              : 
    1994            8 :     if (density < PNGX_REDUCED_TARGET_DENSITY_THRESHOLD && detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_DENSITY_LIMIT) {
    1995            1 :       density_gap = clamp_float((PNGX_REDUCED_TARGET_DENSITY_THRESHOLD - (float)density) * PNGX_REDUCED_TARGET_DENSITY_GAP_SCALE, 0.0f, PNGX_REDUCED_TARGET_DENSITY_GAP_CLAMP);
    1996            1 :       target = (uint32_t)((float)target * (1.0f - density_gap) + PNGX_REDUCED_ROUNDING_OFFSET);
    1997              :     }
    1998              :   }
    1999              : 
    2000            8 :   if (stats) {
    2001            8 :     flatness_score = stats_flatness_factor(stats);
    2002            8 :     if (flatness_score > PNGX_REDUCED_TARGET_FLATNESS_THRESHOLD && detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_FLAT_LIMIT) {
    2003            4 :       flatness_reduction = clamp_float(flatness_score * PNGX_REDUCED_TARGET_FLATNESS_SCALE, 0.0f, PNGX_REDUCED_TARGET_FLATNESS_CLAMP);
    2004            4 :       target = (uint32_t)((float)target * (1.0f - flatness_reduction) + PNGX_REDUCED_ROUNDING_OFFSET);
    2005              :     }
    2006              : 
    2007            8 :     alpha_simple = stats_alpha_simplicity(stats);
    2008            8 :     if (alpha_simple > PNGX_REDUCED_TARGET_ALPHA_SIMPLE_THRESHOLD && detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_ALPHA_LIMIT) {
    2009            3 :       alpha_reduction = clamp_float(alpha_simple * PNGX_REDUCED_TARGET_ALPHA_SIMPLE_SCALE, 0.0f, PNGX_REDUCED_TARGET_ALPHA_SIMPLE_CLAMP);
    2010            3 :       target = (uint32_t)((float)target * (1.0f - alpha_reduction) + PNGX_REDUCED_ROUNDING_OFFSET);
    2011              :     }
    2012              :   }
    2013              : 
    2014            8 :   if (hint <= 0 && unique_colors > PNGX_REDUCED_TARGET_GENTLE_MIN_COLORS && unique_colors <= PNGX_REDUCED_TARGET_GENTLE_MAX_COLORS &&
    2015              :       detail_pressure < PNGX_REDUCED_TARGET_DETAIL_PRESSURE_GENTLE_LIMIT) {
    2016            1 :     span_ratio = ((float)unique_colors - (float)PNGX_REDUCED_TARGET_GENTLE_MIN_COLORS) / PNGX_REDUCED_TARGET_GENTLE_COLOR_RANGE;
    2017            1 :     if (span_ratio < 0.0f) {
    2018            0 :       span_ratio = 0.0f;
    2019            1 :     } else if (span_ratio > 1.0f) {
    2020            0 :       span_ratio = 1.0f;
    2021              :     }
    2022              : 
    2023            1 :     gentle_cut = clamp_float((1.0f - detail_pressure) * PNGX_REDUCED_TARGET_GENTLE_SCALE, 0.0f, PNGX_REDUCED_TARGET_GENTLE_CLAMP);
    2024            1 :     applied_cut = span_ratio * gentle_cut;
    2025            1 :     target = (uint32_t)((float)target * (1.0f - applied_cut) + PNGX_REDUCED_ROUNDING_OFFSET);
    2026              :   }
    2027              : 
    2028            8 :   if (target > unique_colors) {
    2029            0 :     target = (uint32_t)unique_colors;
    2030              :   }
    2031              : 
    2032            8 :   if (target < (uint32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
    2033            1 :     target = COLOPRESSO_PNGX_REDUCED_COLORS_MIN;
    2034              :   }
    2035              : 
    2036            8 :   if (target > grid_cap) {
    2037            0 :     target = grid_cap;
    2038              :   }
    2039              : 
    2040            8 :   if (unlocked > 0 && target > (uint32_t)unlocked) {
    2041            0 :     target = (uint32_t)unlocked;
    2042              :   }
    2043              : 
    2044            8 :   if (unlocked == 0) {
    2045            1 :     target = (uint32_t)unique_colors;
    2046              :   }
    2047              : 
    2048            8 :   return target;
    2049              : }
    2050              : 
    2051            9 : bool pngx_quantize_reduced_rgba32(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint32_t *resolved_target, uint32_t *applied_colors, uint8_t **out_data, size_t *out_size) {
    2052              :   pngx_rgba_image_t image;
    2053              :   pngx_color_histogram_t histogram;
    2054            9 :   pngx_quant_support_t support = {0};
    2055              :   pngx_image_stats_t stats;
    2056              :   pngx_options_t tuned_opts;
    2057            9 :   uint32_t target = 0, actual = 0, manual_limit = 0, auto_trim_limit = 0, grid_cap;
    2058              :   uint8_t bits_rgb, bits_alpha;
    2059              :   size_t grid_unique, passthrough_threshold;
    2060            9 :   bool wrote, manual_target = false, success, auto_target, grid_passthrough, auto_trim_applied = false;
    2061              : 
    2062            9 :   if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
    2063            0 :     return false;
    2064              :   }
    2065              : 
    2066            9 :   if (!load_rgba_image(png_data, png_size, &image)) {
    2067            0 :     return false;
    2068              :   }
    2069              : 
    2070            9 :   histogram.entries = NULL;
    2071            9 :   histogram.count = 0;
    2072            9 :   histogram.unlocked_count = 0;
    2073            9 :   tuned_opts = *opts;
    2074              : 
    2075            9 :   image_stats_reset(&stats);
    2076              : 
    2077            9 :   if (!prepare_quant_support(&image, &tuned_opts, &support, &stats)) {
    2078            0 :     rgba_image_reset(&image);
    2079            0 :     return false;
    2080              :   }
    2081              : 
    2082            9 :   tune_reduced_bitdepth(&image, &stats, &tuned_opts.lossy_reduced_bits_rgb, &tuned_opts.lossy_reduced_alpha_bits);
    2083            9 :   if (!apply_reduced_rgba32_prepass(&image, &tuned_opts, &support, &stats)) {
    2084            0 :     color_histogram_reset(&histogram);
    2085            0 :     quant_support_reset(&support);
    2086            0 :     rgba_image_reset(&image);
    2087              : 
    2088            0 :     return false;
    2089              :   }
    2090              : 
    2091            9 :   bits_rgb = clamp_reduced_bits(tuned_opts.lossy_reduced_bits_rgb);
    2092            9 :   bits_alpha = clamp_reduced_bits(tuned_opts.lossy_reduced_alpha_bits);
    2093            9 :   grid_cap = compute_grid_capacity(bits_rgb, bits_alpha);
    2094            9 :   if (grid_cap == 0) {
    2095            0 :     grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    2096              :   }
    2097            9 :   if (opts->lossy_reduced_colors >= (int32_t)COLOPRESSO_PNGX_REDUCED_COLORS_MIN) {
    2098            2 :     manual_target = true;
    2099            2 :     manual_limit = (uint32_t)opts->lossy_reduced_colors;
    2100            2 :     if (manual_limit > grid_cap) {
    2101            0 :       manual_limit = grid_cap;
    2102              :     }
    2103              :   }
    2104              : 
    2105            9 :   grid_unique = count_unique_rgba(image.rgba, image.pixel_count);
    2106            9 :   grid_cap = compute_grid_capacity(bits_rgb, bits_alpha);
    2107            9 :   auto_target = (opts->lossy_reduced_colors <= 0);
    2108            9 :   grid_passthrough = false;
    2109              : 
    2110            9 :   if (grid_cap == 0) {
    2111            0 :     grid_cap = COLOPRESSO_PNGX_REDUCED_COLORS_MAX;
    2112              :   }
    2113              : 
    2114            9 :   passthrough_threshold = resolve_reduced_passthrough_threshold(grid_cap, &stats);
    2115              : 
    2116            9 :   if (grid_unique > (size_t)grid_cap) {
    2117            0 :     grid_unique = (size_t)grid_cap;
    2118              :   }
    2119              : 
    2120            9 :   if (auto_target && grid_unique >= passthrough_threshold) {
    2121            1 :     grid_passthrough = true;
    2122              :   }
    2123              : 
    2124            9 :   if (grid_passthrough) {
    2125            1 :     wrote = false;
    2126            1 :     snap_rgba_image_to_bits(image.rgba, image.pixel_count, bits_rgb, bits_alpha);
    2127              : 
    2128            1 :     if (resolved_target) {
    2129            1 :       *resolved_target = (uint32_t)grid_unique;
    2130              :     }
    2131              : 
    2132            1 :     if (applied_colors) {
    2133            1 :       *applied_colors = (uint32_t)grid_unique;
    2134              :     }
    2135              : 
    2136            1 :     wrote = create_rgba_png(image.rgba, image.pixel_count, image.width, image.height, out_data, out_size);
    2137            1 :     if (wrote) {
    2138            1 :       cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Reduced RGBA32 grid passthrough kept %zu colors (capacity=%u)", grid_unique, grid_cap);
    2139              :     }
    2140              : 
    2141            1 :     quant_support_reset(&support);
    2142            1 :     rgba_image_reset(&image);
    2143              : 
    2144            1 :     return wrote;
    2145              :   }
    2146              : 
    2147            8 :   if (!build_color_histogram(&image, &tuned_opts, &support, &histogram)) {
    2148            0 :     quant_support_reset(&support);
    2149            0 :     rgba_image_reset(&image);
    2150              : 
    2151            0 :     return false;
    2152              :   }
    2153              : 
    2154            8 :   target = resolve_reduced_rgba32_target(&histogram, image.pixel_count, opts->lossy_reduced_colors, bits_rgb, bits_alpha, &stats);
    2155            8 :   if (histogram.unlocked_count == 0 || target == 0) {
    2156            1 :     actual = (uint32_t)histogram.count;
    2157            1 :     snap_rgba_image_to_bits(image.rgba, image.pixel_count, bits_rgb, bits_alpha);
    2158              :   } else {
    2159            7 :     if (!apply_reduced_rgba32_quantization(&histogram, &image, target, bits_rgb, bits_alpha, &actual)) {
    2160            0 :       color_histogram_reset(&histogram);
    2161            0 :       quant_support_reset(&support);
    2162            0 :       rgba_image_reset(&image);
    2163              : 
    2164            0 :       return false;
    2165              :     }
    2166              :   }
    2167              : 
    2168            8 :   if (!manual_target) {
    2169            6 :     auto_trim_limit = compute_auto_trim_limit(&histogram, image.pixel_count, actual, bits_rgb, bits_alpha, &stats);
    2170            6 :     if (auto_trim_limit > 0 && auto_trim_limit < actual) {
    2171            4 :       if (enforce_manual_reduced_limit(&image, auto_trim_limit, bits_rgb, bits_alpha, &actual)) {
    2172            2 :         auto_trim_applied = true;
    2173            2 :         cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Reduced RGBA32 auto trim applied %u -> %u colors", target, auto_trim_limit);
    2174              :       } else {
    2175            0 :         cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Reduced RGBA32 auto trim request failed (limit=%u)", auto_trim_limit);
    2176            0 :         auto_trim_limit = 0;
    2177              :       }
    2178              :     } else {
    2179            4 :       auto_trim_limit = 0;
    2180              :     }
    2181              :   }
    2182              : 
    2183            8 :   if (manual_target) {
    2184            2 :     if (!enforce_manual_reduced_limit(&image, manual_limit, bits_rgb, bits_alpha, &actual)) {
    2185            0 :       color_histogram_reset(&histogram);
    2186            0 :       quant_support_reset(&support);
    2187            0 :       rgba_image_reset(&image);
    2188              : 
    2189            0 :       return false;
    2190              :     }
    2191              :   }
    2192              : 
    2193            8 :   success = create_rgba_png(image.rgba, image.pixel_count, image.width, image.height, out_data, out_size);
    2194              : 
    2195            8 :   if (resolved_target) {
    2196            8 :     if (manual_target) {
    2197            2 :       *resolved_target = manual_limit;
    2198            6 :     } else if (auto_trim_applied && auto_trim_limit > 0) {
    2199            2 :       *resolved_target = auto_trim_limit;
    2200            4 :     } else if (histogram.unlocked_count == 0 || target == 0) {
    2201            1 :       *resolved_target = (uint32_t)histogram.count;
    2202              :     } else {
    2203            3 :       *resolved_target = target;
    2204              :     }
    2205              :   }
    2206              : 
    2207            8 :   if (applied_colors) {
    2208            8 :     *applied_colors = actual;
    2209              :   }
    2210              : 
    2211            8 :   color_histogram_reset(&histogram);
    2212            8 :   quant_support_reset(&support);
    2213            8 :   rgba_image_reset(&image);
    2214              : 
    2215            8 :   return success;
    2216              : }
        

Generated by: LCOV version 2.0-1