anim_util.c (26749B)
1 // Copyright 2015 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 // Utilities for animated images 11 12 #include "./anim_util.h" 13 14 #include <assert.h> 15 #include <math.h> 16 #include <stdio.h> 17 #include <string.h> 18 19 #if defined(WEBP_HAVE_GIF) 20 #include <gif_lib.h> 21 #endif 22 23 #include "../imageio/imageio_util.h" 24 #include "./gifdec.h" 25 #include "./unicode.h" 26 #include "./unicode_gif.h" 27 #include "webp/decode.h" 28 #include "webp/demux.h" 29 #include "webp/format_constants.h" 30 #include "webp/mux_types.h" 31 #include "webp/types.h" 32 33 #if defined(_MSC_VER) && _MSC_VER < 1900 34 #define snprintf _snprintf 35 #endif 36 37 static const int kNumChannels = 4; 38 39 // ----------------------------------------------------------------------------- 40 // Common utilities. 41 42 #if defined(WEBP_HAVE_GIF) 43 // Returns true if the frame covers the full canvas. 44 static int IsFullFrame(int width, int height, 45 int canvas_width, int canvas_height) { 46 return (width == canvas_width && height == canvas_height); 47 } 48 #endif // WEBP_HAVE_GIF 49 50 static int CheckSizeForOverflow(uint64_t size) { 51 return (size == (size_t)size); 52 } 53 54 static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) { 55 uint32_t i; 56 uint8_t* mem = NULL; 57 DecodedFrame* frames = NULL; 58 const uint64_t rgba_size = 59 (uint64_t)image->canvas_width * kNumChannels * image->canvas_height; 60 const uint64_t total_size = (uint64_t)num_frames * rgba_size * sizeof(*mem); 61 const uint64_t total_frame_size = (uint64_t)num_frames * sizeof(*frames); 62 if (!CheckSizeForOverflow(total_size) || 63 !CheckSizeForOverflow(total_frame_size)) { 64 return 0; 65 } 66 mem = (uint8_t*)WebPMalloc((size_t)total_size); 67 frames = (DecodedFrame*)WebPMalloc((size_t)total_frame_size); 68 69 if (mem == NULL || frames == NULL) { 70 WebPFree(mem); 71 WebPFree(frames); 72 return 0; 73 } 74 WebPFree(image->raw_mem); 75 image->num_frames = num_frames; 76 image->frames = frames; 77 for (i = 0; i < num_frames; ++i) { 78 frames[i].rgba = mem + i * rgba_size; 79 frames[i].duration = 0; 80 frames[i].is_key_frame = 0; 81 } 82 image->raw_mem = mem; 83 return 1; 84 } 85 86 void ClearAnimatedImage(AnimatedImage* const image) { 87 if (image != NULL) { 88 WebPFree(image->raw_mem); 89 WebPFree(image->frames); 90 image->num_frames = 0; 91 image->frames = NULL; 92 image->raw_mem = NULL; 93 } 94 } 95 96 #if defined(WEBP_HAVE_GIF) 97 // Clear the canvas to transparent. 98 static void ZeroFillCanvas(uint8_t* rgba, 99 uint32_t canvas_width, uint32_t canvas_height) { 100 memset(rgba, 0, canvas_width * kNumChannels * canvas_height); 101 } 102 103 // Clear given frame rectangle to transparent. 104 static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset, 105 int y_offset, int width, int height) { 106 int j; 107 assert(width * kNumChannels <= rgba_stride); 108 rgba += y_offset * rgba_stride + x_offset * kNumChannels; 109 for (j = 0; j < height; ++j) { 110 memset(rgba, 0, width * kNumChannels); 111 rgba += rgba_stride; 112 } 113 } 114 115 // Copy width * height pixels from 'src' to 'dst'. 116 static void CopyCanvas(const uint8_t* src, uint8_t* dst, 117 uint32_t width, uint32_t height) { 118 assert(src != NULL && dst != NULL); 119 memcpy(dst, src, width * kNumChannels * height); 120 } 121 122 // Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'. 123 static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride, 124 int x_offset, int y_offset, 125 int width, int height) { 126 int j; 127 const int width_in_bytes = width * kNumChannels; 128 const size_t offset = y_offset * stride + x_offset * kNumChannels; 129 assert(width_in_bytes <= stride); 130 src += offset; 131 dst += offset; 132 for (j = 0; j < height; ++j) { 133 memcpy(dst, src, width_in_bytes); 134 src += stride; 135 dst += stride; 136 } 137 } 138 #endif // WEBP_HAVE_GIF 139 140 // Canonicalize all transparent pixels to transparent black to aid comparison. 141 static void CleanupTransparentPixels(uint32_t* rgba, 142 uint32_t width, uint32_t height) { 143 const uint32_t* const rgba_end = rgba + width * height; 144 while (rgba < rgba_end) { 145 const uint8_t alpha = (*rgba >> 24) & 0xff; 146 if (alpha == 0) { 147 *rgba = 0; 148 } 149 ++rgba; 150 } 151 } 152 153 // Dump frame to a PAM file. Returns true on success. 154 static int DumpFrame(const char filename[], const char dump_folder[], 155 uint32_t frame_num, const uint8_t rgba[], 156 int canvas_width, int canvas_height) { 157 int ok = 0; 158 size_t max_len; 159 int y; 160 const W_CHAR* base_name = NULL; 161 W_CHAR* file_name = NULL; 162 FILE* f = NULL; 163 const char* row; 164 165 if (dump_folder == NULL) dump_folder = (const char*)TO_W_CHAR("."); 166 167 base_name = WSTRRCHR(filename, '/'); 168 base_name = (base_name == NULL) ? (const W_CHAR*)filename : base_name + 1; 169 max_len = WSTRLEN(dump_folder) + 1 + WSTRLEN(base_name) 170 + strlen("_frame_") + strlen(".pam") + 8; 171 file_name = (W_CHAR*)WebPMalloc(max_len * sizeof(*file_name)); 172 if (file_name == NULL) goto End; 173 174 if (WSNPRINTF(file_name, max_len, "%s/%s_frame_%d.pam", 175 (const W_CHAR*)dump_folder, base_name, frame_num) < 0) { 176 fprintf(stderr, "Error while generating file name\n"); 177 goto End; 178 } 179 180 f = WFOPEN(file_name, "wb"); 181 if (f == NULL) { 182 WFPRINTF(stderr, "Error opening file for writing: %s\n", file_name); 183 ok = 0; 184 goto End; 185 } 186 if (fprintf(f, "P7\nWIDTH %d\nHEIGHT %d\n" 187 "DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", 188 canvas_width, canvas_height) < 0) { 189 WFPRINTF(stderr, "Write error for file %s\n", file_name); 190 goto End; 191 } 192 row = (const char*)rgba; 193 for (y = 0; y < canvas_height; ++y) { 194 if (fwrite(row, canvas_width * kNumChannels, 1, f) != 1) { 195 WFPRINTF(stderr, "Error writing to file: %s\n", file_name); 196 goto End; 197 } 198 row += canvas_width * kNumChannels; 199 } 200 ok = 1; 201 End: 202 if (f != NULL) fclose(f); 203 WebPFree(file_name); 204 return ok; 205 } 206 207 // ----------------------------------------------------------------------------- 208 // WebP Decoding. 209 210 // Returns true if this is a valid WebP bitstream. 211 static int IsWebP(const WebPData* const webp_data) { 212 return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0); 213 } 214 215 // Read animated WebP bitstream 'webp_data' into 'AnimatedImage' struct. 216 static int ReadAnimatedWebP(const char filename[], 217 const WebPData* const webp_data, 218 AnimatedImage* const image, int dump_frames, 219 const char dump_folder[]) { 220 int ok = 0; 221 int dump_ok = 1; 222 uint32_t frame_index = 0; 223 int prev_frame_timestamp = 0; 224 WebPAnimDecoder* dec; 225 WebPAnimInfo anim_info; 226 227 memset(image, 0, sizeof(*image)); 228 229 dec = WebPAnimDecoderNew(webp_data, NULL); 230 if (dec == NULL) { 231 WFPRINTF(stderr, "Error parsing image: %s\n", (const W_CHAR*)filename); 232 goto End; 233 } 234 235 if (!WebPAnimDecoderGetInfo(dec, &anim_info)) { 236 fprintf(stderr, "Error getting global info about the animation\n"); 237 goto End; 238 } 239 240 // Animation properties. 241 image->canvas_width = anim_info.canvas_width; 242 image->canvas_height = anim_info.canvas_height; 243 image->loop_count = anim_info.loop_count; 244 image->bgcolor = anim_info.bgcolor; 245 246 // Allocate frames. 247 if (!AllocateFrames(image, anim_info.frame_count)) goto End; 248 249 // Decode frames. 250 while (WebPAnimDecoderHasMoreFrames(dec)) { 251 DecodedFrame* curr_frame; 252 uint8_t* curr_rgba; 253 uint8_t* frame_rgba; 254 int timestamp; 255 256 if (!WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp)) { 257 fprintf(stderr, "Error decoding frame #%u\n", frame_index); 258 goto End; 259 } 260 assert(frame_index < anim_info.frame_count); 261 curr_frame = &image->frames[frame_index]; 262 curr_rgba = curr_frame->rgba; 263 curr_frame->duration = timestamp - prev_frame_timestamp; 264 curr_frame->is_key_frame = 0; // Unused. 265 memcpy(curr_rgba, frame_rgba, 266 image->canvas_width * kNumChannels * image->canvas_height); 267 268 // Needed only because we may want to compare with GIF later. 269 CleanupTransparentPixels((uint32_t*)curr_rgba, 270 image->canvas_width, image->canvas_height); 271 272 if (dump_frames && dump_ok) { 273 dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba, 274 image->canvas_width, image->canvas_height); 275 if (!dump_ok) { // Print error once, but continue decode loop. 276 fprintf(stderr, "Error dumping frames to %s\n", dump_folder); 277 } 278 } 279 280 ++frame_index; 281 prev_frame_timestamp = timestamp; 282 } 283 ok = dump_ok; 284 if (ok) image->format = ANIM_WEBP; 285 286 End: 287 WebPAnimDecoderDelete(dec); 288 return ok; 289 } 290 291 // ----------------------------------------------------------------------------- 292 // GIF Decoding. 293 294 #if defined(WEBP_HAVE_GIF) 295 296 // Returns true if this is a valid GIF bitstream. 297 static int IsGIF(const WebPData* const data) { 298 return data->size > GIF_STAMP_LEN && 299 (!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) || 300 !memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) || 301 !memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN)); 302 } 303 304 // GIFLIB_MAJOR is only defined in libgif >= 4.2.0. 305 #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) 306 # define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR) 307 # define LOCAL_GIF_PREREQ(maj, min) \ 308 (LOCAL_GIF_VERSION >= (((maj) << 8) | (min))) 309 #else 310 # define LOCAL_GIF_VERSION 0 311 # define LOCAL_GIF_PREREQ(maj, min) 0 312 #endif 313 314 #if !LOCAL_GIF_PREREQ(5, 0) 315 316 // Added in v5.0 317 typedef struct { 318 int DisposalMode; 319 #define DISPOSAL_UNSPECIFIED 0 // No disposal specified 320 #define DISPOSE_DO_NOT 1 // Leave image in place 321 #define DISPOSE_BACKGROUND 2 // Set area to background color 322 #define DISPOSE_PREVIOUS 3 // Restore to previous content 323 int UserInputFlag; // User confirmation required before disposal 324 int DelayTime; // Pre-display delay in 0.01sec units 325 int TransparentColor; // Palette index for transparency, -1 if none 326 #define NO_TRANSPARENT_COLOR -1 327 } GraphicsControlBlock; 328 329 static int DGifExtensionToGCB(const size_t GifExtensionLength, 330 const GifByteType* GifExtension, 331 GraphicsControlBlock* gcb) { 332 if (GifExtensionLength != 4) { 333 return GIF_ERROR; 334 } 335 gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07; 336 gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0; 337 gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8); 338 if (GifExtension[0] & 0x01) { 339 gcb->TransparentColor = (int)GifExtension[3]; 340 } else { 341 gcb->TransparentColor = NO_TRANSPARENT_COLOR; 342 } 343 return GIF_OK; 344 } 345 346 static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex, 347 GraphicsControlBlock* gcb) { 348 int i; 349 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) { 350 return GIF_ERROR; 351 } 352 gcb->DisposalMode = DISPOSAL_UNSPECIFIED; 353 gcb->UserInputFlag = 0; 354 gcb->DelayTime = 0; 355 gcb->TransparentColor = NO_TRANSPARENT_COLOR; 356 357 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) { 358 ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; 359 if (ep->Function == GRAPHICS_EXT_FUNC_CODE) { 360 return DGifExtensionToGCB( 361 ep->ByteCount, (const GifByteType*)ep->Bytes, gcb); 362 } 363 } 364 return GIF_ERROR; 365 } 366 367 #define CONTINUE_EXT_FUNC_CODE 0x00 368 369 // Signature was changed in v5.0 370 #define DGifOpenFileName(a, b) DGifOpenFileName(a) 371 372 #endif // !LOCAL_GIF_PREREQ(5, 0) 373 374 // Signature changed in v5.1 375 #if !LOCAL_GIF_PREREQ(5, 1) 376 #define DGifCloseFile(a, b) DGifCloseFile(a) 377 #endif 378 379 static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose, 380 const DecodedFrame* const prev_frame, 381 int canvas_width, int canvas_height) { 382 if (prev_frame == NULL) return 1; 383 if (prev_dispose == DISPOSE_BACKGROUND) { 384 if (IsFullFrame(prev_desc->Width, prev_desc->Height, 385 canvas_width, canvas_height)) { 386 return 1; 387 } 388 if (prev_frame->is_key_frame) return 1; 389 } 390 return 0; 391 } 392 393 static int GetTransparentIndexGIF(GifFileType* gif) { 394 GraphicsControlBlock first_gcb; 395 memset(&first_gcb, 0, sizeof(first_gcb)); 396 DGifSavedExtensionToGCB(gif, 0, &first_gcb); 397 return first_gcb.TransparentColor; 398 } 399 400 static uint32_t GetBackgroundColorGIF(GifFileType* gif) { 401 const int transparent_index = GetTransparentIndexGIF(gif); 402 const ColorMapObject* const color_map = gif->SColorMap; 403 if (transparent_index != NO_TRANSPARENT_COLOR && 404 gif->SBackGroundColor == transparent_index) { 405 return 0x00000000; // Special case: transparent black. 406 } else if (color_map == NULL || color_map->Colors == NULL 407 || gif->SBackGroundColor >= color_map->ColorCount) { 408 return 0xffffffff; // Invalid: assume white. 409 } else { 410 const GifColorType color = color_map->Colors[gif->SBackGroundColor]; 411 return (0xffu << 24) | 412 (color.Red << 16) | 413 (color.Green << 8) | 414 (color.Blue << 0); 415 } 416 } 417 418 // Find appropriate app extension and get loop count from the next extension. 419 // We use Chrome's interpretation of the 'loop_count' semantics: 420 // if not present -> loop once 421 // if present and loop_count == 0, return 0 ('infinite'). 422 // if present and loop_count != 0, it's the number of *extra* loops 423 // so we need to return loop_count + 1 as total loop number. 424 static uint32_t GetLoopCountGIF(const GifFileType* const gif) { 425 int i; 426 for (i = 0; i < gif->ImageCount; ++i) { 427 const SavedImage* const image = &gif->SavedImages[i]; 428 int j; 429 for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) { 430 const ExtensionBlock* const eb1 = image->ExtensionBlocks + j; 431 const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1; 432 const char* const signature = (const char*)eb1->Bytes; 433 const int signature_is_ok = 434 (eb1->Function == APPLICATION_EXT_FUNC_CODE) && 435 (eb1->ByteCount == 11) && 436 (!memcmp(signature, "NETSCAPE2.0", 11) || 437 !memcmp(signature, "ANIMEXTS1.0", 11)); 438 if (signature_is_ok && 439 eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 && 440 eb2->Bytes[0] == 1) { 441 const uint32_t extra_loop = ((uint32_t)(eb2->Bytes[2]) << 8) + 442 ((uint32_t)(eb2->Bytes[1]) << 0); 443 return (extra_loop > 0) ? extra_loop + 1 : 0; 444 } 445 } 446 } 447 return 1; // Default. 448 } 449 450 // Get duration of 'n'th frame in milliseconds. 451 static int GetFrameDurationGIF(GifFileType* gif, int n) { 452 GraphicsControlBlock gcb; 453 memset(&gcb, 0, sizeof(gcb)); 454 DGifSavedExtensionToGCB(gif, n, &gcb); 455 return gcb.DelayTime * 10; 456 } 457 458 // Returns true if frame 'target' completely covers 'covered'. 459 static int CoversFrameGIF(const GifImageDesc* const target, 460 const GifImageDesc* const covered) { 461 return target->Left <= covered->Left && 462 covered->Left + covered->Width <= target->Left + target->Width && 463 target->Top <= covered->Top && 464 covered->Top + covered->Height <= target->Top + target->Height; 465 } 466 467 static void RemapPixelsGIF(const uint8_t* const src, 468 const ColorMapObject* const cmap, 469 int transparent_color, int len, uint8_t* dst) { 470 int i; 471 for (i = 0; i < len; ++i) { 472 if (src[i] != transparent_color) { 473 // If a pixel in the current frame is transparent, we don't modify it, so 474 // that we can see-through the corresponding pixel from an earlier frame. 475 const GifColorType c = cmap->Colors[src[i]]; 476 dst[4 * i + 0] = c.Red; 477 dst[4 * i + 1] = c.Green; 478 dst[4 * i + 2] = c.Blue; 479 dst[4 * i + 3] = 0xff; 480 } 481 } 482 } 483 484 static int ReadFrameGIF(const SavedImage* const gif_image, 485 const ColorMapObject* cmap, int transparent_color, 486 int out_stride, uint8_t* const dst) { 487 const GifImageDesc* image_desc = &gif_image->ImageDesc; 488 const uint8_t* in; 489 uint8_t* out; 490 int j; 491 492 if (image_desc->ColorMap) cmap = image_desc->ColorMap; 493 494 if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { 495 fprintf(stderr, "Potentially corrupt color map.\n"); 496 return 0; 497 } 498 499 in = (const uint8_t*)gif_image->RasterBits; 500 out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels; 501 502 for (j = 0; j < image_desc->Height; ++j) { 503 RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out); 504 in += image_desc->Width; 505 out += out_stride; 506 } 507 return 1; 508 } 509 510 // Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct. 511 static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image, 512 int dump_frames, const char dump_folder[]) { 513 uint32_t frame_count; 514 uint32_t canvas_width, canvas_height; 515 uint32_t i; 516 int gif_error; 517 GifFileType* gif; 518 519 gif = DGifOpenFileUnicode((const W_CHAR*)filename, NULL); 520 if (gif == NULL) { 521 WFPRINTF(stderr, "Could not read file: %s.\n", (const W_CHAR*)filename); 522 return 0; 523 } 524 525 gif_error = DGifSlurp(gif); 526 if (gif_error != GIF_OK) { 527 WFPRINTF(stderr, "Could not parse image: %s.\n", (const W_CHAR*)filename); 528 GIFDisplayError(gif, gif_error); 529 DGifCloseFile(gif, NULL); 530 return 0; 531 } 532 533 // Animation properties. 534 image->canvas_width = (uint32_t)gif->SWidth; 535 image->canvas_height = (uint32_t)gif->SHeight; 536 if (image->canvas_width > MAX_CANVAS_SIZE || 537 image->canvas_height > MAX_CANVAS_SIZE) { 538 fprintf(stderr, "Invalid canvas dimension: %d x %d\n", 539 image->canvas_width, image->canvas_height); 540 DGifCloseFile(gif, NULL); 541 return 0; 542 } 543 image->loop_count = GetLoopCountGIF(gif); 544 image->bgcolor = GetBackgroundColorGIF(gif); 545 546 frame_count = (uint32_t)gif->ImageCount; 547 if (frame_count == 0) { 548 DGifCloseFile(gif, NULL); 549 return 0; 550 } 551 552 if (image->canvas_width == 0 || image->canvas_height == 0) { 553 image->canvas_width = gif->SavedImages[0].ImageDesc.Width; 554 image->canvas_height = gif->SavedImages[0].ImageDesc.Height; 555 gif->SavedImages[0].ImageDesc.Left = 0; 556 gif->SavedImages[0].ImageDesc.Top = 0; 557 if (image->canvas_width == 0 || image->canvas_height == 0) { 558 fprintf(stderr, "Invalid canvas size in GIF.\n"); 559 DGifCloseFile(gif, NULL); 560 return 0; 561 } 562 } 563 // Allocate frames. 564 if (!AllocateFrames(image, frame_count)) { 565 DGifCloseFile(gif, NULL); 566 return 0; 567 } 568 569 canvas_width = image->canvas_width; 570 canvas_height = image->canvas_height; 571 572 // Decode and reconstruct frames. 573 for (i = 0; i < frame_count; ++i) { 574 const int canvas_width_in_bytes = canvas_width * kNumChannels; 575 const SavedImage* const curr_gif_image = &gif->SavedImages[i]; 576 GraphicsControlBlock curr_gcb; 577 DecodedFrame* curr_frame; 578 uint8_t* curr_rgba; 579 580 memset(&curr_gcb, 0, sizeof(curr_gcb)); 581 DGifSavedExtensionToGCB(gif, i, &curr_gcb); 582 583 curr_frame = &image->frames[i]; 584 curr_rgba = curr_frame->rgba; 585 curr_frame->duration = GetFrameDurationGIF(gif, i); 586 // Force frames with a small or no duration to 100ms to be consistent 587 // with web browsers and other transcoding tools (like gif2webp itself). 588 if (curr_frame->duration <= 10) curr_frame->duration = 100; 589 590 if (i == 0) { // Initialize as transparent. 591 curr_frame->is_key_frame = 1; 592 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height); 593 } else { 594 DecodedFrame* const prev_frame = &image->frames[i - 1]; 595 const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc; 596 GraphicsControlBlock prev_gcb; 597 memset(&prev_gcb, 0, sizeof(prev_gcb)); 598 DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb); 599 600 curr_frame->is_key_frame = 601 IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame, 602 canvas_width, canvas_height); 603 604 if (curr_frame->is_key_frame) { // Initialize as transparent. 605 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height); 606 } else { 607 int prev_frame_disposed, curr_frame_opaque; 608 int prev_frame_completely_covered; 609 // Initialize with previous canvas. 610 uint8_t* const prev_rgba = image->frames[i - 1].rgba; 611 CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height); 612 613 // Dispose previous frame rectangle. 614 prev_frame_disposed = 615 (prev_gcb.DisposalMode == DISPOSE_BACKGROUND || 616 prev_gcb.DisposalMode == DISPOSE_PREVIOUS); 617 curr_frame_opaque = 618 (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR); 619 prev_frame_completely_covered = 620 curr_frame_opaque && 621 CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc); 622 623 if (prev_frame_disposed && !prev_frame_completely_covered) { 624 switch (prev_gcb.DisposalMode) { 625 case DISPOSE_BACKGROUND: { 626 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes, 627 prev_desc->Left, prev_desc->Top, 628 prev_desc->Width, prev_desc->Height); 629 break; 630 } 631 case DISPOSE_PREVIOUS: { 632 int src_frame_num = i - 2; 633 while (src_frame_num >= 0) { 634 GraphicsControlBlock src_frame_gcb; 635 memset(&src_frame_gcb, 0, sizeof(src_frame_gcb)); 636 DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb); 637 if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break; 638 --src_frame_num; 639 } 640 if (src_frame_num >= 0) { 641 // Restore pixels inside previous frame rectangle to 642 // corresponding pixels in source canvas. 643 uint8_t* const src_frame_rgba = 644 image->frames[src_frame_num].rgba; 645 CopyFrameRectangle(src_frame_rgba, curr_rgba, 646 canvas_width_in_bytes, 647 prev_desc->Left, prev_desc->Top, 648 prev_desc->Width, prev_desc->Height); 649 } else { 650 // Source canvas doesn't exist. So clear previous frame 651 // rectangle to background. 652 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes, 653 prev_desc->Left, prev_desc->Top, 654 prev_desc->Width, prev_desc->Height); 655 } 656 break; 657 } 658 default: 659 break; // Nothing to do. 660 } 661 } 662 } 663 } 664 665 // Decode current frame. 666 if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor, 667 canvas_width_in_bytes, curr_rgba)) { 668 DGifCloseFile(gif, NULL); 669 return 0; 670 } 671 672 if (dump_frames) { 673 if (!DumpFrame(filename, dump_folder, i, curr_rgba, 674 canvas_width, canvas_height)) { 675 DGifCloseFile(gif, NULL); 676 return 0; 677 } 678 } 679 } 680 image->format = ANIM_GIF; 681 DGifCloseFile(gif, NULL); 682 return 1; 683 } 684 685 #else 686 687 static int IsGIF(const WebPData* const data) { 688 (void)data; 689 return 0; 690 } 691 692 static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image, 693 int dump_frames, const char dump_folder[]) { 694 (void)filename; 695 (void)image; 696 (void)dump_frames; 697 (void)dump_folder; 698 fprintf(stderr, "GIF support not compiled. Please install the libgif-dev " 699 "package before building.\n"); 700 return 0; 701 } 702 703 #endif // WEBP_HAVE_GIF 704 705 // ----------------------------------------------------------------------------- 706 707 int ReadAnimatedImage(const char filename[], AnimatedImage* const image, 708 int dump_frames, const char dump_folder[]) { 709 int ok = 0; 710 WebPData webp_data; 711 712 WebPDataInit(&webp_data); 713 memset(image, 0, sizeof(*image)); 714 715 if (!ImgIoUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) { 716 WFPRINTF(stderr, "Error reading file: %s\n", (const W_CHAR*)filename); 717 return 0; 718 } 719 720 if (IsWebP(&webp_data)) { 721 ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames, 722 dump_folder); 723 } else if (IsGIF(&webp_data)) { 724 ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder); 725 } else { 726 WFPRINTF(stderr, 727 "Unknown file type: %s. Supported file types are WebP and GIF\n", 728 (const W_CHAR*)filename); 729 ok = 0; 730 } 731 if (!ok) ClearAnimatedImage(image); 732 WebPDataClear(&webp_data); 733 return ok; 734 } 735 736 static void Accumulate(double v1, double v2, double* const max_diff, 737 double* const sse) { 738 const double diff = fabs(v1 - v2); 739 if (diff > *max_diff) *max_diff = diff; 740 *sse += diff * diff; 741 } 742 743 void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[], 744 uint32_t width, uint32_t height, int premultiply, 745 int* const max_diff, double* const psnr) { 746 const uint32_t stride = width * kNumChannels; 747 const int kAlphaChannel = kNumChannels - 1; 748 double f_max_diff = 0.; 749 double sse = 0.; 750 uint32_t x, y; 751 for (y = 0; y < height; ++y) { 752 for (x = 0; x < stride; x += kNumChannels) { 753 int k; 754 const size_t offset = (size_t)y * stride + x; 755 const int alpha1 = rgba1[offset + kAlphaChannel]; 756 const int alpha2 = rgba2[offset + kAlphaChannel]; 757 Accumulate(alpha1, alpha2, &f_max_diff, &sse); 758 if (!premultiply) { 759 for (k = 0; k < kAlphaChannel; ++k) { 760 Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse); 761 } 762 } else { 763 // premultiply R/G/B channels with alpha value 764 for (k = 0; k < kAlphaChannel; ++k) { 765 Accumulate(rgba1[offset + k] * alpha1 / 255., 766 rgba2[offset + k] * alpha2 / 255., 767 &f_max_diff, &sse); 768 } 769 } 770 } 771 } 772 *max_diff = (int)f_max_diff; 773 if (*max_diff == 0) { 774 *psnr = 99.; // PSNR when images are identical. 775 } else { 776 sse /= stride * height; 777 assert(sse != 0.0); 778 *psnr = 4.3429448 * log(255. * 255. / sse); 779 } 780 } 781 782 void GetAnimatedImageVersions(int* const decoder_version, 783 int* const demux_version) { 784 *decoder_version = WebPGetDecoderVersion(); 785 *demux_version = WebPGetDemuxVersion(); 786 }