github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/examples/gif2webp.c (about)

     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 <string.h>
    18  
    19  #ifdef HAVE_CONFIG_H
    20  #include "config.h"
    21  #endif
    22  
    23  #ifdef WEBP_HAVE_GIF
    24  
    25  #include <gif_lib.h>
    26  #include "webp/encode.h"
    27  #include "webp/mux.h"
    28  #include "./example_util.h"
    29  #include "./gif2webp_util.h"
    30  
    31  #define GIF_TRANSPARENT_MASK 0x01
    32  #define GIF_DISPOSE_MASK     0x07
    33  #define GIF_DISPOSE_SHIFT    2
    34  #define WHITE_COLOR          0xffffffff
    35  #define MAX_CACHE_SIZE       30
    36  
    37  //------------------------------------------------------------------------------
    38  
    39  static int transparent_index = -1;  // Index of transparent color in the map.
    40  
    41  static void SanitizeKeyFrameIntervals(size_t* const kmin_ptr,
    42                                        size_t* const kmax_ptr) {
    43    size_t kmin = *kmin_ptr;
    44    size_t kmax = *kmax_ptr;
    45    int print_warning = 1;
    46  
    47    if (kmin == 0) {  // Disable keyframe insertion.
    48      kmax = ~0;
    49      kmin = kmax - 1;
    50      print_warning = 0;
    51    }
    52    if (kmax == 0) {
    53      kmax = ~0;
    54      print_warning = 0;
    55    }
    56  
    57    if (kmin >= kmax) {
    58      kmin = kmax - 1;
    59      if (print_warning) {
    60        fprintf(stderr,
    61                "WARNING: Setting kmin = %d, so that kmin < kmax.\n", (int)kmin);
    62      }
    63    } else if (kmin < (kmax / 2 + 1)) {
    64      // This ensures that cache.keyframe + kmin >= kmax is always true. So, we
    65      // can flush all the frames in the ‘count_since_key_frame == kmax’ case.
    66      kmin = (kmax / 2 + 1);
    67      if (print_warning) {
    68        fprintf(stderr,
    69                "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n",
    70                (int)kmin);
    71      }
    72    }
    73    // Limit the max number of frames that are allocated.
    74    if (kmax - kmin > MAX_CACHE_SIZE) {
    75      kmin = kmax - MAX_CACHE_SIZE;
    76      if (print_warning) {
    77        fprintf(stderr,
    78                "WARNING: Setting kmin = %d, so that kmax - kmin <= 30.\n",
    79                (int)kmin);
    80      }
    81    }
    82    *kmin_ptr = kmin;
    83    *kmax_ptr = kmax;
    84  }
    85  
    86  static void Remap(const uint8_t* const src, const GifFileType* const gif,
    87                    uint32_t* dst, int len) {
    88    int i;
    89    const GifColorType* colors;
    90    const ColorMapObject* const cmap =
    91        gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
    92    if (cmap == NULL) return;
    93    colors = cmap->Colors;
    94  
    95    for (i = 0; i < len; ++i) {
    96      const GifColorType c = colors[src[i]];
    97      dst[i] = (src[i] == transparent_index) ? WEBP_UTIL_TRANSPARENT_COLOR
    98             : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24);
    99    }
   100  }
   101  
   102  // Read the GIF image frame.
   103  static int ReadFrame(GifFileType* const gif, WebPFrameRect* const gif_rect,
   104                       WebPPicture* const webp_frame) {
   105    WebPPicture sub_image;
   106    const GifImageDesc image_desc = gif->Image;
   107    uint32_t* dst = NULL;
   108    uint8_t* tmp = NULL;
   109    int ok = 0;
   110    WebPFrameRect rect = {
   111        image_desc.Left, image_desc.Top, image_desc.Width, image_desc.Height
   112    };
   113    *gif_rect = rect;
   114  
   115    // Use a view for the sub-picture:
   116    if (!WebPPictureView(webp_frame, rect.x_offset, rect.y_offset,
   117                         rect.width, rect.height, &sub_image)) {
   118      fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n",
   119              rect.width, rect.height, rect.x_offset, rect.y_offset);
   120      return 0;
   121    }
   122    dst = sub_image.argb;
   123  
   124    tmp = (uint8_t*)malloc(rect.width * sizeof(*tmp));
   125    if (tmp == NULL) goto End;
   126  
   127    if (image_desc.Interlace) {  // Interlaced image.
   128      // We need 4 passes, with the following offsets and jumps.
   129      const int interlace_offsets[] = { 0, 4, 2, 1 };
   130      const int interlace_jumps[]   = { 8, 8, 4, 2 };
   131      int pass;
   132      for (pass = 0; pass < 4; ++pass) {
   133        int y;
   134        for (y = interlace_offsets[pass]; y < rect.height;
   135             y += interlace_jumps[pass]) {
   136          if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End;
   137          Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width);
   138        }
   139      }
   140    } else {  // Non-interlaced image.
   141      int y;
   142      for (y = 0; y < rect.height; ++y) {
   143        if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End;
   144        Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width);
   145      }
   146    }
   147    ok = 1;
   148  
   149   End:
   150    if (!ok) webp_frame->error_code = sub_image.error_code;
   151    WebPPictureFree(&sub_image);
   152    free(tmp);
   153    return ok;
   154  }
   155  
   156  static int GetBackgroundColor(const ColorMapObject* const color_map,
   157                                int bgcolor_idx, uint32_t* const bgcolor) {
   158    if (transparent_index != -1 && bgcolor_idx == transparent_index) {
   159      *bgcolor = WEBP_UTIL_TRANSPARENT_COLOR;  // Special case.
   160      return 1;
   161    } else if (color_map == NULL || color_map->Colors == NULL
   162               || bgcolor_idx >= color_map->ColorCount) {
   163      return 0;  // Invalid color map or index.
   164    } else {
   165      const GifColorType color = color_map->Colors[bgcolor_idx];
   166      *bgcolor = (0xff        << 24)
   167               | (color.Red   << 16)
   168               | (color.Green <<  8)
   169               | (color.Blue  <<  0);
   170      return 1;
   171    }
   172  }
   173  
   174  static void DisplayGifError(const GifFileType* const gif, int gif_error) {
   175    // GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
   176    // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
   177  #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && \
   178          ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4)
   179  #if GIFLIB_MAJOR >= 5
   180    // Static string actually, hence the const char* cast.
   181    const char* error_str = (const char*)GifErrorString(
   182        (gif == NULL) ? gif_error : gif->Error);
   183  #else
   184    const char* error_str = (const char*)GifErrorString();
   185    (void)gif;
   186  #endif
   187    if (error_str == NULL) error_str = "Unknown error";
   188    fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
   189  #else
   190    (void)gif;
   191    fprintf(stderr, "GIFLib Error %d: ", gif_error);
   192    PrintGifError();
   193    fprintf(stderr, "\n");
   194  #endif
   195  }
   196  
   197  static const char* const kErrorMessages[] = {
   198    "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
   199    "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
   200  };
   201  
   202  static const char* ErrorString(WebPMuxError err) {
   203    assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
   204    return kErrorMessages[-err];
   205  }
   206  
   207  enum {
   208    METADATA_ICC  = (1 << 0),
   209    METADATA_XMP  = (1 << 1),
   210    METADATA_ALL  = METADATA_ICC | METADATA_XMP
   211  };
   212  
   213  //------------------------------------------------------------------------------
   214  
   215  static void Help(void) {
   216    printf("Usage:\n");
   217    printf(" gif2webp [options] gif_file -o webp_file\n");
   218    printf("options:\n");
   219    printf("  -h / -help  ............ this help\n");
   220    printf("  -lossy ................. Encode image using lossy compression.\n");
   221    printf("  -mixed ................. For each frame in the image, pick lossy\n"
   222           "                           or lossless compression heuristically.\n");
   223    printf("  -q <float> ............. quality factor (0:small..100:big)\n");
   224    printf("  -m <int> ............... compression method (0=fast, 6=slowest)\n");
   225    printf("  -kmin <int> ............ Min distance between key frames\n");
   226    printf("  -kmax <int> ............ Max distance between key frames\n");
   227    printf("  -f <int> ............... filter strength (0=off..100)\n");
   228    printf("  -metadata <string> ..... comma separated list of metadata to\n");
   229    printf("                           ");
   230    printf("copy from the input to the output if present.\n");
   231    printf("                           "
   232           "Valid values: all, none, icc, xmp (default)\n");
   233    printf("  -mt .................... use multi-threading if available\n");
   234    printf("\n");
   235    printf("  -version ............... print version number and exit.\n");
   236    printf("  -v ..................... verbose.\n");
   237    printf("  -quiet ................. don't print anything.\n");
   238    printf("\n");
   239  }
   240  
   241  //------------------------------------------------------------------------------
   242  
   243  int main(int argc, const char *argv[]) {
   244    int verbose = 0;
   245    int gif_error = GIF_ERROR;
   246    WebPMuxError err = WEBP_MUX_OK;
   247    int ok = 0;
   248    const char *in_file = NULL, *out_file = NULL;
   249    FILE* out = NULL;
   250    GifFileType* gif = NULL;
   251    WebPConfig config;
   252    WebPPicture frame;
   253    WebPMuxFrameInfo info;
   254    WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
   255    WebPFrameCache* cache = NULL;
   256  
   257    int is_first_frame = 1;     // Whether we are processing the first frame.
   258    int done;
   259    int c;
   260    int quiet = 0;
   261    WebPMux* mux = NULL;
   262    WebPData webp_data = { NULL, 0 };
   263    int keep_metadata = METADATA_XMP;  // ICC not output by default.
   264    int stored_icc = 0;  // Whether we have already stored an ICC profile.
   265    int stored_xmp = 0;
   266  
   267    int default_kmin = 1;  // Whether to use default kmin value.
   268    int default_kmax = 1;
   269    size_t kmin = 0;
   270    size_t kmax = 0;
   271    int allow_mixed = 0;   // If true, each frame can be lossy or lossless.
   272  
   273    memset(&info, 0, sizeof(info));
   274    info.id = WEBP_CHUNK_ANMF;
   275    info.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
   276    info.blend_method = WEBP_MUX_BLEND;
   277  
   278    if (!WebPConfigInit(&config) || !WebPPictureInit(&frame)) {
   279      fprintf(stderr, "Error! Version mismatch!\n");
   280      return -1;
   281    }
   282    config.lossless = 1;  // Use lossless compression by default.
   283    config.image_hint = WEBP_HINT_GRAPH;   // always low-color
   284  
   285    if (argc == 1) {
   286      Help();
   287      return 0;
   288    }
   289  
   290    for (c = 1; c < argc; ++c) {
   291      if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
   292        Help();
   293        return 0;
   294      } else if (!strcmp(argv[c], "-o") && c < argc - 1) {
   295        out_file = argv[++c];
   296      } else if (!strcmp(argv[c], "-lossy")) {
   297        config.lossless = 0;
   298      } else if (!strcmp(argv[c], "-mixed")) {
   299        allow_mixed = 1;
   300        config.lossless = 0;
   301      } else if (!strcmp(argv[c], "-q") && c < argc - 1) {
   302        config.quality = (float)strtod(argv[++c], NULL);
   303      } else if (!strcmp(argv[c], "-m") && c < argc - 1) {
   304        config.method = strtol(argv[++c], NULL, 0);
   305      } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
   306        kmax = strtoul(argv[++c], NULL, 0);
   307        default_kmax = 0;
   308      } else if (!strcmp(argv[c], "-kmin") && c < argc - 1) {
   309        kmin = strtoul(argv[++c], NULL, 0);
   310        default_kmin = 0;
   311      } else if (!strcmp(argv[c], "-f") && c < argc - 1) {
   312        config.filter_strength = strtol(argv[++c], NULL, 0);
   313      } else if (!strcmp(argv[c], "-metadata") && c < argc - 1) {
   314        static const struct {
   315          const char* option;
   316          int flag;
   317        } kTokens[] = {
   318          { "all",  METADATA_ALL },
   319          { "none", 0 },
   320          { "icc",  METADATA_ICC },
   321          { "xmp",  METADATA_XMP },
   322        };
   323        const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens);
   324        const char* start = argv[++c];
   325        const char* const end = start + strlen(start);
   326  
   327        keep_metadata = 0;
   328        while (start < end) {
   329          size_t i;
   330          const char* token = strchr(start, ',');
   331          if (token == NULL) token = end;
   332  
   333          for (i = 0; i < kNumTokens; ++i) {
   334            if ((size_t)(token - start) == strlen(kTokens[i].option) &&
   335                !strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) {
   336              if (kTokens[i].flag != 0) {
   337                keep_metadata |= kTokens[i].flag;
   338              } else {
   339                keep_metadata = 0;
   340              }
   341              break;
   342            }
   343          }
   344          if (i == kNumTokens) {
   345            fprintf(stderr, "Error! Unknown metadata type '%.*s'\n",
   346                    (int)(token - start), start);
   347            Help();
   348            return -1;
   349          }
   350          start = token + 1;
   351        }
   352      } else if (!strcmp(argv[c], "-mt")) {
   353        ++config.thread_level;
   354      } else if (!strcmp(argv[c], "-version")) {
   355        const int enc_version = WebPGetEncoderVersion();
   356        const int mux_version = WebPGetMuxVersion();
   357        printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
   358               (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
   359               enc_version & 0xff, (mux_version >> 16) & 0xff,
   360               (mux_version >> 8) & 0xff, mux_version & 0xff);
   361        return 0;
   362      } else if (!strcmp(argv[c], "-quiet")) {
   363        quiet = 1;
   364      } else if (!strcmp(argv[c], "-v")) {
   365        verbose = 1;
   366      } else if (!strcmp(argv[c], "--")) {
   367        if (c < argc - 1) in_file = argv[++c];
   368        break;
   369      } else if (argv[c][0] == '-') {
   370        fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
   371        Help();
   372        return -1;
   373      } else {
   374        in_file = argv[c];
   375      }
   376    }
   377  
   378    // Appropriate default kmin, kmax values for lossy and lossless.
   379    if (default_kmin) {
   380      kmin = config.lossless ? 9 : 3;
   381    }
   382    if (default_kmax) {
   383      kmax = config.lossless ? 17 : 5;
   384    }
   385    SanitizeKeyFrameIntervals(&kmin, &kmax);
   386  
   387    if (!WebPValidateConfig(&config)) {
   388      fprintf(stderr, "Error! Invalid configuration.\n");
   389      goto End;
   390    }
   391  
   392    if (in_file == NULL) {
   393      fprintf(stderr, "No input file specified!\n");
   394      Help();
   395      goto End;
   396    }
   397  
   398    // Start the decoder object
   399  #if defined(GIFLIB_MAJOR) && (GIFLIB_MAJOR >= 5)
   400    // There was an API change in version 5.0.0.
   401    gif = DGifOpenFileName(in_file, &gif_error);
   402  #else
   403    gif = DGifOpenFileName(in_file);
   404  #endif
   405    if (gif == NULL) goto End;
   406  
   407    // Allocate current buffer
   408    frame.width = gif->SWidth;
   409    frame.height = gif->SHeight;
   410    frame.use_argb = 1;
   411    if (!WebPPictureAlloc(&frame)) goto End;
   412  
   413    // Initialize cache
   414    cache = WebPFrameCacheNew(frame.width, frame.height, kmin, kmax, allow_mixed);
   415    if (cache == NULL) goto End;
   416  
   417    mux = WebPMuxNew();
   418    if (mux == NULL) {
   419      fprintf(stderr, "ERROR: could not create a mux object.\n");
   420      goto End;
   421    }
   422  
   423    // Loop over GIF images
   424    done = 0;
   425    do {
   426      GifRecordType type;
   427      if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
   428  
   429      switch (type) {
   430        case IMAGE_DESC_RECORD_TYPE: {
   431          WebPFrameRect gif_rect;
   432  
   433          if (!DGifGetImageDesc(gif)) goto End;
   434          if (!ReadFrame(gif, &gif_rect, &frame)) {
   435            goto End;
   436          }
   437  
   438          if (!WebPFrameCacheAddFrame(cache, &config, &gif_rect, &frame, &info)) {
   439            fprintf(stderr, "Error! Cannot encode frame as WebP\n");
   440            fprintf(stderr, "Error code: %d\n", frame.error_code);
   441          }
   442  
   443          err = WebPFrameCacheFlush(cache, verbose, mux);
   444          if (err != WEBP_MUX_OK) {
   445            fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
   446                    ErrorString(err));
   447            goto End;
   448          }
   449          is_first_frame = 0;
   450          break;
   451        }
   452        case EXTENSION_RECORD_TYPE: {
   453          int extension;
   454          GifByteType *data = NULL;
   455          if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
   456            goto End;
   457          }
   458          switch (extension) {
   459            case COMMENT_EXT_FUNC_CODE: {
   460              break;  // Do nothing for now.
   461            }
   462            case GRAPHICS_EXT_FUNC_CODE: {
   463              const int flags = data[1];
   464              const int dispose = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK;
   465              const int delay = data[2] | (data[3] << 8);  // In 10 ms units.
   466              if (data[0] != 4) goto End;
   467              info.duration = delay * 10;  // Duration is in 1 ms units for WebP.
   468              if (dispose == 3) {
   469                static int warning_printed = 0;
   470                if (!warning_printed) {
   471                  fprintf(stderr, "WARNING: GIF_DISPOSE_RESTORE unsupported.\n");
   472                  warning_printed = 1;
   473                }
   474                // failsafe. TODO(urvang): emulate the correct behaviour by
   475                // recoding the whole frame.
   476                info.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
   477              } else {
   478                info.dispose_method =
   479                    (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
   480                                   : WEBP_MUX_DISPOSE_NONE;
   481              }
   482              transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
   483              if (is_first_frame) {
   484                if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
   485                                        &anim.bgcolor)) {
   486                  fprintf(stderr, "GIF decode warning: invalid background color "
   487                                  "index. Assuming white background.\n");
   488                }
   489                WebPUtilClearPic(&frame, NULL);
   490              }
   491              break;
   492            }
   493            case PLAINTEXT_EXT_FUNC_CODE: {
   494              break;
   495            }
   496            case APPLICATION_EXT_FUNC_CODE: {
   497              if (data[0] != 11) break;    // Chunk is too short
   498              if (!memcmp(data + 1, "NETSCAPE2.0", 11)) {
   499                // Recognize and parse Netscape2.0 NAB extension for loop count.
   500                if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
   501                if (data == NULL) goto End;  // Loop count sub-block missing.
   502                if (data[0] != 3 && data[1] != 1) break;   // wrong size/marker
   503                anim.loop_count = data[2] | (data[3] << 8);
   504                if (verbose) printf("Loop count: %d\n", anim.loop_count);
   505              } else {  // An extension containing metadata.
   506                // We only store the first encountered chunk of each type, and
   507                // only if requested by the user.
   508                const int is_xmp = (keep_metadata & METADATA_XMP) &&
   509                                   !stored_xmp &&
   510                                   !memcmp(data + 1, "XMP DataXMP", 11);
   511                const int is_icc = (keep_metadata & METADATA_ICC) &&
   512                                   !stored_icc &&
   513                                   !memcmp(data + 1, "ICCRGBG1012", 11);
   514                if (is_xmp || is_icc) {
   515                  const char* const fourccs[2] = { "XMP " , "ICCP" };
   516                  const char* const features[2] = { "XMP" , "ICC" };
   517                  WebPData metadata = { NULL, 0 };
   518                  // Construct metadata from sub-blocks.
   519                  // Usual case (including ICC profile): In each sub-block, the
   520                  // first byte specifies its size in bytes (0 to 255) and the
   521                  // rest of the bytes contain the data.
   522                  // Special case for XMP data: In each sub-block, the first byte
   523                  // is also part of the XMP payload. XMP in GIF also has a 257
   524                  // byte padding data. See the XMP specification for details.
   525                  while (1) {
   526                    WebPData prev_metadata = metadata;
   527                    WebPData subblock;
   528                    if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) {
   529                      WebPDataClear(&metadata);
   530                      goto End;
   531                    }
   532                    if (data == NULL) break;  // Finished.
   533                    subblock.size = is_xmp ? data[0] + 1 : data[0];
   534                    assert(subblock.size > 0);
   535                    subblock.bytes = is_xmp ? data : data + 1;
   536                    metadata.bytes =
   537                        (uint8_t*)realloc((void*)metadata.bytes,
   538                                          prev_metadata.size + subblock.size);
   539                    if (metadata.bytes == NULL) {
   540                      WebPDataClear(&prev_metadata);
   541                      goto End;
   542                    }
   543                    metadata.size += subblock.size;
   544                    memcpy((void*)(metadata.bytes + prev_metadata.size),
   545                           subblock.bytes, subblock.size);
   546                  }
   547                  if (is_xmp) {
   548                    // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00.
   549                    const size_t xmp_pading_size = 257;
   550                    if (metadata.size > xmp_pading_size) {
   551                      metadata.size -= xmp_pading_size;
   552                    }
   553                  }
   554  
   555                  // Add metadata chunk.
   556                  err = WebPMuxSetChunk(mux, fourccs[is_icc], &metadata, 1);
   557                  if (verbose) {
   558                    printf("%s size: %d\n", features[is_icc], (int)metadata.size);
   559                  }
   560                  WebPDataClear(&metadata);
   561                  if (err != WEBP_MUX_OK) {
   562                    fprintf(stderr, "ERROR (%s): Could not set %s chunk.\n",
   563                            ErrorString(err), features[is_icc]);
   564                    goto End;
   565                  }
   566                  if (is_icc) {
   567                    stored_icc = 1;
   568                  } else if (is_xmp) {
   569                    stored_xmp = 1;
   570                  }
   571                }
   572              }
   573              break;
   574            }
   575            default: {
   576              break;  // skip
   577            }
   578          }
   579          while (data != NULL) {
   580            if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
   581          }
   582          break;
   583        }
   584        case TERMINATE_RECORD_TYPE: {
   585          done = 1;
   586          break;
   587        }
   588        default: {
   589          if (verbose) {
   590            fprintf(stderr, "Skipping over unknown record type %d\n", type);
   591          }
   592          break;
   593        }
   594      }
   595    } while (!done);
   596  
   597    // Flush any pending frames.
   598    err = WebPFrameCacheFlushAll(cache, verbose, mux);
   599    if (err != WEBP_MUX_OK) {
   600      fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
   601              ErrorString(err));
   602      goto End;
   603    }
   604  
   605    // Finish muxing
   606    err = WebPMuxSetAnimationParams(mux, &anim);
   607    if (err != WEBP_MUX_OK) {
   608      fprintf(stderr, "ERROR (%s): Could not set animation parameters.\n",
   609              ErrorString(err));
   610      goto End;
   611    }
   612  
   613    err = WebPMuxAssemble(mux, &webp_data);
   614    if (err != WEBP_MUX_OK) {
   615      fprintf(stderr, "ERROR (%s) assembling the WebP file.\n", ErrorString(err));
   616      goto End;
   617    }
   618    if (out_file != NULL) {
   619      if (!ExUtilWriteFile(out_file, webp_data.bytes, webp_data.size)) {
   620        fprintf(stderr, "Error writing output file: %s\n", out_file);
   621        goto End;
   622      }
   623      if (!quiet) {
   624        printf("Saved output file: %s\n", out_file);
   625      }
   626    } else {
   627      if (!quiet) {
   628        printf("Nothing written; use -o flag to save the result.\n");
   629      }
   630    }
   631  
   632    // All OK.
   633    ok = 1;
   634    gif_error = GIF_OK;
   635  
   636   End:
   637    WebPDataClear(&webp_data);
   638    WebPMuxDelete(mux);
   639    WebPPictureFree(&frame);
   640    WebPFrameCacheDelete(cache);
   641    if (out != NULL && out_file != NULL) fclose(out);
   642  
   643    if (gif_error != GIF_OK) {
   644      DisplayGifError(gif, gif_error);
   645    }
   646    if (gif != NULL) {
   647      DGifCloseFile(gif);
   648    }
   649  
   650    return !ok;
   651  }
   652  
   653  #else  // !WEBP_HAVE_GIF
   654  
   655  int main(int argc, const char *argv[]) {
   656    fprintf(stderr, "GIF support not enabled in %s.\n", argv[0]);
   657    (void)argc;
   658    return 0;
   659  }
   660  
   661  #endif
   662  
   663  //------------------------------------------------------------------------------