use header-less lossless bitstream for alpha channel

This saves ~26 bytes of headers.
* introduce new VP8LDecodeAlphaImageStream() for decoding
* use VP8LEncodeStream() for encoding
* refactor code a bit

still TODO: make the alpha-quality/enc-method user-configurable

Change-Id: I23e599bebe335cfb5868e746e076c3358ef12e71
This commit is contained in:
Pascal Massimino 2012-05-23 08:01:44 -07:00
parent 75d7f3b222
commit 39bf5d6497
4 changed files with 118 additions and 64 deletions

View File

@ -11,7 +11,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "./vp8i.h" #include "./vp8i.h"
#include "../webp/decode.h" #include "./vp8li.h"
#include "../utils/filters.h" #include "../utils/filters.h"
#include "../utils/quant_levels.h" #include "../utils/quant_levels.h"
@ -79,26 +79,12 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size,
ok = (data_size >= decoded_size); ok = (data_size >= decoded_size);
decoded_data = (uint8_t*)data + ALPHA_HEADER_LEN; decoded_data = (uint8_t*)data + ALPHA_HEADER_LEN;
} else { } else {
size_t i;
int w, h;
uint32_t* const output =
(uint32_t*)WebPDecodeRGBA(data + ALPHA_HEADER_LEN,
data_size - ALPHA_HEADER_LEN,
&w, &h);
if (w != width || h != height || output == NULL) {
free(output);
return 0;
}
decoded_data = (uint8_t*)malloc(decoded_size); decoded_data = (uint8_t*)malloc(decoded_size);
if (decoded_data == NULL) { if (decoded_data == NULL) return 0;
free(output); ok = VP8LDecodeAlphaImageStream(width, height,
return 0; data + ALPHA_HEADER_LEN,
} data_size - ALPHA_HEADER_LEN,
for (i = 0; i < decoded_size; ++i) { decoded_data);
decoded_data[i] = (output[i] >> 8) & 0xff;
}
free(output);
ok = 1;
} }
if (ok) { if (ok) {
@ -119,10 +105,10 @@ static int DecodeAlpha(const uint8_t* data, size_t data_size,
// Construct raw_data (height x stride) from alpha data (height x width). // Construct raw_data (height x stride) from alpha data (height x width).
CopyPlane(decoded_data, width, output, stride, width, height); CopyPlane(decoded_data, width, output, stride, width, height);
} }
}
if (pre_processing == ALPHA_PREPROCESSED_LEVELS) { if (pre_processing == ALPHA_PREPROCESSED_LEVELS) {
ok = DequantizeLevels(decoded_data, width, height); ok = DequantizeLevels(decoded_data, width, height);
} }
}
Error: Error:
if (method != ALPHA_NO_COMPRESSION) { if (method != ALPHA_NO_COMPRESSION) {

View File

@ -455,7 +455,7 @@ static int EmitRows(WEBP_CSP_MODE colorspace,
// Note that 'pixel_stride' is in units of 'uint32_t' (and not 'bytes). // Note that 'pixel_stride' is in units of 'uint32_t' (and not 'bytes).
// Returns true if the crop window is not empty. // Returns true if the crop window is not empty.
static int SetCropWindow(VP8Io* const io, int y_start, int y_end, static int SetCropWindow(VP8Io* const io, int y_start, int y_end,
uint32_t** const in_data, int pixel_stride) { const uint32_t** const in_data, int pixel_stride) {
assert(y_start < y_end); assert(y_start < y_end);
assert(io->crop_left < io->crop_right); assert(io->crop_left < io->crop_right);
if (y_end > io->crop_bottom) { if (y_end > io->crop_bottom) {
@ -497,52 +497,59 @@ static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr,
typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row); typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row);
// Processes (transforms, scales & color-converts) the rows decoded after the static void ApplyTransforms(VP8LDecoder* const dec, int num_rows,
// last call. const uint32_t* const rows) {
static void ProcessRows(VP8LDecoder* const dec, int row) {
int n = dec->next_transform_; int n = dec->next_transform_;
VP8Io* const io = dec->io_;
WebPDecParams* const params = (WebPDecParams*)io->opaque;
const WebPDecBuffer* const output = params->output;
const int argb_offset = dec->width_ * dec->last_row_;
const int num_rows = row - dec->last_row_;
const int cache_pixs = dec->width_ * num_rows; const int cache_pixs = dec->width_ * num_rows;
uint32_t* rows_data = dec->argb_cache_; uint32_t* rows_data = dec->argb_cache_;
int num_rows_out = num_rows; // Default. const int start_row = dec->last_row_;
const int end_row = start_row + num_rows;
if (num_rows <= 0) return; // Nothing to be done.
// Inverse transforms. // Inverse transforms.
// TODO: most transforms only need to operate on the cropped region only. // TODO: most transforms only need to operate on the cropped region only.
memcpy(rows_data, dec->argb_ + argb_offset, cache_pixs * sizeof(*rows_data)); memcpy(rows_data, rows, cache_pixs * sizeof(*rows_data));
while (n-- > 0) { while (n-- > 0) {
VP8LTransform* const transform = &dec->transforms_[n]; VP8LTransform* const transform = &dec->transforms_[n];
VP8LInverseTransform(transform, dec->last_row_, row, VP8LInverseTransform(transform, start_row, end_row, rows, rows_data);
dec->argb_ + argb_offset, rows_data);
} }
}
// Processes (transforms, scales & color-converts) the rows decoded after the
// last call.
static void ProcessRows(VP8LDecoder* const dec, int row) {
const uint32_t* const rows = dec->argb_ + dec->width_ * dec->last_row_;
const int num_rows = row - dec->last_row_;
if (num_rows <= 0) return; // Nothing to be done.
ApplyTransforms(dec, num_rows, rows);
// Emit output. // Emit output.
{ {
VP8Io* const io = dec->io_;
const uint32_t* rows_data = dec->argb_cache_;
if (!SetCropWindow(io, dec->last_row_, row, &rows_data, io->width)) {
// Nothing to output (this time).
} else {
WebPDecParams* const params = (WebPDecParams*)io->opaque;
const WebPDecBuffer* const output = params->output;
const WebPRGBABuffer* const buf = &output->u.RGBA; const WebPRGBABuffer* const buf = &output->u.RGBA;
uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride; uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride;
const WEBP_CSP_MODE colorspace = output->colorspace;
if (!SetCropWindow(io, dec->last_row_, row, &rows_data, io->width)) {
num_rows_out = 0; // Nothing to output.
} else {
const int in_stride = io->width * sizeof(*rows_data); const int in_stride = io->width * sizeof(*rows_data);
num_rows_out = io->use_scaling ? const WEBP_CSP_MODE colorspace = output->colorspace;
const int num_rows_out = io->use_scaling ?
EmitRescaledRows(dec, colorspace, rows_data, in_stride, io->mb_h, EmitRescaledRows(dec, colorspace, rows_data, in_stride, io->mb_h,
rgba, buf->stride) : rgba, buf->stride) :
EmitRows(colorspace, rows_data, in_stride, io->mb_w, io->mb_h, EmitRows(colorspace, rows_data, in_stride, io->mb_w, io->mb_h,
rgba, buf->stride); rgba, buf->stride);
// Update 'last_out_row_'.
dec->last_out_row_ += num_rows_out;
assert(dec->last_out_row_ <= output->height);
} }
} }
// Update 'last_row' and 'last_out_row'. // Update 'last_row_'.
dec->last_row_ = row; dec->last_row_ = row;
assert(dec->last_row_ <= io->height); assert(dec->last_row_ <= dec->height_);
dec->last_out_row_ += num_rows_out;
assert(dec->last_out_row_ <= output->height);
} }
static int DecodeImageData(VP8LDecoder* const dec, static int DecodeImageData(VP8LDecoder* const dec,
@ -940,6 +947,63 @@ static int AllocateARGBBuffers(VP8LDecoder* const dec, int final_width) {
return 1; return 1;
} }
//------------------------------------------------------------------------------
// Special row-processing that only stores the alpha data.
static void ExtractAlphaRows(VP8LDecoder* const dec, int row) {
const int num_rows = row - dec->last_row_;
const int cache_pixs = dec->width_ * num_rows;
const int argb_offset = dec->width_ * dec->last_row_;
const uint32_t* const in = dec->argb_ + argb_offset;
if (num_rows <= 0) return; // Nothing to be done.
ApplyTransforms(dec, num_rows, in);
// Extract alpha (which is stored in the green plane).
{
uint8_t* const dst = (uint8_t*)dec->io_->opaque + argb_offset;
const uint32_t* const src = dec->argb_cache_;
int i;
for (i = 0; i < cache_pixs; ++i) dst[i] = (src[i] >> 8) & 0xff;
}
dec->last_row_ = dec->last_out_row_ = row;
}
int VP8LDecodeAlphaImageStream(int width, int height, const uint8_t* const data,
const size_t data_size, uint8_t* const output) {
VP8Io io;
int ok = 0;
VP8LDecoder* const dec = VP8LNew();
if (dec == NULL) return 0;
dec->width_ = width;
dec->height_ = height;
dec->io_ = &io;
VP8InitIo(&io);
WebPInitCustomIo(NULL, &io); // Just a sanity Init. io won't be used.
io.opaque = output;
dec->status_ = VP8_STATUS_OK;
VP8LInitBitReader(&dec->br_, data, data_size);
dec->action_ = READ_HDR;
if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Err;
// Allocate output (note that dec->width_ may have changed here).
if (!AllocateARGBBuffers(dec, width)) goto Err;
// Decode (with special row processing).
dec->action_ = READ_DATA;
ok = DecodeImageData(dec, dec->argb_, dec->width_, dec->height_,
ExtractAlphaRows);
Err:
VP8LDelete(dec);
return ok;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) {

View File

@ -106,6 +106,13 @@ int VP8LGetInfo(const uint8_t* data,
size_t data_size, // data available so far size_t data_size, // data available so far
int *width, int *height); int *width, int *height);
// Decodes a raw image stream (without header) and store the alpha data
// into *output, which must be of size width x height. Returns false in case
// of error.
int VP8LDecodeAlphaImageStream(int width, int height, const uint8_t* const data,
const size_t data_size, uint8_t* const output);
// Allocates and initialize a new lossless decoder instance. // Allocates and initialize a new lossless decoder instance.
VP8LDecoder* VP8LNew(void); VP8LDecoder* VP8LNew(void);

View File

@ -54,16 +54,6 @@ extern "C" {
#ifdef USE_LOSSLESS_ENCODER #ifdef USE_LOSSLESS_ENCODER
#include "../enc/vp8li.h" #include "../enc/vp8li.h"
#include "../webp/encode.h"
static int MyWriter(const uint8_t* data, size_t data_size,
const WebPPicture* const picture) {
VP8BitWriter* const bw = (VP8BitWriter*)picture->custom_ptr;
if (data_size > 0) {
VP8BitWriterAppend(bw, data, data_size);
}
return !bw->error_;
}
static int EncodeLossless(const uint8_t* data, int width, int height, static int EncodeLossless(const uint8_t* data, int width, int height,
VP8BitWriter* const bw) { VP8BitWriter* const bw) {
@ -71,6 +61,7 @@ static int EncodeLossless(const uint8_t* data, int width, int height,
int ok = 0; int ok = 0;
WebPConfig config; WebPConfig config;
WebPPicture picture; WebPPicture picture;
VP8LBitWriter tmp_bw;
WebPPictureInit(&picture); WebPPictureInit(&picture);
picture.width = width; picture.width = width;
@ -78,6 +69,7 @@ static int EncodeLossless(const uint8_t* data, int width, int height,
picture.use_argb_input = 1; picture.use_argb_input = 1;
if (!WebPPictureAlloc(&picture)) return 0; if (!WebPPictureAlloc(&picture)) return 0;
// Transfer the alpha values to the green channel.
{ {
int i, j; int i, j;
uint32_t* dst = picture.argb; uint32_t* dst = picture.argb;
@ -90,16 +82,21 @@ static int EncodeLossless(const uint8_t* data, int width, int height,
dst += picture.argb_stride; dst += picture.argb_stride;
} }
} }
picture.writer = MyWriter;
picture.custom_ptr = (void*)bw;
WebPConfigInit(&config); WebPConfigInit(&config);
config.lossless = 1; config.lossless = 1;
config.method = 2; config.method = 2;
config.quality = 100; config.quality = 100; // TODO(skal): make it adjustable.
ok = WebPEncode(&config, &picture); VP8LBitWriterInit(&tmp_bw, (width * height) >> 3);
ok = (VP8LEncodeStream(&config, &picture, &tmp_bw) == VP8_ENC_OK);
WebPPictureFree(&picture); WebPPictureFree(&picture);
if (ok) {
const uint8_t* const data = VP8LBitWriterFinish(&tmp_bw);
const size_t data_size = VP8LBitWriterNumBytes(&tmp_bw);
VP8BitWriterAppend(bw, data, data_size);
}
VP8LBitWriterDestroy(&tmp_bw);
return ok && !bw->error_; return ok && !bw->error_;
} }