Intertwined decoding of alpha and RGB

This will reduce the time to first decoded pixel.

Change-Id: I07b900c0ed4af3aac806b2731e11cd18ec16d016
This commit is contained in:
Urvang Joshi 2013-06-11 16:04:54 -07:00
parent 3fa595a571
commit 068db59e26
11 changed files with 250 additions and 88 deletions

3
NEWS
View File

@ -2,6 +2,9 @@
* Add incremental decoding support for images containing ALPH and ICCP chunks.
* New function: WebPMuxGetCanvasSize
* BMP and TIFF format output added to 'dwebp'
* Significant memory reduction for decoding lossy images with alpha.
* Intertwined decoding of RGB and alpha for a shorter
time-to-first-decoded-pixel.
- 3/20/13: version 0.3.0
This is a binary compatible release.

View File

@ -176,6 +176,7 @@ HDRS_INSTALLED = \
src/webp/types.h \
HDRS = \
src/dec/alphai.h \
src/dec/decode_vp8.h \
src/dec/vp8i.h \
src/dec/vp8li.h \

View File

@ -3,6 +3,7 @@ noinst_LTLIBRARIES = libwebpdecode.la
libwebpdecode_la_SOURCES =
libwebpdecode_la_SOURCES += alpha.c
libwebpdecode_la_SOURCES += alphai.h
libwebpdecode_la_SOURCES += buffer.c
libwebpdecode_la_SOURCES += decode_vp8.h
libwebpdecode_la_SOURCES += frame.c

View File

