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