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