LCOV - code coverage report
Current view: top level - src - file.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 72.4 % 123 89
Test Date: 2026-11-09 10:50:48 Functions: 100.0 % 6 6
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 <errno.h>
      13              : #include <stdbool.h>
      14              : #include <stdint.h>
      15              : #include <stdio.h>
      16              : #include <stdlib.h>
      17              : #include <string.h>
      18              : 
      19              : #include <sys/stat.h>
      20              : #include <sys/types.h>
      21              : 
      22              : #include <colopresso.h>
      23              : #include <colopresso/portable.h>
      24              : 
      25              : #include "internal/avif.h"
      26              : #include "internal/log.h"
      27              : #include "internal/png.h"
      28              : #include "internal/webp.h"
      29              : 
      30              : #if COLOPRESSO_WITH_FILE_OPS
      31              : 
      32           18 : static inline bool cpres_get_file_size_bytes(const char *path, size_t *size_out)
      33              : {
      34              :     struct stat st;
      35              : 
      36           18 :     if (!path || !size_out) {
      37            0 :         return false;
      38              :     }
      39              : 
      40           18 :     if (stat(path, &st) != 0 || st.st_size < 0) {
      41            3 :         return false;
      42              :     }
      43              : 
      44           15 :     *size_out = (size_t)st.st_size;
      45              : 
      46           15 :     return true;
      47              : }
      48              : 
      49           13 : static inline cpres_error_t write_output_file(const char *path, const uint8_t *data, size_t size)
      50              : {
      51              :     FILE *fp;
      52              :     size_t written;
      53              : 
      54           13 :     fp = fopen(path, "wb");
      55           13 :     if (!fp) {
      56            0 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "Failed to open output file '%s'", path);
      57              : 
      58            0 :         return CPRES_ERROR_IO;
      59              :     }
      60              : 
      61           13 :     written = fwrite(data, 1, size, fp);
      62           13 :     if (fclose(fp) != 0 || written != size) {
      63            0 :         colopresso_remove_regular_file(path);
      64              : 
      65            0 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "Failed to write output file '%s'", path);
      66              : 
      67            0 :         return CPRES_ERROR_IO;
      68              :     }
      69              : 
      70           13 :     return CPRES_OK;
      71              : }
      72              : 
      73            4 : extern cpres_error_t cpres_read_file_to_memory(const char *path, uint8_t **data_out,
      74              :                                                size_t *size_out)
      75              : {
      76              :     FILE *fp;
      77              :     uint64_t file_size64;
      78              :     uint8_t *buffer;
      79              :     size_t read_size, file_size;
      80              : 
      81            4 :     if (!path || !data_out || !size_out) {
      82            0 :         return CPRES_ERROR_INVALID_PARAMETER;
      83              :     }
      84              : 
      85            4 :     *data_out = NULL;
      86            4 :     *size_out = 0;
      87              : 
      88            4 :     fp = fopen(path, "rb");
      89            4 :     if (!fp) {
      90            1 :         return CPRES_ERROR_FILE_NOT_FOUND;
      91              :     }
      92              : 
      93            3 :     if (!colopresso_fseeko(fp, 0, SEEK_END)) {
      94            0 :         fclose(fp);
      95              : 
      96            0 :         return CPRES_ERROR_IO;
      97              :     }
      98              : 
      99            3 :     if (!colopresso_ftello(fp, &file_size64)) {
     100            0 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "ftello failed for '%s': errno=%d", path, errno);
     101            0 :         fclose(fp);
     102              : 
     103            0 :         return CPRES_ERROR_INVALID_PARAMETER;
     104              :     }
     105              : 
     106            3 :     if (file_size64 <= 0 || file_size64 > (uint64_t)SIZE_MAX) {
     107            0 :         fclose(fp);
     108              : 
     109            0 :         return CPRES_ERROR_INVALID_PARAMETER;
     110              :     }
     111              : 
     112            3 :     if (!colopresso_fseeko(fp, 0, SEEK_SET)) {
     113            0 :         fclose(fp);
     114              : 
     115            0 :         return CPRES_ERROR_IO;
     116              :     }
     117              : 
     118            3 :     file_size = (size_t)file_size64;
     119              : 
     120            3 :     buffer = (uint8_t *)malloc(file_size);
     121            3 :     if (!buffer) {
     122            0 :         fclose(fp);
     123              : 
     124            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     125              :     }
     126              : 
     127            3 :     read_size = fread(buffer, 1, file_size, fp);
     128            3 :     fclose(fp);
     129              : 
     130            3 :     if (read_size != file_size) {
     131            0 :         free(buffer);
     132              : 
     133            0 :         return CPRES_ERROR_IO;
     134              :     }
     135              : 
     136            3 :     *data_out = buffer;
     137            3 :     *size_out = read_size;
     138              : 
     139            3 :     return CPRES_OK;
     140              : }
     141              : 
     142           13 : extern cpres_error_t cpres_encode_webp_file(const char *input_path, const char *output_path,
     143              :                                             const cpres_config_t *config)
     144              : {
     145              :     cpres_error_t error;
     146              :     uint32_t width, height;
     147           13 :     uint8_t *rgba_data = NULL, *webp_data = NULL;
     148           13 :     size_t input_size = 0, webp_size = 0;
     149              :     bool have_input_size;
     150              : 
     151           13 :     if (!input_path || !output_path || !config) {
     152            6 :         return CPRES_ERROR_INVALID_PARAMETER;
     153              :     }
     154              : 
     155            7 :     have_input_size = cpres_get_file_size_bytes(input_path, &input_size);
     156              : 
     157            7 :     error = png_decode_from_file(input_path, &rgba_data, &width, &height);
     158            7 :     if (error != CPRES_OK) {
     159            2 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "PNG read failed: %s", cpres_error_string(error));
     160              : 
     161            2 :         return error;
     162              :     }
     163              : 
     164            5 :     colopresso_log(CPRES_LOG_LEVEL_DEBUG, "PNG loaded - %dx%d pixels", width, height);
     165              : 
     166            5 :     error = webp_encode_rgba_to_memory(rgba_data, width, height, &webp_data, &webp_size, config);
     167            5 :     free(rgba_data);
     168              : 
     169            5 :     if (error != CPRES_OK) {
     170            0 :         return error;
     171              :     }
     172              : 
     173            5 :     if (have_input_size && webp_size >= input_size) {
     174            0 :         colopresso_log(CPRES_LOG_LEVEL_WARNING,
     175              :                        "WebP: Encoded output larger than input (%zu > %zu)", webp_size, input_size);
     176            0 :         cpres_free(webp_data);
     177              : 
     178            0 :         return CPRES_ERROR_OUTPUT_NOT_SMALLER;
     179              :     }
     180              : 
     181            5 :     error = write_output_file(output_path, webp_data, webp_size);
     182            5 :     cpres_free(webp_data);
     183              : 
     184            5 :     return error;
     185              : }
     186              : 
     187           10 : extern cpres_error_t cpres_encode_avif_file(const char *input_path, const char *output_path,
     188              :                                             const cpres_config_t *config)
     189              : {
     190              :     cpres_error_t error;
     191              :     uint32_t width, height;
     192           10 :     uint8_t *rgba_data = NULL, *avif_data = NULL;
     193           10 :     size_t avif_size = 0, input_size = 0;
     194              :     bool have_input_size;
     195              : 
     196           10 :     if (!input_path || !output_path || !config) {
     197            3 :         return CPRES_ERROR_INVALID_PARAMETER;
     198              :     }
     199              : 
     200            7 :     have_input_size = cpres_get_file_size_bytes(input_path, &input_size);
     201              : 
     202            7 :     error = png_decode_from_file(input_path, &rgba_data, &width, &height);
     203            7 :     if (error != CPRES_OK) {
     204            2 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "PNG read (AVIF) failed: %s",
     205              :                        cpres_error_string(error));
     206              : 
     207            2 :         return error;
     208              :     }
     209              : 
     210            5 :     colopresso_log(CPRES_LOG_LEVEL_DEBUG, "PNG loaded (AVIF) - %dx%d pixels", width, height);
     211              : 
     212            5 :     error = avif_encode_rgba_to_memory(rgba_data, width, height, &avif_data, &avif_size, config);
     213            5 :     free(rgba_data);
     214              : 
     215            5 :     if (error != CPRES_OK) {
     216            0 :         return error;
     217              :     }
     218              : 
     219            5 :     if (have_input_size && avif_size >= input_size) {
     220            0 :         colopresso_log(CPRES_LOG_LEVEL_WARNING,
     221              :                        "AVIF: Encoded output larger than input (%zu > %zu)", avif_size, input_size);
     222            0 :         cpres_free(avif_data);
     223              : 
     224            0 :         return CPRES_ERROR_OUTPUT_NOT_SMALLER;
     225              :     }
     226              : 
     227            5 :     error = write_output_file(output_path, avif_data, avif_size);
     228            5 :     cpres_free(avif_data);
     229              : 
     230            5 :     return error;
     231              : }
     232              : 
     233            6 : extern cpres_error_t cpres_encode_pngx_file(const char *input_path, const char *output_path,
     234              :                                             const cpres_config_t *config)
     235              : {
     236              :     cpres_error_t err;
     237            6 :     uint8_t *input_data = NULL, *optimized_data = NULL;
     238            6 :     size_t input_size = 0, png_size = 0, optimized_size = 0;
     239            6 :     bool have_input_size = false, allow_lossy_rgba_larger_output = false;
     240              : 
     241            6 :     if (!input_path || !output_path || !config) {
     242            2 :         return CPRES_ERROR_INVALID_PARAMETER;
     243              :     }
     244              : 
     245            4 :     have_input_size = cpres_get_file_size_bytes(input_path, &input_size);
     246            4 :     allow_lossy_rgba_larger_output =
     247            8 :         (config && config->pngx_lossy_enable &&
     248            4 :          (config->pngx_lossy_type == CPRES_PNGX_LOSSY_TYPE_LIMITED_RGBA4444));
     249              : 
     250            4 :     err = cpres_read_file_to_memory(input_path, &input_data, &png_size);
     251            4 :     if (err != CPRES_OK) {
     252            1 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "PNG read (PNGX) failed: %s",
     253              :                        cpres_error_string(err));
     254              : 
     255            1 :         return err;
     256              :     }
     257              : 
     258            3 :     err = cpres_encode_pngx_memory(input_data, png_size, &optimized_data, &optimized_size, config);
     259            3 :     free(input_data);
     260              : 
     261            3 :     if (err != CPRES_OK) {
     262            0 :         return err;
     263              :     }
     264              : 
     265            3 :     if (have_input_size && optimized_size >= input_size) {
     266            0 :         if (allow_lossy_rgba_larger_output) {
     267            0 :             colopresso_log(CPRES_LOG_LEVEL_WARNING,
     268              :                            "PNGX: RGBA lossy output larger than input (%zu > %zu) but forcing "
     269              :                            "write per RGBA mode",
     270              :                            optimized_size, input_size);
     271              :         } else {
     272            0 :             colopresso_log(CPRES_LOG_LEVEL_WARNING,
     273              :                            "PNGX: Optimized output larger than input (%zu > %zu)", optimized_size,
     274              :                            input_size);
     275            0 :             cpres_free(optimized_data);
     276              : 
     277            0 :             return CPRES_ERROR_OUTPUT_NOT_SMALLER;
     278              :         }
     279              :     }
     280              : 
     281            3 :     err = write_output_file(output_path, optimized_data, optimized_size);
     282            3 :     cpres_free(optimized_data);
     283              : 
     284            3 :     return err;
     285              : }
     286              : 
     287              : #endif /* COLOPRESSO_WITH_FILE_OPS */
        

Generated by: LCOV version 2.0-1