loaders: Make use of double metadata type.
[gfxprim.git] / libs / loaders / GP_PNG.c
blob5f324aa62246dafbec2ef3e288e993edbf51e4ca
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 png_timep mod_time;
275 if (png_get_tIME(png, png_info, &mod_time)) {
276 GP_MetaDataCreateInt(data, "mod_sec", mod_time->second);
277 GP_MetaDataCreateInt(data, "mod_min", mod_time->minute);
278 GP_MetaDataCreateInt(data, "mod_hour", mod_time->hour);
279 GP_MetaDataCreateInt(data, "mod_day", mod_time->day);
280 GP_MetaDataCreateInt(data, "mod_mon", mod_time->month);
281 GP_MetaDataCreateInt(data, "mod_year", mod_time->year);
284 double width, height;
286 if (png_get_sCAL(png, png_info, &unit, &width, &height)) {
287 GP_MetaDataCreateDouble(data, "width", width);
288 GP_MetaDataCreateDouble(data, "height", height);
289 GP_MetaDataCreateInt(data, "unit", unit);
292 png_textp text_ptr;
293 int text_cnt;
295 if (png_get_text(png, png_info, &text_ptr, &text_cnt)) {
296 int i;
298 for (i = 0; i < text_cnt; i++) {
300 if (text_ptr[i].compression != PNG_TEXT_COMPRESSION_NONE)
301 continue;
303 char buf[GP_META_RECORD_ID_MAX];
304 snprintf(buf, GP_META_RECORD_ID_MAX, "text:%s", text_ptr[i].key);
305 GP_MetaDataCreateString(data, buf, text_ptr[i].text, 1);
310 int GP_ReadPNGMetaData(FILE *f, GP_MetaData *data)
312 png_structp png;
313 png_infop png_info = NULL;
314 int err;
316 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
318 if (png == NULL) {
319 GP_DEBUG(1, "Failed to allocate PNG read buffer");
320 err = ENOMEM;
321 goto err1;
324 png_info = png_create_info_struct(png);
326 if (png_info == NULL) {
327 GP_DEBUG(1, "Failed to allocate PNG info buffer");
328 err = ENOMEM;
329 goto err2;
332 if (setjmp(png_jmpbuf(png))) {
333 GP_DEBUG(1, "Failed to read PNG file :(");
334 //TODO: should we get better error description from libpng?
335 err = EIO;
336 goto err2;
339 png_init_io(png, f);
340 png_set_sig_bytes(png, 8);
341 png_read_info(png, png_info);
343 load_meta_data(png, png_info, data);
345 return 0;
346 err2:
347 png_destroy_read_struct(&png, png_info ? &png_info : NULL, NULL);
348 err1:
349 errno = err;
350 return 1;
353 int GP_LoadPNGMetaData(const char *src_path, GP_MetaData *data)
355 FILE *f;
356 int ret;
358 if (GP_OpenPNG(src_path, &f))
359 return 1;
361 ret = GP_ReadPNGMetaData(f, data);
363 fclose(f);
365 return ret;
369 * Maps gfxprim Pixel Type to the PNG format
371 static int prepare_png_header(const GP_Context *src, png_structp png,
372 png_infop png_info, int *bit_endian_flag)
374 int bit_depth, color_type;
376 switch (src->pixel_type) {
377 case GP_PIXEL_BGR888:
378 case GP_PIXEL_RGB888:
379 bit_depth = 8;
380 color_type = PNG_COLOR_TYPE_RGB;
381 break;
382 case GP_PIXEL_G1:
383 bit_depth = 1;
384 color_type = PNG_COLOR_TYPE_GRAY;
385 break;
386 case GP_PIXEL_G2:
387 bit_depth = 2;
388 color_type = PNG_COLOR_TYPE_GRAY;
389 break;
390 case GP_PIXEL_G4:
391 bit_depth = 4;
392 color_type = PNG_COLOR_TYPE_GRAY;
393 break;
394 case GP_PIXEL_G8:
395 bit_depth = 8;
396 color_type = PNG_COLOR_TYPE_GRAY;
397 break;
398 default:
399 return 1;
400 break;
403 /* If pointers weren't passed, just return it is okay */
404 if (png == NULL || png_info == NULL)
405 return 0;
407 png_set_IHDR(png, png_info, src->w, src->h, bit_depth, color_type,
408 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
409 PNG_FILTER_TYPE_DEFAULT);
411 /* start the actuall writing */
412 png_write_info(png, png_info);
414 //png_set_packing(png);
416 /* prepare for format conversion */
417 switch (src->pixel_type) {
418 case GP_PIXEL_RGB888:
419 png_set_bgr(png);
420 break;
421 case GP_PIXEL_G1:
422 case GP_PIXEL_G2:
423 case GP_PIXEL_G4:
424 *bit_endian_flag = !src->bit_endian;
425 break;
426 default:
427 break;
430 return 0;
433 static int write_png_data(const GP_Context *src, png_structp png,
434 GP_ProgressCallback *callback, int bit_endian_flag)
436 /* Look if we need to swap data when writing */
437 if (bit_endian_flag) {
438 switch (src->pixel_type) {
439 case GP_PIXEL_G1:
440 case GP_PIXEL_G2:
441 case GP_PIXEL_G4:
442 png_set_packswap(png);
443 break;
444 default:
445 return ENOSYS;
446 break;
450 unsigned int y;
452 for (y = 0; y < src->h; y++) {
453 png_bytep row = GP_PIXEL_ADDR(src, 0, y);
454 png_write_row(png, row);
456 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
457 GP_DEBUG(1, "Operation aborted");
458 return ECANCELED;
462 return 0;
465 int GP_SavePNG(const GP_Context *src, const char *dst_path,
466 GP_ProgressCallback *callback)
468 FILE *f;
469 png_structp png;
470 png_infop png_info = NULL;
471 int err;
473 GP_DEBUG(1, "Saving PNG Image '%s'", dst_path);
475 if (prepare_png_header(src, NULL, NULL, NULL)) {
476 GP_DEBUG(1, "Can't save png with %s pixel type",
477 GP_PixelTypeName(src->pixel_type));
478 return ENOSYS;
481 f = fopen(dst_path, "wb");
483 if (f == NULL) {
484 err = errno;
485 GP_DEBUG(1, "Failed to open '%s' for writing: %s",
486 dst_path, strerror(errno));
487 goto err0;
490 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
492 if (png == NULL) {
493 GP_DEBUG(1, "Failed to allocate PNG write buffer");
494 err = ENOMEM;
495 goto err2;
498 png_info = png_create_info_struct(png);
500 if (png_info == NULL) {
501 GP_DEBUG(1, "Failed to allocate PNG info buffer");
502 err = ENOMEM;
503 goto err3;
506 if (setjmp(png_jmpbuf(png))) {
507 GP_DEBUG(1, "Failed to write PNG file :(");
508 //TODO: should we get better error description from libpng?
509 err = EIO;
510 goto err3;
513 png_init_io(png, f);
515 int bit_endian_flag = 0;
516 /* Fill png header and prepare for data */
517 prepare_png_header(src, png, png_info, &bit_endian_flag);
519 /* Write bitmap buffer */
520 if ((err = write_png_data(src, png, callback, bit_endian_flag)))
521 goto err3;
523 png_write_end(png, png_info);
524 png_destroy_write_struct(&png, &png_info);
526 if (fclose(f)) {
527 err = errno;
528 GP_DEBUG(1, "Failed to close file '%s': %s",
529 dst_path, strerror(errno));
530 goto err1;
533 GP_ProgressCallbackDone(callback);
535 return 0;
536 err3:
537 png_destroy_write_struct(&png, png_info == NULL ? NULL : &png_info);
538 err2:
539 fclose(f);
540 err1:
541 unlink(dst_path);
542 err0:
543 errno = err;
544 return 1;
547 #else
549 int GP_OpenPNG(const char GP_UNUSED(*src_path),
550 FILE GP_UNUSED(**f))
552 errno = ENOSYS;
553 return 1;
556 GP_Context *GP_ReadPNG(FILE GP_UNUSED(*f),
557 GP_ProgressCallback GP_UNUSED(*callback))
559 errno = ENOSYS;
560 return NULL;
563 GP_Context *GP_LoadPNG(const char GP_UNUSED(*src_path),
564 GP_ProgressCallback GP_UNUSED(*callback))
566 errno = ENOSYS;
567 return NULL;
570 int GP_ReadPNGMetaData(FILE GP_UNUSED(*f), GP_MetaData GP_UNUSED(*data))
572 errno = ENOSYS;
573 return NULL;
576 int GP_LoadPNGMetaData(const char GP_UNUSED(*src_path), GP_MetaData GP_UNUSED(*data))
578 errno = ENOSYS;
579 return NULL;
582 int GP_SavePNG(const GP_Context GP_UNUSED(*src),
583 const char GP_UNUSED(*dst_path),
584 GP_ProgressCallback GP_UNUSED(*callback))
586 errno = ENOSYS;
587 return 1;
590 #endif /* HAVE_LIBPNG */