• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 //  simple tool to convert animated GIFs to WebP
11 //
12 // Authors: Skal (pascal.massimino@gmail.com)
13 //          Urvang (urvang@google.com)
14 
15 #include <assert.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #ifdef HAVE_CONFIG_H
21 #include "webp/config.h"
22 #endif
23 
24 #ifdef WEBP_HAVE_GIF
25 
26 #if defined(HAVE_UNISTD_H) && HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29 
30 #include <gif_lib.h>
31 #include "sharpyuv/sharpyuv.h"
32 #include "webp/encode.h"
33 #include "webp/mux.h"
34 #include "../examples/example_util.h"
35 #include "../imageio/imageio_util.h"
36 #include "./gifdec.h"
37 #include "./unicode.h"
38 #include "./unicode_gif.h"
39 
40 #if !defined(STDIN_FILENO)
41 #define STDIN_FILENO 0
42 #endif
43 
44 //------------------------------------------------------------------------------
45 
46 static int transparent_index = GIF_INDEX_INVALID;  // Opaque by default.
47 
48 static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
49   "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
50   "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
51 };
52 
ErrorString(WebPMuxError err)53 static const char* ErrorString(WebPMuxError err) {
54   assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
55   return kErrorMessages[-err];
56 }
57 
58 enum {
59   METADATA_ICC  = (1 << 0),
60   METADATA_XMP  = (1 << 1),
61   METADATA_ALL  = METADATA_ICC | METADATA_XMP
62 };
63 
64 //------------------------------------------------------------------------------
65 
Help(void)66 static void Help(void) {
67   printf("Usage:\n");
68   printf(" gif2webp [options] gif_file -o webp_file\n");
69   printf("Options:\n");
70   printf("  -h / -help ............. this help\n");
71   printf("  -lossy ................. encode image using lossy compression\n");
72   printf("  -mixed ................. for each frame in the image, pick lossy\n"
73          "                           or lossless compression heuristically\n");
74   printf("  -near_lossless <int> ... use near-lossless image preprocessing\n"
75          "                           (0..100=off), default=100\n");
76   printf("  -sharp_yuv ............. use sharper (and slower) RGB->YUV "
77                                     "conversion\n"
78          "                           (lossy only)\n");
79   printf("  -q <float> ............. quality factor (0:small..100:big)\n");
80   printf("  -m <int> ............... compression method (0=fast, 6=slowest), "
81          "default=4\n");
82   printf("  -min_size .............. minimize output size (default:off)\n"
83          "                           lossless compression by default; can be\n"
84          "                           combined with -q, -m, -lossy or -mixed\n"
85          "                           options\n");
86   printf("  -kmin <int> ............ min distance between key frames\n");
87   printf("  -kmax <int> ............ max distance between key frames\n");
88   printf("  -f <int> ............... filter strength (0=off..100)\n");
89   printf("  -metadata <string> ..... comma separated list of metadata to\n");
90   printf("                           ");
91   printf("copy from the input to the output if present\n");
92   printf("                           ");
93   printf("Valid values: all, none, icc, xmp (default)\n");
94   printf("  -loop_compatibility .... use compatibility mode for Chrome\n");
95   printf("                           version prior to M62 (inclusive)\n");
96   printf("  -mt .................... use multi-threading if available\n");
97   printf("\n");
98   printf("  -version ............... print version number and exit\n");
99   printf("  -v ..................... verbose\n");
100   printf("  -quiet ................. don't print anything\n");
101   printf("\n");
102 }
103 
104 //------------------------------------------------------------------------------
105 
106 // Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure.
main(int argc,const char * argv[])107 int main(int argc, const char* argv[]) {
108   int verbose = 0;
109   int gif_error = GIF_ERROR;
110   WebPMuxError err = WEBP_MUX_OK;
111   int ok = 0;
112   const W_CHAR* in_file = NULL, *out_file = NULL;
113   GifFileType* gif = NULL;
114   int frame_duration = 0;
115   int frame_timestamp = 0;
116   GIFDisposeMethod orig_dispose = GIF_DISPOSE_NONE;
117 
118   WebPPicture frame;                // Frame rectangle only (not disposed).
119   WebPPicture curr_canvas;          // Not disposed.
120   WebPPicture prev_canvas;          // Disposed.
121 
122   WebPAnimEncoder* enc = NULL;
123   WebPAnimEncoderOptions enc_options;
124   WebPConfig config;
125 
126   int frame_number = 0;     // Whether we are processing the first frame.
127   int done;
128   int c;
129   int quiet = 0;
130   WebPData webp_data;
131 
132   int keep_metadata = METADATA_XMP;  // ICC not output by default.
133   WebPData icc_data;
134   int stored_icc = 0;         // Whether we have already stored an ICC profile.
135   WebPData xmp_data;
136   int stored_xmp = 0;         // Whether we have already stored an XMP profile.
137   int loop_count = 0;         // default: infinite
138   int stored_loop_count = 0;  // Whether we have found an explicit loop count.
139   int loop_compatibility = 0;
140   WebPMux* mux = NULL;
141 
142   int default_kmin = 1;  // Whether to use default kmin value.
143   int default_kmax = 1;
144 
145   INIT_WARGV(argc, argv);
146 
147   if (!WebPConfigInit(&config) || !WebPAnimEncoderOptionsInit(&enc_options) ||
148       !WebPPictureInit(&frame) || !WebPPictureInit(&curr_canvas) ||
149       !WebPPictureInit(&prev_canvas)) {
150     fprintf(stderr, "Error! Version mismatch!\n");
151     FREE_WARGV_AND_RETURN(EXIT_FAILURE);
152   }
153   config.lossless = 1;  // Use lossless compression by default.
154 
155   WebPDataInit(&webp_data);
156   WebPDataInit(&icc_data);
157   WebPDataInit(&xmp_data);
158 
159   if (argc == 1) {
160     Help();
161     FREE_WARGV_AND_RETURN(EXIT_FAILURE);
162   }
163 
164   for (c = 1; c < argc; ++c) {
165     int parse_error = 0;
166     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
167       Help();
168       FREE_WARGV_AND_RETURN(EXIT_SUCCESS);
169     } else if (!strcmp(argv[c], "-o") && c < argc - 1) {
170       out_file = GET_WARGV(argv, ++c);
171     } else if (!strcmp(argv[c], "-lossy")) {
172       config.lossless = 0;
173     } else if (!strcmp(argv[c], "-mixed")) {
174       enc_options.allow_mixed = 1;
175       config.lossless = 0;
176     } else if (!strcmp(argv[c], "-near_lossless") && c < argc - 1) {
177       config.near_lossless = ExUtilGetInt(argv[++c], 0, &parse_error);
178     } else if (!strcmp(argv[c], "-sharp_yuv")) {
179       config.use_sharp_yuv = 1;
180     } else if (!strcmp(argv[c], "-loop_compatibility")) {
181       loop_compatibility = 1;
182     } else if (!strcmp(argv[c], "-q") && c < argc - 1) {
183       config.quality = ExUtilGetFloat(argv[++c], &parse_error);
184     } else if (!strcmp(argv[c], "-m") && c < argc - 1) {
185       config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
186     } else if (!strcmp(argv[c], "-min_size")) {
187       enc_options.minimize_size = 1;
188     } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
189       enc_options.kmax = ExUtilGetInt(argv[++c], 0, &parse_error);
190       default_kmax = 0;
191     } else if (!strcmp(argv[c], "-kmin") && c < argc - 1) {
192       enc_options.kmin = ExUtilGetInt(argv[++c], 0, &parse_error);
193       default_kmin = 0;
194     } else if (!strcmp(argv[c], "-f") && c < argc - 1) {
195       config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error);
196     } else if (!strcmp(argv[c], "-metadata") && c < argc - 1) {
197       static const struct {
198         const char* option;
199         int flag;
200       } kTokens[] = {
201         { "all",  METADATA_ALL },
202         { "none", 0 },
203         { "icc",  METADATA_ICC },
204         { "xmp",  METADATA_XMP },
205       };
206       const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens);
207       const char* start = argv[++c];
208       const char* const end = start + strlen(start);
209 
210       keep_metadata = 0;
211       while (start < end) {
212         size_t i;
213         const char* token = strchr(start, ',');
214         if (token == NULL) token = end;
215 
216         for (i = 0; i < kNumTokens; ++i) {
217           if ((size_t)(token - start) == strlen(kTokens[i].option) &&
218               !strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) {
219             if (kTokens[i].flag != 0) {
220               keep_metadata |= kTokens[i].flag;
221             } else {
222               keep_metadata = 0;
223             }
224             break;
225           }
226         }
227         if (i == kNumTokens) {
228           fprintf(stderr, "Error! Unknown metadata type '%.*s'\n",
229                   (int)(token - start), start);
230           Help();
231           FREE_WARGV_AND_RETURN(EXIT_FAILURE);
232         }
233         start = token + 1;
234       }
235     } else if (!strcmp(argv[c], "-mt")) {
236       ++config.thread_level;
237     } else if (!strcmp(argv[c], "-version")) {
238       const int enc_version = WebPGetEncoderVersion();
239       const int mux_version = WebPGetMuxVersion();
240       const int sharpyuv_version = SharpYuvGetVersion();
241       printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
242              (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
243              enc_version & 0xff, (mux_version >> 16) & 0xff,
244              (mux_version >> 8) & 0xff, mux_version & 0xff);
245       printf("libsharpyuv: %d.%d.%d\n", (sharpyuv_version >> 24) & 0xff,
246              (sharpyuv_version >> 16) & 0xffff, sharpyuv_version & 0xff);
247       FREE_WARGV_AND_RETURN(EXIT_SUCCESS);
248     } else if (!strcmp(argv[c], "-quiet")) {
249       quiet = 1;
250       enc_options.verbose = 0;
251     } else if (!strcmp(argv[c], "-v")) {
252       verbose = 1;
253       enc_options.verbose = 1;
254     } else if (!strcmp(argv[c], "--")) {
255       if (c < argc - 1) in_file = GET_WARGV(argv, ++c);
256       break;
257     } else if (argv[c][0] == '-') {
258       fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
259       Help();
260       FREE_WARGV_AND_RETURN(EXIT_FAILURE);
261     } else {
262       in_file = GET_WARGV(argv, c);
263     }
264 
265     if (parse_error) {
266       Help();
267       FREE_WARGV_AND_RETURN(EXIT_FAILURE);
268     }
269   }
270 
271   // Appropriate default kmin, kmax values for lossy and lossless.
272   if (default_kmin) {
273     enc_options.kmin = config.lossless ? 9 : 3;
274   }
275   if (default_kmax) {
276     enc_options.kmax = config.lossless ? 17 : 5;
277   }
278 
279   if (!WebPValidateConfig(&config)) {
280     fprintf(stderr, "Error! Invalid configuration.\n");
281     goto End;
282   }
283 
284   if (in_file == NULL) {
285     fprintf(stderr, "No input file specified!\n");
286     Help();
287     goto End;
288   }
289 
290   // Start the decoder object
291   gif = DGifOpenFileUnicode(in_file, &gif_error);
292   if (gif == NULL) goto End;
293 
294   // Loop over GIF images
295   done = 0;
296   do {
297     GifRecordType type;
298     if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
299 
300     switch (type) {
301       case IMAGE_DESC_RECORD_TYPE: {
302         GIFFrameRect gif_rect;
303         GifImageDesc* const image_desc = &gif->Image;
304 
305         if (!DGifGetImageDesc(gif)) goto End;
306 
307         if (frame_number == 0) {
308           if (verbose) {
309             printf("Canvas screen: %d x %d\n", gif->SWidth, gif->SHeight);
310           }
311           // Fix some broken GIF global headers that report
312           // 0 x 0 screen dimension.
313           if (gif->SWidth == 0 || gif->SHeight == 0) {
314             image_desc->Left = 0;
315             image_desc->Top = 0;
316             gif->SWidth = image_desc->Width;
317             gif->SHeight = image_desc->Height;
318             if (gif->SWidth <= 0 || gif->SHeight <= 0) {
319               goto End;
320             }
321             if (verbose) {
322               printf("Fixed canvas screen dimension to: %d x %d\n",
323                      gif->SWidth, gif->SHeight);
324             }
325           }
326           // Allocate current buffer.
327           frame.width = gif->SWidth;
328           frame.height = gif->SHeight;
329           frame.use_argb = 1;
330           if (!WebPPictureAlloc(&frame)) goto End;
331           GIFClearPic(&frame, NULL);
332           if (!(WebPPictureCopy(&frame, &curr_canvas) &&
333                 WebPPictureCopy(&frame, &prev_canvas))) {
334             fprintf(stderr, "Error allocating canvas.\n");
335             goto End;
336           }
337 
338           // Background color.
339           GIFGetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
340                                 transparent_index,
341                                 &enc_options.anim_params.bgcolor);
342 
343           // Initialize encoder.
344           enc = WebPAnimEncoderNew(curr_canvas.width, curr_canvas.height,
345                                    &enc_options);
346           if (enc == NULL) {
347             fprintf(stderr,
348                     "Error! Could not create encoder object. Possibly due to "
349                     "a memory error.\n");
350             goto End;
351           }
352         }
353 
354         // Some even more broken GIF can have sub-rect with zero width/height.
355         if (image_desc->Width == 0 || image_desc->Height == 0) {
356           image_desc->Width = gif->SWidth;
357           image_desc->Height = gif->SHeight;
358         }
359 
360         if (!GIFReadFrame(gif, transparent_index, &gif_rect, &frame)) {
361           goto End;
362         }
363         // Blend frame rectangle with previous canvas to compose full canvas.
364         // Note that 'curr_canvas' is same as 'prev_canvas' at this point.
365         GIFBlendFrames(&frame, &gif_rect, &curr_canvas);
366 
367         if (!WebPAnimEncoderAdd(enc, &curr_canvas, frame_timestamp, &config)) {
368           fprintf(stderr, "Error while adding frame #%d: %s\n", frame_number,
369                   WebPAnimEncoderGetError(enc));
370           goto End;
371         } else {
372           ++frame_number;
373         }
374 
375         // Update canvases.
376         GIFDisposeFrame(orig_dispose, &gif_rect, &prev_canvas, &curr_canvas);
377         GIFCopyPixels(&curr_canvas, &prev_canvas);
378 
379         // Force frames with a small or no duration to 100ms to be consistent
380         // with web browsers and other transcoding tools. This also avoids
381         // incorrect durations between frames when padding frames are
382         // discarded.
383         if (frame_duration <= 10) {
384           frame_duration = 100;
385         }
386 
387         // Update timestamp (for next frame).
388         frame_timestamp += frame_duration;
389 
390         // In GIF, graphic control extensions are optional for a frame, so we
391         // may not get one before reading the next frame. To handle this case,
392         // we reset frame properties to reasonable defaults for the next frame.
393         orig_dispose = GIF_DISPOSE_NONE;
394         frame_duration = 0;
395         transparent_index = GIF_INDEX_INVALID;
396         break;
397       }
398       case EXTENSION_RECORD_TYPE: {
399         int extension;
400         GifByteType* data = NULL;
401         if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
402           goto End;
403         }
404         if (data == NULL) continue;
405 
406         switch (extension) {
407           case COMMENT_EXT_FUNC_CODE: {
408             break;  // Do nothing for now.
409           }
410           case GRAPHICS_EXT_FUNC_CODE: {
411             if (!GIFReadGraphicsExtension(data, &frame_duration, &orig_dispose,
412                                           &transparent_index)) {
413               goto End;
414             }
415             break;
416           }
417           case PLAINTEXT_EXT_FUNC_CODE: {
418             break;
419           }
420           case APPLICATION_EXT_FUNC_CODE: {
421             if (data[0] != 11) break;    // Chunk is too short
422             if (!memcmp(data + 1, "NETSCAPE2.0", 11) ||
423                 !memcmp(data + 1, "ANIMEXTS1.0", 11)) {
424               if (!GIFReadLoopCount(gif, &data, &loop_count)) {
425                 goto End;
426               }
427               if (verbose) {
428                 fprintf(stderr, "Loop count: %d\n", loop_count);
429               }
430               stored_loop_count = loop_compatibility ? (loop_count != 0) : 1;
431             } else {  // An extension containing metadata.
432               // We only store the first encountered chunk of each type, and
433               // only if requested by the user.
434               const int is_xmp = (keep_metadata & METADATA_XMP) &&
435                                  !stored_xmp &&
436                                  !memcmp(data + 1, "XMP DataXMP", 11);
437               const int is_icc = (keep_metadata & METADATA_ICC) &&
438                                  !stored_icc &&
439                                  !memcmp(data + 1, "ICCRGBG1012", 11);
440               if (is_xmp || is_icc) {
441                 if (!GIFReadMetadata(gif, &data,
442                                      is_xmp ? &xmp_data : &icc_data)) {
443                   goto End;
444                 }
445                 if (is_icc) {
446                   stored_icc = 1;
447                 } else if (is_xmp) {
448                   stored_xmp = 1;
449                 }
450               }
451             }
452             break;
453           }
454           default: {
455             break;  // skip
456           }
457         }
458         while (data != NULL) {
459           if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
460         }
461         break;
462       }
463       case TERMINATE_RECORD_TYPE: {
464         done = 1;
465         break;
466       }
467       default: {
468         if (verbose) {
469           fprintf(stderr, "Skipping over unknown record type %d\n", type);
470         }
471         break;
472       }
473     }
474   } while (!done);
475 
476   // Last NULL frame.
477   if (!WebPAnimEncoderAdd(enc, NULL, frame_timestamp, NULL)) {
478     fprintf(stderr, "Error flushing WebP muxer.\n");
479     fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
480   }
481 
482   if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
483     fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
484     goto End;
485   }
486   // If there's only one frame, we don't need to handle loop count.
487   if (frame_number == 1) {
488     loop_count = 0;
489   } else if (!loop_compatibility) {
490     if (!stored_loop_count) {
491       // if no loop-count element is seen, the default is '1' (loop-once)
492       // and we need to signal it explicitly in WebP. Note however that
493       // in case there's a single frame, we still don't need to store it.
494       if (frame_number > 1) {
495         stored_loop_count = 1;
496         loop_count = 1;
497       }
498     } else if (loop_count > 0 && loop_count < 65535) {
499       // adapt GIF's semantic to WebP's (except in the infinite-loop case)
500       loop_count += 1;
501     }
502   }
503   // loop_count of 0 is the default (infinite), so no need to signal it
504   if (loop_count == 0) stored_loop_count = 0;
505 
506   if (stored_loop_count || stored_icc || stored_xmp) {
507     // Re-mux to add loop count and/or metadata as needed.
508     mux = WebPMuxCreate(&webp_data, 1);
509     if (mux == NULL) {
510       fprintf(stderr, "ERROR: Could not re-mux to add loop count/metadata.\n");
511       goto End;
512     }
513     WebPDataClear(&webp_data);
514 
515     if (stored_loop_count) {  // Update loop count.
516       WebPMuxAnimParams new_params;
517       err = WebPMuxGetAnimationParams(mux, &new_params);
518       if (err != WEBP_MUX_OK) {
519         fprintf(stderr, "ERROR (%s): Could not fetch loop count.\n",
520                 ErrorString(err));
521         goto End;
522       }
523       new_params.loop_count = loop_count;
524       err = WebPMuxSetAnimationParams(mux, &new_params);
525       if (err != WEBP_MUX_OK) {
526         fprintf(stderr, "ERROR (%s): Could not update loop count.\n",
527                 ErrorString(err));
528         goto End;
529       }
530     }
531 
532     if (stored_icc) {   // Add ICCP chunk.
533       err = WebPMuxSetChunk(mux, "ICCP", &icc_data, 1);
534       if (verbose) {
535         fprintf(stderr, "ICC size: %d\n", (int)icc_data.size);
536       }
537       if (err != WEBP_MUX_OK) {
538         fprintf(stderr, "ERROR (%s): Could not set ICC chunk.\n",
539                 ErrorString(err));
540         goto End;
541       }
542     }
543 
544     if (stored_xmp) {   // Add XMP chunk.
545       err = WebPMuxSetChunk(mux, "XMP ", &xmp_data, 1);
546       if (verbose) {
547         fprintf(stderr, "XMP size: %d\n", (int)xmp_data.size);
548       }
549       if (err != WEBP_MUX_OK) {
550         fprintf(stderr, "ERROR (%s): Could not set XMP chunk.\n",
551                 ErrorString(err));
552         goto End;
553       }
554     }
555 
556     err = WebPMuxAssemble(mux, &webp_data);
557     if (err != WEBP_MUX_OK) {
558       fprintf(stderr, "ERROR (%s): Could not assemble when re-muxing to add "
559               "loop count/metadata.\n", ErrorString(err));
560       goto End;
561     }
562   }
563 
564   if (out_file != NULL) {
565     if (!ImgIoUtilWriteFile((const char*)out_file, webp_data.bytes,
566                             webp_data.size)) {
567       WFPRINTF(stderr, "Error writing output file: %s\n", out_file);
568       goto End;
569     }
570     if (!quiet) {
571       if (!WSTRCMP(out_file, "-")) {
572         fprintf(stderr, "Saved %d bytes to STDIO\n",
573                 (int)webp_data.size);
574       } else {
575         WFPRINTF(stderr, "Saved output file (%d bytes): %s\n",
576                  (int)webp_data.size, out_file);
577       }
578     }
579   } else {
580     if (!quiet) {
581       fprintf(stderr, "Nothing written; use -o flag to save the result "
582                       "(%d bytes).\n", (int)webp_data.size);
583     }
584   }
585 
586   // All OK.
587   ok = 1;
588   gif_error = GIF_OK;
589 
590  End:
591   WebPDataClear(&icc_data);
592   WebPDataClear(&xmp_data);
593   WebPMuxDelete(mux);
594   WebPDataClear(&webp_data);
595   WebPPictureFree(&frame);
596   WebPPictureFree(&curr_canvas);
597   WebPPictureFree(&prev_canvas);
598   WebPAnimEncoderDelete(enc);
599 
600   if (gif_error != GIF_OK) {
601     GIFDisplayError(gif, gif_error);
602   }
603   if (gif != NULL) {
604 #if LOCAL_GIF_PREREQ(5,1)
605     DGifCloseFile(gif, &gif_error);
606 #else
607     DGifCloseFile(gif);
608 #endif
609   }
610 
611   FREE_WARGV_AND_RETURN(ok ? EXIT_SUCCESS : EXIT_FAILURE);
612 }
613 
614 #else  // !WEBP_HAVE_GIF
615 
main(int argc,const char * argv[])616 int main(int argc, const char* argv[]) {
617   fprintf(stderr, "GIF support not enabled in %s.\n", argv[0]);
618   (void)argc;
619   return EXIT_FAILURE;
620 }
621 
622 #endif
623 
624 //------------------------------------------------------------------------------
625