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
This commit is contained in:
Pascal Massimino 2012-05-22 02:36:22 -07:00
parent d73e63a726
commit 638528cd1e
3 changed files with 69 additions and 33 deletions

View File

@ -443,10 +443,17 @@ Notes for tiles containing VP8 data:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ChunkHeader('ALPH') | | 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: : The filtering method used:
@ -455,28 +462,31 @@ Filtering method (F): 4 bits
* `2`: Vertical filter. * `2`: Vertical filter.
* `3`: Gradient 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. * `0`: no pre-processing
* `1`: Backward reference counts encoded with arithmetic encoder. * `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`. : SHOULD be `0`.
Alpha bitstream: _Chunk Size_ - `2` bytes Alpha bitstream: _Chunk Size_ - `1` bytes
: Encoded alpha bitstream. : Encoded alpha bitstream.
This optional chunk contains encoded alpha data for a single tile. This optional chunk contains encoded alpha data for a single tile.
Either **ALL or NONE** of the tiles must contain this chunk. Either **ALL or NONE** of the tiles must contain this chunk.
The alpha channel can be encoded either losslessly or with lossy The alpha channel data is losslessly stored as raw data (when
preprocessing (quantization). After the optional preprocessing, the compression method is '0') or compressed using the lossless format
alpha values are encoded with a lossless compression method like (when the compression method is '1').
zlib.
#### Color profile #### Color profile

View File

