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) {