1 // Copyright 2010 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 // Command-line tool for decoding a WebP image. 11 // 12 // Author: Skal (pascal.massimino (at) gmail.com) 13 14 #include <assert.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #ifdef HAVE_CONFIG_H 20 #include "config.h" 21 #endif 22 23 #ifdef WEBP_HAVE_PNG 24 #include <png.h> 25 #endif 26 27 #ifdef HAVE_WINCODEC_H 28 #ifdef __MINGW32__ 29 #define INITGUID // Without this GUIDs are declared extern and fail to link 30 #endif 31 #define CINTERFACE 32 #define COBJMACROS 33 #define _WIN32_IE 0x500 // Workaround bug in shlwapi.h when compiling C++ 34 // code with COBJMACROS. 35 #include <shlwapi.h> 36 #include <windows.h> 37 #include <wincodec.h> 38 #endif 39 40 #include "webp/decode.h" 41 #include "./example_util.h" 42 #include "./stopwatch.h" 43 44 static int verbose = 0; 45 #ifndef WEBP_DLL 46 #if defined(__cplusplus) || defined(c_plusplus) 47 extern "C" { 48 #endif 49 50 extern void* VP8GetCPUInfo; // opaque forward declaration. 51 52 #if defined(__cplusplus) || defined(c_plusplus) 53 } // extern "C" 54 #endif 55 #endif // WEBP_DLL 56 57 //------------------------------------------------------------------------------ 58 59 // Output types 60 typedef enum { 61 PNG = 0, 62 PAM, 63 PPM, 64 PGM, 65 YUV, 66 ALPHA_PLANE_ONLY // this is for experimenting only 67 } OutputFileFormat; 68 69 #ifdef HAVE_WINCODEC_H 70 71 #define IFS(fn) \ 72 do { \ 73 if (SUCCEEDED(hr)) { \ 74 hr = (fn); \ 75 if (FAILED(hr)) fprintf(stderr, #fn " failed %08lx\n", hr); \ 76 } \ 77 } while (0) 78 79 #ifdef __cplusplus 80 #define MAKE_REFGUID(x) (x) 81 #else 82 #define MAKE_REFGUID(x) &(x) 83 #endif 84 85 static HRESULT CreateOutputStream(const char* out_file_name, IStream** stream) { 86 HRESULT hr = S_OK; 87 IFS(SHCreateStreamOnFileA(out_file_name, STGM_WRITE | STGM_CREATE, stream)); 88 if (FAILED(hr)) { 89 fprintf(stderr, "Error opening output file %s (%08lx)\n", 90 out_file_name, hr); 91 } 92 return hr; 93 } 94 95 static HRESULT WriteUsingWIC(const char* out_file_name, REFGUID container_guid, 96 unsigned char* rgb, int stride, 97 uint32_t width, uint32_t height, int has_alpha) { 98 HRESULT hr = S_OK; 99 IWICImagingFactory* factory = NULL; 100 IWICBitmapFrameEncode* frame = NULL; 101 IWICBitmapEncoder* encoder = NULL; 102 IStream* stream = NULL; 103 WICPixelFormatGUID pixel_format = has_alpha ? GUID_WICPixelFormat32bppBGRA 104 : GUID_WICPixelFormat24bppBGR; 105 106 IFS(CoInitialize(NULL)); 107 IFS(CoCreateInstance(MAKE_REFGUID(CLSID_WICImagingFactory), NULL, 108 CLSCTX_INPROC_SERVER, 109 MAKE_REFGUID(IID_IWICImagingFactory), 110 (LPVOID*)&factory)); 111 if (hr == REGDB_E_CLASSNOTREG) { 112 fprintf(stderr, 113 "Couldn't access Windows Imaging Component (are you running " 114 "Windows XP SP3 or newer?). PNG support not available. " 115 "Use -ppm or -pgm for available PPM and PGM formats.\n"); 116 } 117 IFS(CreateOutputStream(out_file_name, &stream)); 118 IFS(IWICImagingFactory_CreateEncoder(factory, container_guid, NULL, 119 &encoder)); 120 IFS(IWICBitmapEncoder_Initialize(encoder, stream, 121 WICBitmapEncoderNoCache)); 122 IFS(IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL)); 123 IFS(IWICBitmapFrameEncode_Initialize(frame, NULL)); 124 IFS(IWICBitmapFrameEncode_SetSize(frame, width, height)); 125 IFS(IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format)); 126 IFS(IWICBitmapFrameEncode_WritePixels(frame, height, stride, 127 height * stride, rgb)); 128 IFS(IWICBitmapFrameEncode_Commit(frame)); 129 IFS(IWICBitmapEncoder_Commit(encoder)); 130 131 if (frame != NULL) IUnknown_Release(frame); 132 if (encoder != NULL) IUnknown_Release(encoder); 133 if (factory != NULL) IUnknown_Release(factory); 134 if (stream != NULL) IUnknown_Release(stream); 135 return hr; 136 } 137 138 static int WritePNG(const char* out_file_name, 139 const WebPDecBuffer* const buffer) { 140 const uint32_t width = buffer->width; 141 const uint32_t height = buffer->height; 142 unsigned char* const rgb = buffer->u.RGBA.rgba; 143 const int stride = buffer->u.RGBA.stride; 144 const int has_alpha = (buffer->colorspace == MODE_BGRA); 145 146 return SUCCEEDED(WriteUsingWIC(out_file_name, 147 MAKE_REFGUID(GUID_ContainerFormatPng), 148 rgb, stride, width, height, has_alpha)); 149 } 150 151 #elif defined(WEBP_HAVE_PNG) // !HAVE_WINCODEC_H 152 static void PNGAPI error_function(png_structp png, png_const_charp dummy) { 153 (void)dummy; // remove variable-unused warning 154 longjmp(png_jmpbuf(png), 1); 155 } 156 157 static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) { 158 const uint32_t width = buffer->width; 159 const uint32_t height = buffer->height; 160 unsigned char* const rgb = buffer->u.RGBA.rgba; 161 const int stride = buffer->u.RGBA.stride; 162 const int has_alpha = (buffer->colorspace == MODE_RGBA); 163 png_structp png; 164 png_infop info; 165 png_uint_32 y; 166 167 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 168 NULL, error_function, NULL); 169 if (png == NULL) { 170 return 0; 171 } 172 info = png_create_info_struct(png); 173 if (info == NULL) { 174 png_destroy_write_struct(&png, NULL); 175 return 0; 176 } 177 if (setjmp(png_jmpbuf(png))) { 178 png_destroy_write_struct(&png, &info); 179 return 0; 180 } 181 png_init_io(png, out_file); 182 png_set_IHDR(png, info, width, height, 8, 183 has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, 184 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, 185 PNG_FILTER_TYPE_DEFAULT); 186 png_write_info(png, info); 187 for (y = 0; y < height; ++y) { 188 png_bytep row = rgb + y * stride; 189 png_write_rows(png, &row, 1); 190 } 191 png_write_end(png, info); 192 png_destroy_write_struct(&png, &info); 193 return 1; 194 } 195 #else // !HAVE_WINCODEC_H && !WEBP_HAVE_PNG 196 static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) { 197 (void)out_file; 198 (void)buffer; 199 fprintf(stderr, "PNG support not compiled. Please install the libpng " 200 "development package before building.\n"); 201 fprintf(stderr, "You can run with -ppm flag to decode in PPM format.\n"); 202 return 0; 203 } 204 #endif 205 206 static int WritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha) { 207 const uint32_t width = buffer->width; 208 const uint32_t height = buffer->height; 209 const unsigned char* const rgb = buffer->u.RGBA.rgba; 210 const int stride = buffer->u.RGBA.stride; 211 const size_t bytes_per_px = alpha ? 4 : 3; 212 uint32_t y; 213 214 if (alpha) { 215 fprintf(fout, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" 216 "TUPLTYPE RGB_ALPHA\nENDHDR\n", width, height); 217 } else { 218 fprintf(fout, "P6\n%d %d\n255\n", width, height); 219 } 220 for (y = 0; y < height; ++y) { 221 if (fwrite(rgb + y * stride, width, bytes_per_px, fout) != bytes_per_px) { 222 return 0; 223 } 224 } 225 return 1; 226 } 227 228 static int WriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) { 229 const uint32_t width = buffer->width; 230 const uint32_t height = buffer->height; 231 const unsigned char* const a = buffer->u.YUVA.a; 232 const int a_stride = buffer->u.YUVA.a_stride; 233 uint32_t y; 234 assert(a != NULL); 235 fprintf(fout, "P5\n%d %d\n255\n", width, height); 236 for (y = 0; y < height; ++y) { 237 if (fwrite(a + y * a_stride, width, 1, fout) != 1) { 238 return 0; 239 } 240 } 241 return 1; 242 } 243 244 // format=PGM: save a grayscale PGM file using the IMC4 layout 245 // (http://www.fourcc.org/yuv.php#IMC4). This is a very convenient format for 246 // viewing the samples, esp. for odd dimensions. 247 // format=YUV: just save the Y/U/V/A planes sequentially without header. 248 static int WritePGMOrYUV(FILE* fout, const WebPDecBuffer* const buffer, 249 OutputFileFormat format) { 250 const int width = buffer->width; 251 const int height = buffer->height; 252 const WebPYUVABuffer* const yuv = &buffer->u.YUVA; 253 int ok = 1; 254 int y; 255 const int pad = (format == YUV) ? 0 : 1; 256 const int uv_width = (width + 1) / 2; 257 const int uv_height = (height + 1) / 2; 258 const int out_stride = (width + pad) & ~pad; 259 const int a_height = yuv->a ? height : 0; 260 if (format == PGM) { 261 fprintf(fout, "P5\n%d %d\n255\n", 262 out_stride, height + uv_height + a_height); 263 } 264 for (y = 0; ok && y < height; ++y) { 265 ok &= (fwrite(yuv->y + y * yuv->y_stride, width, 1, fout) == 1); 266 if (format == PGM) { 267 if (width & 1) fputc(0, fout); // padding byte 268 } 269 } 270 if (format == PGM) { // IMC4 layout 271 for (y = 0; ok && y < uv_height; ++y) { 272 ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1); 273 ok &= (fwrite(yuv->v + y * yuv->v_stride, uv_width, 1, fout) == 1); 274 } 275 } else { 276 for (y = 0; ok && y < uv_height; ++y) { 277 ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1); 278 } 279 for (y = 0; ok && y < uv_height; ++y) { 280 ok &= (fwrite(yuv->v + y * yuv->v_stride, uv_width, 1, fout) == 1); 281 } 282 } 283 for (y = 0; ok && y < a_height; ++y) { 284 ok &= (fwrite(yuv->a + y * yuv->a_stride, width, 1, fout) == 1); 285 if (format == PGM) { 286 if (width & 1) fputc(0, fout); // padding byte 287 } 288 } 289 return ok; 290 } 291 292 static void SaveOutput(const WebPDecBuffer* const buffer, 293 OutputFileFormat format, const char* const out_file) { 294 FILE* fout = NULL; 295 int needs_open_file = 1; 296 int ok = 1; 297 Stopwatch stop_watch; 298 299 if (verbose) 300 StopwatchReadAndReset(&stop_watch); 301 302 #ifdef HAVE_WINCODEC_H 303 needs_open_file = (format != PNG); 304 #endif 305 if (needs_open_file) { 306 fout = fopen(out_file, "wb"); 307 if (!fout) { 308 fprintf(stderr, "Error opening output file %s\n", out_file); 309 return; 310 } 311 } 312 313 if (format == PNG) { 314 #ifdef HAVE_WINCODEC_H 315 ok &= WritePNG(out_file, buffer); 316 #else 317 ok &= WritePNG(fout, buffer); 318 #endif 319 } else if (format == PAM) { 320 ok &= WritePPM(fout, buffer, 1); 321 } else if (format == PPM) { 322 ok &= WritePPM(fout, buffer, 0); 323 } else if (format == PGM || format == YUV) { 324 ok &= WritePGMOrYUV(fout, buffer, format); 325 } else if (format == ALPHA_PLANE_ONLY) { 326 ok &= WriteAlphaPlane(fout, buffer); 327 } 328 if (fout) { 329 fclose(fout); 330 } 331 if (ok) { 332 printf("Saved file %s\n", out_file); 333 if (verbose) { 334 const double write_time = StopwatchReadAndReset(&stop_watch); 335 printf("Time to write output: %.3fs\n", write_time); 336 } 337 } else { 338 fprintf(stderr, "Error writing file %s !!\n", out_file); 339 } 340 } 341 342 static void Help(void) { 343 printf("Usage: dwebp in_file [options] [-o out_file]\n\n" 344 "Decodes the WebP image file to PNG format [Default]\n" 345 "Use following options to convert into alternate image formats:\n" 346 " -pam ......... save the raw RGBA samples as a color PAM\n" 347 " -ppm ......... save the raw RGB samples as a color PPM\n" 348 " -pgm ......... save the raw YUV samples as a grayscale PGM\n" 349 " file with IMC4 layout.\n" 350 " -yuv ......... save the raw YUV samples in flat layout.\n" 351 "\n" 352 " Other options are:\n" 353 " -version .... print version number and exit.\n" 354 " -nofancy ..... don't use the fancy YUV420 upscaler.\n" 355 " -nofilter .... disable in-loop filtering.\n" 356 " -mt .......... use multi-threading\n" 357 " -crop <x> <y> <w> <h> ... crop output with the given rectangle\n" 358 " -scale <w> <h> .......... scale the output (*after* any cropping)\n" 359 " -alpha ....... only save the alpha plane.\n" 360 " -h ....... this help message.\n" 361 " -v ....... verbose (e.g. print encoding/decoding times)\n" 362 #ifndef WEBP_DLL 363 " -noasm ....... disable all assembly optimizations.\n" 364 #endif 365 ); 366 } 367 368 static const char* const kStatusMessages[] = { 369 "OK", "OUT_OF_MEMORY", "INVALID_PARAM", "BITSTREAM_ERROR", 370 "UNSUPPORTED_FEATURE", "SUSPENDED", "USER_ABORT", "NOT_ENOUGH_DATA" 371 }; 372 373 int main(int argc, const char *argv[]) { 374 const char *in_file = NULL; 375 const char *out_file = NULL; 376 377 WebPDecoderConfig config; 378 WebPDecBuffer* const output_buffer = &config.output; 379 WebPBitstreamFeatures* const bitstream = &config.input; 380 OutputFileFormat format = PNG; 381 int c; 382 383 if (!WebPInitDecoderConfig(&config)) { 384 fprintf(stderr, "Library version mismatch!\n"); 385 return -1; 386 } 387 388 for (c = 1; c < argc; ++c) { 389 if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { 390 Help(); 391 return 0; 392 } else if (!strcmp(argv[c], "-o") && c < argc - 1) { 393 out_file = argv[++c]; 394 } else if (!strcmp(argv[c], "-alpha")) { 395 format = ALPHA_PLANE_ONLY; 396 } else if (!strcmp(argv[c], "-nofancy")) { 397 config.options.no_fancy_upsampling = 1; 398 } else if (!strcmp(argv[c], "-nofilter")) { 399 config.options.bypass_filtering = 1; 400 } else if (!strcmp(argv[c], "-pam")) { 401 format = PAM; 402 } else if (!strcmp(argv[c], "-ppm")) { 403 format = PPM; 404 } else if (!strcmp(argv[c], "-version")) { 405 const int version = WebPGetDecoderVersion(); 406 printf("%d.%d.%d\n", 407 (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 408 return 0; 409 } else if (!strcmp(argv[c], "-pgm")) { 410 format = PGM; 411 } else if (!strcmp(argv[c], "-yuv")) { 412 format = YUV; 413 } else if (!strcmp(argv[c], "-mt")) { 414 config.options.use_threads = 1; 415 } else if (!strcmp(argv[c], "-crop") && c < argc - 4) { 416 config.options.use_cropping = 1; 417 config.options.crop_left = strtol(argv[++c], NULL, 0); 418 config.options.crop_top = strtol(argv[++c], NULL, 0); 419 config.options.crop_width = strtol(argv[++c], NULL, 0); 420 config.options.crop_height = strtol(argv[++c], NULL, 0); 421 } else if (!strcmp(argv[c], "-scale") && c < argc - 2) { 422 config.options.use_scaling = 1; 423 config.options.scaled_width = strtol(argv[++c], NULL, 0); 424 config.options.scaled_height = strtol(argv[++c], NULL, 0); 425 } else if (!strcmp(argv[c], "-v")) { 426 verbose = 1; 427 #ifndef WEBP_DLL 428 } else if (!strcmp(argv[c], "-noasm")) { 429 VP8GetCPUInfo = NULL; 430 #endif 431 } else if (argv[c][0] == '-') { 432 fprintf(stderr, "Unknown option '%s'\n", argv[c]); 433 Help(); 434 return -1; 435 } else { 436 in_file = argv[c]; 437 } 438 } 439 440 if (in_file == NULL) { 441 fprintf(stderr, "missing input file!!\n"); 442 Help(); 443 return -1; 444 } 445 446 { 447 Stopwatch stop_watch; 448 VP8StatusCode status = VP8_STATUS_OK; 449 int ok; 450 size_t data_size = 0; 451 const uint8_t* data = NULL; 452 453 if (!ExUtilReadFile(in_file, &data, &data_size)) return -1; 454 455 if (verbose) 456 StopwatchReadAndReset(&stop_watch); 457 458 status = WebPGetFeatures(data, data_size, bitstream); 459 if (status != VP8_STATUS_OK) { 460 goto end; 461 } 462 463 if (bitstream->has_animation) { 464 fprintf(stderr, 465 "Error! Decoding of an animated WebP file is not supported.\n" 466 " Use webpmux to extract the individual frames or\n" 467 " vwebp to view this image.\n"); 468 } 469 470 switch (format) { 471 case PNG: 472 #ifdef HAVE_WINCODEC_H 473 output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR; 474 #else 475 output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB; 476 #endif 477 break; 478 case PAM: 479 output_buffer->colorspace = MODE_RGBA; 480 break; 481 case PPM: 482 output_buffer->colorspace = MODE_RGB; // drops alpha for PPM 483 break; 484 case PGM: 485 case YUV: 486 output_buffer->colorspace = bitstream->has_alpha ? MODE_YUVA : MODE_YUV; 487 break; 488 case ALPHA_PLANE_ONLY: 489 output_buffer->colorspace = MODE_YUVA; 490 break; 491 default: 492 free((void*)data); 493 return -1; 494 } 495 status = WebPDecode(data, data_size, &config); 496 497 if (verbose) { 498 const double decode_time = StopwatchReadAndReset(&stop_watch); 499 printf("Time to decode picture: %.3fs\n", decode_time); 500 } 501 end: 502 free((void*)data); 503 ok = (status == VP8_STATUS_OK); 504 if (!ok) { 505 fprintf(stderr, "Decoding of %s failed.\n", in_file); 506 fprintf(stderr, "Status: %d (%s)\n", status, kStatusMessages[status]); 507 return -1; 508 } 509 } 510 511 if (out_file) { 512 printf("Decoded %s. Dimensions: %d x %d%s. Now saving...\n", in_file, 513 output_buffer->width, output_buffer->height, 514 bitstream->has_alpha ? " (with alpha)" : ""); 515 SaveOutput(output_buffer, format, out_file); 516 } else { 517 printf("File %s can be decoded (dimensions: %d x %d)%s.\n", 518 in_file, output_buffer->width, output_buffer->height, 519 bitstream->has_alpha ? " (with alpha)" : ""); 520 printf("Nothing written; use -o flag to save the result as e.g. PNG.\n"); 521 } 522 WebPFreeDecBuffer(output_buffer); 523 524 return 0; 525 } 526 527 //------------------------------------------------------------------------------ 528