avcodec/webp: add support for Animated WebP decoding

Fixes: 4907

Adds Animated WebP feature according to spec:
https://developers.google.com/speed/webp/docs/riff_container#animation

Original work by Josef Zlomek <josef@pex.com>
and Thilo Borgmann <thilo.borgmann@mail.de>

Signed-off-by: Ramiro Polla <ramiro.polla@gmail.com>
This commit is contained in:
Ramiro Polla
2026-05-19 11:36:10 +02:00
parent 2ca634f5db
commit a3d8ba6613
5 changed files with 633 additions and 2 deletions
+1
View File
@@ -12,6 +12,7 @@ version <next>:
- SMPTE 2094-50 metadata support and passthrough
- ProRes RAW VideoToolbox hwaccel
- APV Vulkan hwaccel
- Animated WebP decoder
version 8.1:
Vendored
+1
View File
@@ -3366,6 +3366,7 @@ vp9_decoder_select="videodsp vp9_parser cbs_vp9 vp9_superframe_split_bsf"
vvc_decoder_select="cabac cbs_h266 golomb videodsp vvc_sei"
wcmv_decoder_select="inflate_wrapper"
webp_decoder_select="vp8_decoder"
webp_anim_decoder_select="vp8_decoder"
wmalossless_decoder_select="llauddsp"
wmapro_decoder_select="sinewin wma_freqs"
wmav1_decoder_select="sinewin wma_freqs"
+1
View File
@@ -846,6 +846,7 @@ OBJS-$(CONFIG_WBMP_DECODER) += wbmpdec.o
OBJS-$(CONFIG_WBMP_ENCODER) += wbmpenc.o
OBJS-$(CONFIG_WCMV_DECODER) += wcmv.o
OBJS-$(CONFIG_WEBP_DECODER) += webp.o
OBJS-$(CONFIG_WEBP_ANIM_DECODER) += webp.o
OBJS-$(CONFIG_WEBVTT_DECODER) += webvttdec.o ass.o
OBJS-$(CONFIG_WEBVTT_ENCODER) += webvttenc.o ass_split.o
OBJS-$(CONFIG_WMALOSSLESS_DECODER) += wmalosslessdec.o wma_common.o
+1
View File
@@ -396,6 +396,7 @@ extern const FFCodec ff_vqc_decoder;
extern const FFCodec ff_vvc_decoder;
extern const FFCodec ff_wbmp_decoder;
extern const FFCodec ff_wbmp_encoder;
extern const FFCodec ff_webp_anim_decoder;
extern const FFCodec ff_webp_decoder;
extern const FFCodec ff_wcmv_decoder;
extern const FFCodec ff_wrapped_avframe_encoder;
+629 -2
View File
@@ -2,6 +2,7 @@
* WebP (.webp) image decoder
* Copyright (c) 2013 Aneesh Dogra <aneesh@sugarlabs.org>
* Copyright (c) 2013 Justin Ruggles <justin.ruggles@gmail.com>
* Copyright (c) 2020 Pexeso Inc.
*
* This file is part of FFmpeg.
*
@@ -38,10 +39,13 @@
* @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
* XMP metadata
*
* Unimplemented:
* - Animation
* @author Josef Zlomek, Pexeso Inc. <josef@pex.com>
* Animation
*/
#include "config_components.h"
#include "libavutil/colorspace.h"
#include "libavutil/imgutils.h"
#include "libavutil/mem.h"
@@ -1593,3 +1597,626 @@ const FFCodec ff_webp_decoder = {
.caps_internal = FF_CODEC_CAP_ICC_PROFILES |
FF_CODEC_CAP_USES_PROGRESSFRAMES,
};
#if CONFIG_WEBP_ANIM_DECODER
#define ANMF_FLAG_DISPOSE (1 << 0)
#define ANMF_FLAG_NO_BLEND (1 << 1)
typedef struct AnimatedWebPContext {
WebPContext w;
AVFrame *canvas; /* AVFrame for canvas */
AVFrame *subframe; /* AVFrame for subframe */
int canvas_width; /* canvas width */
int canvas_height; /* canvas height */
int anmf_flags; /* frame flags from ANMF chunk */
int pos_x; /* frame position X */
int pos_y; /* frame position Y */
int duration; /* frame duration */
int prev_anmf_flags; /* previous frame flags from ANMF chunk */
int prev_width; /* previous frame width */
int prev_height; /* previous frame height */
int prev_pos_x; /* previous frame position X */
int prev_pos_y; /* previous frame position Y */
uint8_t background_argb[4]; /* background color in ARGB format */
uint8_t background_yuva[4]; /* background color in YUVA format */
} AnimatedWebPContext;
/*
* Blend src (foreground) into dst (background), in ARGB format.
* pos_x, pos_y is the position in dst.
*/
static void blend_alpha_argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y)
{
for (int y = 0; y < src->height; y++) {
const uint8_t *src_argb = src->data[0] + y * src->linesize[0];
uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * sizeof(uint32_t);
for (int x = 0; x < src->width; x++) {
int src_alpha = src_argb[0];
int dst_alpha = dst_argb[0];
if (src_alpha == 255) {
memcpy(dst_argb, src_argb, sizeof(uint32_t));
} else if (src_alpha == 0) {
// no-op
} else {
int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8;
int blend_alpha = src_alpha + tmp_alpha;
int scale = (1UL << 24) / blend_alpha;
dst_argb[0] = blend_alpha;
dst_argb[1] = (((uint32_t) (src_argb[1] * src_alpha + dst_argb[1] * tmp_alpha)) * scale) >> 24;
dst_argb[2] = (((uint32_t) (src_argb[2] * src_alpha + dst_argb[2] * tmp_alpha)) * scale) >> 24;
dst_argb[3] = (((uint32_t) (src_argb[3] * src_alpha + dst_argb[3] * tmp_alpha)) * scale) >> 24;
}
src_argb += sizeof(uint32_t);
dst_argb += sizeof(uint32_t);
}
}
}
/*
* Blend src (foreground) into dst (background), in YUVA format.
* pos_x, pos_y is the position in dst.
*/
static void blend_alpha_yuva(AVFrame *dst, AVFrame *src, int pos_x, int pos_y)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(src->format);
int plane_y = desc->comp[0].plane;
int plane_u = desc->comp[1].plane;
int plane_v = desc->comp[2].plane;
int plane_a = desc->comp[3].plane;
// blend U & V planes first, because the later step may modify alpha plane
for (int y = 0; y < AV_CEIL_RSHIFT(src->height, 1); y++) {
int tile_h = FFMIN(src->height - y * 2, 2);
const uint8_t *src_u = src->data[plane_u] + y * src->linesize[plane_u];
const uint8_t *src_v = src->data[plane_v] + y * src->linesize[plane_v];
uint8_t *dst_u = dst->data[plane_u] + ((pos_y >> 1) + y) * dst->linesize[plane_u] + (pos_x >> 1);
uint8_t *dst_v = dst->data[plane_v] + ((pos_y >> 1) + y) * dst->linesize[plane_v] + (pos_x >> 1);
for (int x = 0; x < AV_CEIL_RSHIFT(src->width, 1); x++) {
int tile_w = FFMIN(src->width - x * 2, 2);
// calculate the average alpha of the tile
int src_alpha = 0;
int dst_alpha = 0;
for (int yy = 0; yy < tile_h; yy++) {
for (int xx = 0; xx < tile_w; xx++) {
src_alpha += src->data[plane_a][(y * 2 + yy) * src->linesize[plane_a] +
(x * 2 + xx)];
dst_alpha += dst->data[plane_a][(((pos_y >> 1) + y) * 2 + yy) * dst->linesize[plane_a] +
(((pos_x >> 1) + x) * 2 + xx)];
}
}
int shift = (tile_h == 2) + (tile_w == 2);
src_alpha = AV_CEIL_RSHIFT(src_alpha, shift);
dst_alpha = AV_CEIL_RSHIFT(dst_alpha, shift);
if (src_alpha == 255) {
*dst_u = *src_u;
*dst_v = *src_v;
} else if (src_alpha == 0) {
// no-op
} else {
int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8;
int blend_alpha = src_alpha + tmp_alpha;
int scale = (1UL << 24) / blend_alpha;
*dst_u = (((uint32_t) (*src_u * src_alpha + *dst_u * tmp_alpha)) * scale) >> 24;
*dst_v = (((uint32_t) (*src_v * src_alpha + *dst_v * tmp_alpha)) * scale) >> 24;
}
src_u += 1;
src_v += 1;
dst_u += 1;
dst_v += 1;
}
}
// blend Y & A planes
for (int y = 0; y < src->height; y++) {
const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y];
const uint8_t *src_a = src->data[plane_a] + y * src->linesize[plane_a];
uint8_t *dst_y = dst->data[plane_y] + (pos_y + y) * dst->linesize[plane_y] + pos_x;
uint8_t *dst_a = dst->data[plane_a] + (pos_y + y) * dst->linesize[plane_a] + pos_x;
for (int x = 0; x < src->width; x++) {
int src_alpha = *src_a;
int dst_alpha = *dst_a;
if (src_alpha == 255) {
*dst_y = *src_y;
*dst_a = 255;
} else if (src_alpha == 0) {
// no-op
} else {
int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8;
int blend_alpha = src_alpha + tmp_alpha;
int scale = (1UL << 24) / blend_alpha;
*dst_y = (((uint32_t) (*src_y * src_alpha + *dst_y * tmp_alpha)) * scale) >> 24;
*dst_a = blend_alpha;
}
src_y += 1;
src_a += 1;
dst_y += 1;
dst_a += 1;
}
}
}
static av_always_inline void webp_yuva2argb(uint8_t *out, int Y, int U, int V, int A)
{
// variables used in macros
const uint8_t *cm = ff_crop_tab + MAX_NEG_CROP;
uint8_t r, g, b;
int y, cb, cr;
int r_add, g_add, b_add;
YUV_TO_RGB1_CCIR(U, V);
YUV_TO_RGB2_CCIR(r, g, b, Y);
out[0] = av_clip_uint8(A);
out[1] = av_clip_uint8(r);
out[2] = av_clip_uint8(g);
out[3] = av_clip_uint8(b);
}
static void copy_yuva2argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y)
{
const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format);
int alpha = src_desc->nb_components > 3;
int plane_y = src_desc->comp[0].plane;
int plane_u = src_desc->comp[1].plane;
int plane_v = src_desc->comp[2].plane;
int plane_a = src_desc->comp[3].plane;
for (int y = 0; y < src->height; y++) {
const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y];
const uint8_t *src_u = src->data[plane_u] + (y >> 1) * src->linesize[plane_u];
const uint8_t *src_v = src->data[plane_v] + (y >> 1) * src->linesize[plane_v];
const uint8_t *src_a = NULL;
uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * 4;
if (alpha)
src_a = src->data[plane_a] + y * src->linesize[plane_a];
for (int x = 0; x < src->width; x++) {
webp_yuva2argb(dst_argb, *src_y, *src_u, *src_v, (alpha ? *src_a : 255));
src_y += 1;
src_u += x & 1;
src_v += x & 1;
if (alpha)
src_a += 1;
dst_argb += sizeof(uint32_t);
}
}
}
static void blend_yuva2argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y)
{
const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format);
int plane_y = src_desc->comp[0].plane;
int plane_u = src_desc->comp[1].plane;
int plane_v = src_desc->comp[2].plane;
int plane_a = src_desc->comp[3].plane;
for (int y = 0; y < src->height; y++) {
const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y];
const uint8_t *src_u = src->data[plane_u] + (y >> 1) * src->linesize[plane_u];
const uint8_t *src_v = src->data[plane_v] + (y >> 1) * src->linesize[plane_v];
const uint8_t *src_a = src->data[plane_a] + y * src->linesize[plane_a];
uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * 4;
for (int x = 0; x < src->width; x++) {
int src_alpha = *src_a;
int dst_alpha = dst_argb[0];
if (src_alpha == 255) {
webp_yuva2argb(dst_argb, *src_y, *src_u, *src_v, src_alpha);
} else if (src_alpha == 0) {
// no-op
} else {
uint8_t tmp[4];
int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8;
int blend_alpha = src_alpha + tmp_alpha;
int scale = (1UL << 24) / blend_alpha;
webp_yuva2argb(tmp, *src_y, *src_u, *src_v, src_alpha);
dst_argb[0] = blend_alpha;
dst_argb[1] = (((uint32_t) (tmp[1] * src_alpha + dst_argb[1] * tmp_alpha)) * scale) >> 24;
dst_argb[2] = (((uint32_t) (tmp[2] * src_alpha + dst_argb[2] * tmp_alpha)) * scale) >> 24;
dst_argb[3] = (((uint32_t) (tmp[3] * src_alpha + dst_argb[3] * tmp_alpha)) * scale) >> 24;
}
src_y += 1;
src_u += x & 1;
src_v += x & 1;
src_a += 1;
dst_argb += sizeof(uint32_t);
}
}
}
static int blend_subframe_into_canvas(AnimatedWebPContext *s)
{
AVFrame *canvas = s->canvas;
AVFrame *frame = s->subframe;
if ((s->anmf_flags & ANMF_FLAG_NO_BLEND)
|| frame->format == AV_PIX_FMT_YUV420P) {
// do not blend, overwrite
if (canvas->format == AV_PIX_FMT_ARGB) {
if (canvas->format == frame->format) {
const uint8_t *src = frame->data[0];
uint8_t *dst = canvas->data[0] +
s->pos_y * canvas->linesize[0] +
s->pos_x * sizeof(uint32_t);
for (int y = 0; y < s->w.height; y++) {
memcpy(dst, src, s->w.width * sizeof(uint32_t));
src += frame->linesize[0];
dst += canvas->linesize[0];
}
} else {
copy_yuva2argb(canvas, frame, s->pos_x, s->pos_y);
}
} else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ {
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
for (int comp = 0; comp < desc->nb_components; comp++) {
int plane = desc->comp[comp].plane;
int shift = (comp == 1 || comp == 2) ? 1 : 0;
const uint8_t *src = frame->data[plane];
uint8_t *dst = canvas->data[plane] +
(s->pos_y >> shift) * canvas->linesize[plane] +
(s->pos_x >> shift);
for (int y = 0; y < AV_CEIL_RSHIFT(s->w.height, shift); y++) {
memcpy(dst, src, AV_CEIL_RSHIFT(s->w.width, shift));
src += frame->linesize[plane];
dst += canvas->linesize[plane];
}
}
if (canvas->format == AV_PIX_FMT_YUVA420P && desc->nb_components < 4) {
// frame does not have alpha, set alpha to 255
const AVPixFmtDescriptor *canvas_desc = av_pix_fmt_desc_get(canvas->format);
int plane = canvas_desc->comp[3].plane;
uint8_t *dst = canvas->data[plane] + s->pos_y * canvas->linesize[plane] + s->pos_x;
for (int y = 0; y < s->w.height; y++) {
memset(dst, 255, s->w.width);
dst += canvas->linesize[plane];
}
}
}
} else {
// alpha blending
if (canvas->format == AV_PIX_FMT_ARGB) {
if (canvas->format == frame->format) {
blend_alpha_argb(canvas, frame, s->pos_x, s->pos_y);
} else {
blend_yuva2argb(canvas, frame, s->pos_x, s->pos_y);
}
} else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ {
blend_alpha_yuva(canvas, frame, s->pos_x, s->pos_y);
}
}
return 0;
}
/**
* Fill a rectangle on the canvas with the background color (transparent black
* by default, or the color from the ANIM chunk if provided by the demuxer).
*/
static void fill_canvas_rect(AnimatedWebPContext *s, int pos_x, int pos_y, int width, int height)
{
AVFrame *canvas = s->canvas;
if (canvas->format == AV_PIX_FMT_ARGB) {
uint32_t bg_color = AV_RN32(s->background_argb);
int is_repeatable = (bg_color == ((bg_color & 0xff) * 0x01010101));
for (int y = 0; y < height; y++) {
uint32_t *dst = (uint32_t *) (canvas->data[0] + (pos_y + y) * canvas->linesize[0]) + pos_x;
if (is_repeatable) {
memset(dst, bg_color, width * sizeof(uint32_t));
} else {
for (int x = 0; x < width; x++)
dst[x] = bg_color;
}
}
} else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ {
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format);
for (int comp = 0; comp < desc->nb_components; comp++) {
int shift = (comp == 1 || comp == 2) ? 1 : 0;
int plane = desc->comp[comp].plane;
uint8_t *dst = canvas->data[plane] + (pos_y >> shift) * canvas->linesize[plane] + (pos_x >> shift);
for (int y = 0; y < AV_CEIL_RSHIFT(height, shift); y++) {
memset(dst, s->background_yuva[plane], AV_CEIL_RSHIFT(width, shift));
dst += canvas->linesize[plane];
}
}
}
}
static int allocate_canvas(AnimatedWebPContext *s, int format)
{
s->w.avctx->pix_fmt = format;
int ret = ff_set_dimensions(s->w.avctx, s->canvas_width, s->canvas_height);
if (ret < 0)
return ret;
return ff_reget_buffer(s->w.avctx, s->canvas, 0);
}
static int prepare_canvas(AnimatedWebPContext *s, int key_frame, int format)
{
int ret;
/**
* Clear the canvas on keyframes and frames that overwrite the entire
* canvas.
*/
if (key_frame ||
((s->anmf_flags & ANMF_FLAG_NO_BLEND) &&
(s->pos_x == 0) && (s->pos_x + s->w.width == s->canvas_width) &&
(s->pos_y == 0) && (s->pos_y + s->w.height == s->canvas_height)))
av_frame_unref(s->canvas);
if (!s->canvas->buf[0]) {
/* Allocate new canvas frame */
ret = allocate_canvas(s, format);
if (ret < 0)
return ret;
/* ... and initialize it. */
fill_canvas_rect(s, 0, 0, s->canvas->width, s->canvas->height);
} else {
if (format == AV_PIX_FMT_ARGB && s->canvas->format == AV_PIX_FMT_YUVA420P) {
/**
* If we have a lossless frame following a lossy frame, we upgrade
* the canvas to ARGB, but we don't convert the canvas back to YUVA
* if there is a lossy frame following a lossless frame.
*/
AVFrame *yuva_canvas = av_frame_clone(s->canvas);
if (!yuva_canvas)
return AVERROR(ENOMEM);
av_frame_unref(s->canvas);
ret = allocate_canvas(s, AV_PIX_FMT_ARGB);
if (ret < 0) {
av_frame_free(&yuva_canvas);
return ret;
}
copy_yuva2argb(s->canvas, yuva_canvas, 0, 0);
av_frame_free(&yuva_canvas);
} else {
/**
* The decode frame function returns a reference to the canvas,
* therefore we have to ensure it is writable before using it
* for a new frame.
*/
ret = av_frame_make_writable(s->canvas);
if (ret < 0)
return ret;
}
/* Dispose of previous frame if needed. */
if (s->prev_anmf_flags & ANMF_FLAG_DISPOSE)
fill_canvas_rect(s, s->prev_pos_x, s->prev_pos_y, s->prev_width, s->prev_height);
}
return 0;
}
static int webp_anim_decode_frame(AVCodecContext *avctx, AVFrame *p,
int *got_frame, AVPacket *avpkt)
{
AnimatedWebPContext *s = avctx->priv_data;
int key_frame = (avpkt->flags & AV_PKT_FLAG_KEY);
int ret;
GetByteContext gb;
bytestream2_init(&gb, avpkt->data, avpkt->size);
/* Parse ANMF header. */
s->pos_x = bytestream2_get_le24(&gb) * 2;
s->pos_y = bytestream2_get_le24(&gb) * 2;
s->w.width = bytestream2_get_le24(&gb) + 1;
s->w.height = bytestream2_get_le24(&gb) + 1;
s->duration = bytestream2_get_le24(&gb);
s->anmf_flags = bytestream2_get_byte(&gb);
av_log(avctx, AV_LOG_DEBUG,
"ANMF frame pos: %dx%d size: %dx%d duration: %d\n",
s->pos_x, s->pos_y, s->w.width, s->w.height, s->duration);
/* Reset alpha field from previous frame. */
s->w.has_alpha = 0;
/* Parse ANMF subchunks. */
while (bytestream2_get_bytes_left(&gb) > 8) {
uint32_t chunk_type = bytestream2_get_le32(&gb);
uint32_t chunk_size = bytestream2_get_le32(&gb);
if (chunk_size == UINT32_MAX) {
ret = AVERROR_INVALIDDATA;
goto end;
}
chunk_size += chunk_size & 1;
if (bytestream2_get_bytes_left(&gb) < chunk_size) {
/* we seem to be running out of data, but it could also be that the
* bitstream has trailing junk leading to bogus chunk_size. */
break;
}
switch (chunk_type) {
case MKTAG('A', 'L', 'P', 'H'): {
if (chunk_size == 0) {
av_log(avctx, AV_LOG_ERROR, "invalid ALPHA chunk size\n");
ret = AVERROR_INVALIDDATA;
goto end;
}
int alpha_header = bytestream2_get_byte(&gb);
s->w.alpha_data = avpkt->data + bytestream2_tell(&gb);
s->w.alpha_data_size = chunk_size - 1;
bytestream2_skip(&gb, s->w.alpha_data_size);
int filter_m = (alpha_header >> 2) & 0x03;
int compression = alpha_header & 0x03;
if (compression > ALPHA_COMPRESSION_VP8L) {
av_log(avctx, AV_LOG_VERBOSE,
"skipping unsupported ALPHA chunk\n");
} else {
s->w.has_alpha = 1;
s->w.alpha_compression = compression;
s->w.alpha_filter = filter_m;
}
break;
}
case MKTAG('V', 'P', '8', ' '):
if (*got_frame) {
av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra VP8 chunk\n");
bytestream2_skip(&gb, chunk_size);
break;
}
ret = vp8_lossy_decode_frame(avctx, s->subframe, got_frame,
avpkt->data + bytestream2_tell(&gb),
chunk_size);
if (ret < 0)
goto end;
ret = prepare_canvas(s, key_frame, AV_PIX_FMT_YUVA420P);
if (ret < 0)
goto end;
bytestream2_skip(&gb, chunk_size);
break;
case MKTAG('V', 'P', '8', 'L'):
if (*got_frame) {
av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra VP8L chunk\n");
bytestream2_skip(&gb, chunk_size);
break;
}
ret = vp8_lossless_decode_frame(avctx, s->subframe, got_frame,
avpkt->data + bytestream2_tell(&gb),
chunk_size, 0);
if (ret < 0)
goto end;
ret = prepare_canvas(s, key_frame, AV_PIX_FMT_ARGB);
if (ret < 0)
goto end;
#if FF_API_CODEC_PROPS
FF_DISABLE_DEPRECATION_WARNINGS
avctx->properties |= FF_CODEC_PROPERTY_LOSSLESS;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
bytestream2_skip(&gb, chunk_size);
break;
default:
av_log(avctx, AV_LOG_VERBOSE, "skipping unknown chunk: %s\n",
av_fourcc2str(chunk_type));
bytestream2_skip(&gb, chunk_size);
break;
}
}
if (!*got_frame) {
av_log(avctx, AV_LOG_ERROR, "image data not found\n");
ret = AVERROR_INVALIDDATA;
goto end;
}
/* The subframe dimensions may have been modified by update_canvas_size() */
if (s->pos_x + s->w.width > s->canvas_width ||
s->pos_y + s->w.height > s->canvas_height) {
av_log(avctx, AV_LOG_ERROR,
"Frame (%dx%d at pos %dx%d) does not fit into canvas (%dx%d)\n",
s->w.width, s->w.height, s->pos_x, s->pos_y,
s->canvas_width, s->canvas_height);
ret = AVERROR_INVALIDDATA;
goto end;
}
ret = blend_subframe_into_canvas(s);
if (ret < 0)
goto end;
ret = av_frame_ref(p, s->canvas);
if (ret < 0)
goto end;
p->pict_type = key_frame ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P;
p->pts = avpkt->pts;
p->duration = s->duration;
s->prev_anmf_flags = s->anmf_flags;
s->prev_width = s->w.width;
s->prev_height = s->w.height;
s->prev_pos_x = s->pos_x;
s->prev_pos_y = s->pos_y;
ret = avpkt->size;
end:
av_frame_unref(s->subframe);
return ret;
}
static av_cold int webp_anim_decode_init(AVCodecContext *avctx)
{
AnimatedWebPContext *s = avctx->priv_data;
s->w.avctx = avctx;
s->canvas_width = avctx->width;
s->canvas_height = avctx->height;
s->canvas = av_frame_alloc();
if (!s->canvas)
return AVERROR(ENOMEM);
s->subframe = av_frame_alloc();
if (!s->subframe)
return AVERROR(ENOMEM);
/**
* Use background color if it was provided by the demuxer. Otherwise, the
* background color will be 0x00000000 (transparent black).
*/
if (avctx->extradata_size >= 4) {
s->background_argb[0] = avctx->extradata[3];
s->background_argb[1] = avctx->extradata[2];
s->background_argb[2] = avctx->extradata[1];
s->background_argb[3] = avctx->extradata[0];
}
/* Convert background color to YUVA. */
const uint8_t *argb = s->background_argb;
s->background_yuva[0] = RGB_TO_Y_CCIR(argb[1], argb[2], argb[3]);
s->background_yuva[1] = RGB_TO_U_CCIR(argb[1], argb[2], argb[3], 0);
s->background_yuva[2] = RGB_TO_V_CCIR(argb[1], argb[2], argb[3], 0);
s->background_yuva[3] = argb[0];
return webp_decode_init(avctx);
}
static av_cold int webp_anim_decode_close(AVCodecContext *avctx)
{
AnimatedWebPContext *s = avctx->priv_data;
av_frame_free(&s->canvas);
av_frame_free(&s->subframe);
return webp_decode_close(avctx);
}
const FFCodec ff_webp_anim_decoder = {
.p.name = "webp_anim",
CODEC_LONG_NAME("Animated WebP image"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_WEBP_ANIM,
.priv_data_size = sizeof(AnimatedWebPContext),
.init = webp_anim_decode_init,
FF_CODEC_DECODE_CB(webp_anim_decode_frame),
.close = webp_anim_decode_close,
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_SLICE_THREADS,
.caps_internal = FF_CODEC_CAP_USES_PROGRESSFRAMES,
};
#endif /* CONFIG_WEBP_ANIM_DECODER */