go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

gif2webp.c (21538B)


      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 
     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 
     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.
    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 
    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 //------------------------------------------------------------------------------