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 */
|