Commit | Line | Data |
---|---|---|
1d7e3124 | 1 | #include "impng.h" |
02d1d628 | 2 | #include "png.h" |
21c6936b | 3 | #include <stdlib.h> |
02d1d628 | 4 | |
02d1d628 AMH |
5 | /* this is a way to get number of channels from color space |
6 | * Color code to channel number */ | |
7 | ||
b33c08f8 | 8 | static int CC2C[PNG_COLOR_MASK_PALETTE|PNG_COLOR_MASK_COLOR|PNG_COLOR_MASK_ALPHA]; |
02d1d628 AMH |
9 | |
10 | #define PNG_BYTES_TO_CHECK 4 | |
cfb628e2 TC |
11 | |
12 | static i_img * | |
13 | read_direct8(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height); | |
14 | ||
c631b2d5 TC |
15 | static i_img * |
16 | read_direct16(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height); | |
17 | ||
963d3602 TC |
18 | static i_img * |
19 | read_paletted(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height); | |
20 | ||
79b4c849 TC |
21 | static i_img * |
22 | read_bilevel(png_structp png_ptr, png_infop info_ptr, i_img_dim width, i_img_dim height); | |
23 | ||
81089826 TC |
24 | static int |
25 | write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im); | |
26 | ||
d9610331 TC |
27 | static int |
28 | write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im); | |
29 | ||
21c6936b TC |
30 | static int |
31 | write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits); | |
32 | ||
b8961d05 TC |
33 | static int |
34 | write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im); | |
21c6936b | 35 | |
d0f15206 TC |
36 | static void |
37 | get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth, int color_type); | |
38 | ||
39 | static int | |
40 | set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr); | |
41 | ||
42 | static const char * | |
43 | get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size); | |
44 | ||
647508aa TC |
45 | unsigned |
46 | i_png_lib_version(void) { | |
47 | return png_access_version_number(); | |
48 | } | |
02d1d628 | 49 | |
02d1d628 | 50 | static void |
790923a4 | 51 | wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { |
f5b4354e | 52 | io_glue *ig = png_get_io_ptr(png_ptr); |
38eab175 | 53 | ssize_t rc = i_io_read(ig, data, length); |
790923a4 | 54 | if (rc != length) png_error(png_ptr, "Read overflow error on an iolayer source."); |
02d1d628 AMH |
55 | } |
56 | ||
02d1d628 | 57 | static void |
790923a4 | 58 | wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { |
38eab175 | 59 | ssize_t rc; |
f5b4354e | 60 | io_glue *ig = png_get_io_ptr(png_ptr); |
6d5c85a2 | 61 | rc = i_io_write(ig, data, length); |
790923a4 | 62 | if (rc != length) png_error(png_ptr, "Write error on an iolayer source."); |
02d1d628 AMH |
63 | } |
64 | ||
65 | static void | |
790923a4 | 66 | wiol_flush_data(png_structp png_ptr) { |
6352c169 TC |
67 | io_glue *ig = png_get_io_ptr(png_ptr); |
68 | if (!i_io_flush(ig)) | |
69 | png_error(png_ptr, "Error flushing output"); | |
02d1d628 | 70 | } |
02d1d628 | 71 | |
38eab175 TC |
72 | static void |
73 | error_handler(png_structp png_ptr, png_const_charp msg) { | |
74 | mm_log((1, "PNG error: '%s'\n", msg)); | |
75 | ||
76 | i_push_error(0, msg); | |
77 | longjmp(png_jmpbuf(png_ptr), 1); | |
78 | } | |
79 | ||
80 | /* | |
81 | ||
963d3602 TC |
82 | For writing a warning might have information about an error, so send |
83 | it to the error stack. | |
38eab175 TC |
84 | |
85 | */ | |
86 | static void | |
87 | write_warn_handler(png_structp png_ptr, png_const_charp msg) { | |
88 | mm_log((1, "PNG write warning '%s'\n", msg)); | |
89 | ||
90 | i_push_error(0, msg); | |
91 | } | |
92 | ||
93 | #define PNG_DIM_MAX 0x7fffffffL | |
94 | ||
02d1d628 | 95 | undef_int |
790923a4 | 96 | i_writepng_wiol(i_img *im, io_glue *ig) { |
02d1d628 | 97 | png_structp png_ptr; |
a807aae6 | 98 | png_infop info_ptr = NULL; |
0a53238e | 99 | i_img_dim width,height; |
02d1d628 | 100 | volatile int cspace,channels; |
21c6936b | 101 | int bits; |
b8961d05 | 102 | int is_bilevel = 0, zero_is_white; |
02d1d628 | 103 | |
790923a4 | 104 | mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig)); |
8d14daab TC |
105 | |
106 | i_clear_error(); | |
107 | ||
108 | if (im->xsize > PNG_UINT_31_MAX || im->ysize > PNG_UINT_31_MAX) { | |
109 | i_push_error(0, "image too large for PNG"); | |
110 | return 0; | |
111 | } | |
112 | ||
790923a4 AMH |
113 | height = im->ysize; |
114 | width = im->xsize; | |
02d1d628 | 115 | |
38eab175 TC |
116 | /* if we ever have 64-bit i_img_dim |
117 | * the libpng docs state that png_set_user_limits() can be used to | |
118 | * override the PNG_USER_*_MAX limits, but as implemented they | |
119 | * don't. We check against the theoretical limit of PNG here, and | |
120 | * try to override the limits below, in case the libpng | |
121 | * implementation ever matches the documentation. | |
122 | * | |
123 | * https://sourceforge.net/tracker/?func=detail&atid=105624&aid=3314943&group_id=5624 | |
647508aa | 124 | * fixed in libpng 1.5.3 |
34e56776 | 125 | */ |
38eab175 TC |
126 | if (width > PNG_DIM_MAX || height > PNG_DIM_MAX) { |
127 | i_push_error(0, "Image too large for PNG"); | |
128 | return 0; | |
129 | } | |
130 | ||
02d1d628 AMH |
131 | channels=im->channels; |
132 | ||
b8961d05 TC |
133 | if (i_img_is_monochrome(im, &zero_is_white)) { |
134 | is_bilevel = 1; | |
135 | bits = 1; | |
136 | cspace = PNG_COLOR_TYPE_GRAY; | |
137 | mm_log((1, "i_writepng: bilevel output\n")); | |
138 | } | |
139 | else if (im->type == i_palette_type) { | |
21c6936b TC |
140 | int colors = i_colorcount(im); |
141 | ||
142 | cspace = PNG_COLOR_TYPE_PALETTE; | |
143 | bits = 1; | |
144 | while ((1 << bits) < colors) { | |
145 | bits += bits; | |
146 | } | |
b8961d05 | 147 | mm_log((1, "i_writepng: paletted output\n")); |
21c6936b TC |
148 | } |
149 | else { | |
150 | switch (channels) { | |
151 | case 1: | |
152 | cspace = PNG_COLOR_TYPE_GRAY; | |
153 | break; | |
154 | case 2: | |
155 | cspace = PNG_COLOR_TYPE_GRAY_ALPHA; | |
156 | break; | |
157 | case 3: | |
158 | cspace = PNG_COLOR_TYPE_RGB; | |
159 | break; | |
160 | case 4: | |
161 | cspace = PNG_COLOR_TYPE_RGB_ALPHA; | |
162 | break; | |
163 | default: | |
164 | fprintf(stderr, "Internal error, channels = %d\n", channels); | |
165 | abort(); | |
166 | } | |
d9610331 | 167 | bits = im->bits > 8 ? 16 : 8; |
b8961d05 | 168 | mm_log((1, "i_writepng: direct output\n")); |
21c6936b | 169 | } |
02d1d628 | 170 | |
b8961d05 | 171 | mm_log((1,"i_writepng: cspace=%d, bits=%d\n",cspace, bits)); |
02d1d628 AMH |
172 | |
173 | /* Create and initialize the png_struct with the desired error handler | |
174 | * functions. If you want to use the default stderr and longjump method, | |
175 | * you can supply NULL for the last three parameters. We also check that | |
176 | * the library version is compatible with the one used at compile time, | |
177 | * in case we are using dynamically linked libraries. REQUIRED. | |
178 | */ | |
179 | ||
38eab175 TC |
180 | png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, |
181 | error_handler, write_warn_handler); | |
02d1d628 | 182 | |
790923a4 AMH |
183 | if (png_ptr == NULL) return 0; |
184 | ||
02d1d628 AMH |
185 | |
186 | /* Allocate/initialize the image information data. REQUIRED */ | |
187 | info_ptr = png_create_info_struct(png_ptr); | |
188 | ||
189 | if (info_ptr == NULL) { | |
a807aae6 | 190 | png_destroy_write_struct(&png_ptr, &info_ptr); |
790923a4 | 191 | return 0; |
02d1d628 AMH |
192 | } |
193 | ||
194 | /* Set error handling. REQUIRED if you aren't supplying your own | |
195 | * error hadnling functions in the png_create_write_struct() call. | |
196 | */ | |
f5b4354e | 197 | if (setjmp(png_jmpbuf(png_ptr))) { |
a807aae6 | 198 | png_destroy_write_struct(&png_ptr, &info_ptr); |
02d1d628 AMH |
199 | return(0); |
200 | } | |
201 | ||
790923a4 | 202 | png_set_write_fn(png_ptr, (png_voidp) (ig), wiol_write_data, wiol_flush_data); |
02d1d628 AMH |
203 | |
204 | /* Set the image information here. Width and height are up to 2^31, | |
205 | * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on | |
206 | * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, | |
207 | * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, | |
208 | * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or | |
209 | * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST | |
210 | * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED | |
211 | */ | |
212 | ||
38eab175 TC |
213 | /* by default, libpng (not PNG) limits the image size to a maximum |
214 | * 1000000 pixels in each direction, but Imager doesn't. | |
215 | * Configure libpng to avoid that limit. | |
216 | */ | |
217 | png_set_user_limits(png_ptr, width, height); | |
218 | ||
21c6936b | 219 | png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace, |
02d1d628 AMH |
220 | PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
221 | ||
d0f15206 TC |
222 | if (!set_png_tags(im, png_ptr, info_ptr)) { |
223 | png_destroy_write_struct(&png_ptr, &info_ptr); | |
224 | return 0; | |
faa9b3e7 TC |
225 | } |
226 | ||
b8961d05 TC |
227 | if (is_bilevel) { |
228 | if (!write_bilevel(png_ptr, info_ptr, im)) { | |
229 | png_destroy_write_struct(&png_ptr, &info_ptr); | |
230 | return 0; | |
231 | } | |
232 | } | |
233 | else if (im->type == i_palette_type) { | |
21c6936b TC |
234 | if (!write_paletted(png_ptr, info_ptr, im, bits)) { |
235 | png_destroy_write_struct(&png_ptr, &info_ptr); | |
236 | return 0; | |
237 | } | |
238 | } | |
d9610331 TC |
239 | else if (bits == 16) { |
240 | if (!write_direct16(png_ptr, info_ptr, im)) { | |
241 | png_destroy_write_struct(&png_ptr, &info_ptr); | |
242 | return 0; | |
243 | } | |
244 | } | |
245 | else { | |
21c6936b TC |
246 | if (!write_direct8(png_ptr, info_ptr, im)) { |
247 | png_destroy_write_struct(&png_ptr, &info_ptr); | |
248 | return 0; | |
249 | } | |
faa9b3e7 | 250 | } |
790923a4 | 251 | |
02d1d628 | 252 | png_write_end(png_ptr, info_ptr); |
790923a4 | 253 | |
a807aae6 | 254 | png_destroy_write_struct(&png_ptr, &info_ptr); |
02d1d628 | 255 | |
6d5c85a2 TC |
256 | if (i_io_close(ig)) |
257 | return 0; | |
10461f9a | 258 | |
02d1d628 AMH |
259 | return(1); |
260 | } | |
261 | ||
38eab175 TC |
262 | typedef struct { |
263 | char *warnings; | |
264 | } i_png_read_state, *i_png_read_statep; | |
02d1d628 | 265 | |
38eab175 TC |
266 | static void |
267 | read_warn_handler(png_structp, png_const_charp); | |
02d1d628 | 268 | |
38eab175 TC |
269 | static void |
270 | cleanup_read_state(i_png_read_statep); | |
02d1d628 AMH |
271 | |
272 | i_img* | |
1d7e3124 | 273 | i_readpng_wiol(io_glue *ig) { |
02576e8d | 274 | i_img *im = NULL; |
02d1d628 AMH |
275 | png_structp png_ptr; |
276 | png_infop info_ptr; | |
790923a4 | 277 | png_uint_32 width, height; |
02d1d628 | 278 | int bit_depth, color_type, interlace_type; |
432d0ff0 | 279 | int channels; |
02d1d628 | 280 | unsigned int sig_read; |
38eab175 | 281 | i_png_read_state rs; |
02d1d628 | 282 | |
38eab175 | 283 | rs.warnings = NULL; |
790923a4 | 284 | sig_read = 0; |
02d1d628 | 285 | |
1d7e3124 | 286 | mm_log((1,"i_readpng_wiol(ig %p)\n", ig)); |
38eab175 | 287 | i_clear_error(); |
02d1d628 | 288 | |
38eab175 TC |
289 | png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &rs, |
290 | error_handler, read_warn_handler); | |
291 | if (!png_ptr) { | |
292 | i_push_error(0, "Cannot create PNG read structure"); | |
293 | return NULL; | |
294 | } | |
790923a4 | 295 | png_set_read_fn(png_ptr, (png_voidp) (ig), wiol_read_data); |
02d1d628 AMH |
296 | |
297 | info_ptr = png_create_info_struct(png_ptr); | |
298 | if (info_ptr == NULL) { | |
299 | png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
38eab175 | 300 | i_push_error(0, "Cannot create PNG info structure"); |
02d1d628 AMH |
301 | return NULL; |
302 | } | |
303 | ||
f5b4354e | 304 | if (setjmp(png_jmpbuf(png_ptr))) { |
02576e8d | 305 | if (im) i_img_destroy(im); |
790923a4 | 306 | mm_log((1,"i_readpng_wiol: error.\n")); |
02d1d628 | 307 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); |
38eab175 | 308 | cleanup_read_state(&rs); |
02d1d628 AMH |
309 | return NULL; |
310 | } | |
647508aa TC |
311 | |
312 | /* we do our own limit checks */ | |
313 | png_set_user_limits(png_ptr, PNG_DIM_MAX, PNG_DIM_MAX); | |
790923a4 | 314 | |
02d1d628 AMH |
315 | png_set_sig_bytes(png_ptr, sig_read); |
316 | png_read_info(png_ptr, info_ptr); | |
317 | png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); | |
318 | ||
5e84d110 TC |
319 | mm_log((1, "png_get_IHDR results: width %u, height %u, bit_depth %d, color_type %d, interlace_type %d\n", |
320 | (unsigned)width, (unsigned)height, bit_depth,color_type,interlace_type)); | |
02d1d628 AMH |
321 | |
322 | CC2C[PNG_COLOR_TYPE_GRAY]=1; | |
323 | CC2C[PNG_COLOR_TYPE_PALETTE]=3; | |
324 | CC2C[PNG_COLOR_TYPE_RGB]=3; | |
325 | CC2C[PNG_COLOR_TYPE_RGB_ALPHA]=4; | |
326 | CC2C[PNG_COLOR_TYPE_GRAY_ALPHA]=2; | |
790923a4 AMH |
327 | channels = CC2C[color_type]; |
328 | ||
329 | mm_log((1,"i_readpng_wiol: channels %d\n",channels)); | |
330 | ||
77157728 TC |
331 | if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) { |
332 | mm_log((1, "i_readpnm: image size exceeds limits\n")); | |
333 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
334 | return NULL; | |
335 | } | |
336 | ||
963d3602 TC |
337 | if (color_type == PNG_COLOR_TYPE_PALETTE) { |
338 | im = read_paletted(png_ptr, info_ptr, channels, width, height); | |
339 | } | |
79b4c849 TC |
340 | else if (color_type == PNG_COLOR_TYPE_GRAY |
341 | && bit_depth == 1 | |
342 | && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { | |
343 | im = read_bilevel(png_ptr, info_ptr, width, height); | |
344 | } | |
c631b2d5 TC |
345 | else if (bit_depth == 16) { |
346 | im = read_direct16(png_ptr, info_ptr, channels, width, height); | |
347 | } | |
963d3602 TC |
348 | else { |
349 | im = read_direct8(png_ptr, info_ptr, channels, width, height); | |
350 | } | |
cfb628e2 TC |
351 | |
352 | if (im) | |
6fa6c8ee | 353 | get_png_tags(im, png_ptr, info_ptr, bit_depth, color_type); |
cfb628e2 TC |
354 | |
355 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
356 | ||
357 | if (im) { | |
358 | if (rs.warnings) { | |
359 | i_tags_set(&im->tags, "png_warnings", rs.warnings, -1); | |
360 | } | |
361 | } | |
362 | cleanup_read_state(&rs); | |
363 | ||
364 | mm_log((1,"(%p) <- i_readpng_wiol\n", im)); | |
365 | ||
366 | return im; | |
367 | } | |
368 | ||
369 | static i_img * | |
370 | read_direct8(png_structp png_ptr, png_infop info_ptr, int channels, | |
371 | i_img_dim width, i_img_dim height) { | |
372 | i_img * volatile vim = NULL; | |
373 | int color_type = png_get_color_type(png_ptr, info_ptr); | |
374 | int bit_depth = png_get_bit_depth(png_ptr, info_ptr); | |
375 | i_img_dim y; | |
376 | int number_passes, pass; | |
377 | i_img *im; | |
34e56776 TC |
378 | unsigned char *line; |
379 | unsigned char * volatile vline = NULL; | |
cfb628e2 TC |
380 | |
381 | if (setjmp(png_jmpbuf(png_ptr))) { | |
382 | if (vim) i_img_destroy(vim); | |
34e56776 | 383 | if (vline) myfree(vline); |
cfb628e2 TC |
384 | |
385 | return NULL; | |
386 | } | |
387 | ||
388 | number_passes = png_set_interlace_handling(png_ptr); | |
389 | mm_log((1,"number of passes=%d\n",number_passes)); | |
390 | ||
02d1d628 AMH |
391 | png_set_strip_16(png_ptr); |
392 | png_set_packing(png_ptr); | |
6829f5f6 | 393 | |
cfb628e2 TC |
394 | if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) |
395 | png_set_expand(png_ptr); | |
396 | ||
6829f5f6 AMH |
397 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
398 | channels++; | |
399 | mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels)); | |
400 | png_set_expand(png_ptr); | |
401 | } | |
790923a4 | 402 | |
02d1d628 | 403 | png_read_update_info(png_ptr, info_ptr); |
790923a4 | 404 | |
cfb628e2 | 405 | im = vim = i_img_8_new(width,height,channels); |
352c64ed TC |
406 | if (!im) { |
407 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
408 | return NULL; | |
409 | } | |
02d1d628 | 410 | |
34e56776 | 411 | line = vline = mymalloc(channels * width); |
cfb628e2 TC |
412 | for (pass = 0; pass < number_passes; pass++) { |
413 | for (y = 0; y < height; y++) { | |
34e56776 TC |
414 | if (pass > 0) |
415 | i_gsamp(im, 0, width, y, line, NULL, channels); | |
416 | png_read_row(png_ptr,(png_bytep)line, NULL); | |
417 | i_psamp(im, 0, width, y, line, NULL, channels); | |
cfb628e2 | 418 | } |
38eab175 | 419 | } |
34e56776 TC |
420 | myfree(line); |
421 | vline = NULL; | |
02d1d628 | 422 | |
cfb628e2 TC |
423 | png_read_end(png_ptr, info_ptr); |
424 | ||
790923a4 AMH |
425 | return im; |
426 | } | |
faa9b3e7 | 427 | |
c631b2d5 TC |
428 | static i_img * |
429 | read_direct16(png_structp png_ptr, png_infop info_ptr, int channels, | |
430 | i_img_dim width, i_img_dim height) { | |
431 | i_img * volatile vim = NULL; | |
c631b2d5 TC |
432 | i_img_dim x, y; |
433 | int number_passes, pass; | |
434 | i_img *im; | |
435 | unsigned char *line; | |
436 | unsigned char * volatile vline = NULL; | |
437 | unsigned *bits_line; | |
438 | unsigned * volatile vbits_line = NULL; | |
439 | size_t row_bytes; | |
440 | ||
441 | if (setjmp(png_jmpbuf(png_ptr))) { | |
442 | if (vim) i_img_destroy(vim); | |
443 | if (vline) myfree(vline); | |
444 | if (vbits_line) myfree(vbits_line); | |
445 | ||
446 | return NULL; | |
447 | } | |
448 | ||
449 | number_passes = png_set_interlace_handling(png_ptr); | |
450 | mm_log((1,"number of passes=%d\n",number_passes)); | |
451 | ||
452 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { | |
453 | channels++; | |
454 | mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels)); | |
455 | png_set_expand(png_ptr); | |
456 | } | |
457 | ||
458 | png_read_update_info(png_ptr, info_ptr); | |
459 | ||
460 | im = vim = i_img_16_new(width,height,channels); | |
461 | if (!im) { | |
462 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
463 | return NULL; | |
464 | } | |
465 | ||
466 | row_bytes = png_get_rowbytes(png_ptr, info_ptr); | |
467 | line = vline = mymalloc(row_bytes); | |
468 | memset(line, 0, row_bytes); | |
469 | bits_line = vbits_line = mymalloc(sizeof(unsigned) * width * channels); | |
470 | for (pass = 0; pass < number_passes; pass++) { | |
471 | for (y = 0; y < height; y++) { | |
472 | if (pass > 0) { | |
473 | i_gsamp_bits(im, 0, width, y, bits_line, NULL, channels, 16); | |
474 | for (x = 0; x < width * channels; ++x) { | |
475 | line[x*2] = bits_line[x] >> 8; | |
476 | line[x*2+1] = bits_line[x] & 0xff; | |
477 | } | |
478 | } | |
479 | png_read_row(png_ptr,(png_bytep)line, NULL); | |
480 | for (x = 0; x < width * channels; ++x) | |
481 | bits_line[x] = (line[x*2] << 8) + line[x*2+1]; | |
482 | i_psamp_bits(im, 0, width, y, bits_line, NULL, channels, 16); | |
483 | } | |
484 | } | |
485 | myfree(line); | |
486 | myfree(bits_line); | |
487 | vline = NULL; | |
488 | vbits_line = NULL; | |
489 | ||
490 | png_read_end(png_ptr, info_ptr); | |
491 | ||
492 | return im; | |
493 | } | |
494 | ||
79b4c849 TC |
495 | static i_img * |
496 | read_bilevel(png_structp png_ptr, png_infop info_ptr, | |
497 | i_img_dim width, i_img_dim height) { | |
498 | i_img * volatile vim = NULL; | |
499 | i_img_dim x, y; | |
500 | int number_passes, pass; | |
501 | i_img *im; | |
502 | unsigned char *line; | |
503 | unsigned char * volatile vline = NULL; | |
504 | i_color palette[2]; | |
505 | ||
506 | if (setjmp(png_jmpbuf(png_ptr))) { | |
507 | if (vim) i_img_destroy(vim); | |
508 | if (vline) myfree(vline); | |
509 | ||
510 | return NULL; | |
511 | } | |
512 | ||
513 | number_passes = png_set_interlace_handling(png_ptr); | |
514 | mm_log((1,"number of passes=%d\n",number_passes)); | |
515 | ||
516 | png_set_packing(png_ptr); | |
517 | ||
518 | png_set_expand(png_ptr); | |
519 | ||
520 | png_read_update_info(png_ptr, info_ptr); | |
521 | ||
522 | im = vim = i_img_pal_new(width, height, 1, 256); | |
523 | if (!im) { | |
524 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
525 | return NULL; | |
526 | } | |
527 | ||
528 | palette[0].channel[0] = palette[0].channel[1] = palette[0].channel[2] = | |
529 | palette[0].channel[3] = 0; | |
530 | palette[1].channel[0] = palette[1].channel[1] = palette[1].channel[2] = | |
531 | palette[1].channel[3] = 255; | |
532 | i_addcolors(im, palette, 2); | |
533 | ||
534 | line = vline = mymalloc(width); | |
535 | memset(line, 0, width); | |
536 | for (pass = 0; pass < number_passes; pass++) { | |
537 | for (y = 0; y < height; y++) { | |
538 | if (pass > 0) { | |
539 | i_gpal(im, 0, width, y, line); | |
540 | /* expand indexes back to 0/255 */ | |
541 | for (x = 0; x < width; ++x) | |
542 | line[x] = line[x] ? 255 : 0; | |
543 | } | |
544 | png_read_row(png_ptr,(png_bytep)line, NULL); | |
545 | ||
546 | /* back to palette indexes */ | |
547 | for (x = 0; x < width; ++x) | |
548 | line[x] = line[x] ? 1 : 0; | |
549 | i_ppal(im, 0, width, y, line); | |
550 | } | |
551 | } | |
552 | myfree(line); | |
553 | vline = NULL; | |
554 | ||
555 | png_read_end(png_ptr, info_ptr); | |
556 | ||
557 | return im; | |
558 | } | |
559 | ||
560 | /* FIXME: do we need to unscale palette color values from the | |
561 | supplied alphas? */ | |
963d3602 TC |
562 | static i_img * |
563 | read_paletted(png_structp png_ptr, png_infop info_ptr, int channels, | |
564 | i_img_dim width, i_img_dim height) { | |
565 | i_img * volatile vim = NULL; | |
566 | int color_type = png_get_color_type(png_ptr, info_ptr); | |
567 | int bit_depth = png_get_bit_depth(png_ptr, info_ptr); | |
568 | i_img_dim y; | |
569 | int number_passes, pass; | |
570 | i_img *im; | |
571 | unsigned char *line; | |
572 | unsigned char * volatile vline = NULL; | |
573 | int num_palette, i; | |
574 | png_colorp png_palette; | |
575 | png_bytep png_pal_trans; | |
576 | png_color_16p png_color_trans; | |
577 | int num_pal_trans; | |
578 | ||
579 | if (setjmp(png_jmpbuf(png_ptr))) { | |
580 | if (vim) i_img_destroy(vim); | |
581 | if (vline) myfree(vline); | |
582 | ||
583 | return NULL; | |
584 | } | |
585 | ||
586 | number_passes = png_set_interlace_handling(png_ptr); | |
587 | mm_log((1,"number of passes=%d\n",number_passes)); | |
588 | ||
589 | png_set_strip_16(png_ptr); | |
590 | png_set_packing(png_ptr); | |
591 | ||
592 | if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) | |
593 | png_set_expand(png_ptr); | |
594 | ||
595 | if (!png_get_PLTE(png_ptr, info_ptr, &png_palette, &num_palette)) { | |
596 | i_push_error(0, "Paletted image with no PLTE chunk"); | |
597 | return NULL; | |
598 | } | |
599 | ||
600 | if (png_get_tRNS(png_ptr, info_ptr, &png_pal_trans, &num_pal_trans, | |
601 | &png_color_trans)) { | |
602 | channels++; | |
603 | } | |
604 | else { | |
605 | num_pal_trans = 0; | |
606 | } | |
607 | ||
608 | png_read_update_info(png_ptr, info_ptr); | |
609 | ||
610 | im = vim = i_img_pal_new(width, height, channels, 256); | |
611 | if (!im) { | |
612 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); | |
613 | return NULL; | |
614 | } | |
615 | ||
616 | for (i = 0; i < num_palette; ++i) { | |
617 | i_color c; | |
618 | ||
619 | c.rgba.r = png_palette[i].red; | |
620 | c.rgba.g = png_palette[i].green; | |
621 | c.rgba.b = png_palette[i].blue; | |
622 | if (i < num_pal_trans) | |
623 | c.rgba.a = png_pal_trans[i]; | |
624 | else | |
625 | c.rgba.a = 255; | |
626 | i_addcolors(im, &c, 1); | |
627 | } | |
628 | ||
629 | line = vline = mymalloc(width); | |
630 | for (pass = 0; pass < number_passes; pass++) { | |
631 | for (y = 0; y < height; y++) { | |
632 | if (pass > 0) | |
633 | i_gpal(im, 0, width, y, line); | |
634 | png_read_row(png_ptr,(png_bytep)line, NULL); | |
635 | i_ppal(im, 0, width, y, line); | |
636 | } | |
637 | } | |
638 | myfree(line); | |
639 | vline = NULL; | |
640 | ||
641 | png_read_end(png_ptr, info_ptr); | |
642 | ||
643 | return im; | |
644 | } | |
645 | ||
b51bcea5 TC |
646 | struct png_text_name { |
647 | const char *keyword; | |
648 | const char *tagname; | |
649 | }; | |
650 | ||
651 | static const struct png_text_name | |
652 | text_tags[] = { | |
653 | { "Author", "png_author" }, | |
654 | { "Comment", "i_comment" }, | |
655 | { "Copyright", "png_copyright" }, | |
656 | { "Creation Time", "png_creation_time" }, | |
657 | { "Description", "png_description" }, | |
658 | { "Disclaimer", "png_disclaimer" }, | |
659 | { "Software", "png_software" }, | |
660 | { "Source", "png_source" }, | |
661 | { "Title", "png_title" }, | |
662 | { "Warning", "png_warning" } | |
663 | }; | |
664 | ||
665 | static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags); | |
666 | ||
d0f15206 TC |
667 | static const char * const |
668 | chroma_tags[] = { | |
669 | "png_chroma_white_x", | |
670 | "png_chroma_white_y", | |
671 | "png_chroma_red_x", | |
672 | "png_chroma_red_y", | |
673 | "png_chroma_green_x", | |
674 | "png_chroma_green_y", | |
675 | "png_chroma_blue_x", | |
676 | "png_chroma_blue_y" | |
677 | }; | |
678 | ||
679 | static const int chroma_tag_count = sizeof(chroma_tags) / sizeof(*chroma_tags); | |
680 | ||
38eab175 | 681 | static void |
6fa6c8ee TC |
682 | get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, |
683 | int bit_depth, int color_type) { | |
faa9b3e7 TC |
684 | png_uint_32 xres, yres; |
685 | int unit_type; | |
352c64ed | 686 | |
1d7e3124 | 687 | i_tags_set(&im->tags, "i_format", "png", -1); |
faa9b3e7 | 688 | if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) { |
5e84d110 | 689 | mm_log((1,"pHYs (%u, %u) %d\n", (unsigned)xres, (unsigned)yres, unit_type)); |
faa9b3e7 | 690 | if (unit_type == PNG_RESOLUTION_METER) { |
2e41e30b TC |
691 | i_tags_set_float2(&im->tags, "i_xres", 0, xres * 0.0254, 5); |
692 | i_tags_set_float2(&im->tags, "i_yres", 0, yres * 0.0254, 5); | |
faa9b3e7 TC |
693 | } |
694 | else { | |
1d7e3124 TC |
695 | i_tags_setn(&im->tags, "i_xres", xres); |
696 | i_tags_setn(&im->tags, "i_yres", yres); | |
697 | i_tags_setn(&im->tags, "i_aspect_only", 1); | |
faa9b3e7 TC |
698 | } |
699 | } | |
a4fa5d5e TC |
700 | { |
701 | int interlace = png_get_interlace_type(png_ptr, info_ptr); | |
702 | ||
703 | i_tags_setn(&im->tags, "png_interlace", interlace != PNG_INTERLACE_NONE); | |
704 | switch (interlace) { | |
705 | case PNG_INTERLACE_NONE: | |
706 | i_tags_set(&im->tags, "png_interlace_name", "none", -1); | |
707 | break; | |
708 | ||
709 | case PNG_INTERLACE_ADAM7: | |
710 | i_tags_set(&im->tags, "png_interlace_name", "adam7", -1); | |
711 | break; | |
712 | ||
713 | default: | |
714 | i_tags_set(&im->tags, "png_interlace_name", "unknown", -1); | |
715 | break; | |
716 | } | |
00cff942 TC |
717 | } |
718 | ||
79b4c849 TC |
719 | /* the various readers can call png_set_expand(), libpng will make |
720 | it's internal record of bit_depth at least 8 in that case */ | |
721 | i_tags_setn(&im->tags, "png_bits", bit_depth); | |
b51bcea5 | 722 | |
86464bbf | 723 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
b51bcea5 TC |
724 | int intent; |
725 | if (png_get_sRGB(png_ptr, info_ptr, &intent)) { | |
726 | i_tags_setn(&im->tags, "png_srgb_intent", intent); | |
727 | } | |
728 | } | |
86464bbf TC |
729 | else { |
730 | /* Ignore these if there's an sRGB chunk, libpng simulates | |
731 | their existence if there's an sRGB chunk, and the PNG spec says | |
732 | that these are ignored if the sRGB is present, so ignore them. | |
733 | */ | |
b51bcea5 | 734 | double gamma; |
d0f15206 | 735 | double chroma[8]; |
86464bbf TC |
736 | |
737 | if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { | |
738 | i_tags_set_float2(&im->tags, "png_gamma", 0, gamma, 4); | |
739 | } | |
740 | ||
d0f15206 TC |
741 | if (png_get_cHRM(png_ptr, info_ptr, chroma+0, chroma+1, |
742 | chroma+2, chroma+3, chroma+4, chroma+5, | |
743 | chroma+6, chroma+7)) { | |
744 | int i; | |
745 | ||
746 | for (i = 0; i < chroma_tag_count; ++i) | |
747 | i_tags_set_float2(&im->tags, chroma_tags[i], 0, chroma[i], 4); | |
b51bcea5 TC |
748 | } |
749 | } | |
750 | ||
751 | { | |
752 | int num_text; | |
753 | png_text *text; | |
754 | ||
755 | if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { | |
756 | int i; | |
d0f15206 | 757 | int custom_index = 0; |
b51bcea5 TC |
758 | for (i = 0; i < num_text; ++i) { |
759 | int j; | |
86464bbf | 760 | int found = 0; |
d0f15206 TC |
761 | int compressed = text[i].compression == PNG_ITXT_COMPRESSION_zTXt |
762 | || text[i].compression == PNG_TEXT_COMPRESSION_zTXt; | |
b51bcea5 TC |
763 | |
764 | for (j = 0; j < text_tags_count; ++j) { | |
765 | if (strcmp(text_tags[j].keyword, text[i].key) == 0) { | |
d0f15206 | 766 | char tag_name[50]; |
b51bcea5 | 767 | i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1); |
d0f15206 TC |
768 | if (compressed) { |
769 | sprintf(tag_name, "%s_compressed", text_tags[j].tagname); | |
770 | i_tags_setn(&im->tags, tag_name, 1); | |
771 | } | |
86464bbf | 772 | found = 1; |
b51bcea5 TC |
773 | break; |
774 | } | |
775 | } | |
86464bbf TC |
776 | |
777 | if (!found) { | |
778 | char tag_name[50]; | |
d0f15206 | 779 | sprintf(tag_name, "png_text%d_key", custom_index); |
86464bbf | 780 | i_tags_set(&im->tags, tag_name, text[i].key, -1); |
d0f15206 | 781 | sprintf(tag_name, "png_text%d_text", custom_index); |
86464bbf | 782 | i_tags_set(&im->tags, tag_name, text[i].text, -1); |
d0f15206 | 783 | sprintf(tag_name, "png_text%d_type", custom_index); |
86464bbf TC |
784 | i_tags_set(&im->tags, tag_name, |
785 | (text[i].compression == PNG_TEXT_COMPRESSION_NONE | |
786 | || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ? | |
787 | "text" : "itxt", -1); | |
d0f15206 TC |
788 | if (compressed) { |
789 | sprintf(tag_name, "png_text%d_compressed", custom_index); | |
790 | i_tags_setn(&im->tags, tag_name, 1); | |
791 | } | |
792 | ++custom_index; | |
86464bbf | 793 | } |
b51bcea5 TC |
794 | } |
795 | } | |
796 | } | |
86464bbf TC |
797 | |
798 | { | |
799 | png_time *mod_time; | |
800 | ||
801 | if (png_get_tIME(png_ptr, info_ptr, &mod_time)) { | |
802 | char time_formatted[80]; | |
803 | ||
804 | sprintf(time_formatted, "%d-%02d-%02dT%02d:%02d:%02d", | |
805 | mod_time->year, mod_time->month, mod_time->day, | |
806 | mod_time->hour, mod_time->minute, mod_time->second); | |
807 | i_tags_set(&im->tags, "png_time", time_formatted, -1); | |
808 | } | |
809 | } | |
6fa6c8ee TC |
810 | |
811 | { | |
812 | png_color_16 *back; | |
813 | i_color c; | |
814 | ||
815 | if (png_get_bKGD(png_ptr, info_ptr, &back)) { | |
816 | switch (color_type) { | |
817 | case PNG_COLOR_TYPE_GRAY: | |
818 | case PNG_COLOR_TYPE_GRAY_ALPHA: | |
819 | { | |
820 | /* lib png stores the raw gray value rather than scaling it | |
821 | to 16-bit (or 8), we use 8-bit color for i_background */ | |
822 | ||
823 | int gray; | |
824 | switch (bit_depth) { | |
825 | case 16: | |
826 | gray = back->gray >> 8; | |
827 | break; | |
828 | case 8: | |
829 | gray = back->gray; | |
830 | break; | |
831 | case 4: | |
832 | gray = 0x11 * back->gray; | |
833 | break; | |
834 | case 2: | |
835 | gray = 0x55 * back->gray; | |
836 | break; | |
837 | case 1: | |
838 | gray = back->gray ? 0xFF : 0; | |
839 | break; | |
840 | default: | |
841 | gray = 0; | |
842 | } | |
843 | c.rgb.r = c.rgb.g = c.rgb.b = gray; | |
844 | break; | |
845 | } | |
846 | ||
847 | case PNG_COLOR_TYPE_RGB: | |
848 | case PNG_COLOR_TYPE_RGB_ALPHA: | |
849 | { | |
850 | c.rgb.r = bit_depth == 16 ? (back->red >> 8) : back->red; | |
851 | c.rgb.g = bit_depth == 16 ? (back->green >> 8) : back->green; | |
852 | c.rgb.b = bit_depth == 16 ? (back->blue >> 8) : back->blue; | |
853 | break; | |
854 | } | |
855 | ||
856 | case PNG_COLOR_TYPE_PALETTE: | |
857 | c.rgb.r = back->red; | |
858 | c.rgb.g = back->green; | |
859 | c.rgb.b = back->blue; | |
860 | break; | |
861 | } | |
862 | ||
863 | c.rgba.a = 255; | |
864 | i_tags_set_color(&im->tags, "i_background", 0, &c); | |
865 | } | |
866 | } | |
faa9b3e7 | 867 | } |
38eab175 | 868 | |
d0f15206 TC |
869 | #define GET_STR_BUF_SIZE 40 |
870 | ||
871 | static int | |
872 | set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr) { | |
873 | double xres, yres; | |
874 | int aspect_only, have_res = 1; | |
875 | ||
876 | if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) { | |
877 | if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) | |
878 | ; /* nothing to do */ | |
879 | else | |
880 | yres = xres; | |
881 | } | |
882 | else { | |
883 | if (i_tags_get_float(&im->tags, "i_yres", 0, &yres)) | |
884 | xres = yres; | |
885 | else | |
886 | have_res = 0; | |
887 | } | |
888 | if (have_res) { | |
889 | aspect_only = 0; | |
890 | i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only); | |
891 | xres /= 0.0254; | |
892 | yres /= 0.0254; | |
893 | png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, | |
894 | aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER); | |
895 | } | |
896 | ||
897 | { | |
898 | int intent; | |
899 | if (i_tags_get_int(&im->tags, "png_srgb_intent", 0, &intent)) { | |
900 | if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) { | |
901 | i_push_error(0, "tag png_srgb_intent out of range"); | |
902 | return 0; | |
903 | } | |
904 | png_set_sRGB(png_ptr, info_ptr, intent); | |
905 | } | |
906 | else { | |
907 | double chroma[8], gamma; | |
908 | int i; | |
909 | int found_chroma_count = 0; | |
910 | ||
911 | for (i = 0; i < chroma_tag_count; ++i) { | |
912 | if (i_tags_get_float(&im->tags, chroma_tags[i], 0, chroma+i)) | |
913 | ++found_chroma_count; | |
914 | } | |
915 | ||
916 | if (found_chroma_count) { | |
917 | if (found_chroma_count != chroma_tag_count) { | |
918 | i_push_error(0, "all png_chroma_* tags must be supplied or none"); | |
919 | return 0; | |
920 | } | |
921 | ||
922 | png_set_cHRM(png_ptr, info_ptr, chroma[0], chroma[1], chroma[2], | |
923 | chroma[3], chroma[4], chroma[5], chroma[6], chroma[7]); | |
924 | } | |
925 | ||
926 | if (i_tags_get_float(&im->tags, "png_gamma", 0, &gamma)) { | |
927 | png_set_gAMA(png_ptr, info_ptr, gamma); | |
928 | } | |
929 | } | |
930 | } | |
931 | ||
932 | { | |
933 | /* png_set_text() is sparsely documented, it isn't indicated whether | |
934 | multiple calls add to or replace the lists of texts, and | |
935 | whether the text/keyword data is copied or not. | |
936 | ||
937 | Examining the linpng code reveals that png_set_text() adds to | |
938 | the list and that the text is copied. | |
939 | */ | |
940 | int i; | |
941 | ||
942 | /* do our standard tags */ | |
943 | for (i = 0; i < text_tags_count; ++i) { | |
944 | char buf[GET_STR_BUF_SIZE]; | |
945 | size_t size; | |
946 | const char *data; | |
947 | ||
948 | data = get_string2(&im->tags, text_tags[i].tagname, buf, &size); | |
949 | if (data) { | |
950 | png_text text; | |
951 | int compression = size > 1000; | |
952 | char compress_tag[40]; | |
953 | ||
954 | if (memchr(data, '\0', size)) { | |
955 | i_push_errorf(0, "tag %s may not contain NUL characters", text_tags[i].tagname); | |
956 | return 0; | |
957 | } | |
958 | ||
959 | sprintf(compress_tag, "%s_compressed", text_tags[i].tagname); | |
960 | i_tags_get_int(&im->tags, compress_tag, 0, &compression); | |
961 | ||
962 | text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt | |
963 | : PNG_TEXT_COMPRESSION_NONE; | |
964 | text.key = (char *)text_tags[i].keyword; | |
965 | text.text_length = size; | |
966 | text.text = (char *)data; | |
967 | #ifdef PNG_iTXt_SUPPORTED | |
968 | text.itxt_length = 0; | |
969 | text.lang = NULL; | |
970 | text.lang_key = NULL; | |
971 | #endif | |
972 | ||
973 | png_set_text(png_ptr, info_ptr, &text, 1); | |
974 | } | |
975 | } | |
976 | ||
977 | /* for non-standard tags ensure keywords are limited to 1 to 79 | |
978 | characters */ | |
979 | i = 0; | |
980 | while (1) { | |
981 | char tag_name[50]; | |
982 | char key_buf[GET_STR_BUF_SIZE], value_buf[GET_STR_BUF_SIZE]; | |
983 | const char *key, *value; | |
984 | size_t key_size, value_size; | |
985 | ||
986 | sprintf(tag_name, "png_text%d_key", i); | |
987 | key = get_string2(&im->tags, tag_name, key_buf, &key_size); | |
988 | ||
989 | if (key) { | |
990 | size_t k; | |
991 | if (key_size < 1 || key_size > 79) { | |
992 | i_push_errorf(0, "tag %s must be between 1 and 79 characters in length", tag_name); | |
993 | return 0; | |
994 | } | |
995 | ||
996 | if (key[0] == ' ' || key[key_size-1] == ' ') { | |
997 | i_push_errorf(0, "tag %s may not contain leading or trailing spaces", tag_name); | |
998 | return 0; | |
999 | } | |
1000 | ||
1001 | if (strstr(key, " ")) { | |
1002 | i_push_errorf(0, "tag %s may not contain consecutive spaces", tag_name); | |
1003 | return 0; | |
1004 | } | |
1005 | ||
1006 | for (k = 0; k < key_size; ++k) { | |
0a53238e | 1007 | if (key[k] < 32 || (key[k] > 126 && key[k] < 161)) { |
d0f15206 TC |
1008 | i_push_errorf(0, "tag %s may only contain Latin1 characters 32-126, 161-255", tag_name); |
1009 | return 0; | |
1010 | } | |
1011 | } | |
1012 | } | |
1013 | ||
1014 | sprintf(tag_name, "png_text%d_text", i); | |
1015 | value = get_string2(&im->tags, tag_name, value_buf, &value_size); | |
1016 | ||
1017 | if (value) { | |
1018 | if (memchr(value, '\0', value_size)) { | |
1019 | i_push_errorf(0, "tag %s may not contain NUL characters", tag_name); | |
1020 | return 0; | |
1021 | } | |
1022 | } | |
1023 | ||
1024 | if (key && value) { | |
1025 | png_text text; | |
1026 | int compression = value_size > 1000; | |
1027 | ||
1028 | sprintf(tag_name, "png_text%d_compressed", i); | |
1029 | i_tags_get_int(&im->tags, tag_name, 0, &compression); | |
1030 | ||
1031 | text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt | |
1032 | : PNG_TEXT_COMPRESSION_NONE; | |
1033 | text.key = (char *)key; | |
1034 | text.text_length = value_size; | |
1035 | text.text = (char *)value; | |
1036 | #ifdef PNG_iTXt_SUPPORTED | |
1037 | text.itxt_length = 0; | |
1038 | text.lang = NULL; | |
1039 | text.lang_key = NULL; | |
1040 | #endif | |
1041 | ||
1042 | png_set_text(png_ptr, info_ptr, &text, 1); | |
1043 | } | |
1044 | else if (key) { | |
1045 | i_push_errorf(0, "tag png_text%d_key found but not png_text%d_text", i, i); | |
1046 | return 0; | |
1047 | } | |
1048 | else if (value) { | |
1049 | i_push_errorf(0, "tag png_text%d_text found but not png_text%d_key", i, i); | |
1050 | return 0; | |
1051 | } | |
1052 | else { | |
1053 | break; | |
1054 | } | |
1055 | ++i; | |
1056 | } | |
1057 | } | |
1058 | ||
1059 | { | |
1060 | char buf[GET_STR_BUF_SIZE]; | |
1061 | size_t time_size; | |
1062 | const char *timestr = get_string2(&im->tags, "png_time", buf, &time_size); | |
1063 | ||
1064 | if (timestr) { | |
1065 | int year, month, day, hour, minute, second; | |
1066 | png_time mod_time; | |
1067 | ||
1068 | if (sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) { | |
1069 | /* rough validation */ | |
1070 | if (month < 1 || month > 12 | |
1071 | || day < 1 || day > 31 | |
1072 | || hour < 0 || hour > 23 | |
1073 | || minute < 0 || minute > 59 | |
1074 | || second < 0 || second > 60) { | |
1075 | i_push_error(0, "invalid date/time for png_time"); | |
1076 | return 0; | |
1077 | } | |
1078 | mod_time.year = year; | |
1079 | mod_time.month = month; | |
1080 | mod_time.day = day; | |
1081 | mod_time.hour = hour; | |
1082 | mod_time.minute = minute; | |
1083 | mod_time.second = second; | |
1084 | ||
1085 | png_set_tIME(png_ptr, info_ptr, &mod_time); | |
1086 | } | |
1087 | else { | |
1088 | i_push_error(0, "png_time must be formatted 'y-m-dTh:m:s'"); | |
1089 | return 0; | |
1090 | } | |
1091 | } | |
1092 | } | |
1093 | ||
1094 | { | |
1095 | /* no bKGD support yet, maybe later | |
1096 | it may be simpler to do it in the individual writers | |
1097 | */ | |
1098 | } | |
1099 | ||
1100 | return 1; | |
1101 | } | |
1102 | ||
1103 | static const char * | |
1104 | get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size) { | |
1105 | int index; | |
1106 | ||
1107 | if (i_tags_find(tags, name, 0, &index)) { | |
1108 | const i_img_tag *entry = tags->tags + index; | |
1109 | ||
1110 | if (entry->data) { | |
1111 | *size = entry->size; | |
1112 | ||
1113 | return entry->data; | |
1114 | } | |
1115 | else { | |
1116 | *size = sprintf(buf, "%d", entry->idata); | |
1117 | ||
1118 | return buf; | |
1119 | } | |
1120 | } | |
1121 | return NULL; | |
1122 | } | |
1123 | ||
81089826 TC |
1124 | static int |
1125 | write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) { | |
1126 | unsigned char *data, *volatile vdata = NULL; | |
1127 | i_img_dim y; | |
1128 | ||
1129 | if (setjmp(png_jmpbuf(png_ptr))) { | |
1130 | if (vdata) | |
1131 | myfree(vdata); | |
1132 | ||
1133 | return 0; | |
1134 | } | |
1135 | ||
21c6936b TC |
1136 | png_write_info(png_ptr, info_ptr); |
1137 | ||
81089826 TC |
1138 | vdata = data = mymalloc(im->xsize * im->channels); |
1139 | for (y = 0; y < im->ysize; y++) { | |
1140 | i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels); | |
1141 | png_write_row(png_ptr, (png_bytep)data); | |
1142 | } | |
1143 | myfree(data); | |
1144 | ||
1145 | return 1; | |
1146 | } | |
1147 | ||
d9610331 TC |
1148 | static int |
1149 | write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) { | |
1150 | unsigned *data, *volatile vdata = NULL; | |
1151 | unsigned char *tran_data, * volatile vtran_data = NULL; | |
1152 | i_img_dim samples_per_row = im->xsize * im->channels; | |
1153 | ||
1154 | i_img_dim y; | |
1155 | ||
1156 | if (setjmp(png_jmpbuf(png_ptr))) { | |
1157 | if (vdata) | |
1158 | myfree(vdata); | |
1159 | if (vtran_data) | |
1160 | myfree(vtran_data); | |
1161 | ||
1162 | return 0; | |
1163 | } | |
1164 | ||
1165 | png_write_info(png_ptr, info_ptr); | |
1166 | ||
1167 | vdata = data = mymalloc(samples_per_row * sizeof(unsigned)); | |
1168 | vtran_data = tran_data = mymalloc(samples_per_row * 2); | |
1169 | for (y = 0; y < im->ysize; y++) { | |
1170 | i_img_dim i; | |
1171 | unsigned char *p = tran_data; | |
1172 | i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16); | |
1173 | for (i = 0; i < samples_per_row; ++i) { | |
1174 | p[0] = data[i] >> 8; | |
1175 | p[1] = data[i] & 0xff; | |
1176 | p += 2; | |
1177 | } | |
1178 | png_write_row(png_ptr, (png_bytep)tran_data); | |
1179 | } | |
1180 | myfree(tran_data); | |
1181 | myfree(data); | |
1182 | ||
1183 | return 1; | |
1184 | } | |
1185 | ||
21c6936b TC |
1186 | static int |
1187 | write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) { | |
1188 | unsigned char *data, *volatile vdata = NULL; | |
1189 | i_img_dim y; | |
1190 | unsigned char pal_map[256]; | |
1191 | png_color pcolors[256]; | |
1192 | i_color colors[256]; | |
1193 | int count = i_colorcount(im); | |
1194 | int i; | |
1195 | ||
1196 | if (setjmp(png_jmpbuf(png_ptr))) { | |
1197 | if (vdata) | |
1198 | myfree(vdata); | |
1199 | ||
1200 | return 0; | |
1201 | } | |
1202 | ||
1203 | i_getcolors(im, 0, colors, count); | |
1204 | if (im->channels < 3) { | |
1205 | /* convert the greyscale palette to color */ | |
1206 | int i; | |
1207 | for (i = 0; i < count; ++i) { | |
1208 | i_color *c = colors + i; | |
1209 | c->channel[3] = c->channel[1]; | |
1210 | c->channel[2] = c->channel[1] = c->channel[0]; | |
1211 | } | |
1212 | } | |
1213 | ||
1214 | if (i_img_has_alpha(im)) { | |
1215 | int i; | |
1216 | int bottom_index = 0, top_index = count-1; | |
1217 | ||
1218 | /* fill out the palette map */ | |
1219 | for (i = 0; i < count; ++i) | |
1220 | pal_map[i] = i; | |
1221 | ||
1222 | /* the PNG spec suggests sorting the palette by alpha, but that's | |
1223 | unnecessary - all we want to do is move the opaque entries to | |
1224 | the end */ | |
1225 | while (bottom_index < top_index) { | |
1226 | if (colors[bottom_index].rgba.a == 255) { | |
1227 | pal_map[bottom_index] = top_index; | |
1228 | pal_map[top_index--] = bottom_index; | |
1229 | } | |
1230 | ++bottom_index; | |
1231 | } | |
1232 | } | |
1233 | ||
1234 | for (i = 0; i < count; ++i) { | |
1235 | int srci = i_img_has_alpha(im) ? pal_map[i] : i; | |
1236 | ||
1237 | pcolors[i].red = colors[srci].rgb.r; | |
1238 | pcolors[i].green = colors[srci].rgb.g; | |
1239 | pcolors[i].blue = colors[srci].rgb.b; | |
1240 | } | |
1241 | ||
1242 | png_set_PLTE(png_ptr, info_ptr, pcolors, count); | |
1243 | ||
1244 | if (i_img_has_alpha(im)) { | |
1245 | unsigned char trans[256]; | |
1246 | int i; | |
1247 | ||
1248 | for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) { | |
1249 | trans[i] = colors[pal_map[i]].rgba.a; | |
1250 | } | |
1251 | png_set_tRNS(png_ptr, info_ptr, trans, i, NULL); | |
1252 | } | |
1253 | ||
1254 | png_write_info(png_ptr, info_ptr); | |
1255 | ||
1256 | png_set_packing(png_ptr); | |
1257 | ||
1258 | vdata = data = mymalloc(im->xsize); | |
1259 | for (y = 0; y < im->ysize; y++) { | |
1260 | i_gpal(im, 0, im->xsize, y, data); | |
1261 | if (i_img_has_alpha(im)) { | |
1262 | i_img_dim x; | |
1263 | for (x = 0; x < im->xsize; ++x) | |
1264 | data[x] = pal_map[data[x]]; | |
1265 | } | |
1266 | png_write_row(png_ptr, (png_bytep)data); | |
1267 | } | |
1268 | myfree(data); | |
1269 | ||
1270 | return 1; | |
1271 | } | |
1272 | ||
b8961d05 TC |
1273 | static int |
1274 | write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) { | |
1275 | unsigned char *data, *volatile vdata = NULL; | |
1276 | i_img_dim y; | |
21c6936b | 1277 | |
b8961d05 TC |
1278 | if (setjmp(png_jmpbuf(png_ptr))) { |
1279 | if (vdata) | |
1280 | myfree(vdata); | |
1281 | ||
1282 | return 0; | |
21c6936b | 1283 | } |
21c6936b | 1284 | |
b8961d05 TC |
1285 | png_write_info(png_ptr, info_ptr); |
1286 | ||
1287 | png_set_packing(png_ptr); | |
1288 | ||
1289 | vdata = data = mymalloc(im->xsize); | |
1290 | for (y = 0; y < im->ysize; y++) { | |
1291 | i_gsamp(im, 0, im->xsize, y, data, NULL, 1); | |
1292 | png_write_row(png_ptr, (png_bytep)data); | |
1293 | } | |
1294 | myfree(data); | |
1295 | ||
1296 | return 1; | |
1297 | } | |
21c6936b | 1298 | |
38eab175 TC |
1299 | static void |
1300 | read_warn_handler(png_structp png_ptr, png_const_charp msg) { | |
1301 | i_png_read_statep rs = (i_png_read_statep)png_get_error_ptr(png_ptr); | |
1302 | char *workp; | |
1303 | size_t new_size; | |
1304 | ||
1305 | mm_log((1, "PNG read warning '%s'\n", msg)); | |
1306 | ||
1307 | /* in case this is part of an error report */ | |
1308 | i_push_error(0, msg); | |
1309 | ||
1310 | /* and save in the warnings so if we do manage to succeed, we | |
1311 | * can save it as a tag | |
1312 | */ | |
1313 | new_size = (rs->warnings ? strlen(rs->warnings) : 0) | |
1314 | + 1 /* NUL */ | |
1315 | + strlen(msg) /* new text */ | |
1316 | + 1; /* newline */ | |
1317 | workp = myrealloc(rs->warnings, new_size); | |
1318 | if (!rs->warnings) | |
1319 | *workp = '\0'; | |
1320 | strcat(workp, msg); | |
1321 | strcat(workp, "\n"); | |
1322 | rs->warnings = workp; | |
1323 | } | |
1324 | ||
1325 | static void | |
1326 | cleanup_read_state(i_png_read_statep rs) { | |
1327 | if (rs->warnings) | |
1328 | myfree(rs->warnings); | |
1329 | } |