From 3584abca1619968c8c029fffeace469bba30866a Mon Sep 17 00:00:00 2001 From: Urvang Joshi Date: Tue, 10 Nov 2015 09:27:59 -0800 Subject: [PATCH] AnimDecoder: option to decode to common color modes. Change-Id: I77ddab9abe3c4b35a9bcfe4c90b3e43d3aef166d --- examples/anim_util.c | 2 +- src/demux/anim_decode.c | 172 ++++++++++++++++++++++++++++++---------- src/webp/demux.h | 63 +++++++++++---- src/webp/mux.h | 6 +- 4 files changed, 180 insertions(+), 63 deletions(-) diff --git a/examples/anim_util.c b/examples/anim_util.c index cb834964..46f0c5d3 100644 --- a/examples/anim_util.c +++ b/examples/anim_util.c @@ -201,7 +201,7 @@ static int ReadAnimatedWebP(const char filename[], memset(image, 0, sizeof(*image)); - dec = WebPAnimDecoderNew(webp_data); + dec = WebPAnimDecoderNew(webp_data, NULL); if (dec == NULL) { fprintf(stderr, "Error parsing image: %s\n", filename); goto End; diff --git a/src/demux/anim_decode.c b/src/demux/anim_decode.c index fa68ffc7..29aa12b7 100644 --- a/src/demux/anim_decode.c +++ b/src/demux/anim_decode.c @@ -23,8 +23,18 @@ #define NUM_CHANNELS 4 +typedef void (*BlendRowFunc)(uint32_t* const, const uint32_t* const, int); +static void BlendPixelRowNonPremult(uint32_t* const src, + const uint32_t* const dst, int num_pixels); +static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst, + int num_pixels); + struct WebPAnimDecoder { WebPDemuxer* demux_; // Demuxer created from given WebP bitstream. + WebPDecoderConfig config_; // Decoder config. + // Note: we use a pointer to a function blending multiple pixels at a time to + // allow possible inlining of per-pixel blending function. + BlendRowFunc blend_func_; // Pointer to the chose blend row function. WebPAnimInfo info_; // Global info about the animation. uint8_t* curr_frame_; // Current canvas (not disposed). uint8_t* prev_frame_disposed_; // Previous canvas (properly disposed). @@ -35,8 +45,45 @@ struct WebPAnimDecoder { // (starting from 1). }; -WebPAnimDecoder* WebPAnimDecoderNewInternal(const WebPData* webp_data, +static void DefaultDecoderOptions(WebPAnimDecoderOptions* const dec_options) { + dec_options->color_mode = MODE_RGBA; +} + +int WebPAnimDecoderOptionsInitInternal(WebPAnimDecoderOptions* dec_options, int abi_version) { + if (dec_options == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) { + return 0; + } + DefaultDecoderOptions(dec_options); + return 1; +} + +static int ApplyDecoderOptions(const WebPAnimDecoderOptions* const dec_options, + WebPAnimDecoder* const dec) { + WEBP_CSP_MODE mode; + WebPDecoderConfig* config = &dec->config_; + assert(dec_options != NULL); + + mode = dec_options->color_mode; + if (mode != MODE_RGBA && mode != MODE_BGRA && + mode != MODE_rgbA && mode != MODE_bgrA) { + return 0; + } + dec->blend_func_ = (mode == MODE_RGBA || mode == MODE_BGRA) + ? &BlendPixelRowNonPremult + : &BlendPixelRowPremult; + WebPInitDecoderConfig(config); + config->output.colorspace = mode; + config->output.is_external_memory = 1; + // Note: config->output.u.RGBA is set at the time of decoding each frame. + return 1; +} + +WebPAnimDecoder* WebPAnimDecoderNewInternal( + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options, + int abi_version) { + WebPAnimDecoderOptions options; WebPAnimDecoder* dec = NULL; if (webp_data == NULL || WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) { @@ -47,6 +94,13 @@ WebPAnimDecoder* WebPAnimDecoderNewInternal(const WebPData* webp_data, dec = (WebPAnimDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); if (dec == NULL) goto Error; + if (dec_options != NULL) { + options = *dec_options; + } else { + DefaultDecoderOptions(&options); + } + if (!ApplyDecoderOptions(&options, dec)) goto Error; + dec->demux_ = WebPDemux(webp_data); if (dec->demux_ == NULL) goto Error; @@ -88,20 +142,20 @@ static int IsFullFrame(int width, int height, int canvas_width, } // Clear the canvas to transparent. -static void ZeroFillCanvas(uint8_t* rgba, uint32_t canvas_width, +static void ZeroFillCanvas(uint8_t* buf, uint32_t canvas_width, uint32_t canvas_height) { - memset(rgba, 0, canvas_width * NUM_CHANNELS * canvas_height); + memset(buf, 0, canvas_width * NUM_CHANNELS * canvas_height); } // Clear given frame rectangle to transparent. -static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset, +static void ZeroFillFrameRect(uint8_t* buf, int buf_stride, int x_offset, int y_offset, int width, int height) { int j; - assert(width * NUM_CHANNELS <= rgba_stride); - rgba += y_offset * rgba_stride + x_offset * NUM_CHANNELS; + assert(width * NUM_CHANNELS <= buf_stride); + buf += y_offset * buf_stride + x_offset * NUM_CHANNELS; for (j = 0; j < height; ++j) { - memset(rgba, 0, width * NUM_CHANNELS); - rgba += rgba_stride; + memset(buf, 0, width * NUM_CHANNELS); + buf += buf_stride; } } @@ -133,8 +187,10 @@ static int IsKeyFrame(const WebPIterator* const curr, // Blend a single channel of 'src' over 'dst', given their alpha channel values. -static uint8_t BlendChannel(uint32_t src, uint8_t src_a, uint32_t dst, - uint8_t dst_a, uint32_t scale, int shift) { +// 'src' and 'dst' are assumed to be NOT pre-multiplied by alpha. +static uint8_t BlendChannelNonPremult(uint32_t src, uint8_t src_a, + uint32_t dst, uint8_t dst_a, + uint32_t scale, int shift) { const uint8_t src_channel = (src >> shift) & 0xff; const uint8_t dst_channel = (dst >> shift) & 0xff; const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a; @@ -143,7 +199,7 @@ static uint8_t BlendChannel(uint32_t src, uint8_t src_a, uint32_t dst, } // Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha. -static uint32_t BlendPixel(uint32_t src, uint32_t dst) { +static uint32_t BlendPixelNonPremult(uint32_t src, uint32_t dst) { const uint8_t src_a = (src >> 24) & 0xff; if (src_a == 0) { @@ -157,11 +213,11 @@ static uint32_t BlendPixel(uint32_t src, uint32_t dst) { const uint32_t scale = (1UL << 24) / blend_a; const uint8_t blend_r = - BlendChannel(src, src_a, dst, dst_factor_a, scale, 0); + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 0); const uint8_t blend_g = - BlendChannel(src, src_a, dst, dst_factor_a, scale, 8); + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 8); const uint8_t blend_b = - BlendChannel(src, src_a, dst, dst_factor_a, scale, 16); + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 16); assert(src_a + dst_factor_a < 256); return (blend_r << 0) | @@ -171,6 +227,46 @@ static uint32_t BlendPixel(uint32_t src, uint32_t dst) { } } +// Blend 'num_pixels' in 'src' over 'dst' assuming they are NOT pre-multiplied +// by alpha. +static void BlendPixelRowNonPremult(uint32_t* const src, + const uint32_t* const dst, int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint8_t src_alpha = (src[i] >> 24) & 0xff; + if (src_alpha != 0xff) { + src[i] = BlendPixelNonPremult(src[i], dst[i]); + } + } +} + +// Individually multiply each channel in 'pix' by 'scale'. +static WEBP_INLINE uint32_t ChannelwiseMultiply(uint32_t pix, uint32_t scale) { + uint32_t mask = 0x00FF00FF; + uint32_t rb = ((pix & mask) * scale) >> 8; + uint32_t ag = ((pix >> 8) & mask) * scale; + return (rb & mask) | (ag & ~mask); +} + +// Blend 'src' over 'dst' assuming they are pre-multiplied by alpha. +static uint32_t BlendPixelPremult(uint32_t src, uint32_t dst) { + const uint8_t src_a = (src >> 24) & 0xff; + return src + ChannelwiseMultiply(dst, 256 - src_a); +} + +// Blend 'num_pixels' in 'src' over 'dst' assuming they are pre-multiplied by +// alpha. +static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst, + int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint8_t src_alpha = (src[i] >> 24) & 0xff; + if (src_alpha != 0xff) { + src[i] = BlendPixelPremult(src[i], dst[i]); + } + } +} + // Returns two ranges ( pairs) at row 'canvas_y', that belong to // 'src' but not 'dst'. A point range is empty if the corresponding width is 0. static void FindBlendRangeAtRow(const WebPIterator* const src, @@ -204,33 +300,21 @@ static void FindBlendRangeAtRow(const WebPIterator* const src, } } -// Blend 'num_pixels' in 'src' over 'dst'. -static void BlendPixelRow(uint32_t* const src, const uint32_t* const dst, - int num_pixels) { - int i; - for (i = 0; i < num_pixels; ++i) { - uint32_t* const src_pixel_ptr = &src[i]; - const uint8_t src_alpha = (*src_pixel_ptr >> 24) & 0xff; - if (src_alpha != 0xff) { - const uint32_t dst_pixel = dst[i]; - *src_pixel_ptr = BlendPixel(*src_pixel_ptr, dst_pixel); - } - } -} - int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, - uint8_t** rgba_ptr, int* timestamp_ptr) { + uint8_t** buf_ptr, int* timestamp_ptr) { WebPIterator iter; uint32_t width; uint32_t height; int is_key_frame; int timestamp; + BlendRowFunc blend_row; - if (dec == NULL || rgba_ptr == NULL || timestamp_ptr == NULL) return 0; + if (dec == NULL || buf_ptr == NULL || timestamp_ptr == NULL) return 0; if (!WebPAnimDecoderHasMoreFrames(dec)) return 0; width = dec->info_.canvas_width; height = dec->info_.canvas_height; + blend_row = dec->blend_func_; // Get compressed frame. if (!WebPDemuxGetFrame(dec->demux_, dec->next_frame_, &iter)) { @@ -249,16 +333,17 @@ int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, // Decode. { - const uint8_t* input = iter.fragment.bytes; - const size_t input_size = iter.fragment.size; - const size_t output_offset = + const uint8_t* in = iter.fragment.bytes; + const size_t in_size = iter.fragment.size; + const size_t out_offset = (iter.y_offset * width + iter.x_offset) * NUM_CHANNELS; - uint8_t* output = dec->curr_frame_ + output_offset; - const int output_stride = NUM_CHANNELS * width; - const size_t output_size = output_stride * iter.height; + WebPDecoderConfig* const config = &dec->config_; + WebPRGBABuffer* const buf = &config->output.u.RGBA; + buf->stride = NUM_CHANNELS * width; + buf->size = buf->stride * iter.height; + buf->rgba = dec->curr_frame_ + out_offset; - if (WebPDecodeRGBAInto(input, input_size, output, output_size, - output_stride) == NULL) { + if (WebPDecode(in, in_size, config) != VP8_STATUS_OK) { goto Error; } } @@ -275,9 +360,8 @@ int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, for (y = 0; y < iter.height; ++y) { const size_t offset = (iter.y_offset + y) * width + iter.x_offset; - BlendPixelRow((uint32_t*)dec->curr_frame_ + offset, - (uint32_t*)dec->prev_frame_disposed_ + offset, - iter.width); + blend_row((uint32_t*)dec->curr_frame_ + offset, + (uint32_t*)dec->prev_frame_disposed_ + offset, iter.width); } } else { int y; @@ -293,12 +377,12 @@ int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, &left2, &width2); if (width1 > 0) { const size_t offset1 = canvas_y * width + left1; - BlendPixelRow((uint32_t*)dec->curr_frame_ + offset1, + blend_row((uint32_t*)dec->curr_frame_ + offset1, (uint32_t*)dec->prev_frame_disposed_ + offset1, width1); } if (width2 > 0) { const size_t offset2 = canvas_y * width + left2; - BlendPixelRow((uint32_t*)dec->curr_frame_ + offset2, + blend_row((uint32_t*)dec->curr_frame_ + offset2, (uint32_t*)dec->prev_frame_disposed_ + offset2, width2); } } @@ -318,7 +402,7 @@ int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, ++dec->next_frame_; // All OK, fill in the values. - *rgba_ptr = dec->curr_frame_; + *buf_ptr = dec->curr_frame_; *timestamp_ptr = timestamp; return 1; diff --git a/src/webp/demux.h b/src/webp/demux.h index b62ab5c2..306090e0 100644 --- a/src/webp/demux.h +++ b/src/webp/demux.h @@ -48,13 +48,14 @@ #ifndef WEBP_WEBP_DEMUX_H_ #define WEBP_WEBP_DEMUX_H_ +#include "./decode.h" // for WEBP_CSP_MODE #include "./mux_types.h" #ifdef __cplusplus extern "C" { #endif -#define WEBP_DEMUX_ABI_VERSION 0x0102 // MAJOR(8b) + MINOR(8b) +#define WEBP_DEMUX_ABI_VERSION 0x0103 // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -64,6 +65,7 @@ typedef struct WebPDemuxer WebPDemuxer; typedef struct WebPIterator WebPIterator; typedef struct WebPChunkIterator WebPChunkIterator; typedef struct WebPAnimInfo WebPAnimInfo; +typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions; //------------------------------------------------------------------------------ @@ -223,15 +225,19 @@ WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); // // Code Example: /* - WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data); + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_options); + // Tune 'dec_options' as needed. + WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options); WebPAnimInfo anim_info; WebPAnimDecoderGetInfo(dec, &anim_info); for (uint32_t i = 0; i < anim_info.loop_count; ++i) { while (WebPAnimDecoderHasMoreFrames(dec)) { - uint8_t* frame_rgba; + uint8_t* buf; int timestamp; - WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp); - // ... (Render 'frame_rgba' based on 'timestamp'). + WebPAnimDecoderGetNext(dec, &buf, ×tamp); + // ... (Render 'buf' based on 'timestamp'). + // ... (Do NOT free 'buf', as it is owned by 'dec'). } WebPAnimDecoderReset(dec); } @@ -240,19 +246,46 @@ WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. +// Global options. +struct WebPAnimDecoderOptions { + // Output colorspace. Only the following modes are supported: + // MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA. + WEBP_CSP_MODE color_mode; + uint32_t padding[8]; // Padding for later use. +}; + // Internal, version-checked, entry point. -WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal(const WebPData*, int); +WEBP_EXTERN(int) WebPAnimDecoderOptionsInitInternal( + WebPAnimDecoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimDecoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimDecoderOptionsInit() must have succeeded before using the +// 'dec_options' object. +static WEBP_INLINE int WebPAnimDecoderOptionsInit( + WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderOptionsInitInternal(dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal( + const WebPData*, const WebPAnimDecoderOptions*, int); // Creates and initializes a WebPAnimDecoder object. // Parameters: // webp_data - (in) WebP bitstream. This should remain unchanged during the // lifetime of the output WebPAnimDecoder object. +// dec_options - (in) decoding options. Can be passed NULL to choose +// reasonable defaults (in particular, color mode MODE_RGBA +// will be picked). // Returns: // A pointer to the newly created WebPAnimDecoder object, or NULL in case of -// parsing/memory error. +// parsing error, invalid option or memory error. static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( - const WebPData* webp_data) { - return WebPAnimDecoderNewInternal(webp_data, WEBP_DEMUX_ABI_VERSION); + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderNewInternal(webp_data, dec_options, + WEBP_DEMUX_ABI_VERSION); } // Global information about the animation.. @@ -274,20 +307,20 @@ struct WebPAnimInfo { WEBP_EXTERN(int) WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, WebPAnimInfo* info); -// Fetch the next frame from 'dec' in RGBA format. This will be a fully -// reconstructed canvas of size 'canvas_width * 4 * canvas_height', and not just -// the frame sub-rectangle. -// The returned 'rgba' buffer is valid only until the next call to +// Fetch the next frame from 'dec' based on options supplied to +// WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size +// 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The +// returned buffer 'buf' is valid only until the next call to // WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). // Parameters: // dec - (in/out) decoder instance from which the next frame is to be fetched. -// rgba - (out) decoded frame in RGBA format. +// buf - (out) decoded frame. // timestamp - (out) timestamp of the frame in milliseconds. // Returns: // False if any of the arguments are NULL, or if there is a parsing or // decoding error, or if there are no more frames. Otherwise, returns true. WEBP_EXTERN(int) WebPAnimDecoderGetNext(WebPAnimDecoder* dec, - uint8_t** rgba, int* timestamp); + uint8_t** buf, int* timestamp); // Check if there are more frames left to decode. // Parameters: diff --git a/src/webp/mux.h b/src/webp/mux.h index bfa0cf0b..2ba2c656 100644 --- a/src/webp/mux.h +++ b/src/webp/mux.h @@ -449,8 +449,8 @@ WEBP_EXTERN(int) WebPAnimEncoderOptionsInitInternal( // Should always be called, to initialize a fresh WebPAnimEncoderOptions // structure before modification. Returns false in case of version mismatch. -// WebPAnimEncoderOptionsInit() must have succeeded before using the 'options' -// object. +// WebPAnimEncoderOptionsInit() must have succeeded before using the +// 'enc_options' object. static WEBP_INLINE int WebPAnimEncoderOptionsInit( WebPAnimEncoderOptions* enc_options) { return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION); @@ -463,7 +463,7 @@ WEBP_EXTERN(WebPAnimEncoder*) WebPAnimEncoderNewInternal( // Creates and initializes a WebPAnimEncoder object. // Parameters: // width/height - (in) canvas width and height of the animation. -// encoder_options - (in) encoding options; can be passed NULL to pick +// enc_options - (in) encoding options; can be passed NULL to pick // reasonable defaults. // Returns: // A pointer to the newly created WebPAnimEncoder object.