blob: cb83496400307ebf6d4bdb0332ccb1a479eb37d6 [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
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070019#ifdef 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"
Pascal Massimino96201e52015-11-06 07:47:03 +010025#include "./example_util.h"
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070026
Pascal Massimino96201e52015-11-06 07:47:03 +010027#if defined(_MSC_VER) && _MSC_VER < 1900
28#define snprintf _snprintf
29#endif
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070030
31static const int kNumChannels = 4;
32
33// -----------------------------------------------------------------------------
34// Common utilities.
35
36// Returns true if the frame covers the full canvas.
Pascal Massimino96201e52015-11-06 07:47:03 +010037static int IsFullFrame(int width, int height,
38 int canvas_width, int canvas_height) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070039 return (width == canvas_width && height == canvas_height);
40}
41
Pascal Massimino96201e52015-11-06 07:47:03 +010042static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
43 uint32_t i;
44 const size_t rgba_size =
45 image->canvas_width * kNumChannels * image->canvas_height;
46 uint8_t* const mem = (uint8_t*)malloc(num_frames * rgba_size * sizeof(*mem));
47 DecodedFrame* const frames =
48 (DecodedFrame*)malloc(num_frames * sizeof(*frames));
49
50 if (mem == NULL || frames == NULL) {
51 free(mem);
52 free(frames);
53 return 0;
54 }
55 free(image->raw_mem);
56 image->num_frames = num_frames;
57 image->frames = frames;
58 for (i = 0; i < num_frames; ++i) {
59 frames[i].rgba = mem + i * rgba_size;
60 frames[i].duration = 0;
61 frames[i].is_key_frame = 0;
62 }
63 image->raw_mem = mem;
64 return 1;
65}
66
67void ClearAnimatedImage(AnimatedImage* const image) {
68 if (image != NULL) {
69 free(image->raw_mem);
70 free(image->frames);
71 image->num_frames = 0;
72 image->frames = NULL;
73 image->raw_mem = NULL;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070074 }
75}
76
77// Clear the canvas to transparent.
78static void ZeroFillCanvas(uint8_t* rgba,
79 uint32_t canvas_width, uint32_t canvas_height) {
80 memset(rgba, 0, canvas_width * kNumChannels * canvas_height);
81}
82
83// Clear given frame rectangle to transparent.
84static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
85 int y_offset, int width, int height) {
Pascal Massimino96201e52015-11-06 07:47:03 +010086 int j;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070087 assert(width * kNumChannels <= rgba_stride);
88 rgba += y_offset * rgba_stride + x_offset * kNumChannels;
Pascal Massimino96201e52015-11-06 07:47:03 +010089 for (j = 0; j < height; ++j) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -070090 memset(rgba, 0, width * kNumChannels);
91 rgba += rgba_stride;
92 }
93}
94
95// Copy width * height pixels from 'src' to 'dst'.
96static void CopyCanvas(const uint8_t* src, uint8_t* dst,
97 uint32_t width, uint32_t height) {
98 assert(src != NULL && dst != NULL);
99 memcpy(dst, src, width * kNumChannels * height);
100}
101
102// Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'.
103static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
104 int x_offset, int y_offset,
105 int width, int height) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100106 int j;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700107 const int width_in_bytes = width * kNumChannels;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700108 const size_t offset = y_offset * stride + x_offset * kNumChannels;
Pascal Massimino96201e52015-11-06 07:47:03 +0100109 assert(width_in_bytes <= stride);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700110 src += offset;
111 dst += offset;
Pascal Massimino96201e52015-11-06 07:47:03 +0100112 for (j = 0; j < height; ++j) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700113 memcpy(dst, src, width_in_bytes);
114 src += stride;
115 dst += stride;
116 }
117}
118
119// Canonicalize all transparent pixels to transparent black to aid comparison.
120static void CleanupTransparentPixels(uint32_t* rgba,
121 uint32_t width, uint32_t height) {
122 const uint32_t* const rgba_end = rgba + width * height;
123 while (rgba < rgba_end) {
124 const uint8_t alpha = (*rgba >> 24) & 0xff;
125 if (alpha == 0) {
126 *rgba = 0;
127 }
128 ++rgba;
129 }
130}
131
Pascal Massimino96201e52015-11-06 07:47:03 +0100132// Dump frame to a PAM file. Returns true on success.
133static int DumpFrame(const char filename[], const char dump_folder[],
134 uint32_t frame_num, const uint8_t rgba[],
135 int canvas_width, int canvas_height) {
136 int ok = 0;
137 size_t max_len;
138 int y;
139 const char* base_name = NULL;
140 char* file_name = NULL;
141 FILE* f = NULL;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700142
Pascal Massimino96201e52015-11-06 07:47:03 +0100143 base_name = strrchr(filename, '/');
144 base_name = (base_name == NULL) ? filename : base_name + 1;
145 max_len = strlen(dump_folder) + 1 + strlen(base_name)
146 + strlen("_frame_") + strlen(".pam") + 8;
147 file_name = (char*)malloc(max_len * sizeof(*file_name));
148 if (file_name == NULL) goto End;
149
150 if (snprintf(file_name, max_len, "%s/%s_frame_%d.pam",
151 dump_folder, base_name, frame_num) < 0) {
152 fprintf(stderr, "Error while generating file name\n");
153 goto End;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700154 }
155
Pascal Massimino96201e52015-11-06 07:47:03 +0100156 f = fopen(file_name, "wb");
157 if (f == NULL) {
158 fprintf(stderr, "Error opening file for writing: %s\n", file_name);
159 ok = 0;
160 goto End;
161 }
162 if (fprintf(f, "P7\nWIDTH %d\nHEIGHT %d\n"
163 "DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n",
164 canvas_width, canvas_height) < 0) {
165 fprintf(stderr, "Write error for file %s\n", file_name);
166 goto End;
167 }
168 for (y = 0; y < canvas_height; ++y) {
169 if (fwrite((const char*)(rgba) + y * canvas_width * kNumChannels,
170 canvas_width * kNumChannels, 1, f) != 1) {
171 fprintf(stderr, "Error writing to file: %s\n", file_name);
172 goto End;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700173 }
174 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100175 ok = 1;
176 End:
177 if (f != NULL) fclose(f);
178 free(file_name);
179 return ok;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700180}
181
182// -----------------------------------------------------------------------------
183// WebP Decoding.
184
185// Returns true if this is a valid WebP bitstream.
Pascal Massimino96201e52015-11-06 07:47:03 +0100186static int IsWebP(const WebPData* const webp_data) {
187 return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700188}
189
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700190// Read animated WebP bitstream 'file_str' into 'AnimatedImage' struct.
Pascal Massimino96201e52015-11-06 07:47:03 +0100191static int ReadAnimatedWebP(const char filename[],
192 const WebPData* const webp_data,
193 AnimatedImage* const image, int dump_frames,
194 const char dump_folder[]) {
195 int ok = 0;
196 int dump_ok = 1;
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700197 uint32_t frame_index = 0;
198 int prev_frame_timestamp = 0;
Pascal Massimino96201e52015-11-06 07:47:03 +0100199 WebPAnimDecoder* dec;
200 WebPAnimInfo anim_info;
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700201
Pascal Massimino96201e52015-11-06 07:47:03 +0100202 memset(image, 0, sizeof(*image));
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700203
Pascal Massimino96201e52015-11-06 07:47:03 +0100204 dec = WebPAnimDecoderNew(webp_data);
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700205 if (dec == NULL) {
206 fprintf(stderr, "Error parsing image: %s\n", filename);
207 goto End;
208 }
209
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700210 if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
211 fprintf(stderr, "Error getting global info about the animation\n");
212 goto End;
213 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700214
215 // Animation properties.
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700216 image->canvas_width = anim_info.canvas_width;
217 image->canvas_height = anim_info.canvas_height;
218 image->loop_count = anim_info.loop_count;
219 image->bgcolor = anim_info.bgcolor;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700220
221 // Allocate frames.
Pascal Massimino96201e52015-11-06 07:47:03 +0100222 if (!AllocateFrames(image, anim_info.frame_count)) return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700223
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700224 // Decode frames.
225 while (WebPAnimDecoderHasMoreFrames(dec)) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100226 DecodedFrame* curr_frame;
227 uint8_t* curr_rgba;
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700228 uint8_t* frame_rgba;
229 int timestamp;
Pascal Massimino96201e52015-11-06 07:47:03 +0100230
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700231 if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &timestamp)) {
232 fprintf(stderr, "Error decoding frame #%u\n", frame_index);
233 goto End;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700234 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100235 curr_frame = &image->frames[frame_index];
236 curr_rgba = curr_frame->rgba;
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700237 curr_frame->duration = timestamp - prev_frame_timestamp;
Pascal Massimino96201e52015-11-06 07:47:03 +0100238 curr_frame->is_key_frame = 0; // Unused.
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700239 memcpy(curr_rgba, frame_rgba,
240 image->canvas_width * kNumChannels * image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700241
242 // Needed only because we may want to compare with GIF later.
Pascal Massimino96201e52015-11-06 07:47:03 +0100243 CleanupTransparentPixels((uint32_t*)curr_rgba,
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700244 image->canvas_width, image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700245
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700246 if (dump_frames && dump_ok) {
247 dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
248 image->canvas_width, image->canvas_height);
249 if (!dump_ok) { // Print error once, but continue decode loop.
250 fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
251 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700252 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700253
254 ++frame_index;
255 prev_frame_timestamp = timestamp;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700256 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700257 ok = dump_ok;
258
259 End:
260 WebPAnimDecoderDelete(dec);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700261 return ok;
262}
263
264// -----------------------------------------------------------------------------
265// GIF Decoding.
266
267// Returns true if this is a valid GIF bitstream.
Pascal Massimino96201e52015-11-06 07:47:03 +0100268static int IsGIF(const WebPData* const data) {
269 return data->size > GIF_STAMP_LEN &&
270 (!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
271 !memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
272 !memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700273}
274
275#ifdef WEBP_HAVE_GIF
276
277// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
278#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
279# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
280# define LOCAL_GIF_PREREQ(maj, min) \
281 (LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
282#else
283# define LOCAL_GIF_VERSION 0
284# define LOCAL_GIF_PREREQ(maj, min) 0
285#endif
286
287#if !LOCAL_GIF_PREREQ(5, 0)
288
289// Added in v5.0
Pascal Massimino96201e52015-11-06 07:47:03 +0100290typedef struct {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700291 int DisposalMode;
292#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
293#define DISPOSE_DO_NOT 1 // Leave image in place
294#define DISPOSE_BACKGROUND 2 // Set area to background color
295#define DISPOSE_PREVIOUS 3 // Restore to previous content
Pascal Massimino96201e52015-11-06 07:47:03 +0100296 int UserInputFlag; // User confirmation required before disposal
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700297 int DelayTime; // Pre-display delay in 0.01sec units
298 int TransparentColor; // Palette index for transparency, -1 if none
299#define NO_TRANSPARENT_COLOR -1
300} GraphicsControlBlock;
301
302static int DGifExtensionToGCB(const size_t GifExtensionLength,
303 const GifByteType* GifExtension,
304 GraphicsControlBlock* gcb) {
305 if (GifExtensionLength != 4) {
306 return GIF_ERROR;
307 }
308 gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
309 gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
310 gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
311 if (GifExtension[0] & 0x01) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100312 gcb->TransparentColor = (int)GifExtension[3];
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700313 } else {
314 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
315 }
316 return GIF_OK;
317}
318
319static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
320 GraphicsControlBlock* gcb) {
321 int i;
322 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
323 return GIF_ERROR;
324 }
325 gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
Pascal Massimino96201e52015-11-06 07:47:03 +0100326 gcb->UserInputFlag = 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700327 gcb->DelayTime = 0;
328 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
329
330 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
331 ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
332 if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
333 return DGifExtensionToGCB(
Pascal Massimino96201e52015-11-06 07:47:03 +0100334 ep->ByteCount, (const GifByteType*)ep->Bytes, gcb);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700335 }
336 }
337 return GIF_ERROR;
338}
339
340#define CONTINUE_EXT_FUNC_CODE 0x00
341
342// Signature was changed in v5.0
343#define DGifOpenFileName(a, b) DGifOpenFileName(a)
344
345#endif // !LOCAL_GIF_PREREQ(5, 0)
346
347// Signature changed in v5.1
348#if !LOCAL_GIF_PREREQ(5, 1)
349#define DGifCloseFile(a, b) DGifCloseFile(a)
350#endif
351
352static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
353 // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
354#if LOCAL_GIF_PREREQ(4, 2)
355#if LOCAL_GIF_PREREQ(5, 0)
Pascal Massimino96201e52015-11-06 07:47:03 +0100356 const char* error_str =
357 GifErrorString((gif == NULL) ? gif_error : gif->Error);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700358#else
Pascal Massimino96201e52015-11-06 07:47:03 +0100359 const char* error_str = GifErrorString();
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700360 (void)gif;
361#endif
362 if (error_str == NULL) error_str = "Unknown error";
363 fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
364#else
365 (void)gif;
366 fprintf(stderr, "GIFLib Error %d: ", gif_error);
367 PrintGifError();
368 fprintf(stderr, "\n");
369#endif
370}
371
Pascal Massimino96201e52015-11-06 07:47:03 +0100372static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
373 const DecodedFrame* const prev_frame,
374 int canvas_width, int canvas_height) {
375 if (prev_frame == NULL) return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700376 if (prev_dispose == DISPOSE_BACKGROUND) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100377 if (IsFullFrame(prev_desc->Width, prev_desc->Height,
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700378 canvas_width, canvas_height)) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100379 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700380 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100381 if (prev_frame->is_key_frame) return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700382 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100383 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700384}
385
386static int GetTransparentIndexGIF(GifFileType* gif) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100387 GraphicsControlBlock first_gcb;
388 memset(&first_gcb, 0, sizeof(first_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700389 DGifSavedExtensionToGCB(gif, 0, &first_gcb);
390 return first_gcb.TransparentColor;
391}
392
393static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
394 const int transparent_index = GetTransparentIndexGIF(gif);
395 const ColorMapObject* const color_map = gif->SColorMap;
396 if (transparent_index != NO_TRANSPARENT_COLOR &&
397 gif->SBackGroundColor == transparent_index) {
398 return 0x00ffffff; // Special case: transparent white.
399 } else if (color_map == NULL || color_map->Colors == NULL
400 || gif->SBackGroundColor >= color_map->ColorCount) {
401 return 0xffffffff; // Invalid: assume white.
402 } else {
403 const GifColorType color = color_map->Colors[gif->SBackGroundColor];
404 return (0xff << 24) |
405 (color.Red << 16) |
406 (color.Green << 8) |
407 (color.Blue << 0);
408 }
409}
410
411// Find appropriate app extension and get loop count from the next extension.
412static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100413 int i;
414 for (i = 0; i < gif->ImageCount; ++i) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700415 const SavedImage* const image = &gif->SavedImages[i];
Pascal Massimino96201e52015-11-06 07:47:03 +0100416 int j;
417 for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700418 const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
419 const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
Pascal Massimino96201e52015-11-06 07:47:03 +0100420 const char* const signature = (const char*)eb1->Bytes;
421 const int signature_is_ok =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700422 (eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
423 (eb1->ByteCount == 11) &&
424 (!memcmp(signature, "NETSCAPE2.0", 11) ||
425 !memcmp(signature, "ANIMEXTS1.0", 11));
426 if (signature_is_ok &&
427 eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
428 eb2->Bytes[0] == 1) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100429 return ((uint32_t)(eb2->Bytes[2]) << 8) +
430 ((uint32_t)(eb2->Bytes[1]) << 0);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700431 }
432 }
433 }
434 return 0; // Default.
435}
436
437// Get duration of 'n'th frame in milliseconds.
438static int GetFrameDurationGIF(GifFileType* gif, int n) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100439 GraphicsControlBlock gcb;
440 memset(&gcb, 0, sizeof(gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700441 DGifSavedExtensionToGCB(gif, n, &gcb);
442 return gcb.DelayTime * 10;
443}
444
445// Returns true if frame 'target' completely covers 'covered'.
Pascal Massimino96201e52015-11-06 07:47:03 +0100446static int CoversFrameGIF(const GifImageDesc* const target,
447 const GifImageDesc* const covered) {
448 return target->Left <= covered->Left &&
449 covered->Left + covered->Width <= target->Left + target->Width &&
450 target->Top <= covered->Top &&
451 covered->Top + covered->Height <= target->Top + target->Height;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700452}
453
454static void RemapPixelsGIF(const uint8_t* const src,
455 const ColorMapObject* const cmap,
456 int transparent_color, int len, uint8_t* dst) {
457 int i;
458 for (i = 0; i < len; ++i) {
459 if (src[i] != transparent_color) {
460 // If a pixel in the current frame is transparent, we don't modify it, so
461 // that we can see-through the corresponding pixel from an earlier frame.
462 const GifColorType c = cmap->Colors[src[i]];
463 dst[4 * i + 0] = c.Red;
464 dst[4 * i + 1] = c.Green;
465 dst[4 * i + 2] = c.Blue;
466 dst[4 * i + 3] = 0xff;
467 }
468 }
469}
470
Pascal Massimino96201e52015-11-06 07:47:03 +0100471static int ReadFrameGIF(const SavedImage* const gif_image,
472 const ColorMapObject* cmap, int transparent_color,
473 int out_stride, uint8_t* const dst) {
474 const GifImageDesc* image_desc = &gif_image->ImageDesc;
475 const uint8_t* in;
476 uint8_t* out;
477 int j;
478
479 if (image_desc->ColorMap) cmap = image_desc->ColorMap;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700480
481 if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100482 fprintf(stderr, "Potentially corrupt color map.\n");
483 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700484 }
485
Pascal Massimino96201e52015-11-06 07:47:03 +0100486 in = (const uint8_t*)gif_image->RasterBits;
487 out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700488
Pascal Massimino96201e52015-11-06 07:47:03 +0100489 for (j = 0; j < image_desc->Height; ++j) {
490 RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out);
491 in += image_desc->Width;
492 out += out_stride;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700493 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100494 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700495}
496
497// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
Pascal Massimino96201e52015-11-06 07:47:03 +0100498static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
499 int dump_frames, const char dump_folder[]) {
500 uint32_t frame_count;
501 uint32_t canvas_width, canvas_height;
502 uint32_t i;
503 int gif_error;
504 GifFileType* gif;
505
506 gif = DGifOpenFileName(filename, NULL);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700507 if (gif == NULL) {
508 fprintf(stderr, "Could not read file: %s.\n", filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100509 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700510 }
511
Pascal Massimino96201e52015-11-06 07:47:03 +0100512 gif_error = DGifSlurp(gif);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700513 if (gif_error != GIF_OK) {
514 fprintf(stderr, "Could not parse image: %s.\n", filename);
515 GIFDisplayError(gif, gif_error);
516 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100517 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700518 }
519
520 // Animation properties.
Pascal Massimino96201e52015-11-06 07:47:03 +0100521 image->canvas_width = (uint32_t)gif->SWidth;
522 image->canvas_height = (uint32_t)gif->SHeight;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700523 if (image->canvas_width > MAX_CANVAS_SIZE ||
524 image->canvas_height > MAX_CANVAS_SIZE) {
525 fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
526 image->canvas_width, image->canvas_height);
527 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100528 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700529 }
530 image->loop_count = GetLoopCountGIF(gif);
531 image->bgcolor = GetBackgroundColorGIF(gif);
532
Pascal Massimino96201e52015-11-06 07:47:03 +0100533 frame_count = (uint32_t)gif->ImageCount;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700534 if (frame_count == 0) {
535 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100536 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700537 }
538
539 if (image->canvas_width == 0 || image->canvas_height == 0) {
540 image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
541 image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
542 gif->SavedImages[0].ImageDesc.Left = 0;
543 gif->SavedImages[0].ImageDesc.Top = 0;
544 if (image->canvas_width == 0 || image->canvas_height == 0) {
545 fprintf(stderr, "Invalid canvas size in GIF.\n");
546 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100547 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700548 }
549 }
550 // Allocate frames.
551 AllocateFrames(image, frame_count);
552
Pascal Massimino96201e52015-11-06 07:47:03 +0100553 canvas_width = image->canvas_width;
554 canvas_height = image->canvas_height;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700555
556 // Decode and reconstruct frames.
Pascal Massimino96201e52015-11-06 07:47:03 +0100557 for (i = 0; i < frame_count; ++i) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700558 const int canvas_width_in_bytes = canvas_width * kNumChannels;
559 const SavedImage* const curr_gif_image = &gif->SavedImages[i];
Pascal Massimino96201e52015-11-06 07:47:03 +0100560 GraphicsControlBlock curr_gcb;
561 DecodedFrame* curr_frame;
562 uint8_t* curr_rgba;
563
564 memset(&curr_gcb, 0, sizeof(curr_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700565 DGifSavedExtensionToGCB(gif, i, &curr_gcb);
566
Pascal Massimino96201e52015-11-06 07:47:03 +0100567 curr_frame = &image->frames[i];
568 curr_rgba = curr_frame->rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700569 curr_frame->duration = GetFrameDurationGIF(gif, i);
570
571 if (i == 0) { // Initialize as transparent.
Pascal Massimino96201e52015-11-06 07:47:03 +0100572 curr_frame->is_key_frame = 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700573 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
574 } else {
575 DecodedFrame* const prev_frame = &image->frames[i - 1];
Pascal Massimino96201e52015-11-06 07:47:03 +0100576 const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
577 GraphicsControlBlock prev_gcb;
578 memset(&prev_gcb, 0, sizeof(prev_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700579 DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
580
581 curr_frame->is_key_frame =
582 IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
583 canvas_width, canvas_height);
584
585 if (curr_frame->is_key_frame) { // Initialize as transparent.
586 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
587 } else {
Pascal Massimino96201e52015-11-06 07:47:03 +0100588 int prev_frame_disposed, curr_frame_opaque;
589 int prev_frame_completely_covered;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700590 // Initialize with previous canvas.
Pascal Massimino96201e52015-11-06 07:47:03 +0100591 uint8_t* const prev_rgba = image->frames[i - 1].rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700592 CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
593
594 // Dispose previous frame rectangle.
Pascal Massimino96201e52015-11-06 07:47:03 +0100595 prev_frame_disposed =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700596 (prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
597 prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
Pascal Massimino96201e52015-11-06 07:47:03 +0100598 curr_frame_opaque =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700599 (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
Pascal Massimino96201e52015-11-06 07:47:03 +0100600 prev_frame_completely_covered =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700601 curr_frame_opaque &&
Pascal Massimino96201e52015-11-06 07:47:03 +0100602 CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700603
604 if (prev_frame_disposed && !prev_frame_completely_covered) {
605 switch (prev_gcb.DisposalMode) {
606 case DISPOSE_BACKGROUND: {
607 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100608 prev_desc->Left, prev_desc->Top,
609 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700610 break;
611 }
612 case DISPOSE_PREVIOUS: {
613 int src_frame_num = i - 2;
614 while (src_frame_num >= 0) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100615 GraphicsControlBlock src_frame_gcb;
616 memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700617 DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
618 if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
619 --src_frame_num;
620 }
621 if (src_frame_num >= 0) {
622 // Restore pixels inside previous frame rectangle to
623 // corresponding pixels in source canvas.
624 uint8_t* const src_frame_rgba =
Pascal Massimino96201e52015-11-06 07:47:03 +0100625 image->frames[src_frame_num].rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700626 CopyFrameRectangle(src_frame_rgba, curr_rgba,
627 canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100628 prev_desc->Left, prev_desc->Top,
629 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700630 } else {
631 // Source canvas doesn't exist. So clear previous frame
632 // rectangle to background.
633 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100634 prev_desc->Left, prev_desc->Top,
635 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700636 }
637 break;
638 }
639 default:
640 break; // Nothing to do.
641 }
642 }
643 }
644 }
645
646 // Decode current frame.
647 if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
648 canvas_width_in_bytes, curr_rgba)) {
649 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100650 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700651 }
652
653 if (dump_frames) {
654 if (!DumpFrame(filename, dump_folder, i, curr_rgba,
655 canvas_width, canvas_height)) {
656 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100657 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700658 }
659 }
660 }
661 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100662 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700663}
664
665#else
666
Pascal Massimino96201e52015-11-06 07:47:03 +0100667static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
668 int dump_frames, const char dump_folder[]) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700669 (void)filename;
670 (void)image;
671 (void)dump_frames;
672 (void)dump_folder;
673 fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
674 "package before building.\n");
Pascal Massimino96201e52015-11-06 07:47:03 +0100675 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700676}
677
678#endif // WEBP_HAVE_GIF
679
680// -----------------------------------------------------------------------------
681
Pascal Massimino96201e52015-11-06 07:47:03 +0100682int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
683 int dump_frames, const char dump_folder[]) {
684 int ok = 0;
685 WebPData webp_data;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700686
Pascal Massimino96201e52015-11-06 07:47:03 +0100687 WebPDataInit(&webp_data);
688 memset(image, 0, sizeof(*image));
689
690 if (!ExUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700691 fprintf(stderr, "Error reading file: %s\n", filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100692 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700693 }
694
Pascal Massimino96201e52015-11-06 07:47:03 +0100695 if (IsWebP(&webp_data)) {
696 ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames,
697 dump_folder);
698 } else if (IsGIF(&webp_data)) {
699 ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700700 } else {
701 fprintf(stderr,
702 "Unknown file type: %s. Supported file types are WebP and GIF\n",
703 filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100704 ok = 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700705 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100706 if (!ok) ClearAnimatedImage(image);
707 WebPDataClear(&webp_data);
708 return ok;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700709}
710
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000711static void Accumulate(double v1, double v2, double* const max_diff,
712 double* const sse) {
713 const double diff = fabs(v1 - v2);
714 if (diff > *max_diff) *max_diff = diff;
715 *sse += diff * diff;
716}
717
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700718void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
Pascal Massimino96201e52015-11-06 07:47:03 +0100719 uint32_t width, uint32_t height, int premultiply,
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000720 int* const max_diff, double* const psnr) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700721 const uint32_t stride = width * kNumChannels;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000722 const int kAlphaChannel = kNumChannels - 1;
723 double f_max_diff = 0.;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700724 double sse = 0.;
Pascal Massimino96201e52015-11-06 07:47:03 +0100725 uint32_t x, y;
726 for (y = 0; y < height; ++y) {
727 for (x = 0; x < stride; x += kNumChannels) {
728 int k;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700729 const size_t offset = y * stride + x;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000730 const int alpha1 = rgba1[offset + kAlphaChannel];
731 const int alpha2 = rgba2[offset + kAlphaChannel];
732 Accumulate(alpha1, alpha2, &f_max_diff, &sse);
733 if (!premultiply) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100734 for (k = 0; k < kAlphaChannel; ++k) {
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000735 Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
736 }
737 } else {
738 // premultiply R/G/B channels with alpha value
Pascal Massimino96201e52015-11-06 07:47:03 +0100739 for (k = 0; k < kAlphaChannel; ++k) {
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000740 Accumulate(rgba1[offset + k] * alpha1 / 255.,
741 rgba2[offset + k] * alpha2 / 255.,
742 &f_max_diff, &sse);
743 }
744 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700745 }
746 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100747 *max_diff = (int)f_max_diff;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700748 if (*max_diff == 0) {
749 *psnr = 99.; // PSNR when images are identical.
750 } else {
751 sse /= stride * height;
Pascal Massimino96201e52015-11-06 07:47:03 +0100752 *psnr = 4.3429448 * log(255. * 255. / sse);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700753 }
754}