@ -12,9 +12,9 @@
// Author: Skal (pascal.massimino@gmail.com)
#include <stdlib.h>
#include "./alphai.h"
#include "./vp8i.h"
#include "./vp8li.h"
#include "../utils/filters.h"
#include "../utils/quant_levels_dec.h"
#include "../webp/format_constants.h"
@ -23,87 +23,140 @@ extern "C" {
#endif
//------------------------------------------------------------------------------
// Decodes the compressed data 'data' of size 'data_size' into the 'output'.
// The 'output' buffer should be pre-allocated and must be of the same
// dimension 'height'x'width', as that of the image.
//
// Returns 1 on successfully decoding the compressed alpha and
// 0 if either:
// error in bit-stream header (invalid compression mode or filter), or
// error returned by appropriate compression method.
// ALPHDecoder object.
static int DecodeAlpha(const uint8_t* data, size_t data_size,
int width, int height, uint8_t* output) {
WEBP_FILTER_TYPE filter;
int pre_processing;
int rsrv;
ALPHDecoder* ALPHNew(void) {
ALPHDecoder* const dec = (ALPHDecoder*)calloc(1, sizeof(*dec));
return dec;
}
void ALPHDelete(ALPHDecoder* const dec) {
if (dec != NULL) {
VP8LDelete(dec->vp8l_dec_);
dec->vp8l_dec_ = NULL;
free(dec);
}
}
//------------------------------------------------------------------------------
// Decoding.
// Initialize alpha decoding by parsing the alpha header and decoding the image
// header for alpha data stored using lossless compression.
// Returns false in case of error in alpha header (data too short, invalid
// compression method or filter, error in lossless header data etc).
static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data,
size_t data_size, int width, int height, uint8_t* output) {
int ok = 0;
int method;
const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN;
const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN;
int rsrv;
assert(width > 0 && height > 0);
assert(data != NULL && output != NULL);
dec->width_ = width;
dec->height_ = height;
if (data_size <= ALPHA_HEADER_LEN) {
return 0;
}
method = (data[0] >> 0) & 0x03;
filter = (data[0] >> 2) & 0x03;
pre_processing = (data[0] >> 4) & 0x03;
dec->method_ = (data[0] >> 0) & 0x03;
dec->filter_ = (data[0] >> 2) & 0x03;
dec->pre_processing_ = (data[0] >> 4) & 0x03;
rsrv = (data[0] >> 6) & 0x03;
if (method < ALPHA_NO_COMPRESSION ||
method > ALPHA_LOSSLESS_COMPRESSION ||
filter >= WEBP_FILTER_LAST ||
pre_processing > ALPHA_PREPROCESSED_LEVELS ||
if (dec->method_ < ALPHA_NO_COMPRESSION ||
dec->method_ > ALPHA_LOSSLESS_COMPRESSION ||
dec->filter_ >= WEBP_FILTER_LAST ||
dec->pre_processing_ > ALPHA_PREPROCESSED_LEVELS ||
rsrv != 0) {
return 0;
}
if (method == ALPHA_NO_COMPRESSION) {
const size_t alpha_decoded_size = width * height;
if (dec->method_ == ALPHA_NO_COMPRESSION) {
const size_t alpha_decoded_size = dec->width_ * dec->height_;
ok = (alpha_data_size >= alpha_decoded_size);
if (ok) memcpy(output, alpha_data, alpha_decoded_size);
} else {
ok = VP8LDecodeAlphaImageStream(width, height, alpha_data, alpha_data_size,
output);
assert(dec->method_ == ALPHA_LOSSLESS_COMPRESSION);
ok = VP8LDecodeAlphaHeader(dec, alpha_data, alpha_data_size, output);
}
if (ok) {
WebPUnfilterFunc unfilter_func = WebPUnfilters[filter];
if (unfilter_func != NULL) {
// TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode
// and apply filter per image-row.
unfilter_func(width, height, width, 0, height, output);
}
if (pre_processing == ALPHA_PREPROCESSED_LEVELS) {
ok = DequantizeLevels(output, width, height, 0, height);
}
}
return ok;
}
// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha
// starting from row number 'row'. It assumes that rows upto (row - 1) have
// already been decoded.
// Returns false in case of bitstream error.
static int ALPHDecode(VP8Decoder* const dec, int row, int num_rows) {
ALPHDecoder* const alph_dec = dec->alph_dec_;
const int width = alph_dec->width_;
const int height = alph_dec->height_;
WebPUnfilterFunc unfilter_func = WebPUnfilters[alph_dec->filter_];
uint8_t* const output = dec->alpha_plane_;
if (alph_dec->method_ == ALPHA_NO_COMPRESSION) {
const size_t offset = row * width;
const size_t num_pixels = num_rows * width;
assert(dec->alpha_data_size_ >= ALPHA_HEADER_LEN + offset + num_pixels);
memcpy(dec->alpha_plane_ + offset,
dec->alpha_data_ + ALPHA_HEADER_LEN + offset, num_pixels);
} else { // alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION
assert(alph_dec->vp8l_dec_ != NULL);
if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) {
return 0;
}
}
if (unfilter_func != NULL) {
unfilter_func(width, height, width, row, num_rows, output);
}
if (alph_dec->pre_processing_ == ALPHA_PREPROCESSED_LEVELS) {
if (!DequantizeLevels(output, width, height, row, num_rows)) {
return 0;
}
}
if (row + num_rows == dec->pic_hdr_.height_) {
dec->is_alpha_decoded_ = 1;
}
return 1;
}
//------------------------------------------------------------------------------
// Main entry point.
const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec,
int row, int num_rows) {
const int width = dec->pic_hdr_.width_;
const int height = dec->pic_hdr_.height_;
if (row < 0 || num_rows < 0 || row + num_rows > height) {
if (row < 0 || num_rows <= 0 || row + num_rows > height) {
return NULL; // sanity check.
}
if (row == 0) {
// Decode everything during the first call.
assert(!dec->is_alpha_decoded_);
if (!DecodeAlpha(dec->alpha_data_, (size_t)dec->alpha_data_size_,
// Initialize decoding.
assert(dec->alpha_plane_ != NULL);
dec->alph_dec_ = ALPHNew();
if (dec->alph_dec_ == NULL) return NULL;
if (!ALPHInit(dec->alph_dec_, dec->alpha_data_, dec->alpha_data_size_,
width, height, dec->alpha_plane_)) {
return NULL; // Error.
ALPHDelete(dec->alph_dec_);
dec->alph_dec_ = NULL;
return NULL;
}
dec->is_alpha_decoded_ = 1;
}
if (!dec->is_alpha_decoded_) {
int ok = 0;
assert(dec->alph_dec_ != NULL);
ok = ALPHDecode(dec, row, num_rows);
if (!ok || dec->is_alpha_decoded_) {
ALPHDelete(dec->alph_dec_);
dec->alph_dec_ = NULL;
}
if (!ok) return NULL; // Error.
}
// Return a pointer to the current decoded row.

55
src/dec/alphai.h Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// 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.
// -----------------------------------------------------------------------------
//
// Alpha decoder: internal header.
//
// Author: Urvang (urvang@google.com)
#ifndef WEBP_DEC_ALPHAI_H_
#define WEBP_DEC_ALPHAI_H_
#include "./webpi.h"
#include "../utils/filters.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
struct VP8LDecoder; // Defined in dec/vp8li.h.
typedef struct ALPHDecoder ALPHDecoder;
struct ALPHDecoder {
int width_;
int height_;
int method_;
WEBP_FILTER_TYPE filter_;
int pre_processing_;
struct VP8LDecoder* vp8l_dec_;
VP8Io io_;
size_t bytes_per_pixel_; // Although alpha channel requires only 1 byte per
// pixel, sometimes VP8LDecoder may need to allocate
// 4 bytes per pixel internally during decode.
};
//------------------------------------------------------------------------------
// internal functions. Not public.
// Allocates a new alpha decoder instance.
ALPHDecoder* ALPHNew(void);
// Clears and deallocates an alpha decoder instance.
void ALPHDelete(ALPHDecoder* const dec);
//------------------------------------------------------------------------------
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"
#endif
#endif /* WEBP_DEC_ALPHAI_H_ */

View File

@ -201,11 +201,8 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) {
}
io->a = NULL;
if (dec->alpha_data_ != NULL && y_start < y_end) {
// TODO(skal): several things to correct here:
// * testing presence of alpha with dec->alpha_data_ is not a good idea
// * we're actually decompressing the full plane only once. It should be
// more obvious from signature.
// * we could free alpha_data_ right after this call, but we don't own.
// TODO(skal): testing presence of alpha with dec->alpha_data_ is not a
// good idea.
io->a = VP8DecompressAlphaRows(dec, y_start, y_end - y_start);
if (io->a == NULL) {
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,

View File

@ -15,6 +15,7 @@
#include <string.h>
#include <stdlib.h>
#include "./alphai.h"
#include "./webpi.h"
#include "./vp8i.h"
#include "../utils/utils.h"
@ -143,7 +144,22 @@ static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) {
}
assert(last_part >= 0);
dec->parts_[last_part].buf_end_ = mem->buf_ + mem->end_;
if (NeedCompressedAlpha(idec)) dec->alpha_data_ += offset;
if (NeedCompressedAlpha(idec)) {
ALPHDecoder* const alph_dec = dec->alph_dec_;
dec->alpha_data_ += offset;
if (alph_dec != NULL) {
if (alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION) {
VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec_;
assert(alph_vp8l_dec != NULL);
assert(dec->alpha_data_size_ >= ALPHA_HEADER_LEN);
VP8LBitReaderSetBuffer(&alph_vp8l_dec->br_,
dec->alpha_data_ + ALPHA_HEADER_LEN,
dec->alpha_data_size_ - ALPHA_HEADER_LEN);
} else { // alph_dec->method_ == ALPHA_NO_COMPRESSION
// Nothing special to do in this case.
}
}
}
} else { // Resize lossless bitreader
VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_;
VP8LBitReaderSetBuffer(&dec->br_, new_base, MemDataSize(mem));

View File

@ -13,6 +13,7 @@
#include <stdlib.h>
#include "./alphai.h"
#include "./vp8i.h"
#include "./vp8li.h"
#include "./webpi.h"
@ -748,6 +749,8 @@ void VP8Clear(VP8Decoder* const dec) {
if (dec->use_threads_) {
WebPWorkerEnd(&dec->worker_);
}
ALPHDelete(dec->alph_dec_);
dec->alph_dec_ = NULL;
free(dec->mem_);
dec->mem_ = NULL;
dec->mem_size_ = 0;

View File

@ -279,12 +279,14 @@ struct VP8Decoder {
int filter_row_; // per-row flag
VP8FInfo fstrengths_[NUM_MB_SEGMENTS][2]; // precalculated per-segment/type
// extensions
// Alpha
struct ALPHDecoder* alph_dec_; // alpha-plane decoder object
const uint8_t* alpha_data_; // compressed alpha data (if present)
size_t alpha_data_size_;
int is_alpha_decoded_; // true if alpha_data_ is decoded in alpha_plane_
uint8_t* alpha_plane_; // output. Persistent, contains the whole data.
// extensions
int layer_colorspace_;
const uint8_t* layer_data_; // compressed layer data (if present)
size_t layer_data_size_;

View File

@ -14,6 +14,7 @@
#include <stdio.h>
#include <stdlib.h>
#include "./alphai.h"
#include "./vp8li.h"
#include "../dsp/lossless.h"
#include "../dsp/yuv.h"
@ -1125,52 +1126,72 @@ static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int row) {
dec->last_row_ = dec->last_out_row_ = row;
}
int VP8LDecodeAlphaImageStream(int width, int height, const uint8_t* const data,
size_t data_size, uint8_t* const output) {
VP8Io io;
int ok = 0;
VP8LDecoder* const dec = VP8LNew();
size_t bytes_per_pixel = sizeof(uint32_t); // Default: BGRA mode.
if (dec == NULL) return 0;
int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec,
const uint8_t* const data, size_t data_size,
uint8_t* const output) {
VP8LDecoder* dec;
VP8Io* io;
assert(alph_dec != NULL);
alph_dec->vp8l_dec_ = VP8LNew();
if (alph_dec->vp8l_dec_ == NULL) return 0;
dec = alph_dec->vp8l_dec_;
dec->width_ = width;
dec->height_ = height;
dec->io_ = &io;
alph_dec->bytes_per_pixel_ = sizeof(uint32_t); // Default: BGRA mode.
VP8InitIo(&io);
WebPInitCustomIo(NULL, &io); // Just a sanity Init. io won't be used.
io.opaque = output;
io.width = width;
io.height = height;
dec->width_ = alph_dec->width_;
dec->height_ = alph_dec->height_;
dec->io_ = &alph_dec->io_;
io = dec->io_;
VP8InitIo(io);
WebPInitCustomIo(NULL, io); // Just a sanity Init. io won't be used.
io->opaque = output;
io->width = alph_dec->width_;
io->height = alph_dec->height_;
dec->status_ = VP8_STATUS_OK;
VP8LInitBitReader(&dec->br_, data, data_size);
dec->action_ = READ_HDR;
if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Err;
if (!DecodeImageStream(alph_dec->width_, alph_dec->height_, 1, dec, NULL)) {
goto Err;
}
// Special case: if alpha data contains only the color indexing transform
// (a frequent case), we will use DecodeAlphaData() method that only needs
// allocation of 1 byte per pixel (alpha channel).
if (dec->next_transform_ == 1 &&
dec->transforms_[0].type_ == COLOR_INDEXING_TRANSFORM) {
bytes_per_pixel = sizeof(uint8_t);
alph_dec->bytes_per_pixel_ = sizeof(uint8_t);
}
// Allocate internal buffers (note that dec->width_ may have changed here).
if (!AllocateInternalBuffers(dec, width, bytes_per_pixel)) goto Err;
if (!AllocateInternalBuffers(dec, alph_dec->width_,
alph_dec->bytes_per_pixel_)) {
goto Err;
}
// Decode (with special row processing).
dec->action_ = READ_DATA;
ok = (bytes_per_pixel == sizeof(uint8_t)) ?
DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_,
dec->height_, ExtractPalettedAlphaRows) :
DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_,
dec->height_, ExtractAlphaRows);
return 1;
Err:
VP8LDelete(dec);
return ok;
VP8LDelete(alph_dec->vp8l_dec_);
alph_dec->vp8l_dec_ = NULL;
return 0;
}
int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) {
VP8LDecoder* const dec = alph_dec->vp8l_dec_;
assert(dec != NULL);
assert(dec->action_ == READ_DATA);
assert(last_row <= dec->height_);
// Decode (with special row processing).
return (alph_dec->bytes_per_pixel_ == sizeof(uint8_t)) ?
DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_,
last_row, ExtractPalettedAlphaRows) :
DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, last_row,
ExtractAlphaRows);
}
//------------------------------------------------------------------------------

View File

@ -57,7 +57,8 @@ typedef struct {
HTreeGroup *htree_groups_;
} VP8LMetadata;
typedef struct {
typedef struct VP8LDecoder VP8LDecoder;
struct VP8LDecoder {
VP8StatusCode status_;
VP8LDecodeState action_;
VP8LDecodeState state_;
@ -88,18 +89,27 @@ typedef struct {
uint8_t *rescaler_memory; // Working memory for rescaling work.
WebPRescaler *rescaler; // Common rescaler for all channels.
} VP8LDecoder;
};
//------------------------------------------------------------------------------
// internal functions. Not public.
struct ALPHDecoder; // Defined in dec/alphai.h.
// in vp8l.c
// 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,
size_t data_size, uint8_t* const output);
// Decodes image header for alpha data stored using lossless compression.
// Returns false in case of error.
int VP8LDecodeAlphaHeader(struct ALPHDecoder* const alph_dec,
const uint8_t* const data, size_t data_size,
uint8_t* const output);
// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are
// already decoded in previous call(s), it will resume decoding from where it
// was paused.
// Returns false in case of bitstream error.
int VP8LDecodeAlphaImageStream(struct ALPHDecoder* const alph_dec,
int last_row);
// Allocates and initialize a new lossless decoder instance.
VP8LDecoder* VP8LNew(void);