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