1*b2055c35SXin Li // Copyright 2012 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 // simple tool to convert animated GIFs to WebP
11*b2055c35SXin Li //
12*b2055c35SXin Li // Authors: Skal ([email protected])
13*b2055c35SXin Li // Urvang ([email protected])
14*b2055c35SXin Li
15*b2055c35SXin Li #include <assert.h>
16*b2055c35SXin Li #include <stdio.h>
17*b2055c35SXin Li #include <stdlib.h>
18*b2055c35SXin Li #include <string.h>
19*b2055c35SXin Li
20*b2055c35SXin Li #ifdef HAVE_CONFIG_H
21*b2055c35SXin Li #include "webp/config.h"
22*b2055c35SXin Li #endif
23*b2055c35SXin Li
24*b2055c35SXin Li #ifdef WEBP_HAVE_GIF
25*b2055c35SXin Li
26*b2055c35SXin Li #if defined(HAVE_UNISTD_H) && HAVE_UNISTD_H
27*b2055c35SXin Li #include <unistd.h>
28*b2055c35SXin Li #endif
29*b2055c35SXin Li
30*b2055c35SXin Li #include <gif_lib.h>
31*b2055c35SXin Li #include "webp/encode.h"
32*b2055c35SXin Li #include "webp/mux.h"
33*b2055c35SXin Li #include "../examples/example_util.h"
34*b2055c35SXin Li #include "../imageio/imageio_util.h"
35*b2055c35SXin Li #include "./gifdec.h"
36*b2055c35SXin Li #include "./unicode.h"
37*b2055c35SXin Li #include "./unicode_gif.h"
38*b2055c35SXin Li
39*b2055c35SXin Li #if !defined(STDIN_FILENO)
40*b2055c35SXin Li #define STDIN_FILENO 0
41*b2055c35SXin Li #endif
42*b2055c35SXin Li
43*b2055c35SXin Li //------------------------------------------------------------------------------
44*b2055c35SXin Li
45*b2055c35SXin Li static int transparent_index = GIF_INDEX_INVALID; // Opaque by default.
46*b2055c35SXin Li
47*b2055c35SXin Li static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
48*b2055c35SXin Li "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
49*b2055c35SXin Li "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
50*b2055c35SXin Li };
51*b2055c35SXin Li
ErrorString(WebPMuxError err)52*b2055c35SXin Li static const char* ErrorString(WebPMuxError err) {
53*b2055c35SXin Li assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
54*b2055c35SXin Li return kErrorMessages[-err];
55*b2055c35SXin Li }
56*b2055c35SXin Li
57*b2055c35SXin Li enum {
58*b2055c35SXin Li METADATA_ICC = (1 << 0),
59*b2055c35SXin Li METADATA_XMP = (1 << 1),
60*b2055c35SXin Li METADATA_ALL = METADATA_ICC | METADATA_XMP
61*b2055c35SXin Li };
62*b2055c35SXin Li
63*b2055c35SXin Li //------------------------------------------------------------------------------
64*b2055c35SXin Li
Help(void)65*b2055c35SXin Li static void Help(void) {
66*b2055c35SXin Li printf("Usage:\n");
67*b2055c35SXin Li printf(" gif2webp [options] gif_file -o webp_file\n");
68*b2055c35SXin Li printf("Options:\n");
69*b2055c35SXin Li printf(" -h / -help ............. this help\n");
70*b2055c35SXin Li printf(" -lossy ................. encode image using lossy compression\n");
71*b2055c35SXin Li printf(" -mixed ................. for each frame in the image, pick lossy\n"
72*b2055c35SXin Li " or lossless compression heuristically\n");
73*b2055c35SXin Li printf(" -q <float> ............. quality factor (0:small..100:big)\n");
74*b2055c35SXin Li printf(" -m <int> ............... compression method (0=fast, 6=slowest)\n");
75*b2055c35SXin Li printf(" -min_size .............. minimize output size (default:off)\n"
76*b2055c35SXin Li " lossless compression by default; can be\n"
77*b2055c35SXin Li " combined with -q, -m, -lossy or -mixed\n"
78*b2055c35SXin Li " options\n");
79*b2055c35SXin Li printf(" -kmin <int> ............ min distance between key frames\n");
80*b2055c35SXin Li printf(" -kmax <int> ............ max distance between key frames\n");
81*b2055c35SXin Li printf(" -f <int> ............... filter strength (0=off..100)\n");
82*b2055c35SXin Li printf(" -metadata <string> ..... comma separated list of metadata to\n");
83*b2055c35SXin Li printf(" ");
84*b2055c35SXin Li printf("copy from the input to the output if present\n");
85*b2055c35SXin Li printf(" ");
86*b2055c35SXin Li printf("Valid values: all, none, icc, xmp (default)\n");
87*b2055c35SXin Li printf(" -loop_compatibility .... use compatibility mode for Chrome\n");
88*b2055c35SXin Li printf(" version prior to M62 (inclusive)\n");
89*b2055c35SXin Li printf(" -mt .................... use multi-threading if available\n");
90*b2055c35SXin Li printf("\n");
91*b2055c35SXin Li printf(" -version ............... print version number and exit\n");
92*b2055c35SXin Li printf(" -v ..................... verbose\n");
93*b2055c35SXin Li printf(" -quiet ................. don't print anything\n");
94*b2055c35SXin Li printf("\n");
95*b2055c35SXin Li }
96*b2055c35SXin Li
97*b2055c35SXin Li //------------------------------------------------------------------------------
98*b2055c35SXin Li
main(int argc,const char * argv[])99*b2055c35SXin Li int main(int argc, const char* argv[]) {
100*b2055c35SXin Li int verbose = 0;
101*b2055c35SXin Li int gif_error = GIF_ERROR;
102*b2055c35SXin Li WebPMuxError err = WEBP_MUX_OK;
103*b2055c35SXin Li int ok = 0;
104*b2055c35SXin Li const W_CHAR* in_file = NULL, *out_file = NULL;
105*b2055c35SXin Li GifFileType* gif = NULL;
106*b2055c35SXin Li int frame_duration = 0;
107*b2055c35SXin Li int frame_timestamp = 0;
108*b2055c35SXin Li GIFDisposeMethod orig_dispose = GIF_DISPOSE_NONE;
109*b2055c35SXin Li
110*b2055c35SXin Li WebPPicture frame; // Frame rectangle only (not disposed).
111*b2055c35SXin Li WebPPicture curr_canvas; // Not disposed.
112*b2055c35SXin Li WebPPicture prev_canvas; // Disposed.
113*b2055c35SXin Li
114*b2055c35SXin Li WebPAnimEncoder* enc = NULL;
115*b2055c35SXin Li WebPAnimEncoderOptions enc_options;
116*b2055c35SXin Li WebPConfig config;
117*b2055c35SXin Li
118*b2055c35SXin Li int frame_number = 0; // Whether we are processing the first frame.
119*b2055c35SXin Li int done;
120*b2055c35SXin Li int c;
121*b2055c35SXin Li int quiet = 0;
122*b2055c35SXin Li WebPData webp_data;
123*b2055c35SXin Li
124*b2055c35SXin Li int keep_metadata = METADATA_XMP; // ICC not output by default.
125*b2055c35SXin Li WebPData icc_data;
126*b2055c35SXin Li int stored_icc = 0; // Whether we have already stored an ICC profile.
127*b2055c35SXin Li WebPData xmp_data;
128*b2055c35SXin Li int stored_xmp = 0; // Whether we have already stored an XMP profile.
129*b2055c35SXin Li int loop_count = 0; // default: infinite
130*b2055c35SXin Li int stored_loop_count = 0; // Whether we have found an explicit loop count.
131*b2055c35SXin Li int loop_compatibility = 0;
132*b2055c35SXin Li WebPMux* mux = NULL;
133*b2055c35SXin Li
134*b2055c35SXin Li int default_kmin = 1; // Whether to use default kmin value.
135*b2055c35SXin Li int default_kmax = 1;
136*b2055c35SXin Li
137*b2055c35SXin Li INIT_WARGV(argc, argv);
138*b2055c35SXin Li
139*b2055c35SXin Li if (!WebPConfigInit(&config) || !WebPAnimEncoderOptionsInit(&enc_options) ||
140*b2055c35SXin Li !WebPPictureInit(&frame) || !WebPPictureInit(&curr_canvas) ||
141*b2055c35SXin Li !WebPPictureInit(&prev_canvas)) {
142*b2055c35SXin Li fprintf(stderr, "Error! Version mismatch!\n");
143*b2055c35SXin Li FREE_WARGV_AND_RETURN(-1);
144*b2055c35SXin Li }
145*b2055c35SXin Li config.lossless = 1; // Use lossless compression by default.
146*b2055c35SXin Li
147*b2055c35SXin Li WebPDataInit(&webp_data);
148*b2055c35SXin Li WebPDataInit(&icc_data);
149*b2055c35SXin Li WebPDataInit(&xmp_data);
150*b2055c35SXin Li
151*b2055c35SXin Li if (argc == 1) {
152*b2055c35SXin Li Help();
153*b2055c35SXin Li FREE_WARGV_AND_RETURN(0);
154*b2055c35SXin Li }
155*b2055c35SXin Li
156*b2055c35SXin Li for (c = 1; c < argc; ++c) {
157*b2055c35SXin Li int parse_error = 0;
158*b2055c35SXin Li if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
159*b2055c35SXin Li Help();
160*b2055c35SXin Li FREE_WARGV_AND_RETURN(0);
161*b2055c35SXin Li } else if (!strcmp(argv[c], "-o") && c < argc - 1) {
162*b2055c35SXin Li out_file = GET_WARGV(argv, ++c);
163*b2055c35SXin Li } else if (!strcmp(argv[c], "-lossy")) {
164*b2055c35SXin Li config.lossless = 0;
165*b2055c35SXin Li } else if (!strcmp(argv[c], "-mixed")) {
166*b2055c35SXin Li enc_options.allow_mixed = 1;
167*b2055c35SXin Li config.lossless = 0;
168*b2055c35SXin Li } else if (!strcmp(argv[c], "-loop_compatibility")) {
169*b2055c35SXin Li loop_compatibility = 1;
170*b2055c35SXin Li } else if (!strcmp(argv[c], "-q") && c < argc - 1) {
171*b2055c35SXin Li config.quality = ExUtilGetFloat(argv[++c], &parse_error);
172*b2055c35SXin Li } else if (!strcmp(argv[c], "-m") && c < argc - 1) {
173*b2055c35SXin Li config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
174*b2055c35SXin Li } else if (!strcmp(argv[c], "-min_size")) {
175*b2055c35SXin Li enc_options.minimize_size = 1;
176*b2055c35SXin Li } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
177*b2055c35SXin Li enc_options.kmax = ExUtilGetInt(argv[++c], 0, &parse_error);
178*b2055c35SXin Li default_kmax = 0;
179*b2055c35SXin Li } else if (!strcmp(argv[c], "-kmin") && c < argc - 1) {
180*b2055c35SXin Li enc_options.kmin = ExUtilGetInt(argv[++c], 0, &parse_error);
181*b2055c35SXin Li default_kmin = 0;
182*b2055c35SXin Li } else if (!strcmp(argv[c], "-f") && c < argc - 1) {
183*b2055c35SXin Li config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error);
184*b2055c35SXin Li } else if (!strcmp(argv[c], "-metadata") && c < argc - 1) {
185*b2055c35SXin Li static const struct {
186*b2055c35SXin Li const char* option;
187*b2055c35SXin Li int flag;
188*b2055c35SXin Li } kTokens[] = {
189*b2055c35SXin Li { "all", METADATA_ALL },
190*b2055c35SXin Li { "none", 0 },
191*b2055c35SXin Li { "icc", METADATA_ICC },
192*b2055c35SXin Li { "xmp", METADATA_XMP },
193*b2055c35SXin Li };
194*b2055c35SXin Li const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens);
195*b2055c35SXin Li const char* start = argv[++c];
196*b2055c35SXin Li const char* const end = start + strlen(start);
197*b2055c35SXin Li
198*b2055c35SXin Li keep_metadata = 0;
199*b2055c35SXin Li while (start < end) {
200*b2055c35SXin Li size_t i;
201*b2055c35SXin Li const char* token = strchr(start, ',');
202*b2055c35SXin Li if (token == NULL) token = end;
203*b2055c35SXin Li
204*b2055c35SXin Li for (i = 0; i < kNumTokens; ++i) {
205*b2055c35SXin Li if ((size_t)(token - start) == strlen(kTokens[i].option) &&
206*b2055c35SXin Li !strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) {
207*b2055c35SXin Li if (kTokens[i].flag != 0) {
208*b2055c35SXin Li keep_metadata |= kTokens[i].flag;
209*b2055c35SXin Li } else {
210*b2055c35SXin Li keep_metadata = 0;
211*b2055c35SXin Li }
212*b2055c35SXin Li break;
213*b2055c35SXin Li }
214*b2055c35SXin Li }
215*b2055c35SXin Li if (i == kNumTokens) {
216*b2055c35SXin Li fprintf(stderr, "Error! Unknown metadata type '%.*s'\n",
217*b2055c35SXin Li (int)(token - start), start);
218*b2055c35SXin Li Help();
219*b2055c35SXin Li FREE_WARGV_AND_RETURN(-1);
220*b2055c35SXin Li }
221*b2055c35SXin Li start = token + 1;
222*b2055c35SXin Li }
223*b2055c35SXin Li } else if (!strcmp(argv[c], "-mt")) {
224*b2055c35SXin Li ++config.thread_level;
225*b2055c35SXin Li } else if (!strcmp(argv[c], "-version")) {
226*b2055c35SXin Li const int enc_version = WebPGetEncoderVersion();
227*b2055c35SXin Li const int mux_version = WebPGetMuxVersion();
228*b2055c35SXin Li printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
229*b2055c35SXin Li (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
230*b2055c35SXin Li enc_version & 0xff, (mux_version >> 16) & 0xff,
231*b2055c35SXin Li (mux_version >> 8) & 0xff, mux_version & 0xff);
232*b2055c35SXin Li FREE_WARGV_AND_RETURN(0);
233*b2055c35SXin Li } else if (!strcmp(argv[c], "-quiet")) {
234*b2055c35SXin Li quiet = 1;
235*b2055c35SXin Li enc_options.verbose = 0;
236*b2055c35SXin Li } else if (!strcmp(argv[c], "-v")) {
237*b2055c35SXin Li verbose = 1;
238*b2055c35SXin Li enc_options.verbose = 1;
239*b2055c35SXin Li } else if (!strcmp(argv[c], "--")) {
240*b2055c35SXin Li if (c < argc - 1) in_file = GET_WARGV(argv, ++c);
241*b2055c35SXin Li break;
242*b2055c35SXin Li } else if (argv[c][0] == '-') {
243*b2055c35SXin Li fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
244*b2055c35SXin Li Help();
245*b2055c35SXin Li FREE_WARGV_AND_RETURN(-1);
246*b2055c35SXin Li } else {
247*b2055c35SXin Li in_file = GET_WARGV(argv, c);
248*b2055c35SXin Li }
249*b2055c35SXin Li
250*b2055c35SXin Li if (parse_error) {
251*b2055c35SXin Li Help();
252*b2055c35SXin Li FREE_WARGV_AND_RETURN(-1);
253*b2055c35SXin Li }
254*b2055c35SXin Li }
255*b2055c35SXin Li
256*b2055c35SXin Li // Appropriate default kmin, kmax values for lossy and lossless.
257*b2055c35SXin Li if (default_kmin) {
258*b2055c35SXin Li enc_options.kmin = config.lossless ? 9 : 3;
259*b2055c35SXin Li }
260*b2055c35SXin Li if (default_kmax) {
261*b2055c35SXin Li enc_options.kmax = config.lossless ? 17 : 5;
262*b2055c35SXin Li }
263*b2055c35SXin Li
264*b2055c35SXin Li if (!WebPValidateConfig(&config)) {
265*b2055c35SXin Li fprintf(stderr, "Error! Invalid configuration.\n");
266*b2055c35SXin Li goto End;
267*b2055c35SXin Li }
268*b2055c35SXin Li
269*b2055c35SXin Li if (in_file == NULL) {
270*b2055c35SXin Li fprintf(stderr, "No input file specified!\n");
271*b2055c35SXin Li Help();
272*b2055c35SXin Li goto End;
273*b2055c35SXin Li }
274*b2055c35SXin Li
275*b2055c35SXin Li // Start the decoder object
276*b2055c35SXin Li gif = DGifOpenFileUnicode(in_file, &gif_error);
277*b2055c35SXin Li if (gif == NULL) goto End;
278*b2055c35SXin Li
279*b2055c35SXin Li // Loop over GIF images
280*b2055c35SXin Li done = 0;
281*b2055c35SXin Li do {
282*b2055c35SXin Li GifRecordType type;
283*b2055c35SXin Li if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
284*b2055c35SXin Li
285*b2055c35SXin Li switch (type) {
286*b2055c35SXin Li case IMAGE_DESC_RECORD_TYPE: {
287*b2055c35SXin Li GIFFrameRect gif_rect;
288*b2055c35SXin Li GifImageDesc* const image_desc = &gif->Image;
289*b2055c35SXin Li
290*b2055c35SXin Li if (!DGifGetImageDesc(gif)) goto End;
291*b2055c35SXin Li
292*b2055c35SXin Li if (frame_number == 0) {
293*b2055c35SXin Li if (verbose) {
294*b2055c35SXin Li printf("Canvas screen: %d x %d\n", gif->SWidth, gif->SHeight);
295*b2055c35SXin Li }
296*b2055c35SXin Li // Fix some broken GIF global headers that report
297*b2055c35SXin Li // 0 x 0 screen dimension.
298*b2055c35SXin Li if (gif->SWidth == 0 || gif->SHeight == 0) {
299*b2055c35SXin Li image_desc->Left = 0;
300*b2055c35SXin Li image_desc->Top = 0;
301*b2055c35SXin Li gif->SWidth = image_desc->Width;
302*b2055c35SXin Li gif->SHeight = image_desc->Height;
303*b2055c35SXin Li if (gif->SWidth <= 0 || gif->SHeight <= 0) {
304*b2055c35SXin Li goto End;
305*b2055c35SXin Li }
306*b2055c35SXin Li if (verbose) {
307*b2055c35SXin Li printf("Fixed canvas screen dimension to: %d x %d\n",
308*b2055c35SXin Li gif->SWidth, gif->SHeight);
309*b2055c35SXin Li }
310*b2055c35SXin Li }
311*b2055c35SXin Li // Allocate current buffer.
312*b2055c35SXin Li frame.width = gif->SWidth;
313*b2055c35SXin Li frame.height = gif->SHeight;
314*b2055c35SXin Li frame.use_argb = 1;
315*b2055c35SXin Li if (!WebPPictureAlloc(&frame)) goto End;
316*b2055c35SXin Li GIFClearPic(&frame, NULL);
317*b2055c35SXin Li if (!(WebPPictureCopy(&frame, &curr_canvas) &&
318*b2055c35SXin Li WebPPictureCopy(&frame, &prev_canvas))) {
319*b2055c35SXin Li fprintf(stderr, "Error allocating canvas.\n");
320*b2055c35SXin Li goto End;
321*b2055c35SXin Li }
322*b2055c35SXin Li
323*b2055c35SXin Li // Background color.
324*b2055c35SXin Li GIFGetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
325*b2055c35SXin Li transparent_index,
326*b2055c35SXin Li &enc_options.anim_params.bgcolor);
327*b2055c35SXin Li
328*b2055c35SXin Li // Initialize encoder.
329*b2055c35SXin Li enc = WebPAnimEncoderNew(curr_canvas.width, curr_canvas.height,
330*b2055c35SXin Li &enc_options);
331*b2055c35SXin Li if (enc == NULL) {
332*b2055c35SXin Li fprintf(stderr,
333*b2055c35SXin Li "Error! Could not create encoder object. Possibly due to "
334*b2055c35SXin Li "a memory error.\n");
335*b2055c35SXin Li goto End;
336*b2055c35SXin Li }
337*b2055c35SXin Li }
338*b2055c35SXin Li
339*b2055c35SXin Li // Some even more broken GIF can have sub-rect with zero width/height.
340*b2055c35SXin Li if (image_desc->Width == 0 || image_desc->Height == 0) {
341*b2055c35SXin Li image_desc->Width = gif->SWidth;
342*b2055c35SXin Li image_desc->Height = gif->SHeight;
343*b2055c35SXin Li }
344*b2055c35SXin Li
345*b2055c35SXin Li if (!GIFReadFrame(gif, transparent_index, &gif_rect, &frame)) {
346*b2055c35SXin Li goto End;
347*b2055c35SXin Li }
348*b2055c35SXin Li // Blend frame rectangle with previous canvas to compose full canvas.
349*b2055c35SXin Li // Note that 'curr_canvas' is same as 'prev_canvas' at this point.
350*b2055c35SXin Li GIFBlendFrames(&frame, &gif_rect, &curr_canvas);
351*b2055c35SXin Li
352*b2055c35SXin Li if (!WebPAnimEncoderAdd(enc, &curr_canvas, frame_timestamp, &config)) {
353*b2055c35SXin Li fprintf(stderr, "Error while adding frame #%d: %s\n", frame_number,
354*b2055c35SXin Li WebPAnimEncoderGetError(enc));
355*b2055c35SXin Li goto End;
356*b2055c35SXin Li } else {
357*b2055c35SXin Li ++frame_number;
358*b2055c35SXin Li }
359*b2055c35SXin Li
360*b2055c35SXin Li // Update canvases.
361*b2055c35SXin Li GIFDisposeFrame(orig_dispose, &gif_rect, &prev_canvas, &curr_canvas);
362*b2055c35SXin Li GIFCopyPixels(&curr_canvas, &prev_canvas);
363*b2055c35SXin Li
364*b2055c35SXin Li // Force frames with a small or no duration to 100ms to be consistent
365*b2055c35SXin Li // with web browsers and other transcoding tools. This also avoids
366*b2055c35SXin Li // incorrect durations between frames when padding frames are
367*b2055c35SXin Li // discarded.
368*b2055c35SXin Li if (frame_duration <= 10) {
369*b2055c35SXin Li frame_duration = 100;
370*b2055c35SXin Li }
371*b2055c35SXin Li
372*b2055c35SXin Li // Update timestamp (for next frame).
373*b2055c35SXin Li frame_timestamp += frame_duration;
374*b2055c35SXin Li
375*b2055c35SXin Li // In GIF, graphic control extensions are optional for a frame, so we
376*b2055c35SXin Li // may not get one before reading the next frame. To handle this case,
377*b2055c35SXin Li // we reset frame properties to reasonable defaults for the next frame.
378*b2055c35SXin Li orig_dispose = GIF_DISPOSE_NONE;
379*b2055c35SXin Li frame_duration = 0;
380*b2055c35SXin Li transparent_index = GIF_INDEX_INVALID;
381*b2055c35SXin Li break;
382*b2055c35SXin Li }
383*b2055c35SXin Li case EXTENSION_RECORD_TYPE: {
384*b2055c35SXin Li int extension;
385*b2055c35SXin Li GifByteType* data = NULL;
386*b2055c35SXin Li if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
387*b2055c35SXin Li goto End;
388*b2055c35SXin Li }
389*b2055c35SXin Li if (data == NULL) continue;
390*b2055c35SXin Li
391*b2055c35SXin Li switch (extension) {
392*b2055c35SXin Li case COMMENT_EXT_FUNC_CODE: {
393*b2055c35SXin Li break; // Do nothing for now.
394*b2055c35SXin Li }
395*b2055c35SXin Li case GRAPHICS_EXT_FUNC_CODE: {
396*b2055c35SXin Li if (!GIFReadGraphicsExtension(data, &frame_duration, &orig_dispose,
397*b2055c35SXin Li &transparent_index)) {
398*b2055c35SXin Li goto End;
399*b2055c35SXin Li }
400*b2055c35SXin Li break;
401*b2055c35SXin Li }
402*b2055c35SXin Li case PLAINTEXT_EXT_FUNC_CODE: {
403*b2055c35SXin Li break;
404*b2055c35SXin Li }
405*b2055c35SXin Li case APPLICATION_EXT_FUNC_CODE: {
406*b2055c35SXin Li if (data[0] != 11) break; // Chunk is too short
407*b2055c35SXin Li if (!memcmp(data + 1, "NETSCAPE2.0", 11) ||
408*b2055c35SXin Li !memcmp(data + 1, "ANIMEXTS1.0", 11)) {
409*b2055c35SXin Li if (!GIFReadLoopCount(gif, &data, &loop_count)) {
410*b2055c35SXin Li goto End;
411*b2055c35SXin Li }
412*b2055c35SXin Li if (verbose) {
413*b2055c35SXin Li fprintf(stderr, "Loop count: %d\n", loop_count);
414*b2055c35SXin Li }
415*b2055c35SXin Li stored_loop_count = loop_compatibility ? (loop_count != 0) : 1;
416*b2055c35SXin Li } else { // An extension containing metadata.
417*b2055c35SXin Li // We only store the first encountered chunk of each type, and
418*b2055c35SXin Li // only if requested by the user.
419*b2055c35SXin Li const int is_xmp = (keep_metadata & METADATA_XMP) &&
420*b2055c35SXin Li !stored_xmp &&
421*b2055c35SXin Li !memcmp(data + 1, "XMP DataXMP", 11);
422*b2055c35SXin Li const int is_icc = (keep_metadata & METADATA_ICC) &&
423*b2055c35SXin Li !stored_icc &&
424*b2055c35SXin Li !memcmp(data + 1, "ICCRGBG1012", 11);
425*b2055c35SXin Li if (is_xmp || is_icc) {
426*b2055c35SXin Li if (!GIFReadMetadata(gif, &data,
427*b2055c35SXin Li is_xmp ? &xmp_data : &icc_data)) {
428*b2055c35SXin Li goto End;
429*b2055c35SXin Li }
430*b2055c35SXin Li if (is_icc) {
431*b2055c35SXin Li stored_icc = 1;
432*b2055c35SXin Li } else if (is_xmp) {
433*b2055c35SXin Li stored_xmp = 1;
434*b2055c35SXin Li }
435*b2055c35SXin Li }
436*b2055c35SXin Li }
437*b2055c35SXin Li break;
438*b2055c35SXin Li }
439*b2055c35SXin Li default: {
440*b2055c35SXin Li break; // skip
441*b2055c35SXin Li }
442*b2055c35SXin Li }
443*b2055c35SXin Li while (data != NULL) {
444*b2055c35SXin Li if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
445*b2055c35SXin Li }
446*b2055c35SXin Li break;
447*b2055c35SXin Li }
448*b2055c35SXin Li case TERMINATE_RECORD_TYPE: {
449*b2055c35SXin Li done = 1;
450*b2055c35SXin Li break;
451*b2055c35SXin Li }
452*b2055c35SXin Li default: {
453*b2055c35SXin Li if (verbose) {
454*b2055c35SXin Li fprintf(stderr, "Skipping over unknown record type %d\n", type);
455*b2055c35SXin Li }
456*b2055c35SXin Li break;
457*b2055c35SXin Li }
458*b2055c35SXin Li }
459*b2055c35SXin Li } while (!done);
460*b2055c35SXin Li
461*b2055c35SXin Li // Last NULL frame.
462*b2055c35SXin Li if (!WebPAnimEncoderAdd(enc, NULL, frame_timestamp, NULL)) {
463*b2055c35SXin Li fprintf(stderr, "Error flushing WebP muxer.\n");
464*b2055c35SXin Li fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
465*b2055c35SXin Li }
466*b2055c35SXin Li
467*b2055c35SXin Li if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
468*b2055c35SXin Li fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
469*b2055c35SXin Li goto End;
470*b2055c35SXin Li }
471*b2055c35SXin Li // If there's only one frame, we don't need to handle loop count.
472*b2055c35SXin Li if (frame_number == 1) {
473*b2055c35SXin Li loop_count = 0;
474*b2055c35SXin Li } else if (!loop_compatibility) {
475*b2055c35SXin Li if (!stored_loop_count) {
476*b2055c35SXin Li // if no loop-count element is seen, the default is '1' (loop-once)
477*b2055c35SXin Li // and we need to signal it explicitly in WebP. Note however that
478*b2055c35SXin Li // in case there's a single frame, we still don't need to store it.
479*b2055c35SXin Li if (frame_number > 1) {
480*b2055c35SXin Li stored_loop_count = 1;
481*b2055c35SXin Li loop_count = 1;
482*b2055c35SXin Li }
483*b2055c35SXin Li } else if (loop_count > 0 && loop_count < 65535) {
484*b2055c35SXin Li // adapt GIF's semantic to WebP's (except in the infinite-loop case)
485*b2055c35SXin Li loop_count += 1;
486*b2055c35SXin Li }
487*b2055c35SXin Li }
488*b2055c35SXin Li // loop_count of 0 is the default (infinite), so no need to signal it
489*b2055c35SXin Li if (loop_count == 0) stored_loop_count = 0;
490*b2055c35SXin Li
491*b2055c35SXin Li if (stored_loop_count || stored_icc || stored_xmp) {
492*b2055c35SXin Li // Re-mux to add loop count and/or metadata as needed.
493*b2055c35SXin Li mux = WebPMuxCreate(&webp_data, 1);
494*b2055c35SXin Li if (mux == NULL) {
495*b2055c35SXin Li fprintf(stderr, "ERROR: Could not re-mux to add loop count/metadata.\n");
496*b2055c35SXin Li goto End;
497*b2055c35SXin Li }
498*b2055c35SXin Li WebPDataClear(&webp_data);
499*b2055c35SXin Li
500*b2055c35SXin Li if (stored_loop_count) { // Update loop count.
501*b2055c35SXin Li WebPMuxAnimParams new_params;
502*b2055c35SXin Li err = WebPMuxGetAnimationParams(mux, &new_params);
503*b2055c35SXin Li if (err != WEBP_MUX_OK) {
504*b2055c35SXin Li fprintf(stderr, "ERROR (%s): Could not fetch loop count.\n",
505*b2055c35SXin Li ErrorString(err));
506*b2055c35SXin Li goto End;
507*b2055c35SXin Li }
508*b2055c35SXin Li new_params.loop_count = loop_count;
509*b2055c35SXin Li err = WebPMuxSetAnimationParams(mux, &new_params);
510*b2055c35SXin Li if (err != WEBP_MUX_OK) {
511*b2055c35SXin Li fprintf(stderr, "ERROR (%s): Could not update loop count.\n",
512*b2055c35SXin Li ErrorString(err));
513*b2055c35SXin Li goto End;
514*b2055c35SXin Li }
515*b2055c35SXin Li }
516*b2055c35SXin Li
517*b2055c35SXin Li if (stored_icc) { // Add ICCP chunk.
518*b2055c35SXin Li err = WebPMuxSetChunk(mux, "ICCP", &icc_data, 1);
519*b2055c35SXin Li if (verbose) {
520*b2055c35SXin Li fprintf(stderr, "ICC size: %d\n", (int)icc_data.size);
521*b2055c35SXin Li }
522*b2055c35SXin Li if (err != WEBP_MUX_OK) {
523*b2055c35SXin Li fprintf(stderr, "ERROR (%s): Could not set ICC chunk.\n",
524*b2055c35SXin Li ErrorString(err));
525*b2055c35SXin Li goto End;
526*b2055c35SXin Li }
527*b2055c35SXin Li }
528*b2055c35SXin Li
529*b2055c35SXin Li if (stored_xmp) { // Add XMP chunk.
530*b2055c35SXin Li err = WebPMuxSetChunk(mux, "XMP ", &xmp_data, 1);
531*b2055c35SXin Li if (verbose) {
532*b2055c35SXin Li fprintf(stderr, "XMP size: %d\n", (int)xmp_data.size);
533*b2055c35SXin Li }
534*b2055c35SXin Li if (err != WEBP_MUX_OK) {
535*b2055c35SXin Li fprintf(stderr, "ERROR (%s): Could not set XMP chunk.\n",
536*b2055c35SXin Li ErrorString(err));
537*b2055c35SXin Li goto End;
538*b2055c35SXin Li }
539*b2055c35SXin Li }
540*b2055c35SXin Li
541*b2055c35SXin Li err = WebPMuxAssemble(mux, &webp_data);
542*b2055c35SXin Li if (err != WEBP_MUX_OK) {
543*b2055c35SXin Li fprintf(stderr, "ERROR (%s): Could not assemble when re-muxing to add "
544*b2055c35SXin Li "loop count/metadata.\n", ErrorString(err));
545*b2055c35SXin Li goto End;
546*b2055c35SXin Li }
547*b2055c35SXin Li }
548*b2055c35SXin Li
549*b2055c35SXin Li if (out_file != NULL) {
550*b2055c35SXin Li if (!ImgIoUtilWriteFile((const char*)out_file, webp_data.bytes,
551*b2055c35SXin Li webp_data.size)) {
552*b2055c35SXin Li WFPRINTF(stderr, "Error writing output file: %s\n", out_file);
553*b2055c35SXin Li goto End;
554*b2055c35SXin Li }
555*b2055c35SXin Li if (!quiet) {
556*b2055c35SXin Li if (!WSTRCMP(out_file, "-")) {
557*b2055c35SXin Li fprintf(stderr, "Saved %d bytes to STDIO\n",
558*b2055c35SXin Li (int)webp_data.size);
559*b2055c35SXin Li } else {
560*b2055c35SXin Li WFPRINTF(stderr, "Saved output file (%d bytes): %s\n",
561*b2055c35SXin Li (int)webp_data.size, out_file);
562*b2055c35SXin Li }
563*b2055c35SXin Li }
564*b2055c35SXin Li } else {
565*b2055c35SXin Li if (!quiet) {
566*b2055c35SXin Li fprintf(stderr, "Nothing written; use -o flag to save the result "
567*b2055c35SXin Li "(%d bytes).\n", (int)webp_data.size);
568*b2055c35SXin Li }
569*b2055c35SXin Li }
570*b2055c35SXin Li
571*b2055c35SXin Li // All OK.
572*b2055c35SXin Li ok = 1;
573*b2055c35SXin Li gif_error = GIF_OK;
574*b2055c35SXin Li
575*b2055c35SXin Li End:
576*b2055c35SXin Li WebPDataClear(&icc_data);
577*b2055c35SXin Li WebPDataClear(&xmp_data);
578*b2055c35SXin Li WebPMuxDelete(mux);
579*b2055c35SXin Li WebPDataClear(&webp_data);
580*b2055c35SXin Li WebPPictureFree(&frame);
581*b2055c35SXin Li WebPPictureFree(&curr_canvas);
582*b2055c35SXin Li WebPPictureFree(&prev_canvas);
583*b2055c35SXin Li WebPAnimEncoderDelete(enc);
584*b2055c35SXin Li
585*b2055c35SXin Li if (gif_error != GIF_OK) {
586*b2055c35SXin Li GIFDisplayError(gif, gif_error);
587*b2055c35SXin Li }
588*b2055c35SXin Li if (gif != NULL) {
589*b2055c35SXin Li #if LOCAL_GIF_PREREQ(5,1)
590*b2055c35SXin Li DGifCloseFile(gif, &gif_error);
591*b2055c35SXin Li #else
592*b2055c35SXin Li DGifCloseFile(gif);
593*b2055c35SXin Li #endif
594*b2055c35SXin Li }
595*b2055c35SXin Li
596*b2055c35SXin Li FREE_WARGV_AND_RETURN(!ok);
597*b2055c35SXin Li }
598*b2055c35SXin Li
599*b2055c35SXin Li #else // !WEBP_HAVE_GIF
600*b2055c35SXin Li
main(int argc,const char * argv[])601*b2055c35SXin Li int main(int argc, const char* argv[]) {
602*b2055c35SXin Li fprintf(stderr, "GIF support not enabled in %s.\n", argv[0]);
603*b2055c35SXin Li (void)argc;
604*b2055c35SXin Li return 0;
605*b2055c35SXin Li }
606*b2055c35SXin Li
607*b2055c35SXin Li #endif
608*b2055c35SXin Li
609*b2055c35SXin Li //------------------------------------------------------------------------------
610