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-2012 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 PNG image support using libpng.
35 #include "../../config.h"
36 #include "core/GP_Debug.h"
44 #include "core/GP_BitSwap.h"
46 int GP_OpenPNG(const char *src_path
, FILE **f
)
51 *f
= fopen(src_path
, "r");
55 GP_DEBUG(1, "Failed to open '%s' : %s",
56 src_path
, strerror(errno
));
60 if (fread(sig
, 1, 8, *f
) <= 0) {
62 GP_DEBUG(1, "Failed to read '%s' : %s",
63 src_path
, strerror(errno
));
67 if (png_sig_cmp(sig
, 0, 8)) {
68 GP_DEBUG(1, "Invalid file header, '%s' not a PNG image?",
74 GP_DEBUG(1, "Found PNG signature in '%s'", src_path
);
84 static const char *interlace_type_name(int interlace
)
87 case PNG_INTERLACE_NONE
:
89 case PNG_INTERLACE_ADAM7
:
96 GP_Context
*GP_ReadPNG(FILE *f
, GP_ProgressCallback
*callback
)
99 png_infop png_info
= NULL
;
101 int depth
, color_type
, interlace_type
;
102 GP_PixelType pixel_type
= GP_PIXEL_UNKNOWN
;
106 png
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
109 GP_DEBUG(1, "Failed to allocate PNG read buffer");
114 png_info
= png_create_info_struct(png
);
116 if (png_info
== NULL
) {
117 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
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
:
147 pixel_type
= GP_PIXEL_G1
;
150 pixel_type
= GP_PIXEL_G2
;
153 pixel_type
= GP_PIXEL_G4
;
156 pixel_type
= GP_PIXEL_G8
;
160 case PNG_COLOR_TYPE_RGB
:
166 pixel_type
= GP_PIXEL_RGB888
;
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) {
175 png_set_packswap(png
);
176 pixel_type
= GP_PIXEL_G1
;
181 /* Convert everything else to RGB888 */
182 //TODO: add palette matching to G2 G4 and G8
183 png_set_palette_to_rgb(png
);
185 pixel_type
= GP_PIXEL_RGB888
;
189 if (pixel_type
== GP_PIXEL_UNKNOWN
) {
190 GP_DEBUG(1, "Unimplemented png format");
195 res
= GP_ContextAlloc(w
, h
, pixel_type
);
202 if (color_type
== PNG_COLOR_TYPE_GRAY
)
203 png_set_packswap(png
);
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");
220 png_destroy_read_struct(&png
, &png_info
, NULL
);
222 GP_ProgressCallbackDone(callback
);
228 png_destroy_read_struct(&png
, png_info
? &png_info
: NULL
, NULL
);
234 GP_Context
*GP_LoadPNG(const char *src_path
, GP_ProgressCallback
*callback
)
239 if (GP_OpenPNG(src_path
, &f
))
242 res
= GP_ReadPNG(f
, callback
);
249 static void load_meta_data(png_structp png
, png_infop png_info
, GP_MetaData
*data
)
253 if (png_get_gAMA(png
, png_info
, &gamma
))
254 GP_MetaDataCreateInt(data
, "gamma", gamma
* 100000);
256 png_uint_32 res_x
, res_y
;
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
)
268 unit_name
= "unknown";
270 GP_MetaDataCreateString(data
, "res_unit", unit_name
, 0);
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
);
295 if (png_get_text(png
, png_info
, &text_ptr
, &text_cnt
)) {
298 for (i
= 0; i
< text_cnt
; i
++) {
300 if (text_ptr
[i
].compression
!= PNG_TEXT_COMPRESSION_NONE
)
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
)
313 png_infop png_info
= NULL
;
316 png
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
319 GP_DEBUG(1, "Failed to allocate PNG read buffer");
324 png_info
= png_create_info_struct(png
);
326 if (png_info
== NULL
) {
327 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
340 png_set_sig_bytes(png
, 8);
341 png_read_info(png
, png_info
);
343 load_meta_data(png
, png_info
, data
);
347 png_destroy_read_struct(&png
, png_info
? &png_info
: NULL
, NULL
);
353 int GP_LoadPNGMetaData(const char *src_path
, GP_MetaData
*data
)
358 if (GP_OpenPNG(src_path
, &f
))
361 ret
= GP_ReadPNGMetaData(f
, data
);
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
:
380 color_type
= PNG_COLOR_TYPE_RGB
;
384 color_type
= PNG_COLOR_TYPE_GRAY
;
388 color_type
= PNG_COLOR_TYPE_GRAY
;
392 color_type
= PNG_COLOR_TYPE_GRAY
;
396 color_type
= PNG_COLOR_TYPE_GRAY
;
403 /* If pointers weren't passed, just return it is okay */
404 if (png
== NULL
|| png_info
== NULL
)
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
:
424 *bit_endian_flag
= !src
->bit_endian
;
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
) {
442 png_set_packswap(png
);
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");
465 int GP_SavePNG(const GP_Context
*src
, const char *dst_path
,
466 GP_ProgressCallback
*callback
)
470 png_infop png_info
= NULL
;
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
));
481 f
= fopen(dst_path
, "wb");
485 GP_DEBUG(1, "Failed to open '%s' for writing: %s",
486 dst_path
, strerror(errno
));
490 png
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
493 GP_DEBUG(1, "Failed to allocate PNG write buffer");
498 png_info
= png_create_info_struct(png
);
500 if (png_info
== NULL
) {
501 GP_DEBUG(1, "Failed to allocate PNG info buffer");
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?
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
)))
523 png_write_end(png
, png_info
);
524 png_destroy_write_struct(&png
, &png_info
);
528 GP_DEBUG(1, "Failed to close file '%s': %s",
529 dst_path
, strerror(errno
));
533 GP_ProgressCallbackDone(callback
);
537 png_destroy_write_struct(&png
, png_info
== NULL
? NULL
: &png_info
);
549 int GP_OpenPNG(const char GP_UNUSED(*src_path
),
556 GP_Context
*GP_ReadPNG(FILE GP_UNUSED(*f
),
557 GP_ProgressCallback
GP_UNUSED(*callback
))
563 GP_Context
*GP_LoadPNG(const char GP_UNUSED(*src_path
),
564 GP_ProgressCallback
GP_UNUSED(*callback
))
570 int GP_ReadPNGMetaData(FILE GP_UNUSED(*f
), GP_MetaData
GP_UNUSED(*data
))
576 int GP_LoadPNGMetaData(const char GP_UNUSED(*src_path
), GP_MetaData
GP_UNUSED(*data
))
582 int GP_SavePNG(const GP_Context
GP_UNUSED(*src
),
583 const char GP_UNUSED(*dst_path
),
584 GP_ProgressCallback
GP_UNUSED(*callback
))
590 #endif /* HAVE_LIBPNG */