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 : typedef struct {
26 : const uint8_t *data;
27 : size_t size;
28 : size_t pos;
29 : } png_memory_reader_t;
30 :
31 18210 : static void png_read_from_memory(png_structp png_ptr, png_bytep data, png_size_t length) {
32 : png_memory_reader_t *reader;
33 :
34 18210 : reader = (png_memory_reader_t *)png_get_io_ptr(png_ptr);
35 18210 : if (reader->pos + length > reader->size) {
36 0 : colopresso_log(CPRES_LOG_LEVEL_ERROR, "Attempted to read past end of PNG data");
37 0 : png_error(png_ptr, "Read past end of data");
38 : return;
39 : }
40 :
41 18210 : memcpy(data, reader->data + reader->pos, length);
42 :
43 18210 : reader->pos += length;
44 : }
45 :
46 93 : static inline cpres_error_t read_png_common(png_structp png, png_infop info, uint8_t **rgba_data, png_uint_32 *width, png_uint_32 *height) {
47 : png_byte color_type, bit_depth;
48 : png_bytep *row_pointers;
49 : png_uint_32 y;
50 : size_t row_bytes, total_size;
51 :
52 93 : png_read_info(png, info);
53 :
54 93 : *width = png_get_image_width(png, info);
55 93 : *height = png_get_image_height(png, info);
56 93 : color_type = png_get_color_type(png, info);
57 93 : bit_depth = png_get_bit_depth(png, info);
58 :
59 93 : if (*width <= 0 || *height <= 0 || *width > 65536 || *height > 65536) {
60 0 : return CPRES_ERROR_INVALID_PNG;
61 : }
62 :
63 93 : if (bit_depth == 16) {
64 9 : png_set_strip_16(png);
65 : }
66 :
67 93 : if (color_type == PNG_COLOR_TYPE_PALETTE) {
68 1 : png_set_palette_to_rgb(png);
69 : }
70 :
71 93 : if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
72 0 : png_set_expand_gray_1_2_4_to_8(png);
73 : }
74 :
75 93 : if (png_get_valid(png, info, PNG_INFO_tRNS)) {
76 0 : png_set_tRNS_to_alpha(png);
77 : }
78 :
79 93 : if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) {
80 1 : png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
81 : }
82 :
83 93 : if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
84 0 : png_set_gray_to_rgb(png);
85 : }
86 :
87 93 : png_read_update_info(png, info);
88 :
89 93 : row_bytes = png_get_rowbytes(png, info);
90 :
91 93 : if (*height > 0 && row_bytes > SIZE_MAX / (*height)) {
92 0 : colopresso_log(CPRES_LOG_LEVEL_ERROR, "Integer overflow detected: image too large");
93 0 : return CPRES_ERROR_OUT_OF_MEMORY;
94 : }
95 :
96 93 : total_size = row_bytes * (*height);
97 :
98 93 : *rgba_data = (uint8_t *)malloc(total_size);
99 93 : if (!*rgba_data) {
100 0 : return CPRES_ERROR_OUT_OF_MEMORY;
101 : }
102 :
103 93 : row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * (*height));
104 93 : if (!row_pointers) {
105 0 : free(*rgba_data);
106 0 : *rgba_data = NULL;
107 0 : return CPRES_ERROR_OUT_OF_MEMORY;
108 : }
109 :
110 38310 : for (y = 0; y < *height; y++) {
111 38217 : row_pointers[y] = *rgba_data + y * row_bytes;
112 : }
113 :
114 93 : png_read_image(png, row_pointers);
115 :
116 93 : free(row_pointers);
117 :
118 93 : return CPRES_OK;
119 : }
120 :
121 86 : extern cpres_error_t png_decode_from_memory(const uint8_t *png_data, size_t png_size, uint8_t **rgba_data, png_uint_32 *width, png_uint_32 *height) {
122 : png_structp png;
123 : png_infop info;
124 86 : png_memory_reader_t reader = {0};
125 : cpres_error_t result;
126 :
127 86 : if (!png_data || png_size < 8) {
128 0 : return CPRES_ERROR_INVALID_PARAMETER;
129 : }
130 :
131 86 : if (png_sig_cmp(png_data, 0, 8) != 0) {
132 3 : return CPRES_ERROR_INVALID_PNG;
133 : }
134 :
135 83 : png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
136 83 : if (!png) {
137 0 : return CPRES_ERROR_OUT_OF_MEMORY;
138 : }
139 :
140 83 : info = png_create_info_struct(png);
141 83 : if (!info) {
142 0 : png_destroy_read_struct(&png, NULL, NULL);
143 0 : return CPRES_ERROR_OUT_OF_MEMORY;
144 : }
145 :
146 83 : if (setjmp(png_jmpbuf(png))) {
147 0 : png_destroy_read_struct(&png, &info, NULL);
148 0 : return CPRES_ERROR_INVALID_PNG;
149 : }
150 :
151 83 : reader.data = png_data;
152 83 : reader.size = png_size;
153 83 : reader.pos = 0;
154 83 : png_set_read_fn(png, &reader, png_read_from_memory);
155 :
156 83 : result = read_png_common(png, info, rgba_data, width, height);
157 :
158 83 : png_destroy_read_struct(&png, &info, NULL);
159 :
160 83 : return result;
161 : }
162 :
163 : #if COLOPRESSO_WITH_FILE_OPS
164 12 : extern cpres_error_t png_decode_from_file(const char *filename, uint8_t **rgba_data, png_uint_32 *width, png_uint_32 *height) {
165 : FILE *fp;
166 : png_structp png;
167 : png_infop info;
168 : cpres_error_t result;
169 :
170 12 : fp = fopen(filename, "rb");
171 12 : if (!fp) {
172 2 : return CPRES_ERROR_FILE_NOT_FOUND;
173 : }
174 :
175 10 : png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
176 10 : if (!png) {
177 0 : fclose(fp);
178 0 : return CPRES_ERROR_OUT_OF_MEMORY;
179 : }
180 :
181 10 : info = png_create_info_struct(png);
182 10 : if (!info) {
183 0 : png_destroy_read_struct(&png, NULL, NULL);
184 0 : fclose(fp);
185 0 : return CPRES_ERROR_OUT_OF_MEMORY;
186 : }
187 :
188 10 : if (setjmp(png_jmpbuf(png))) {
189 0 : png_destroy_read_struct(&png, &info, NULL);
190 0 : fclose(fp);
191 0 : return CPRES_ERROR_INVALID_PNG;
192 : }
193 :
194 10 : png_init_io(png, fp);
195 :
196 10 : result = read_png_common(png, info, rgba_data, width, height);
197 :
198 10 : png_destroy_read_struct(&png, &info, NULL);
199 10 : fclose(fp);
200 :
201 10 : return result;
202 : }
203 :
204 : #endif /* COLOPRESSO_WITH_FILE_OPS */
|