1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 PNG image support using libpng.
35 #include "../../config.h"
36 #include "core/GP_ByteOrder.h"
37 #include "core/GP_Debug.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
)
55 case PNG_INTERLACE_NONE
:
57 case PNG_INTERLACE_ADAM7
:
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
)
75 png_infop png_info
= NULL
;
77 int depth
, color_type
, interlace_type
;
78 GP_PixelType pixel_type
= GP_PIXEL_UNKNOWN
;
83 png
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
86 GP_DEBUG(1, "Failed to allocate PNG read buffer");
91 png_info
= png_create_info_struct(png
);
93 if (png_info
== NULL
) {
94 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
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
:
129 pixel_type
= GP_PIXEL_G1
;
132 pixel_type
= GP_PIXEL_G2
;
135 pixel_type
= GP_PIXEL_G4
;
138 pixel_type
= GP_PIXEL_G8
;
142 pixel_type
= GP_PIXEL_G16
;
147 case PNG_COLOR_TYPE_GRAY
| PNG_COLOR_MASK_ALPHA
:
150 pixel_type
= GP_PIXEL_GA88
;
154 case PNG_COLOR_TYPE_RGB
:
160 pixel_type
= GP_PIXEL_RGB888
;
164 case PNG_COLOR_TYPE_RGB
| PNG_COLOR_MASK_ALPHA
:
167 png_set_swap_alpha(png
);
171 pixel_type
= GP_PIXEL_RGBA8888
;
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) {
180 png_set_packswap(png
);
181 pixel_type
= GP_PIXEL_G1
;
186 /* Convert everything else to RGB888 */
187 //TODO: add palette matching to G2 G4 and G8
188 png_set_palette_to_rgb(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
);
200 pixel_type
= GP_PIXEL_RGB888
;
205 if (pixel_type
== GP_PIXEL_UNKNOWN
) {
206 GP_DEBUG(1, "Unimplemented png format");
211 res
= GP_ContextAlloc(w
, h
, pixel_type
);
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.
227 GP_DEBUG(1, "Enabling byte swap for bpp = %u", depth
);
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");
253 png_destroy_read_struct(&png
, &png_info
, NULL
);
255 GP_ProgressCallbackDone(callback
);
261 png_destroy_read_struct(&png
, png_info
? &png_info
: NULL
, NULL
);
267 static void load_meta_data(png_structp png
, png_infop png_info
, GP_MetaData
*data
)
271 if (png_get_gAMA(png
, png_info
, &gamma
))
272 GP_MetaDataCreateInt(data
, "gamma", gamma
* 100000);
274 png_uint_32 res_x
, res_y
;
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
)
286 unit_name
= "unknown";
288 GP_MetaDataCreateString(data
, "res_unit", unit_name
, 0, 0);
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
);
313 if (png_get_text(png
, png_info
, &text_ptr
, &text_cnt
)) {
316 for (i
= 0; i
< text_cnt
; i
++) {
318 if (text_ptr
[i
].compression
!= PNG_TEXT_COMPRESSION_NONE
)
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
)
331 png_infop png_info
= NULL
;
334 png
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
337 GP_DEBUG(1, "Failed to allocate PNG read buffer");
342 png_info
= png_create_info_struct(png
);
344 if (png_info
== NULL
) {
345 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
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
);
365 png_destroy_read_struct(&png
, png_info
? &png_info
: NULL
, NULL
);
371 int GP_LoadPNGMetaData(const char *src_path
, GP_MetaData
*data
)
376 io
= GP_IOFile(src_path
, GP_IO_RDONLY
);
380 ret
= GP_ReadPNGMetaData(io
, data
);
389 static GP_PixelType save_ptypes
[] = {
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
:
415 color_type
= PNG_COLOR_TYPE_RGB
;
419 color_type
= PNG_COLOR_TYPE_GRAY
;
423 color_type
= PNG_COLOR_TYPE_GRAY
;
427 color_type
= PNG_COLOR_TYPE_GRAY
;
431 color_type
= PNG_COLOR_TYPE_GRAY
;
436 color_type
= PNG_COLOR_TYPE_GRAY
;
439 case GP_PIXEL_RGBA8888
:
441 color_type
= PNG_COLOR_TYPE_RGB
| PNG_COLOR_MASK_ALPHA
;
448 /* If pointers weren't passed, just return it is okay */
449 if (png
== NULL
|| png_info
== NULL
)
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
:
466 case GP_PIXEL_RGBA8888
:
468 png_set_swap_alpha(png
);
473 *bit_endian_flag
= !src
->bit_endian
;
479 #if __BYTE_ORDER == __LITTLE_ENDIAN
481 * PNG stores 16 bit values in big endian, turn
482 * on conversion to little endian if needed.
485 GP_DEBUG(1, "Enabling byte swap for bpp = %u", bit_depth
);
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
) {
502 png_set_packswap(png
);
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");
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
)
538 int GP_WritePNG(const GP_Context
*src
, GP_IO
*io
,
539 GP_ProgressCallback
*callback
)
542 png_infop png_info
= NULL
;
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
));
554 png
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
557 GP_DEBUG(1, "Failed to allocate PNG write buffer");
562 png_info
= png_create_info_struct(png
);
564 if (png_info
== NULL
) {
565 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
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
)))
587 png_write_end(png
, png_info
);
588 png_destroy_write_struct(&png
, &png_info
);
590 GP_ProgressCallbackDone(callback
);
593 png_destroy_write_struct(&png
, png_info
== NULL
? NULL
: &png_info
);
600 int GP_MatchPNG(const void GP_UNUSED(*buf
))
606 GP_Context
*GP_ReadPNG(GP_IO
GP_UNUSED(*io
),
607 GP_ProgressCallback
GP_UNUSED(*callback
))
613 int GP_ReadPNGMetaData(GP_IO
GP_UNUSED(*io
), GP_MetaData
GP_UNUSED(*data
))
619 int GP_LoadPNGMetaData(const char GP_UNUSED(*src_path
), GP_MetaData
GP_UNUSED(*data
))
625 int GP_WritePNG(const GP_Context
*src
, GP_IO
GP_UNUSED(*io
),
626 GP_ProgressCallback
*callback
)
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
);
648 .Write
= GP_WritePNG
,
649 .save_ptypes
= save_ptypes
,
651 .Match
= GP_MatchPNG
,
653 .fmt_name
= "Portable Network Graphics",
654 .extensions
= {"png", NULL
},