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 | |
47 | typedef struct |
48 | { |
49 | guchar *data; |
50 | gsize size; |
51 | gsize position; |
52 | } png_io; |
53 | |
54 | |
55 | static void |
56 | png_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 | |
71 | static void |
72 | png_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 | |
91 | static void |
92 | png_flush_func (png_structp png) |
93 | { |
94 | } |
95 | |
96 | static png_voidp |
97 | png_malloc_callback (png_structp o, |
98 | png_size_t size) |
99 | { |
100 | return g_try_malloc (n_bytes: size); |
101 | } |
102 | |
103 | static void |
104 | png_free_callback (png_structp o, |
105 | png_voidp x) |
106 | { |
107 | g_free (mem: x); |
108 | } |
109 | |
110 | G_GNUC_NORETURN static void |
111 | png_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 | |
124 | static void |
125 | png_simple_warning_callback (png_structp png, |
126 | png_const_charp error_msg) |
127 | { |
128 | } |
129 | |
130 | /* }}} */ |
131 | /* {{{ Public API */ |
132 | |
133 | GdkTexture * |
134 | gdk_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 | |
292 | GBytes * |
293 | gdk_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 | |