loaders: Implement Write() instead of Save()
[gfxprim.git] / libs / loaders / GP_PNG.c
blob924eae3780c43ee7d827478a4d542b70d4e40a1b
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim 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.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim 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 gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 PNG image support using libpng.
29 #include <stdint.h>
30 #include <inttypes.h>
32 #include <errno.h>
33 #include <string.h>
35 #include "../../config.h"
36 #include "core/GP_ByteOrder.h"
37 #include "core/GP_Debug.h"
39 #include "GP_PNG.h"
41 #ifdef HAVE_LIBPNG
43 #include <png.h>
45 #include "core/GP_BitSwap.h"
47 int GP_MatchPNG(const void *buf)
49 return !png_sig_cmp(buf, 0, 8);
52 static const char *interlace_type_name(int interlace)
54 switch (interlace) {
55 case PNG_INTERLACE_NONE:
56 return "none";
57 case PNG_INTERLACE_ADAM7:
58 return "adam7";
59 default:
60 return "unknown";
64 static void read_data(png_structp png_ptr, png_bytep data, png_size_t len)
66 GP_IO *io = png_get_io_ptr(png_ptr);
68 if (GP_IOFill(io, data, len))
69 png_error(png_ptr, "Read Error");
72 GP_Context *GP_ReadPNG(GP_IO *io, GP_ProgressCallback *callback)
74 png_structp png;
75 png_infop png_info = NULL;
76 png_uint_32 w, h;
77 int depth, color_type, interlace_type;
78 GP_PixelType pixel_type = GP_PIXEL_UNKNOWN;
79 GP_Context *res;
80 int err, passes = 1;
81 double gamma;
83 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
85 if (png == NULL) {
86 GP_DEBUG(1, "Failed to allocate PNG read buffer");
87 err = ENOMEM;
88 goto err1;
91 png_info = png_create_info_struct(png);
93 if (png_info == NULL) {
94 GP_DEBUG(1, "Failed to allocate PNG info buffer");
95 err = ENOMEM;
96 goto err2;
99 if (setjmp(png_jmpbuf(png))) {
100 GP_DEBUG(1, "Failed to read PNG file :(");
101 //TODO: should we get better error description from libpng?
102 err = EIO;
103 goto err2;
106 png_set_read_fn(png, io, read_data);
107 png_set_sig_bytes(png, 0);
108 png_read_info(png, png_info);
110 png_get_IHDR(png, png_info, &w, &h, &depth,
111 &color_type, &interlace_type, NULL, NULL);
113 png_get_gAMA(png, png_info, &gamma);
115 GP_DEBUG(2, "Interlace=%s%s %s PNG%s size %ux%u depth %i gamma %.2lf",
116 interlace_type_name(interlace_type),
117 color_type & PNG_COLOR_MASK_PALETTE ? " pallete" : "",
118 color_type & PNG_COLOR_MASK_COLOR ? "color" : "gray",
119 color_type & PNG_COLOR_MASK_ALPHA ? " with alpha channel" : "",
120 (unsigned int)w, (unsigned int)h, depth, gamma);
122 if (interlace_type == PNG_INTERLACE_ADAM7)
123 passes = png_set_interlace_handling(png);
125 switch (color_type) {
126 case PNG_COLOR_TYPE_GRAY:
127 switch (depth) {
128 case 1:
129 pixel_type = GP_PIXEL_G1;
130 break;
131 case 2:
132 pixel_type = GP_PIXEL_G2;
133 break;
134 case 4:
135 pixel_type = GP_PIXEL_G4;
136 break;
137 case 8:
138 pixel_type = GP_PIXEL_G8;
139 break;
140 #ifdef GP_PIXEL_G16
141 case 16:
142 pixel_type = GP_PIXEL_G16;
143 break;
144 #endif
146 break;
147 case PNG_COLOR_TYPE_GRAY | PNG_COLOR_MASK_ALPHA:
148 switch (depth) {
149 case 8:
150 pixel_type = GP_PIXEL_GA88;
151 break;
153 break;
154 case PNG_COLOR_TYPE_RGB:
156 png_set_bgr(png);
158 switch (depth) {
159 case 8:
160 pixel_type = GP_PIXEL_RGB888;
161 break;
163 break;
164 case PNG_COLOR_TYPE_RGB | PNG_COLOR_MASK_ALPHA:
166 png_set_bgr(png);
167 png_set_swap_alpha(png);
169 switch (depth) {
170 case 8:
171 pixel_type = GP_PIXEL_RGBA8888;
172 break;
174 break;
175 case PNG_COLOR_TYPE_PALETTE:
176 /* Grayscale with BPP < 8 is usually saved as palette */
177 if (png_get_channels(png, png_info) == 1) {
178 switch (depth) {
179 case 1:
180 png_set_packswap(png);
181 pixel_type = GP_PIXEL_G1;
182 break;
186 /* Convert everything else to RGB888 */
187 //TODO: add palette matching to G2 G4 and G8
188 png_set_palette_to_rgb(png);
189 png_set_bgr(png);
191 png_read_update_info(png, png_info);
193 png_get_IHDR(png, png_info, &w, &h, &depth,
194 &color_type, NULL, NULL, NULL);
196 if (color_type & PNG_COLOR_MASK_ALPHA) {
197 pixel_type = GP_PIXEL_RGBA8888;
198 png_set_swap_alpha(png);
199 } else {
200 pixel_type = GP_PIXEL_RGB888;
202 break;
205 if (pixel_type == GP_PIXEL_UNKNOWN) {
206 GP_DEBUG(1, "Unimplemented png format");
207 err = ENOSYS;
208 goto err2;
211 res = GP_ContextAlloc(w, h, pixel_type);
213 if (res == NULL) {
214 err = ENOMEM;
215 goto err2;
218 if (color_type == PNG_COLOR_TYPE_GRAY && depth < 8)
219 png_set_packswap(png);
221 #if __BYTE_ORDER == __LITTLE_ENDIAN
223 * PNG stores 16 bit values in big endian, turn
224 * on conversion to little endian if needed.
226 if (depth > 8) {
227 GP_DEBUG(1, "Enabling byte swap for bpp = %u", depth);
228 png_set_swap(png);
230 #endif
232 uint32_t y;
233 int p;
236 * Do the actuall reading.
238 * The passes are needed for adam7 interlacing.
240 for (p = 0; p < passes; p++) {
241 for (y = 0; y < h; y++) {
242 png_bytep row = GP_PIXEL_ADDR(res, 0, y);
243 png_read_row(png, row, NULL);
245 if (GP_ProgressCallbackReport(callback, y + h * p, h * passes, w)) {
246 GP_DEBUG(1, "Operation aborted");
247 err = ECANCELED;
248 goto err3;
253 png_destroy_read_struct(&png, &png_info, NULL);
255 GP_ProgressCallbackDone(callback);
257 return res;
258 err3:
259 GP_ContextFree(res);
260 err2:
261 png_destroy_read_struct(&png, png_info ? &png_info : NULL, NULL);
262 err1:
263 errno = err;
264 return NULL;
267 static void load_meta_data(png_structp png, png_infop png_info, GP_MetaData *data)
269 double gamma;
271 if (png_get_gAMA(png, png_info, &gamma))
272 GP_MetaDataCreateInt(data, "gamma", gamma * 100000);
274 png_uint_32 res_x, res_y;
275 int unit;
277 if (png_get_pHYs(png, png_info, &res_x, &res_y, &unit)) {
278 GP_MetaDataCreateInt(data, "res_x", res_x);
279 GP_MetaDataCreateInt(data, "res_y", res_y);
281 const char *unit_name;
283 if (unit == PNG_RESOLUTION_METER)
284 unit_name = "meter";
285 else
286 unit_name = "unknown";
288 GP_MetaDataCreateString(data, "res_unit", unit_name, 0, 0);
291 png_timep mod_time;
293 if (png_get_tIME(png, png_info, &mod_time)) {
294 GP_MetaDataCreateInt(data, "mod_sec", mod_time->second);
295 GP_MetaDataCreateInt(data, "mod_min", mod_time->minute);
296 GP_MetaDataCreateInt(data, "mod_hour", mod_time->hour);
297 GP_MetaDataCreateInt(data, "mod_day", mod_time->day);
298 GP_MetaDataCreateInt(data, "mod_mon", mod_time->month);
299 GP_MetaDataCreateInt(data, "mod_year", mod_time->year);
302 double width, height;
304 if (png_get_sCAL(png, png_info, &unit, &width, &height)) {
305 GP_MetaDataCreateDouble(data, "width", width);
306 GP_MetaDataCreateDouble(data, "height", height);
307 GP_MetaDataCreateInt(data, "unit", unit);
310 png_textp text_ptr;
311 int text_cnt;
313 if (png_get_text(png, png_info, &text_ptr, &text_cnt)) {
314 int i;
316 for (i = 0; i < text_cnt; i++) {
318 if (text_ptr[i].compression != PNG_TEXT_COMPRESSION_NONE)
319 continue;
321 char buf[GP_META_RECORD_ID_MAX];
322 snprintf(buf, GP_META_RECORD_ID_MAX, "text:%s", text_ptr[i].key);
323 GP_MetaDataCreateString(data, buf, text_ptr[i].text, 0, 1);
328 int GP_ReadPNGMetaData(GP_IO *io, GP_MetaData *data)
330 png_structp png;
331 png_infop png_info = NULL;
332 int err;
334 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
336 if (png == NULL) {
337 GP_DEBUG(1, "Failed to allocate PNG read buffer");
338 err = ENOMEM;
339 goto err1;
342 png_info = png_create_info_struct(png);
344 if (png_info == NULL) {
345 GP_DEBUG(1, "Failed to allocate PNG info buffer");
346 err = ENOMEM;
347 goto err2;
350 if (setjmp(png_jmpbuf(png))) {
351 GP_DEBUG(1, "Failed to read PNG file :(");
352 //TODO: should we get better error description from libpng?
353 err = EIO;
354 goto err2;
357 png_set_read_fn(png, io, read_data);
358 png_set_sig_bytes(png, 0);
359 png_read_info(png, png_info);
361 load_meta_data(png, png_info, data);
363 return 0;
364 err2:
365 png_destroy_read_struct(&png, png_info ? &png_info : NULL, NULL);
366 err1:
367 errno = err;
368 return 1;
371 int GP_LoadPNGMetaData(const char *src_path, GP_MetaData *data)
373 GP_IO *io;
374 int ret, err;
376 io = GP_IOFile(src_path, GP_IO_RDONLY);
377 if (!io)
378 return 1;
380 ret = GP_ReadPNGMetaData(io, data);
382 err = errno;
383 GP_IOClose(io);
384 errno = err;
386 return ret;
389 static GP_PixelType save_ptypes[] = {
390 GP_PIXEL_BGR888,
391 GP_PIXEL_RGB888,
392 GP_PIXEL_G1,
393 GP_PIXEL_G2,
394 GP_PIXEL_G4,
395 GP_PIXEL_G8,
396 #ifdef GP_PIXEL_G16
397 GP_PIXEL_G16,
398 #endif
399 GP_PIXEL_RGBA8888,
400 GP_PIXEL_UNKNOWN,
404 * Maps gfxprim Pixel Type to the PNG format
406 static int prepare_png_header(const GP_Context *src, png_structp png,
407 png_infop png_info, int *bit_endian_flag)
409 int bit_depth, color_type;
411 switch (src->pixel_type) {
412 case GP_PIXEL_BGR888:
413 case GP_PIXEL_RGB888:
414 bit_depth = 8;
415 color_type = PNG_COLOR_TYPE_RGB;
416 break;
417 case GP_PIXEL_G1:
418 bit_depth = 1;
419 color_type = PNG_COLOR_TYPE_GRAY;
420 break;
421 case GP_PIXEL_G2:
422 bit_depth = 2;
423 color_type = PNG_COLOR_TYPE_GRAY;
424 break;
425 case GP_PIXEL_G4:
426 bit_depth = 4;
427 color_type = PNG_COLOR_TYPE_GRAY;
428 break;
429 case GP_PIXEL_G8:
430 bit_depth = 8;
431 color_type = PNG_COLOR_TYPE_GRAY;
432 break;
433 #ifdef GP_PIXEL_G16
434 case GP_PIXEL_G16:
435 bit_depth = 16;
436 color_type = PNG_COLOR_TYPE_GRAY;
437 break;
438 #endif
439 case GP_PIXEL_RGBA8888:
440 bit_depth = 8;
441 color_type = PNG_COLOR_TYPE_RGB | PNG_COLOR_MASK_ALPHA;
442 break;
443 default:
444 return 1;
445 break;
448 /* If pointers weren't passed, just return it is okay */
449 if (png == NULL || png_info == NULL)
450 return 0;
452 png_set_IHDR(png, png_info, src->w, src->h, bit_depth, color_type,
453 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
454 PNG_FILTER_TYPE_DEFAULT);
456 /* start the actuall writing */
457 png_write_info(png, png_info);
459 //png_set_packing(png);
461 /* prepare for format conversions */
462 switch (src->pixel_type) {
463 case GP_PIXEL_RGB888:
464 png_set_bgr(png);
465 break;
466 case GP_PIXEL_RGBA8888:
467 png_set_bgr(png);
468 png_set_swap_alpha(png);
469 break;
470 case GP_PIXEL_G1:
471 case GP_PIXEL_G2:
472 case GP_PIXEL_G4:
473 *bit_endian_flag = !src->bit_endian;
474 break;
475 default:
476 break;
479 #if __BYTE_ORDER == __LITTLE_ENDIAN
481 * PNG stores 16 bit values in big endian, turn
482 * on conversion to little endian if needed.
484 if (bit_depth > 8) {
485 GP_DEBUG(1, "Enabling byte swap for bpp = %u", bit_depth);
486 png_set_swap(png);
488 #endif
490 return 0;
493 static int write_png_data(const GP_Context *src, png_structp png,
494 GP_ProgressCallback *callback, int bit_endian_flag)
496 /* Look if we need to swap data when writing */
497 if (bit_endian_flag) {
498 switch (src->pixel_type) {
499 case GP_PIXEL_G1:
500 case GP_PIXEL_G2:
501 case GP_PIXEL_G4:
502 png_set_packswap(png);
503 break;
504 default:
505 return ENOSYS;
506 break;
510 unsigned int y;
512 for (y = 0; y < src->h; y++) {
513 png_bytep row = GP_PIXEL_ADDR(src, 0, y);
514 png_write_row(png, row);
516 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
517 GP_DEBUG(1, "Operation aborted");
518 return ECANCELED;
522 return 0;
525 static void write_data(png_structp png_ptr, png_bytep data, png_size_t len)
527 GP_IO *io = png_get_io_ptr(png_ptr);
529 if (GP_IOWrite(io, data, len) != (ssize_t)len)
530 png_error(png_ptr, "Write Error");
533 static void flush_data(png_structp png_ptr)
535 (void)png_ptr;
538 int GP_WritePNG(const GP_Context *src, GP_IO *io,
539 GP_ProgressCallback *callback)
541 png_structp png;
542 png_infop png_info = NULL;
543 int err;
545 GP_DEBUG(1, "Writing PNG Image to I/O (%p)", io);
547 if (prepare_png_header(src, NULL, NULL, NULL)) {
548 GP_DEBUG(1, "Can't save png with %s pixel type",
549 GP_PixelTypeName(src->pixel_type));
550 errno = ENOSYS;
551 return 1;
554 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
556 if (png == NULL) {
557 GP_DEBUG(1, "Failed to allocate PNG write buffer");
558 errno = ENOMEM;
559 return 1;
562 png_info = png_create_info_struct(png);
564 if (png_info == NULL) {
565 GP_DEBUG(1, "Failed to allocate PNG info buffer");
566 err = ENOMEM;
567 goto err;
570 if (setjmp(png_jmpbuf(png))) {
571 GP_DEBUG(1, "Failed to write PNG file :(");
572 //TODO: should we get better error description from libpng?
573 err = EIO;
574 goto err;
577 png_set_write_fn(png, io, write_data, flush_data);
579 int bit_endian_flag = 0;
580 /* Fill png header and prepare for data */
581 prepare_png_header(src, png, png_info, &bit_endian_flag);
583 /* Write bitmap buffer */
584 if ((err = write_png_data(src, png, callback, bit_endian_flag)))
585 goto err;
587 png_write_end(png, png_info);
588 png_destroy_write_struct(&png, &png_info);
590 GP_ProgressCallbackDone(callback);
591 return 0;
592 err:
593 png_destroy_write_struct(&png, png_info == NULL ? NULL : &png_info);
594 errno = err;
595 return 1;
598 #else
600 int GP_MatchPNG(const void GP_UNUSED(*buf))
602 errno = ENOSYS;
603 return -1;
606 GP_Context *GP_ReadPNG(GP_IO GP_UNUSED(*io),
607 GP_ProgressCallback GP_UNUSED(*callback))
609 errno = ENOSYS;
610 return NULL;
613 int GP_ReadPNGMetaData(GP_IO GP_UNUSED(*io), GP_MetaData GP_UNUSED(*data))
615 errno = ENOSYS;
616 return 1;
619 int GP_LoadPNGMetaData(const char GP_UNUSED(*src_path), GP_MetaData GP_UNUSED(*data))
621 errno = ENOSYS;
622 return 1;
625 int GP_WritePNG(const GP_Context *src, GP_IO GP_UNUSED(*io),
626 GP_ProgressCallback *callback)
628 errno = ENOSYS;
629 return 1;
632 #endif /* HAVE_LIBPNG */
634 GP_Context *GP_LoadPNG(const char *src_path, GP_ProgressCallback *callback)
636 return GP_LoaderLoadImage(&GP_PNG, src_path, callback);
639 int GP_SavePNG(const GP_Context *src, const char *dst_path,
640 GP_ProgressCallback *callback)
642 return GP_LoaderSaveImage(&GP_PNG, src, dst_path, callback);
645 GP_Loader GP_PNG = {
646 #ifdef HAVE_LIBPNG
647 .Read = GP_ReadPNG,
648 .Write = GP_WritePNG,
649 .save_ptypes = save_ptypes,
650 #endif
651 .Match = GP_MatchPNG,
653 .fmt_name = "Portable Network Graphics",
654 .extensions = {"png", NULL},