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

Generated by: LCOV version 2.0-1