@ -13,15 +13,17 @@
#include "./vp8i.h" #include "./vp8i.h"
#include "../webp/decode.h" #include "../webp/decode.h"
#include "../utils/filters.h" #include "../utils/filters.h"
#include "../utils/quant_levels.h"
#if defined(__cplusplus) || defined(c_plusplus) #if defined(__cplusplus) || defined(c_plusplus)
extern "C" { extern "C" {
#endif #endif
// TODO(skal): find a common place between enc/ and dec/ for these: // 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_NO_COMPRESSION 0
#define ALPHA_LOSSLESS_COMPRESSION 1 #define ALPHA_LOSSLESS_COMPRESSION 1
#define ALPHA_PREPROCESSED_LEVELS 1
// TODO(skal): move to dsp/ ? // TODO(skal): move to dsp/ ?
static void CopyPlane(const uint8_t* src, int src_stride, 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; const size_t decoded_size = height * width;
uint8_t* unfiltered_data = NULL; uint8_t* unfiltered_data = NULL;
WEBP_FILTER_TYPE filter; WEBP_FILTER_TYPE filter;
int pre_processing;
int rsrv;
int ok = 0; int ok = 0;
int method; int method;
@ -59,12 +63,15 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size,
return 0; return 0;
} }
method = data[0] & 0x0f; method = (data[0] >> 0) & 0x03;
filter = data[0] >> 4; filter = (data[0] >> 2) & 0x03;
ok = (data[1] == 0); pre_processing = (data[0] >> 4) & 0x03;
rsrv = (data[0] >> 6) & 0x03;
if (method < ALPHA_NO_COMPRESSION || if (method < ALPHA_NO_COMPRESSION ||
method > ALPHA_LOSSLESS_COMPRESSION || method > ALPHA_LOSSLESS_COMPRESSION ||
filter >= WEBP_FILTER_LAST || !ok) { filter >= WEBP_FILTER_LAST ||
pre_processing > ALPHA_PREPROCESSED_LEVELS ||
rsrv != 0) {
return 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; decoded_data[i] = (output[i] >> 8) & 0xff;
} }
free(output); free(output);
ok = 1;
} }
if (ok) { if (ok) {
WebPFilterFunc unfilter_func = WebPUnfilters[filter]; WebPFilterFunc unfilter_func = WebPUnfilters[filter];
if (unfilter_func) { if (unfilter_func != NULL) {
unfiltered_data = (uint8_t*)malloc(decoded_size); unfiltered_data = (uint8_t*)malloc(decoded_size);
if (unfiltered_data == NULL) { if (unfiltered_data == NULL) {
if (method != ALPHA_NO_COMPRESSION) free(decoded_data); ok = 0;
return 0; goto Error;
} }
// TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode // TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode
// and apply filter per image-row. // 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); 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) { if (method != ALPHA_NO_COMPRESSION) {
free(decoded_data); free(decoded_data);
} }

View File

@ -20,9 +20,11 @@
extern "C" { extern "C" {
#endif #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_NO_COMPRESSION 0
#define ALPHA_LOSSLESS_COMPRESSION 1 #define ALPHA_LOSSLESS_COMPRESSION 1
#define ALPHA_PREPROCESSED_LEVELS 1
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// int EncodeAlpha(const uint8_t* data, int width, int height, int stride, // int EncodeAlpha(const uint8_t* data, int width, int height, int stride,
@ -106,24 +108,32 @@ static int EncodeLossless(const uint8_t* data, int width, int height,
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static int EncodeAlphaInternal(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, int method, int filter, int reduce_levels,
uint8_t* tmp_alpha, VP8BitWriter* const bw) { uint8_t* tmp_alpha, VP8BitWriter* const bw) {
int ok = 0; int ok = 0;
const uint8_t* alpha_src; const uint8_t* alpha_src;
WebPFilterFunc filter_func; WebPFilterFunc filter_func;
uint8_t header[ALPHA_HEADER_LEN]; uint8_t header;
size_t expected_size; size_t expected_size;
const size_t data_size = width * height;
#ifndef USE_LOSSLESS_ENCODER #ifndef USE_LOSSLESS_ENCODER
method = ALPHA_NO_COMPRESSION; method = ALPHA_NO_COMPRESSION;
#endif #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 = expected_size =
(method == ALPHA_NO_COMPRESSION) ? (ALPHA_HEADER_LEN + data_size) (method == ALPHA_NO_COMPRESSION) ? (ALPHA_HEADER_LEN + data_size)
: (data_size >> 5); : (data_size >> 5);
header[0] = (filter << 4) | method; header = method | (filter << 2);
header[1] = 0; // reserved byte for later use if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4;
VP8BitWriterInit(bw, expected_size); VP8BitWriterInit(bw, expected_size);
VP8BitWriterAppend(bw, header, sizeof(header)); VP8BitWriterAppend(bw, &header, ALPHA_HEADER_LEN);
filter_func = WebPFilters[filter]; filter_func = WebPFilters[filter];
if (filter_func) { 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, int quality, int method, int filter,
uint8_t** output, size_t* output_size) { uint8_t** output, size_t* output_size) {
uint8_t* quant_alpha = NULL; uint8_t* quant_alpha = NULL;
const size_t data_size = height * width; const size_t data_size = width * height;
int ok = 1; int ok = 1;
const int reduce_levels = (quality < 100);
// quick sanity checks // quick sanity checks
assert(data != NULL && output != NULL && output_size != NULL); 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). // Extract alpha data (width x height) from raw_data (stride x height).
CopyPlane(data, stride, quant_alpha, width, width, 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 // 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] // mapped to moderate quality 70. Hence Quality:[0, 70] -> Levels:[2, 16]
// and Quality:]70, 100] -> Levels:]16, 256]. // 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; uint8_t* filtered_alpha = NULL;
// We always test WEBP_FILTER_NONE first. // We always test WEBP_FILTER_NONE first.
ok = EncodeAlphaInternal(quant_alpha, width, height, method, ok = EncodeAlphaInternal(quant_alpha, width, height,
WEBP_FILTER_NONE, data_size, NULL, &bw); method, WEBP_FILTER_NONE, reduce_levels,
NULL, &bw);
if (!ok) { if (!ok) {
VP8BitWriterWipeOut(&bw); VP8BitWriterWipeOut(&bw);
goto End; goto End;
@ -235,8 +247,9 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride,
continue; continue;
} }
ok = EncodeAlphaInternal(quant_alpha, width, height, method, test_filter, ok = EncodeAlphaInternal(quant_alpha, width, height,
data_size, filtered_alpha, &tmp_bw); method, test_filter, reduce_levels,
filtered_alpha, &tmp_bw);
if (ok) { if (ok) {
const size_t score = VP8BitWriterSize(&tmp_bw); const size_t score = VP8BitWriterSize(&tmp_bw);
if (score < best_score) { if (score < best_score) {