LCOV - code coverage report
Current view: top level - src - png.c (source / functions) Coverage Total Hit
Test: colopresso Coverage Report Lines: 82.9 % 105 87
Test Date: 2026-11-09 10:50:48 Functions: 100.0 % 7 7
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 <stdbool.h>
      13              : #include <stdint.h>
      14              : #include <stdio.h>
      15              : #include <stdlib.h>
      16              : #include <string.h>
      17              : 
      18              : #include <png.h>
      19              : 
      20              : #include <colopresso.h>
      21              : 
      22              : #include "internal/log.h"
      23              : #include "internal/png.h"
      24              : 
      25              : #define PNG_DECODE_MAX_DIMENSION 65536u
      26              : #define PNG_DECODE_CHANNELS      4u
      27              : 
      28              : typedef struct {
      29              :     const uint8_t *data;
      30              :     size_t size;
      31              :     size_t pos;
      32              : } png_memory_reader_t;
      33              : 
      34              : /* Buffers of one decode. They live in the caller of png_decode_guarded() so that they keep
      35              :  * their values when libpng longjmps out of png_read_rgba_rows(). */
      36              : typedef struct {
      37              :     png_bytep *row_pointers;
      38              :     uint8_t *rgba;
      39              :     png_uint_32 width;
      40              :     png_uint_32 height;
      41              : } png_decode_state_t;
      42              : 
      43          107 : static inline cpres_error_t png_decode_take(png_decode_state_t *state, cpres_error_t result,
      44              :                                             uint8_t **rgba_data, png_uint_32 *width,
      45              :                                             png_uint_32 *height)
      46              : {
      47          107 :     free(state->row_pointers);
      48          107 :     state->row_pointers = NULL;
      49              : 
      50          107 :     if (result != CPRES_OK) {
      51            5 :         free(state->rgba);
      52            5 :         state->rgba = NULL;
      53              : 
      54            5 :         return result;
      55              :     }
      56              : 
      57          102 :     *rgba_data = state->rgba;
      58          102 :     *width = state->width;
      59          102 :     *height = state->height;
      60          102 :     state->rgba = NULL;
      61              : 
      62          102 :     return CPRES_OK;
      63              : }
      64              : 
      65        18673 : static void png_read_from_memory(png_structp png_ptr, png_bytep data, png_size_t length)
      66              : {
      67        18673 :     png_memory_reader_t *reader = (png_memory_reader_t *)png_get_io_ptr(png_ptr);
      68              : 
      69        18673 :     if (length > reader->size - reader->pos) {
      70            3 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "Attempted to read past end of PNG data");
      71            3 :         png_error(png_ptr, "Read past end of data");
      72              :     }
      73              : 
      74        18670 :     memcpy(data, reader->data + reader->pos, length);
      75              : 
      76        18670 :     reader->pos += length;
      77        18670 : }
      78              : 
      79          107 : static bool png_create_read(png_structp *png, png_infop *info)
      80              : {
      81          107 :     *png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
      82          107 :     if (!*png) {
      83            0 :         return false;
      84              :     }
      85              : 
      86          107 :     *info = png_create_info_struct(*png);
      87          107 :     if (!*info) {
      88            0 :         png_destroy_read_struct(png, NULL, NULL);
      89              : 
      90            0 :         return false;
      91              :     }
      92              : 
      93          107 :     return true;
      94              : }
      95              : 
      96          107 : static cpres_error_t png_read_rgba_rows(png_structp png, png_infop info, png_decode_state_t *state)
      97              : {
      98              :     png_byte color_type, bit_depth;
      99              :     png_uint_32 y;
     100              :     size_t row_bytes;
     101              : 
     102          107 :     png_read_info(png, info);
     103              : 
     104          107 :     state->width = png_get_image_width(png, info);
     105          107 :     state->height = png_get_image_height(png, info);
     106          107 :     color_type = png_get_color_type(png, info);
     107          107 :     bit_depth = png_get_bit_depth(png, info);
     108              : 
     109          107 :     if (state->width == 0 || state->height == 0 || state->width > PNG_DECODE_MAX_DIMENSION ||
     110          107 :         state->height > PNG_DECODE_MAX_DIMENSION) {
     111            0 :         return CPRES_ERROR_INVALID_PNG;
     112              :     }
     113              : 
     114          107 :     if (bit_depth == 16) {
     115            9 :         png_set_strip_16(png);
     116              :     }
     117              : 
     118          107 :     if (color_type == PNG_COLOR_TYPE_PALETTE) {
     119            1 :         png_set_palette_to_rgb(png);
     120              :     }
     121              : 
     122          107 :     if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
     123            0 :         png_set_expand_gray_1_2_4_to_8(png);
     124              :     }
     125              : 
     126          107 :     if (png_get_valid(png, info, PNG_INFO_tRNS)) {
     127            0 :         png_set_tRNS_to_alpha(png);
     128              :     }
     129              : 
     130          107 :     if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
     131              :         color_type == PNG_COLOR_TYPE_PALETTE) {
     132            1 :         png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
     133              :     }
     134              : 
     135          107 :     if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
     136            0 :         png_set_gray_to_rgb(png);
     137              :     }
     138              : 
     139          107 :     png_read_update_info(png, info);
     140              : 
     141          107 :     row_bytes = png_get_rowbytes(png, info);
     142          107 :     if (row_bytes != (size_t)state->width * PNG_DECODE_CHANNELS) {
     143            0 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "Unexpected PNG row size after RGBA conversion");
     144              : 
     145            0 :         return CPRES_ERROR_INVALID_PNG;
     146              :     }
     147              : 
     148          107 :     if (row_bytes > SIZE_MAX / (size_t)state->height ||
     149              :         (size_t)state->height > SIZE_MAX / sizeof(png_bytep)) {
     150            0 :         colopresso_log(CPRES_LOG_LEVEL_ERROR, "Integer overflow detected: image too large");
     151              : 
     152            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     153              :     }
     154              : 
     155          107 :     state->rgba = (uint8_t *)malloc(row_bytes * (size_t)state->height);
     156          107 :     if (!state->rgba) {
     157            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     158              :     }
     159              : 
     160          107 :     state->row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * (size_t)state->height);
     161          107 :     if (!state->row_pointers) {
     162            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     163              :     }
     164              : 
     165        40820 :     for (y = 0; y < state->height; ++y) {
     166        40713 :         state->row_pointers[y] = state->rgba + (size_t)y * row_bytes;
     167              :     }
     168              : 
     169          107 :     png_read_image(png, state->row_pointers);
     170              : 
     171          102 :     return CPRES_OK;
     172              : }
     173              : 
     174          107 : static cpres_error_t png_decode_guarded(png_structp png, png_infop info, png_decode_state_t *state)
     175              : {
     176          107 :     if (setjmp(png_jmpbuf(png)) != 0) {
     177            5 :         return CPRES_ERROR_INVALID_PNG;
     178              :     }
     179              : 
     180          107 :     return png_read_rgba_rows(png, info, state);
     181              : }
     182              : 
     183           98 : extern cpres_error_t png_decode_from_memory(const uint8_t *png_data, size_t png_size,
     184              :                                             uint8_t **rgba_data, png_uint_32 *width,
     185              :                                             png_uint_32 *height)
     186              : {
     187           98 :     png_memory_reader_t reader = {0};
     188           98 :     png_decode_state_t state = {0};
     189              :     png_structp png;
     190              :     png_infop info;
     191              :     cpres_error_t result;
     192              : 
     193           98 :     if (!png_data || png_size < 8 || !rgba_data || !width || !height) {
     194            0 :         return CPRES_ERROR_INVALID_PARAMETER;
     195              :     }
     196              : 
     197           98 :     *rgba_data = NULL;
     198              : 
     199           98 :     if (png_sig_cmp(png_data, 0, 8) != 0) {
     200            3 :         return CPRES_ERROR_INVALID_PNG;
     201              :     }
     202              : 
     203           95 :     if (!png_create_read(&png, &info)) {
     204            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     205              :     }
     206              : 
     207           95 :     reader.data = png_data;
     208           95 :     reader.size = png_size;
     209           95 :     reader.pos = 0;
     210           95 :     png_set_read_fn(png, &reader, png_read_from_memory);
     211              : 
     212           95 :     result = png_decode_guarded(png, info, &state);
     213           95 :     png_destroy_read_struct(&png, &info, NULL);
     214              : 
     215           95 :     return png_decode_take(&state, result, rgba_data, width, height);
     216              : }
     217              : 
     218              : #if COLOPRESSO_WITH_FILE_OPS
     219           14 : extern cpres_error_t png_decode_from_file(const char *filename, uint8_t **rgba_data,
     220              :                                           png_uint_32 *width, png_uint_32 *height)
     221              : {
     222           14 :     png_decode_state_t state = {0};
     223              :     png_structp png;
     224              :     png_infop info;
     225              :     cpres_error_t result;
     226              :     FILE *fp;
     227              : 
     228           14 :     if (!filename || !rgba_data || !width || !height) {
     229            0 :         return CPRES_ERROR_INVALID_PARAMETER;
     230              :     }
     231              : 
     232           14 :     *rgba_data = NULL;
     233              : 
     234           14 :     fp = fopen(filename, "rb");
     235           14 :     if (!fp) {
     236            2 :         return CPRES_ERROR_FILE_NOT_FOUND;
     237              :     }
     238              : 
     239           12 :     if (!png_create_read(&png, &info)) {
     240            0 :         fclose(fp);
     241              : 
     242            0 :         return CPRES_ERROR_OUT_OF_MEMORY;
     243              :     }
     244              : 
     245           12 :     png_init_io(png, fp);
     246              : 
     247           12 :     result = png_decode_guarded(png, info, &state);
     248           12 :     png_destroy_read_struct(&png, &info, NULL);
     249           12 :     fclose(fp);
     250              : 
     251           12 :     return png_decode_take(&state, result, rgba_data, width, height);
     252              : }
     253              : #endif /* COLOPRESSO_WITH_FILE_OPS */
        

Generated by: LCOV version 2.0-1