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