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

Generated by: LCOV version 2.0-1