From 638528cd1e2370a6e2285ef94d09b8a760451e6b Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Tue, 22 May 2012 02:36:22 -0700 Subject: [PATCH] bitstream update for lossy alpha compression now, we only use 2 bits for the filtering method, and 2 bits for the compression method. There's two additional bits which are INFORMATIVE, to specify whether the source has been pre-processed (level reduction) during compression. This can be used at decompression time for some post-processing (see DequantizeLevels()). New relevant spec excerpt: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ChunkHeader('ALPH') | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Rsv| P | F | C | Alpha Bitstream... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Compression method (C): 2 bits : The compression method used: * `0`: No compression. * `1`: Backward reference counts encoded with arithmetic encoder. Filtering method (F): 2 bits : The filtering method used: * `0`: None. * `1`: Horizontal filter. * `2`: Vertical filter. * `3`: Gradient filter. Pre-processing (P): 2 bits : These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. * `0`: no pre-processing * `1`: level reduction Decoders are not required to use this information in any specified way. Reserved (Rsv): 2 bits : SHOULD be `0`. Alpha bitstream: _Chunk Size_ - `1` bytes : Encoded alpha bitstream. This optional chunk contains encoded alpha data for a single tile. Either **ALL or NONE** of the tiles must contain this chunk. The alpha channel data is losslessly stored as raw data (when compression method is '0') or compressed using the lossless format (when the compression method is '1'). Change-Id: Ied8f5fb922707a953e6a2b601c69c73e552dda6b --- doc/webp-container-spec.txt | 34 ++++++++++++++++++++------------ src/dec/alpha.c | 29 +++++++++++++++++++-------- src/enc/alpha.c | 39 ++++++++++++++++++++++++------------- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/doc/webp-container-spec.txt b/doc/webp-container-spec.txt index 2fe1c6ab..85d45a89 100644 --- a/doc/webp-container-spec.txt +++ b/doc/webp-container-spec.txt @@ -443,10 +443,17 @@ Notes for tiles containing VP8 data: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ChunkHeader('ALPH') | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | F | C | Reserved | Alpha Bitstream | + |Rsv| P | F | C | Alpha Bitstream... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -Filtering method (F): 4 bits +Compression method (C): 2 bits + +: The compression method used: + + * `0`: No compression. + * `1`: Backward reference counts encoded with arithmetic encoder. + +Filtering method (F): 2 bits : The filtering method used: @@ -455,28 +462,31 @@ Filtering method (F): 4 bits * `2`: Vertical filter. * `3`: Gradient filter. -Compression method (C): 4 bits +Pre-processing (P): 2 bits -: The compression method used: +: These INFORMATIVE bits are used to signal the pre-processing that has +been performed during compression. The decoder can use this information to +e.g. dither the values or smooth the gradients prior to display. - * `0`: No compression. - * `1`: Backward reference counts encoded with arithmetic encoder. + * `0`: no pre-processing + * `1`: level reduction -Reserved: 8 bits +Decoders are not required to use this information in any specified way. + +Reserved (Rsv): 2 bits : SHOULD be `0`. -Alpha bitstream: _Chunk Size_ - `2` bytes +Alpha bitstream: _Chunk Size_ - `1` bytes : Encoded alpha bitstream. This optional chunk contains encoded alpha data for a single tile. Either **ALL or NONE** of the tiles must contain this chunk. -The alpha channel can be encoded either losslessly or with lossy -preprocessing (quantization). After the optional preprocessing, the -alpha values are encoded with a lossless compression method like -zlib. +The alpha channel data is losslessly stored as raw data (when +compression method is '0') or compressed using the lossless format +(when the compression method is '1'). #### Color profile diff --git a/src/dec/alpha.c b/src/dec/alpha.c index acb0d63e..03a30083 100644 --- a/src/dec/alpha.c +++ b/src/dec/alpha.c @@ -13,15 +13,17 @@ #include "./vp8i.h" #include "../webp/decode.h" #include "../utils/filters.h" +#include "../utils/quant_levels.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif // TODO(skal): find a common place between enc/ and dec/ for these: -#define ALPHA_HEADER_LEN 2 +#define ALPHA_HEADER_LEN 1 #define ALPHA_NO_COMPRESSION 0 #define ALPHA_LOSSLESS_COMPRESSION 1 +#define ALPHA_PREPROCESSED_LEVELS 1 // TODO(skal): move to dsp/ ? static void CopyPlane(const uint8_t* src, int src_stride, @@ -49,6 +51,8 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size, const size_t decoded_size = height * width; uint8_t* unfiltered_data = NULL; WEBP_FILTER_TYPE filter; + int pre_processing; + int rsrv; int ok = 0; int method; @@ -59,12 +63,15 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size, return 0; } - method = data[0] & 0x0f; - filter = data[0] >> 4; - ok = (data[1] == 0); + method = (data[0] >> 0) & 0x03; + filter = (data[0] >> 2) & 0x03; + pre_processing = (data[0] >> 4) & 0x03; + rsrv = (data[0] >> 6) & 0x03; if (method < ALPHA_NO_COMPRESSION || method > ALPHA_LOSSLESS_COMPRESSION || - filter >= WEBP_FILTER_LAST || !ok) { + filter >= WEBP_FILTER_LAST || + pre_processing > ALPHA_PREPROCESSED_LEVELS || + rsrv != 0) { return 0; } @@ -91,15 +98,16 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size, decoded_data[i] = (output[i] >> 8) & 0xff; } free(output); + ok = 1; } if (ok) { WebPFilterFunc unfilter_func = WebPUnfilters[filter]; - if (unfilter_func) { + if (unfilter_func != NULL) { unfiltered_data = (uint8_t*)malloc(decoded_size); if (unfiltered_data == NULL) { - if (method != ALPHA_NO_COMPRESSION) free(decoded_data); - return 0; + ok = 0; + goto Error; } // TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode // and apply filter per image-row. @@ -112,6 +120,11 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size, CopyPlane(decoded_data, width, output, stride, width, height); } } + if (pre_processing == ALPHA_PREPROCESSED_LEVELS) { + ok = DequantizeLevels(decoded_data, width, height); + } + + Error: if (method != ALPHA_NO_COMPRESSION) { free(decoded_data); } diff --git a/src/enc/alpha.c b/src/enc/alpha.c index c5e02ad5..a9d3ca7f 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -20,9 +20,11 @@ extern "C" { #endif -#define ALPHA_HEADER_LEN 2 +// TODO(skal): find a common place between enc/ and dec/ for these: +#define ALPHA_HEADER_LEN 1 #define ALPHA_NO_COMPRESSION 0 #define ALPHA_LOSSLESS_COMPRESSION 1 +#define ALPHA_PREPROCESSED_LEVELS 1 // ----------------------------------------------------------------------------- // int EncodeAlpha(const uint8_t* data, int width, int height, int stride, @@ -105,25 +107,33 @@ static int EncodeLossless(const uint8_t* data, int width, int height, // ----------------------------------------------------------------------------- -static int EncodeAlphaInternal(const uint8_t* data, int width, int height, - int method, int filter, size_t data_size, +static int EncodeAlphaInternal(const uint8_t* data, int width, int height, + int method, int filter, int reduce_levels, uint8_t* tmp_alpha, VP8BitWriter* const bw) { int ok = 0; const uint8_t* alpha_src; WebPFilterFunc filter_func; - uint8_t header[ALPHA_HEADER_LEN]; + uint8_t header; size_t expected_size; + const size_t data_size = width * height; #ifndef USE_LOSSLESS_ENCODER method = ALPHA_NO_COMPRESSION; #endif + assert(filter >= 0 && filter < WEBP_FILTER_LAST); + assert(method >= ALPHA_NO_COMPRESSION); + assert(method <= ALPHA_LOSSLESS_COMPRESSION); + assert(sizeof(header) == ALPHA_HEADER_LEN); + // TODO(skal): have a common function and #define's to validate alpha params. + expected_size = (method == ALPHA_NO_COMPRESSION) ? (ALPHA_HEADER_LEN + data_size) : (data_size >> 5); - header[0] = (filter << 4) | method; - header[1] = 0; // reserved byte for later use + header = method | (filter << 2); + if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4; + VP8BitWriterInit(bw, expected_size); - VP8BitWriterAppend(bw, header, sizeof(header)); + VP8BitWriterAppend(bw, &header, ALPHA_HEADER_LEN); filter_func = WebPFilters[filter]; if (filter_func) { @@ -163,8 +173,9 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, int quality, int method, int filter, uint8_t** output, size_t* output_size) { uint8_t* quant_alpha = NULL; - const size_t data_size = height * width; + const size_t data_size = width * height; int ok = 1; + const int reduce_levels = (quality < 100); // quick sanity checks assert(data != NULL && output != NULL && output_size != NULL); @@ -188,7 +199,7 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, // Extract alpha data (width x height) from raw_data (stride x height). CopyPlane(data, stride, quant_alpha, width, width, height); - if (quality < 100) { // No Quantization required for 'quality = 100'. + if (reduce_levels) { // No Quantization required for 'quality = 100'. // 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence // mapped to moderate quality 70. Hence Quality:[0, 70] -> Levels:[2, 16] // and Quality:]70, 100] -> Levels:]16, 256]. @@ -204,8 +215,9 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, uint8_t* filtered_alpha = NULL; // We always test WEBP_FILTER_NONE first. - ok = EncodeAlphaInternal(quant_alpha, width, height, method, - WEBP_FILTER_NONE, data_size, NULL, &bw); + ok = EncodeAlphaInternal(quant_alpha, width, height, + method, WEBP_FILTER_NONE, reduce_levels, + NULL, &bw); if (!ok) { VP8BitWriterWipeOut(&bw); goto End; @@ -235,8 +247,9 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, continue; } - ok = EncodeAlphaInternal(quant_alpha, width, height, method, test_filter, - data_size, filtered_alpha, &tmp_bw); + ok = EncodeAlphaInternal(quant_alpha, width, height, + method, test_filter, reduce_levels, + filtered_alpha, &tmp_bw); if (ok) { const size_t score = VP8BitWriterSize(&tmp_bw); if (score < best_score) {