X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/b51bcea53e36391afe8693d73ba7c691f1855ae4..6494c395d2d9d663dba544b47894ae6bca40dbb0:/PNG/impng.c diff --git a/PNG/impng.c b/PNG/impng.c index 6ac9e8c6..c7a7b8e4 100644 --- a/PNG/impng.c +++ b/PNG/impng.c @@ -1,5 +1,8 @@ #include "impng.h" #include "png.h" +#include +#include +#include /* this is a way to get number of channels from color space * Color code to channel number */ @@ -20,11 +23,61 @@ read_paletted(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim w static i_img * read_bilevel(png_structp png_ptr, png_infop info_ptr, i_img_dim width, i_img_dim height); +static int +write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im); + +static int +write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im); + +static int +write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits); + +static int +write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im); + +static void +get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth, int color_type); + +static int +set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr); + +static const char * +get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size); + unsigned i_png_lib_version(void) { return png_access_version_number(); } +static char const * const +features[] = + { +#ifdef PNG_BENIGN_ERRORS_SUPPORTED + "benign-errors", +#endif +#ifdef PNG_READ_SUPPORTED + "read", +#endif +#ifdef PNG_WRITE_SUPPORTED + "write", +#endif +#ifdef PNG_MNG_FEATURES_SUPPORTED + "mng-features", +#endif +#ifdef PNG_CHECK_cHRM_SUPPORTED + "check-cHRM", +#endif +#ifdef PNG_SET_USER_LIMITS_SUPPORTED + "user-limits", +#endif + NULL + }; + +const char * const * +i_png_features(void) { + return features; +} + static void wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { io_glue *ig = png_get_io_ptr(png_ptr); @@ -42,7 +95,9 @@ wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { static void wiol_flush_data(png_structp png_ptr) { - /* XXX : This needs to be added to the io layer */ + io_glue *ig = png_get_io_ptr(png_ptr); + if (!i_io_flush(ig)) + png_error(png_ptr, "Error flushing output"); } static void @@ -72,12 +127,10 @@ undef_int i_writepng_wiol(i_img *im, io_glue *ig) { png_structp png_ptr; png_infop info_ptr = NULL; - i_img_dim width,height,y; + i_img_dim width,height; volatile int cspace,channels; - double xres, yres; - int aspect_only, have_res; - unsigned char *data; - unsigned char * volatile vdata = NULL; + int bits; + int is_bilevel = 0, zero_is_white; mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig)); @@ -108,13 +161,45 @@ i_writepng_wiol(i_img *im, io_glue *ig) { channels=im->channels; - if (channels > 2) { cspace = PNG_COLOR_TYPE_RGB; channels-=3; } - else { cspace=PNG_COLOR_TYPE_GRAY; channels--; } - - if (channels) cspace|=PNG_COLOR_MASK_ALPHA; - mm_log((1,"cspace=%d\n",cspace)); + if (i_img_is_monochrome(im, &zero_is_white)) { + is_bilevel = 1; + bits = 1; + cspace = PNG_COLOR_TYPE_GRAY; + mm_log((1, "i_writepng: bilevel output\n")); + } + else if (im->type == i_palette_type) { + int colors = i_colorcount(im); + + cspace = PNG_COLOR_TYPE_PALETTE; + bits = 1; + while ((1 << bits) < colors) { + bits += bits; + } + mm_log((1, "i_writepng: paletted output\n")); + } + else { + switch (channels) { + case 1: + cspace = PNG_COLOR_TYPE_GRAY; + break; + case 2: + cspace = PNG_COLOR_TYPE_GRAY_ALPHA; + break; + case 3: + cspace = PNG_COLOR_TYPE_RGB; + break; + case 4: + cspace = PNG_COLOR_TYPE_RGB_ALPHA; + break; + default: + fprintf(stderr, "Internal error, channels = %d\n", channels); + abort(); + } + bits = im->bits > 8 ? 16 : 8; + mm_log((1, "i_writepng: direct output\n")); + } - channels = im->channels; + mm_log((1,"i_writepng: cspace=%d, bits=%d\n",cspace, bits)); /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, @@ -142,8 +227,6 @@ i_writepng_wiol(i_img *im, io_glue *ig) { */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); - if (vdata) - myfree(vdata); return(0); } @@ -164,39 +247,38 @@ i_writepng_wiol(i_img *im, io_glue *ig) { */ png_set_user_limits(png_ptr, width, height); - png_set_IHDR(png_ptr, info_ptr, width, height, 8, cspace, + png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - have_res = 1; - if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) { - if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) - ; /* nothing to do */ - else - yres = xres; + if (!set_png_tags(im, png_ptr, info_ptr)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; } - else { - if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) - xres = yres; - else - have_res = 0; + + if (is_bilevel) { + if (!write_bilevel(png_ptr, info_ptr, im)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + } } - if (have_res) { - aspect_only = 0; - i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only); - xres /= 0.0254; - yres /= 0.0254; - png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, - aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER); + else if (im->type == i_palette_type) { + if (!write_paletted(png_ptr, info_ptr, im, bits)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + } } - - png_write_info(png_ptr, info_ptr); - - vdata = data = mymalloc(im->xsize * im->channels); - for (y = 0; y < height; y++) { - i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels); - png_write_row(png_ptr, (png_bytep)data); + else if (bits == 16) { + if (!write_direct16(png_ptr, info_ptr, im)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + } + } + else { + if (!write_direct8(png_ptr, info_ptr, im)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + } } - myfree(data); png_write_end(png_ptr, info_ptr); @@ -208,9 +290,6 @@ i_writepng_wiol(i_img *im, io_glue *ig) { return(1); } -static void -get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth); - typedef struct { char *warnings; } i_png_read_state, *i_png_read_statep; @@ -222,7 +301,7 @@ static void cleanup_read_state(i_png_read_statep); i_img* -i_readpng_wiol(io_glue *ig) { +i_readpng_wiol(io_glue *ig, int flags) { i_img *im = NULL; png_structp png_ptr; png_infop info_ptr; @@ -245,7 +324,23 @@ i_readpng_wiol(io_glue *ig) { return NULL; } png_set_read_fn(png_ptr, (png_voidp) (ig), wiol_read_data); - + +#if defined(PNG_BENIGN_ERRORS_SUPPORTED) + png_set_benign_errors(png_ptr, (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) ? 1 : 0); +#elif PNG_LIBPNG_VER >= 10400 + if (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) { + i_push_error(0, "libpng not configured to ignore benign errors"); + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + return NULL; + } +#else + if (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) { + i_push_error(0, "libpng too old to ignore benign errors"); + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + return NULL; + } +#endif + info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); @@ -260,7 +355,7 @@ i_readpng_wiol(io_glue *ig) { cleanup_read_state(&rs); return NULL; } - + /* we do our own limit checks */ png_set_user_limits(png_ptr, PNG_DIM_MAX, PNG_DIM_MAX); @@ -268,9 +363,8 @@ i_readpng_wiol(io_glue *ig) { png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); - mm_log((1, - "png_get_IHDR results: width %d, height %d, bit_depth %d, color_type %d, interlace_type %d\n", - width,height,bit_depth,color_type,interlace_type)); + mm_log((1, "png_get_IHDR results: width %u, height %u, bit_depth %d, color_type %d, interlace_type %d\n", + (unsigned)width, (unsigned)height, bit_depth,color_type,interlace_type)); CC2C[PNG_COLOR_TYPE_GRAY]=1; CC2C[PNG_COLOR_TYPE_PALETTE]=3; @@ -303,7 +397,7 @@ i_readpng_wiol(io_glue *ig) { } if (im) - get_png_tags(im, png_ptr, info_ptr, bit_depth); + get_png_tags(im, png_ptr, info_ptr, bit_depth, color_type); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); @@ -617,14 +711,29 @@ text_tags[] = { static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags); +static const char * const +chroma_tags[] = { + "png_chroma_white_x", + "png_chroma_white_y", + "png_chroma_red_x", + "png_chroma_red_y", + "png_chroma_green_x", + "png_chroma_green_y", + "png_chroma_blue_x", + "png_chroma_blue_y" +}; + +static const int chroma_tag_count = sizeof(chroma_tags) / sizeof(*chroma_tags); + static void -get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth) { +get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, + int bit_depth, int color_type) { png_uint_32 xres, yres; int unit_type; i_tags_set(&im->tags, "i_format", "png", -1); if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) { - mm_log((1,"pHYs (%d, %d) %d\n", xres, yres, unit_type)); + mm_log((1,"pHYs (%u, %u) %d\n", (unsigned)xres, (unsigned)yres, unit_type)); if (unit_type == PNG_RESOLUTION_METER) { i_tags_set_float2(&im->tags, "i_xres", 0, xres * 0.0254, 5); i_tags_set_float2(&im->tags, "i_yres", 0, yres * 0.0254, 5); @@ -657,36 +766,32 @@ get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth) /* the various readers can call png_set_expand(), libpng will make it's internal record of bit_depth at least 8 in that case */ i_tags_setn(&im->tags, "png_bits", bit_depth); - - { + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { int intent; if (png_get_sRGB(png_ptr, info_ptr, &intent)) { i_tags_setn(&im->tags, "png_srgb_intent", intent); } } - { + else { + /* Ignore these if there's an sRGB chunk, libpng simulates + their existence if there's an sRGB chunk, and the PNG spec says + that these are ignored if the sRGB is present, so ignore them. + */ double gamma; + double chroma[8]; + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { i_tags_set_float2(&im->tags, "png_gamma", 0, gamma, 4); } - } - { - double white_x, white_y; - double red_x, red_y; - double green_x, green_y; - double blue_x, blue_y; - if (png_get_cHRM(png_ptr, info_ptr, &white_x, &white_y, - &red_x, &red_y, &green_x, &green_y, - &blue_x, &blue_y)) { - i_tags_set_float2(&im->tags, "png_chroma_white_x", 0, white_x, 4); - i_tags_set_float2(&im->tags, "png_chroma_white_y", 0, white_y, 4); - i_tags_set_float2(&im->tags, "png_chroma_red_x", 0, red_x, 4); - i_tags_set_float2(&im->tags, "png_chroma_red_y", 0, red_y, 4); - i_tags_set_float2(&im->tags, "png_chroma_green_x", 0, green_x, 4); - i_tags_set_float2(&im->tags, "png_chroma_green_y", 0, green_y, 4); - i_tags_set_float2(&im->tags, "png_chroma_blue_x", 0, blue_x, 4); - i_tags_set_float2(&im->tags, "png_chroma_blue_y", 0, blue_y, 4); + + if (png_get_cHRM(png_ptr, info_ptr, chroma+0, chroma+1, + chroma+2, chroma+3, chroma+4, chroma+5, + chroma+6, chroma+7)) { + int i; + + for (i = 0; i < chroma_tag_count; ++i) + i_tags_set_float2(&im->tags, chroma_tags[i], 0, chroma[i], 4); } } @@ -696,30 +801,561 @@ get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth) if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { int i; + int custom_index = 0; for (i = 0; i < num_text; ++i) { int j; - char tag_name[50]; - sprintf(tag_name, "png_text%d_key", i); - i_tags_set(&im->tags, tag_name, text[i].key, -1); - sprintf(tag_name, "png_text%d_text", i); - i_tags_set(&im->tags, tag_name, text[i].text, -1); - sprintf(tag_name, "png_text%d_type", i); - i_tags_set(&im->tags, tag_name, - (text[i].compression == PNG_TEXT_COMPRESSION_NONE - || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ? - "text" : "itxt", -1); + int found = 0; + int compressed = text[i].compression == PNG_ITXT_COMPRESSION_zTXt + || text[i].compression == PNG_TEXT_COMPRESSION_zTXt; for (j = 0; j < text_tags_count; ++j) { if (strcmp(text_tags[j].keyword, text[i].key) == 0) { + char tag_name[50]; i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1); + if (compressed) { + sprintf(tag_name, "%s_compressed", text_tags[j].tagname); + i_tags_setn(&im->tags, tag_name, 1); + } + found = 1; + break; + } + } + + if (!found) { + char tag_name[50]; + sprintf(tag_name, "png_text%d_key", custom_index); + i_tags_set(&im->tags, tag_name, text[i].key, -1); + sprintf(tag_name, "png_text%d_text", custom_index); + i_tags_set(&im->tags, tag_name, text[i].text, -1); + sprintf(tag_name, "png_text%d_type", custom_index); + i_tags_set(&im->tags, tag_name, + (text[i].compression == PNG_TEXT_COMPRESSION_NONE + || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ? + "text" : "itxt", -1); + if (compressed) { + sprintf(tag_name, "png_text%d_compressed", custom_index); + i_tags_setn(&im->tags, tag_name, 1); + } + ++custom_index; + } + } + } + } + + { + png_time *mod_time; + + if (png_get_tIME(png_ptr, info_ptr, &mod_time)) { + char time_formatted[80]; + + sprintf(time_formatted, "%d-%02d-%02dT%02d:%02d:%02d", + mod_time->year, mod_time->month, mod_time->day, + mod_time->hour, mod_time->minute, mod_time->second); + i_tags_set(&im->tags, "png_time", time_formatted, -1); + } + } + + { + png_color_16 *back; + i_color c; + + if (png_get_bKGD(png_ptr, info_ptr, &back)) { + switch (color_type) { + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_GRAY_ALPHA: + { + /* lib png stores the raw gray value rather than scaling it + to 16-bit (or 8), we use 8-bit color for i_background */ + + int gray; + switch (bit_depth) { + case 16: + gray = back->gray >> 8; + break; + case 8: + gray = back->gray; + break; + case 4: + gray = 0x11 * back->gray; + break; + case 2: + gray = 0x55 * back->gray; + break; + case 1: + gray = back->gray ? 0xFF : 0; break; + default: + gray = 0; } + c.rgb.r = c.rgb.g = c.rgb.b = gray; + break; } + + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + { + c.rgb.r = bit_depth == 16 ? (back->red >> 8) : back->red; + c.rgb.g = bit_depth == 16 ? (back->green >> 8) : back->green; + c.rgb.b = bit_depth == 16 ? (back->blue >> 8) : back->blue; + break; + } + + case PNG_COLOR_TYPE_PALETTE: + c.rgb.r = back->red; + c.rgb.g = back->green; + c.rgb.b = back->blue; + break; } + + c.rgba.a = 255; + i_tags_set_color(&im->tags, "i_background", 0, &c); } } } +#define GET_STR_BUF_SIZE 40 + +static int +set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr) { + double xres, yres; + int aspect_only, have_res = 1; + + if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) { + if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) + ; /* nothing to do */ + else + yres = xres; + } + else { + if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) + xres = yres; + else + have_res = 0; + } + if (have_res) { + aspect_only = 0; + i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only); + xres /= 0.0254; + yres /= 0.0254; + png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, + aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER); + } + + { + int intent; + if (i_tags_get_int(&im->tags, "png_srgb_intent", 0, &intent)) { + if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) { + i_push_error(0, "tag png_srgb_intent out of range"); + return 0; + } + png_set_sRGB(png_ptr, info_ptr, intent); + } + else { + double chroma[8], gamma; + int i; + int found_chroma_count = 0; + + for (i = 0; i < chroma_tag_count; ++i) { + if (i_tags_get_float(&im->tags, chroma_tags[i], 0, chroma+i)) + ++found_chroma_count; + } + + if (found_chroma_count) { + if (found_chroma_count != chroma_tag_count) { + i_push_error(0, "all png_chroma_* tags must be supplied or none"); + return 0; + } + + png_set_cHRM(png_ptr, info_ptr, chroma[0], chroma[1], chroma[2], + chroma[3], chroma[4], chroma[5], chroma[6], chroma[7]); + } + + if (i_tags_get_float(&im->tags, "png_gamma", 0, &gamma)) { + png_set_gAMA(png_ptr, info_ptr, gamma); + } + } + } + + { + /* png_set_text() is sparsely documented, it isn't indicated whether + multiple calls add to or replace the lists of texts, and + whether the text/keyword data is copied or not. + + Examining the linpng code reveals that png_set_text() adds to + the list and that the text is copied. + */ + int i; + + /* do our standard tags */ + for (i = 0; i < text_tags_count; ++i) { + char buf[GET_STR_BUF_SIZE]; + size_t size; + const char *data; + + data = get_string2(&im->tags, text_tags[i].tagname, buf, &size); + if (data) { + png_text text; + int compression = size > 1000; + char compress_tag[40]; + + if (memchr(data, '\0', size)) { + i_push_errorf(0, "tag %s may not contain NUL characters", text_tags[i].tagname); + return 0; + } + + sprintf(compress_tag, "%s_compressed", text_tags[i].tagname); + i_tags_get_int(&im->tags, compress_tag, 0, &compression); + + text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt + : PNG_TEXT_COMPRESSION_NONE; + text.key = (char *)text_tags[i].keyword; + text.text_length = size; + text.text = (char *)data; +#ifdef PNG_iTXt_SUPPORTED + text.itxt_length = 0; + text.lang = NULL; + text.lang_key = NULL; +#endif + + png_set_text(png_ptr, info_ptr, &text, 1); + } + } + + /* for non-standard tags ensure keywords are limited to 1 to 79 + characters */ + i = 0; + while (1) { + char tag_name[50]; + char key_buf[GET_STR_BUF_SIZE], value_buf[GET_STR_BUF_SIZE]; + const char *key, *value; + size_t key_size, value_size; + + sprintf(tag_name, "png_text%d_key", i); + key = get_string2(&im->tags, tag_name, key_buf, &key_size); + + if (key) { + size_t k; + if (key_size < 1 || key_size > 79) { + i_push_errorf(0, "tag %s must be between 1 and 79 characters in length", tag_name); + return 0; + } + + if (key[0] == ' ' || key[key_size-1] == ' ') { + i_push_errorf(0, "tag %s may not contain leading or trailing spaces", tag_name); + return 0; + } + + if (strstr(key, " ")) { + i_push_errorf(0, "tag %s may not contain consecutive spaces", tag_name); + return 0; + } + + for (k = 0; k < key_size; ++k) { + if (key[k] < 32 || (key[k] > 126 && key[k] < 161)) { + i_push_errorf(0, "tag %s may only contain Latin1 characters 32-126, 161-255", tag_name); + return 0; + } + } + } + + sprintf(tag_name, "png_text%d_text", i); + value = get_string2(&im->tags, tag_name, value_buf, &value_size); + + if (value) { + if (memchr(value, '\0', value_size)) { + i_push_errorf(0, "tag %s may not contain NUL characters", tag_name); + return 0; + } + } + + if (key && value) { + png_text text; + int compression = value_size > 1000; + + sprintf(tag_name, "png_text%d_compressed", i); + i_tags_get_int(&im->tags, tag_name, 0, &compression); + + text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt + : PNG_TEXT_COMPRESSION_NONE; + text.key = (char *)key; + text.text_length = value_size; + text.text = (char *)value; +#ifdef PNG_iTXt_SUPPORTED + text.itxt_length = 0; + text.lang = NULL; + text.lang_key = NULL; +#endif + + png_set_text(png_ptr, info_ptr, &text, 1); + } + else if (key) { + i_push_errorf(0, "tag png_text%d_key found but not png_text%d_text", i, i); + return 0; + } + else if (value) { + i_push_errorf(0, "tag png_text%d_text found but not png_text%d_key", i, i); + return 0; + } + else { + break; + } + ++i; + } + } + + { + char buf[GET_STR_BUF_SIZE]; + size_t time_size; + const char *timestr = get_string2(&im->tags, "png_time", buf, &time_size); + + if (timestr) { + int year, month, day, hour, minute, second; + png_time mod_time; + + if (sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) { + /* rough validation */ + if (month < 1 || month > 12 + || day < 1 || day > 31 + || hour < 0 || hour > 23 + || minute < 0 || minute > 59 + || second < 0 || second > 60) { + i_push_error(0, "invalid date/time for png_time"); + return 0; + } + mod_time.year = year; + mod_time.month = month; + mod_time.day = day; + mod_time.hour = hour; + mod_time.minute = minute; + mod_time.second = second; + + png_set_tIME(png_ptr, info_ptr, &mod_time); + } + else { + i_push_error(0, "png_time must be formatted 'y-m-dTh:m:s'"); + return 0; + } + } + } + + { + int level; + if (i_tags_get_int(&im->tags, "png_compression_level", 0, &level)) { + if (level >= Z_NO_COMPRESSION && level <= Z_BEST_COMPRESSION) + png_set_compression_level(png_ptr, level); + else { + i_push_errorf(0, "png_compression_level must be between %d and %d", + Z_NO_COMPRESSION, Z_BEST_COMPRESSION); + return 0; + } + } + } + + { + /* no bKGD support yet, maybe later + it may be simpler to do it in the individual writers + */ + } + + return 1; +} + +static const char * +get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size) { + int index; + + if (i_tags_find(tags, name, 0, &index)) { + const i_img_tag *entry = tags->tags + index; + + if (entry->data) { + *size = entry->size; + + return entry->data; + } + else { + *size = sprintf(buf, "%d", entry->idata); + + return buf; + } + } + return NULL; +} + +static int +write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) { + unsigned char *data, *volatile vdata = NULL; + i_img_dim y; + + if (setjmp(png_jmpbuf(png_ptr))) { + if (vdata) + myfree(vdata); + + return 0; + } + + png_write_info(png_ptr, info_ptr); + + vdata = data = mymalloc(im->xsize * im->channels); + for (y = 0; y < im->ysize; y++) { + i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels); + png_write_row(png_ptr, (png_bytep)data); + } + myfree(data); + + return 1; +} + +static int +write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) { + unsigned *data, *volatile vdata = NULL; + unsigned char *tran_data, * volatile vtran_data = NULL; + i_img_dim samples_per_row = im->xsize * im->channels; + + i_img_dim y; + + if (setjmp(png_jmpbuf(png_ptr))) { + if (vdata) + myfree(vdata); + if (vtran_data) + myfree(vtran_data); + + return 0; + } + + png_write_info(png_ptr, info_ptr); + + vdata = data = mymalloc(samples_per_row * sizeof(unsigned)); + vtran_data = tran_data = mymalloc(samples_per_row * 2); + for (y = 0; y < im->ysize; y++) { + i_img_dim i; + unsigned char *p = tran_data; + i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16); + for (i = 0; i < samples_per_row; ++i) { + p[0] = data[i] >> 8; + p[1] = data[i] & 0xff; + p += 2; + } + png_write_row(png_ptr, (png_bytep)tran_data); + } + myfree(tran_data); + myfree(data); + + return 1; +} + +static int +write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) { + unsigned char *data, *volatile vdata = NULL; + i_img_dim y; + unsigned char pal_map[256]; + png_color pcolors[256]; + i_color colors[256]; + int count = i_colorcount(im); + int i; + + if (setjmp(png_jmpbuf(png_ptr))) { + if (vdata) + myfree(vdata); + + return 0; + } + + i_getcolors(im, 0, colors, count); + if (im->channels < 3) { + /* convert the greyscale palette to color */ + int i; + for (i = 0; i < count; ++i) { + i_color *c = colors + i; + c->channel[3] = c->channel[1]; + c->channel[2] = c->channel[1] = c->channel[0]; + } + } + + if (i_img_has_alpha(im)) { + int i; + int bottom_index = 0, top_index = count-1; + + /* fill out the palette map */ + for (i = 0; i < count; ++i) + pal_map[i] = i; + + /* the PNG spec suggests sorting the palette by alpha, but that's + unnecessary - all we want to do is move the opaque entries to + the end */ + while (bottom_index < top_index) { + if (colors[bottom_index].rgba.a == 255) { + pal_map[bottom_index] = top_index; + pal_map[top_index--] = bottom_index; + } + ++bottom_index; + } + } + + for (i = 0; i < count; ++i) { + int srci = i_img_has_alpha(im) ? pal_map[i] : i; + + pcolors[i].red = colors[srci].rgb.r; + pcolors[i].green = colors[srci].rgb.g; + pcolors[i].blue = colors[srci].rgb.b; + } + + png_set_PLTE(png_ptr, info_ptr, pcolors, count); + + if (i_img_has_alpha(im)) { + unsigned char trans[256]; + int i; + + for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) { + trans[i] = colors[pal_map[i]].rgba.a; + } + png_set_tRNS(png_ptr, info_ptr, trans, i, NULL); + } + + png_write_info(png_ptr, info_ptr); + + png_set_packing(png_ptr); + + vdata = data = mymalloc(im->xsize); + for (y = 0; y < im->ysize; y++) { + i_gpal(im, 0, im->xsize, y, data); + if (i_img_has_alpha(im)) { + i_img_dim x; + for (x = 0; x < im->xsize; ++x) + data[x] = pal_map[data[x]]; + } + png_write_row(png_ptr, (png_bytep)data); + } + myfree(data); + + return 1; +} + +static int +write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) { + unsigned char *data, *volatile vdata = NULL; + i_img_dim y; + + if (setjmp(png_jmpbuf(png_ptr))) { + if (vdata) + myfree(vdata); + + return 0; + } + + png_write_info(png_ptr, info_ptr); + + png_set_packing(png_ptr); + + vdata = data = mymalloc(im->xsize); + for (y = 0; y < im->ysize; y++) { + i_gsamp(im, 0, im->xsize, y, data, NULL, 1); + png_write_row(png_ptr, (png_bytep)data); + } + myfree(data); + + return 1; +} + static void read_warn_handler(png_structp png_ptr, png_const_charp msg) { i_png_read_statep rs = (i_png_read_statep)png_get_error_ptr(png_ptr);