blob: a62e900010018d3eac03ebcd40762c8ba13e8413 [file] [log] [blame]
Urvang Joshiacd7b5a2015-05-01 16:11:49 -07001// 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#include <fstream>
20#include <sstream> // for 'ostringstream'.
21
22#ifdef WEBP_HAVE_GIF
23#include <gif_lib.h>
24#endif
25#include "webp/format_constants.h"
26#include "webp/decode.h"
27#include "webp/demux.h"
28
29using std::ifstream;
30using std::ios;
31using std::ofstream;
32using std::ostringstream;
33
34static const int kNumChannels = 4;
35
36// -----------------------------------------------------------------------------
37// Common utilities.
38
39// Returns true if the frame covers the full canvas.
40static bool IsFullFrame(int width, int height,
41 int canvas_width, int canvas_height) {
42 return (width == canvas_width && height == canvas_height);
43}
44
45static void AllocateFrames(AnimatedImage* const image, uint32_t frame_count) {
46 image->frames.resize(frame_count);
47 for (size_t i = 0; i < image->frames.size(); ++i) {
48 const size_t rgba_size =
49 image->canvas_width * kNumChannels * image->canvas_height;
50 image->frames[i].rgba.resize(rgba_size);
51 }
52}
53
54// Clear the canvas to transparent.
55static void ZeroFillCanvas(uint8_t* rgba,
56 uint32_t canvas_width, uint32_t canvas_height) {
57 memset(rgba, 0, canvas_width * kNumChannels * canvas_height);
58}
59
60// Clear given frame rectangle to transparent.
61static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
62 int y_offset, int width, int height) {
63 assert(width * kNumChannels <= rgba_stride);
64 rgba += y_offset * rgba_stride + x_offset * kNumChannels;
65 for (int j = 0; j < height; ++j) {
66 memset(rgba, 0, width * kNumChannels);
67 rgba += rgba_stride;
68 }
69}
70
71// Copy width * height pixels from 'src' to 'dst'.
72static void CopyCanvas(const uint8_t* src, uint8_t* dst,
73 uint32_t width, uint32_t height) {
74 assert(src != NULL && dst != NULL);
75 memcpy(dst, src, width * kNumChannels * height);
76}
77
78// Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'.
79static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
80 int x_offset, int y_offset,
81 int width, int height) {
82 const int width_in_bytes = width * kNumChannels;
83 assert(width_in_bytes <= stride);
84 const size_t offset = y_offset * stride + x_offset * kNumChannels;
85 src += offset;
86 dst += offset;
87 for (int j = 0; j < height; ++j) {
88 memcpy(dst, src, width_in_bytes);
89 src += stride;
90 dst += stride;
91 }
92}
93
94// Canonicalize all transparent pixels to transparent black to aid comparison.
95static void CleanupTransparentPixels(uint32_t* rgba,
96 uint32_t width, uint32_t height) {
97 const uint32_t* const rgba_end = rgba + width * height;
98 while (rgba < rgba_end) {
99 const uint8_t alpha = (*rgba >> 24) & 0xff;
100 if (alpha == 0) {
101 *rgba = 0;
102 }
103 ++rgba;
104 }
105}
106
107// Dump frame to a PAM file.
108// Returns true on success.
109static bool DumpFrame(const char filename[], const char dump_folder[],
110 uint32_t frame_num, const uint8_t rgba[],
111 int canvas_width, int canvas_height) {
112 const std::string filename_str = filename;
113 const size_t slash_idx = filename_str.find_last_of("/\\");
114 const std::string base_name = (slash_idx != std::string::npos)
115 ? filename_str.substr(slash_idx + 1)
116 : filename_str;
117 ostringstream dump_file;
118 dump_file << dump_folder << "/" << base_name << "_frame_" << frame_num
119 << ".pam";
120
121 ofstream fout(dump_file.str().c_str(), ios::binary | ios::out);
122 if (!fout.good()) {
123 fprintf(stderr, "Error opening file for writing: %s\n",
124 dump_file.str().c_str());
125 return false;
126 }
127
128 fout << "P7\nWIDTH " << canvas_width << "\nHEIGHT " << canvas_height
129 << "\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n";
130 for (int y = 0; y < canvas_height; ++y) {
131 fout.write(
132 reinterpret_cast<const char*>(rgba) + y * canvas_width * kNumChannels,
133 canvas_width * kNumChannels);
134 if (!fout.good()) {
135 fprintf(stderr, "Error writing to file: %s\n", dump_file.str().c_str());
136 return 0;
137 }
138 }
139 fout.close();
140 return true;
141}
142
143// -----------------------------------------------------------------------------
144// WebP Decoding.
145
146// Returns true if this is a valid WebP bitstream.
147static bool IsWebP(const std::string& file_str) {
148 return WebPGetInfo(reinterpret_cast<const uint8_t*>(file_str.c_str()),
149 file_str.length(), NULL, NULL) != 0;
150}
151
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700152// Read animated WebP bitstream 'file_str' into 'AnimatedImage' struct.
153static bool ReadAnimatedWebP(const char filename[], const std::string& file_str,
154 AnimatedImage* const image, bool dump_frames,
155 const char dump_folder[]) {
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700156 bool ok = false;
157 bool dump_ok = true;
158 uint32_t frame_index = 0;
159 int prev_frame_timestamp = 0;
160
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700161 const WebPData webp_data = {
162 reinterpret_cast<const uint8_t*>(file_str.data()), file_str.size()
163 };
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700164
165 WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data);
166 if (dec == NULL) {
167 fprintf(stderr, "Error parsing image: %s\n", filename);
168 goto End;
169 }
170
171 WebPAnimInfo anim_info;
172 if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
173 fprintf(stderr, "Error getting global info about the animation\n");
174 goto End;
175 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700176
177 // Animation properties.
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700178 image->canvas_width = anim_info.canvas_width;
179 image->canvas_height = anim_info.canvas_height;
180 image->loop_count = anim_info.loop_count;
181 image->bgcolor = anim_info.bgcolor;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700182
183 // Allocate frames.
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700184 AllocateFrames(image, anim_info.frame_count);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700185
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700186 // Decode frames.
187 while (WebPAnimDecoderHasMoreFrames(dec)) {
188 uint8_t* frame_rgba;
189 int timestamp;
190 if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &timestamp)) {
191 fprintf(stderr, "Error decoding frame #%u\n", frame_index);
192 goto End;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700193 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700194 DecodedFrame* const curr_frame = &image->frames[frame_index];
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700195 uint8_t* const curr_rgba = curr_frame->rgba.data();
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700196 curr_frame->duration = timestamp - prev_frame_timestamp;
197 curr_frame->is_key_frame = false; // Unused.
198 memcpy(curr_rgba, frame_rgba,
199 image->canvas_width * kNumChannels * image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700200
201 // Needed only because we may want to compare with GIF later.
202 CleanupTransparentPixels(reinterpret_cast<uint32_t*>(curr_rgba),
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700203 image->canvas_width, image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700204
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700205 if (dump_frames && dump_ok) {
206 dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
207 image->canvas_width, image->canvas_height);
208 if (!dump_ok) { // Print error once, but continue decode loop.
209 fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
210 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700211 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700212
213 ++frame_index;
214 prev_frame_timestamp = timestamp;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700215 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700216 ok = dump_ok;
217
218 End:
219 WebPAnimDecoderDelete(dec);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700220 return ok;
221}
222
223// -----------------------------------------------------------------------------
224// GIF Decoding.
225
226// Returns true if this is a valid GIF bitstream.
227static bool IsGIF(const std::string& file_str) {
228 const char* const cstr = file_str.c_str();
229 return file_str.length() > GIF_STAMP_LEN &&
230 (!memcmp(GIF_STAMP, cstr, GIF_STAMP_LEN) ||
231 !memcmp(GIF87_STAMP, cstr, GIF_STAMP_LEN) ||
232 !memcmp(GIF89_STAMP, cstr, GIF_STAMP_LEN));
233}
234
235#ifdef WEBP_HAVE_GIF
236
237// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
238#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
239# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
240# define LOCAL_GIF_PREREQ(maj, min) \
241 (LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
242#else
243# define LOCAL_GIF_VERSION 0
244# define LOCAL_GIF_PREREQ(maj, min) 0
245#endif
246
247#if !LOCAL_GIF_PREREQ(5, 0)
248
249// Added in v5.0
250typedef struct GraphicsControlBlock {
251 int DisposalMode;
252#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
253#define DISPOSE_DO_NOT 1 // Leave image in place
254#define DISPOSE_BACKGROUND 2 // Set area to background color
255#define DISPOSE_PREVIOUS 3 // Restore to previous content
256 bool UserInputFlag; // User confirmation required before disposal
257 int DelayTime; // Pre-display delay in 0.01sec units
258 int TransparentColor; // Palette index for transparency, -1 if none
259#define NO_TRANSPARENT_COLOR -1
260} GraphicsControlBlock;
261
262static int DGifExtensionToGCB(const size_t GifExtensionLength,
263 const GifByteType* GifExtension,
264 GraphicsControlBlock* gcb) {
265 if (GifExtensionLength != 4) {
266 return GIF_ERROR;
267 }
268 gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
269 gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
270 gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
271 if (GifExtension[0] & 0x01) {
272 gcb->TransparentColor = static_cast<int>(GifExtension[3]);
273 } else {
274 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
275 }
276 return GIF_OK;
277}
278
279static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
280 GraphicsControlBlock* gcb) {
281 int i;
282 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
283 return GIF_ERROR;
284 }
285 gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
286 gcb->UserInputFlag = false;
287 gcb->DelayTime = 0;
288 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
289
290 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
291 ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
292 if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
293 return DGifExtensionToGCB(
294 ep->ByteCount, reinterpret_cast<const GifByteType*>(ep->Bytes), gcb);
295 }
296 }
297 return GIF_ERROR;
298}
299
300#define CONTINUE_EXT_FUNC_CODE 0x00
301
302// Signature was changed in v5.0
303#define DGifOpenFileName(a, b) DGifOpenFileName(a)
304
305#endif // !LOCAL_GIF_PREREQ(5, 0)
306
307// Signature changed in v5.1
308#if !LOCAL_GIF_PREREQ(5, 1)
309#define DGifCloseFile(a, b) DGifCloseFile(a)
310#endif
311
312static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
313 // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
314#if LOCAL_GIF_PREREQ(4, 2)
315#if LOCAL_GIF_PREREQ(5, 0)
316 // Static string actually, hence the const char* cast.
317 const char* error_str = (const char*)GifErrorString(
318 (gif == NULL) ? gif_error : gif->Error);
319#else
320 const char* error_str = (const char*)GifErrorString();
321 (void)gif;
322#endif
323 if (error_str == NULL) error_str = "Unknown error";
324 fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
325#else
326 (void)gif;
327 fprintf(stderr, "GIFLib Error %d: ", gif_error);
328 PrintGifError();
329 fprintf(stderr, "\n");
330#endif
331}
332
333static bool IsKeyFrameGIF(const GifImageDesc& prev_desc, int prev_dispose,
334 const DecodedFrame* const prev_frame,
335 int canvas_width, int canvas_height) {
336 if (prev_frame == NULL) return true;
337 if (prev_dispose == DISPOSE_BACKGROUND) {
338 if (IsFullFrame(prev_desc.Width, prev_desc.Height,
339 canvas_width, canvas_height)) {
340 return true;
341 }
342 if (prev_frame->is_key_frame) return true;
343 }
344 return false;
345}
346
347static int GetTransparentIndexGIF(GifFileType* gif) {
348 GraphicsControlBlock first_gcb = GraphicsControlBlock();
349 DGifSavedExtensionToGCB(gif, 0, &first_gcb);
350 return first_gcb.TransparentColor;
351}
352
353static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
354 const int transparent_index = GetTransparentIndexGIF(gif);
355 const ColorMapObject* const color_map = gif->SColorMap;
356 if (transparent_index != NO_TRANSPARENT_COLOR &&
357 gif->SBackGroundColor == transparent_index) {
358 return 0x00ffffff; // Special case: transparent white.
359 } else if (color_map == NULL || color_map->Colors == NULL
360 || gif->SBackGroundColor >= color_map->ColorCount) {
361 return 0xffffffff; // Invalid: assume white.
362 } else {
363 const GifColorType color = color_map->Colors[gif->SBackGroundColor];
364 return (0xff << 24) |
365 (color.Red << 16) |
366 (color.Green << 8) |
367 (color.Blue << 0);
368 }
369}
370
371// Find appropriate app extension and get loop count from the next extension.
372static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
373 for (int i = 0; i < gif->ImageCount; ++i) {
374 const SavedImage* const image = &gif->SavedImages[i];
375 for (int j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
376 const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
377 const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
378 const char* const signature = reinterpret_cast<const char*>(eb1->Bytes);
379 const bool signature_is_ok =
380 (eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
381 (eb1->ByteCount == 11) &&
382 (!memcmp(signature, "NETSCAPE2.0", 11) ||
383 !memcmp(signature, "ANIMEXTS1.0", 11));
384 if (signature_is_ok &&
385 eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
386 eb2->Bytes[0] == 1) {
387 return (static_cast<uint32_t>(eb2->Bytes[2]) << 8) +
388 (static_cast<uint32_t>(eb2->Bytes[1]) << 0);
389 }
390 }
391 }
392 return 0; // Default.
393}
394
395// Get duration of 'n'th frame in milliseconds.
396static int GetFrameDurationGIF(GifFileType* gif, int n) {
397 GraphicsControlBlock gcb = GraphicsControlBlock();
398 DGifSavedExtensionToGCB(gif, n, &gcb);
399 return gcb.DelayTime * 10;
400}
401
402// Returns true if frame 'target' completely covers 'covered'.
403static bool CoversFrameGIF(const GifImageDesc& target,
404 const GifImageDesc& covered) {
405 return target.Left <= covered.Left &&
406 covered.Left + covered.Width <= target.Left + target.Width &&
407 target.Top <= covered.Top &&
408 covered.Top + covered.Height <= target.Top + target.Height;
409}
410
411static void RemapPixelsGIF(const uint8_t* const src,
412 const ColorMapObject* const cmap,
413 int transparent_color, int len, uint8_t* dst) {
414 int i;
415 for (i = 0; i < len; ++i) {
416 if (src[i] != transparent_color) {
417 // If a pixel in the current frame is transparent, we don't modify it, so
418 // that we can see-through the corresponding pixel from an earlier frame.
419 const GifColorType c = cmap->Colors[src[i]];
420 dst[4 * i + 0] = c.Red;
421 dst[4 * i + 1] = c.Green;
422 dst[4 * i + 2] = c.Blue;
423 dst[4 * i + 3] = 0xff;
424 }
425 }
426}
427
428static bool ReadFrameGIF(const SavedImage* const gif_image,
429 const ColorMapObject* cmap, int transparent_color,
430 int out_stride, uint8_t* const dst) {
431 const GifImageDesc& image_desc = gif_image->ImageDesc;
432 if (image_desc.ColorMap) {
433 cmap = image_desc.ColorMap;
434 }
435
436 if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
437 fprintf(stderr, "Potentially corrupt color map.\n");
438 return false;
439 }
440
441 const uint8_t* in = reinterpret_cast<uint8_t*>(gif_image->RasterBits);
442 uint8_t* out =
443 dst + image_desc.Top * out_stride + image_desc.Left * kNumChannels;
444
445 for (int j = 0; j < image_desc.Height; ++j) {
446 RemapPixelsGIF(in, cmap, transparent_color, image_desc.Width, out);
447 in += image_desc.Width;
448 out += out_stride;
449 }
450 return true;
451}
452
453// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
454static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
455 bool dump_frames, const char dump_folder[]) {
456 GifFileType* gif = DGifOpenFileName(filename, NULL);
457 if (gif == NULL) {
458 fprintf(stderr, "Could not read file: %s.\n", filename);
459 return false;
460 }
461
462 const int gif_error = DGifSlurp(gif);
463 if (gif_error != GIF_OK) {
464 fprintf(stderr, "Could not parse image: %s.\n", filename);
465 GIFDisplayError(gif, gif_error);
466 DGifCloseFile(gif, NULL);
467 return false;
468 }
469
470 // Animation properties.
471 image->canvas_width = static_cast<uint32_t>(gif->SWidth);
472 image->canvas_height = static_cast<uint32_t>(gif->SHeight);
473 if (image->canvas_width > MAX_CANVAS_SIZE ||
474 image->canvas_height > MAX_CANVAS_SIZE) {
475 fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
476 image->canvas_width, image->canvas_height);
477 DGifCloseFile(gif, NULL);
478 return false;
479 }
480 image->loop_count = GetLoopCountGIF(gif);
481 image->bgcolor = GetBackgroundColorGIF(gif);
482
483 const uint32_t frame_count = static_cast<uint32_t>(gif->ImageCount);
484 if (frame_count == 0) {
485 DGifCloseFile(gif, NULL);
486 return false;
487 }
488
489 if (image->canvas_width == 0 || image->canvas_height == 0) {
490 image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
491 image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
492 gif->SavedImages[0].ImageDesc.Left = 0;
493 gif->SavedImages[0].ImageDesc.Top = 0;
494 if (image->canvas_width == 0 || image->canvas_height == 0) {
495 fprintf(stderr, "Invalid canvas size in GIF.\n");
496 DGifCloseFile(gif, NULL);
497 return false;
498 }
499 }
500 // Allocate frames.
501 AllocateFrames(image, frame_count);
502
503 const uint32_t canvas_width = image->canvas_width;
504 const uint32_t canvas_height = image->canvas_height;
505
506 // Decode and reconstruct frames.
507 for (uint32_t i = 0; i < frame_count; ++i) {
508 const int canvas_width_in_bytes = canvas_width * kNumChannels;
509 const SavedImage* const curr_gif_image = &gif->SavedImages[i];
510 GraphicsControlBlock curr_gcb = GraphicsControlBlock();
511 DGifSavedExtensionToGCB(gif, i, &curr_gcb);
512
513 DecodedFrame* const curr_frame = &image->frames[i];
514 uint8_t* const curr_rgba = curr_frame->rgba.data();
515 curr_frame->duration = GetFrameDurationGIF(gif, i);
516
517 if (i == 0) { // Initialize as transparent.
518 curr_frame->is_key_frame = true;
519 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
520 } else {
521 DecodedFrame* const prev_frame = &image->frames[i - 1];
522 const GifImageDesc& prev_desc = gif->SavedImages[i - 1].ImageDesc;
523 GraphicsControlBlock prev_gcb = GraphicsControlBlock();
524 DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
525
526 curr_frame->is_key_frame =
527 IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
528 canvas_width, canvas_height);
529
530 if (curr_frame->is_key_frame) { // Initialize as transparent.
531 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
532 } else {
533 // Initialize with previous canvas.
534 uint8_t* const prev_rgba = image->frames[i - 1].rgba.data();
535 CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
536
537 // Dispose previous frame rectangle.
538 bool prev_frame_disposed =
539 (prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
540 prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
541 bool curr_frame_opaque =
542 (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
543 bool prev_frame_completely_covered =
544 curr_frame_opaque &&
545 CoversFrameGIF(curr_gif_image->ImageDesc, prev_desc);
546
547 if (prev_frame_disposed && !prev_frame_completely_covered) {
548 switch (prev_gcb.DisposalMode) {
549 case DISPOSE_BACKGROUND: {
550 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
551 prev_desc.Left, prev_desc.Top,
552 prev_desc.Width, prev_desc.Height);
553 break;
554 }
555 case DISPOSE_PREVIOUS: {
556 int src_frame_num = i - 2;
557 while (src_frame_num >= 0) {
558 GraphicsControlBlock src_frame_gcb = GraphicsControlBlock();
559 DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
560 if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
561 --src_frame_num;
562 }
563 if (src_frame_num >= 0) {
564 // Restore pixels inside previous frame rectangle to
565 // corresponding pixels in source canvas.
566 uint8_t* const src_frame_rgba =
567 image->frames[src_frame_num].rgba.data();
568 CopyFrameRectangle(src_frame_rgba, curr_rgba,
569 canvas_width_in_bytes,
570 prev_desc.Left, prev_desc.Top,
571 prev_desc.Width, prev_desc.Height);
572 } else {
573 // Source canvas doesn't exist. So clear previous frame
574 // rectangle to background.
575 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
576 prev_desc.Left, prev_desc.Top,
577 prev_desc.Width, prev_desc.Height);
578 }
579 break;
580 }
581 default:
582 break; // Nothing to do.
583 }
584 }
585 }
586 }
587
588 // Decode current frame.
589 if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
590 canvas_width_in_bytes, curr_rgba)) {
591 DGifCloseFile(gif, NULL);
592 return false;
593 }
594
595 if (dump_frames) {
596 if (!DumpFrame(filename, dump_folder, i, curr_rgba,
597 canvas_width, canvas_height)) {
598 DGifCloseFile(gif, NULL);
599 return false;
600 }
601 }
602 }
603 DGifCloseFile(gif, NULL);
604 return true;
605}
606
607#else
608
609static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
610 bool dump_frames, const char dump_folder[]) {
611 (void)filename;
612 (void)image;
613 (void)dump_frames;
614 (void)dump_folder;
615 fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
616 "package before building.\n");
617 return false;
618}
619
620#endif // WEBP_HAVE_GIF
621
622// -----------------------------------------------------------------------------
623
624static bool ReadFile(const char filename[], std::string* filestr) {
625 ifstream fin(filename, ios::binary);
626 if (!fin.good()) return false;
627 ostringstream strout;
628 strout << fin.rdbuf();
629 *filestr = strout.str();
630 fin.close();
631 return true;
632}
633
634bool ReadAnimatedImage(const char filename[], AnimatedImage* const image,
635 bool dump_frames, const char dump_folder[]) {
636 std::string file_str;
637 if (!ReadFile(filename, &file_str)) {
638 fprintf(stderr, "Error reading file: %s\n", filename);
639 return false;
640 }
641
642 if (IsWebP(file_str)) {
643 return ReadAnimatedWebP(filename, file_str, image, dump_frames,
644 dump_folder);
645 } else if (IsGIF(file_str)) {
646 return ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
647 } else {
648 fprintf(stderr,
649 "Unknown file type: %s. Supported file types are WebP and GIF\n",
650 filename);
651 return false;
652 }
653}
654
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000655static void Accumulate(double v1, double v2, double* const max_diff,
656 double* const sse) {
657 const double diff = fabs(v1 - v2);
658 if (diff > *max_diff) *max_diff = diff;
659 *sse += diff * diff;
660}
661
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700662void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000663 uint32_t width, uint32_t height, bool premultiply,
664 int* const max_diff, double* const psnr) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700665 const uint32_t stride = width * kNumChannels;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000666 const int kAlphaChannel = kNumChannels - 1;
667 double f_max_diff = 0.;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700668 double sse = 0.;
669 for (uint32_t y = 0; y < height; ++y) {
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000670 for (uint32_t x = 0; x < stride; x += kNumChannels) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700671 const size_t offset = y * stride + x;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000672 const int alpha1 = rgba1[offset + kAlphaChannel];
673 const int alpha2 = rgba2[offset + kAlphaChannel];
674 Accumulate(alpha1, alpha2, &f_max_diff, &sse);
675 if (!premultiply) {
676 for (int k = 0; k < kAlphaChannel; ++k) {
677 Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
678 }
679 } else {
680 // premultiply R/G/B channels with alpha value
681 for (int k = 0; k < kAlphaChannel; ++k) {
682 Accumulate(rgba1[offset + k] * alpha1 / 255.,
683 rgba2[offset + k] * alpha2 / 255.,
684 &f_max_diff, &sse);
685 }
686 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700687 }
688 }
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000689 *max_diff = static_cast<int>(f_max_diff);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700690 if (*max_diff == 0) {
691 *psnr = 99.; // PSNR when images are identical.
692 } else {
693 sse /= stride * height;
694 *psnr = 10. * log10(255. * 255. / sse);
695 }
696}