LCOV - code coverage report
Current view: top level - src - pngx_reduced.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 85.3 % 1291 1101
Test Date: 2026-02-16 05:23:27 Functions: 100.0 % 57 57
Legend: Lines: hit not hit

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

Generated by: LCOV version 2.0-1