This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit a3d8ba6613dace8ad725808286bf82934e21512b
Author:     Ramiro Polla <[email protected]>
AuthorDate: Wed Apr 22 18:09:26 2026 +0200
Commit:     Ramiro Polla <[email protected]>
CommitDate: Tue May 19 11:36:10 2026 +0200

    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 <[email protected]>
    and Thilo Borgmann <[email protected]>
    
    Signed-off-by: Ramiro Polla <[email protected]>
---
 Changelog              |   1 +
 configure              |   1 +
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   1 +
 libavcodec/webp.c      | 631 ++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 633 insertions(+), 2 deletions(-)

diff --git a/Changelog b/Changelog
index f4171592db..9834f78261 100644
--- a/Changelog
+++ b/Changelog
@@ -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:
diff --git a/configure b/configure
index 514f1723d6..96bbeb372b 100755
--- a/configure
+++ b/configure
@@ -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"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 4935cfc3b3..2312e7b040 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -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
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 5e798c1430..e29a3a4020 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -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;
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index b34ed5e44d..b5af60c673 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -2,6 +2,7 @@
  * WebP (.webp) image decoder
  * Copyright (c) 2013 Aneesh Dogra <[email protected]>
  * Copyright (c) 2013 Justin Ruggles <[email protected]>
+ * 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. <[email protected]>
+ * 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 */

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to