e0609ade15
on ExUtilLoadWebP() failure no allocated memory will be returned, so
it's safe to exit immediately. additionally, any webp specific problems
will already have been reported as part of the call to
WebPGetFeatures().
broken since:
4a0e739
dwebp: move webp decoding to example_util
Change-Id: Ibc632015a1f52bae7f96d063252624123fa7c2da
745 lines
25 KiB
C
745 lines
25 KiB
C
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Command-line tool for decoding a WebP image.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef WEBP_HAVE_PNG
|
|
#include <png.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_WINCODEC_H
|
|
#ifdef __MINGW32__
|
|
#define INITGUID // Without this GUIDs are declared extern and fail to link
|
|
#endif
|
|
#define CINTERFACE
|
|
#define COBJMACROS
|
|
#define _WIN32_IE 0x500 // Workaround bug in shlwapi.h when compiling C++
|
|
// code with COBJMACROS.
|
|
#include <ole2.h> // CreateStreamOnHGlobal()
|
|
#include <shlwapi.h>
|
|
#include <windows.h>
|
|
#include <wincodec.h>
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#include <fcntl.h> // for _O_BINARY
|
|
#include <io.h> // for _setmode()
|
|
#endif
|
|
|
|
#include "webp/decode.h"
|
|
#include "./example_util.h"
|
|
#include "./stopwatch.h"
|
|
|
|
static int verbose = 0;
|
|
#ifndef WEBP_DLL
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
extern void* VP8GetCPUInfo; // opaque forward declaration.
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
#endif // WEBP_DLL
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Output types
|
|
typedef enum {
|
|
PNG = 0,
|
|
PAM,
|
|
PPM,
|
|
PGM,
|
|
BMP,
|
|
TIFF,
|
|
YUV,
|
|
ALPHA_PLANE_ONLY // this is for experimenting only
|
|
} OutputFileFormat;
|
|
|
|
#ifdef HAVE_WINCODEC_H
|
|
|
|
#define IFS(fn) \
|
|
do { \
|
|
if (SUCCEEDED(hr)) { \
|
|
hr = (fn); \
|
|
if (FAILED(hr)) fprintf(stderr, #fn " failed %08lx\n", hr); \
|
|
} \
|
|
} while (0)
|
|
|
|
#ifdef __cplusplus
|
|
#define MAKE_REFGUID(x) (x)
|
|
#else
|
|
#define MAKE_REFGUID(x) &(x)
|
|
#endif
|
|
|
|
static HRESULT CreateOutputStream(const char* out_file_name,
|
|
int write_to_mem, IStream** stream) {
|
|
HRESULT hr = S_OK;
|
|
if (write_to_mem) {
|
|
// Output to a memory buffer. This is freed when 'stream' is released.
|
|
IFS(CreateStreamOnHGlobal(NULL, TRUE, stream));
|
|
} else {
|
|
IFS(SHCreateStreamOnFileA(out_file_name, STGM_WRITE | STGM_CREATE, stream));
|
|
}
|
|
if (FAILED(hr)) {
|
|
fprintf(stderr, "Error opening output file %s (%08lx)\n",
|
|
out_file_name, hr);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WriteUsingWIC(const char* out_file_name, int use_stdout,
|
|
REFGUID container_guid,
|
|
uint8_t* rgb, int stride,
|
|
uint32_t width, uint32_t height, int has_alpha) {
|
|
HRESULT hr = S_OK;
|
|
IWICImagingFactory* factory = NULL;
|
|
IWICBitmapFrameEncode* frame = NULL;
|
|
IWICBitmapEncoder* encoder = NULL;
|
|
IStream* stream = NULL;
|
|
WICPixelFormatGUID pixel_format = has_alpha ? GUID_WICPixelFormat32bppBGRA
|
|
: GUID_WICPixelFormat24bppBGR;
|
|
|
|
IFS(CoInitialize(NULL));
|
|
IFS(CoCreateInstance(MAKE_REFGUID(CLSID_WICImagingFactory), NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
MAKE_REFGUID(IID_IWICImagingFactory),
|
|
(LPVOID*)&factory));
|
|
if (hr == REGDB_E_CLASSNOTREG) {
|
|
fprintf(stderr,
|
|
"Couldn't access Windows Imaging Component (are you running "
|
|
"Windows XP SP3 or newer?). PNG support not available. "
|
|
"Use -ppm or -pgm for available PPM and PGM formats.\n");
|
|
}
|
|
IFS(CreateOutputStream(out_file_name, use_stdout, &stream));
|
|
IFS(IWICImagingFactory_CreateEncoder(factory, container_guid, NULL,
|
|
&encoder));
|
|
IFS(IWICBitmapEncoder_Initialize(encoder, stream,
|
|
WICBitmapEncoderNoCache));
|
|
IFS(IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL));
|
|
IFS(IWICBitmapFrameEncode_Initialize(frame, NULL));
|
|
IFS(IWICBitmapFrameEncode_SetSize(frame, width, height));
|
|
IFS(IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format));
|
|
IFS(IWICBitmapFrameEncode_WritePixels(frame, height, stride,
|
|
height * stride, rgb));
|
|
IFS(IWICBitmapFrameEncode_Commit(frame));
|
|
IFS(IWICBitmapEncoder_Commit(encoder));
|
|
|
|
if (SUCCEEDED(hr) && use_stdout) {
|
|
HGLOBAL image;
|
|
IFS(GetHGlobalFromStream(stream, &image));
|
|
if (SUCCEEDED(hr)) {
|
|
HANDLE std_output = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
DWORD mode;
|
|
const BOOL update_mode = GetConsoleMode(std_output, &mode);
|
|
const void* const image_mem = GlobalLock(image);
|
|
DWORD bytes_written = 0;
|
|
|
|
// Clear output processing if necessary, then output the image.
|
|
if (update_mode) SetConsoleMode(std_output, 0);
|
|
if (!WriteFile(std_output, image_mem, (DWORD)GlobalSize(image),
|
|
&bytes_written, NULL) ||
|
|
bytes_written != GlobalSize(image)) {
|
|
hr = E_FAIL;
|
|
}
|
|
if (update_mode) SetConsoleMode(std_output, mode);
|
|
GlobalUnlock(image);
|
|
}
|
|
}
|
|
|
|
if (frame != NULL) IUnknown_Release(frame);
|
|
if (encoder != NULL) IUnknown_Release(encoder);
|
|
if (factory != NULL) IUnknown_Release(factory);
|
|
if (stream != NULL) IUnknown_Release(stream);
|
|
return hr;
|
|
}
|
|
|
|
static int WritePNG(const char* out_file_name, int use_stdout,
|
|
const WebPDecBuffer* const buffer) {
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
uint8_t* const rgb = buffer->u.RGBA.rgba;
|
|
const int stride = buffer->u.RGBA.stride;
|
|
const int has_alpha = (buffer->colorspace == MODE_BGRA);
|
|
|
|
return SUCCEEDED(WriteUsingWIC(out_file_name, use_stdout,
|
|
MAKE_REFGUID(GUID_ContainerFormatPng),
|
|
rgb, stride, width, height, has_alpha));
|
|
}
|
|
|
|
#elif defined(WEBP_HAVE_PNG) // !HAVE_WINCODEC_H
|
|
static void PNGAPI PNGErrorFunction(png_structp png, png_const_charp dummy) {
|
|
(void)dummy; // remove variable-unused warning
|
|
longjmp(png_jmpbuf(png), 1);
|
|
}
|
|
|
|
static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) {
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
uint8_t* const rgb = buffer->u.RGBA.rgba;
|
|
const int stride = buffer->u.RGBA.stride;
|
|
const int has_alpha = (buffer->colorspace == MODE_RGBA);
|
|
png_structp png;
|
|
png_infop info;
|
|
png_uint_32 y;
|
|
|
|
png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
|
NULL, PNGErrorFunction, NULL);
|
|
if (png == NULL) {
|
|
return 0;
|
|
}
|
|
info = png_create_info_struct(png);
|
|
if (info == NULL) {
|
|
png_destroy_write_struct(&png, NULL);
|
|
return 0;
|
|
}
|
|
if (setjmp(png_jmpbuf(png))) {
|
|
png_destroy_write_struct(&png, &info);
|
|
return 0;
|
|
}
|
|
png_init_io(png, out_file);
|
|
png_set_IHDR(png, info, width, height, 8,
|
|
has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB,
|
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
for (y = 0; y < height; ++y) {
|
|
png_bytep row = rgb + y * stride;
|
|
png_write_rows(png, &row, 1);
|
|
}
|
|
png_write_end(png, info);
|
|
png_destroy_write_struct(&png, &info);
|
|
return 1;
|
|
}
|
|
#else // !HAVE_WINCODEC_H && !WEBP_HAVE_PNG
|
|
static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) {
|
|
(void)out_file;
|
|
(void)buffer;
|
|
fprintf(stderr, "PNG support not compiled. Please install the libpng "
|
|
"development package before building.\n");
|
|
fprintf(stderr, "You can run with -ppm flag to decode in PPM format.\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int WritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha) {
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
const uint8_t* const rgb = buffer->u.RGBA.rgba;
|
|
const int stride = buffer->u.RGBA.stride;
|
|
const size_t bytes_per_px = alpha ? 4 : 3;
|
|
uint32_t y;
|
|
|
|
if (alpha) {
|
|
fprintf(fout, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n"
|
|
"TUPLTYPE RGB_ALPHA\nENDHDR\n", width, height);
|
|
} else {
|
|
fprintf(fout, "P6\n%d %d\n255\n", width, height);
|
|
}
|
|
for (y = 0; y < height; ++y) {
|
|
if (fwrite(rgb + y * stride, width, bytes_per_px, fout) != bytes_per_px) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void PutLE16(uint8_t* const dst, uint32_t value) {
|
|
dst[0] = (value >> 0) & 0xff;
|
|
dst[1] = (value >> 8) & 0xff;
|
|
}
|
|
|
|
static void PutLE32(uint8_t* const dst, uint32_t value) {
|
|
PutLE16(dst + 0, (value >> 0) & 0xffff);
|
|
PutLE16(dst + 2, (value >> 16) & 0xffff);
|
|
}
|
|
|
|
#define BMP_HEADER_SIZE 54
|
|
static int WriteBMP(FILE* fout, const WebPDecBuffer* const buffer) {
|
|
const int has_alpha = (buffer->colorspace != MODE_BGR);
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
const uint8_t* const rgba = buffer->u.RGBA.rgba;
|
|
const int stride = buffer->u.RGBA.stride;
|
|
const uint32_t bytes_per_px = has_alpha ? 4 : 3;
|
|
uint32_t y;
|
|
const uint32_t line_size = bytes_per_px * width;
|
|
const uint32_t bmp_stride = (line_size + 3) & ~3; // pad to 4
|
|
const uint32_t total_size = bmp_stride * height + BMP_HEADER_SIZE;
|
|
uint8_t bmp_header[BMP_HEADER_SIZE] = { 0 };
|
|
|
|
// bitmap file header
|
|
PutLE16(bmp_header + 0, 0x4d42); // signature 'BM'
|
|
PutLE32(bmp_header + 2, total_size); // size including header
|
|
PutLE32(bmp_header + 6, 0); // reserved
|
|
PutLE32(bmp_header + 10, BMP_HEADER_SIZE); // offset to pixel array
|
|
// bitmap info header
|
|
PutLE32(bmp_header + 14, 40); // DIB header size
|
|
PutLE32(bmp_header + 18, width); // dimensions
|
|
PutLE32(bmp_header + 22, -(int)height); // vertical flip!
|
|
PutLE16(bmp_header + 26, 1); // number of planes
|
|
PutLE16(bmp_header + 28, bytes_per_px * 8); // bits per pixel
|
|
PutLE32(bmp_header + 30, 0); // no compression (BI_RGB)
|
|
PutLE32(bmp_header + 34, 0); // image size (dummy)
|
|
PutLE32(bmp_header + 38, 2400); // x pixels/meter
|
|
PutLE32(bmp_header + 42, 2400); // y pixels/meter
|
|
PutLE32(bmp_header + 46, 0); // number of palette colors
|
|
PutLE32(bmp_header + 50, 0); // important color count
|
|
|
|
// TODO(skal): color profile
|
|
|
|
// write header
|
|
if (fwrite(bmp_header, sizeof(bmp_header), 1, fout) != 1) {
|
|
return 0;
|
|
}
|
|
|
|
// write pixel array
|
|
for (y = 0; y < height; ++y) {
|
|
if (fwrite(rgba + y * stride, line_size, 1, fout) != 1) {
|
|
return 0;
|
|
}
|
|
// write padding zeroes
|
|
if (bmp_stride != line_size) {
|
|
const uint8_t zeroes[3] = { 0 };
|
|
if (fwrite(zeroes, bmp_stride - line_size, 1, fout) != 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
#undef BMP_HEADER_SIZE
|
|
|
|
#define NUM_IFD_ENTRIES 15
|
|
#define EXTRA_DATA_SIZE 16
|
|
// 10b for signature/header + n * 12b entries + 4b for IFD terminator:
|
|
#define EXTRA_DATA_OFFSET (10 + 12 * NUM_IFD_ENTRIES + 4)
|
|
#define TIFF_HEADER_SIZE (EXTRA_DATA_OFFSET + EXTRA_DATA_SIZE)
|
|
|
|
static int WriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) {
|
|
const int has_alpha = (buffer->colorspace != MODE_RGB);
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
const uint8_t* const rgba = buffer->u.RGBA.rgba;
|
|
const int stride = buffer->u.RGBA.stride;
|
|
const uint8_t bytes_per_px = has_alpha ? 4 : 3;
|
|
// For non-alpha case, we omit tag 0x152 (ExtraSamples).
|
|
const uint8_t num_ifd_entries = has_alpha ? NUM_IFD_ENTRIES
|
|
: NUM_IFD_ENTRIES - 1;
|
|
uint8_t tiff_header[TIFF_HEADER_SIZE] = {
|
|
0x49, 0x49, 0x2a, 0x00, // little endian signature
|
|
8, 0, 0, 0, // offset to the unique IFD that follows
|
|
// IFD (offset = 8). Entries must be written in increasing tag order.
|
|
num_ifd_entries, 0, // Number of entries in the IFD (12 bytes each).
|
|
0x00, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 10: Width (TBD)
|
|
0x01, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 22: Height (TBD)
|
|
0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0, // 34: BitsPerSample: 8888
|
|
EXTRA_DATA_OFFSET + 0, 0, 0, 0,
|
|
0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 46: Compression: none
|
|
0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 58: Photometric: RGB
|
|
0x11, 0x01, 4, 0, 1, 0, 0, 0, // 70: Strips offset:
|
|
TIFF_HEADER_SIZE, 0, 0, 0, // data follows header
|
|
0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 82: Orientation: topleft
|
|
0x15, 0x01, 3, 0, 1, 0, 0, 0, // 94: SamplesPerPixels
|
|
bytes_per_px, 0, 0, 0,
|
|
0x16, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 106: Rows per strip (TBD)
|
|
0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 118: StripByteCount (TBD)
|
|
0x1a, 0x01, 5, 0, 1, 0, 0, 0, // 130: X-resolution
|
|
EXTRA_DATA_OFFSET + 8, 0, 0, 0,
|
|
0x1b, 0x01, 5, 0, 1, 0, 0, 0, // 142: Y-resolution
|
|
EXTRA_DATA_OFFSET + 8, 0, 0, 0,
|
|
0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration
|
|
0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch)
|
|
0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA
|
|
0, 0, 0, 0, // 190: IFD terminator
|
|
// EXTRA_DATA_OFFSET:
|
|
8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample
|
|
72, 0, 0, 0, 1, 0, 0, 0 // 72 pixels/inch, for X/Y-resolution
|
|
};
|
|
uint32_t y;
|
|
|
|
// Fill placeholders in IFD:
|
|
PutLE32(tiff_header + 10 + 8, width);
|
|
PutLE32(tiff_header + 22 + 8, height);
|
|
PutLE32(tiff_header + 106 + 8, height);
|
|
PutLE32(tiff_header + 118 + 8, width * bytes_per_px * height);
|
|
if (!has_alpha) PutLE32(tiff_header + 178, 0); // IFD terminator
|
|
|
|
// write header
|
|
if (fwrite(tiff_header, sizeof(tiff_header), 1, fout) != 1) {
|
|
return 0;
|
|
}
|
|
// write pixel values
|
|
for (y = 0; y < height; ++y) {
|
|
if (fwrite(rgba + y * stride, bytes_per_px, width, fout) != width) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#undef TIFF_HEADER_SIZE
|
|
#undef EXTRA_DATA_OFFSET
|
|
#undef EXTRA_DATA_SIZE
|
|
#undef NUM_IFD_ENTRIES
|
|
|
|
static int WriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) {
|
|
const uint32_t width = buffer->width;
|
|
const uint32_t height = buffer->height;
|
|
const uint8_t* const a = buffer->u.YUVA.a;
|
|
const int a_stride = buffer->u.YUVA.a_stride;
|
|
uint32_t y;
|
|
assert(a != NULL);
|
|
fprintf(fout, "P5\n%d %d\n255\n", width, height);
|
|
for (y = 0; y < height; ++y) {
|
|
if (fwrite(a + y * a_stride, width, 1, fout) != 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// format=PGM: save a grayscale PGM file using the IMC4 layout
|
|
// (http://www.fourcc.org/yuv.php#IMC4). This is a very convenient format for
|
|
// viewing the samples, esp. for odd dimensions.
|
|
// format=YUV: just save the Y/U/V/A planes sequentially without header.
|
|
static int WritePGMOrYUV(FILE* fout, const WebPDecBuffer* const buffer,
|
|
OutputFileFormat format) {
|
|
const int width = buffer->width;
|
|
const int height = buffer->height;
|
|
const WebPYUVABuffer* const yuv = &buffer->u.YUVA;
|
|
int ok = 1;
|
|
int y;
|
|
const int pad = (format == YUV) ? 0 : 1;
|
|
const int uv_width = (width + 1) / 2;
|
|
const int uv_height = (height + 1) / 2;
|
|
const int out_stride = (width + pad) & ~pad;
|
|
const int a_height = yuv->a ? height : 0;
|
|
if (format == PGM) {
|
|
fprintf(fout, "P5\n%d %d\n255\n",
|
|
out_stride, height + uv_height + a_height);
|
|
}
|
|
for (y = 0; ok && y < height; ++y) {
|
|
ok &= (fwrite(yuv->y + y * yuv->y_stride, width, 1, fout) == 1);
|
|
if (format == PGM) {
|
|
if (width & 1) fputc(0, fout); // padding byte
|
|
}
|
|
}
|
|
if (format == PGM) { // IMC4 layout
|
|
for (y = 0; ok && y < uv_height; ++y) {
|
|
ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1);
|
|
ok &= (fwrite(yuv->v + y * yuv->v_stride, uv_width, 1, fout) == 1);
|
|
}
|
|
} else {
|
|
for (y = 0; ok && y < uv_height; ++y) {
|
|
ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1);
|
|
}
|
|
for (y = 0; ok && y < uv_height; ++y) {
|
|
ok &= (fwrite(yuv->v + y * yuv->v_stride, uv_width, 1, fout) == 1);
|
|
}
|
|
}
|
|
for (y = 0; ok && y < a_height; ++y) {
|
|
ok &= (fwrite(yuv->a + y * yuv->a_stride, width, 1, fout) == 1);
|
|
if (format == PGM) {
|
|
if (width & 1) fputc(0, fout); // padding byte
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int SaveOutput(const WebPDecBuffer* const buffer,
|
|
OutputFileFormat format, const char* const out_file) {
|
|
FILE* fout = NULL;
|
|
int needs_open_file = 1;
|
|
const int use_stdout = !strcmp(out_file, "-");
|
|
int ok = 1;
|
|
Stopwatch stop_watch;
|
|
|
|
if (verbose) {
|
|
StopwatchReset(&stop_watch);
|
|
}
|
|
|
|
#ifdef HAVE_WINCODEC_H
|
|
needs_open_file = (format != PNG);
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
if (use_stdout && _setmode(_fileno(stdout), _O_BINARY) == -1) {
|
|
fprintf(stderr, "Failed to reopen stdout in O_BINARY mode.\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (needs_open_file) {
|
|
fout = use_stdout ? stdout : fopen(out_file, "wb");
|
|
if (fout == NULL) {
|
|
fprintf(stderr, "Error opening output file %s\n", out_file);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (format == PNG) {
|
|
#ifdef HAVE_WINCODEC_H
|
|
ok &= WritePNG(out_file, use_stdout, buffer);
|
|
#else
|
|
ok &= WritePNG(fout, buffer);
|
|
#endif
|
|
} else if (format == PAM) {
|
|
ok &= WritePPM(fout, buffer, 1);
|
|
} else if (format == PPM) {
|
|
ok &= WritePPM(fout, buffer, 0);
|
|
} else if (format == BMP) {
|
|
ok &= WriteBMP(fout, buffer);
|
|
} else if (format == TIFF) {
|
|
ok &= WriteTIFF(fout, buffer);
|
|
} else if (format == PGM || format == YUV) {
|
|
ok &= WritePGMOrYUV(fout, buffer, format);
|
|
} else if (format == ALPHA_PLANE_ONLY) {
|
|
ok &= WriteAlphaPlane(fout, buffer);
|
|
}
|
|
if (fout != NULL && fout != stdout) {
|
|
fclose(fout);
|
|
}
|
|
if (ok) {
|
|
if (use_stdout) {
|
|
fprintf(stderr, "Saved to stdout\n");
|
|
} else {
|
|
fprintf(stderr, "Saved file %s\n", out_file);
|
|
}
|
|
if (verbose) {
|
|
const double write_time = StopwatchReadAndReset(&stop_watch);
|
|
fprintf(stderr, "Time to write output: %.3fs\n", write_time);
|
|
}
|
|
} else {
|
|
if (use_stdout) {
|
|
fprintf(stderr, "Error writing to stdout !!\n");
|
|
} else {
|
|
fprintf(stderr, "Error writing file %s !!\n", out_file);
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static void Help(void) {
|
|
printf("Usage: dwebp in_file [options] [-o out_file]\n\n"
|
|
"Decodes the WebP image file to PNG format [Default]\n"
|
|
"Use following options to convert into alternate image formats:\n"
|
|
" -pam ......... save the raw RGBA samples as a color PAM\n"
|
|
" -ppm ......... save the raw RGB samples as a color PPM\n"
|
|
" -bmp ......... save as uncompressed BMP format\n"
|
|
" -tiff ........ save as uncompressed TIFF format\n"
|
|
" -pgm ......... save the raw YUV samples as a grayscale PGM\n"
|
|
" file with IMC4 layout\n"
|
|
" -yuv ......... save the raw YUV samples in flat layout\n"
|
|
"\n"
|
|
" Other options are:\n"
|
|
" -version .... print version number and exit\n"
|
|
" -nofancy ..... don't use the fancy YUV420 upscaler\n"
|
|
" -nofilter .... disable in-loop filtering\n"
|
|
" -nodither .... disable dithering\n"
|
|
" -dither <d> .. dithering strength (in 0..100)\n"
|
|
" -mt .......... use multi-threading\n"
|
|
" -crop <x> <y> <w> <h> ... crop output with the given rectangle\n"
|
|
" -scale <w> <h> .......... scale the output (*after* any cropping)\n"
|
|
" -flip ........ flip the output vertically\n"
|
|
" -alpha ....... only save the alpha plane\n"
|
|
" -incremental . use incremental decoding (useful for tests)\n"
|
|
" -h ....... this help message\n"
|
|
" -v ....... verbose (e.g. print encoding/decoding times)\n"
|
|
#ifndef WEBP_DLL
|
|
" -noasm ....... disable all assembly optimizations\n"
|
|
#endif
|
|
);
|
|
}
|
|
|
|
static const char* const kFormatType[] = {
|
|
"unspecified", "lossy", "lossless"
|
|
};
|
|
|
|
int main(int argc, const char *argv[]) {
|
|
int ok = 0;
|
|
const char *in_file = NULL;
|
|
const char *out_file = NULL;
|
|
|
|
WebPDecoderConfig config;
|
|
WebPDecBuffer* const output_buffer = &config.output;
|
|
WebPBitstreamFeatures* const bitstream = &config.input;
|
|
OutputFileFormat format = PNG;
|
|
int incremental = 0;
|
|
int c;
|
|
|
|
if (!WebPInitDecoderConfig(&config)) {
|
|
fprintf(stderr, "Library version mismatch!\n");
|
|
return -1;
|
|
}
|
|
|
|
for (c = 1; c < argc; ++c) {
|
|
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
|
Help();
|
|
return 0;
|
|
} else if (!strcmp(argv[c], "-o") && c < argc - 1) {
|
|
out_file = argv[++c];
|
|
} else if (!strcmp(argv[c], "-alpha")) {
|
|
format = ALPHA_PLANE_ONLY;
|
|
} else if (!strcmp(argv[c], "-nofancy")) {
|
|
config.options.no_fancy_upsampling = 1;
|
|
} else if (!strcmp(argv[c], "-nofilter")) {
|
|
config.options.bypass_filtering = 1;
|
|
} else if (!strcmp(argv[c], "-pam")) {
|
|
format = PAM;
|
|
} else if (!strcmp(argv[c], "-ppm")) {
|
|
format = PPM;
|
|
} else if (!strcmp(argv[c], "-bmp")) {
|
|
format = BMP;
|
|
} else if (!strcmp(argv[c], "-tiff")) {
|
|
format = TIFF;
|
|
} else if (!strcmp(argv[c], "-version")) {
|
|
const int version = WebPGetDecoderVersion();
|
|
printf("%d.%d.%d\n",
|
|
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
|
|
return 0;
|
|
} else if (!strcmp(argv[c], "-pgm")) {
|
|
format = PGM;
|
|
} else if (!strcmp(argv[c], "-yuv")) {
|
|
format = YUV;
|
|
} else if (!strcmp(argv[c], "-mt")) {
|
|
config.options.use_threads = 1;
|
|
} else if (!strcmp(argv[c], "-nodither")) {
|
|
config.options.dithering_strength = 0;
|
|
} else if (!strcmp(argv[c], "-dither") && c < argc - 1) {
|
|
config.options.dithering_strength = strtol(argv[++c], NULL, 0);
|
|
} else if (!strcmp(argv[c], "-crop") && c < argc - 4) {
|
|
config.options.use_cropping = 1;
|
|
config.options.crop_left = strtol(argv[++c], NULL, 0);
|
|
config.options.crop_top = strtol(argv[++c], NULL, 0);
|
|
config.options.crop_width = strtol(argv[++c], NULL, 0);
|
|
config.options.crop_height = strtol(argv[++c], NULL, 0);
|
|
} else if (!strcmp(argv[c], "-scale") && c < argc - 2) {
|
|
config.options.use_scaling = 1;
|
|
config.options.scaled_width = strtol(argv[++c], NULL, 0);
|
|
config.options.scaled_height = strtol(argv[++c], NULL, 0);
|
|
} else if (!strcmp(argv[c], "-flip")) {
|
|
config.options.flip = 1;
|
|
} else if (!strcmp(argv[c], "-v")) {
|
|
verbose = 1;
|
|
#ifndef WEBP_DLL
|
|
} else if (!strcmp(argv[c], "-noasm")) {
|
|
VP8GetCPUInfo = NULL;
|
|
#endif
|
|
} else if (!strcmp(argv[c], "-incremental")) {
|
|
incremental = 1;
|
|
} else if (!strcmp(argv[c], "--")) {
|
|
if (c < argc - 1) in_file = argv[++c];
|
|
break;
|
|
} else if (argv[c][0] == '-') {
|
|
fprintf(stderr, "Unknown option '%s'\n", argv[c]);
|
|
Help();
|
|
return -1;
|
|
} else {
|
|
in_file = argv[c];
|
|
}
|
|
}
|
|
|
|
if (in_file == NULL) {
|
|
fprintf(stderr, "missing input file!!\n");
|
|
Help();
|
|
return -1;
|
|
}
|
|
|
|
{
|
|
VP8StatusCode status = VP8_STATUS_OK;
|
|
size_t data_size = 0;
|
|
const uint8_t* data = NULL;
|
|
if (!ExUtilLoadWebP(in_file, &data, &data_size, bitstream)) {
|
|
return -1;
|
|
}
|
|
|
|
switch (format) {
|
|
case PNG:
|
|
#ifdef HAVE_WINCODEC_H
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
|
#else
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB;
|
|
#endif
|
|
break;
|
|
case PAM:
|
|
output_buffer->colorspace = MODE_RGBA;
|
|
break;
|
|
case PPM:
|
|
output_buffer->colorspace = MODE_RGB; // drops alpha for PPM
|
|
break;
|
|
case BMP:
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
|
break;
|
|
case TIFF: // note: force pre-multiplied alpha
|
|
output_buffer->colorspace =
|
|
bitstream->has_alpha ? MODE_rgbA : MODE_RGB;
|
|
break;
|
|
case PGM:
|
|
case YUV:
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_YUVA : MODE_YUV;
|
|
break;
|
|
case ALPHA_PLANE_ONLY:
|
|
output_buffer->colorspace = MODE_YUVA;
|
|
break;
|
|
default:
|
|
free((void*)data);
|
|
return -1;
|
|
}
|
|
|
|
if (incremental) {
|
|
status = ExUtilDecodeWebPIncremental(data, data_size, verbose, &config);
|
|
} else {
|
|
status = ExUtilDecodeWebP(data, data_size, verbose, &config);
|
|
}
|
|
|
|
free((void*)data);
|
|
ok = (status == VP8_STATUS_OK);
|
|
if (!ok) {
|
|
ExUtilPrintWebPError(in_file, status);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (out_file != NULL) {
|
|
fprintf(stderr, "Decoded %s. Dimensions: %d x %d %s. Format: %s. "
|
|
"Now saving...\n",
|
|
in_file, output_buffer->width, output_buffer->height,
|
|
bitstream->has_alpha ? " (with alpha)" : "",
|
|
kFormatType[bitstream->format]);
|
|
ok = SaveOutput(output_buffer, format, out_file);
|
|
} else {
|
|
fprintf(stderr, "File %s can be decoded "
|
|
"(dimensions: %d x %d %s. Format: %s).\n",
|
|
in_file, output_buffer->width, output_buffer->height,
|
|
bitstream->has_alpha ? " (with alpha)" : "",
|
|
kFormatType[bitstream->format]);
|
|
fprintf(stderr, "Nothing written; "
|
|
"use -o flag to save the result as e.g. PNG.\n");
|
|
}
|
|
Exit:
|
|
WebPFreeDecBuffer(output_buffer);
|
|
return ok ? 0 : -1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|