loaders: PNG: add experimental support for metadata.
[gfxprim.git] / libs / loaders / GP_PNG.c
blob2cef50dbad9344db64b7a93114c81ed2b77cf2e1
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-2012 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_Debug.h"
38 #include "GP_PNG.h"
40 #ifdef HAVE_LIBPNG
42 #include <png.h>
44 #include "core/GP_BitSwap.h"
46 int GP_OpenPNG(const char *src_path, FILE **f)
48 uint8_t sig[8];
49 int err;
51 *f = fopen(src_path, "r");
53 if (*f == NULL) {
54 err = errno;
55 GP_DEBUG(1, "Failed to open '%s' : %s",
56 src_path, strerror(errno));
57 goto err1;
60 if (fread(sig, 1, 8, *f) <= 0) {
61 err = errno;
62 GP_DEBUG(1, "Failed to read '%s' : %s",
63 src_path, strerror(errno));
64 goto err2;
67 if (png_sig_cmp(sig, 0, 8)) {
68 GP_DEBUG(1, "Invalid file header, '%s' not a PNG image?",
69 src_path);
70 err = EILSEQ;
71 goto err2;
74 GP_DEBUG(1, "Found PNG signature in '%s'", src_path);
76 return 0;
77 err2:
78 fclose(*f);
79 err1:
80 errno = err;
81 return 1;
84 static const char *interlace_type_name(int interlace)
86 switch (interlace) {
87 case PNG_INTERLACE_NONE:
88 return "none";
89 case PNG_INTERLACE_ADAM7:
90 return "adam7";
91 default:
92 return "unknown";
96 GP_Context *GP_ReadPNG(FILE *f, GP_ProgressCallback *callback)
98 png_structp png;
99 png_infop png_info = NULL;
100 png_uint_32 w, h;
101 int depth, color_type, interlace_type;
102 GP_PixelType pixel_type = GP_PIXEL_UNKNOWN;
103 GP_Context *res;
104 int err;
106 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
108 if (png == NULL) {
109 GP_DEBUG(1, "Failed to allocate PNG read buffer");
110 err = ENOMEM;
111 goto err1;
114 png_info = png_create_info_struct(png);
116 if (png_info == NULL) {
117 GP_DEBUG(1, "Failed to allocate PNG info buffer");
118 err = ENOMEM;
119 goto err2;
122 if (setjmp(png_jmpbuf(png))) {
123 GP_DEBUG(1, "Failed to read PNG file :(");
124 //TODO: should we get better error description from libpng?
125 err = EIO;
126 goto err2;
129 png_init_io(png, f);
130 png_set_sig_bytes(png, 8);
131 png_read_info(png, png_info);
133 png_get_IHDR(png, png_info, &w, &h, &depth,
134 &color_type, &interlace_type, NULL, NULL);
136 GP_DEBUG(2, "Have %s%s interlace %s PNG%s size %ux%u depth %i",
137 interlace_type_name(interlace_type),
138 color_type & PNG_COLOR_MASK_PALETTE ? " pallete " : "",
139 color_type & PNG_COLOR_MASK_COLOR ? "color" : "gray",
140 color_type & PNG_COLOR_MASK_ALPHA ? " with alpha channel" : "",
141 (unsigned int)w, (unsigned int)h, depth);
143 switch (color_type) {
144 case PNG_COLOR_TYPE_GRAY:
145 switch (depth) {
146 case 1:
147 pixel_type = GP_PIXEL_G1;
148 break;
149 case 2:
150 pixel_type = GP_PIXEL_G2;
151 break;
152 case 4:
153 pixel_type = GP_PIXEL_G4;
154 break;
155 case 8:
156 pixel_type = GP_PIXEL_G8;
157 break;
159 break;
160 case PNG_COLOR_TYPE_RGB:
162 png_set_bgr(png);
164 switch (depth) {
165 case 8:
166 pixel_type = GP_PIXEL_RGB888;
167 break;
169 break;
170 case PNG_COLOR_TYPE_PALETTE:
171 /* Grayscale with BPP < 8 is usually saved as palette */
172 if (png_get_channels(png, png_info) == 1) {
173 switch (depth) {
174 case 1:
175 png_set_packswap(png);
176 pixel_type = GP_PIXEL_G1;
177 break;
181 /* Convert everything else to RGB888 */
182 //TODO: add palette matching to G2 G4 and G8
183 png_set_palette_to_rgb(png);
184 png_set_bgr(png);
185 pixel_type = GP_PIXEL_RGB888;
186 break;
189 if (pixel_type == GP_PIXEL_UNKNOWN) {
190 GP_DEBUG(1, "Unimplemented png format");
191 err = ENOSYS;
192 goto err2;
195 res = GP_ContextAlloc(w, h, pixel_type);
197 if (res == NULL) {
198 err = ENOMEM;
199 goto err2;
202 if (color_type == PNG_COLOR_TYPE_GRAY)
203 png_set_packswap(png);
205 uint32_t y;
207 /* start the actuall reading */
208 for (y = 0; y < h; y++) {
209 png_bytep row = GP_PIXEL_ADDR(res, 0, y);
210 png_read_rows(png, &row, NULL, 1);
212 if (GP_ProgressCallbackReport(callback, y, h, w)) {
213 GP_DEBUG(1, "Operation aborted");
214 err = ECANCELED;
215 goto err3;
220 png_destroy_read_struct(&png, &png_info, NULL);
222 GP_ProgressCallbackDone(callback);
224 return res;
225 err3:
226 GP_ContextFree(res);
227 err2:
228 png_destroy_read_struct(&png, png_info ? &png_info : NULL, NULL);
229 err1:
230 errno = err;
231 return NULL;
234 GP_Context *GP_LoadPNG(const char *src_path, GP_ProgressCallback *callback)
236 FILE *f;
237 GP_Context *res;
239 if (GP_OpenPNG(src_path, &f))
240 return NULL;
242 res = GP_ReadPNG(f, callback);
244 fclose(f);
246 return res;
249 static void load_meta_data(png_structp png, png_infop png_info, GP_MetaData *data)
251 double gamma;
253 if (png_get_gAMA(png, png_info, &gamma))
254 GP_MetaDataCreateInt(data, "gamma", gamma * 100000);
256 png_uint_32 res_x, res_y;
257 int unit;
259 if (png_get_pHYs(png, png_info, &res_x, &res_y, &unit)) {
260 GP_MetaDataCreateInt(data, "res_x", res_x);
261 GP_MetaDataCreateInt(data, "res_y", res_y);
263 const char *unit_name;
265 if (unit == PNG_RESOLUTION_METER)
266 unit_name = "meter";
267 else
268 unit_name = "unknown";
270 GP_MetaDataCreateString(data, "res_unit", unit_name, 0);
273 double width, height;
275 if (png_get_sCAL(png, png_info, &unit, &width, &height)) {
276 GP_MetaDataCreateInt(data, "width", width * 1000);
277 GP_MetaDataCreateInt(data, "height", height * 1000);
278 GP_MetaDataCreateInt(data, "unit", unit);
282 int GP_ReadPNGMetaData(FILE *f, GP_MetaData *data)
284 png_structp png;
285 png_infop png_info = NULL;
286 int err;
288 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
290 if (png == NULL) {
291 GP_DEBUG(1, "Failed to allocate PNG read buffer");
292 err = ENOMEM;
293 goto err1;
296 png_info = png_create_info_struct(png);
298 if (png_info == NULL) {
299 GP_DEBUG(1, "Failed to allocate PNG info buffer");
300 err = ENOMEM;
301 goto err2;
304 if (setjmp(png_jmpbuf(png))) {
305 GP_DEBUG(1, "Failed to read PNG file :(");
306 //TODO: should we get better error description from libpng?
307 err = EIO;
308 goto err2;
311 png_init_io(png, f);
312 png_set_sig_bytes(png, 8);
313 png_read_info(png, png_info);
315 load_meta_data(png, png_info, data);
317 return 0;
318 err2:
319 png_destroy_read_struct(&png, png_info ? &png_info : NULL, NULL);
320 err1:
321 errno = err;
322 return 1;
325 int GP_LoadPNGMetaData(const char *src_path, GP_MetaData *data)
327 FILE *f;
328 int ret;
330 if (GP_OpenPNG(src_path, &f))
331 return 1;
333 ret = GP_ReadPNGMetaData(f, data);
335 fclose(f);
337 return ret;
341 * Maps gfxprim Pixel Type to the PNG format
343 static int prepare_png_header(const GP_Context *src, png_structp png,
344 png_infop png_info, int *bit_endian_flag)
346 int bit_depth, color_type;
348 switch (src->pixel_type) {
349 case GP_PIXEL_BGR888:
350 case GP_PIXEL_RGB888:
351 bit_depth = 8;
352 color_type = PNG_COLOR_TYPE_RGB;
353 break;
354 case GP_PIXEL_G1:
355 bit_depth = 1;
356 color_type = PNG_COLOR_TYPE_GRAY;
357 break;
358 case GP_PIXEL_G2:
359 bit_depth = 2;
360 color_type = PNG_COLOR_TYPE_GRAY;
361 break;
362 case GP_PIXEL_G4:
363 bit_depth = 4;
364 color_type = PNG_COLOR_TYPE_GRAY;
365 break;
366 case GP_PIXEL_G8:
367 bit_depth = 8;
368 color_type = PNG_COLOR_TYPE_GRAY;
369 break;
370 default:
371 return 1;
372 break;
375 /* If pointers weren't passed, just return it is okay */
376 if (png == NULL || png_info == NULL)
377 return 0;
379 png_set_IHDR(png, png_info, src->w, src->h, bit_depth, color_type,
380 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
381 PNG_FILTER_TYPE_DEFAULT);
383 /* start the actuall writing */
384 png_write_info(png, png_info);
386 //png_set_packing(png);
388 /* prepare for format conversion */
389 switch (src->pixel_type) {
390 case GP_PIXEL_RGB888:
391 png_set_bgr(png);
392 break;
393 case GP_PIXEL_G1:
394 case GP_PIXEL_G2:
395 case GP_PIXEL_G4:
396 *bit_endian_flag = !src->bit_endian;
397 break;
398 default:
399 break;
402 return 0;
405 static int write_png_data(const GP_Context *src, png_structp png,
406 GP_ProgressCallback *callback, int bit_endian_flag)
408 /* Look if we need to swap data when writing */
409 if (bit_endian_flag) {
410 switch (src->pixel_type) {
411 case GP_PIXEL_G1:
412 case GP_PIXEL_G2:
413 case GP_PIXEL_G4:
414 png_set_packswap(png);
415 break;
416 default:
417 return ENOSYS;
418 break;
422 unsigned int y;
424 for (y = 0; y < src->h; y++) {
425 png_bytep row = GP_PIXEL_ADDR(src, 0, y);
426 png_write_row(png, row);
428 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
429 GP_DEBUG(1, "Operation aborted");
430 return ECANCELED;
434 return 0;
437 int GP_SavePNG(const GP_Context *src, const char *dst_path,
438 GP_ProgressCallback *callback)
440 FILE *f;
441 png_structp png;
442 png_infop png_info = NULL;
443 int err;
445 GP_DEBUG(1, "Saving PNG Image '%s'", dst_path);
447 if (prepare_png_header(src, NULL, NULL, NULL)) {
448 GP_DEBUG(1, "Can't save png with %s pixel type",
449 GP_PixelTypeName(src->pixel_type));
450 return ENOSYS;
453 f = fopen(dst_path, "wb");
455 if (f == NULL) {
456 err = errno;
457 GP_DEBUG(1, "Failed to open '%s' for writing: %s",
458 dst_path, strerror(errno));
459 goto err0;
462 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
464 if (png == NULL) {
465 GP_DEBUG(1, "Failed to allocate PNG write buffer");
466 err = ENOMEM;
467 goto err2;
470 png_info = png_create_info_struct(png);
472 if (png_info == NULL) {
473 GP_DEBUG(1, "Failed to allocate PNG info buffer");
474 err = ENOMEM;
475 goto err3;
478 if (setjmp(png_jmpbuf(png))) {
479 GP_DEBUG(1, "Failed to write PNG file :(");
480 //TODO: should we get better error description from libpng?
481 err = EIO;
482 goto err3;
485 png_init_io(png, f);
487 int bit_endian_flag = 0;
488 /* Fill png header and prepare for data */
489 prepare_png_header(src, png, png_info, &bit_endian_flag);
491 /* Write bitmap buffer */
492 if ((err = write_png_data(src, png, callback, bit_endian_flag)))
493 goto err3;
495 png_write_end(png, png_info);
496 png_destroy_write_struct(&png, &png_info);
498 if (fclose(f)) {
499 err = errno;
500 GP_DEBUG(1, "Failed to close file '%s': %s",
501 dst_path, strerror(errno));
502 goto err1;
505 GP_ProgressCallbackDone(callback);
507 return 0;
508 err3:
509 png_destroy_write_struct(&png, png_info == NULL ? NULL : &png_info);
510 err2:
511 fclose(f);
512 err1:
513 unlink(dst_path);
514 err0:
515 errno = err;
516 return 1;
519 #else
521 int GP_OpenPNG(const char GP_UNUSED(*src_path),
522 FILE GP_UNUSED(**f))
524 errno = ENOSYS;
525 return 1;
528 GP_Context *GP_ReadPNG(FILE GP_UNUSED(*f),
529 GP_ProgressCallback GP_UNUSED(*callback))
531 errno = ENOSYS;
532 return NULL;
535 GP_Context *GP_LoadPNG(const char GP_UNUSED(*src_path),
536 GP_ProgressCallback GP_UNUSED(*callback))
538 errno = ENOSYS;
539 return NULL;
542 int GP_ReadPNGMetaData(FILE GP_UNUSED(*f), GP_MetaData GP_UNUSED(*data))
544 errno = ENOSYS;
545 return NULL;
548 int GP_LoadPNGMetaData(const char GP_UNUSED(*src_path), GP_MetaData GP_UNUSED(*data))
550 errno = ENOSYS;
551 return NULL;
554 int GP_SavePNG(const GP_Context GP_UNUSED(*src),
555 const char GP_UNUSED(*dst_path),
556 GP_ProgressCallback GP_UNUSED(*callback))
558 errno = ENOSYS;
559 return 1;
562 #endif /* HAVE_LIBPNG */