xref: /aosp_15_r20/external/webp/examples/gif2webp.c (revision b2055c353e87c8814eb2b6b1b11112a1562253bd)
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