LCOV - code coverage report
Current view: top level - src - pngx_palette256.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 84.2 % 443 373
Test Date: 2025-12-13 15:41:21 Functions: 100.0 % 15 15
Legend: Lines: hit not hit

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-or-later
       3              :  *
       4              :  * This file is part of colopresso
       5              :  *
       6              :  * Copyright (C) 2025 COLOPL, Inc.
       7              :  *
       8              :  * Author: Go Kudo <g-kudo@colopl.co.jp>
       9              :  * Developed with AI (LLM) code assistance. See `NOTICE` for details.
      10              :  */
      11              : 
      12              : #include <stdlib.h>
      13              : #include <string.h>
      14              : 
      15              : #include <png.h>
      16              : #include <zlib.h>
      17              : 
      18              : #include "internal/log.h"
      19              : #include "internal/pngx_common.h"
      20              : 
      21           24 : static inline void alpha_bleed_rgb_from_opaque(uint8_t *rgba, uint32_t width, uint32_t height, const pngx_options_t *opts) {
      22              :   uint32_t *seed_rgb, x, y, best_rgb;
      23              :   uint16_t *dist, best, d;
      24              :   uint16_t max_distance;
      25              :   uint8_t opaque_threshold, soft_limit;
      26              :   size_t pixel_count, i, base, idx;
      27              :   bool has_seed;
      28              : 
      29           24 :   if (!rgba || width == 0 || height == 0) {
      30            0 :     return;
      31              :   }
      32              : 
      33           24 :   if (!opts || !opts->palette256_alpha_bleed_enable) {
      34            1 :     return;
      35              :   }
      36              : 
      37           23 :   max_distance = opts->palette256_alpha_bleed_max_distance;
      38           23 :   opaque_threshold = opts->palette256_alpha_bleed_opaque_threshold;
      39           23 :   soft_limit = opts->palette256_alpha_bleed_soft_limit;
      40              : 
      41           23 :   if ((size_t)width > SIZE_MAX / (size_t)height) {
      42            0 :     return;
      43              :   }
      44           23 :   pixel_count = (size_t)width * (size_t)height;
      45              : 
      46           23 :   dist = (uint16_t *)malloc(sizeof(uint16_t) * pixel_count);
      47           23 :   seed_rgb = (uint32_t *)malloc(sizeof(uint32_t) * pixel_count);
      48           23 :   if (!dist || !seed_rgb) {
      49            0 :     free(dist);
      50            0 :     free(seed_rgb);
      51            0 :     return;
      52              :   }
      53              : 
      54           23 :   has_seed = false;
      55     10671513 :   for (i = 0; i < pixel_count; ++i) {
      56     10671490 :     base = i * 4;
      57     10671490 :     if (rgba[base + 3] == 0) {
      58         4096 :       rgba[base + 0] = 0;
      59         4096 :       rgba[base + 1] = 0;
      60         4096 :       rgba[base + 2] = 0;
      61              :     }
      62              : 
      63     10671490 :     if (rgba[base + 3] >= opaque_threshold) {
      64        84009 :       dist[i] = 0;
      65        84009 :       seed_rgb[i] = ((uint32_t)rgba[base + 0] << 16) | ((uint32_t)rgba[base + 1] << 8) | (uint32_t)rgba[base + 2];
      66        84009 :       has_seed = true;
      67              :     } else {
      68     10587481 :       dist[i] = UINT16_MAX;
      69     10587481 :       seed_rgb[i] = 0;
      70              :     }
      71              :   }
      72              : 
      73           23 :   if (!has_seed) {
      74            1 :     free(dist);
      75            1 :     free(seed_rgb);
      76            1 :     return;
      77              :   }
      78              : 
      79           88 :   for (i = 0; i < 3; ++i) {
      80        32421 :     for (y = 0; y < height; ++y) {
      81     32034537 :       for (x = 0; x < width; ++x) {
      82     32002182 :         idx = (size_t)y * (size_t)width + (size_t)x;
      83     32002182 :         best = dist[idx];
      84     32002182 :         best_rgb = seed_rgb[idx];
      85              : 
      86     32002182 :         if (x > 0 && dist[idx - 1] != UINT16_MAX && (uint16_t)(dist[idx - 1] + 1) < best) {
      87     10570172 :           best = (uint16_t)(dist[idx - 1] + 1);
      88     10570172 :           best_rgb = seed_rgb[idx - 1];
      89              :         }
      90     32002182 :         if (y > 0 && dist[idx - (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width] + 1) < best) {
      91      4105526 :           best = (uint16_t)(dist[idx - (size_t)width] + 1);
      92      4105526 :           best_rgb = seed_rgb[idx - (size_t)width];
      93              :         }
      94     32002182 :         if (x > 0 && y > 0 && dist[idx - (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width - 1] + 1) < best) {
      95       202756 :           best = (uint16_t)(dist[idx - (size_t)width - 1] + 1);
      96       202756 :           best_rgb = seed_rgb[idx - (size_t)width - 1];
      97              :         }
      98     32002182 :         if (x + 1 < width && y > 0 && dist[idx - (size_t)width + 1] != UINT16_MAX && (uint16_t)(dist[idx - (size_t)width + 1] + 1) < best) {
      99       258250 :           best = (uint16_t)(dist[idx - (size_t)width + 1] + 1);
     100       258250 :           best_rgb = seed_rgb[idx - (size_t)width + 1];
     101              :         }
     102              : 
     103     32002182 :         dist[idx] = best;
     104     32002182 :         seed_rgb[idx] = best_rgb;
     105              :       }
     106              :     }
     107              : 
     108        32421 :     for (y = height; y-- > 0;) {
     109     32034537 :       for (x = width; x-- > 0;) {
     110     32002182 :         idx = (size_t)y * (size_t)width + (size_t)x;
     111     32002182 :         best = dist[idx];
     112     32002182 :         best_rgb = seed_rgb[idx];
     113              : 
     114     32002182 :         if (x + 1 < width && dist[idx + 1] != UINT16_MAX && (uint16_t)(dist[idx + 1] + 1) < best) {
     115      4028774 :           best = (uint16_t)(dist[idx + 1] + 1);
     116      4028774 :           best_rgb = seed_rgb[idx + 1];
     117              :         }
     118     32002182 :         if (y + 1 < height && dist[idx + (size_t)width] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width] + 1) < best) {
     119      2899342 :           best = (uint16_t)(dist[idx + (size_t)width] + 1);
     120      2899342 :           best_rgb = seed_rgb[idx + (size_t)width];
     121              :         }
     122     32002182 :         if (x + 1 < width && y + 1 < height && dist[idx + (size_t)width + 1] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width + 1] + 1) < best) {
     123        76180 :           best = (uint16_t)(dist[idx + (size_t)width + 1] + 1);
     124        76180 :           best_rgb = seed_rgb[idx + (size_t)width + 1];
     125              :         }
     126     32002182 :         if (x > 0 && y + 1 < height && dist[idx + (size_t)width - 1] != UINT16_MAX && (uint16_t)(dist[idx + (size_t)width - 1] + 1) < best) {
     127      1460099 :           best = (uint16_t)(dist[idx + (size_t)width - 1] + 1);
     128      1460099 :           best_rgb = seed_rgb[idx + (size_t)width - 1];
     129              :         }
     130              : 
     131     32002182 :         dist[idx] = best;
     132     32002182 :         seed_rgb[idx] = best_rgb;
     133              :       }
     134              :     }
     135              :   }
     136              : 
     137        10807 :   for (y = 0; y < height; ++y) {
     138     10678179 :     for (x = 0; x < width; ++x) {
     139     10667394 :       idx = (size_t)y * (size_t)width + (size_t)x;
     140     10667394 :       d = dist[idx];
     141     10667394 :       base = idx * 4;
     142              : 
     143     10667394 :       if (rgba[base + 3] <= soft_limit && d != UINT16_MAX && d <= max_distance) {
     144            1 :         rgba[base + 0] = (uint8_t)((seed_rgb[idx] >> 16) & 0xFFu);
     145            1 :         rgba[base + 1] = (uint8_t)((seed_rgb[idx] >> 8) & 0xFFu);
     146            1 :         rgba[base + 2] = (uint8_t)(seed_rgb[idx] & 0xFFu);
     147              :       }
     148              :     }
     149              :   }
     150              : 
     151           22 :   free(dist);
     152           22 :   free(seed_rgb);
     153              : }
     154              : 
     155           24 : static inline void sanitize_transparent_palette(cpres_rgba_color_t *palette, size_t palette_len) {
     156              :   size_t i;
     157              : 
     158           24 :   if (!palette || palette_len == 0) {
     159            0 :     return;
     160              :   }
     161              : 
     162         3145 :   for (i = 0; i < palette_len; ++i) {
     163         3121 :     if (palette[i].a == 0) {
     164            1 :       palette[i].r = 0;
     165            1 :       palette[i].g = 0;
     166            1 :       palette[i].b = 0;
     167              :     }
     168              :   }
     169              : }
     170              : 
     171           21 : static inline bool is_smooth_gradient_profile(const pngx_image_stats_t *stats, const pngx_options_t *opts) {
     172              :   float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
     173              : 
     174           21 :   if (!stats) {
     175            0 :     return false;
     176              :   }
     177              : 
     178           21 :   opaque_ratio_threshold = (opts ? opts->palette256_profile_opaque_ratio_threshold : -1.0f);
     179           21 :   if (opaque_ratio_threshold < 0.0f) {
     180            0 :     opaque_ratio_threshold = PNGX_PALETTE256_GRADIENT_PROFILE_OPAQUE_RATIO_THRESHOLD;
     181              :   }
     182              : 
     183           21 :   gradient_mean_max = (opts ? opts->palette256_profile_gradient_mean_max : -1.0f);
     184           21 :   if (gradient_mean_max < 0.0f) {
     185            0 :     gradient_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_GRADIENT_MEAN_MAX;
     186              :   }
     187              : 
     188           21 :   saturation_mean_max = (opts ? opts->palette256_profile_saturation_mean_max : -1.0f);
     189           21 :   if (saturation_mean_max < 0.0f) {
     190            0 :     saturation_mean_max = PNGX_PALETTE256_GRADIENT_PROFILE_SATURATION_MEAN_MAX;
     191              :   }
     192              : 
     193           21 :   if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
     194            2 :     return true;
     195              :   }
     196              : 
     197           19 :   return false;
     198              : }
     199              : 
     200           24 : static inline void tune_quant_params_for_image(PngxBridgeQuantParams *params, const pngx_options_t *opts, const pngx_image_stats_t *stats) {
     201              :   uint8_t quality_min, quality_max;
     202              :   float opaque_ratio_threshold, gradient_mean_max, saturation_mean_max;
     203              :   int32_t speed_max, quality_min_floor, quality_max_target;
     204              : 
     205           24 :   if (!params || !opts || !stats) {
     206            0 :     return;
     207              :   }
     208              : 
     209           24 :   opaque_ratio_threshold = opts->palette256_tune_opaque_ratio_threshold;
     210           24 :   if (opaque_ratio_threshold < 0.0f) {
     211            0 :     opaque_ratio_threshold = PNGX_PALETTE256_TUNE_OPAQUE_RATIO_THRESHOLD;
     212              :   }
     213           24 :   gradient_mean_max = opts->palette256_tune_gradient_mean_max;
     214           24 :   if (gradient_mean_max < 0.0f) {
     215            0 :     gradient_mean_max = PNGX_PALETTE256_TUNE_GRADIENT_MEAN_MAX;
     216              :   }
     217           24 :   saturation_mean_max = opts->palette256_tune_saturation_mean_max;
     218           24 :   if (saturation_mean_max < 0.0f) {
     219            0 :     saturation_mean_max = PNGX_PALETTE256_TUNE_SATURATION_MEAN_MAX;
     220              :   }
     221              : 
     222           24 :   speed_max = opts->palette256_tune_speed_max;
     223           24 :   if (speed_max < 0) {
     224            0 :     speed_max = PNGX_PALETTE256_TUNE_SPEED_MAX;
     225              :   }
     226           24 :   quality_min_floor = opts->palette256_tune_quality_min_floor;
     227           24 :   if (quality_min_floor < 0) {
     228            0 :     quality_min_floor = PNGX_PALETTE256_TUNE_QUALITY_MIN_FLOOR;
     229              :   }
     230           24 :   quality_max_target = opts->palette256_tune_quality_max_target;
     231           24 :   if (quality_max_target < 0) {
     232            0 :     quality_max_target = PNGX_PALETTE256_TUNE_QUALITY_MAX_TARGET;
     233              :   }
     234              : 
     235           24 :   if (stats->opaque_ratio > opaque_ratio_threshold && stats->gradient_mean < gradient_mean_max && stats->saturation_mean < saturation_mean_max) {
     236            4 :     if (params->speed > speed_max) {
     237            3 :       params->speed = speed_max;
     238              :     }
     239              : 
     240            4 :     quality_min = params->quality_min;
     241            4 :     quality_max = params->quality_max;
     242              : 
     243            4 :     if (quality_max < (uint8_t)quality_max_target) {
     244            3 :       quality_max = (uint8_t)quality_max_target;
     245              :     }
     246            4 :     if (quality_min < (uint8_t)quality_min_floor) {
     247            3 :       quality_min = (uint8_t)quality_min_floor;
     248              :     }
     249            4 :     if (quality_min > quality_max) {
     250            1 :       quality_min = quality_max;
     251              :     }
     252              : 
     253            4 :     params->quality_min = quality_min;
     254            4 :     params->quality_max = quality_max;
     255              :   }
     256              : }
     257              : 
     258           24 : static inline void postprocess_indices(uint8_t *indices, uint32_t width, uint32_t height, const cpres_rgba_color_t *palette, size_t palette_len, const pngx_quant_support_t *support,
     259              :                                        const pngx_options_t *opts) {
     260              :   uint32_t x, y, dist_sq;
     261              :   uint8_t base_color, importance, neighbor_colors[4], neighbor_used, *reference, candidate;
     262              :   size_t pixel_count, idx;
     263              :   float cutoff;
     264              :   bool neighbor_has_base;
     265              : 
     266           24 :   if (!indices || !support || !opts || width == 0 || height == 0) {
     267           19 :     return;
     268              :   }
     269              : 
     270           24 :   if (!opts->postprocess_smooth_enable) {
     271            2 :     return;
     272              :   }
     273              : 
     274           22 :   if (opts->lossy_dither_level >= PNGX_POSTPROCESS_DISABLE_DITHER_THRESHOLD) {
     275           17 :     return;
     276              :   }
     277              : 
     278            5 :   if (!support->importance_map || support->importance_map_len < (size_t)width * (size_t)height) {
     279            0 :     return;
     280              :   }
     281              : 
     282            5 :   cutoff = opts->postprocess_smooth_importance_cutoff;
     283            5 :   if (cutoff >= 0.0f) {
     284            4 :     if (cutoff > 1.0f) {
     285            0 :       cutoff = 1.0f;
     286              :     }
     287              :   } else {
     288            1 :     cutoff = -1.0f;
     289              :   }
     290              : 
     291            5 :   pixel_count = (size_t)width * (size_t)height;
     292              : 
     293            5 :   reference = (uint8_t *)malloc(pixel_count);
     294            5 :   if (!reference) {
     295            0 :     return;
     296              :   }
     297              : 
     298            5 :   memcpy(reference, indices, pixel_count);
     299              : 
     300          230 :   for (y = 0; y < height; ++y) {
     301        13539 :     for (x = 0; x < width; ++x) {
     302        13314 :       idx = (size_t)y * (size_t)width + (size_t)x;
     303        13314 :       base_color = reference[idx];
     304        13314 :       importance = support->importance_map[idx];
     305        13314 :       neighbor_used = 0;
     306        13314 :       neighbor_has_base = false;
     307              : 
     308        13314 :       if (cutoff >= 0.0f && ((float)importance / 255.0f) >= cutoff) {
     309            2 :         continue;
     310              :       }
     311              : 
     312        13312 :       if (x > 0) {
     313        13088 :         neighbor_colors[neighbor_used] = reference[idx - 1];
     314        13088 :         if (neighbor_colors[neighbor_used] == base_color) {
     315        12950 :           neighbor_has_base = true;
     316              :         }
     317        13088 :         ++neighbor_used;
     318              :       }
     319        13312 :       if (x + 1 < width) {
     320        13087 :         neighbor_colors[neighbor_used] = reference[idx + 1];
     321        13087 :         if (neighbor_colors[neighbor_used] == base_color) {
     322        12950 :           neighbor_has_base = true;
     323              :         }
     324        13087 :         ++neighbor_used;
     325              :       }
     326        13312 :       if (y > 0) {
     327        13087 :         neighbor_colors[neighbor_used] = reference[idx - (size_t)width];
     328        13087 :         if (neighbor_colors[neighbor_used] == base_color) {
     329        13009 :           neighbor_has_base = true;
     330              :         }
     331        13087 :         ++neighbor_used;
     332              :       }
     333        13312 :       if (y + 1 < height) {
     334        13087 :         neighbor_colors[neighbor_used] = reference[idx + (size_t)width];
     335        13087 :         if (neighbor_colors[neighbor_used] == base_color) {
     336        13009 :           neighbor_has_base = true;
     337              :         }
     338        13087 :         ++neighbor_used;
     339              :       }
     340              : 
     341        13312 :       if (neighbor_used < 3) {
     342           17 :         continue;
     343              :       }
     344              : 
     345        13295 :       candidate = neighbor_colors[0];
     346        13295 :       if (neighbor_used == 3) {
     347          864 :         if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate)) {
     348           20 :           continue;
     349              :         }
     350              :       } else {
     351        12431 :         if (!(neighbor_colors[1] == candidate && neighbor_colors[2] == candidate && neighbor_colors[3] == candidate)) {
     352          354 :           continue;
     353              :         }
     354              :       }
     355              : 
     356        12921 :       if (candidate == base_color) {
     357        12920 :         continue;
     358              :       }
     359              : 
     360            1 :       if (neighbor_has_base) {
     361            0 :         continue;
     362              :       }
     363              : 
     364            1 :       if (palette && palette_len > 0 && (size_t)base_color < palette_len && (size_t)candidate < palette_len) {
     365            1 :         dist_sq = color_distance_sq(&palette[base_color], &palette[candidate]);
     366            1 :         if (dist_sq > PNGX_POSTPROCESS_MAX_COLOR_DISTANCE_SQ) {
     367            0 :           continue;
     368              :         }
     369              :       }
     370              : 
     371            1 :       indices[idx] = candidate;
     372              :     }
     373              :   }
     374              : 
     375            5 :   free(reference);
     376              : }
     377              : 
     378           24 : static inline void fill_quant_params(PngxBridgeQuantParams *params, const pngx_options_t *opts, const uint8_t *importance_map, size_t importance_map_len) {
     379              :   uint8_t quality_min, quality_max;
     380              : 
     381           24 :   if (!params || !opts) {
     382            0 :     return;
     383              :   }
     384              : 
     385           24 :   quality_min = clamp_uint8_t(opts->lossy_quality_min, 0, 100);
     386           24 :   quality_max = clamp_uint8_t(opts->lossy_quality_max, quality_min, 100);
     387           24 :   params->speed = clamp_int32_t((int32_t)opts->lossy_speed, 1, 10);
     388           24 :   params->quality_min = quality_min;
     389           24 :   params->quality_max = quality_max;
     390           24 :   params->max_colors = clamp_uint32_t((uint32_t)opts->lossy_max_colors, 2, 256);
     391           24 :   params->min_posterization = -1;
     392           24 :   params->dithering_level = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
     393           24 :   params->importance_map = importance_map;
     394           24 :   params->importance_map_len = importance_map_len;
     395           24 :   params->fixed_colors = opts->protected_colors;
     396           24 :   params->fixed_colors_len = (size_t)((opts->protected_colors_count > 0) ? opts->protected_colors_count : 0);
     397           24 :   params->remap = true;
     398              : }
     399              : 
     400           24 : static inline void free_quant_output(PngxBridgeQuantOutput *output) {
     401           24 :   if (!output) {
     402            0 :     return;
     403              :   }
     404              : 
     405           24 :   if (output->palette) {
     406           24 :     pngx_bridge_free((uint8_t *)output->palette);
     407           24 :     output->palette = NULL;
     408              :   }
     409           24 :   output->palette_len = 0;
     410              : 
     411           24 :   if (output->indices) {
     412           24 :     pngx_bridge_free(output->indices);
     413           24 :     output->indices = NULL;
     414              :   }
     415              : 
     416           24 :   output->indices_len = 0;
     417              : }
     418              : 
     419           41 : static inline bool init_write_struct(png_structp *png_ptr, png_infop *info_ptr) {
     420              :   png_structp tmp_png;
     421              :   png_infop tmp_info;
     422              : 
     423           41 :   if (!png_ptr || !info_ptr) {
     424            0 :     return false;
     425              :   }
     426              : 
     427           41 :   tmp_png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
     428           41 :   if (!tmp_png) {
     429            0 :     return false;
     430              :   }
     431              : 
     432           41 :   tmp_info = png_create_info_struct(tmp_png);
     433           41 :   if (!tmp_info) {
     434            0 :     png_destroy_write_struct(&tmp_png, NULL);
     435            0 :     return false;
     436              :   }
     437              : 
     438           41 :   *png_ptr = tmp_png;
     439           41 :   *info_ptr = tmp_info;
     440              : 
     441           41 :   return true;
     442              : }
     443              : 
     444         6457 : static inline bool memory_buffer_reserve(png_memory_buffer_t *buffer, size_t additional) {
     445              :   size_t required, capacity;
     446              :   uint8_t *new_data;
     447              : 
     448         6457 :   if (!buffer || additional > SIZE_MAX - buffer->size) {
     449            0 :     return false;
     450              :   }
     451              : 
     452         6457 :   required = buffer->size + additional;
     453         6457 :   capacity = buffer->capacity ? buffer->capacity : 4096;
     454              : 
     455         6609 :   while (capacity < required) {
     456          152 :     if (capacity > SIZE_MAX / 2) {
     457            0 :       capacity = required;
     458            0 :       break;
     459              :     }
     460              : 
     461          152 :     capacity *= 2;
     462              :   }
     463              : 
     464         6457 :   new_data = (uint8_t *)realloc(buffer->data, capacity);
     465         6457 :   if (!new_data) {
     466            0 :     return false;
     467              :   }
     468              : 
     469         6457 :   buffer->data = new_data;
     470         6457 :   buffer->capacity = capacity;
     471              : 
     472         6457 :   return true;
     473              : }
     474              : 
     475         6457 : static inline void memory_write(png_structp png_ptr, png_bytep data, png_size_t length) {
     476         6457 :   png_memory_buffer_t *buffer = (png_memory_buffer_t *)png_get_io_ptr(png_ptr);
     477              : 
     478         6457 :   if (!buffer || !memory_buffer_reserve(buffer, length)) {
     479            0 :     png_error(png_ptr, "png write failure");
     480              :     return;
     481              :   }
     482              : 
     483         6457 :   memcpy(buffer->data + buffer->size, data, length);
     484              : 
     485         6457 :   buffer->size += length;
     486              : }
     487              : 
     488           41 : static inline bool finalize_memory_png(png_memory_buffer_t *buffer, uint8_t **out_data, size_t *out_size) {
     489              :   uint8_t *shrunk;
     490              : 
     491           41 :   if (!buffer || !out_data || !out_size) {
     492            0 :     return false;
     493              :   }
     494              : 
     495           41 :   if (buffer->data) {
     496           41 :     shrunk = (uint8_t *)realloc(buffer->data, buffer->size);
     497           41 :     if (shrunk) {
     498           41 :       buffer->data = shrunk;
     499           41 :       buffer->capacity = buffer->size;
     500              :     }
     501              :   }
     502              : 
     503           41 :   *out_data = buffer->data;
     504           41 :   *out_size = buffer->size;
     505           41 :   if (!buffer->data || buffer->size == 0) {
     506            0 :     memory_buffer_reset(buffer);
     507            0 :     return false;
     508              :   }
     509              : 
     510           41 :   return true;
     511              : }
     512              : 
     513           41 : static inline png_bytep *build_row_pointers(const uint8_t *data, size_t row_stride, uint32_t height) {
     514              :   png_bytep *row_pointers;
     515              :   uint32_t y;
     516              : 
     517           41 :   if (!data || row_stride == 0 || height == 0) {
     518            0 :     return NULL;
     519              :   }
     520              : 
     521           41 :   row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
     522           41 :   if (!row_pointers) {
     523            0 :     return NULL;
     524              :   }
     525              : 
     526        15920 :   for (y = 0; y < height; ++y) {
     527        15879 :     row_pointers[y] = (png_bytep)(data + (size_t)y * row_stride);
     528              :   }
     529              : 
     530           41 :   return row_pointers;
     531              : }
     532              : 
     533           24 : static inline bool pngx_create_palette_png(const uint8_t *indices, size_t indices_len, const cpres_rgba_color_t *palette, size_t palette_len, uint32_t width, uint32_t height, uint8_t **out_data,
     534              :                                            size_t *out_size) {
     535              :   png_color palette_data[256];
     536              :   png_byte alpha_data[256];
     537              :   png_structp png_ptr;
     538              :   png_infop info_ptr;
     539              :   png_bytep *row_pointers;
     540              :   png_memory_buffer_t buffer;
     541              :   size_t expected_len, i;
     542              :   int num_trans;
     543              : 
     544           24 :   if (!indices || !palette || !out_data || !out_size || width == 0 || height == 0) {
     545            0 :     return false;
     546              :   }
     547              : 
     548           24 :   expected_len = (size_t)width * (size_t)height;
     549           24 :   if (indices_len != expected_len || palette_len == 0 || palette_len > 256) {
     550            0 :     return false;
     551              :   }
     552              : 
     553           24 :   if (!init_write_struct(&png_ptr, &info_ptr)) {
     554            0 :     return false;
     555              :   }
     556              : 
     557           24 :   buffer.data = NULL;
     558           24 :   buffer.size = 0;
     559           24 :   buffer.capacity = 0;
     560           24 :   row_pointers = NULL;
     561              : 
     562           24 :   if (setjmp(png_jmpbuf(png_ptr)) != 0) {
     563            0 :     memory_buffer_reset(&buffer);
     564            0 :     png_destroy_write_struct(&png_ptr, &info_ptr);
     565            0 :     free(row_pointers);
     566            0 :     return false;
     567              :   }
     568              : 
     569           24 :   png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
     570           24 :   png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
     571           24 :   png_set_compression_strategy(png_ptr, Z_FILTERED);
     572           24 :   png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
     573           24 :   png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
     574              : 
     575           24 :   num_trans = 0;
     576         3145 :   for (i = 0; i < palette_len; ++i) {
     577         3121 :     palette_data[i].red = palette[i].r;
     578         3121 :     palette_data[i].green = palette[i].g;
     579         3121 :     palette_data[i].blue = palette[i].b;
     580         3121 :     alpha_data[i] = palette[i].a;
     581         3121 :     if (palette[i].a != 255) {
     582         3043 :       num_trans = (int)(i + 1);
     583              :     }
     584              :   }
     585              : 
     586           24 :   png_set_PLTE(png_ptr, info_ptr, palette_data, (int)palette_len);
     587           24 :   if (num_trans > 0) {
     588           19 :     png_set_tRNS(png_ptr, info_ptr, alpha_data, num_trans, NULL);
     589              :   }
     590              : 
     591           24 :   png_write_info(png_ptr, info_ptr);
     592              : 
     593           24 :   row_pointers = build_row_pointers(indices, (size_t)width, height);
     594           24 :   if (!row_pointers) {
     595            0 :     png_error(png_ptr, "png row allocation failed");
     596              :     return false;
     597              :   }
     598              : 
     599           24 :   png_write_image(png_ptr, row_pointers);
     600           24 :   png_write_end(png_ptr, NULL);
     601              : 
     602           24 :   free(row_pointers);
     603           24 :   png_destroy_write_struct(&png_ptr, &info_ptr);
     604              : 
     605           24 :   return finalize_memory_png(&buffer, out_data, out_size);
     606              : }
     607              : 
     608           17 : bool create_rgba_png(const uint8_t *rgba, size_t pixel_count, uint32_t width, uint32_t height, uint8_t **out_data, size_t *out_size) {
     609              :   png_structp png_ptr;
     610              :   png_infop info_ptr;
     611              :   png_bytep *row_pointers;
     612              :   png_memory_buffer_t buffer;
     613              :   size_t expected_pixels;
     614              : 
     615           17 :   if (!rgba || !out_data || !out_size || width == 0 || height == 0) {
     616            0 :     return false;
     617              :   }
     618              : 
     619           17 :   expected_pixels = (size_t)width * (size_t)height;
     620           17 :   if (pixel_count != expected_pixels) {
     621            0 :     return false;
     622              :   }
     623              : 
     624           17 :   if (!init_write_struct(&png_ptr, &info_ptr)) {
     625            0 :     return false;
     626              :   }
     627              : 
     628           17 :   buffer.data = NULL;
     629           17 :   buffer.size = 0;
     630           17 :   buffer.capacity = 0;
     631           17 :   row_pointers = NULL;
     632              : 
     633           17 :   if (setjmp(png_jmpbuf(png_ptr)) != 0) {
     634            0 :     memory_buffer_reset(&buffer);
     635            0 :     png_destroy_write_struct(&png_ptr, &info_ptr);
     636            0 :     free(row_pointers);
     637            0 :     return false;
     638              :   }
     639              : 
     640           17 :   png_set_write_fn(png_ptr, &buffer, memory_write, NULL);
     641           17 :   png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
     642           17 :   png_set_compression_strategy(png_ptr, Z_FILTERED);
     643           17 :   png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS);
     644           17 :   png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
     645           17 :   png_write_info(png_ptr, info_ptr);
     646              : 
     647           17 :   row_pointers = build_row_pointers(rgba, (size_t)width * PNGX_RGBA_CHANNELS, height);
     648           17 :   if (!row_pointers) {
     649            0 :     png_error(png_ptr, "png row allocation failed");
     650              : 
     651              :     return false;
     652              :   }
     653              : 
     654           17 :   png_write_image(png_ptr, row_pointers);
     655           17 :   png_write_end(png_ptr, NULL);
     656              : 
     657           17 :   free(row_pointers);
     658           17 :   png_destroy_write_struct(&png_ptr, &info_ptr);
     659              : 
     660           17 :   return finalize_memory_png(&buffer, out_data, out_size);
     661              : }
     662              : 
     663           24 : bool pngx_quantize_palette256(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint8_t **out_data, size_t *out_size, int *quant_quality) {
     664              :   PngxBridgeQuantParams params, fallback_params;
     665              :   PngxBridgeQuantOutput output;
     666              :   PngxBridgeQuantStatus status;
     667              :   pngx_options_t tuned_opts;
     668              :   pngx_quant_support_t support;
     669              :   pngx_image_stats_t stats;
     670              :   pngx_rgba_image_t image;
     671              :   size_t pixel_count;
     672              :   float resolved_dither, estimated_dither, gradient_dither_floor;
     673              :   bool prefer_uniform, relaxed_quality, success;
     674              : 
     675           24 :   if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
     676            0 :     return false;
     677              :   }
     678              : 
     679           24 :   if (quant_quality) {
     680           24 :     *quant_quality = -1;
     681              :   }
     682              : 
     683           24 :   if (!load_rgba_image(png_data, png_size, &image)) {
     684            0 :     return false;
     685              :   }
     686              : 
     687           24 :   alpha_bleed_rgb_from_opaque(image.rgba, image.width, image.height, opts);
     688              : 
     689           24 :   pixel_count = image.pixel_count;
     690           24 :   image_stats_reset(&stats);
     691           24 :   memset(&support, 0, sizeof(support));
     692           24 :   if (!prepare_quant_support(&image, opts, &support, &stats)) {
     693            0 :     rgba_image_reset(&image);
     694            0 :     quant_support_reset(&support);
     695              : 
     696            0 :     return false;
     697              :   }
     698              : 
     699           24 :   tuned_opts = *opts;
     700           24 :   prefer_uniform = opts->palette256_gradient_profile_enable ? is_smooth_gradient_profile(&stats, &tuned_opts) : false;
     701           24 :   if (prefer_uniform) {
     702            2 :     tuned_opts.saliency_map_enable = false;
     703            2 :     tuned_opts.chroma_anchor_enable = false;
     704            2 :     tuned_opts.postprocess_smooth_enable = false;
     705              :   } else {
     706           22 :     build_fixed_palette(opts, &support, &tuned_opts);
     707              :   }
     708              : 
     709           24 :   resolved_dither = resolve_quant_dither(opts, &stats);
     710              : 
     711           24 :   if (opts->lossy_dither_auto) {
     712            0 :     estimated_dither = estimate_bitdepth_dither_level(image.rgba, image.width, image.height, 8);
     713            0 :     if (estimated_dither > resolved_dither) {
     714            0 :       resolved_dither = estimated_dither;
     715              :     }
     716              :   }
     717              : 
     718           24 :   gradient_dither_floor = tuned_opts.palette256_gradient_profile_dither_floor;
     719           24 :   if (gradient_dither_floor < 0.0f) {
     720            0 :     gradient_dither_floor = PNGX_PALETTE256_GRADIENT_PROFILE_DITHER_FLOOR;
     721              :   }
     722              : 
     723           24 :   if (prefer_uniform && resolved_dither < gradient_dither_floor) {
     724            2 :     resolved_dither = gradient_dither_floor;
     725              :   }
     726              : 
     727           24 :   tuned_opts.lossy_dither_level = resolved_dither;
     728              : 
     729           24 :   fill_quant_params(&params, &tuned_opts, prefer_uniform ? NULL : support.importance_map, prefer_uniform ? 0 : support.importance_map_len);
     730              : 
     731           24 :   params.dithering_level = resolved_dither;
     732              : 
     733           24 :   tune_quant_params_for_image(&params, &tuned_opts, &stats);
     734              : 
     735           24 :   output.palette = NULL;
     736           24 :   output.palette_len = 0;
     737           24 :   output.indices = NULL;
     738           24 :   output.indices_len = 0;
     739           24 :   output.quality = -1;
     740           24 :   relaxed_quality = false;
     741              : 
     742           24 :   status = pngx_bridge_quantize((const cpres_rgba_color_t *)image.rgba, pixel_count, image.width, image.height, &params, &output);
     743           24 :   pngx_set_last_error((int)status);
     744              : 
     745           24 :   if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW && params.quality_min > 0) {
     746            1 :     fallback_params = params;
     747            1 :     fallback_params.quality_min = 0;
     748              : 
     749            1 :     if (fallback_params.quality_max < fallback_params.quality_min) {
     750            0 :       fallback_params.quality_max = fallback_params.quality_min;
     751              :     }
     752              : 
     753            1 :     output.palette = NULL;
     754            1 :     output.palette_len = 0;
     755            1 :     output.indices = NULL;
     756            1 :     output.indices_len = 0;
     757            1 :     output.quality = -1;
     758              : 
     759            1 :     status = pngx_bridge_quantize((const cpres_rgba_color_t *)image.rgba, pixel_count, image.width, image.height, &fallback_params, &output);
     760            1 :     pngx_set_last_error((int)status);
     761            1 :     if (status == PNGX_BRIDGE_QUANT_STATUS_OK) {
     762            1 :       relaxed_quality = true;
     763              :     }
     764              :   }
     765              : 
     766           24 :   if (status != PNGX_BRIDGE_QUANT_STATUS_OK) {
     767            0 :     free_quant_output(&output);
     768            0 :     rgba_image_reset(&image);
     769            0 :     quant_support_reset(&support);
     770            0 :     if (status == PNGX_BRIDGE_QUANT_STATUS_QUALITY_TOO_LOW) {
     771            0 :       cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Quantization quality too low");
     772              :     }
     773              : 
     774            0 :     return false;
     775              :   }
     776              : 
     777           24 :   if (relaxed_quality) {
     778            1 :     cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Relaxed quantization quality floor");
     779              :   }
     780              : 
     781           24 :   if (quant_quality) {
     782           24 :     *quant_quality = output.quality;
     783              :   }
     784              : 
     785           24 :   success = false;
     786           24 :   if (output.indices && output.indices_len == pixel_count && output.palette && output.palette_len > 0 && output.palette_len <= 256) {
     787           24 :     sanitize_transparent_palette(output.palette, output.palette_len);
     788           24 :     postprocess_indices(output.indices, image.width, image.height, output.palette, output.palette_len, &support, &tuned_opts);
     789           24 :     success = pngx_create_palette_png(output.indices, output.indices_len, output.palette, output.palette_len, image.width, image.height, out_data, out_size);
     790              :   }
     791              : 
     792           24 :   free_quant_output(&output);
     793           24 :   rgba_image_reset(&image);
     794           24 :   quant_support_reset(&support);
     795              : 
     796           24 :   return success;
     797              : }
        

Generated by: LCOV version 2.0-1