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