diff --git a/README b/README index 8b904180..a90fda0f 100644 --- a/README +++ b/README @@ -405,12 +405,12 @@ The 'idec' object must always be released (even upon an error condition) by calling: WebPDelete(idec). To retrieve partially decoded picture samples, one must use the corresponding -method: WebPIDecGetRGB or WebPIDecGetYUV. +method: WebPIDecGetRGB or WebPIDecGetYUVA. It will return the last displayable pixel row. Lastly, note that decoding can also be performed into a pre-allocated pixel buffer. This buffer must be passed when creating a WebPIDecoder, calling -WebPINewRGB() or WebPINewYUV(). +WebPINewRGB() or WebPINewYUVA(). Please have a look at the src/webp/decode.h header for further details. diff --git a/src/dec/idec.c b/src/dec/idec.c index 7205991c..7df790ce 100644 --- a/src/dec/idec.c +++ b/src/dec/idec.c @@ -567,7 +567,7 @@ WebPIDecoder* WebPIDecode(const uint8_t* data, size_t data_size, } // Create an instance of the incremental decoder idec = WebPINewDecoder(config ? &config->output : NULL); - if (!idec) { + if (idec == NULL) { return NULL; } // Finish initialization @@ -599,7 +599,7 @@ WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE mode, uint8_t* output_buffer, WebPIDecoder* idec; if (mode >= MODE_YUV) return NULL; idec = WebPINewDecoder(NULL); - if (!idec) return NULL; + if (idec == NULL) return NULL; idec->output_.colorspace = mode; idec->output_.is_external_memory = 1; idec->output_.u.RGBA.rgba = output_buffer; @@ -608,12 +608,13 @@ WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE mode, uint8_t* output_buffer, return idec; } -WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride, - uint8_t* u, size_t u_size, int u_stride, - uint8_t* v, size_t v_size, int v_stride) { +WebPIDecoder* WebPINewYUVA(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride) { WebPIDecoder* const idec = WebPINewDecoder(NULL); - if (!idec) return NULL; - idec->output_.colorspace = MODE_YUV; + if (idec == NULL) return NULL; + idec->output_.colorspace = (a == NULL) ? MODE_YUV : MODE_YUVA; idec->output_.is_external_memory = 1; idec->output_.u.YUVA.y = luma; idec->output_.u.YUVA.y_stride = luma_stride; @@ -624,9 +625,21 @@ WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride, idec->output_.u.YUVA.v = v; idec->output_.u.YUVA.v_stride = v_stride; idec->output_.u.YUVA.v_size = v_size; + idec->output_.u.YUVA.a = a; + idec->output_.u.YUVA.a_stride = a_stride; + idec->output_.u.YUVA.a_size = a_size; return idec; } +WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride) { + return WebPINewYUVA(luma, luma_size, luma_stride, + u, u_size, u_stride, + v, v_size, v_stride, + NULL, 0, 0); +} + //------------------------------------------------------------------------------ static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) { @@ -698,15 +711,15 @@ const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, int* left, int* top, int* width, int* height) { const WebPDecBuffer* const src = GetOutputBuffer(idec); - if (left) *left = 0; - if (top) *top = 0; + if (left != NULL) *left = 0; + if (top != NULL) *top = 0; // TODO(skal): later include handling of rotations. if (src) { - if (width) *width = src->width; - if (height) *height = idec->params_.last_y; + if (width != NULL) *width = src->width; + if (height != NULL) *height = idec->params_.last_y; } else { - if (width) *width = 0; - if (height) *height = 0; + if (width != NULL) *width = 0; + if (height != NULL) *height = 0; } return src; } @@ -714,35 +727,38 @@ const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec, int* last_y, int* width, int* height, int* stride) { const WebPDecBuffer* const src = GetOutputBuffer(idec); - if (!src) return NULL; + if (src == NULL) return NULL; if (src->colorspace >= MODE_YUV) { return NULL; } - if (last_y) *last_y = idec->params_.last_y; - if (width) *width = src->width; - if (height) *height = src->height; - if (stride) *stride = src->u.RGBA.stride; + if (last_y != NULL) *last_y = idec->params_.last_y; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.RGBA.stride; return src->u.RGBA.rgba; } -uint8_t* WebPIDecGetYUV(const WebPIDecoder* idec, int* last_y, - uint8_t** u, uint8_t** v, - int* width, int* height, int *stride, int* uv_stride) { +uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, + int* stride, int* uv_stride, int* a_stride) { const WebPDecBuffer* const src = GetOutputBuffer(idec); - if (!src) return NULL; + if (src == NULL) return NULL; if (src->colorspace < MODE_YUV) { return NULL; } - if (last_y) *last_y = idec->params_.last_y; - if (u) *u = src->u.YUVA.u; - if (v) *v = src->u.YUVA.v; - if (width) *width = src->width; - if (height) *height = src->height; - if (stride) *stride = src->u.YUVA.y_stride; - if (uv_stride) *uv_stride = src->u.YUVA.u_stride; + if (last_y != NULL) *last_y = idec->params_.last_y; + if (u != NULL) *u = src->u.YUVA.u; + if (v != NULL) *v = src->u.YUVA.v; + if (a != NULL) *a = src->u.YUVA.a; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.YUVA.y_stride; + if (uv_stride != NULL) *uv_stride = src->u.YUVA.u_stride; + if (a_stride != NULL) *a_stride = src->u.YUVA.a_stride; return src->u.YUVA.y; } diff --git a/src/dec/vp8l.c b/src/dec/vp8l.c index 398faf91..70edbeb6 100644 --- a/src/dec/vp8l.c +++ b/src/dec/vp8l.c @@ -14,6 +14,7 @@ #include #include "./vp8li.h" #include "../dsp/lossless.h" +#include "../dsp/yuv.h" #include "../utils/huffman.h" #include "../utils/utils.h" @@ -404,10 +405,12 @@ static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) { return 1; } +//------------------------------------------------------------------------------ +// Export to ARGB + // We have special "export" function since we need to convert from BGRA -static int Export(VP8LDecoder* const dec, WEBP_CSP_MODE colorspace, +static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace, int rgba_stride, uint8_t* const rgba) { - WebPRescaler* const rescaler = dec->rescaler; const uint32_t* const src = (const uint32_t*)rescaler->dst; const int dst_width = rescaler->dst_width; int num_lines_out = 0; @@ -421,18 +424,19 @@ static int Export(VP8LDecoder* const dec, WEBP_CSP_MODE colorspace, } // Emit scaled rows. -static int EmitRescaledRows(VP8LDecoder* const dec, WEBP_CSP_MODE colorspace, +static int EmitRescaledRows(const VP8LDecoder* const dec, const uint32_t* const data, int in_stride, int mb_h, uint8_t* const out, int out_stride) { + const WEBP_CSP_MODE colorspace = dec->output_->colorspace; const uint8_t* const in = (const uint8_t*)data; int num_lines_in = 0; int num_lines_out = 0; while (num_lines_in < mb_h) { - const uint8_t* row_in = in + num_lines_in * in_stride; + const uint8_t* const row_in = in + num_lines_in * in_stride; uint8_t* const row_out = out + num_lines_out * out_stride; num_lines_in += WebPRescalerImport(dec->rescaler, mb_h - num_lines_in, row_in, in_stride); - num_lines_out += Export(dec, colorspace, out_stride, row_out); + num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out); } return num_lines_out; } @@ -453,6 +457,113 @@ static int EmitRows(WEBP_CSP_MODE colorspace, return mb_h; // Num rows out == num rows in. } +//------------------------------------------------------------------------------ +// Export to YUVA + +static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos, + const WebPDecBuffer* const output) { + const WebPYUVABuffer* const buf = &output->u.YUVA; + // first, the luma plane + { + int i; + uint8_t* const y = buf->y + y_pos * buf->y_stride; + for (i = 0; i < width; ++i) { + const uint32_t p = src[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff); + } + } + + // then U/V planes + { + uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride; + uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride; + const int uv_width = width >> 1; + int i; + for (i = 0; i < uv_width; ++i) { + const uint32_t v0 = src[2 * i + 0]; + const uint32_t v1 = src[2 * i + 1]; + // VP8RGBToU/V expects four accumulated pixels. Hence we need to + // scale r/g/b value by a factor 2. We just shift v0/v1 one bit less. + const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe); + const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe); + const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe); + if (!(y_pos & 1)) { // even lines: store values + u[i] = VP8RGBToU(r, g, b); + v[i] = VP8RGBToV(r, g, b); + } else { // odd lines: average with previous values + const int tmp_u = VP8RGBToU(r, g, b); + const int tmp_v = VP8RGBToV(r, g, b); + // Approximated average-of-four. But it's an acceptable diff. + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } + if (width & 1) { // last pixel + const uint32_t v0 = src[2 * i + 0]; + const int r = (v0 >> 14) & 0x3fc; + const int g = (v0 >> 6) & 0x3fc; + const int b = (v0 << 2) & 0x3fc; + if (!(y_pos & 1)) { // even lines + u[i] = VP8RGBToU(r, g, b); + v[i] = VP8RGBToV(r, g, b); + } else { // odd lines (note: we could just skip this) + const int tmp_u = VP8RGBToU(r, g, b); + const int tmp_v = VP8RGBToV(r, g, b); + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } + } + // Lastly, store alpha if needed. + if (buf->a != NULL) { + int i; + uint8_t* const a = buf->a + y_pos * buf->a_stride; + for (i = 0; i < width; ++i) a[i] = (src[i] >> 24); + } +} + +static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) { + WebPRescaler* const rescaler = dec->rescaler; + const uint32_t* const src = (const uint32_t*)rescaler->dst; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + ConvertToYUVA(src, dst_width, y_pos, dec->output_); + ++y_pos; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, + const uint32_t* const data, + int in_stride, int mb_h) { + const uint8_t* const in = (const uint8_t*)data; + int num_lines_in = 0; + int y_pos = dec->last_out_row_; + while (num_lines_in < mb_h) { + const uint8_t* const row_in = in + num_lines_in * in_stride; + num_lines_in += WebPRescalerImport(dec->rescaler, mb_h - num_lines_in, + row_in, in_stride); + y_pos += ExportYUVA(dec, y_pos); + } + return y_pos; +} + +static int EmitRowsYUVA(const VP8LDecoder* const dec, + const uint32_t* const data, int in_stride, + int mb_w, int num_rows) { + int y_pos = dec->last_out_row_; + const uint8_t* row_in = (const uint8_t*)data; + while (num_rows-- > 0) { + ConvertToYUVA((const uint32_t*)row_in, mb_w, y_pos, dec->output_); + row_in += in_stride; + ++y_pos; + } + return y_pos; +} + //------------------------------------------------------------------------------ // Cropping. @@ -537,19 +648,23 @@ static void ProcessRows(VP8LDecoder* const dec, int row) { 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; - uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride; + const WebPDecBuffer* const output = dec->output_; const int in_stride = io->width * sizeof(*rows_data); - 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, - rgba, buf->stride) : - EmitRows(colorspace, rows_data, in_stride, io->mb_w, io->mb_h, - rgba, buf->stride); - // Update 'last_out_row_'. - dec->last_out_row_ += num_rows_out; + if (output->colorspace < MODE_YUV) { // convert to RGBA + const WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride; + const int num_rows_out = io->use_scaling ? + EmitRescaledRows(dec, rows_data, in_stride, io->mb_h, + rgba, buf->stride) : + EmitRows(output->colorspace, rows_data, in_stride, + io->mb_w, io->mb_h, rgba, buf->stride); + // Update 'last_out_row_'. + dec->last_out_row_ += num_rows_out; + } else { // convert to YUVA + dec->last_out_row_ = io->use_scaling ? + EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) : + EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h); + } assert(dec->last_out_row_ <= output->height); } } @@ -818,6 +933,8 @@ void VP8LClear(VP8LDecoder* const dec) { free(dec->rescaler_memory); dec->rescaler_memory = NULL; + + dec->output_ = NULL; // leave no trace behind } void VP8LDelete(VP8LDecoder* const dec) { @@ -950,7 +1067,7 @@ static int AllocateARGBBuffers(VP8LDecoder* const dec, int final_width) { assert(dec->width_ <= final_width); dec->argb_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(*dec->argb_)); if (dec->argb_ == NULL) { - dec->argb_cache_ = NULL; + dec->argb_cache_ = NULL; // for sanity check dec->status_ = VP8_STATUS_OUT_OF_MEMORY; return 0; } @@ -1052,20 +1169,16 @@ int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { int VP8LDecodeImage(VP8LDecoder* const dec) { VP8Io* io = NULL; WebPDecParams* params = NULL; - WebPDecBuffer* output = NULL; // Sanity checks. if (dec == NULL) return 0; io = dec->io_; + assert(io != NULL); params = (WebPDecParams*)io->opaque; assert(params != NULL); - output = params->output; - // YUV modes are invalid. - if (output->colorspace >= MODE_YUV) { - dec->status_ = VP8_STATUS_INVALID_PARAM; - goto Err; - } + dec->output_ = params->output; + assert(dec->output_ != NULL); // Initialization. if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) { diff --git a/src/dec/vp8li.h b/src/dec/vp8li.h index 542dbb71..ee29eb5f 100644 --- a/src/dec/vp8li.h +++ b/src/dec/vp8li.h @@ -61,6 +61,8 @@ typedef struct { VP8LDecodeState state_; VP8Io *io_; + const WebPDecBuffer *output_; // shortcut to io->opaque->output + uint32_t *argb_; // Internal data: always in BGRA color mode. uint32_t *argb_cache_; // Scratch buffer for temporary BGRA storage. diff --git a/src/webp/decode.h b/src/webp/decode.h index 9df8f89e..43b6c58f 100644 --- a/src/webp/decode.h +++ b/src/webp/decode.h @@ -233,7 +233,7 @@ typedef enum { // // // The above call decodes the current available buffer. // // Part of the image can now be refreshed by calling to -// // WebPIDecGetRGB()/WebPIDecGetYUV() etc. +// // WebPIDecGetRGB()/WebPIDecGetYUVA() etc. // } // WebPIDelete(idec); @@ -260,9 +260,18 @@ WEBP_EXTERN(WebPIDecoder*) WebPINewRGB( // will output the raw luma/chroma samples into a preallocated planes. The luma // plane is specified by its pointer 'luma', its size 'luma_size' and its stride // 'luma_stride'. Similarly, the chroma-u plane is specified by the 'u', -// 'u_size' and 'u_stride' parameters, and the chroma-v plane by 'v', 'v_size' -// and 'v_size'. +// 'u_size' and 'u_stride' parameters, and the chroma-v plane by 'v' +// and 'v_size'. And same for the alpha-plane. The 'a' pointer can be pass +// NULL in case one is not interested in the transparency plane. // Returns NULL if the allocation failed. +WEBP_EXTERN(WebPIDecoder*) WebPINewYUVA( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride); + +// Deprecated version of the above, without the alpha plane. +// Kept for backward compatibility. WEBP_EXTERN(WebPIDecoder*) WebPINewYUV( uint8_t* luma, size_t luma_size, int luma_stride, uint8_t* u, size_t u_size, int u_stride, @@ -296,12 +305,22 @@ WEBP_EXTERN(uint8_t*) WebPIDecGetRGB( const WebPIDecoder* idec, int* last_y, int* width, int* height, int* stride); -// Same as above function to get YUV image. Returns pointer to the luma plane -// or NULL in case of error. -WEBP_EXTERN(uint8_t*) WebPIDecGetYUV( +// Same as above function to get a YUVA image. Returns pointer to the luma +// plane or NULL in case of error. If there is no alpha information +// the alpha pointer '*a' will be returned NULL. +WEBP_EXTERN(uint8_t*) WebPIDecGetYUVA( const WebPIDecoder* idec, int* last_y, - uint8_t** u, uint8_t** v, - int* width, int* height, int* stride, int* uv_stride); + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, int* uv_stride, int* a_stride); + +// Deprecated alpha-less version of WebPIDecGetYUVA(): it will ignore the +// alpha information (if present). Kept for backward compatibility. +static WEBP_INLINE uint8_t* WebPIDecGetYUV( + const WebPIDecoder* idec, int* last_y, uint8_t** u, uint8_t** v, + int* width, int* height, int* stride, int* uv_stride) { + return WebPIDecGetYUVA(idec, last_y, u, v, NULL, width, height, + stride, uv_stride, NULL); +} // Generic call to retrieve information about the displayable area. // If non NULL, the left/right/width/height pointers are filled with the visible