blob: 417fec2f1ad989f3de14be68e1cd9d3b5042f883 [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
Urvang Joshi3584abc2015-11-10 09:27:59 -0800204 dec = WebPAnimDecoderNew(webp_data, NULL);
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 }
James Zern82006432016-02-23 11:13:38 -0800235 assert(frame_index < anim_info.frame_count);
Pascal Massimino96201e52015-11-06 07:47:03 +0100236 curr_frame = &image->frames[frame_index];
237 curr_rgba = curr_frame->rgba;
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700238 curr_frame->duration = timestamp - prev_frame_timestamp;
Pascal Massimino96201e52015-11-06 07:47:03 +0100239 curr_frame->is_key_frame = 0; // Unused.
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700240 memcpy(curr_rgba, frame_rgba,
241 image->canvas_width * kNumChannels * image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700242
243 // Needed only because we may want to compare with GIF later.
Pascal Massimino96201e52015-11-06 07:47:03 +0100244 CleanupTransparentPixels((uint32_t*)curr_rgba,
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700245 image->canvas_width, image->canvas_height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700246
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700247 if (dump_frames && dump_ok) {
248 dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
249 image->canvas_width, image->canvas_height);
250 if (!dump_ok) { // Print error once, but continue decode loop.
251 fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
252 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700253 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700254
255 ++frame_index;
256 prev_frame_timestamp = timestamp;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700257 }
Urvang Joshid39dc8f2015-09-02 16:20:24 -0700258 ok = dump_ok;
259
260 End:
261 WebPAnimDecoderDelete(dec);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700262 return ok;
263}
264
265// -----------------------------------------------------------------------------
266// GIF Decoding.
267
268// Returns true if this is a valid GIF bitstream.
Pascal Massimino96201e52015-11-06 07:47:03 +0100269static int IsGIF(const WebPData* const data) {
270 return data->size > GIF_STAMP_LEN &&
271 (!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
272 !memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
273 !memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700274}
275
276#ifdef WEBP_HAVE_GIF
277
278// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
279#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
280# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
281# define LOCAL_GIF_PREREQ(maj, min) \
282 (LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
283#else
284# define LOCAL_GIF_VERSION 0
285# define LOCAL_GIF_PREREQ(maj, min) 0
286#endif
287
288#if !LOCAL_GIF_PREREQ(5, 0)
289
290// Added in v5.0
Pascal Massimino96201e52015-11-06 07:47:03 +0100291typedef struct {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700292 int DisposalMode;
293#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
294#define DISPOSE_DO_NOT 1 // Leave image in place
295#define DISPOSE_BACKGROUND 2 // Set area to background color
296#define DISPOSE_PREVIOUS 3 // Restore to previous content
Pascal Massimino96201e52015-11-06 07:47:03 +0100297 int UserInputFlag; // User confirmation required before disposal
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700298 int DelayTime; // Pre-display delay in 0.01sec units
299 int TransparentColor; // Palette index for transparency, -1 if none
300#define NO_TRANSPARENT_COLOR -1
301} GraphicsControlBlock;
302
303static int DGifExtensionToGCB(const size_t GifExtensionLength,
304 const GifByteType* GifExtension,
305 GraphicsControlBlock* gcb) {
306 if (GifExtensionLength != 4) {
307 return GIF_ERROR;
308 }
309 gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
310 gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
311 gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
312 if (GifExtension[0] & 0x01) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100313 gcb->TransparentColor = (int)GifExtension[3];
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700314 } else {
315 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
316 }
317 return GIF_OK;
318}
319
320static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
321 GraphicsControlBlock* gcb) {
322 int i;
323 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
324 return GIF_ERROR;
325 }
326 gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
Pascal Massimino96201e52015-11-06 07:47:03 +0100327 gcb->UserInputFlag = 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700328 gcb->DelayTime = 0;
329 gcb->TransparentColor = NO_TRANSPARENT_COLOR;
330
331 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
332 ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
333 if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
334 return DGifExtensionToGCB(
Pascal Massimino96201e52015-11-06 07:47:03 +0100335 ep->ByteCount, (const GifByteType*)ep->Bytes, gcb);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700336 }
337 }
338 return GIF_ERROR;
339}
340
341#define CONTINUE_EXT_FUNC_CODE 0x00
342
343// Signature was changed in v5.0
344#define DGifOpenFileName(a, b) DGifOpenFileName(a)
345
346#endif // !LOCAL_GIF_PREREQ(5, 0)
347
348// Signature changed in v5.1
349#if !LOCAL_GIF_PREREQ(5, 1)
350#define DGifCloseFile(a, b) DGifCloseFile(a)
351#endif
352
353static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
354 // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
355#if LOCAL_GIF_PREREQ(4, 2)
356#if LOCAL_GIF_PREREQ(5, 0)
Pascal Massimino96201e52015-11-06 07:47:03 +0100357 const char* error_str =
358 GifErrorString((gif == NULL) ? gif_error : gif->Error);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700359#else
Pascal Massimino96201e52015-11-06 07:47:03 +0100360 const char* error_str = GifErrorString();
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700361 (void)gif;
362#endif
363 if (error_str == NULL) error_str = "Unknown error";
364 fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
365#else
366 (void)gif;
367 fprintf(stderr, "GIFLib Error %d: ", gif_error);
368 PrintGifError();
369 fprintf(stderr, "\n");
370#endif
371}
372
Pascal Massimino96201e52015-11-06 07:47:03 +0100373static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
374 const DecodedFrame* const prev_frame,
375 int canvas_width, int canvas_height) {
376 if (prev_frame == NULL) return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700377 if (prev_dispose == DISPOSE_BACKGROUND) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100378 if (IsFullFrame(prev_desc->Width, prev_desc->Height,
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700379 canvas_width, canvas_height)) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100380 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700381 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100382 if (prev_frame->is_key_frame) return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700383 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100384 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700385}
386
387static int GetTransparentIndexGIF(GifFileType* gif) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100388 GraphicsControlBlock first_gcb;
389 memset(&first_gcb, 0, sizeof(first_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700390 DGifSavedExtensionToGCB(gif, 0, &first_gcb);
391 return first_gcb.TransparentColor;
392}
393
394static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
395 const int transparent_index = GetTransparentIndexGIF(gif);
396 const ColorMapObject* const color_map = gif->SColorMap;
397 if (transparent_index != NO_TRANSPARENT_COLOR &&
398 gif->SBackGroundColor == transparent_index) {
399 return 0x00ffffff; // Special case: transparent white.
400 } else if (color_map == NULL || color_map->Colors == NULL
401 || gif->SBackGroundColor >= color_map->ColorCount) {
402 return 0xffffffff; // Invalid: assume white.
403 } else {
404 const GifColorType color = color_map->Colors[gif->SBackGroundColor];
405 return (0xff << 24) |
406 (color.Red << 16) |
407 (color.Green << 8) |
408 (color.Blue << 0);
409 }
410}
411
412// Find appropriate app extension and get loop count from the next extension.
413static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100414 int i;
415 for (i = 0; i < gif->ImageCount; ++i) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700416 const SavedImage* const image = &gif->SavedImages[i];
Pascal Massimino96201e52015-11-06 07:47:03 +0100417 int j;
418 for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700419 const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
420 const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
Pascal Massimino96201e52015-11-06 07:47:03 +0100421 const char* const signature = (const char*)eb1->Bytes;
422 const int signature_is_ok =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700423 (eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
424 (eb1->ByteCount == 11) &&
425 (!memcmp(signature, "NETSCAPE2.0", 11) ||
426 !memcmp(signature, "ANIMEXTS1.0", 11));
427 if (signature_is_ok &&
428 eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
429 eb2->Bytes[0] == 1) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100430 return ((uint32_t)(eb2->Bytes[2]) << 8) +
431 ((uint32_t)(eb2->Bytes[1]) << 0);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700432 }
433 }
434 }
435 return 0; // Default.
436}
437
438// Get duration of 'n'th frame in milliseconds.
439static int GetFrameDurationGIF(GifFileType* gif, int n) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100440 GraphicsControlBlock gcb;
441 memset(&gcb, 0, sizeof(gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700442 DGifSavedExtensionToGCB(gif, n, &gcb);
443 return gcb.DelayTime * 10;
444}
445
446// Returns true if frame 'target' completely covers 'covered'.
Pascal Massimino96201e52015-11-06 07:47:03 +0100447static int CoversFrameGIF(const GifImageDesc* const target,
448 const GifImageDesc* const covered) {
449 return target->Left <= covered->Left &&
450 covered->Left + covered->Width <= target->Left + target->Width &&
451 target->Top <= covered->Top &&
452 covered->Top + covered->Height <= target->Top + target->Height;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700453}
454
455static void RemapPixelsGIF(const uint8_t* const src,
456 const ColorMapObject* const cmap,
457 int transparent_color, int len, uint8_t* dst) {
458 int i;
459 for (i = 0; i < len; ++i) {
460 if (src[i] != transparent_color) {
461 // If a pixel in the current frame is transparent, we don't modify it, so
462 // that we can see-through the corresponding pixel from an earlier frame.
463 const GifColorType c = cmap->Colors[src[i]];
464 dst[4 * i + 0] = c.Red;
465 dst[4 * i + 1] = c.Green;
466 dst[4 * i + 2] = c.Blue;
467 dst[4 * i + 3] = 0xff;
468 }
469 }
470}
471
Pascal Massimino96201e52015-11-06 07:47:03 +0100472static int ReadFrameGIF(const SavedImage* const gif_image,
473 const ColorMapObject* cmap, int transparent_color,
474 int out_stride, uint8_t* const dst) {
475 const GifImageDesc* image_desc = &gif_image->ImageDesc;
476 const uint8_t* in;
477 uint8_t* out;
478 int j;
479
480 if (image_desc->ColorMap) cmap = image_desc->ColorMap;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700481
482 if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100483 fprintf(stderr, "Potentially corrupt color map.\n");
484 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700485 }
486
Pascal Massimino96201e52015-11-06 07:47:03 +0100487 in = (const uint8_t*)gif_image->RasterBits;
488 out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700489
Pascal Massimino96201e52015-11-06 07:47:03 +0100490 for (j = 0; j < image_desc->Height; ++j) {
491 RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out);
492 in += image_desc->Width;
493 out += out_stride;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700494 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100495 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700496}
497
498// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
Pascal Massimino96201e52015-11-06 07:47:03 +0100499static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
500 int dump_frames, const char dump_folder[]) {
501 uint32_t frame_count;
502 uint32_t canvas_width, canvas_height;
503 uint32_t i;
504 int gif_error;
505 GifFileType* gif;
506
507 gif = DGifOpenFileName(filename, NULL);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700508 if (gif == NULL) {
509 fprintf(stderr, "Could not read file: %s.\n", filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100510 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700511 }
512
Pascal Massimino96201e52015-11-06 07:47:03 +0100513 gif_error = DGifSlurp(gif);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700514 if (gif_error != GIF_OK) {
515 fprintf(stderr, "Could not parse image: %s.\n", filename);
516 GIFDisplayError(gif, gif_error);
517 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100518 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700519 }
520
521 // Animation properties.
Pascal Massimino96201e52015-11-06 07:47:03 +0100522 image->canvas_width = (uint32_t)gif->SWidth;
523 image->canvas_height = (uint32_t)gif->SHeight;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700524 if (image->canvas_width > MAX_CANVAS_SIZE ||
525 image->canvas_height > MAX_CANVAS_SIZE) {
526 fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
527 image->canvas_width, image->canvas_height);
528 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100529 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700530 }
531 image->loop_count = GetLoopCountGIF(gif);
532 image->bgcolor = GetBackgroundColorGIF(gif);
533
Pascal Massimino96201e52015-11-06 07:47:03 +0100534 frame_count = (uint32_t)gif->ImageCount;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700535 if (frame_count == 0) {
536 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100537 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700538 }
539
540 if (image->canvas_width == 0 || image->canvas_height == 0) {
541 image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
542 image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
543 gif->SavedImages[0].ImageDesc.Left = 0;
544 gif->SavedImages[0].ImageDesc.Top = 0;
545 if (image->canvas_width == 0 || image->canvas_height == 0) {
546 fprintf(stderr, "Invalid canvas size in GIF.\n");
547 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100548 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700549 }
550 }
551 // Allocate frames.
552 AllocateFrames(image, frame_count);
553
Pascal Massimino96201e52015-11-06 07:47:03 +0100554 canvas_width = image->canvas_width;
555 canvas_height = image->canvas_height;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700556
557 // Decode and reconstruct frames.
Pascal Massimino96201e52015-11-06 07:47:03 +0100558 for (i = 0; i < frame_count; ++i) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700559 const int canvas_width_in_bytes = canvas_width * kNumChannels;
560 const SavedImage* const curr_gif_image = &gif->SavedImages[i];
Pascal Massimino96201e52015-11-06 07:47:03 +0100561 GraphicsControlBlock curr_gcb;
562 DecodedFrame* curr_frame;
563 uint8_t* curr_rgba;
564
565 memset(&curr_gcb, 0, sizeof(curr_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700566 DGifSavedExtensionToGCB(gif, i, &curr_gcb);
567
Pascal Massimino96201e52015-11-06 07:47:03 +0100568 curr_frame = &image->frames[i];
569 curr_rgba = curr_frame->rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700570 curr_frame->duration = GetFrameDurationGIF(gif, i);
571
572 if (i == 0) { // Initialize as transparent.
Pascal Massimino96201e52015-11-06 07:47:03 +0100573 curr_frame->is_key_frame = 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700574 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
575 } else {
576 DecodedFrame* const prev_frame = &image->frames[i - 1];
Pascal Massimino96201e52015-11-06 07:47:03 +0100577 const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
578 GraphicsControlBlock prev_gcb;
579 memset(&prev_gcb, 0, sizeof(prev_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700580 DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
581
582 curr_frame->is_key_frame =
583 IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
584 canvas_width, canvas_height);
585
586 if (curr_frame->is_key_frame) { // Initialize as transparent.
587 ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
588 } else {
Pascal Massimino96201e52015-11-06 07:47:03 +0100589 int prev_frame_disposed, curr_frame_opaque;
590 int prev_frame_completely_covered;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700591 // Initialize with previous canvas.
Pascal Massimino96201e52015-11-06 07:47:03 +0100592 uint8_t* const prev_rgba = image->frames[i - 1].rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700593 CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
594
595 // Dispose previous frame rectangle.
Pascal Massimino96201e52015-11-06 07:47:03 +0100596 prev_frame_disposed =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700597 (prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
598 prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
Pascal Massimino96201e52015-11-06 07:47:03 +0100599 curr_frame_opaque =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700600 (curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
Pascal Massimino96201e52015-11-06 07:47:03 +0100601 prev_frame_completely_covered =
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700602 curr_frame_opaque &&
Pascal Massimino96201e52015-11-06 07:47:03 +0100603 CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700604
605 if (prev_frame_disposed && !prev_frame_completely_covered) {
606 switch (prev_gcb.DisposalMode) {
607 case DISPOSE_BACKGROUND: {
608 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100609 prev_desc->Left, prev_desc->Top,
610 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700611 break;
612 }
613 case DISPOSE_PREVIOUS: {
614 int src_frame_num = i - 2;
615 while (src_frame_num >= 0) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100616 GraphicsControlBlock src_frame_gcb;
617 memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700618 DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
619 if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
620 --src_frame_num;
621 }
622 if (src_frame_num >= 0) {
623 // Restore pixels inside previous frame rectangle to
624 // corresponding pixels in source canvas.
625 uint8_t* const src_frame_rgba =
Pascal Massimino96201e52015-11-06 07:47:03 +0100626 image->frames[src_frame_num].rgba;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700627 CopyFrameRectangle(src_frame_rgba, curr_rgba,
628 canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100629 prev_desc->Left, prev_desc->Top,
630 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700631 } else {
632 // Source canvas doesn't exist. So clear previous frame
633 // rectangle to background.
634 ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
Pascal Massimino96201e52015-11-06 07:47:03 +0100635 prev_desc->Left, prev_desc->Top,
636 prev_desc->Width, prev_desc->Height);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700637 }
638 break;
639 }
640 default:
641 break; // Nothing to do.
642 }
643 }
644 }
645 }
646
647 // Decode current frame.
648 if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
649 canvas_width_in_bytes, curr_rgba)) {
650 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100651 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700652 }
653
654 if (dump_frames) {
655 if (!DumpFrame(filename, dump_folder, i, curr_rgba,
656 canvas_width, canvas_height)) {
657 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100658 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700659 }
660 }
661 }
662 DGifCloseFile(gif, NULL);
Pascal Massimino96201e52015-11-06 07:47:03 +0100663 return 1;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700664}
665
666#else
667
Pascal Massimino96201e52015-11-06 07:47:03 +0100668static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
669 int dump_frames, const char dump_folder[]) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700670 (void)filename;
671 (void)image;
672 (void)dump_frames;
673 (void)dump_folder;
674 fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
675 "package before building.\n");
Pascal Massimino96201e52015-11-06 07:47:03 +0100676 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700677}
678
679#endif // WEBP_HAVE_GIF
680
681// -----------------------------------------------------------------------------
682
Pascal Massimino96201e52015-11-06 07:47:03 +0100683int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
684 int dump_frames, const char dump_folder[]) {
685 int ok = 0;
686 WebPData webp_data;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700687
Pascal Massimino96201e52015-11-06 07:47:03 +0100688 WebPDataInit(&webp_data);
689 memset(image, 0, sizeof(*image));
690
691 if (!ExUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700692 fprintf(stderr, "Error reading file: %s\n", filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100693 return 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700694 }
695
Pascal Massimino96201e52015-11-06 07:47:03 +0100696 if (IsWebP(&webp_data)) {
697 ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames,
698 dump_folder);
699 } else if (IsGIF(&webp_data)) {
700 ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700701 } else {
702 fprintf(stderr,
703 "Unknown file type: %s. Supported file types are WebP and GIF\n",
704 filename);
Pascal Massimino96201e52015-11-06 07:47:03 +0100705 ok = 0;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700706 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100707 if (!ok) ClearAnimatedImage(image);
708 WebPDataClear(&webp_data);
709 return ok;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700710}
711
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000712static void Accumulate(double v1, double v2, double* const max_diff,
713 double* const sse) {
714 const double diff = fabs(v1 - v2);
715 if (diff > *max_diff) *max_diff = diff;
716 *sse += diff * diff;
717}
718
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700719void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
Pascal Massimino96201e52015-11-06 07:47:03 +0100720 uint32_t width, uint32_t height, int premultiply,
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000721 int* const max_diff, double* const psnr) {
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700722 const uint32_t stride = width * kNumChannels;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000723 const int kAlphaChannel = kNumChannels - 1;
724 double f_max_diff = 0.;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700725 double sse = 0.;
Pascal Massimino96201e52015-11-06 07:47:03 +0100726 uint32_t x, y;
727 for (y = 0; y < height; ++y) {
728 for (x = 0; x < stride; x += kNumChannels) {
729 int k;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700730 const size_t offset = y * stride + x;
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000731 const int alpha1 = rgba1[offset + kAlphaChannel];
732 const int alpha2 = rgba2[offset + kAlphaChannel];
733 Accumulate(alpha1, alpha2, &f_max_diff, &sse);
734 if (!premultiply) {
Pascal Massimino96201e52015-11-06 07:47:03 +0100735 for (k = 0; k < kAlphaChannel; ++k) {
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000736 Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
737 }
738 } else {
739 // premultiply R/G/B channels with alpha value
Pascal Massimino96201e52015-11-06 07:47:03 +0100740 for (k = 0; k < kAlphaChannel; ++k) {
Pascal Massiminoacb297e2015-07-07 22:45:49 +0000741 Accumulate(rgba1[offset + k] * alpha1 / 255.,
742 rgba2[offset + k] * alpha2 / 255.,
743 &f_max_diff, &sse);
744 }
745 }
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700746 }
747 }
Pascal Massimino96201e52015-11-06 07:47:03 +0100748 *max_diff = (int)f_max_diff;
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700749 if (*max_diff == 0) {
750 *psnr = 99.; // PSNR when images are identical.
751 } else {
752 sse /= stride * height;
Pascal Massimino96201e52015-11-06 07:47:03 +0100753 *psnr = 4.3429448 * log(255. * 255. / sse);
Urvang Joshiacd7b5a2015-05-01 16:11:49 -0700754 }
755}