1*b2055c35SXin Li // Copyright 2016 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 // generate an animated WebP out of a sequence of images
11*b2055c35SXin Li // (PNG, JPEG, ...)
12*b2055c35SXin Li //
13*b2055c35SXin Li // Example usage:
14*b2055c35SXin Li // img2webp -o out.webp -q 40 -mixed -duration 40 input??.png
15*b2055c35SXin Li //
16*b2055c35SXin Li // Author: [email protected] (Pascal Massimino)
17*b2055c35SXin Li
18*b2055c35SXin Li #include <stdio.h>
19*b2055c35SXin Li #include <stdlib.h>
20*b2055c35SXin Li #include <string.h>
21*b2055c35SXin Li
22*b2055c35SXin Li #ifdef HAVE_CONFIG_H
23*b2055c35SXin Li #include "webp/config.h"
24*b2055c35SXin Li #endif
25*b2055c35SXin Li
26*b2055c35SXin Li #include "../examples/example_util.h"
27*b2055c35SXin Li #include "../imageio/image_dec.h"
28*b2055c35SXin Li #include "../imageio/imageio_util.h"
29*b2055c35SXin Li #include "./stopwatch.h"
30*b2055c35SXin Li #include "./unicode.h"
31*b2055c35SXin Li #include "sharpyuv/sharpyuv.h"
32*b2055c35SXin Li #include "webp/encode.h"
33*b2055c35SXin Li #include "webp/mux.h"
34*b2055c35SXin Li
35*b2055c35SXin Li //------------------------------------------------------------------------------
36*b2055c35SXin Li
Help(void)37*b2055c35SXin Li static void Help(void) {
38*b2055c35SXin Li printf("Usage:\n\n");
39*b2055c35SXin Li printf(" img2webp [file_options] [[frame_options] frame_file]...");
40*b2055c35SXin Li printf(" [-o webp_file]\n\n");
41*b2055c35SXin Li
42*b2055c35SXin Li printf("File-level options (only used at the start of compression):\n");
43*b2055c35SXin Li printf(" -min_size ............ minimize size\n");
44*b2055c35SXin Li printf(" -kmax <int> .......... maximum number of frame between key-frames\n"
45*b2055c35SXin Li " (0=only keyframes)\n");
46*b2055c35SXin Li printf(" -kmin <int> .......... minimum number of frame between key-frames\n"
47*b2055c35SXin Li " (0=disable key-frames altogether)\n");
48*b2055c35SXin Li printf(" -mixed ............... use mixed lossy/lossless automatic mode\n");
49*b2055c35SXin Li printf(" -near_lossless <int> . use near-lossless image preprocessing\n"
50*b2055c35SXin Li " (0..100=off), default=100\n");
51*b2055c35SXin Li printf(" -sharp_yuv ........... use sharper (and slower) RGB->YUV "
52*b2055c35SXin Li "conversion\n "
53*b2055c35SXin Li "(lossy only)\n");
54*b2055c35SXin Li printf(" -loop <int> .......... loop count (default: 0, = infinite loop)\n");
55*b2055c35SXin Li printf(" -v ................... verbose mode\n");
56*b2055c35SXin Li printf(" -h ................... this help\n");
57*b2055c35SXin Li printf(" -version ............. print version number and exit\n");
58*b2055c35SXin Li printf("\n");
59*b2055c35SXin Li
60*b2055c35SXin Li printf("Per-frame options (only used for subsequent images input):\n");
61*b2055c35SXin Li printf(" -d <int> ............. frame duration in ms (default: 100)\n");
62*b2055c35SXin Li printf(" -lossless ........... use lossless mode (default)\n");
63*b2055c35SXin Li printf(" -lossy ... ........... use lossy mode\n");
64*b2055c35SXin Li printf(" -q <float> ........... quality\n");
65*b2055c35SXin Li printf(" -m <int> ............. method to use\n");
66*b2055c35SXin Li
67*b2055c35SXin Li printf("\n");
68*b2055c35SXin Li printf("example: img2webp -loop 2 in0.png -lossy in1.jpg\n"
69*b2055c35SXin Li " -d 80 in2.tiff -o out.webp\n");
70*b2055c35SXin Li printf("\nNote: if a single file name is passed as the argument, the "
71*b2055c35SXin Li "arguments will be\n");
72*b2055c35SXin Li printf("tokenized from this file. The file name must not start with "
73*b2055c35SXin Li "the character '-'.\n");
74*b2055c35SXin Li printf("\nSupported input formats:\n %s\n",
75*b2055c35SXin Li WebPGetEnabledInputFileFormats());
76*b2055c35SXin Li }
77*b2055c35SXin Li
78*b2055c35SXin Li //------------------------------------------------------------------------------
79*b2055c35SXin Li
ReadImage(const char filename[],WebPPicture * const pic)80*b2055c35SXin Li static int ReadImage(const char filename[], WebPPicture* const pic) {
81*b2055c35SXin Li const uint8_t* data = NULL;
82*b2055c35SXin Li size_t data_size = 0;
83*b2055c35SXin Li WebPImageReader reader;
84*b2055c35SXin Li int ok;
85*b2055c35SXin Li #ifdef HAVE_WINCODEC_H
86*b2055c35SXin Li // Try to decode the file using WIC falling back to the other readers for
87*b2055c35SXin Li // e.g., WebP.
88*b2055c35SXin Li ok = ReadPictureWithWIC(filename, pic, 1, NULL);
89*b2055c35SXin Li if (ok) return 1;
90*b2055c35SXin Li #endif
91*b2055c35SXin Li if (!ImgIoUtilReadFile(filename, &data, &data_size)) return 0;
92*b2055c35SXin Li reader = WebPGuessImageReader(data, data_size);
93*b2055c35SXin Li ok = reader(data, data_size, pic, 1, NULL);
94*b2055c35SXin Li WebPFree((void*)data);
95*b2055c35SXin Li return ok;
96*b2055c35SXin Li }
97*b2055c35SXin Li
SetLoopCount(int loop_count,WebPData * const webp_data)98*b2055c35SXin Li static int SetLoopCount(int loop_count, WebPData* const webp_data) {
99*b2055c35SXin Li int ok = 1;
100*b2055c35SXin Li WebPMuxError err;
101*b2055c35SXin Li uint32_t features;
102*b2055c35SXin Li WebPMuxAnimParams new_params;
103*b2055c35SXin Li WebPMux* const mux = WebPMuxCreate(webp_data, 1);
104*b2055c35SXin Li if (mux == NULL) return 0;
105*b2055c35SXin Li
106*b2055c35SXin Li err = WebPMuxGetFeatures(mux, &features);
107*b2055c35SXin Li ok = (err == WEBP_MUX_OK);
108*b2055c35SXin Li if (!ok || !(features & ANIMATION_FLAG)) goto End;
109*b2055c35SXin Li
110*b2055c35SXin Li err = WebPMuxGetAnimationParams(mux, &new_params);
111*b2055c35SXin Li ok = (err == WEBP_MUX_OK);
112*b2055c35SXin Li if (ok) {
113*b2055c35SXin Li new_params.loop_count = loop_count;
114*b2055c35SXin Li err = WebPMuxSetAnimationParams(mux, &new_params);
115*b2055c35SXin Li ok = (err == WEBP_MUX_OK);
116*b2055c35SXin Li }
117*b2055c35SXin Li if (ok) {
118*b2055c35SXin Li WebPDataClear(webp_data);
119*b2055c35SXin Li err = WebPMuxAssemble(mux, webp_data);
120*b2055c35SXin Li ok = (err == WEBP_MUX_OK);
121*b2055c35SXin Li }
122*b2055c35SXin Li
123*b2055c35SXin Li End:
124*b2055c35SXin Li WebPMuxDelete(mux);
125*b2055c35SXin Li if (!ok) {
126*b2055c35SXin Li fprintf(stderr, "Error during loop-count setting\n");
127*b2055c35SXin Li }
128*b2055c35SXin Li return ok;
129*b2055c35SXin Li }
130*b2055c35SXin Li
131*b2055c35SXin Li //------------------------------------------------------------------------------
132*b2055c35SXin Li
main(int argc,const char * argv[])133*b2055c35SXin Li int main(int argc, const char* argv[]) {
134*b2055c35SXin Li const char* output = NULL;
135*b2055c35SXin Li WebPAnimEncoder* enc = NULL;
136*b2055c35SXin Li int verbose = 0;
137*b2055c35SXin Li int pic_num = 0;
138*b2055c35SXin Li int duration = 100;
139*b2055c35SXin Li int timestamp_ms = 0;
140*b2055c35SXin Li int loop_count = 0;
141*b2055c35SXin Li int width = 0, height = 0;
142*b2055c35SXin Li WebPAnimEncoderOptions anim_config;
143*b2055c35SXin Li WebPConfig config;
144*b2055c35SXin Li WebPPicture pic;
145*b2055c35SXin Li WebPData webp_data;
146*b2055c35SXin Li int c;
147*b2055c35SXin Li int have_input = 0;
148*b2055c35SXin Li CommandLineArguments cmd_args;
149*b2055c35SXin Li int ok;
150*b2055c35SXin Li
151*b2055c35SXin Li INIT_WARGV(argc, argv);
152*b2055c35SXin Li
153*b2055c35SXin Li ok = ExUtilInitCommandLineArguments(argc - 1, argv + 1, &cmd_args);
154*b2055c35SXin Li if (!ok) FREE_WARGV_AND_RETURN(1);
155*b2055c35SXin Li
156*b2055c35SXin Li argc = cmd_args.argc_;
157*b2055c35SXin Li argv = cmd_args.argv_;
158*b2055c35SXin Li
159*b2055c35SXin Li WebPDataInit(&webp_data);
160*b2055c35SXin Li if (!WebPAnimEncoderOptionsInit(&anim_config) ||
161*b2055c35SXin Li !WebPConfigInit(&config) ||
162*b2055c35SXin Li !WebPPictureInit(&pic)) {
163*b2055c35SXin Li fprintf(stderr, "Library version mismatch!\n");
164*b2055c35SXin Li ok = 0;
165*b2055c35SXin Li goto End;
166*b2055c35SXin Li }
167*b2055c35SXin Li
168*b2055c35SXin Li // 1st pass of option parsing
169*b2055c35SXin Li for (c = 0; ok && c < argc; ++c) {
170*b2055c35SXin Li if (argv[c][0] == '-') {
171*b2055c35SXin Li int parse_error = 0;
172*b2055c35SXin Li if (!strcmp(argv[c], "-o") && c + 1 < argc) {
173*b2055c35SXin Li argv[c] = NULL;
174*b2055c35SXin Li output = (const char*)GET_WARGV_SHIFTED(argv, ++c);
175*b2055c35SXin Li } else if (!strcmp(argv[c], "-kmin") && c + 1 < argc) {
176*b2055c35SXin Li argv[c] = NULL;
177*b2055c35SXin Li anim_config.kmin = ExUtilGetInt(argv[++c], 0, &parse_error);
178*b2055c35SXin Li } else if (!strcmp(argv[c], "-kmax") && c + 1 < argc) {
179*b2055c35SXin Li argv[c] = NULL;
180*b2055c35SXin Li anim_config.kmax = ExUtilGetInt(argv[++c], 0, &parse_error);
181*b2055c35SXin Li } else if (!strcmp(argv[c], "-loop") && c + 1 < argc) {
182*b2055c35SXin Li argv[c] = NULL;
183*b2055c35SXin Li loop_count = ExUtilGetInt(argv[++c], 0, &parse_error);
184*b2055c35SXin Li if (loop_count < 0) {
185*b2055c35SXin Li fprintf(stderr, "Invalid non-positive loop-count (%d)\n", loop_count);
186*b2055c35SXin Li parse_error = 1;
187*b2055c35SXin Li }
188*b2055c35SXin Li } else if (!strcmp(argv[c], "-min_size")) {
189*b2055c35SXin Li anim_config.minimize_size = 1;
190*b2055c35SXin Li } else if (!strcmp(argv[c], "-mixed")) {
191*b2055c35SXin Li anim_config.allow_mixed = 1;
192*b2055c35SXin Li config.lossless = 0;
193*b2055c35SXin Li } else if (!strcmp(argv[c], "-near_lossless") && c + 1 < argc) {
194*b2055c35SXin Li argv[c] = NULL;
195*b2055c35SXin Li config.near_lossless = ExUtilGetInt(argv[++c], 0, &parse_error);
196*b2055c35SXin Li } else if (!strcmp(argv[c], "-sharp_yuv")) {
197*b2055c35SXin Li config.use_sharp_yuv = 1;
198*b2055c35SXin Li } else if (!strcmp(argv[c], "-v")) {
199*b2055c35SXin Li verbose = 1;
200*b2055c35SXin Li } else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
201*b2055c35SXin Li Help();
202*b2055c35SXin Li FREE_WARGV_AND_RETURN(0);
203*b2055c35SXin Li } else if (!strcmp(argv[c], "-version")) {
204*b2055c35SXin Li const int enc_version = WebPGetEncoderVersion();
205*b2055c35SXin Li const int mux_version = WebPGetMuxVersion();
206*b2055c35SXin Li const int sharpyuv_version = SharpYuvGetVersion();
207*b2055c35SXin Li printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
208*b2055c35SXin Li (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
209*b2055c35SXin Li enc_version & 0xff, (mux_version >> 16) & 0xff,
210*b2055c35SXin Li (mux_version >> 8) & 0xff, mux_version & 0xff);
211*b2055c35SXin Li printf("libsharpyuv: %d.%d.%d\n", (sharpyuv_version >> 24) & 0xff,
212*b2055c35SXin Li (sharpyuv_version >> 16) & 0xffff, sharpyuv_version & 0xff);
213*b2055c35SXin Li goto End;
214*b2055c35SXin Li } else {
215*b2055c35SXin Li continue;
216*b2055c35SXin Li }
217*b2055c35SXin Li ok = !parse_error;
218*b2055c35SXin Li if (!ok) goto End;
219*b2055c35SXin Li argv[c] = NULL; // mark option as 'parsed' during 1st pass
220*b2055c35SXin Li } else {
221*b2055c35SXin Li have_input |= 1;
222*b2055c35SXin Li }
223*b2055c35SXin Li }
224*b2055c35SXin Li if (!have_input) {
225*b2055c35SXin Li fprintf(stderr, "No input file(s) for generating animation!\n");
226*b2055c35SXin Li goto End;
227*b2055c35SXin Li }
228*b2055c35SXin Li
229*b2055c35SXin Li // image-reading pass
230*b2055c35SXin Li pic_num = 0;
231*b2055c35SXin Li config.lossless = 1;
232*b2055c35SXin Li for (c = 0; ok && c < argc; ++c) {
233*b2055c35SXin Li if (argv[c] == NULL) continue;
234*b2055c35SXin Li if (argv[c][0] == '-') { // parse local options
235*b2055c35SXin Li int parse_error = 0;
236*b2055c35SXin Li if (!strcmp(argv[c], "-lossy")) {
237*b2055c35SXin Li if (!anim_config.allow_mixed) config.lossless = 0;
238*b2055c35SXin Li } else if (!strcmp(argv[c], "-lossless")) {
239*b2055c35SXin Li if (!anim_config.allow_mixed) config.lossless = 1;
240*b2055c35SXin Li } else if (!strcmp(argv[c], "-q") && c + 1 < argc) {
241*b2055c35SXin Li config.quality = ExUtilGetFloat(argv[++c], &parse_error);
242*b2055c35SXin Li } else if (!strcmp(argv[c], "-m") && c + 1 < argc) {
243*b2055c35SXin Li config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
244*b2055c35SXin Li } else if (!strcmp(argv[c], "-d") && c + 1 < argc) {
245*b2055c35SXin Li duration = ExUtilGetInt(argv[++c], 0, &parse_error);
246*b2055c35SXin Li if (duration <= 0) {
247*b2055c35SXin Li fprintf(stderr, "Invalid negative duration (%d)\n", duration);
248*b2055c35SXin Li parse_error = 1;
249*b2055c35SXin Li }
250*b2055c35SXin Li } else {
251*b2055c35SXin Li parse_error = 1; // shouldn't be here.
252*b2055c35SXin Li fprintf(stderr, "Unknown option [%s]\n", argv[c]);
253*b2055c35SXin Li }
254*b2055c35SXin Li ok = !parse_error;
255*b2055c35SXin Li if (!ok) goto End;
256*b2055c35SXin Li continue;
257*b2055c35SXin Li }
258*b2055c35SXin Li
259*b2055c35SXin Li if (ok) {
260*b2055c35SXin Li ok = WebPValidateConfig(&config);
261*b2055c35SXin Li if (!ok) {
262*b2055c35SXin Li fprintf(stderr, "Invalid configuration.\n");
263*b2055c35SXin Li goto End;
264*b2055c35SXin Li }
265*b2055c35SXin Li }
266*b2055c35SXin Li
267*b2055c35SXin Li // read next input image
268*b2055c35SXin Li pic.use_argb = 1;
269*b2055c35SXin Li ok = ReadImage((const char*)GET_WARGV_SHIFTED(argv, c), &pic);
270*b2055c35SXin Li if (!ok) goto End;
271*b2055c35SXin Li
272*b2055c35SXin Li if (enc == NULL) {
273*b2055c35SXin Li width = pic.width;
274*b2055c35SXin Li height = pic.height;
275*b2055c35SXin Li enc = WebPAnimEncoderNew(width, height, &anim_config);
276*b2055c35SXin Li ok = (enc != NULL);
277*b2055c35SXin Li if (!ok) {
278*b2055c35SXin Li fprintf(stderr, "Could not create WebPAnimEncoder object.\n");
279*b2055c35SXin Li }
280*b2055c35SXin Li }
281*b2055c35SXin Li
282*b2055c35SXin Li if (ok) {
283*b2055c35SXin Li ok = (width == pic.width && height == pic.height);
284*b2055c35SXin Li if (!ok) {
285*b2055c35SXin Li fprintf(stderr, "Frame #%d dimension mismatched! "
286*b2055c35SXin Li "Got %d x %d. Was expecting %d x %d.\n",
287*b2055c35SXin Li pic_num, pic.width, pic.height, width, height);
288*b2055c35SXin Li }
289*b2055c35SXin Li }
290*b2055c35SXin Li
291*b2055c35SXin Li if (ok) {
292*b2055c35SXin Li ok = WebPAnimEncoderAdd(enc, &pic, timestamp_ms, &config);
293*b2055c35SXin Li if (!ok) {
294*b2055c35SXin Li fprintf(stderr, "Error while adding frame #%d\n", pic_num);
295*b2055c35SXin Li }
296*b2055c35SXin Li }
297*b2055c35SXin Li WebPPictureFree(&pic);
298*b2055c35SXin Li if (!ok) goto End;
299*b2055c35SXin Li
300*b2055c35SXin Li if (verbose) {
301*b2055c35SXin Li WFPRINTF(stderr, "Added frame #%3d at time %4d (file: %s)\n",
302*b2055c35SXin Li pic_num, timestamp_ms, GET_WARGV_SHIFTED(argv, c));
303*b2055c35SXin Li }
304*b2055c35SXin Li timestamp_ms += duration;
305*b2055c35SXin Li ++pic_num;
306*b2055c35SXin Li }
307*b2055c35SXin Li
308*b2055c35SXin Li // add a last fake frame to signal the last duration
309*b2055c35SXin Li ok = ok && WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL);
310*b2055c35SXin Li ok = ok && WebPAnimEncoderAssemble(enc, &webp_data);
311*b2055c35SXin Li if (!ok) {
312*b2055c35SXin Li fprintf(stderr, "Error during final animation assembly.\n");
313*b2055c35SXin Li }
314*b2055c35SXin Li
315*b2055c35SXin Li End:
316*b2055c35SXin Li // free resources
317*b2055c35SXin Li WebPAnimEncoderDelete(enc);
318*b2055c35SXin Li
319*b2055c35SXin Li if (ok && loop_count > 0) { // Re-mux to add loop count.
320*b2055c35SXin Li ok = SetLoopCount(loop_count, &webp_data);
321*b2055c35SXin Li }
322*b2055c35SXin Li
323*b2055c35SXin Li if (ok) {
324*b2055c35SXin Li if (output != NULL) {
325*b2055c35SXin Li ok = ImgIoUtilWriteFile(output, webp_data.bytes, webp_data.size);
326*b2055c35SXin Li if (ok) WFPRINTF(stderr, "output file: %s ", (const W_CHAR*)output);
327*b2055c35SXin Li } else {
328*b2055c35SXin Li fprintf(stderr, "[no output file specified] ");
329*b2055c35SXin Li }
330*b2055c35SXin Li }
331*b2055c35SXin Li
332*b2055c35SXin Li if (ok) {
333*b2055c35SXin Li fprintf(stderr, "[%d frames, %u bytes].\n",
334*b2055c35SXin Li pic_num, (unsigned int)webp_data.size);
335*b2055c35SXin Li }
336*b2055c35SXin Li WebPDataClear(&webp_data);
337*b2055c35SXin Li ExUtilDeleteCommandLineArguments(&cmd_args);
338*b2055c35SXin Li FREE_WARGV_AND_RETURN(ok ? 0 : 1);
339*b2055c35SXin Li }
340