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