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