LCOV - code coverage report
Current view: top level - src - colopresso.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 94.6 % 239 226
Test Date: 2025-12-13 15:41:21 Functions: 100.0 % 13 13
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 <stdbool.h>
      13              : #include <stdio.h>
      14              : #include <stdlib.h>
      15              : #include <string.h>
      16              : 
      17              : #include <avif/avif.h>
      18              : #include <png.h>
      19              : #include <webp/encode.h>
      20              : 
      21              : #include <colopresso.h>
      22              : #include <colopresso/portable.h>
      23              : 
      24              : #include "internal/sanitizer.h"
      25              : 
      26              : #include "internal/log.h"
      27              : #include "internal/png.h"
      28              : 
      29              : #include "internal/avif.h"
      30              : #include "internal/pngx.h"
      31              : #include "internal/webp.h"
      32              : 
      33          134 : void cpres_config_init_defaults(cpres_config_t *config) {
      34          134 :   if (!config) {
      35            1 :     return;
      36              :   }
      37              : 
      38          133 :   memset(config, 0, sizeof(*config));
      39              : 
      40          133 :   config->webp_quality = COLOPRESSO_WEBP_DEFAULT_QUALITY;
      41          133 :   config->webp_lossless = COLOPRESSO_WEBP_DEFAULT_LOSSLESS;
      42          133 :   config->webp_method = COLOPRESSO_WEBP_DEFAULT_METHOD;
      43          133 :   config->webp_target_size = COLOPRESSO_WEBP_DEFAULT_TARGET_SIZE;
      44          133 :   config->webp_target_psnr = COLOPRESSO_WEBP_DEFAULT_TARGET_PSNR;
      45          133 :   config->webp_segments = COLOPRESSO_WEBP_DEFAULT_SEGMENTS;
      46          133 :   config->webp_sns_strength = COLOPRESSO_WEBP_DEFAULT_SNS_STRENGTH;
      47          133 :   config->webp_filter_strength = COLOPRESSO_WEBP_DEFAULT_FILTER_STRENGTH;
      48          133 :   config->webp_filter_sharpness = COLOPRESSO_WEBP_DEFAULT_FILTER_SHARPNESS;
      49          133 :   config->webp_filter_type = COLOPRESSO_WEBP_DEFAULT_FILTER_TYPE;
      50          133 :   config->webp_autofilter = COLOPRESSO_WEBP_DEFAULT_AUTOFILTER;
      51          133 :   config->webp_alpha_compression = COLOPRESSO_WEBP_DEFAULT_ALPHA_COMPRESSION;
      52          133 :   config->webp_alpha_filtering = COLOPRESSO_WEBP_DEFAULT_ALPHA_FILTERING;
      53          133 :   config->webp_alpha_quality = COLOPRESSO_WEBP_DEFAULT_ALPHA_QUALITY;
      54          133 :   config->webp_pass = COLOPRESSO_WEBP_DEFAULT_PASS;
      55          133 :   config->webp_preprocessing = COLOPRESSO_WEBP_DEFAULT_PREPROCESSING;
      56          133 :   config->webp_partitions = COLOPRESSO_WEBP_DEFAULT_PARTITIONS;
      57          133 :   config->webp_partition_limit = COLOPRESSO_WEBP_DEFAULT_PARTITION_LIMIT;
      58          133 :   config->webp_emulate_jpeg_size = COLOPRESSO_WEBP_DEFAULT_EMULATE_JPEG_SIZE;
      59          133 :   config->webp_thread_level = COLOPRESSO_WEBP_DEFAULT_THREAD_LEVEL;
      60          133 :   config->webp_low_memory = COLOPRESSO_WEBP_DEFAULT_LOW_MEMORY;
      61          133 :   config->webp_near_lossless = COLOPRESSO_WEBP_DEFAULT_NEAR_LOSSLESS;
      62          133 :   config->webp_exact = COLOPRESSO_WEBP_DEFAULT_EXACT;
      63          133 :   config->webp_use_delta_palette = COLOPRESSO_WEBP_DEFAULT_USE_DELTA_PALETTE;
      64          133 :   config->webp_use_sharp_yuv = COLOPRESSO_WEBP_DEFAULT_USE_SHARP_YUV;
      65              : 
      66          133 :   config->avif_quality = COLOPRESSO_AVIF_DEFAULT_QUALITY;
      67          133 :   config->avif_alpha_quality = COLOPRESSO_AVIF_DEFAULT_ALPHA_QUALITY;
      68          133 :   config->avif_lossless = COLOPRESSO_AVIF_DEFAULT_LOSSLESS;
      69          133 :   config->avif_speed = COLOPRESSO_AVIF_DEFAULT_SPEED;
      70          133 :   config->avif_threads = COLOPRESSO_AVIF_DEFAULT_THREADS;
      71              : 
      72          133 :   config->pngx_level = COLOPRESSO_PNGX_DEFAULT_LEVEL;
      73          133 :   config->pngx_strip_safe = COLOPRESSO_PNGX_DEFAULT_STRIP_SAFE;
      74          133 :   config->pngx_optimize_alpha = COLOPRESSO_PNGX_DEFAULT_OPTIMIZE_ALPHA;
      75          133 :   config->pngx_lossy_enable = COLOPRESSO_PNGX_DEFAULT_LOSSY_ENABLE;
      76          133 :   config->pngx_lossy_type = COLOPRESSO_PNGX_DEFAULT_LOSSY_TYPE;
      77          133 :   config->pngx_lossy_max_colors = COLOPRESSO_PNGX_DEFAULT_LOSSY_MAX_COLORS;
      78          133 :   config->pngx_lossy_reduced_colors = COLOPRESSO_PNGX_DEFAULT_REDUCED_COLORS;
      79          133 :   config->pngx_lossy_reduced_bits_rgb = COLOPRESSO_PNGX_DEFAULT_REDUCED_BITS_RGB;
      80          133 :   config->pngx_lossy_reduced_alpha_bits = COLOPRESSO_PNGX_DEFAULT_REDUCED_ALPHA_BITS;
      81          133 :   config->pngx_lossy_quality_min = COLOPRESSO_PNGX_DEFAULT_LOSSY_QUALITY_MIN;
      82          133 :   config->pngx_lossy_quality_max = COLOPRESSO_PNGX_DEFAULT_LOSSY_QUALITY_MAX;
      83          133 :   config->pngx_lossy_speed = COLOPRESSO_PNGX_DEFAULT_LOSSY_SPEED;
      84          133 :   config->pngx_lossy_dither_level = COLOPRESSO_PNGX_DEFAULT_LOSSY_DITHER_LEVEL;
      85          133 :   config->pngx_saliency_map_enable = COLOPRESSO_PNGX_DEFAULT_SALIENCY_MAP_ENABLE;
      86          133 :   config->pngx_chroma_anchor_enable = COLOPRESSO_PNGX_DEFAULT_CHROMA_ANCHOR_ENABLE;
      87          133 :   config->pngx_adaptive_dither_enable = COLOPRESSO_PNGX_DEFAULT_ADAPTIVE_DITHER_ENABLE;
      88          133 :   config->pngx_gradient_boost_enable = COLOPRESSO_PNGX_DEFAULT_GRADIENT_BOOST_ENABLE;
      89          133 :   config->pngx_chroma_weight_enable = COLOPRESSO_PNGX_DEFAULT_CHROMA_WEIGHT_ENABLE;
      90          133 :   config->pngx_postprocess_smooth_enable = COLOPRESSO_PNGX_DEFAULT_POSTPROCESS_SMOOTH_ENABLE;
      91          133 :   config->pngx_postprocess_smooth_importance_cutoff = COLOPRESSO_PNGX_DEFAULT_POSTPROCESS_SMOOTH_IMPORTANCE_CUTOFF;
      92          133 :   config->pngx_palette256_gradient_profile_enable = COLOPRESSO_PNGX_DEFAULT_PALETTE256_GRADIENT_PROFILE_ENABLE;
      93          133 :   config->pngx_palette256_gradient_dither_floor = COLOPRESSO_PNGX_DEFAULT_PALETTE256_GRADIENT_DITHER_FLOOR;
      94          133 :   config->pngx_palette256_alpha_bleed_enable = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_ENABLE;
      95          133 :   config->pngx_palette256_alpha_bleed_max_distance = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_MAX_DISTANCE;
      96          133 :   config->pngx_palette256_alpha_bleed_opaque_threshold = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_OPAQUE_THRESHOLD;
      97          133 :   config->pngx_palette256_alpha_bleed_soft_limit = COLOPRESSO_PNGX_DEFAULT_PALETTE256_ALPHA_BLEED_SOFT_LIMIT;
      98          133 :   config->pngx_palette256_profile_opaque_ratio_threshold = COLOPRESSO_PNGX_DEFAULT_PALETTE256_PROFILE_OPAQUE_RATIO_THRESHOLD;
      99          133 :   config->pngx_palette256_profile_gradient_mean_max = COLOPRESSO_PNGX_DEFAULT_PALETTE256_PROFILE_GRADIENT_MEAN_MAX;
     100          133 :   config->pngx_palette256_profile_saturation_mean_max = COLOPRESSO_PNGX_DEFAULT_PALETTE256_PROFILE_SATURATION_MEAN_MAX;
     101          133 :   config->pngx_palette256_tune_opaque_ratio_threshold = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_OPAQUE_RATIO_THRESHOLD;
     102          133 :   config->pngx_palette256_tune_gradient_mean_max = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_GRADIENT_MEAN_MAX;
     103          133 :   config->pngx_palette256_tune_saturation_mean_max = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_SATURATION_MEAN_MAX;
     104          133 :   config->pngx_palette256_tune_speed_max = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_SPEED_MAX;
     105          133 :   config->pngx_palette256_tune_quality_min_floor = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_QUALITY_MIN_FLOOR;
     106          133 :   config->pngx_palette256_tune_quality_max_target = COLOPRESSO_PNGX_DEFAULT_PALETTE256_TUNE_QUALITY_MAX_TARGET;
     107          133 :   config->pngx_threads = COLOPRESSO_PNGX_DEFAULT_THREADS;
     108              : }
     109              : 
     110           37 : cpres_error_t cpres_encode_webp_memory(const uint8_t *png_data, size_t png_size, uint8_t **webp_data, size_t *webp_size, const cpres_config_t *config) {
     111              :   uint32_t width, height;
     112              :   cpres_error_t error;
     113              :   uint8_t *rgba_data;
     114              :   size_t encoded_size;
     115              : 
     116           37 :   if (!png_data || !webp_data || !webp_size || !config) {
     117            8 :     return CPRES_ERROR_INVALID_PARAMETER;
     118              :   }
     119              : 
     120           29 :   if (png_size == 0) {
     121            2 :     return CPRES_ERROR_INVALID_PARAMETER;
     122              :   }
     123              : 
     124           27 :   if (png_size > COLOPRESSO_PNG_MAX_MEMORY_INPUT_SIZE) {
     125            2 :     return CPRES_ERROR_INVALID_PARAMETER;
     126              :   }
     127              : 
     128           25 :   *webp_data = NULL;
     129           25 :   *webp_size = 0;
     130           25 :   encoded_size = 0;
     131              : 
     132           25 :   rgba_data = NULL;
     133           25 :   error = cpres_png_decode_from_memory(png_data, png_size, &rgba_data, &width, &height);
     134           25 :   if (error != CPRES_OK) {
     135            3 :     cpres_log(CPRES_LOG_LEVEL_ERROR, "PNG decode from memory failed: %s", cpres_error_string(error));
     136            3 :     return error;
     137              :   }
     138              : 
     139           22 :   cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNG decoded from memory - %dx%d pixels", width, height);
     140              : 
     141           22 :   error = cpres_webp_encode_rgba_to_memory(rgba_data, width, height, webp_data, &encoded_size, config);
     142           22 :   if (error == CPRES_OK) {
     143           22 :     if (*webp_data && encoded_size >= png_size) {
     144            1 :       cpres_log(CPRES_LOG_LEVEL_WARNING, "WebP: Encoded output larger than input (%zu > %zu)", encoded_size, png_size);
     145            1 :       cpres_free(*webp_data);
     146            1 :       *webp_data = NULL;
     147            1 :       error = CPRES_ERROR_OUTPUT_NOT_SMALLER;
     148              :     }
     149           22 :     *webp_size = encoded_size;
     150              :   }
     151              : 
     152           22 :   free(rgba_data);
     153              : 
     154           22 :   return error;
     155              : }
     156              : 
     157           18 : cpres_error_t cpres_encode_avif_memory(const uint8_t *png_data, size_t png_size, uint8_t **avif_data, size_t *avif_size, const cpres_config_t *config) {
     158              :   uint32_t width, height;
     159              :   uint8_t *rgba_data;
     160              :   size_t encoded_size;
     161              :   cpres_error_t error;
     162              : 
     163           18 :   if (!png_data || !avif_data || !avif_size || !config) {
     164            4 :     return CPRES_ERROR_INVALID_PARAMETER;
     165              :   }
     166           14 :   if (png_size == 0) {
     167            1 :     return CPRES_ERROR_INVALID_PARAMETER;
     168              :   }
     169              : 
     170           13 :   if (png_size > COLOPRESSO_PNG_MAX_MEMORY_INPUT_SIZE) {
     171            1 :     return CPRES_ERROR_INVALID_PARAMETER;
     172              :   }
     173              : 
     174           12 :   *avif_data = NULL;
     175           12 :   *avif_size = 0;
     176           12 :   encoded_size = 0;
     177              : 
     178           12 :   rgba_data = NULL;
     179           12 :   error = cpres_png_decode_from_memory(png_data, png_size, &rgba_data, &width, &height);
     180           12 :   if (error != CPRES_OK) {
     181            0 :     cpres_log(CPRES_LOG_LEVEL_ERROR, "PNG decode (AVIF) from memory failed: %s", cpres_error_string(error));
     182            0 :     return error;
     183              :   }
     184              : 
     185           12 :   cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNG decoded (AVIF) from memory - %dx%d pixels", width, height);
     186              : 
     187           12 :   error = cpres_avif_encode_rgba_to_memory(rgba_data, width, height, avif_data, &encoded_size, config);
     188           12 :   if (error == CPRES_OK) {
     189           12 :     if (*avif_data && encoded_size >= png_size) {
     190            1 :       cpres_log(CPRES_LOG_LEVEL_WARNING, "AVIF: Encoded output larger than input (%zu > %zu)", encoded_size, png_size);
     191            1 :       cpres_free(*avif_data);
     192            1 :       *avif_data = NULL;
     193            1 :       error = CPRES_ERROR_OUTPUT_NOT_SMALLER;
     194              :     }
     195           12 :     *avif_size = encoded_size;
     196              :   }
     197           12 :   free(rgba_data);
     198              : 
     199           12 :   return error;
     200              : }
     201              : 
     202           36 : cpres_error_t cpres_encode_pngx_memory(const uint8_t *png_data, size_t png_size, uint8_t **optimized_data, size_t *optimized_size, const cpres_config_t *config) {
     203              :   pngx_options_t opts;
     204              :   uint8_t *lossless_data, *quant_data, *quant_optimized, *final_data;
     205              :   size_t lossless_size, quant_size, quant_optimized_size, final_size, candidate_size;
     206              :   bool lossless_ok, quant_ok, quant_lossless_ok, quant_is_rgba_lossy, final_is_quantized;
     207              :   int quant_quality, threads;
     208              : 
     209           36 :   if (!png_data || png_size == 0 || !optimized_data || !optimized_size || !config) {
     210            5 :     return CPRES_ERROR_INVALID_PARAMETER;
     211              :   }
     212              : 
     213           31 :   if (png_size > COLOPRESSO_PNG_MAX_MEMORY_INPUT_SIZE) {
     214            0 :     return CPRES_ERROR_INVALID_PARAMETER;
     215              :   }
     216              : 
     217           31 :   *optimized_data = NULL;
     218           31 :   *optimized_size = 0;
     219              : 
     220           31 :   lossless_data = NULL;
     221           31 :   lossless_size = 0;
     222           31 :   quant_data = NULL;
     223           31 :   quant_size = 0;
     224           31 :   quant_optimized = NULL;
     225           31 :   quant_optimized_size = 0;
     226           31 :   final_data = NULL;
     227           31 :   final_size = 0;
     228           31 :   candidate_size = 0;
     229           31 :   lossless_ok = false;
     230           31 :   quant_ok = false;
     231           31 :   quant_lossless_ok = false;
     232           31 :   quant_is_rgba_lossy = false;
     233           31 :   final_is_quantized = false;
     234           31 :   quant_quality = -1;
     235           31 :   threads = 0;
     236              : 
     237           31 :   cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Starting optimization - input size: %zu bytes", png_size);
     238              : 
     239           31 :   pngx_fill_pngx_options(&opts, config);
     240              : 
     241           31 :   threads = config ? config->pngx_threads : 0;
     242           31 :   if (threads > 0) {
     243           31 :     pngx_bridge_init_threads(threads);
     244              :   }
     245              : 
     246           31 :   quant_is_rgba_lossy = (opts.lossy_type == PNGX_LOSSY_TYPE_LIMITED_RGBA4444 || opts.lossy_type == PNGX_LOSSY_TYPE_REDUCED_RGBA32);
     247              : 
     248           31 :   quant_ok = pngx_should_attempt_quantization(&opts) && pngx_run_quantization(png_data, png_size, &opts, &quant_data, &quant_size, &quant_quality);
     249           31 :   if (quant_ok) {
     250           30 :     cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Quantization produced %zu bytes (quality=%d)", quant_size, quant_quality);
     251              :   }
     252              : 
     253           31 :   lossless_ok = pngx_run_lossless_optimization(png_data, png_size, &opts, &lossless_data, &lossless_size);
     254           31 :   if (!lossless_ok) {
     255            0 :     lossless_data = (uint8_t *)malloc(png_size);
     256            0 :     if (!lossless_data) {
     257            0 :       free(quant_data);
     258            0 :       return CPRES_ERROR_OUT_OF_MEMORY;
     259              :     }
     260            0 :     memcpy(lossless_data, png_data, png_size);
     261            0 :     lossless_size = png_size;
     262              :   }
     263           31 :   cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Lossless optimization produced %zu bytes", lossless_size);
     264              : 
     265           31 :   final_data = lossless_data;
     266           31 :   final_size = lossless_size;
     267           31 :   final_is_quantized = false;
     268              : 
     269           31 :   if (quant_ok) {
     270           30 :     if (!quant_is_rgba_lossy) {
     271           23 :       quant_lossless_ok = pngx_run_lossless_optimization(quant_data, quant_size, &opts, &quant_optimized, &quant_optimized_size);
     272           23 :       if (quant_lossless_ok) {
     273           23 :         cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Lossless optimization on quantized data produced %zu bytes", quant_optimized_size);
     274           23 :         free(quant_data);
     275           23 :         quant_data = NULL;
     276              :       } else {
     277            0 :         quant_optimized = quant_data;
     278            0 :         quant_optimized_size = quant_size;
     279            0 :         quant_data = NULL;
     280              :       }
     281              :     } else {
     282            7 :       quant_optimized = quant_data;
     283            7 :       quant_optimized_size = quant_size;
     284            7 :       quant_data = NULL;
     285              :     }
     286              :   }
     287              : 
     288           31 :   if (quant_ok) {
     289           30 :     candidate_size = quant_optimized_size;
     290           30 :     if (quant_is_rgba_lossy || pngx_quantization_better(lossless_size, candidate_size)) {
     291           25 :       final_data = quant_optimized;
     292           25 :       final_size = candidate_size;
     293           25 :       final_is_quantized = true;
     294           25 :       free(lossless_data);
     295           25 :       cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Selected quantized result (%zu bytes)", final_size);
     296              :     } else {
     297            5 :       free(quant_optimized);
     298            5 :       quant_optimized = NULL;
     299            5 :       cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Selected lossless result (%zu bytes)", final_size);
     300              :     }
     301              :   }
     302              : 
     303           31 :   if (!final_data) {
     304            0 :     return CPRES_ERROR_ENCODE_FAILED;
     305              :   }
     306              : 
     307           31 :   if (final_size >= png_size) {
     308            5 :     if (!(quant_is_rgba_lossy && final_is_quantized)) {
     309            2 :       cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: Optimized output larger than input (%zu > %zu)", final_size, png_size);
     310            2 :       free(final_data);
     311            2 :       *optimized_data = NULL;
     312            2 :       *optimized_size = final_size;
     313            2 :       return CPRES_ERROR_OUTPUT_NOT_SMALLER;
     314              :     }
     315              : 
     316            3 :     cpres_log(CPRES_LOG_LEVEL_WARNING, "PNGX: RGBA lossy output larger than input (%zu > %zu) but forcing write per RGBA mode", final_size, png_size);
     317              :   }
     318              : 
     319              :   MSAN_UNPOISON(final_data, final_size);
     320              : 
     321           29 :   *optimized_data = final_data;
     322           29 :   *optimized_size = final_size;
     323              : 
     324           29 :   return CPRES_OK;
     325              : }
     326              : 
     327           87 : void cpres_free(uint8_t *data) {
     328           87 :   if (data) {
     329           83 :     free(data);
     330              :   }
     331           87 : }
     332              : 
     333           18 : const char *cpres_error_string(cpres_error_t error) {
     334           18 :   switch (error) {
     335            1 :   case CPRES_OK:
     336            1 :     return "Success";
     337            4 :   case CPRES_ERROR_FILE_NOT_FOUND:
     338            4 :     return "File not found";
     339            4 :   case CPRES_ERROR_INVALID_PNG:
     340            4 :     return "Invalid PNG file";
     341            1 :   case CPRES_ERROR_INVALID_FORMAT:
     342            1 :     return "Invalid WebP file";
     343            1 :   case CPRES_ERROR_OUT_OF_MEMORY:
     344            1 :     return "Out of memory";
     345            1 :   case CPRES_ERROR_ENCODE_FAILED:
     346            1 :     return "Encoding failed";
     347            1 :   case CPRES_ERROR_DECODE_FAILED:
     348            1 :     return "Decoding failed";
     349            1 :   case CPRES_ERROR_IO:
     350            1 :     return "I/O error";
     351            1 :   case CPRES_ERROR_INVALID_PARAMETER:
     352            1 :     return "Invalid parameter";
     353            2 :   case CPRES_ERROR_OUTPUT_NOT_SMALLER:
     354            2 :     return "Output image would be larger than input";
     355            1 :   default:
     356            1 :     return "Unknown error";
     357              :   }
     358              : }
     359              : 
     360            1 : uint32_t cpres_get_version(void) { return (uint32_t)COLOPRESSO_VERSION; }
     361              : 
     362            1 : uint32_t cpres_get_libwebp_version(void) { return (uint32_t)WebPGetEncoderVersion(); }
     363              : 
     364            1 : uint32_t cpres_get_libpng_version(void) { return (uint32_t)png_access_version_number(); }
     365              : 
     366            1 : uint32_t cpres_get_libavif_version(void) { return (uint32_t)AVIF_VERSION; }
     367              : 
     368            1 : uint32_t cpres_get_pngx_oxipng_version(void) { return pngx_bridge_oxipng_version(); }
     369              : 
     370            1 : uint32_t cpres_get_pngx_libimagequant_version(void) { return pngx_bridge_libimagequant_version(); }
     371              : 
     372            1 : uint32_t cpres_get_buildtime(void) {
     373              : #ifdef COLOPRESSO_BUILDTIME
     374            1 :   return (uint32_t)COLOPRESSO_BUILDTIME;
     375              : #else
     376              :   return 0;
     377              : #endif
     378              : }
        

Generated by: LCOV version 2.0-1