| // Copyright 2012 Google Inc. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the COPYING file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| // ----------------------------------------------------------------------------- |
| // |
| // simple tool to convert animated GIFs to WebP |
| // |
| // Authors: Skal (pascal.massimino@gmail.com) |
| // Urvang (urvang@google.com) |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "webp/config.h" |
| #endif |
| |
| #ifdef WEBP_HAVE_GIF |
| |
| #include <gif_lib.h> |
| #include "webp/encode.h" |
| #include "webp/mux.h" |
| #include "./example_util.h" |
| #include "./gif2webp_util.h" |
| |
| // GIFLIB_MAJOR is only defined in libgif >= 4.2.0. |
| #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) |
| # define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR) |
| # define LOCAL_GIF_PREREQ(maj, min) \ |
| (LOCAL_GIF_VERSION >= (((maj) << 8) | (min))) |
| #else |
| # define LOCAL_GIF_VERSION 0 |
| # define LOCAL_GIF_PREREQ(maj, min) 0 |
| #endif |
| |
| #define GIF_TRANSPARENT_MASK 0x01 |
| #define GIF_DISPOSE_MASK 0x07 |
| #define GIF_DISPOSE_SHIFT 2 |
| #define WHITE_COLOR 0xffffffff |
| #define MAX_CACHE_SIZE 30 |
| |
| //------------------------------------------------------------------------------ |
| |
| static int transparent_index = -1; // Opaque frame by default. |
| |
| static void SanitizeKeyFrameIntervals(size_t* const kmin_ptr, |
| size_t* const kmax_ptr) { |
| size_t kmin = *kmin_ptr; |
| size_t kmax = *kmax_ptr; |
| int print_warning = 1; |
| |
| if (kmin == 0) { // Disable keyframe insertion. |
| kmax = ~0; |
| kmin = kmax - 1; |
| print_warning = 0; |
| } |
| if (kmax == 0) { |
| kmax = ~0; |
| print_warning = 0; |
| } |
| |
| if (kmin >= kmax) { |
| kmin = kmax - 1; |
| if (print_warning) { |
| fprintf(stderr, |
| "WARNING: Setting kmin = %d, so that kmin < kmax.\n", (int)kmin); |
| } |
| } else if (kmin < (kmax / 2 + 1)) { |
| // This ensures that cache.keyframe + kmin >= kmax is always true. So, we |
| // can flush all the frames in the ‘count_since_key_frame == kmax’ case. |
| kmin = (kmax / 2 + 1); |
| if (print_warning) { |
| fprintf(stderr, |
| "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n", |
| (int)kmin); |
| } |
| } |
| // Limit the max number of frames that are allocated. |
| if (kmax - kmin > MAX_CACHE_SIZE) { |
| kmin = kmax - MAX_CACHE_SIZE; |
| if (print_warning) { |
| fprintf(stderr, |
| "WARNING: Setting kmin = %d, so that kmax - kmin <= 30.\n", |
| (int)kmin); |
| } |
| } |
| *kmin_ptr = kmin; |
| *kmax_ptr = kmax; |
| } |
| |
| static void Remap(const uint8_t* const src, const GifFileType* const gif, |
| uint32_t* dst, int len) { |
| int i; |
| const GifColorType* colors; |
| const ColorMapObject* const cmap = |
| gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; |
| if (cmap == NULL) return; |
| colors = cmap->Colors; |
| |
| for (i = 0; i < len; ++i) { |
| const GifColorType c = colors[src[i]]; |
| dst[i] = (src[i] == transparent_index) ? WEBP_UTIL_TRANSPARENT_COLOR |
| : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24); |
| } |
| } |
| |
| // Read the GIF image frame. |
| static int ReadFrame(GifFileType* const gif, WebPFrameRect* const gif_rect, |
| WebPPicture* const webp_frame) { |
| WebPPicture sub_image; |
| const GifImageDesc* const image_desc = &gif->Image; |
| uint32_t* dst = NULL; |
| uint8_t* tmp = NULL; |
| int ok = 0; |
| WebPFrameRect rect = { |
| image_desc->Left, image_desc->Top, image_desc->Width, image_desc->Height |
| }; |
| *gif_rect = rect; |
| |
| // Use a view for the sub-picture: |
| if (!WebPPictureView(webp_frame, rect.x_offset, rect.y_offset, |
| rect.width, rect.height, &sub_image)) { |
| fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", |
| rect.width, rect.height, rect.x_offset, rect.y_offset); |
| return 0; |
| } |
| dst = sub_image.argb; |
| |
| tmp = (uint8_t*)malloc(rect.width * sizeof(*tmp)); |
| if (tmp == NULL) goto End; |
| |
| if (image_desc->Interlace) { // Interlaced image. |
| // We need 4 passes, with the following offsets and jumps. |
| const int interlace_offsets[] = { 0, 4, 2, 1 }; |
| const int interlace_jumps[] = { 8, 8, 4, 2 }; |
| int pass; |
| for (pass = 0; pass < 4; ++pass) { |
| int y; |
| for (y = interlace_offsets[pass]; y < rect.height; |
| y += interlace_jumps[pass]) { |
| if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; |
| Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width); |
| } |
| } |
| } else { // Non-interlaced image. |
| int y; |
| for (y = 0; y < rect.height; ++y) { |
| if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; |
| Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width); |
| } |
| } |
| ok = 1; |
| |
| End: |
| if (!ok) webp_frame->error_code = sub_image.error_code; |
| WebPPictureFree(&sub_image); |
| free(tmp); |
| return ok; |
| } |
| |
| static void GetBackgroundColor(const ColorMapObject* const color_map, |
| int bgcolor_idx, uint32_t* const bgcolor) { |
| if (transparent_index != -1 && bgcolor_idx == transparent_index) { |
| *bgcolor = WEBP_UTIL_TRANSPARENT_COLOR; // Special case. |
| } else if (color_map == NULL || color_map->Colors == NULL |
| || bgcolor_idx >= color_map->ColorCount) { |
| *bgcolor = WHITE_COLOR; |
| fprintf(stderr, |
| "GIF decode warning: invalid background color index. Assuming " |
| "white background.\n"); |
| } else { |
| const GifColorType color = color_map->Colors[bgcolor_idx]; |
| *bgcolor = (0xff << 24) |
| | (color.Red << 16) |
| | (color.Green << 8) |
| | (color.Blue << 0); |
| } |
| } |
| |
| static void DisplayGifError(const GifFileType* const gif, int gif_error) { |
| // libgif 4.2.0 has retired PrintGifError() and added GifErrorString(). |
| #if LOCAL_GIF_PREREQ(4,2) |
| #if LOCAL_GIF_PREREQ(5,0) |
| // Static string actually, hence the const char* cast. |
| const char* error_str = (const char*)GifErrorString( |
| (gif == NULL) ? gif_error : gif->Error); |
| #else |
| const char* error_str = (const char*)GifErrorString(); |
| (void)gif; |
| #endif |
| if (error_str == NULL) error_str = "Unknown error"; |
| fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str); |
| #else |
| (void)gif; |
| fprintf(stderr, "GIFLib Error %d: ", gif_error); |
| PrintGifError(); |
| fprintf(stderr, "\n"); |
| #endif |
| } |
| |
| static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { |
| "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", |
| "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA" |
| }; |
| |
| static const char* ErrorString(WebPMuxError err) { |
| assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA); |
| return kErrorMessages[-err]; |
| } |
| |
| enum { |
| METADATA_ICC = (1 << 0), |
| METADATA_XMP = (1 << 1), |
| METADATA_ALL = METADATA_ICC | METADATA_XMP |
| }; |
| |
| //------------------------------------------------------------------------------ |
| |
| static void Help(void) { |
| printf("Usage:\n"); |
| printf(" gif2webp [options] gif_file -o webp_file\n"); |
| printf("Options:\n"); |
| printf(" -h / -help ............ this help\n"); |
| printf(" -lossy ................. encode image using lossy compression\n"); |
| printf(" -mixed ................. for each frame in the image, pick lossy\n" |
| " or lossless compression heuristically\n"); |
| printf(" -q <float> ............. quality factor (0:small..100:big)\n"); |
| printf(" -m <int> ............... compression method (0=fast, 6=slowest)\n"); |
| printf(" -kmin <int> ............ min distance between key frames\n"); |
| printf(" -kmax <int> ............ max distance between key frames\n"); |
| printf(" -f <int> ............... filter strength (0=off..100)\n"); |
| printf(" -metadata <string> ..... comma separated list of metadata to\n"); |
| printf(" "); |
| printf("copy from the input to the output if present\n"); |
| printf(" " |
| "Valid values: all, none, icc, xmp (default)\n"); |
| printf(" -mt .................... use multi-threading if available\n"); |
| printf("\n"); |
| printf(" -version ............... print version number and exit\n"); |
| printf(" -v ..................... verbose\n"); |
| printf(" -quiet ................. don't print anything\n"); |
| printf("\n"); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| int main(int argc, const char *argv[]) { |
| int verbose = 0; |
| int gif_error = GIF_ERROR; |
| WebPMuxError err = WEBP_MUX_OK; |
| int ok = 0; |
| const char *in_file = NULL, *out_file = NULL; |
| FILE* out = NULL; |
| GifFileType* gif = NULL; |
| WebPConfig config; |
| WebPPicture frame; |
| int duration = 0; |
| FrameDisposeMethod orig_dispose = FRAME_DISPOSE_NONE; |
| WebPMuxAnimParams anim = { WHITE_COLOR, 0 }; |
| WebPFrameCache* cache = NULL; |
| |
| int is_first_frame = 1; // Whether we are processing the first frame. |
| int done; |
| int c; |
| int quiet = 0; |
| WebPMux* mux = NULL; |
| WebPData webp_data = { NULL, 0 }; |
| int keep_metadata = METADATA_XMP; // ICC not output by default. |
| int stored_icc = 0; // Whether we have already stored an ICC profile. |
| int stored_xmp = 0; |
| |
| int default_kmin = 1; // Whether to use default kmin value. |
| int default_kmax = 1; |
| size_t kmin = 0; |
| size_t kmax = 0; |
| int allow_mixed = 0; // If true, each frame can be lossy or lossless. |
| |
| if (!WebPConfigInit(&config) || !WebPPictureInit(&frame)) { |
| fprintf(stderr, "Error! Version mismatch!\n"); |
| return -1; |
| } |
| config.lossless = 1; // Use lossless compression by default. |
| |
| if (argc == 1) { |
| Help(); |
| return 0; |
| } |
| |
| for (c = 1; c < argc; ++c) { |
| int parse_error = 0; |
| if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { |
| Help(); |
| return 0; |
| } else if (!strcmp(argv[c], "-o") && c < argc - 1) { |
| out_file = argv[++c]; |
| } else if (!strcmp(argv[c], "-lossy")) { |
| config.lossless = 0; |
| } else if (!strcmp(argv[c], "-mixed")) { |
| allow_mixed = 1; |
| config.lossless = 0; |
| } else if (!strcmp(argv[c], "-q") && c < argc - 1) { |
| config.quality = ExUtilGetFloat(argv[++c], &parse_error); |
| } else if (!strcmp(argv[c], "-m") && c < argc - 1) { |
| config.method = ExUtilGetInt(argv[++c], 0, &parse_error); |
| } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) { |
| kmax = ExUtilGetUInt(argv[++c], 0, &parse_error); |
| default_kmax = 0; |
| } else if (!strcmp(argv[c], "-kmin") && c < argc - 1) { |
| kmin = ExUtilGetUInt(argv[++c], 0, &parse_error); |
| default_kmin = 0; |
| } else if (!strcmp(argv[c], "-f") && c < argc - 1) { |
| config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error); |
| } else if (!strcmp(argv[c], "-metadata") && c < argc - 1) { |
| static const struct { |
| const char* option; |
| int flag; |
| } kTokens[] = { |
| { "all", METADATA_ALL }, |
| { "none", 0 }, |
| { "icc", METADATA_ICC }, |
| { "xmp", METADATA_XMP }, |
| }; |
| const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens); |
| const char* start = argv[++c]; |
| const char* const end = start + strlen(start); |
| |
| keep_metadata = 0; |
| while (start < end) { |
| size_t i; |
| const char* token = strchr(start, ','); |
| if (token == NULL) token = end; |
| |
| for (i = 0; i < kNumTokens; ++i) { |
| if ((size_t)(token - start) == strlen(kTokens[i].option) && |
| !strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) { |
| if (kTokens[i].flag != 0) { |
| keep_metadata |= kTokens[i].flag; |
| } else { |
| keep_metadata = 0; |
| } |
| break; |
| } |
| } |
| if (i == kNumTokens) { |
| fprintf(stderr, "Error! Unknown metadata type '%.*s'\n", |
| (int)(token - start), start); |
| Help(); |
| return -1; |
| } |
| start = token + 1; |
| } |
| } else if (!strcmp(argv[c], "-mt")) { |
| ++config.thread_level; |
| } else if (!strcmp(argv[c], "-version")) { |
| const int enc_version = WebPGetEncoderVersion(); |
| const int mux_version = WebPGetMuxVersion(); |
| printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n", |
| (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff, |
| enc_version & 0xff, (mux_version >> 16) & 0xff, |
| (mux_version >> 8) & 0xff, mux_version & 0xff); |
| return 0; |
| } else if (!strcmp(argv[c], "-quiet")) { |
| quiet = 1; |
| } else if (!strcmp(argv[c], "-v")) { |
| verbose = 1; |
| } else if (!strcmp(argv[c], "--")) { |
| if (c < argc - 1) in_file = argv[++c]; |
| break; |
| } else if (argv[c][0] == '-') { |
| fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]); |
| Help(); |
| return -1; |
| } else { |
| in_file = argv[c]; |
| } |
| |
| if (parse_error) { |
| Help(); |
| return -1; |
| } |
| } |
| |
| // Appropriate default kmin, kmax values for lossy and lossless. |
| if (default_kmin) { |
| kmin = config.lossless ? 9 : 3; |
| } |
| if (default_kmax) { |
| kmax = config.lossless ? 17 : 5; |
| } |
| SanitizeKeyFrameIntervals(&kmin, &kmax); |
| |
| if (!WebPValidateConfig(&config)) { |
| fprintf(stderr, "Error! Invalid configuration.\n"); |
| goto End; |
| } |
| |
| if (in_file == NULL) { |
| fprintf(stderr, "No input file specified!\n"); |
| Help(); |
| goto End; |
| } |
| |
| // Start the decoder object |
| #if LOCAL_GIF_PREREQ(5,0) |
| gif = DGifOpenFileName(in_file, &gif_error); |
| #else |
| gif = DGifOpenFileName(in_file); |
| #endif |
| if (gif == NULL) goto End; |
| |
| mux = WebPMuxNew(); |
| if (mux == NULL) { |
| fprintf(stderr, "ERROR: could not create a mux object.\n"); |
| goto End; |
| } |
| |
| // Loop over GIF images |
| done = 0; |
| do { |
| GifRecordType type; |
| if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End; |
| |
| switch (type) { |
| case IMAGE_DESC_RECORD_TYPE: { |
| WebPFrameRect gif_rect; |
| GifImageDesc* const image_desc = &gif->Image; |
| |
| if (!DGifGetImageDesc(gif)) goto End; |
| |
| // Fix some broken GIF global headers that report |
| // 0 x 0 screen dimension. |
| if (is_first_frame) { |
| if (verbose) { |
| printf("Canvas screen: %d x %d\n", gif->SWidth, gif->SHeight); |
| } |
| if (gif->SWidth == 0 || gif->SHeight == 0) { |
| image_desc->Left = 0; |
| image_desc->Top = 0; |
| gif->SWidth = image_desc->Width; |
| gif->SHeight = image_desc->Height; |
| if (gif->SWidth <= 0 || gif->SHeight <= 0) { |
| goto End; |
| } |
| if (verbose) { |
| printf("Fixed canvas screen dimension to: %d x %d\n", |
| gif->SWidth, gif->SHeight); |
| } |
| } |
| #if WEBP_MUX_ABI_VERSION > 0x0101 |
| // Set definitive canvas size. |
| err = WebPMuxSetCanvasSize(mux, gif->SWidth, gif->SHeight); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "Invalid canvas size %d x %d\n", |
| gif->SWidth, gif->SHeight); |
| goto End; |
| } |
| #endif |
| // Allocate current buffer. |
| frame.width = gif->SWidth; |
| frame.height = gif->SHeight; |
| frame.use_argb = 1; |
| if (!WebPPictureAlloc(&frame)) goto End; |
| WebPUtilClearPic(&frame, NULL); |
| |
| // Initialize cache. |
| cache = WebPFrameCacheNew(frame.width, frame.height, |
| kmin, kmax, allow_mixed); |
| if (cache == NULL) goto End; |
| |
| // Background color. |
| GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor, |
| &anim.bgcolor); |
| } |
| // Some even more broken GIF can have sub-rect with zero width/height. |
| if (image_desc->Width == 0 || image_desc->Height == 0) { |
| image_desc->Width = gif->SWidth; |
| image_desc->Height = gif->SHeight; |
| } |
| |
| if (!ReadFrame(gif, &gif_rect, &frame)) { |
| goto End; |
| } |
| |
| if (!WebPFrameCacheAddFrame(cache, &config, &gif_rect, orig_dispose, |
| duration, &frame)) { |
| fprintf(stderr, "Error! Cannot encode frame as WebP\n"); |
| fprintf(stderr, "Error code: %d\n", frame.error_code); |
| } |
| |
| err = WebPFrameCacheFlush(cache, verbose, mux); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "ERROR (%s): Could not add animation frame.\n", |
| ErrorString(err)); |
| goto End; |
| } |
| is_first_frame = 0; |
| |
| // In GIF, graphic control extensions are optional for a frame, so we |
| // may not get one before reading the next frame. To handle this case, |
| // we reset frame properties to reasonable defaults for the next frame. |
| orig_dispose = FRAME_DISPOSE_NONE; |
| duration = 0; |
| transparent_index = -1; // Opaque frame by default. |
| break; |
| } |
| case EXTENSION_RECORD_TYPE: { |
| int extension; |
| GifByteType *data = NULL; |
| if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) { |
| goto End; |
| } |
| switch (extension) { |
| case COMMENT_EXT_FUNC_CODE: { |
| break; // Do nothing for now. |
| } |
| case GRAPHICS_EXT_FUNC_CODE: { |
| const int flags = data[1]; |
| const int dispose = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK; |
| const int delay = data[2] | (data[3] << 8); // In 10 ms units. |
| if (data[0] != 4) goto End; |
| duration = delay * 10; // Duration is in 1 ms units for WebP. |
| switch (dispose) { |
| case 3: |
| orig_dispose = FRAME_DISPOSE_RESTORE_PREVIOUS; |
| break; |
| case 2: |
| orig_dispose = FRAME_DISPOSE_BACKGROUND; |
| break; |
| case 1: |
| case 0: |
| default: |
| orig_dispose = FRAME_DISPOSE_NONE; |
| break; |
| } |
| transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; |
| break; |
| } |
| case PLAINTEXT_EXT_FUNC_CODE: { |
| break; |
| } |
| case APPLICATION_EXT_FUNC_CODE: { |
| if (data[0] != 11) break; // Chunk is too short |
| if (!memcmp(data + 1, "NETSCAPE2.0", 11) || |
| !memcmp(data + 1, "ANIMEXTS1.0", 11)) { |
| // Recognize and parse Netscape2.0 NAB extension for loop count. |
| if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; |
| if (data == NULL) goto End; // Loop count sub-block missing. |
| if (data[0] < 3 || data[1] != 1) break; // wrong size/marker |
| anim.loop_count = data[2] | (data[3] << 8); |
| if (verbose) { |
| fprintf(stderr, "Loop count: %d\n", anim.loop_count); |
| } |
| } else { // An extension containing metadata. |
| // We only store the first encountered chunk of each type, and |
| // only if requested by the user. |
| const int is_xmp = (keep_metadata & METADATA_XMP) && |
| !stored_xmp && |
| !memcmp(data + 1, "XMP DataXMP", 11); |
| const int is_icc = (keep_metadata & METADATA_ICC) && |
| !stored_icc && |
| !memcmp(data + 1, "ICCRGBG1012", 11); |
| if (is_xmp || is_icc) { |
| const char* const fourccs[2] = { "XMP " , "ICCP" }; |
| const char* const features[2] = { "XMP" , "ICC" }; |
| WebPData metadata = { NULL, 0 }; |
| // Construct metadata from sub-blocks. |
| // Usual case (including ICC profile): In each sub-block, the |
| // first byte specifies its size in bytes (0 to 255) and the |
| // rest of the bytes contain the data. |
| // Special case for XMP data: In each sub-block, the first byte |
| // is also part of the XMP payload. XMP in GIF also has a 257 |
| // byte padding data. See the XMP specification for details. |
| while (1) { |
| WebPData prev_metadata = metadata; |
| WebPData subblock; |
| if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) { |
| WebPDataClear(&metadata); |
| goto End; |
| } |
| if (data == NULL) break; // Finished. |
| subblock.size = is_xmp ? data[0] + 1 : data[0]; |
| assert(subblock.size > 0); |
| subblock.bytes = is_xmp ? data : data + 1; |
| metadata.bytes = |
| (uint8_t*)realloc((void*)metadata.bytes, |
| prev_metadata.size + subblock.size); |
| if (metadata.bytes == NULL) { |
| WebPDataClear(&prev_metadata); |
| goto End; |
| } |
| metadata.size += subblock.size; |
| memcpy((void*)(metadata.bytes + prev_metadata.size), |
| subblock.bytes, subblock.size); |
| } |
| if (is_xmp) { |
| // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00. |
| const size_t xmp_pading_size = 257; |
| if (metadata.size > xmp_pading_size) { |
| metadata.size -= xmp_pading_size; |
| } |
| } |
| |
| // Add metadata chunk. |
| err = WebPMuxSetChunk(mux, fourccs[is_icc], &metadata, 1); |
| if (verbose) { |
| fprintf(stderr, "%s size: %d\n", |
| features[is_icc], (int)metadata.size); |
| } |
| WebPDataClear(&metadata); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "ERROR (%s): Could not set %s chunk.\n", |
| ErrorString(err), features[is_icc]); |
| goto End; |
| } |
| if (is_icc) { |
| stored_icc = 1; |
| } else if (is_xmp) { |
| stored_xmp = 1; |
| } |
| } |
| } |
| break; |
| } |
| default: { |
| break; // skip |
| } |
| } |
| while (data != NULL) { |
| if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; |
| } |
| break; |
| } |
| case TERMINATE_RECORD_TYPE: { |
| done = 1; |
| break; |
| } |
| default: { |
| if (verbose) { |
| fprintf(stderr, "Skipping over unknown record type %d\n", type); |
| } |
| break; |
| } |
| } |
| } while (!done); |
| |
| // Flush any pending frames. |
| err = WebPFrameCacheFlushAll(cache, verbose, mux); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "ERROR (%s): Could not add animation frame.\n", |
| ErrorString(err)); |
| goto End; |
| } |
| |
| // Finish muxing |
| err = WebPMuxSetAnimationParams(mux, &anim); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "ERROR (%s): Could not set animation parameters.\n", |
| ErrorString(err)); |
| goto End; |
| } |
| |
| err = WebPMuxAssemble(mux, &webp_data); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "ERROR (%s) assembling the WebP file.\n", ErrorString(err)); |
| goto End; |
| } |
| if (out_file != NULL) { |
| if (!ExUtilWriteFile(out_file, webp_data.bytes, webp_data.size)) { |
| fprintf(stderr, "Error writing output file: %s\n", out_file); |
| goto End; |
| } |
| if (!quiet) { |
| fprintf(stderr, "Saved output file: %s\n", out_file); |
| } |
| } else { |
| if (!quiet) { |
| fprintf(stderr, "Nothing written; use -o flag to save the result.\n"); |
| } |
| } |
| |
| // All OK. |
| ok = 1; |
| gif_error = GIF_OK; |
| |
| End: |
| WebPDataClear(&webp_data); |
| WebPMuxDelete(mux); |
| WebPPictureFree(&frame); |
| WebPFrameCacheDelete(cache); |
| if (out != NULL && out_file != NULL) fclose(out); |
| |
| if (gif_error != GIF_OK) { |
| DisplayGifError(gif, gif_error); |
| } |
| if (gif != NULL) { |
| #if LOCAL_GIF_PREREQ(5,1) |
| DGifCloseFile(gif, &gif_error); |
| #else |
| DGifCloseFile(gif); |
| #endif |
| } |
| |
| return !ok; |
| } |
| |
| #else // !WEBP_HAVE_GIF |
| |
| int main(int argc, const char *argv[]) { |
| fprintf(stderr, "GIF support not enabled in %s.\n", argv[0]); |
| (void)argc; |
| return 0; |
| } |
| |
| #endif |
| |
| //------------------------------------------------------------------------------ |