1/* GDK - The GIMP Drawing Kit
2 * Copyright (C) 2021 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gdkpngprivate.h"
21
22#include "gdkintl.h"
23#include "gdkmemoryformatprivate.h"
24#include "gdkmemorytextureprivate.h"
25#include "gdkprofilerprivate.h"
26#include "gdktexture.h"
27#include "gdktextureprivate.h"
28#include "gsk/gl/fp16private.h"
29#include <png.h>
30#include <stdio.h>
31
32/* The main difference between the png load/save code here and
33 * gdk-pixbuf is that we can support loading 16-bit data in the
34 * future, and we can extract gamma and colorspace information
35 * to produce linear, color-corrected data.
36 */
37
38/* {{{ Callbacks */
39
40/* No sigsetjmp on Windows */
41#ifndef HAVE_SIGSETJMP
42#define sigjmp_buf jmp_buf
43#define sigsetjmp(jb, x) setjmp(jb)
44#define siglongjmp longjmp
45#endif
46
47typedef struct
48{
49 guchar *data;
50 gsize size;
51 gsize position;
52} png_io;
53
54
55static void
56png_read_func (png_structp png,
57 png_bytep data,
58 png_size_t size)
59{
60 png_io *io;
61
62 io = png_get_io_ptr (png_ptr: png);
63
64 if (io->position + size > io->size)
65 png_error (png_ptr: png, error_message: "Read past EOF");
66
67 memcpy (dest: data, src: io->data + io->position, n: size);
68 io->position += size;
69}
70
71static void
72png_write_func (png_structp png,
73 png_bytep data,
74 png_size_t size)
75{
76 png_io *io;
77
78 io = png_get_io_ptr (png_ptr: png);
79
80 if (io->position > io->size ||
81 io->size - io->position < size)
82 {
83 io->size = io->position + size;
84 io->data = g_realloc (mem: io->data, n_bytes: io->size);
85 }
86
87 memcpy (dest: io->data + io->position, src: data, n: size);
88 io->position += size;
89}
90
91static void
92png_flush_func (png_structp png)
93{
94}
95
96static png_voidp
97png_malloc_callback (png_structp o,
98 png_size_t size)
99{
100 return g_try_malloc (n_bytes: size);
101}
102
103static void
104png_free_callback (png_structp o,
105 png_voidp x)
106{
107 g_free (mem: x);
108}
109
110G_GNUC_NORETURN static void
111png_simple_error_callback (png_structp png,
112 png_const_charp error_msg)
113{
114 GError **error = png_get_error_ptr (png_ptr: png);
115
116 if (error && !*error)
117 g_set_error (err: error,
118 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
119 _("Error reading png (%s)"), error_msg);
120
121 longjmp (png_jmpbuf (png), val: 1);
122}
123
124static void
125png_simple_warning_callback (png_structp png,
126 png_const_charp error_msg)
127{
128}
129
130/* }}} */
131/* {{{ Public API */
132
133GdkTexture *
134gdk_load_png (GBytes *bytes,
135 GError **error)
136{
137 png_io io;
138 png_struct *png = NULL;
139 png_info *info;
140 guint width, height;
141 int depth, color_type;
142 int interlace, stride;
143 GdkMemoryFormat format;
144 guchar *buffer = NULL;
145 guchar **row_pointers = NULL;
146 GBytes *out_bytes;
147 GdkTexture *texture;
148 int bpp;
149 G_GNUC_UNUSED gint64 before = GDK_PROFILER_CURRENT_TIME;
150
151 io.data = (guchar *)g_bytes_get_data (bytes, size: &io.size);
152 io.position = 0;
153
154 png = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
155 error_ptr: error,
156 error_fn: png_simple_error_callback,
157 warn_fn: png_simple_warning_callback,
158 NULL,
159 malloc_fn: png_malloc_callback,
160 free_fn: png_free_callback);
161 if (png == NULL)
162 g_error ("Out of memory");
163
164 info = png_create_info_struct (png_ptr: png);
165 if (info == NULL)
166 g_error ("Out of memory");
167
168 png_set_read_fn (png_ptr: png, io_ptr: &io, read_data_fn: png_read_func);
169
170 if (sigsetjmp (png_jmpbuf (png), 1))
171 {
172 g_free (mem: buffer);
173 g_free (mem: row_pointers);
174 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
175 return NULL;
176 }
177
178 png_read_info (png_ptr: png, info_ptr: info);
179
180 png_get_IHDR (png_ptr: png, info_ptr: info,
181 width: &width, height: &height, bit_depth: &depth,
182 color_type: &color_type, interlace_method: &interlace, NULL, NULL);
183
184 if (color_type == PNG_COLOR_TYPE_PALETTE)
185 png_set_palette_to_rgb (png_ptr: png);
186
187 if (color_type == PNG_COLOR_TYPE_GRAY)
188 png_set_expand_gray_1_2_4_to_8 (png_ptr: png);
189
190 if (png_get_valid (png_ptr: png, info_ptr: info, PNG_INFO_tRNS))
191 png_set_tRNS_to_alpha (png_ptr: png);
192
193 if (depth < 8)
194 png_set_packing (png_ptr: png);
195
196 if (color_type == PNG_COLOR_TYPE_GRAY ||
197 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
198 png_set_gray_to_rgb (png_ptr: png);
199
200 if (interlace != PNG_INTERLACE_NONE)
201 png_set_interlace_handling (png_ptr: png);
202
203#if G_BYTE_ORDER == G_LITTLE_ENDIAN
204 png_set_swap (png_ptr: png);
205#endif
206
207 png_read_update_info (png_ptr: png, info_ptr: info);
208 png_get_IHDR (png_ptr: png, info_ptr: info,
209 width: &width, height: &height, bit_depth: &depth,
210 color_type: &color_type, interlace_method: &interlace, NULL, NULL);
211 if (depth != 8 && depth != 16)
212 {
213 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
214 g_set_error (err: error,
215 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
216 _("Unsupported depth %u in png image"), depth);
217 return NULL;
218 }
219
220 switch (color_type)
221 {
222 case PNG_COLOR_TYPE_RGB_ALPHA:
223 if (depth == 8)
224 {
225 format = GDK_MEMORY_R8G8B8A8;
226 }
227 else
228 {
229 format = GDK_MEMORY_R16G16B16A16;
230 }
231 break;
232 case PNG_COLOR_TYPE_RGB:
233 if (depth == 8)
234 {
235 format = GDK_MEMORY_R8G8B8;
236 }
237 else if (depth == 16)
238 {
239 format = GDK_MEMORY_R16G16B16;
240 }
241 break;
242 default:
243 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
244 g_set_error (err: error,
245 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
246 _("Unsupported color type %u in png image"), color_type);
247 return NULL;
248 }
249
250 bpp = gdk_memory_format_bytes_per_pixel (format);
251 stride = width * bpp;
252 if (stride % 8)
253 stride += 8 - stride % 8;
254
255 buffer = g_try_malloc_n (n_blocks: height, n_block_bytes: stride);
256 row_pointers = g_try_malloc_n (n_blocks: height, n_block_bytes: sizeof (char *));
257
258 if (!buffer || !row_pointers)
259 {
260 g_free (mem: buffer);
261 g_free (mem: row_pointers);
262 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
263 g_set_error (err: error,
264 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_TOO_LARGE,
265 _("Not enough memory for image size %ux%u"), width, height);
266 return NULL;
267 }
268
269 for (int i = 0; i < height; i++)
270 row_pointers[i] = &buffer[i * stride];
271
272 png_read_image (png_ptr: png, image: row_pointers);
273 png_read_end (png_ptr: png, info_ptr: info);
274
275 out_bytes = g_bytes_new_take (data: buffer, size: height * stride);
276 texture = gdk_memory_texture_new (width, height, format, bytes: out_bytes, stride);
277 g_bytes_unref (bytes: out_bytes);
278
279 g_free (mem: row_pointers);
280 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
281
282 if (GDK_PROFILER_IS_RUNNING)
283 {
284 gint64 end = GDK_PROFILER_CURRENT_TIME;
285 if (end - before > 500000)
286 gdk_profiler_add_mark (before, end - before, "png load", NULL);
287 }
288
289 return texture;
290}
291
292GBytes *
293gdk_save_png (GdkTexture *texture)
294{
295 png_struct *png = NULL;
296 png_info *info;
297 png_io io = { NULL, 0, 0 };
298 int width, height;
299 gsize stride;
300 const guchar *data;
301 int y;
302 GdkMemoryTexture *memtex;
303 GdkMemoryFormat format;
304 int png_format;
305 int depth;
306
307 width = gdk_texture_get_width (texture);
308 height = gdk_texture_get_height (texture);
309 format = gdk_texture_get_format (self: texture);
310
311 switch (format)
312 {
313 case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
314 case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
315 case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
316 case GDK_MEMORY_B8G8R8A8:
317 case GDK_MEMORY_A8R8G8B8:
318 case GDK_MEMORY_R8G8B8A8:
319 case GDK_MEMORY_A8B8G8R8:
320 format = GDK_MEMORY_R8G8B8A8;
321 png_format = PNG_COLOR_TYPE_RGB_ALPHA;
322 depth = 8;
323 break;
324
325 case GDK_MEMORY_R8G8B8:
326 case GDK_MEMORY_B8G8R8:
327 format = GDK_MEMORY_R8G8B8;
328 png_format = PNG_COLOR_TYPE_RGB;
329 depth = 8;
330 break;
331
332 case GDK_MEMORY_R16G16B16A16:
333 case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
334 case GDK_MEMORY_R16G16B16A16_FLOAT:
335 case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
336 case GDK_MEMORY_R32G32B32A32_FLOAT:
337 case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
338 format = GDK_MEMORY_R16G16B16A16;
339 png_format = PNG_COLOR_TYPE_RGB_ALPHA;
340 depth = 16;
341 break;
342
343 case GDK_MEMORY_R16G16B16:
344 case GDK_MEMORY_R16G16B16_FLOAT:
345 case GDK_MEMORY_R32G32B32_FLOAT:
346 format = GDK_MEMORY_R16G16B16;
347 png_format = PNG_COLOR_TYPE_RGB;
348 depth = 16;
349 break;
350
351 case GDK_MEMORY_N_FORMATS:
352 default:
353 g_assert_not_reached ();
354 }
355
356 png = png_create_write_struct_2 (PNG_LIBPNG_VER_STRING, NULL,
357 error_fn: png_simple_error_callback,
358 warn_fn: png_simple_warning_callback,
359 NULL,
360 malloc_fn: png_malloc_callback,
361 free_fn: png_free_callback);
362 if (!png)
363 return NULL;
364
365 info = png_create_info_struct (png_ptr: png);
366 if (!info)
367 {
368 png_destroy_read_struct (png_ptr_ptr: &png, NULL, NULL);
369 return NULL;
370 }
371
372 memtex = gdk_memory_texture_from_texture (texture, format);
373
374 if (sigsetjmp (png_jmpbuf (png), 1))
375 {
376 g_object_unref (object: memtex);
377 g_free (mem: io.data);
378 png_destroy_read_struct (png_ptr_ptr: &png, info_ptr_ptr: &info, NULL);
379 return NULL;
380 }
381
382 png_set_write_fn (png_ptr: png, io_ptr: &io, write_data_fn: png_write_func, output_flush_fn: png_flush_func);
383
384 png_set_IHDR (png_ptr: png, info_ptr: info, width, height, bit_depth: depth,
385 color_type: png_format,
386 PNG_INTERLACE_NONE,
387 PNG_COMPRESSION_TYPE_DEFAULT,
388 PNG_FILTER_TYPE_DEFAULT);
389
390 png_write_info (png_ptr: png, info_ptr: info);
391
392#if G_BYTE_ORDER == G_LITTLE_ENDIAN
393 png_set_swap (png_ptr: png);
394#endif
395
396 data = gdk_memory_texture_get_data (self: memtex);
397 stride = gdk_memory_texture_get_stride (self: memtex);
398 for (y = 0; y < height; y++)
399 png_write_row (png_ptr: png, row: data + y * stride);
400
401 png_write_end (png_ptr: png, info_ptr: info);
402
403 png_destroy_write_struct (png_ptr_ptr: &png, info_ptr_ptr: &info);
404
405 g_object_unref (object: memtex);
406
407 return g_bytes_new_take (data: io.data, size: io.size);
408}
409
410/* }}} */
411
412/* vim:set foldmethod=marker expandtab: */
413

source code of gtk/gdk/loaders/gdkpng.c