1 /*
2 * optim.c
3 * The main PNG optimization engine.
4 *
5 * Copyright (C) 2001-2017 Cosmin Truta and the Contributing Authors.
6 *
7 * This software is distributed under the zlib license.
8 * Please see the accompanying LICENSE file.
9 */
10
11 #include <limits.h>
12 #include <stdarg.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16
17 #include "optipng.h"
18 #include "proginfo.h"
19
20 #include "bitset.h"
21 #include "ioutil.h"
22 #include "opngreduc.h"
23 #include "png.h"
24 #include "pngxtern.h"
25 #include "pngxutil.h"
26 #include "ratio.h"
27 #include "zlib.h"
28
29
30 /*
31 * User exception setup.
32 */
33 #include "cexcept.h"
34 define_exception_type(const char *);
35 struct exception_context the_exception_context[1];
36
37
38 /*
39 * The optimization level presets.
40 */
41 static const struct opng_preset
42 {
43 const char *compr_level;
44 const char *mem_level;
45 const char *strategy;
46 const char *filter;
47 } presets[OPNG_OPTIM_LEVEL_MAX + 1] =
48 {
49 /* { -zc -zm -zs -f } */
50 { "", "", "", "" }, /* -o0 */
51 { "", "", "", "" }, /* -o1 */
52 { "9", "8", "0-", "0,5" }, /* -o2 */
53 { "9", "8-9", "0-", "0,5" }, /* -o3 */
54 { "9", "8", "0-", "0-" }, /* -o4 */
55 { "9", "8-9", "0-", "0-" }, /* -o5 */
56 { "1-9", "8", "0-", "0-" }, /* -o6 */
57 { "1-9", "8-9", "0-", "0-" } /* -o7 */
58 };
59
60 /*
61 * The filter table.
62 */
63 static const int filter_table[OPNG_FILTER_MAX + 1] =
64 {
65 PNG_FILTER_NONE, /* -f0 */
66 PNG_FILTER_SUB, /* -f1 */
67 PNG_FILTER_UP, /* -f2 */
68 PNG_FILTER_AVG, /* -f3 */
69 PNG_FILTER_PAETH, /* -f4 */
70 PNG_ALL_FILTERS /* -f5 */
71 };
72
73 /*
74 * Status flags.
75 */
76 enum
77 {
78 INPUT_IS_PNG_FILE = 0x0001,
79 INPUT_HAS_PNG_DATASTREAM = 0x0002,
80 INPUT_HAS_PNG_SIGNATURE = 0x0004,
81 INPUT_HAS_DIGITAL_SIGNATURE = 0x0008,
82 INPUT_HAS_MULTIPLE_IMAGES = 0x0010,
83 INPUT_HAS_APNG = 0x0020,
84 INPUT_HAS_STRIPPED_DATA = 0x0040,
85 INPUT_HAS_JUNK = 0x0080,
86 INPUT_HAS_ERRORS = 0x0100,
87 OUTPUT_NEEDS_NEW_FILE = 0x1000,
88 OUTPUT_NEEDS_NEW_IDAT = 0x2000,
89 OUTPUT_HAS_ERRORS = 0x4000
90 };
91
92 /*
93 * The chunks handled by OptiPNG.
94 */
95 static const png_byte sig_PLTE[4] = { 0x50, 0x4c, 0x54, 0x45 };
96 static const png_byte sig_tRNS[4] = { 0x74, 0x52, 0x4e, 0x53 };
97 static const png_byte sig_IDAT[4] = { 0x49, 0x44, 0x41, 0x54 };
98 static const png_byte sig_IEND[4] = { 0x49, 0x45, 0x4e, 0x44 };
99 static const png_byte sig_bKGD[4] = { 0x62, 0x4b, 0x47, 0x44 };
100 static const png_byte sig_hIST[4] = { 0x68, 0x49, 0x53, 0x54 };
101 static const png_byte sig_sBIT[4] = { 0x73, 0x42, 0x49, 0x54 };
102 static const png_byte sig_dSIG[4] = { 0x64, 0x53, 0x49, 0x47 };
103 static const png_byte sig_acTL[4] = { 0x61, 0x63, 0x54, 0x4c };
104 static const png_byte sig_fcTL[4] = { 0x66, 0x63, 0x54, 0x4c };
105 static const png_byte sig_fdAT[4] = { 0x66, 0x64, 0x41, 0x54 };
106
107 /*
108 * The optimization engine.
109 * (Since the engine is not thread-safe, there isn't much to put in here...)
110 */
111 static struct opng_engine_struct
112 {
113 int started;
114 } engine;
115
116 /*
117 * The optimization process.
118 */
119 static struct opng_process_struct
120 {
121 unsigned int status;
122 int num_iterations;
123 opng_foffset_t in_datastream_offset;
124 opng_fsize_t in_file_size, out_file_size;
125 opng_fsize_t in_idat_size, out_idat_size;
126 opng_fsize_t best_idat_size, max_idat_size;
127 png_uint_32 in_plte_trns_size, out_plte_trns_size;
128 png_uint_32 reductions;
129 opng_bitset_t compr_level_set, mem_level_set, strategy_set, filter_set;
130 int best_compr_level, best_mem_level, best_strategy, best_filter;
131 } process;
132
133 /*
134 * The optimization process limits.
135 */
136 static const opng_fsize_t idat_size_max = PNG_UINT_31_MAX;
137 static const char *idat_size_max_string = "2GB";
138
139 /*
140 * The optimization process summary.
141 */
142 static struct opng_summary_struct
143 {
144 unsigned int file_count;
145 unsigned int err_count;
146 unsigned int fix_count;
147 unsigned int snip_count;
148 } summary;
149
150 /*
151 * The optimized image.
152 */
153 static struct opng_image_struct
154 {
155 png_uint_32 width; /* IHDR */
156 png_uint_32 height;
157 int bit_depth;
158 int color_type;
159 int compression_type;
160 int filter_type;
161 int interlace_type;
162 png_bytepp row_pointers; /* IDAT */
163 png_colorp palette; /* PLTE */
164 int num_palette;
165 png_color_16p background_ptr; /* bKGD */
166 png_color_16 background;
167 png_uint_16p hist; /* hIST */
168 png_color_8p sig_bit_ptr; /* sBIT */
169 png_color_8 sig_bit;
170 png_bytep trans_alpha; /* tRNS */
171 int num_trans;
172 png_color_16p trans_color_ptr;
173 png_color_16 trans_color;
174 png_unknown_chunkp unknowns; /* everything else */
175 int num_unknowns;
176 } image;
177
178 /*
179 * The user options.
180 */
181 static struct opng_options options;
182
183
184 /*
185 * The user interface.
186 */
187 static void (*usr_printf)(const char *fmt, ...);
188 static void (*usr_print_cntrl)(int cntrl_code);
189 static void (*usr_progress)(unsigned long num, unsigned long denom);
190 static void (*usr_panic)(const char *msg);
191
192
193 /*
194 * More global variables, for quick access and bonus style points.
195 */
196 static png_structp read_ptr;
197 static png_infop read_info_ptr;
198 static png_structp write_ptr;
199 static png_infop write_info_ptr;
200
201
202 /*
203 * Internal debugging tool.
204 */
205 #define OPNG_ENSURE(cond, msg) \
206 { if (!(cond)) usr_panic(msg); } /* strong check, no #ifdef's */
207
208
209 /*
210 * Size ratio display.
211 */
212 static void
opng_print_fsize_ratio(opng_fsize_t num,opng_fsize_t denom)213 opng_print_fsize_ratio(opng_fsize_t num, opng_fsize_t denom)
214 {
215 #if OPNG_FSIZE_MAX <= ULONG_MAX
216 #define RATIO_TYPE struct opng_ulratio
217 #define RATIO_CONV_FN opng_ulratio_to_factor_string
218 #else
219 #define RATIO_TYPE struct opng_ullratio
220 #define RATIO_CONV_FN opng_ullratio_to_factor_string
221 #endif
222
223 char buffer[32];
224 RATIO_TYPE ratio;
225 int result;
226
227 ratio.num = num;
228 ratio.denom = denom;
229 result = RATIO_CONV_FN(buffer, sizeof(buffer), &ratio);
230 usr_printf("%s%s", buffer, (result > 0) ? "" : "...");
231
232 #undef RATIO_TYPE
233 #undef RATIO_CONV_FN
234 }
235
236 /*
237 * Size change display.
238 */
239 static void
opng_print_fsize_difference(opng_fsize_t init_size,opng_fsize_t final_size,int show_ratio)240 opng_print_fsize_difference(opng_fsize_t init_size, opng_fsize_t final_size,
241 int show_ratio)
242 {
243 opng_fsize_t difference;
244 int sign;
245
246 if (init_size <= final_size)
247 {
248 sign = 0;
249 difference = final_size - init_size;
250 }
251 else
252 {
253 sign = 1;
254 difference = init_size - final_size;
255 }
256
257 if (difference == 0)
258 {
259 usr_printf("no change");
260 return;
261 }
262 if (difference == 1)
263 usr_printf("1 byte");
264 else
265 usr_printf("%" OPNG_FSIZE_PRIu " bytes", difference);
266 if (show_ratio && init_size > 0)
267 {
268 usr_printf(" = ");
269 opng_print_fsize_ratio(difference, init_size);
270 }
271 usr_printf((sign == 0) ? " increase" : " decrease");
272 }
273
274 /*
275 * Image info display.
276 */
277 static void
opng_print_image_info(int show_dim,int show_depth,int show_type,int show_interlaced)278 opng_print_image_info(int show_dim, int show_depth, int show_type,
279 int show_interlaced)
280 {
281 static const int type_channels[8] = {1, 0, 3, 1, 2, 0, 4, 0};
282 int channels, printed;
283
284 printed = 0;
285 if (show_dim)
286 {
287 printed = 1;
288 usr_printf("%lux%lu pixels",
289 (unsigned long)image.width, (unsigned long)image.height);
290 }
291 if (show_depth)
292 {
293 if (printed)
294 usr_printf(", ");
295 printed = 1;
296 channels = type_channels[image.color_type & 7];
297 if (channels != 1)
298 usr_printf("%dx%d bits/pixel", channels, image.bit_depth);
299 else if (image.bit_depth != 1)
300 usr_printf("%d bits/pixel", image.bit_depth);
301 else
302 usr_printf("1 bit/pixel");
303 }
304 if (show_type)
305 {
306 if (printed)
307 usr_printf(", ");
308 printed = 1;
309 if (image.color_type & PNG_COLOR_MASK_PALETTE)
310 {
311 if (image.num_palette == 1)
312 usr_printf("1 color");
313 else
314 usr_printf("%d colors", image.num_palette);
315 if (image.num_trans > 0)
316 usr_printf(" (%d transparent)", image.num_trans);
317 usr_printf(" in palette");
318 }
319 else
320 {
321 usr_printf((image.color_type & PNG_COLOR_MASK_COLOR) ?
322 "RGB" : "grayscale");
323 if (image.color_type & PNG_COLOR_MASK_ALPHA)
324 usr_printf("+alpha");
325 else if (image.trans_color_ptr != NULL)
326 usr_printf("+transparency");
327 }
328 }
329 if (show_interlaced)
330 {
331 if (image.interlace_type != PNG_INTERLACE_NONE)
332 {
333 if (printed)
334 usr_printf(", ");
335 usr_printf("interlaced");
336 }
337 }
338 }
339
340 /*
341 * Warning display.
342 */
343 static void
opng_print_warning(const char * msg)344 opng_print_warning(const char *msg)
345 {
346 usr_print_cntrl('\v'); /* VT: new paragraph */
347 usr_printf("Warning: %s\n", msg);
348 }
349
350 /*
351 * Error display.
352 */
353 static void
opng_print_error(const char * msg)354 opng_print_error(const char *msg)
355 {
356 usr_print_cntrl('\v'); /* VT: new paragraph */
357 usr_printf("Error: %s\n", msg);
358 }
359
360 /*
361 * Warning handler.
362 */
363 static void
opng_warning(png_structp png_ptr,png_const_charp msg)364 opng_warning(png_structp png_ptr, png_const_charp msg)
365 {
366 /* Error in input or output file; processing may continue. */
367 /* Recovery requires (re)compression of IDAT. */
368 if (png_ptr == read_ptr)
369 process.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT);
370 opng_print_warning(msg);
371 }
372
373 /*
374 * Error handler.
375 */
376 static void
opng_error(png_structp png_ptr,png_const_charp msg)377 opng_error(png_structp png_ptr, png_const_charp msg)
378 {
379 /* Error in input or output file; processing must stop. */
380 /* Recovery requires (re)compression of IDAT. */
381 if (png_ptr == read_ptr)
382 process.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT);
383 Throw msg;
384 }
385
386 /*
387 * Memory deallocator.
388 */
389 static void
opng_free(void * ptr)390 opng_free(void *ptr)
391 {
392 /* This deallocator must be compatible with libpng's memory allocation
393 * routines, png_malloc() and png_free().
394 * If those routines change, this one must be changed accordingly.
395 */
396 free(ptr);
397 }
398
399 /*
400 * IDAT size checker.
401 */
402 static void
opng_check_idat_size(opng_fsize_t size)403 opng_check_idat_size(opng_fsize_t size)
404 {
405 if (size > idat_size_max)
406 Throw "IDAT sizes larger than the maximum chunk size "
407 "are currently unsupported";
408 }
409
410 /*
411 * Chunk handler.
412 */
413 static void
opng_set_keep_unknown_chunk(png_structp png_ptr,int keep,png_bytep chunk_type)414 opng_set_keep_unknown_chunk(png_structp png_ptr,
415 int keep, png_bytep chunk_type)
416 {
417 png_byte chunk_name[5];
418
419 /* Call png_set_keep_unknown_chunks() once per each chunk type only. */
420 memcpy(chunk_name, chunk_type, 4);
421 chunk_name[4] = 0;
422 if (!png_handle_as_unknown(png_ptr, chunk_name))
423 png_set_keep_unknown_chunks(png_ptr, keep, chunk_name, 1);
424 }
425
426 /*
427 * Chunk categorization.
428 */
429 static int
opng_is_image_chunk(png_bytep chunk_type)430 opng_is_image_chunk(png_bytep chunk_type)
431 {
432 if ((chunk_type[0] & 0x20) == 0)
433 return 1;
434 /* Although tRNS is listed as ancillary in the PNG specification, it stores
435 * alpha samples, which is critical information. For example, tRNS cannot
436 * be generally ignored when rendering animations.
437 * Operations claimed to be lossless must treat tRNS as a critical chunk.
438 */
439 if (memcmp(chunk_type, sig_tRNS, 4) == 0)
440 return 1;
441 return 0;
442 }
443
444 /*
445 * Chunk categorization.
446 */
447 static int
opng_is_apng_chunk(png_bytep chunk_type)448 opng_is_apng_chunk(png_bytep chunk_type)
449 {
450 if (memcmp(chunk_type, sig_acTL, 4) == 0 ||
451 memcmp(chunk_type, sig_fcTL, 4) == 0 ||
452 memcmp(chunk_type, sig_fdAT, 4) == 0)
453 return 1;
454 return 0;
455 }
456
457 /*
458 * Chunk filter.
459 */
460 static int
opng_allow_chunk(png_bytep chunk_type)461 opng_allow_chunk(png_bytep chunk_type)
462 {
463 /* Always allow critical chunks and tRNS. */
464 if (opng_is_image_chunk(chunk_type))
465 return 1;
466 /* Block all the other chunks if requested. */
467 if (options.strip_all)
468 return 0;
469 /* Always block the digital signature chunks. */
470 if (memcmp(chunk_type, sig_dSIG, 4) == 0)
471 return 0;
472 /* Block the APNG chunks when snipping. */
473 if (options.snip && opng_is_apng_chunk(chunk_type))
474 return 0;
475 /* Allow all the other chunks. */
476 return 1;
477 }
478
479 /*
480 * Chunk handler.
481 */
482 static void
opng_handle_chunk(png_structp png_ptr,png_bytep chunk_type)483 opng_handle_chunk(png_structp png_ptr, png_bytep chunk_type)
484 {
485 int keep;
486
487 if (opng_is_image_chunk(chunk_type))
488 return;
489
490 if (options.strip_all)
491 {
492 process.status |= INPUT_HAS_STRIPPED_DATA | INPUT_HAS_JUNK;
493 opng_set_keep_unknown_chunk(png_ptr,
494 PNG_HANDLE_CHUNK_NEVER, chunk_type);
495 return;
496 }
497
498 /* Let libpng handle bKGD, hIST and sBIT. */
499 if (memcmp(chunk_type, sig_bKGD, 4) == 0 ||
500 memcmp(chunk_type, sig_hIST, 4) == 0 ||
501 memcmp(chunk_type, sig_sBIT, 4) == 0)
502 return;
503
504 /* Everything else is handled as unknown by libpng. */
505 keep = PNG_HANDLE_CHUNK_ALWAYS;
506 if (memcmp(chunk_type, sig_dSIG, 4) == 0)
507 {
508 /* Recognize dSIG, but let libpng handle it as unknown. */
509 process.status |= INPUT_HAS_DIGITAL_SIGNATURE;
510 }
511 else if (opng_is_apng_chunk(chunk_type))
512 {
513 /* Recognize APNG, but let libpng handle it as unknown. */
514 process.status |= INPUT_HAS_APNG;
515 if (memcmp(chunk_type, sig_fdAT, 4) == 0)
516 process.status |= INPUT_HAS_MULTIPLE_IMAGES;
517 if (options.snip)
518 {
519 process.status |= INPUT_HAS_JUNK;
520 keep = PNG_HANDLE_CHUNK_NEVER;
521 }
522 }
523 opng_set_keep_unknown_chunk(png_ptr, keep, chunk_type);
524 }
525
526 /*
527 * Initialization for input handler.
528 */
529 static void
opng_init_read_data(void)530 opng_init_read_data(void)
531 {
532 /* The relevant process data members are set to zero,
533 * and nothing else needs to be done at this moment.
534 */
535 }
536
537 /*
538 * Initialization for output handler.
539 */
540 static void
opng_init_write_data(void)541 opng_init_write_data(void)
542 {
543 process.out_file_size = 0;
544 process.out_plte_trns_size = 0;
545 process.out_idat_size = 0;
546 }
547
548 /*
549 * Input handler.
550 */
551 static void
opng_read_data(png_structp png_ptr,png_bytep data,size_t length)552 opng_read_data(png_structp png_ptr, png_bytep data, size_t length)
553 {
554 FILE *stream = (FILE *)png_get_io_ptr(png_ptr);
555 int io_state = pngx_get_io_state(png_ptr);
556 int io_state_loc = io_state & PNGX_IO_MASK_LOC;
557 png_bytep chunk_sig;
558
559 /* Read the data. */
560 if (fread(data, 1, length, stream) != length)
561 png_error(png_ptr,
562 "Can't read the input file or unexpected end of file");
563
564 if (process.in_file_size == 0) /* first piece of PNG data */
565 {
566 OPNG_ENSURE(length == 8, "PNG I/O must start with the first 8 bytes");
567 process.in_datastream_offset = opng_ftello(stream) - 8;
568 process.status |= INPUT_HAS_PNG_DATASTREAM;
569 if (io_state_loc == PNGX_IO_SIGNATURE)
570 process.status |= INPUT_HAS_PNG_SIGNATURE;
571 if (process.in_datastream_offset == 0)
572 process.status |= INPUT_IS_PNG_FILE;
573 else if (process.in_datastream_offset < 0)
574 png_error(png_ptr,
575 "Can't get the file-position indicator in input file");
576 process.in_file_size = (opng_fsize_t)process.in_datastream_offset;
577 }
578 process.in_file_size += length;
579
580 /* Handle the OptiPNG-specific events. */
581 OPNG_ENSURE((io_state & PNGX_IO_READING) && (io_state_loc != 0),
582 "Incorrect info in png_ptr->io_state");
583 if (io_state_loc == PNGX_IO_CHUNK_HDR)
584 {
585 /* In libpng 1.4.x and later, the chunk length and the chunk name
586 * are serialized in a single operation. This is also ensured by
587 * the opngio add-on for libpng 1.2.x and earlier.
588 */
589 OPNG_ENSURE(length == 8, "Reading chunk header, expecting 8 bytes");
590 chunk_sig = data + 4;
591
592 if (memcmp(chunk_sig, sig_IDAT, 4) == 0)
593 {
594 OPNG_ENSURE(png_ptr == read_ptr, "Incorrect I/O handler setup");
595 if (png_get_rows(read_ptr, read_info_ptr) == NULL) /* 1st IDAT */
596 {
597 OPNG_ENSURE(process.in_idat_size == 0,
598 "Found IDAT with no rows");
599 /* Allocate the rows here, bypassing libpng.
600 * This allows to initialize the contents and perform recovery
601 * in case of a premature EOF.
602 */
603 if (png_get_image_height(read_ptr, read_info_ptr) == 0)
604 return; /* premature IDAT; an error will occur later */
605 OPNG_ENSURE(pngx_malloc_rows(read_ptr, read_info_ptr,
606 0) != NULL,
607 "Failed allocation of image rows; "
608 "unsafe libpng allocator");
609 png_data_freer(read_ptr, read_info_ptr,
610 PNG_USER_WILL_FREE_DATA, PNG_FREE_ROWS);
611 }
612 else
613 {
614 /* There is split IDAT overhead. Join IDATs. */
615 process.status |= INPUT_HAS_JUNK;
616 }
617 process.in_idat_size += png_get_uint_32(data);
618 }
619 else if (memcmp(chunk_sig, sig_PLTE, 4) == 0 ||
620 memcmp(chunk_sig, sig_tRNS, 4) == 0)
621 {
622 /* Add the chunk overhead (header + CRC) to the data size. */
623 process.in_plte_trns_size += png_get_uint_32(data) + 12;
624 }
625 else
626 opng_handle_chunk(png_ptr, chunk_sig);
627 }
628 else if (io_state_loc == PNGX_IO_CHUNK_CRC)
629 {
630 OPNG_ENSURE(length == 4, "Reading chunk CRC, expecting 4 bytes");
631 }
632 }
633
634 /*
635 * Output handler.
636 */
637 static void
opng_write_data(png_structp png_ptr,png_bytep data,size_t length)638 opng_write_data(png_structp png_ptr, png_bytep data, size_t length)
639 {
640 static int allow_crt_chunk;
641 static int crt_chunk_is_idat;
642 static opng_foffset_t crt_idat_offset;
643 static opng_fsize_t crt_idat_size;
644 static png_uint_32 crt_idat_crc;
645 FILE *stream = (FILE *)png_get_io_ptr(png_ptr);
646 int io_state = pngx_get_io_state(png_ptr);
647 int io_state_loc = io_state & PNGX_IO_MASK_LOC;
648 png_bytep chunk_sig;
649 png_byte buf[4];
650
651 OPNG_ENSURE((io_state & PNGX_IO_WRITING) && (io_state_loc != 0),
652 "Incorrect info in png_ptr->io_state");
653
654 /* Handle the OptiPNG-specific events. */
655 if (io_state_loc == PNGX_IO_CHUNK_HDR)
656 {
657 OPNG_ENSURE(length == 8, "Writing chunk header, expecting 8 bytes");
658 chunk_sig = data + 4;
659 allow_crt_chunk = opng_allow_chunk(chunk_sig);
660 if (memcmp(chunk_sig, sig_IDAT, 4) == 0)
661 {
662 crt_chunk_is_idat = 1;
663 process.out_idat_size += png_get_uint_32(data);
664 /* Abandon the trial if IDAT is bigger than the maximum allowed. */
665 if (stream == NULL)
666 {
667 if (process.out_idat_size > process.max_idat_size)
668 Throw NULL; /* early interruption, not an error */
669 }
670 }
671 else /* not IDAT */
672 {
673 crt_chunk_is_idat = 0;
674 if (memcmp(chunk_sig, sig_PLTE, 4) == 0 ||
675 memcmp(chunk_sig, sig_tRNS, 4) == 0)
676 {
677 /* Add the chunk overhead (header + CRC) to the data size. */
678 process.out_plte_trns_size += png_get_uint_32(data) + 12;
679 }
680 }
681 }
682 else if (io_state_loc == PNGX_IO_CHUNK_CRC)
683 {
684 OPNG_ENSURE(length == 4, "Writing chunk CRC, expecting 4 bytes");
685 }
686
687 /* Exit early if this is only a trial. */
688 if (stream == NULL)
689 return;
690
691 /* Continue only if the current chunk type is allowed. */
692 if (io_state_loc != PNGX_IO_SIGNATURE && !allow_crt_chunk)
693 return;
694
695 /* Here comes an elaborate way of writing the data, in which all IDATs
696 * are joined into a single chunk.
697 * Normally, the user-supplied I/O routines are not so complicated.
698 */
699 switch (io_state_loc)
700 {
701 case PNGX_IO_CHUNK_HDR:
702 if (crt_chunk_is_idat)
703 {
704 if (crt_idat_offset == 0)
705 {
706 /* This is the header of the first IDAT. */
707 crt_idat_offset = opng_ftello(stream);
708 /* Try guessing the size of the final (joined) IDAT. */
709 if (process.best_idat_size > 0)
710 {
711 /* The guess is expected to be right. */
712 crt_idat_size = process.best_idat_size;
713 }
714 else
715 {
716 /* The guess could be wrong.
717 * The size of the final IDAT will be revised.
718 */
719 crt_idat_size = length;
720 }
721 png_save_uint_32(data, (png_uint_32)crt_idat_size);
722 /* Start computing the CRC of the final IDAT. */
723 crt_idat_crc = crc32(0, sig_IDAT, 4);
724 }
725 else
726 {
727 /* This is not the first IDAT. Do not write its header. */
728 return;
729 }
730 }
731 else
732 {
733 if (crt_idat_offset != 0)
734 {
735 /* This is the header of the first chunk after IDAT.
736 * Finalize IDAT before resuming the normal operation.
737 */
738 png_save_uint_32(buf, crt_idat_crc);
739 if (fwrite(buf, 1, 4, stream) != 4)
740 io_state = 0; /* error */
741 process.out_file_size += 4;
742 if (process.out_idat_size != crt_idat_size)
743 {
744 /* The IDAT size has not been guessed correctly.
745 * It must be updated in a non-streamable way.
746 */
747 OPNG_ENSURE(process.best_idat_size == 0,
748 "Wrong guess of the output IDAT size");
749 opng_check_idat_size(process.out_idat_size);
750 png_save_uint_32(buf, (png_uint_32)process.out_idat_size);
751 if (opng_fwriteo(stream, crt_idat_offset, SEEK_SET,
752 buf, 4) != 4)
753 io_state = 0; /* error */
754 }
755 if (io_state == 0)
756 png_error(png_ptr, "Can't finalize IDAT");
757 crt_idat_offset = 0;
758 }
759 }
760 break;
761 case PNGX_IO_CHUNK_DATA:
762 if (crt_chunk_is_idat)
763 crt_idat_crc = crc32(crt_idat_crc, data, length);
764 break;
765 case PNGX_IO_CHUNK_CRC:
766 if (crt_chunk_is_idat)
767 {
768 /* Defer writing until the first non-IDAT occurs. */
769 return;
770 }
771 break;
772 }
773
774 /* Write the data. */
775 if (fwrite(data, 1, length, stream) != length)
776 png_error(png_ptr, "Can't write the output file");
777 process.out_file_size += length;
778 }
779
780 /*
781 * Image info initialization.
782 */
783 static void
opng_clear_image_info(void)784 opng_clear_image_info(void)
785 {
786 memset(&image, 0, sizeof(image));
787 }
788
789 /*
790 * Image info transfer.
791 */
792 static void
opng_load_image_info(png_structp png_ptr,png_infop info_ptr,int load_meta)793 opng_load_image_info(png_structp png_ptr, png_infop info_ptr, int load_meta)
794 {
795 memset(&image, 0, sizeof(image));
796
797 png_get_IHDR(png_ptr, info_ptr,
798 &image.width, &image.height, &image.bit_depth,
799 &image.color_type, &image.interlace_type,
800 &image.compression_type, &image.filter_type);
801 image.row_pointers = png_get_rows(png_ptr, info_ptr);
802 png_get_PLTE(png_ptr, info_ptr, &image.palette, &image.num_palette);
803 /* Transparency is not considered metadata, although tRNS is ancillary.
804 * See the comment in opng_is_image_chunk() above.
805 */
806 if (png_get_tRNS(png_ptr, info_ptr,
807 &image.trans_alpha,
808 &image.num_trans, &image.trans_color_ptr))
809 {
810 /* Double copying (pointer + value) is necessary here
811 * due to an inconsistency in the libpng design.
812 */
813 if (image.trans_color_ptr != NULL)
814 {
815 image.trans_color = *image.trans_color_ptr;
816 image.trans_color_ptr = &image.trans_color;
817 }
818 }
819
820 if (!load_meta)
821 return;
822
823 if (png_get_bKGD(png_ptr, info_ptr, &image.background_ptr))
824 {
825 /* Same problem as in tRNS. */
826 image.background = *image.background_ptr;
827 image.background_ptr = &image.background;
828 }
829 png_get_hIST(png_ptr, info_ptr, &image.hist);
830 if (png_get_sBIT(png_ptr, info_ptr, &image.sig_bit_ptr))
831 {
832 /* Same problem as in tRNS. */
833 image.sig_bit = *image.sig_bit_ptr;
834 image.sig_bit_ptr = &image.sig_bit;
835 }
836 image.num_unknowns =
837 png_get_unknown_chunks(png_ptr, info_ptr, &image.unknowns);
838 }
839
840 /*
841 * Image info transfer.
842 */
843 static void
opng_store_image_info(png_structp png_ptr,png_infop info_ptr,int store_meta)844 opng_store_image_info(png_structp png_ptr, png_infop info_ptr, int store_meta)
845 {
846 int i;
847
848 OPNG_ENSURE(image.row_pointers != NULL, "No info in image");
849
850 png_set_IHDR(png_ptr, info_ptr,
851 image.width, image.height, image.bit_depth,
852 image.color_type, image.interlace_type,
853 image.compression_type, image.filter_type);
854 png_set_rows(write_ptr, write_info_ptr, image.row_pointers);
855 if (image.palette != NULL)
856 png_set_PLTE(png_ptr, info_ptr, image.palette, image.num_palette);
857 /* Transparency is not considered metadata, although tRNS is ancillary.
858 * See the comment in opng_is_image_chunk() above.
859 */
860 if (image.trans_alpha != NULL || image.trans_color_ptr != NULL)
861 png_set_tRNS(png_ptr, info_ptr,
862 image.trans_alpha,
863 image.num_trans, image.trans_color_ptr);
864
865 if (!store_meta)
866 return;
867
868 if (image.background_ptr != NULL)
869 png_set_bKGD(png_ptr, info_ptr, image.background_ptr);
870 if (image.hist != NULL)
871 png_set_hIST(png_ptr, info_ptr, image.hist);
872 if (image.sig_bit_ptr != NULL)
873 png_set_sBIT(png_ptr, info_ptr, image.sig_bit_ptr);
874 if (image.num_unknowns != 0)
875 {
876 png_set_unknown_chunks(png_ptr, info_ptr,
877 image.unknowns, image.num_unknowns);
878 /* This should be handled by libpng. */
879 for (i = 0; i < image.num_unknowns; ++i)
880 png_set_unknown_chunk_location(png_ptr, info_ptr,
881 i, image.unknowns[i].location);
882 }
883 }
884
885 /*
886 * Image info destruction.
887 */
888 static void
opng_destroy_image_info(void)889 opng_destroy_image_info(void)
890 {
891 png_uint_32 i;
892 int j;
893
894 if (image.row_pointers == NULL)
895 return; /* nothing to clean up */
896
897 for (i = 0; i < image.height; ++i)
898 opng_free(image.row_pointers[i]);
899 opng_free(image.row_pointers);
900 opng_free(image.palette);
901 opng_free(image.trans_alpha);
902 opng_free(image.hist);
903 for (j = 0; j < image.num_unknowns; ++j)
904 opng_free(image.unknowns[j].data);
905 opng_free(image.unknowns);
906 /* DO NOT deallocate background_ptr, sig_bit_ptr, trans_color_ptr.
907 * See the comments regarding double copying inside opng_load_image_info().
908 */
909
910 /* Clear the space here and do not worry about double-deallocation issues
911 * that might arise later on.
912 */
913 memset(&image, 0, sizeof(image));
914 }
915
916 /*
917 * Image file reading.
918 */
919 static void
opng_read_file(FILE * infile)920 opng_read_file(FILE *infile)
921 {
922 const char *fmt_name;
923 int num_img;
924 png_uint_32 reductions;
925 const char * volatile err_msg; /* volatile is required by cexcept */
926
927 Try
928 {
929 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
930 NULL, opng_error, opng_warning);
931 read_info_ptr = png_create_info_struct(read_ptr);
932 if (read_info_ptr == NULL)
933 Throw "Out of memory";
934
935 /* Override the default libpng settings. */
936 png_set_keep_unknown_chunks(read_ptr,
937 PNG_HANDLE_CHUNK_ALWAYS, NULL, 0);
938 png_set_user_limits(read_ptr, PNG_UINT_31_MAX, PNG_UINT_31_MAX);
939
940 /* Read the input image file. */
941 opng_init_read_data();
942 pngx_set_read_fn(read_ptr, infile, opng_read_data);
943 fmt_name = NULL;
944 num_img = pngx_read_image(read_ptr, read_info_ptr, &fmt_name, NULL);
945 if (num_img <= 0)
946 Throw "Unrecognized image file format";
947 if (num_img > 1)
948 process.status |= INPUT_HAS_MULTIPLE_IMAGES;
949 if ((process.status & INPUT_IS_PNG_FILE) &&
950 (process.status & INPUT_HAS_MULTIPLE_IMAGES))
951 {
952 /* pngxtern can't distinguish between APNG and proper PNG. */
953 fmt_name = (process.status & INPUT_HAS_PNG_SIGNATURE) ?
954 "APNG" : "APNG datastream";
955 }
956 OPNG_ENSURE(fmt_name != NULL, "No format name from pngxtern");
957
958 if (process.in_file_size == 0)
959 {
960 if (opng_fgetsize(infile, &process.in_file_size) < 0)
961 {
962 opng_print_warning("Can't get the correct file size");
963 process.in_file_size = 0;
964 }
965 }
966
967 err_msg = NULL; /* everything is ok */
968 }
969 Catch (err_msg)
970 {
971 /* If the critical info has been loaded, treat all errors as warnings.
972 * This enables a more advanced data recovery.
973 */
974 if (opng_validate_image(read_ptr, read_info_ptr))
975 {
976 png_warning(read_ptr, err_msg);
977 err_msg = NULL;
978 }
979 }
980
981 Try
982 {
983 if (err_msg != NULL)
984 Throw err_msg;
985
986 /* Display format and image information. */
987 if (strcmp(fmt_name, "PNG") != 0)
988 {
989 usr_printf("Importing %s", fmt_name);
990 if (process.status & INPUT_HAS_MULTIPLE_IMAGES)
991 {
992 if (!(process.status & INPUT_IS_PNG_FILE))
993 usr_printf(" (multi-image or animation)");
994 if (options.snip)
995 usr_printf("; snipping...");
996 }
997 usr_printf("\n");
998 }
999 opng_load_image_info(read_ptr, read_info_ptr, 1);
1000 opng_print_image_info(1, 1, 1, 1);
1001 usr_printf("\n");
1002
1003 /* Choose the applicable image reductions. */
1004 reductions = OPNG_REDUCE_ALL & ~OPNG_REDUCE_METADATA;
1005 if (options.nb)
1006 reductions &= ~OPNG_REDUCE_BIT_DEPTH;
1007 if (options.nc)
1008 reductions &= ~OPNG_REDUCE_COLOR_TYPE;
1009 if (options.np)
1010 reductions &= ~OPNG_REDUCE_PALETTE;
1011 if (options.nz && (process.status & INPUT_HAS_PNG_DATASTREAM))
1012 {
1013 /* Do not reduce files with PNG datastreams under -nz. */
1014 reductions = OPNG_REDUCE_NONE;
1015 }
1016 if (process.status & INPUT_HAS_DIGITAL_SIGNATURE)
1017 {
1018 /* Do not reduce signed files. */
1019 reductions = OPNG_REDUCE_NONE;
1020 }
1021 if ((process.status & INPUT_IS_PNG_FILE) &&
1022 (process.status & INPUT_HAS_MULTIPLE_IMAGES) &&
1023 (reductions != OPNG_REDUCE_NONE) && !options.snip)
1024 {
1025 usr_printf(
1026 "Can't reliably reduce APNG file; disabling reductions.\n"
1027 "(Did you want to -snip and optimize the first frame?)\n");
1028 reductions = OPNG_REDUCE_NONE;
1029 }
1030
1031 /* Try to reduce the image. */
1032 process.reductions =
1033 opng_reduce_image(read_ptr, read_info_ptr, reductions);
1034
1035 /* If the image is reduced, enforce full compression. */
1036 if (process.reductions != OPNG_REDUCE_NONE)
1037 {
1038 opng_load_image_info(read_ptr, read_info_ptr, 1);
1039 usr_printf("Reducing image to ");
1040 opng_print_image_info(0, 1, 1, 0);
1041 usr_printf("\n");
1042 }
1043
1044 /* Change the interlace type if required. */
1045 if (options.interlace >= 0 &&
1046 image.interlace_type != options.interlace)
1047 {
1048 image.interlace_type = options.interlace;
1049 /* A change in interlacing requires IDAT recoding. */
1050 process.status |= OUTPUT_NEEDS_NEW_IDAT;
1051 }
1052 }
1053 Catch (err_msg)
1054 {
1055 /* Do the cleanup, then rethrow the exception. */
1056 png_data_freer(read_ptr, read_info_ptr,
1057 PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL);
1058 png_destroy_read_struct(&read_ptr, &read_info_ptr, NULL);
1059 Throw err_msg;
1060 }
1061
1062 /* Destroy the libpng structures, but leave the enclosed data intact
1063 * to allow further processing.
1064 */
1065 png_data_freer(read_ptr, read_info_ptr,
1066 PNG_USER_WILL_FREE_DATA, PNG_FREE_ALL);
1067 png_destroy_read_struct(&read_ptr, &read_info_ptr, NULL);
1068 }
1069
1070 /*
1071 * PNG file writing.
1072 *
1073 * If the output file is NULL, PNG encoding is still done,
1074 * but no file is written.
1075 */
1076 static void
opng_write_file(FILE * outfile,int compression_level,int memory_level,int compression_strategy,int filter)1077 opng_write_file(FILE *outfile,
1078 int compression_level, int memory_level,
1079 int compression_strategy, int filter)
1080 {
1081 const char * volatile err_msg; /* volatile is required by cexcept */
1082
1083 OPNG_ENSURE(compression_level >= OPNG_COMPR_LEVEL_MIN &&
1084 compression_level <= OPNG_COMPR_LEVEL_MAX &&
1085 memory_level >= OPNG_MEM_LEVEL_MIN &&
1086 memory_level <= OPNG_MEM_LEVEL_MAX &&
1087 compression_strategy >= OPNG_STRATEGY_MIN &&
1088 compression_strategy <= OPNG_STRATEGY_MAX &&
1089 filter >= OPNG_FILTER_MIN &&
1090 filter <= OPNG_FILTER_MAX,
1091 "Invalid encoding parameters");
1092
1093 Try
1094 {
1095 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
1096 NULL, opng_error, opng_warning);
1097 write_info_ptr = png_create_info_struct(write_ptr);
1098 if (write_info_ptr == NULL)
1099 Throw "Out of memory";
1100
1101 png_set_compression_level(write_ptr, compression_level);
1102 png_set_compression_mem_level(write_ptr, memory_level);
1103 png_set_compression_strategy(write_ptr, compression_strategy);
1104 png_set_filter(write_ptr, PNG_FILTER_TYPE_BASE, filter_table[filter]);
1105 if (compression_strategy != Z_HUFFMAN_ONLY &&
1106 compression_strategy != Z_RLE)
1107 {
1108 if (options.window_bits > 0)
1109 png_set_compression_window_bits(write_ptr,
1110 options.window_bits);
1111 }
1112 else
1113 {
1114 #ifdef WBITS_8_OK
1115 png_set_compression_window_bits(write_ptr, 8);
1116 #else
1117 png_set_compression_window_bits(write_ptr, 9);
1118 #endif
1119 }
1120
1121 /* Override the default libpng settings. */
1122 png_set_keep_unknown_chunks(write_ptr,
1123 PNG_HANDLE_CHUNK_ALWAYS, NULL, 0);
1124 png_set_user_limits(write_ptr, PNG_UINT_31_MAX, PNG_UINT_31_MAX);
1125
1126 /* Write the PNG stream. */
1127 opng_store_image_info(write_ptr, write_info_ptr, (outfile != NULL));
1128 opng_init_write_data();
1129 pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL);
1130 png_write_png(write_ptr, write_info_ptr, 0, NULL);
1131
1132 err_msg = NULL; /* everything is ok */
1133 }
1134 Catch (err_msg)
1135 {
1136 /* Set IDAT size to invalid. */
1137 process.out_idat_size = idat_size_max + 1;
1138 }
1139
1140 /* Destroy the libpng structures. */
1141 png_destroy_write_struct(&write_ptr, &write_info_ptr);
1142
1143 if (err_msg != NULL)
1144 Throw err_msg;
1145 }
1146
1147 /*
1148 * PNG file copying.
1149 */
1150 static void
opng_copy_file(FILE * infile,FILE * outfile)1151 opng_copy_file(FILE *infile, FILE *outfile)
1152 {
1153 volatile png_bytep buf; /* volatile is required by cexcept */
1154 const png_uint_32 buf_size_incr = 0x1000;
1155 png_uint_32 buf_size, length;
1156 png_byte chunk_hdr[8];
1157 const char * volatile err_msg;
1158
1159 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
1160 NULL, opng_error, opng_warning);
1161 if (write_ptr == NULL)
1162 Throw "Out of memory";
1163 opng_init_write_data();
1164 pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL);
1165
1166 Try
1167 {
1168 buf = NULL;
1169 buf_size = 0;
1170
1171 /* Write the signature in the output file. */
1172 pngx_write_sig(write_ptr);
1173
1174 /* Copy all chunks until IEND. */
1175 /* Error checking is done only at a very basic level. */
1176 do
1177 {
1178 if (fread(chunk_hdr, 8, 1, infile) != 1) /* length + name */
1179 Throw "Read error";
1180 length = png_get_uint_32(chunk_hdr);
1181 if (length > PNG_UINT_31_MAX)
1182 {
1183 if (buf == NULL && length == 0x89504e47UL) /* "\x89PNG" */
1184 {
1185 /* Skip the signature. */
1186 continue;
1187 }
1188 Throw "Data error";
1189 }
1190 if (length + 4 > buf_size)
1191 {
1192 png_free(write_ptr, buf);
1193 buf_size =
1194 (((length + 4) + (buf_size_incr - 1)) / buf_size_incr) *
1195 buf_size_incr;
1196 buf = (png_bytep)png_malloc(write_ptr, buf_size);
1197 /* Do not use realloc() here, it's slower. */
1198 }
1199 if (fread(buf, length + 4, 1, infile) != 1) /* data + crc */
1200 Throw "Read error";
1201 png_write_chunk(write_ptr, chunk_hdr + 4, buf, length);
1202 } while (memcmp(chunk_hdr + 4, sig_IEND, 4) != 0);
1203
1204 err_msg = NULL; /* everything is ok */
1205 }
1206 Catch (err_msg)
1207 {
1208 }
1209
1210 png_free(write_ptr, buf);
1211 png_destroy_write_struct(&write_ptr, NULL);
1212
1213 if (err_msg != NULL)
1214 Throw err_msg;
1215 }
1216
1217 /*
1218 * Iteration initialization.
1219 */
1220 static void
opng_init_iteration(opng_bitset_t cmdline_set,opng_bitset_t mask_set,const char * preset,opng_bitset_t * output_set)1221 opng_init_iteration(opng_bitset_t cmdline_set, opng_bitset_t mask_set,
1222 const char *preset, opng_bitset_t *output_set)
1223 {
1224 opng_bitset_t preset_set;
1225 int check;
1226
1227 *output_set = cmdline_set & mask_set;
1228 if (*output_set == 0 && cmdline_set != 0)
1229 Throw "Iteration parameter(s) out of range";
1230 if (*output_set == 0 || options.optim_level >= 0)
1231 {
1232 check =
1233 opng_strparse_rangeset_to_bitset(&preset_set, preset, mask_set);
1234 OPNG_ENSURE(check == 0, "[internal] Invalid preset");
1235 *output_set |= preset_set & mask_set;
1236 }
1237 }
1238
1239 /*
1240 * Iteration initialization.
1241 */
1242 static void
opng_init_iterations(void)1243 opng_init_iterations(void)
1244 {
1245 opng_bitset_t compr_level_set, mem_level_set, strategy_set, filter_set;
1246 opng_bitset_t strategy_singles_set;
1247 int preset_index;
1248 int t1, t2;
1249
1250 /* Set the IDAT size limit. The trials that pass this limit will be
1251 * abandoned, as there will be no need to wait until their completion.
1252 * This limit may further decrease as iterations go on.
1253 */
1254 if ((process.status & OUTPUT_NEEDS_NEW_IDAT) || options.full)
1255 process.max_idat_size = idat_size_max;
1256 else
1257 {
1258 OPNG_ENSURE(process.in_idat_size > 0, "No IDAT in input");
1259 /* Add the input PLTE and tRNS sizes to the initial max IDAT size,
1260 * to account for the changes that may occur during reduction.
1261 * This incurs a negligible overhead on processing only: the final
1262 * IDAT size will not be affected, because a precise check will be
1263 * performed at the end, inside opng_finish_iterations().
1264 */
1265 process.max_idat_size =
1266 process.in_idat_size + process.in_plte_trns_size;
1267 }
1268
1269 /* Get preset_index from options.optim_level, but leave the latter intact,
1270 * because the effect of "optipng -o2 -z... -f..." is slightly different
1271 * from the effect of "optipng -z... -f..." (without "-o").
1272 */
1273 preset_index = options.optim_level;
1274 if (preset_index < 0)
1275 preset_index = OPNG_OPTIM_LEVEL_DEFAULT;
1276 else if (preset_index > OPNG_OPTIM_LEVEL_MAX)
1277 preset_index = OPNG_OPTIM_LEVEL_MAX;
1278
1279 /* Initialize the iteration sets.
1280 * Combine the user-defined values with the optimization presets.
1281 */
1282 opng_init_iteration(options.compr_level_set, OPNG_COMPR_LEVEL_SET_MASK,
1283 presets[preset_index].compr_level, &compr_level_set);
1284 opng_init_iteration(options.mem_level_set, OPNG_MEM_LEVEL_SET_MASK,
1285 presets[preset_index].mem_level, &mem_level_set);
1286 opng_init_iteration(options.strategy_set, OPNG_STRATEGY_SET_MASK,
1287 presets[preset_index].strategy, &strategy_set);
1288 opng_init_iteration(options.filter_set, OPNG_FILTER_SET_MASK,
1289 presets[preset_index].filter, &filter_set);
1290
1291 /* Replace the empty sets with the libpng's "best guess" heuristics. */
1292 if (compr_level_set == 0)
1293 opng_bitset_set(&compr_level_set, Z_BEST_COMPRESSION); /* -zc9 */
1294 if (mem_level_set == 0)
1295 opng_bitset_set(&mem_level_set, 8);
1296 if (image.bit_depth < 8 || image.palette != NULL)
1297 {
1298 if (strategy_set == 0)
1299 opng_bitset_set(&strategy_set, Z_DEFAULT_STRATEGY); /* -zs0 */
1300 if (filter_set == 0)
1301 opng_bitset_set(&filter_set, 0); /* -f0 */
1302 }
1303 else
1304 {
1305 if (strategy_set == 0)
1306 opng_bitset_set(&strategy_set, Z_FILTERED); /* -zs1 */
1307 if (filter_set == 0)
1308 opng_bitset_set(&filter_set, 5); /* -f0 */
1309 }
1310
1311 /* Store the results into process. */
1312 process.compr_level_set = compr_level_set;
1313 process.mem_level_set = mem_level_set;
1314 process.strategy_set = strategy_set;
1315 process.filter_set = filter_set;
1316 strategy_singles_set = (1 << Z_HUFFMAN_ONLY) | (1 << Z_RLE);
1317 t1 = opng_bitset_count(compr_level_set) *
1318 opng_bitset_count(strategy_set & ~strategy_singles_set);
1319 t2 = opng_bitset_count(strategy_set & strategy_singles_set);
1320 process.num_iterations = (t1 + t2) *
1321 opng_bitset_count(mem_level_set) *
1322 opng_bitset_count(filter_set);
1323 OPNG_ENSURE(process.num_iterations > 0, "Invalid iteration parameters");
1324 }
1325
1326 /*
1327 * Iteration.
1328 */
1329 static void
opng_iterate(void)1330 opng_iterate(void)
1331 {
1332 opng_bitset_t compr_level_set, mem_level_set, strategy_set, filter_set;
1333 int compr_level, mem_level, strategy, filter;
1334 int counter;
1335 int line_reused;
1336
1337 OPNG_ENSURE(process.num_iterations > 0, "Iterations not initialized");
1338
1339 compr_level_set = process.compr_level_set;
1340 mem_level_set = process.mem_level_set;
1341 strategy_set = process.strategy_set;
1342 filter_set = process.filter_set;
1343
1344 if ((process.num_iterations == 1) &&
1345 (process.status & OUTPUT_NEEDS_NEW_IDAT))
1346 {
1347 /* There is only one combination. Select it and return. */
1348 process.best_idat_size = 0; /* unknown */
1349 process.best_compr_level = opng_bitset_find_first(compr_level_set);
1350 process.best_mem_level = opng_bitset_find_first(mem_level_set);
1351 process.best_strategy = opng_bitset_find_first(strategy_set);
1352 process.best_filter = opng_bitset_find_first(filter_set);
1353 return;
1354 }
1355
1356 /* Prepare for the big iteration. */
1357 process.best_idat_size = idat_size_max + 1;
1358 process.best_compr_level = -1;
1359 process.best_mem_level = -1;
1360 process.best_strategy = -1;
1361 process.best_filter = -1;
1362
1363 /* Iterate through the "hyper-rectangle" (zc, zm, zs, f). */
1364 usr_printf("\nTrying:\n");
1365 line_reused = 0;
1366 counter = 0;
1367 for (filter = OPNG_FILTER_MIN;
1368 filter <= OPNG_FILTER_MAX;
1369 ++filter)
1370 {
1371 if (!opng_bitset_test(filter_set, filter))
1372 continue;
1373 for (strategy = OPNG_STRATEGY_MIN;
1374 strategy <= OPNG_STRATEGY_MAX;
1375 ++strategy)
1376 {
1377 if (!opng_bitset_test(strategy_set, strategy))
1378 continue;
1379 if (strategy == Z_HUFFMAN_ONLY)
1380 {
1381 /* Under Z_HUFFMAN_ONLY, all compression levels
1382 * (deflate_fast and deflate_slow combined)
1383 * produce the same output. Pick level 1.
1384 */
1385 compr_level_set = 0;
1386 opng_bitset_set(&compr_level_set, 1);
1387 }
1388 else if (strategy == Z_RLE)
1389 {
1390 /* Under Z_RLE, all deflate_fast compression levels produce
1391 * the same output. Ditto about the deflate_slow levels.
1392 * Pick level 9, in preference for deflate_slow.
1393 */
1394 compr_level_set = 0;
1395 opng_bitset_set(&compr_level_set, 9);
1396 }
1397 else
1398 {
1399 /* Restore compr_level_set. */
1400 compr_level_set = process.compr_level_set;
1401 }
1402 for (compr_level = OPNG_COMPR_LEVEL_MAX;
1403 compr_level >= OPNG_COMPR_LEVEL_MIN;
1404 --compr_level)
1405 {
1406 if (!opng_bitset_test(compr_level_set, compr_level))
1407 continue;
1408 for (mem_level = OPNG_MEM_LEVEL_MAX;
1409 mem_level >= OPNG_MEM_LEVEL_MIN;
1410 --mem_level)
1411 {
1412 if (!opng_bitset_test(mem_level_set, mem_level))
1413 continue;
1414 usr_printf(" zc = %d zm = %d zs = %d f = %d",
1415 compr_level, mem_level, strategy, filter);
1416 usr_progress(counter, process.num_iterations);
1417 ++counter;
1418 opng_write_file(NULL,
1419 compr_level, mem_level, strategy, filter);
1420 if (process.out_idat_size > idat_size_max)
1421 {
1422 if (options.verbose)
1423 {
1424 usr_printf("\t\tIDAT too big\n");
1425 line_reused = 0;
1426 }
1427 else
1428 {
1429 usr_print_cntrl('\r'); /* CR: reset line */
1430 line_reused = 1;
1431 }
1432 continue;
1433 }
1434 usr_printf("\t\tIDAT size = %" OPNG_FSIZE_PRIu "\n",
1435 process.out_idat_size);
1436 line_reused = 0;
1437 if (process.best_idat_size < process.out_idat_size)
1438 {
1439 /* The current best size is smaller than the last size.
1440 * Discard the last iteration.
1441 */
1442 continue;
1443 }
1444 if (process.best_idat_size == process.out_idat_size &&
1445 (process.best_strategy == Z_HUFFMAN_ONLY ||
1446 process.best_strategy == Z_RLE))
1447 {
1448 /* The current best size is equal to the last size;
1449 * the current best strategy is already the fastest.
1450 * Discard the last iteration.
1451 */
1452 continue;
1453 }
1454 process.best_compr_level = compr_level;
1455 process.best_mem_level = mem_level;
1456 process.best_strategy = strategy;
1457 process.best_filter = filter;
1458 process.best_idat_size = process.out_idat_size;
1459 if (!options.full)
1460 process.max_idat_size = process.out_idat_size;
1461 }
1462 }
1463 }
1464 }
1465 if (line_reused)
1466 usr_print_cntrl(-31); /* minus N: erase N chars from start of line */
1467
1468 OPNG_ENSURE(counter == process.num_iterations,
1469 "Inconsistent iteration counter");
1470 usr_progress(counter, process.num_iterations);
1471 }
1472
1473 /*
1474 * Iteration finalization.
1475 */
1476 static void
opng_finish_iterations(void)1477 opng_finish_iterations(void)
1478 {
1479 if (process.best_idat_size + process.out_plte_trns_size <
1480 process.in_idat_size + process.in_plte_trns_size)
1481 process.status |= OUTPUT_NEEDS_NEW_IDAT;
1482 if (process.status & OUTPUT_NEEDS_NEW_IDAT)
1483 {
1484 if (process.best_idat_size <= idat_size_max)
1485 {
1486 usr_printf("\nSelecting parameters:\n");
1487 usr_printf(" zc = %d zm = %d zs = %d f = %d",
1488 process.best_compr_level, process.best_mem_level,
1489 process.best_strategy, process.best_filter);
1490 if (process.best_idat_size > 0)
1491 {
1492 /* At least one trial has been run. */
1493 usr_printf("\t\tIDAT size = %" OPNG_FSIZE_PRIu,
1494 process.best_idat_size);
1495 }
1496 usr_printf("\n");
1497 }
1498 else
1499 {
1500 /* The compressed image data is larger than the maximum allowed. */
1501 usr_printf(" zc = * zm = * zs = * f = *\t\tIDAT size > %s\n",
1502 idat_size_max_string);
1503 }
1504 }
1505 }
1506
1507 /*
1508 * Image file optimization.
1509 */
1510 static void
opng_optimize_impl(const char * infile_name)1511 opng_optimize_impl(const char *infile_name)
1512 {
1513 static FILE *infile, *outfile; /* static or volatile is required */
1514 static const char *infile_name_local; /* by cexcept */
1515 static const char *outfile_name, *bakfile_name;
1516 static int new_outfile, has_backup;
1517 char name_buf[FILENAME_MAX], tmp_buf[FILENAME_MAX];
1518 const char * volatile err_msg;
1519
1520 memset(&process, 0, sizeof(process));
1521 if (options.force)
1522 process.status |= OUTPUT_NEEDS_NEW_IDAT;
1523
1524 err_msg = NULL; /* prepare for error handling */
1525
1526 infile_name_local = infile_name;
1527 if ((infile = fopen(infile_name_local, "rb")) == NULL)
1528 Throw "Can't open the input file";
1529 Try
1530 {
1531 opng_read_file(infile);
1532 }
1533 Catch (err_msg)
1534 {
1535 OPNG_ENSURE(err_msg != NULL, "Mysterious error in opng_read_file");
1536 }
1537 fclose(infile); /* finally */
1538 if (err_msg != NULL)
1539 Throw err_msg; /* rethrow */
1540
1541 /* Check the error flag. This must be the first check. */
1542 if (process.status & INPUT_HAS_ERRORS)
1543 {
1544 usr_printf("Recoverable errors found in input.");
1545 if (options.fix)
1546 {
1547 usr_printf(" Fixing...\n");
1548 process.status |= OUTPUT_NEEDS_NEW_FILE;
1549 }
1550 else
1551 {
1552 usr_printf(" Rerun " PROGRAM_NAME " with -fix enabled.\n");
1553 Throw "Previous error(s) not fixed";
1554 }
1555 }
1556
1557 /* Check the junk flag. */
1558 if (process.status & INPUT_HAS_JUNK)
1559 process.status |= OUTPUT_NEEDS_NEW_FILE;
1560
1561 /* Check the PNG signature and datastream flags. */
1562 if (!(process.status & INPUT_HAS_PNG_SIGNATURE))
1563 process.status |= OUTPUT_NEEDS_NEW_FILE;
1564 if (process.status & INPUT_HAS_PNG_DATASTREAM)
1565 {
1566 if (options.nz && (process.status & OUTPUT_NEEDS_NEW_IDAT))
1567 {
1568 usr_printf(
1569 "IDAT recoding is necessary, but is disabled by the user.\n");
1570 Throw "Can't continue";
1571 }
1572 }
1573 else
1574 process.status |= OUTPUT_NEEDS_NEW_IDAT;
1575
1576 /* Check the digital signature flag. */
1577 if (process.status & INPUT_HAS_DIGITAL_SIGNATURE)
1578 {
1579 usr_printf("Digital signature found in input.");
1580 if (options.force)
1581 {
1582 usr_printf(" Erasing...\n");
1583 process.status |= OUTPUT_NEEDS_NEW_FILE;
1584 }
1585 else
1586 {
1587 usr_printf(" Rerun " PROGRAM_NAME " with -force enabled.\n");
1588 Throw "Can't optimize digitally-signed files";
1589 }
1590 }
1591
1592 /* Check the multi-image flag. */
1593 if (process.status & INPUT_HAS_MULTIPLE_IMAGES)
1594 {
1595 if (!options.snip && !(process.status & INPUT_IS_PNG_FILE))
1596 {
1597 usr_printf("Conversion to PNG requires snipping. "
1598 "Rerun " PROGRAM_NAME " with -snip enabled.\n");
1599 Throw "Incompatible input format";
1600 }
1601 }
1602 if ((process.status & INPUT_HAS_APNG) && options.snip)
1603 process.status |= OUTPUT_NEEDS_NEW_FILE;
1604
1605 /* Check the stripped-data flag. */
1606 if (process.status & INPUT_HAS_STRIPPED_DATA)
1607 usr_printf("Stripping metadata...\n");
1608
1609 /* Initialize the output file name. */
1610 outfile_name = NULL;
1611 if (!(process.status & INPUT_IS_PNG_FILE))
1612 {
1613 if (opng_path_replace_ext(name_buf, sizeof(name_buf),
1614 infile_name_local, ".png") == NULL)
1615 Throw "Can't create the output file (name too long)";
1616 outfile_name = name_buf;
1617 }
1618 if (options.out_name != NULL)
1619 outfile_name = options.out_name; /* override the old name */
1620 if (options.dir_name != NULL)
1621 {
1622 const char *tmp_name;
1623 if (outfile_name != NULL)
1624 {
1625 strcpy(tmp_buf, outfile_name);
1626 tmp_name = tmp_buf;
1627 }
1628 else
1629 tmp_name = infile_name_local;
1630 if (opng_path_replace_dir(name_buf, sizeof(name_buf),
1631 tmp_name, options.dir_name) == NULL)
1632 Throw "Can't create the output file (name too long)";
1633 outfile_name = name_buf;
1634 }
1635 if (outfile_name == NULL)
1636 {
1637 outfile_name = infile_name_local;
1638 new_outfile = 0;
1639 }
1640 else
1641 {
1642 int test_eq = opng_os_test_eq(infile_name_local, outfile_name);
1643 if (test_eq >= 0)
1644 new_outfile = (test_eq == 0);
1645 else
1646 {
1647 /* We don't know if the two paths point to the same file.
1648 * Use a crude path name comparison.
1649 */
1650 new_outfile = (strcmp(infile_name_local, outfile_name) != 0);
1651 }
1652 }
1653
1654 /* Initialize the backup file name. */
1655 bakfile_name = tmp_buf;
1656 if (new_outfile)
1657 {
1658 if (opng_path_make_backup(tmp_buf, sizeof(tmp_buf),
1659 outfile_name) == NULL)
1660 bakfile_name = NULL;
1661 }
1662 else
1663 {
1664 if (opng_path_make_backup(tmp_buf, sizeof(tmp_buf),
1665 infile_name_local) == NULL)
1666 bakfile_name = NULL;
1667 }
1668 /* Check the name even in simulation mode, to ensure a uniform behavior. */
1669 if (bakfile_name == NULL)
1670 Throw "Can't create backup file (name too long)";
1671 /* Check the backup file before engaging in lengthy trials. */
1672 if (!options.simulate && opng_os_test(outfile_name, "e") == 0)
1673 {
1674 if (new_outfile && !options.backup && !options.clobber)
1675 {
1676 usr_printf("The output file exists. "
1677 "Rerun " PROGRAM_NAME " with -backup enabled.\n");
1678 Throw "Can't overwrite the output file";
1679 }
1680 if (opng_os_test(outfile_name, "fw") != 0 ||
1681 (!options.clobber && opng_os_test(bakfile_name, "e") == 0))
1682 Throw "Can't back up the existing output file";
1683 }
1684
1685 /* Display the input IDAT/file sizes. */
1686 if (process.status & INPUT_HAS_PNG_DATASTREAM)
1687 usr_printf("Input IDAT size = %" OPNG_FSIZE_PRIu " bytes\n",
1688 process.in_idat_size);
1689 usr_printf("Input file size = %" OPNG_FSIZE_PRIu " bytes\n",
1690 process.in_file_size);
1691
1692 /* Find the best parameters and see if it's worth recompressing. */
1693 if (!options.nz || (process.status & OUTPUT_NEEDS_NEW_IDAT))
1694 {
1695 opng_init_iterations();
1696 opng_iterate();
1697 opng_finish_iterations();
1698 }
1699 if (process.status & OUTPUT_NEEDS_NEW_IDAT)
1700 {
1701 process.status |= OUTPUT_NEEDS_NEW_FILE;
1702 opng_check_idat_size(process.best_idat_size);
1703 }
1704
1705 /* Stop here? */
1706 if (!(process.status & OUTPUT_NEEDS_NEW_FILE))
1707 {
1708 usr_printf("\n%s is already optimized.\n", infile_name_local);
1709 if (!new_outfile)
1710 return;
1711 }
1712 if (options.simulate)
1713 {
1714 usr_printf("\nNo output: simulation mode.\n");
1715 return;
1716 }
1717
1718 /* Make room for the output file. */
1719 if (new_outfile)
1720 {
1721 usr_printf("\nOutput file: %s\n", outfile_name);
1722 if (options.dir_name != NULL)
1723 opng_os_create_dir(options.dir_name);
1724 has_backup = 0;
1725 if (opng_os_test(outfile_name, "e") == 0)
1726 {
1727 if (opng_os_rename(outfile_name, bakfile_name,
1728 options.clobber) != 0)
1729 Throw "Can't back up the output file";
1730 has_backup = 1;
1731 }
1732 }
1733 else
1734 {
1735 if (opng_os_rename(infile_name_local, bakfile_name,
1736 options.clobber) != 0)
1737 Throw "Can't back up the input file";
1738 has_backup = 1;
1739 }
1740
1741 outfile = fopen(outfile_name, "wb");
1742 Try
1743 {
1744 if (outfile == NULL)
1745 Throw "Can't open the output file";
1746 if (process.status & OUTPUT_NEEDS_NEW_IDAT)
1747 {
1748 /* Write a brand new PNG datastream to the output. */
1749 opng_write_file(outfile,
1750 process.best_compr_level, process.best_mem_level,
1751 process.best_strategy, process.best_filter);
1752 }
1753 else
1754 {
1755 /* Copy the input PNG datastream to the output. */
1756 infile = fopen(new_outfile ? infile_name_local : bakfile_name,
1757 "rb");
1758 if (infile == NULL)
1759 Throw "Can't reopen the input file";
1760 Try
1761 {
1762 if (process.in_datastream_offset > 0 &&
1763 opng_fseeko(infile, process.in_datastream_offset,
1764 SEEK_SET) != 0)
1765 Throw "Can't reposition the input file";
1766 process.best_idat_size = process.in_idat_size;
1767 opng_copy_file(infile, outfile);
1768 }
1769 Catch (err_msg)
1770 {
1771 OPNG_ENSURE(err_msg != NULL,
1772 "Mysterious error in opng_copy_file");
1773 }
1774 fclose(infile); /* finally */
1775 if (err_msg != NULL)
1776 Throw err_msg; /* rethrow */
1777 }
1778 }
1779 Catch (err_msg)
1780 {
1781 if (outfile != NULL)
1782 fclose(outfile);
1783 /* Restore the original input file and rethrow the exception. */
1784 if (has_backup)
1785 {
1786 if (opng_os_rename(bakfile_name,
1787 new_outfile ? outfile_name : infile_name_local,
1788 1) != 0)
1789 opng_print_warning(
1790 "Can't recover the original file from backup");
1791 }
1792 else
1793 {
1794 OPNG_ENSURE(new_outfile,
1795 "Overwrote input with no temporary backup");
1796 if (opng_os_unlink(outfile_name) != 0)
1797 opng_print_warning("Can't remove the broken output file");
1798 }
1799 Throw err_msg; /* rethrow */
1800 }
1801 /* assert(err_msg == NULL); */
1802 fclose(outfile);
1803
1804 /* Preserve file attributes (e.g. ownership, access rights, time stamps)
1805 * on request, if possible.
1806 */
1807 if (options.preserve)
1808 opng_os_copy_attr(new_outfile ? infile_name_local : bakfile_name,
1809 outfile_name);
1810
1811 /* Remove the backup file if it is not needed. */
1812 if (!new_outfile && !options.backup)
1813 {
1814 if (opng_os_unlink(bakfile_name) != 0)
1815 opng_print_warning("Can't remove the backup file");
1816 }
1817
1818 /* Display the output IDAT/file sizes. */
1819 usr_printf("\nOutput IDAT size = %" OPNG_FSIZE_PRIu " bytes",
1820 process.out_idat_size);
1821 if (process.status & INPUT_HAS_PNG_DATASTREAM)
1822 {
1823 usr_printf(" (");
1824 opng_print_fsize_difference(process.in_idat_size,
1825 process.out_idat_size, 0);
1826 usr_printf(")");
1827 }
1828 usr_printf("\nOutput file size = %" OPNG_FSIZE_PRIu " bytes (",
1829 process.out_file_size);
1830 opng_print_fsize_difference(process.in_file_size,
1831 process.out_file_size, 1);
1832 usr_printf(")\n");
1833 }
1834
1835 /*
1836 * Engine initialization.
1837 */
1838 int
opng_initialize(const struct opng_options * init_options,const struct opng_ui * init_ui)1839 opng_initialize(const struct opng_options *init_options,
1840 const struct opng_ui *init_ui)
1841 {
1842 /* Initialize and check the validity of the user interface. */
1843 usr_printf = init_ui->printf_fn;
1844 usr_print_cntrl = init_ui->print_cntrl_fn;
1845 usr_progress = init_ui->progress_fn;
1846 usr_panic = init_ui->panic_fn;
1847 if (usr_printf == NULL ||
1848 usr_print_cntrl == NULL ||
1849 usr_progress == NULL ||
1850 usr_panic == NULL)
1851 return -1;
1852
1853 /* Initialize and adjust the user options. */
1854 options = *init_options;
1855 if (options.optim_level == 0)
1856 {
1857 options.nb = options.nc = options.np = 1;
1858 options.nz = 1;
1859 }
1860
1861 /* Start the engine. */
1862 memset(&summary, 0, sizeof(summary));
1863 engine.started = 1;
1864 return 0;
1865 }
1866
1867 /*
1868 * Engine execution.
1869 */
1870 int
opng_optimize(const char * infile_name)1871 opng_optimize(const char *infile_name)
1872 {
1873 const char *err_msg;
1874 volatile int result; /* volatile not needed, but keeps compilers happy */
1875
1876 OPNG_ENSURE(engine.started, "The OptiPNG engine is not running");
1877
1878 usr_printf("** Processing: %s\n", infile_name);
1879 ++summary.file_count;
1880 opng_clear_image_info();
1881 Try
1882 {
1883 opng_optimize_impl(infile_name);
1884 if (process.status & INPUT_HAS_ERRORS)
1885 {
1886 ++summary.err_count;
1887 ++summary.fix_count;
1888 }
1889 if (process.status & INPUT_HAS_MULTIPLE_IMAGES)
1890 {
1891 if (options.snip)
1892 ++summary.snip_count;
1893 }
1894 result = 0;
1895 }
1896 Catch (err_msg)
1897 {
1898 ++summary.err_count;
1899 opng_print_error(err_msg);
1900 result = -1;
1901 }
1902 opng_destroy_image_info();
1903 usr_printf("\n");
1904 return result;
1905 }
1906
1907 /*
1908 * Engine finalization.
1909 */
1910 int
opng_finalize(void)1911 opng_finalize(void)
1912 {
1913 /* Print the status report. */
1914 if (options.verbose || summary.snip_count > 0 || summary.err_count > 0)
1915 {
1916 usr_printf("** Status report\n");
1917 usr_printf("%u file(s) have been processed.\n", summary.file_count);
1918 if (summary.snip_count > 0)
1919 {
1920 usr_printf("%u multi-image file(s) have been snipped.\n",
1921 summary.snip_count);
1922 }
1923 if (summary.err_count > 0)
1924 {
1925 usr_printf("%u error(s) have been encountered.\n",
1926 summary.err_count);
1927 if (summary.fix_count > 0)
1928 usr_printf("%u erroneous file(s) have been fixed.\n",
1929 summary.fix_count);
1930 }
1931 }
1932
1933 /* Stop the engine. */
1934 engine.started = 0;
1935 return 0;
1936 }
1937