2012-01-06 23:49:06 +01:00
|
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
2010-09-30 15:34:38 +02:00
|
|
|
//
|
2013-06-07 08:05:58 +02:00
|
|
|
// 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.
|
2010-09-30 15:34:38 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
//
|
2013-04-02 01:10:35 +02:00
|
|
|
// Command-line tool for decoding a WebP image.
|
2010-09-30 15:34:38 +02:00
|
|
|
//
|
|
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2011-06-07 02:56:50 +02:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2011-02-01 07:00:33 +01:00
|
|
|
#ifdef WEBP_HAVE_PNG
|
|
|
|
#include <png.h>
|
|
|
|
#endif
|
|
|
|
|
2011-06-07 02:56:50 +02:00
|
|
|
#ifdef HAVE_WINCODEC_H
|
|
|
|
#ifdef __MINGW32__
|
|
|
|
#define INITGUID // Without this GUIDs are declared extern and fail to link
|
|
|
|
#endif
|
2011-02-17 00:01:27 +01:00
|
|
|
#define CINTERFACE
|
|
|
|
#define COBJMACROS
|
|
|
|
#define _WIN32_IE 0x500 // Workaround bug in shlwapi.h when compiling C++
|
|
|
|
// code with COBJMACROS.
|
2013-08-21 00:01:23 +02:00
|
|
|
#include <ole2.h> // CreateStreamOnHGlobal()
|
2011-02-17 00:01:27 +01:00
|
|
|
#include <shlwapi.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <wincodec.h>
|
|
|
|
#endif
|
|
|
|
|
2013-08-19 21:09:37 +02:00
|
|
|
#if defined(_WIN32)
|
|
|
|
#include <fcntl.h> // for _O_BINARY
|
|
|
|
#include <io.h> // for _setmode()
|
|
|
|
#endif
|
|
|
|
|
2010-09-30 15:34:38 +02:00
|
|
|
#include "webp/decode.h"
|
2012-05-12 01:00:57 +02:00
|
|
|
#include "./example_util.h"
|
2012-05-15 22:48:11 +02:00
|
|
|
#include "./stopwatch.h"
|
2010-09-30 15:34:38 +02:00
|
|
|
|
2011-11-04 23:20:08 +01:00
|
|
|
static int verbose = 0;
|
|
|
|
#ifndef WEBP_DLL
|
2010-09-30 15:34:38 +02:00
|
|
|
#if defined(__cplusplus) || defined(c_plusplus)
|
|
|
|
extern "C" {
|
|
|
|
#endif
|
|
|
|
|
2011-09-02 23:30:08 +02:00
|
|
|
extern void* VP8GetCPUInfo; // opaque forward declaration.
|
2011-11-04 23:20:08 +01:00
|
|
|
|
|
|
|
#if defined(__cplusplus) || defined(c_plusplus)
|
|
|
|
} // extern "C"
|
2011-07-15 23:53:03 +02:00
|
|
|
#endif
|
2011-11-04 23:20:08 +01:00
|
|
|
#endif // WEBP_DLL
|
2011-06-11 00:10:18 +02:00
|
|
|
|
2011-08-25 23:22:32 +02:00
|
|
|
//------------------------------------------------------------------------------
|
2011-06-20 09:45:15 +02:00
|
|
|
|
|
|
|
// Output types
|
|
|
|
typedef enum {
|
|
|
|
PNG = 0,
|
2012-07-21 01:06:06 +02:00
|
|
|
PAM,
|
2011-06-20 09:45:15 +02:00
|
|
|
PPM,
|
|
|
|
PGM,
|
2013-04-10 03:51:59 +02:00
|
|
|
BMP,
|
2013-05-09 12:57:50 +02:00
|
|
|
TIFF,
|
2013-01-28 23:22:14 +01:00
|
|
|
YUV,
|
2011-06-20 09:45:15 +02:00
|
|
|
ALPHA_PLANE_ONLY // this is for experimenting only
|
|
|
|
} OutputFileFormat;
|
2011-02-17 00:01:27 +01:00
|
|
|
|
2011-06-07 02:56:50 +02:00
|
|
|
#ifdef HAVE_WINCODEC_H
|
2011-02-17 00:01:27 +01:00
|
|
|
|
2012-10-09 03:33:18 +02:00
|
|
|
#define IFS(fn) \
|
|
|
|
do { \
|
2013-02-05 01:13:59 +01:00
|
|
|
if (SUCCEEDED(hr)) { \
|
|
|
|
hr = (fn); \
|
|
|
|
if (FAILED(hr)) fprintf(stderr, #fn " failed %08lx\n", hr); \
|
|
|
|
} \
|
2011-02-17 00:01:27 +01:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
#define MAKE_REFGUID(x) (x)
|
|
|
|
#else
|
|
|
|
#define MAKE_REFGUID(x) &(x)
|
|
|
|
#endif
|
|
|
|
|
2013-08-21 00:01:23 +02:00
|
|
|
static HRESULT CreateOutputStream(const char* out_file_name,
|
|
|
|
int write_to_mem, IStream** stream) {
|
2011-02-17 00:01:27 +01:00
|
|
|
HRESULT hr = S_OK;
|
2013-08-21 00:01:23 +02:00
|
|
|
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));
|
|
|
|
}
|
2013-02-05 01:13:59 +01:00
|
|
|
if (FAILED(hr)) {
|
|
|
|
fprintf(stderr, "Error opening output file %s (%08lx)\n",
|
|
|
|
out_file_name, hr);
|
|
|
|
}
|
2011-02-17 00:01:27 +01:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
2013-08-21 00:01:23 +02:00
|
|
|
static HRESULT WriteUsingWIC(const char* out_file_name, int use_stdout,
|
|
|
|
REFGUID container_guid,
|
2013-08-20 23:57:35 +02:00
|
|
|
uint8_t* rgb, int stride,
|
2011-06-22 03:29:30 +02:00
|
|
|
uint32_t width, uint32_t height, int has_alpha) {
|
2011-02-17 00:01:27 +01:00
|
|
|
HRESULT hr = S_OK;
|
2013-02-16 05:20:57 +01:00
|
|
|
IWICImagingFactory* factory = NULL;
|
|
|
|
IWICBitmapFrameEncode* frame = NULL;
|
|
|
|
IWICBitmapEncoder* encoder = NULL;
|
|
|
|
IStream* stream = NULL;
|
2011-06-22 03:29:30 +02:00
|
|
|
WICPixelFormatGUID pixel_format = has_alpha ? GUID_WICPixelFormat32bppBGRA
|
|
|
|
: GUID_WICPixelFormat24bppBGR;
|
2011-02-17 00:01:27 +01:00
|
|
|
|
|
|
|
IFS(CoInitialize(NULL));
|
|
|
|
IFS(CoCreateInstance(MAKE_REFGUID(CLSID_WICImagingFactory), NULL,
|
2013-02-16 05:20:57 +01:00
|
|
|
CLSCTX_INPROC_SERVER,
|
|
|
|
MAKE_REFGUID(IID_IWICImagingFactory),
|
|
|
|
(LPVOID*)&factory));
|
2011-02-17 00:01:27 +01:00
|
|
|
if (hr == REGDB_E_CLASSNOTREG) {
|
2012-01-24 21:46:46 +01:00
|
|
|
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");
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
2013-08-21 00:01:23 +02:00
|
|
|
IFS(CreateOutputStream(out_file_name, use_stdout, &stream));
|
2013-02-16 05:20:57 +01:00
|
|
|
IFS(IWICImagingFactory_CreateEncoder(factory, container_guid, NULL,
|
|
|
|
&encoder));
|
|
|
|
IFS(IWICBitmapEncoder_Initialize(encoder, stream,
|
2011-02-18 19:26:42 +01:00
|
|
|
WICBitmapEncoderNoCache));
|
2013-02-16 05:20:57 +01:00
|
|
|
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));
|
|
|
|
|
2013-08-21 00:01:23 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-16 05:20:57 +01:00
|
|
|
if (frame != NULL) IUnknown_Release(frame);
|
|
|
|
if (encoder != NULL) IUnknown_Release(encoder);
|
|
|
|
if (factory != NULL) IUnknown_Release(factory);
|
|
|
|
if (stream != NULL) IUnknown_Release(stream);
|
2011-02-17 00:01:27 +01:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
2013-08-21 00:01:23 +02:00
|
|
|
static int WritePNG(const char* out_file_name, int use_stdout,
|
2011-06-20 09:45:15 +02:00
|
|
|
const WebPDecBuffer* const buffer) {
|
|
|
|
const uint32_t width = buffer->width;
|
|
|
|
const uint32_t height = buffer->height;
|
2013-08-20 23:57:35 +02:00
|
|
|
uint8_t* const rgb = buffer->u.RGBA.rgba;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int stride = buffer->u.RGBA.stride;
|
2011-06-22 03:29:30 +02:00
|
|
|
const int has_alpha = (buffer->colorspace == MODE_BGRA);
|
|
|
|
|
2013-08-21 00:01:23 +02:00
|
|
|
return SUCCEEDED(WriteUsingWIC(out_file_name, use_stdout,
|
2013-02-16 05:20:57 +01:00
|
|
|
MAKE_REFGUID(GUID_ContainerFormatPng),
|
|
|
|
rgb, stride, width, height, has_alpha));
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
|
|
|
|
2011-06-07 02:56:50 +02:00
|
|
|
#elif defined(WEBP_HAVE_PNG) // !HAVE_WINCODEC_H
|
2011-02-01 07:00:33 +01:00
|
|
|
static void PNGAPI error_function(png_structp png, png_const_charp dummy) {
|
2011-03-25 23:04:11 +01:00
|
|
|
(void)dummy; // remove variable-unused warning
|
2011-02-01 07:00:33 +01:00
|
|
|
longjmp(png_jmpbuf(png), 1);
|
|
|
|
}
|
|
|
|
|
2011-06-20 09:45:15 +02:00
|
|
|
static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) {
|
|
|
|
const uint32_t width = buffer->width;
|
|
|
|
const uint32_t height = buffer->height;
|
2013-08-20 23:57:35 +02:00
|
|
|
uint8_t* const rgb = buffer->u.RGBA.rgba;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int stride = buffer->u.RGBA.stride;
|
|
|
|
const int has_alpha = (buffer->colorspace == MODE_RGBA);
|
2011-02-01 07:00:33 +01:00
|
|
|
png_structp png;
|
|
|
|
png_infop info;
|
2011-03-25 23:04:11 +01:00
|
|
|
png_uint_32 y;
|
2011-02-01 07:00:33 +01:00
|
|
|
|
|
|
|
png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
|
|
|
NULL, error_function, 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);
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
png_set_IHDR(png, info, width, height, 8,
|
|
|
|
has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB,
|
2011-02-01 07:00:33 +01:00
|
|
|
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;
|
|
|
|
}
|
2011-06-07 02:56:50 +02:00
|
|
|
#else // !HAVE_WINCODEC_H && !WEBP_HAVE_PNG
|
2011-06-20 09:45:15 +02:00
|
|
|
static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) {
|
2011-06-24 22:50:02 +02:00
|
|
|
(void)out_file;
|
|
|
|
(void)buffer;
|
2012-01-24 21:46:46 +01:00
|
|
|
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");
|
2011-02-01 07:00:33 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-07-21 01:06:06 +02:00
|
|
|
static int WritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha) {
|
2011-06-20 09:45:15 +02:00
|
|
|
const uint32_t width = buffer->width;
|
|
|
|
const uint32_t height = buffer->height;
|
2013-08-20 23:57:35 +02:00
|
|
|
const uint8_t* const rgb = buffer->u.RGBA.rgba;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int stride = buffer->u.RGBA.stride;
|
2012-07-21 01:06:06 +02:00
|
|
|
const size_t bytes_per_px = alpha ? 4 : 3;
|
2011-06-20 09:45:15 +02:00
|
|
|
uint32_t y;
|
2012-07-21 01:06:06 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
for (y = 0; y < height; ++y) {
|
2012-07-21 01:06:06 +02:00
|
|
|
if (fwrite(rgb + y * stride, width, bytes_per_px, fout) != bytes_per_px) {
|
2011-06-20 09:45:15 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
|
|
|
|
2013-04-10 03:51:59 +02:00
|
|
|
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;
|
2013-06-02 07:48:08 +02:00
|
|
|
const uint32_t bytes_per_px = has_alpha ? 4 : 3;
|
2013-04-10 03:51:59 +02:00
|
|
|
uint32_t y;
|
2013-06-02 07:48:08 +02:00
|
|
|
const uint32_t line_size = bytes_per_px * width;
|
|
|
|
const uint32_t bmp_stride = (line_size + 3) & ~3; // pad to 4
|
2013-04-10 03:51:59 +02:00
|
|
|
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
|
|
|
|
|
2013-05-09 12:57:50 +02:00
|
|
|
#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
|
|
|
|
|
2011-06-20 09:45:15 +02:00
|
|
|
static int WriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) {
|
|
|
|
const uint32_t width = buffer->width;
|
|
|
|
const uint32_t height = buffer->height;
|
2013-08-20 23:57:35 +02:00
|
|
|
const uint8_t* const a = buffer->u.YUVA.a;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int a_stride = buffer->u.YUVA.a_stride;
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
uint32_t y;
|
2011-06-20 09:45:15 +02:00
|
|
|
assert(a != NULL);
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
fprintf(fout, "P5\n%d %d\n255\n", width, height);
|
|
|
|
for (y = 0; y < height; ++y) {
|
2011-06-20 09:45:15 +02:00
|
|
|
if (fwrite(a + y * a_stride, width, 1, fout) != 1) {
|
|
|
|
return 0;
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-01-28 23:22:14 +01:00
|
|
|
// 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) {
|
2011-06-20 09:45:15 +02:00
|
|
|
const int width = buffer->width;
|
|
|
|
const int height = buffer->height;
|
|
|
|
const WebPYUVABuffer* const yuv = &buffer->u.YUVA;
|
2011-02-17 00:01:27 +01:00
|
|
|
int ok = 1;
|
2011-06-20 09:45:15 +02:00
|
|
|
int y;
|
2013-01-28 23:22:14 +01:00
|
|
|
const int pad = (format == YUV) ? 0 : 1;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int uv_width = (width + 1) / 2;
|
|
|
|
const int uv_height = (height + 1) / 2;
|
2013-01-28 23:22:14 +01:00
|
|
|
const int out_stride = (width + pad) & ~pad;
|
2011-06-20 09:45:15 +02:00
|
|
|
const int a_height = yuv->a ? height : 0;
|
2013-01-28 23:22:14 +01:00
|
|
|
if (format == PGM) {
|
|
|
|
fprintf(fout, "P5\n%d %d\n255\n",
|
|
|
|
out_stride, height + uv_height + a_height);
|
|
|
|
}
|
2011-02-17 00:01:27 +01:00
|
|
|
for (y = 0; ok && y < height; ++y) {
|
2011-06-20 09:45:15 +02:00
|
|
|
ok &= (fwrite(yuv->y + y * yuv->y_stride, width, 1, fout) == 1);
|
2013-01-28 23:22:14 +01:00
|
|
|
if (format == PGM) {
|
|
|
|
if (width & 1) fputc(0, fout); // padding byte
|
|
|
|
}
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
2013-01-28 23:22:14 +01:00
|
|
|
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);
|
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
|
|
|
for (y = 0; ok && y < a_height; ++y) {
|
|
|
|
ok &= (fwrite(yuv->a + y * yuv->a_stride, width, 1, fout) == 1);
|
2013-01-28 23:22:14 +01:00
|
|
|
if (format == PGM) {
|
|
|
|
if (width & 1) fputc(0, fout); // padding byte
|
|
|
|
}
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2013-08-19 21:09:37 +02:00
|
|
|
static int SaveOutput(const WebPDecBuffer* const buffer,
|
2013-08-20 02:51:04 +02:00
|
|
|
OutputFileFormat format, const char* const out_file) {
|
2011-06-20 09:45:15 +02:00
|
|
|
FILE* fout = NULL;
|
|
|
|
int needs_open_file = 1;
|
2013-08-21 00:01:23 +02:00
|
|
|
const int use_stdout = !strcmp(out_file, "-");
|
2011-06-20 09:45:15 +02:00
|
|
|
int ok = 1;
|
|
|
|
Stopwatch stop_watch;
|
|
|
|
|
2013-09-12 09:32:28 +02:00
|
|
|
if (verbose) {
|
|
|
|
StopwatchReset(&stop_watch);
|
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
|
2011-06-22 03:29:30 +02:00
|
|
|
#ifdef HAVE_WINCODEC_H
|
2011-06-20 09:45:15 +02:00
|
|
|
needs_open_file = (format != PNG);
|
|
|
|
#endif
|
2013-08-19 21:09:37 +02:00
|
|
|
|
|
|
|
#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
|
|
|
|
|
2011-06-20 09:45:15 +02:00
|
|
|
if (needs_open_file) {
|
2013-08-19 21:09:37 +02:00
|
|
|
fout = use_stdout ? stdout : fopen(out_file, "wb");
|
|
|
|
if (fout == NULL) {
|
2011-06-20 09:45:15 +02:00
|
|
|
fprintf(stderr, "Error opening output file %s\n", out_file);
|
2013-08-19 21:09:37 +02:00
|
|
|
return 0;
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (format == PNG) {
|
|
|
|
#ifdef HAVE_WINCODEC_H
|
2013-08-21 00:01:23 +02:00
|
|
|
ok &= WritePNG(out_file, use_stdout, buffer);
|
2011-06-20 09:45:15 +02:00
|
|
|
#else
|
|
|
|
ok &= WritePNG(fout, buffer);
|
|
|
|
#endif
|
2012-07-21 01:06:06 +02:00
|
|
|
} else if (format == PAM) {
|
|
|
|
ok &= WritePPM(fout, buffer, 1);
|
2011-06-20 09:45:15 +02:00
|
|
|
} else if (format == PPM) {
|
2012-07-21 01:06:06 +02:00
|
|
|
ok &= WritePPM(fout, buffer, 0);
|
2013-04-10 03:51:59 +02:00
|
|
|
} else if (format == BMP) {
|
|
|
|
ok &= WriteBMP(fout, buffer);
|
2013-05-09 12:57:50 +02:00
|
|
|
} else if (format == TIFF) {
|
|
|
|
ok &= WriteTIFF(fout, buffer);
|
2013-01-28 23:22:14 +01:00
|
|
|
} else if (format == PGM || format == YUV) {
|
|
|
|
ok &= WritePGMOrYUV(fout, buffer, format);
|
2011-06-20 09:45:15 +02:00
|
|
|
} else if (format == ALPHA_PLANE_ONLY) {
|
|
|
|
ok &= WriteAlphaPlane(fout, buffer);
|
|
|
|
}
|
2013-08-19 21:09:37 +02:00
|
|
|
if (fout != NULL && fout != stdout) {
|
2011-06-20 09:45:15 +02:00
|
|
|
fclose(fout);
|
|
|
|
}
|
|
|
|
if (ok) {
|
2013-08-21 00:01:23 +02:00
|
|
|
if (use_stdout) {
|
2013-08-19 21:09:37 +02:00
|
|
|
fprintf(stderr, "Saved to stdout\n");
|
2013-08-21 00:01:23 +02:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Saved file %s\n", out_file);
|
2013-08-19 21:09:37 +02:00
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
if (verbose) {
|
2013-01-21 17:20:14 +01:00
|
|
|
const double write_time = StopwatchReadAndReset(&stop_watch);
|
2013-08-19 21:09:37 +02:00
|
|
|
fprintf(stderr, "Time to write output: %.3fs\n", write_time);
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
|
|
|
} else {
|
2013-08-21 00:01:23 +02:00
|
|
|
if (use_stdout) {
|
2013-08-20 02:52:33 +02:00
|
|
|
fprintf(stderr, "Error writing to stdout !!\n");
|
2013-08-21 00:01:23 +02:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Error writing file %s !!\n", out_file);
|
2013-08-19 21:09:37 +02:00
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
2013-08-19 21:09:37 +02:00
|
|
|
return ok;
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
2011-02-01 07:00:33 +01:00
|
|
|
|
2011-03-25 23:04:11 +01:00
|
|
|
static void Help(void) {
|
2011-06-20 09:45:15 +02:00
|
|
|
printf("Usage: dwebp in_file [options] [-o out_file]\n\n"
|
2011-02-01 07:00:33 +01:00
|
|
|
"Decodes the WebP image file to PNG format [Default]\n"
|
|
|
|
"Use following options to convert into alternate image formats:\n"
|
2012-07-21 01:06:06 +02:00
|
|
|
" -pam ......... save the raw RGBA samples as a color PAM\n"
|
|
|
|
" -ppm ......... save the raw RGB samples as a color PPM\n"
|
2013-04-10 03:51:59 +02:00
|
|
|
" -bmp ......... save as uncompressed BMP format\n"
|
2013-05-09 12:57:50 +02:00
|
|
|
" -tiff ........ save as uncompressed TIFF format\n"
|
2011-06-20 09:45:15 +02:00
|
|
|
" -pgm ......... save the raw YUV samples as a grayscale PGM\n"
|
2013-04-10 03:51:59 +02:00
|
|
|
" file with IMC4 layout\n"
|
|
|
|
" -yuv ......... save the raw YUV samples in flat layout\n"
|
2013-01-28 23:22:14 +01:00
|
|
|
"\n"
|
2011-06-20 09:45:15 +02:00
|
|
|
" 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"
|
2013-11-26 22:59:02 +01:00
|
|
|
" -nodither .... disable dithering.\n"
|
|
|
|
" -dither <d> .. dithering strength (in 0..100)\n"
|
2011-07-22 22:09:10 +02:00
|
|
|
" -mt .......... use multi-threading\n"
|
2011-06-20 09:45:15 +02:00
|
|
|
" -crop <x> <y> <w> <h> ... crop output with the given rectangle\n"
|
|
|
|
" -scale <w> <h> .......... scale the output (*after* any cropping)\n"
|
|
|
|
" -alpha ....... only save the alpha plane.\n"
|
2013-09-19 13:20:45 +02:00
|
|
|
" -incremental . use incremental decoding (useful for tests)\n"
|
2011-06-20 09:45:15 +02:00
|
|
|
" -h ....... this help message.\n"
|
|
|
|
" -v ....... verbose (e.g. print encoding/decoding times)\n"
|
2011-07-15 23:53:03 +02:00
|
|
|
#ifndef WEBP_DLL
|
2011-06-20 09:45:15 +02:00
|
|
|
" -noasm ....... disable all assembly optimizations.\n"
|
2011-07-15 23:53:03 +02:00
|
|
|
#endif
|
2010-12-17 14:53:50 +01:00
|
|
|
);
|
2010-09-30 15:34:38 +02:00
|
|
|
}
|
|
|
|
|
2011-06-20 09:45:15 +02:00
|
|
|
static const char* const kStatusMessages[] = {
|
|
|
|
"OK", "OUT_OF_MEMORY", "INVALID_PARAM", "BITSTREAM_ERROR",
|
|
|
|
"UNSUPPORTED_FEATURE", "SUSPENDED", "USER_ABORT", "NOT_ENOUGH_DATA"
|
|
|
|
};
|
|
|
|
|
2013-08-21 23:56:35 +02:00
|
|
|
static const char* const kFormatType[] = {
|
|
|
|
"unspecified", "lossy", "lossless"
|
|
|
|
};
|
|
|
|
|
2011-01-23 21:47:44 +01:00
|
|
|
int main(int argc, const char *argv[]) {
|
2013-08-19 21:09:37 +02:00
|
|
|
int ok = 0;
|
2010-09-30 15:34:38 +02:00
|
|
|
const char *in_file = NULL;
|
|
|
|
const char *out_file = NULL;
|
|
|
|
|
2011-06-20 09:45:15 +02:00
|
|
|
WebPDecoderConfig config;
|
|
|
|
WebPDecBuffer* const output_buffer = &config.output;
|
|
|
|
WebPBitstreamFeatures* const bitstream = &config.input;
|
2011-02-01 07:00:33 +01:00
|
|
|
OutputFileFormat format = PNG;
|
2013-09-19 13:20:45 +02:00
|
|
|
int incremental = 0;
|
2010-09-30 15:34:38 +02:00
|
|
|
int c;
|
2011-06-20 09:45:15 +02:00
|
|
|
|
|
|
|
if (!WebPInitDecoderConfig(&config)) {
|
|
|
|
fprintf(stderr, "Library version mismatch!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-09-30 15:34:38 +02:00
|
|
|
for (c = 1; c < argc; ++c) {
|
2011-02-01 07:00:33 +01:00
|
|
|
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
2011-03-25 23:04:11 +01:00
|
|
|
Help();
|
2010-09-30 15:34:38 +02:00
|
|
|
return 0;
|
|
|
|
} else if (!strcmp(argv[c], "-o") && c < argc - 1) {
|
|
|
|
out_file = argv[++c];
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
} else if (!strcmp(argv[c], "-alpha")) {
|
|
|
|
format = ALPHA_PLANE_ONLY;
|
2011-06-20 09:45:15 +02:00
|
|
|
} else if (!strcmp(argv[c], "-nofancy")) {
|
|
|
|
config.options.no_fancy_upsampling = 1;
|
|
|
|
} else if (!strcmp(argv[c], "-nofilter")) {
|
|
|
|
config.options.bypass_filtering = 1;
|
2012-07-21 01:06:06 +02:00
|
|
|
} else if (!strcmp(argv[c], "-pam")) {
|
|
|
|
format = PAM;
|
2011-02-01 07:00:33 +01:00
|
|
|
} else if (!strcmp(argv[c], "-ppm")) {
|
|
|
|
format = PPM;
|
2013-04-10 03:51:59 +02:00
|
|
|
} else if (!strcmp(argv[c], "-bmp")) {
|
|
|
|
format = BMP;
|
2013-05-09 12:57:50 +02:00
|
|
|
} else if (!strcmp(argv[c], "-tiff")) {
|
|
|
|
format = TIFF;
|
2011-03-25 00:17:10 +01:00
|
|
|
} else if (!strcmp(argv[c], "-version")) {
|
|
|
|
const int version = WebPGetDecoderVersion();
|
|
|
|
printf("%d.%d.%d\n",
|
2012-01-24 21:46:46 +01:00
|
|
|
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
|
2011-03-25 00:17:10 +01:00
|
|
|
return 0;
|
2011-02-01 07:00:33 +01:00
|
|
|
} else if (!strcmp(argv[c], "-pgm")) {
|
|
|
|
format = PGM;
|
2013-01-28 23:22:14 +01:00
|
|
|
} else if (!strcmp(argv[c], "-yuv")) {
|
|
|
|
format = YUV;
|
2011-07-22 22:09:10 +02:00
|
|
|
} else if (!strcmp(argv[c], "-mt")) {
|
|
|
|
config.options.use_threads = 1;
|
2013-11-26 22:59:02 +01:00
|
|
|
} 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);
|
2011-06-20 09:45:15 +02:00
|
|
|
} 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);
|
2011-02-17 00:01:27 +01:00
|
|
|
} else if (!strcmp(argv[c], "-v")) {
|
|
|
|
verbose = 1;
|
2011-07-15 23:53:03 +02:00
|
|
|
#ifndef WEBP_DLL
|
2011-06-11 00:10:18 +02:00
|
|
|
} else if (!strcmp(argv[c], "-noasm")) {
|
2011-09-02 23:30:08 +02:00
|
|
|
VP8GetCPUInfo = NULL;
|
2011-07-15 23:53:03 +02:00
|
|
|
#endif
|
2013-09-19 13:20:45 +02:00
|
|
|
} else if (!strcmp(argv[c], "-incremental")) {
|
|
|
|
incremental = 1;
|
2010-09-30 15:34:38 +02:00
|
|
|
} else if (argv[c][0] == '-') {
|
2012-01-24 21:46:46 +01:00
|
|
|
fprintf(stderr, "Unknown option '%s'\n", argv[c]);
|
2011-03-25 23:04:11 +01:00
|
|
|
Help();
|
2010-09-30 15:34:38 +02:00
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
in_file = argv[c];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_file == NULL) {
|
2012-01-24 21:46:46 +01:00
|
|
|
fprintf(stderr, "missing input file!!\n");
|
2011-03-25 23:04:11 +01:00
|
|
|
Help();
|
2010-09-30 15:34:38 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2011-06-20 09:45:15 +02:00
|
|
|
Stopwatch stop_watch;
|
|
|
|
VP8StatusCode status = VP8_STATUS_OK;
|
2012-05-12 01:00:57 +02:00
|
|
|
size_t data_size = 0;
|
|
|
|
const uint8_t* data = NULL;
|
2011-06-20 09:45:15 +02:00
|
|
|
|
2012-05-12 01:00:57 +02:00
|
|
|
if (!ExUtilReadFile(in_file, &data, &data_size)) return -1;
|
2010-09-30 15:34:38 +02:00
|
|
|
|
2013-09-12 09:32:28 +02:00
|
|
|
if (verbose) {
|
|
|
|
StopwatchReset(&stop_watch);
|
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
|
2012-05-12 01:00:57 +02:00
|
|
|
status = WebPGetFeatures(data, data_size, bitstream);
|
2011-06-20 09:45:15 +02:00
|
|
|
if (status != VP8_STATUS_OK) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2013-03-20 21:05:04 +01:00
|
|
|
if (bitstream->has_animation) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error! Decoding of an animated WebP file is not supported.\n"
|
|
|
|
" Use webpmux to extract the individual frames or\n"
|
|
|
|
" vwebp to view this image.\n");
|
|
|
|
}
|
|
|
|
|
2011-02-01 07:00:33 +01:00
|
|
|
switch (format) {
|
|
|
|
case PNG:
|
2011-06-22 03:29:30 +02:00
|
|
|
#ifdef HAVE_WINCODEC_H
|
|
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
2011-02-17 00:01:27 +01:00
|
|
|
#else
|
2011-06-20 09:45:15 +02:00
|
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB;
|
2011-02-17 00:01:27 +01:00
|
|
|
#endif
|
|
|
|
break;
|
2012-07-21 01:06:06 +02:00
|
|
|
case PAM:
|
|
|
|
output_buffer->colorspace = MODE_RGBA;
|
|
|
|
break;
|
2011-02-01 07:00:33 +01:00
|
|
|
case PPM:
|
2011-06-20 09:45:15 +02:00
|
|
|
output_buffer->colorspace = MODE_RGB; // drops alpha for PPM
|
2011-02-01 07:00:33 +01:00
|
|
|
break;
|
2013-04-10 03:51:59 +02:00
|
|
|
case BMP:
|
|
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
|
|
|
break;
|
2013-05-09 12:57:50 +02:00
|
|
|
case TIFF: // note: force pre-multiplied alpha
|
|
|
|
output_buffer->colorspace =
|
|
|
|
bitstream->has_alpha ? MODE_rgbA : MODE_RGB;
|
|
|
|
break;
|
2011-02-01 07:00:33 +01:00
|
|
|
case PGM:
|
2013-01-28 23:22:14 +01:00
|
|
|
case YUV:
|
2011-06-20 09:45:15 +02:00
|
|
|
output_buffer->colorspace = bitstream->has_alpha ? MODE_YUVA : MODE_YUV;
|
2011-02-01 07:00:33 +01:00
|
|
|
break;
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
case ALPHA_PLANE_ONLY:
|
2011-06-20 09:45:15 +02:00
|
|
|
output_buffer->colorspace = MODE_YUVA;
|
EXPERIMENTAL: add support for alpha channel
This is a (minor) bitstream change: if the 'color_space' bit is set to '1'
(which is normally an undefined/invalid behaviour), we add extra data at the
end of partition #0 (so-called 'extensions')
Namely, we add the size of the extension data as 3 bytes (little-endian),
followed by a set of bits telling which extensions we're incorporating.
The data then _preceeds_ this trailing tags.
This is all experimental, and you'll need to have
'#define WEBP_EXPERIMENTAL_FEATURES' in webp/types.h to enable this code
(at your own risk! :))
Still, this hack produces almost-valid WebP file for decoders that don't
check this color_space bit. In particular, previous 'dwebp' (and for instance
Chrome) will recognize this files and decode them, but without the alpha
of course. Other decoder will just see random extra stuff at the end of
partition #0.
To experiment with the alpha-channel, you need to compile on Unix platform
and use PNGs for input/output.
If 'alpha.png' is a source with alpha channel, then you can try (on Unix):
cwebp alpha.png -o alpha.webp
dwebp alpha.webp -o test.png
cwebp now has a '-noalpha' flag to ignore any alpha information from the
source, if present.
More hacking and experimenting welcome!
Change-Id: I3c7b1fd8411c9e7a9f77690e898479ad85c52f3e
2011-04-26 01:58:04 +02:00
|
|
|
break;
|
2011-02-01 07:00:33 +01:00
|
|
|
default:
|
2012-05-12 01:00:57 +02:00
|
|
|
free((void*)data);
|
2011-02-01 07:00:33 +01:00
|
|
|
return -1;
|
2010-10-31 08:36:33 +01:00
|
|
|
}
|
2013-09-19 13:20:45 +02:00
|
|
|
|
|
|
|
// Decoding call.
|
|
|
|
if (!incremental) {
|
|
|
|
status = WebPDecode(data, data_size, &config);
|
|
|
|
} else {
|
2013-11-26 22:59:02 +01:00
|
|
|
WebPIDecoder* const idec = WebPIDecode(data, data_size, &config);
|
2013-09-19 13:20:45 +02:00
|
|
|
if (idec == NULL) {
|
|
|
|
fprintf(stderr, "Failed during WebPINewDecoder().\n");
|
|
|
|
status = VP8_STATUS_OUT_OF_MEMORY;
|
|
|
|
ok = 0;
|
|
|
|
} else {
|
|
|
|
status = WebPIUpdate(idec, data, data_size);
|
|
|
|
WebPIDelete(idec);
|
|
|
|
}
|
|
|
|
}
|
2011-02-01 07:00:33 +01:00
|
|
|
|
2011-02-17 00:01:27 +01:00
|
|
|
if (verbose) {
|
2013-01-21 17:20:14 +01:00
|
|
|
const double decode_time = StopwatchReadAndReset(&stop_watch);
|
2013-08-19 21:09:37 +02:00
|
|
|
fprintf(stderr, "Time to decode picture: %.3fs\n", decode_time);
|
2011-02-17 00:01:27 +01:00
|
|
|
}
|
2011-06-20 09:45:15 +02:00
|
|
|
end:
|
2012-05-12 01:00:57 +02:00
|
|
|
free((void*)data);
|
2011-06-20 09:45:15 +02:00
|
|
|
ok = (status == VP8_STATUS_OK);
|
|
|
|
if (!ok) {
|
|
|
|
fprintf(stderr, "Decoding of %s failed.\n", in_file);
|
|
|
|
fprintf(stderr, "Status: %d (%s)\n", status, kStatusMessages[status]);
|
2013-09-19 13:20:45 +02:00
|
|
|
goto Exit;
|
2011-06-20 09:45:15 +02:00
|
|
|
}
|
2010-09-30 15:34:38 +02:00
|
|
|
}
|
|
|
|
|
2013-08-19 21:09:37 +02:00
|
|
|
if (out_file != NULL) {
|
2013-08-21 23:56:35 +02:00
|
|
|
fprintf(stderr, "Decoded %s. Dimensions: %d x %d %s. Format: %s. "
|
|
|
|
"Now saving...\n",
|
2013-08-19 21:09:37 +02:00
|
|
|
in_file, output_buffer->width, output_buffer->height,
|
2013-08-21 23:56:35 +02:00
|
|
|
bitstream->has_alpha ? " (with alpha)" : "",
|
|
|
|
kFormatType[bitstream->format]);
|
2013-08-19 21:09:37 +02:00
|
|
|
ok = SaveOutput(output_buffer, format, out_file);
|
2011-02-17 00:01:27 +01:00
|
|
|
} else {
|
2013-08-21 23:56:35 +02:00
|
|
|
fprintf(stderr, "File %s can be decoded "
|
|
|
|
"(dimensions: %d x %d %s. Format: %s).\n",
|
2013-08-20 02:51:04 +02:00
|
|
|
in_file, output_buffer->width, output_buffer->height,
|
2013-08-21 23:56:35 +02:00
|
|
|
bitstream->has_alpha ? " (with alpha)" : "",
|
|
|
|
kFormatType[bitstream->format]);
|
2013-08-19 21:09:37 +02:00
|
|
|
fprintf(stderr, "Nothing written; "
|
|
|
|
"use -o flag to save the result as e.g. PNG.\n");
|
2010-09-30 15:34:38 +02:00
|
|
|
}
|
2013-09-19 13:20:45 +02:00
|
|
|
Exit:
|
2011-06-20 09:45:15 +02:00
|
|
|
WebPFreeDecBuffer(output_buffer);
|
2013-08-19 21:09:37 +02:00
|
|
|
return ok ? 0 : -1;
|
2010-09-30 15:34:38 +02:00
|
|
|
}
|
|
|
|
|
2011-08-25 23:22:32 +02:00
|
|
|
//------------------------------------------------------------------------------
|