LCOV - code coverage report
Current view: top level - src - pngx_limited.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 81.4 % 86 70
Test Date: 2025-12-13 15:41:21 Functions: 100.0 % 5 5
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              : 
      17              : #include "internal/log.h"
      18              : #include "internal/pngx_common.h"
      19              : 
      20            3 : static inline uint8_t lossy_type_bits(uint8_t lossy_type) {
      21            3 :   switch (lossy_type) {
      22            3 :   case PNGX_LOSSY_TYPE_LIMITED_RGBA4444:
      23            3 :     return PNGX_LIMITED_RGBA4444_BITS;
      24            0 :   default:
      25            0 :     return PNGX_FULL_CHANNEL_BITS;
      26              :   }
      27              : }
      28              : 
      29      1671168 : static inline void process_bitdepth_pixel(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint32_t x, uint32_t y, uint8_t bits_per_channel, float dither_level, float *err_curr, float *err_next,
      30              :                                           bool left_to_right) {
      31              :   uint8_t channel, quantized;
      32      1671168 :   size_t pixel_index = ((size_t)y * (size_t)width + (size_t)x) * PNGX_RGBA_CHANNELS, err_index = (size_t)x * PNGX_RGBA_CHANNELS;
      33              :   float value, error;
      34              : 
      35      8355840 :   for (channel = 0; channel < PNGX_RGBA_CHANNELS; ++channel) {
      36      6684672 :     value = (float)rgba[pixel_index + channel] + err_curr[err_index + channel];
      37      6684672 :     quantized = quantize_channel_value(value, bits_per_channel);
      38      6684672 :     error = (value - (float)quantized) * dither_level;
      39              : 
      40      6684672 :     rgba[pixel_index + channel] = quantized;
      41              : 
      42      6684672 :     if (dither_level <= 0.0f || error == 0.0f) {
      43          690 :       continue;
      44              :     }
      45              : 
      46      6683982 :     if (left_to_right) {
      47      3341912 :       if (x + 1 < width) {
      48      3338845 :         err_curr[err_index + PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
      49              :       }
      50      3341912 :       if (y + 1 < height) {
      51      3341912 :         if (x > 0) {
      52      3338842 :           err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
      53              :         }
      54      3341912 :         err_next[err_index + channel] += error * (5.0f / 16.0f);
      55      3341912 :         if (x + 1 < width) {
      56      3338845 :           err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
      57              :         }
      58              :       }
      59              :     } else {
      60      3342070 :       if (x > 0) {
      61      3338998 :         err_curr[err_index - PNGX_RGBA_CHANNELS + channel] += error * (7.0f / 16.0f);
      62              :       }
      63      3342070 :       if (y + 1 < height) {
      64      3335926 :         if (x + 1 < width) {
      65      3332874 :           err_next[err_index + PNGX_RGBA_CHANNELS + channel] += error * (3.0f / 16.0f);
      66              :         }
      67      3335926 :         err_next[err_index + channel] += error * (5.0f / 16.0f);
      68      3335926 :         if (x > 0) {
      69      3332866 :           err_next[err_index - PNGX_RGBA_CHANNELS + channel] += error * (1.0f / 16.0f);
      70              :         }
      71              :       }
      72              :     }
      73              :   }
      74      1671168 : }
      75              : 
      76            3 : static inline void reduce_rgba_bitdepth_dither(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_per_channel, float dither_level) {
      77              :   uint32_t x, y;
      78              :   size_t row_stride;
      79              :   float *err_curr, *err_next, *tmp;
      80              :   bool left_to_right;
      81              : 
      82            3 :   if (!rgba || width == 0 || height == 0 || bits_per_channel >= PNGX_FULL_CHANNEL_BITS) {
      83            0 :     return;
      84              :   }
      85              : 
      86            3 :   if (dither_level <= 0.0f) {
      87            0 :     snap_rgba_image_to_bits(rgba, (size_t)width * (size_t)height, bits_per_channel, bits_per_channel);
      88            0 :     return;
      89              :   }
      90              : 
      91            3 :   row_stride = (size_t)width * PNGX_RGBA_CHANNELS;
      92            3 :   err_curr = (float *)calloc(row_stride, sizeof(float));
      93            3 :   err_next = (float *)calloc(row_stride, sizeof(float));
      94            3 :   if (!err_curr || !err_next) {
      95            0 :     free(err_curr);
      96            0 :     free(err_next);
      97            0 :     snap_rgba_image_to_bits(rgba, (size_t)width * (size_t)height, bits_per_channel, bits_per_channel);
      98            0 :     return;
      99              :   }
     100              : 
     101         1539 :   for (y = 0; y < height; ++y) {
     102         1536 :     left_to_right = ((y & 1) == 0);
     103         1536 :     memset(err_next, 0, row_stride * sizeof(float));
     104         1536 :     if (left_to_right) {
     105       836352 :       for (x = 0; x < width; ++x) {
     106       835584 :         process_bitdepth_pixel(rgba, width, height, x, y, bits_per_channel, dither_level, err_curr, err_next, true);
     107              :       }
     108              :     } else {
     109          768 :       x = width;
     110       836352 :       while (x-- > 0) {
     111       835584 :         process_bitdepth_pixel(rgba, width, height, x, y, bits_per_channel, dither_level, err_curr, err_next, false);
     112              :       }
     113              :     }
     114              : 
     115         1536 :     tmp = err_curr;
     116         1536 :     err_curr = err_next;
     117         1536 :     err_next = tmp;
     118              :   }
     119              : 
     120            3 :   free(err_curr);
     121            3 :   free(err_next);
     122              : }
     123              : 
     124            3 : static inline void reduce_rgba_bitdepth(uint8_t *rgba, png_uint_32 width, png_uint_32 height, uint8_t bits_per_channel, float dither_level) {
     125            3 :   if (!rgba || width == 0 || height == 0) {
     126            0 :     return;
     127              :   }
     128              : 
     129            3 :   if (bits_per_channel >= PNGX_FULL_CHANNEL_BITS) {
     130            0 :     return;
     131              :   }
     132              : 
     133            3 :   if (dither_level > 0.0f) {
     134            3 :     reduce_rgba_bitdepth_dither(rgba, width, height, bits_per_channel, dither_level);
     135              :   } else {
     136            0 :     snap_rgba_image_to_bits(rgba, (size_t)width * (size_t)height, bits_per_channel, bits_per_channel);
     137              :   }
     138              : }
     139              : 
     140            3 : bool pngx_quantize_limited4444(const uint8_t *png_data, size_t png_size, const pngx_options_t *opts, uint8_t **out_data, size_t *out_size) {
     141              :   pngx_rgba_image_t image;
     142              :   float resolved_dither;
     143              :   bool success;
     144              : 
     145            3 :   if (!png_data || png_size == 0 || !opts || !out_data || !out_size) {
     146            0 :     return false;
     147              :   }
     148              : 
     149            3 :   if (!load_rgba_image(png_data, png_size, &image)) {
     150            0 :     return false;
     151              :   }
     152              : 
     153            3 :   if (opts->lossy_dither_auto) {
     154            0 :     resolved_dither = estimate_bitdepth_dither_level(image.rgba, image.width, image.height, lossy_type_bits(opts->lossy_type));
     155              :   } else {
     156            3 :     resolved_dither = clamp_float(opts->lossy_dither_level, 0.0f, 1.0f);
     157              :   }
     158              : 
     159            3 :   reduce_rgba_bitdepth(image.rgba, image.width, image.height, lossy_type_bits(opts->lossy_type), resolved_dither);
     160              : 
     161            3 :   success = create_rgba_png(image.rgba, image.pixel_count, image.width, image.height, out_data, out_size);
     162            3 :   rgba_image_reset(&image);
     163              : 
     164            3 :   if (success) {
     165            3 :     const char *label = lossy_type_label(opts->lossy_type);
     166            3 :     if (opts->lossy_dither_auto) {
     167            0 :       cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Auto dither %.2f selected for %s", resolved_dither, label);
     168              :     } else {
     169            3 :       cpres_log(CPRES_LOG_LEVEL_DEBUG, "PNGX: Manual dither %.2f used for %s", resolved_dither, label);
     170              :     }
     171              :   }
     172              : 
     173            3 :   return success;
     174              : }
        

Generated by: LCOV version 2.0-1