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 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
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;
fprintf(stderr, "Internal error, channels = %d\n", channels);
abort();
}
- bits = 8;
+ bits = im->bits > 8 ? 16 : 8;
mm_log((1, "i_writepng: direct output\n"));
}
*/
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
- if (vdata)
- myfree(vdata);
return(0);
}
*/
png_set_user_limits(png_ptr, width, height);
- mm_log((1, ">png_set_IHDR\n"));
png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
- mm_log((1, "<png_set_IHDR\n"));
- 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);
+ if (!set_png_tags(im, png_ptr, info_ptr)) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return 0;
}
if (is_bilevel) {
return 0;
}
}
- else {
+ 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;
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;
}
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);
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;
/* 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);
}
}
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;
+ }
}
}
+
+ {
+ /* 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
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;