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