[FFmpeg-devel] [PATCH] libavcodec/pngdec: support 'previous' dispose operation for APNG.

Benoit Fouet benoit.fouet at free.fr
Mon Dec 1 11:41:41 CET 2014


---
Tested against all the materials I have at hand.
There is an artifact showing for https://raw.githubusercontent.com/maxcom/lorsource/master/src/test/resources/images/i_want_to_be_a_hero__apng_animated__by_tamalesyatole-d5ht8eu.png
which I don't really understand, as it seems the individual frames are correct
for our decoder, but the disposal that's done for other decoders (tested
firefox and chrome) is not the same for the end of the cape.
---
 libavcodec/pngdec.c | 93 ++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 71 insertions(+), 22 deletions(-)

diff --git a/libavcodec/pngdec.c b/libavcodec/pngdec.c
index 9e52d0b..2ca3dee 100644
--- a/libavcodec/pngdec.c
+++ b/libavcodec/pngdec.c
@@ -23,6 +23,7 @@
 
 #include "libavutil/bprint.h"
 #include "libavutil/imgutils.h"
+#include "libavutil/thread.h"
 #include "avcodec.h"
 #include "bytestream.h"
 #include "internal.h"
@@ -38,9 +39,16 @@ typedef struct PNGDecContext {
     AVCodecContext *avctx;
 
     GetByteContext gb;
+    ThreadFrame previous_picture;
     ThreadFrame last_picture;
     ThreadFrame picture;
 
+#if CONFIG_APNG_DECODER
+    AVMutex mutex;
+    int frame_id;
+    int *pframe_id;
+#endif
+
     int state;
     int width, height;
     int cur_w, cur_h;
@@ -827,14 +835,18 @@ static int decode_fctl_chunk(AVCodecContext *avctx, PNGDecContext *s,
             return AVERROR_INVALIDDATA;
 
     /* always (re)start with a clean frame */
-    if (sequence_number == 0)
+    if (sequence_number == 0) {
         s->dispose_op = APNG_DISPOSE_OP_BACKGROUND;
-
-    if (s->dispose_op == APNG_DISPOSE_OP_PREVIOUS) {
-        av_log(avctx, AV_LOG_ERROR,
-               "Dispose operation 'previous' is not yet implemented, using 'none'.\n");
+        ff_mutex_lock(&s->mutex);
+        *s->pframe_id = 0;
+        ff_mutex_unlock(&s->mutex);
+    } else if (*s->pframe_id == 1 && s->dispose_op == APNG_DISPOSE_OP_PREVIOUS)
+        /* previous for the second frame is the first frame */
         s->dispose_op = APNG_DISPOSE_OP_NONE;
-    }
+
+    ff_mutex_lock(&s->mutex);
+    (*s->pframe_id)++;
+    ff_mutex_unlock(&s->mutex);
 
     return 0;
 }
@@ -864,8 +876,9 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
 {
     int i, j;
     uint8_t *pd      = p->data[0];
-    /* TODO make pd_last point to the one before for APNG_DISPOSE_OP_PREVIOUS */
     uint8_t *pd_last = s->last_picture.f->data[0];
+    uint8_t *pd_last_region = s->dispose_op == APNG_DISPOSE_OP_PREVIOUS ?
+                                s->previous_picture.f->data[0] : s->last_picture.f->data[0];
     int ls = FFMIN(av_image_get_linesize(p->format, s->width, 0), s->width * s->bpp);
 
     if (s->blend_op == APNG_BLEND_OP_OVER &&
@@ -876,6 +889,9 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
     }
 
     ff_thread_await_progress(&s->last_picture, INT_MAX, 0);
+    if (s->dispose_op == APNG_DISPOSE_OP_PREVIOUS)
+        ff_thread_await_progress(&s->previous_picture, INT_MAX, 0);
+
     for (j = 0; j < s->y_offset; j++) {
         for (i = 0; i < ls; i++)
             pd[i] = pd_last[i];
@@ -886,6 +902,7 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
     if (s->dispose_op != APNG_DISPOSE_OP_BACKGROUND && s->blend_op == APNG_BLEND_OP_OVER) {
         uint8_t ri, gi, bi, ai;
 
+        pd_last_region += s->y_offset * s->image_linesize;
         if (avctx->pix_fmt == AV_PIX_FMT_RGBA) {
             ri = 0;
             gi = 1;
@@ -907,17 +924,17 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
                 /* output = alpha * foreground + (1-alpha) * background */
                 switch (alpha) {
                 case 0:
-                    pd[i+ri] = pd_last[i+ri];
-                    pd[i+gi] = pd_last[i+gi];
-                    pd[i+bi] = pd_last[i+bi];
+                    pd[i+ri] = pd_last_region[i+ri];
+                    pd[i+gi] = pd_last_region[i+gi];
+                    pd[i+bi] = pd_last_region[i+bi];
                     pd[i+ai] = 0xff;
                     break;
                 case 255:
                     break;
                 default:
-                    pd[i+ri] = FAST_DIV255(alpha * pd[i+ri] + (255 - alpha) * pd_last[i+ri]);
-                    pd[i+gi] = FAST_DIV255(alpha * pd[i+gi] + (255 - alpha) * pd_last[i+gi]);
-                    pd[i+bi] = FAST_DIV255(alpha * pd[i+bi] + (255 - alpha) * pd_last[i+bi]);
+                    pd[i+ri] = FAST_DIV255(alpha * pd[i+ri] + (255 - alpha) * pd_last_region[i+ri]);
+                    pd[i+gi] = FAST_DIV255(alpha * pd[i+gi] + (255 - alpha) * pd_last_region[i+gi]);
+                    pd[i+bi] = FAST_DIV255(alpha * pd[i+bi] + (255 - alpha) * pd_last_region[i+bi]);
                     pd[i+ai] = 0xff;
                     break;
                 }
@@ -926,6 +943,7 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
                 pd[i] = pd_last[i];
             pd      += s->image_linesize;
             pd_last += s->image_linesize;
+            pd_last_region += s->image_linesize;
         }
     } else {
         for (j = s->y_offset; j < s->y_offset + s->cur_h; j++) {
@@ -955,6 +973,7 @@ static int decode_frame_common(AVCodecContext *avctx, PNGDecContext *s,
     uint32_t tag, length;
     int decode_next_dat = 0;
     int ret = AVERROR_INVALIDDATA;
+    AVFrame *ref;
 
     for (;;) {
         length = bytestream2_get_bytes_left(&s->gb);
@@ -1053,11 +1072,13 @@ exit_loop:
         handle_small_bpp(s, p);
 
     /* handle p-frames only if a predecessor frame is available */
-    if (s->last_picture.f->data[0]) {
+    ref = s->dispose_op == APNG_DISPOSE_OP_PREVIOUS ?
+             s->previous_picture.f : s->last_picture.f;
+    if (ref->data[0]) {
         if (   !(avpkt->flags & AV_PKT_FLAG_KEY) && avctx->codec_tag != AV_RL32("MPNG")
-            && s->last_picture.f->width == p->width
-            && s->last_picture.f->height== p->height
-            && s->last_picture.f->format== p->format
+            && ref->width == p->width
+            && ref->height== p->height
+            && ref->format== p->format
          ) {
             if (CONFIG_PNG_DECODER && avctx->codec_id != AV_CODEC_ID_APNG)
                 handle_p_frame_png(s, p);
@@ -1141,9 +1162,13 @@ static int decode_frame_apng(AVCodecContext *avctx,
     PNGDecContext *const s = avctx->priv_data;
     int ret;
     AVFrame *p;
+    ThreadFrame tmp;
 
-    ff_thread_release_buffer(avctx, &s->last_picture);
-    FFSWAP(ThreadFrame, s->picture, s->last_picture);
+    ff_thread_release_buffer(avctx, &s->previous_picture);
+    tmp = s->previous_picture;
+    s->previous_picture = s->last_picture;
+    s->last_picture = s->picture;
+    s->picture = tmp;
     p = s->picture.f;
 
     if (!(s->state & PNG_IHDR)) {
@@ -1193,13 +1218,26 @@ static int update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
 {
     PNGDecContext *psrc = src->priv_data;
     PNGDecContext *pdst = dst->priv_data;
+    int ret;
 
     if (dst == src)
         return 0;
 
+#if CONFIG_APNG_DECODER
+    pdst->pframe_id = psrc->pframe_id;
+    ff_mutex_destroy(&pdst->mutex);
+    pdst->mutex = psrc->mutex;
+#endif
+
     ff_thread_release_buffer(dst, &pdst->picture);
-    if (psrc->picture.f->data[0])
-        return ff_thread_ref_frame(&pdst->picture, &psrc->picture);
+    if (psrc->picture.f->data[0] &&
+        (ret = ff_thread_ref_frame(&pdst->picture, &psrc->picture)) < 0)
+        return ret;
+    if (CONFIG_APNG_DECODER && dst->codec_id == AV_CODEC_ID_APNG) {
+        ff_thread_release_buffer(dst, &pdst->last_picture);
+        if (psrc->last_picture.f->data[0])
+            return ff_thread_ref_frame(&pdst->last_picture, &psrc->last_picture);
+    }
 
     return 0;
 }
@@ -1209,9 +1247,10 @@ static av_cold int png_dec_init(AVCodecContext *avctx)
     PNGDecContext *s = avctx->priv_data;
 
     s->avctx = avctx;
+    s->previous_picture.f = av_frame_alloc();
     s->last_picture.f = av_frame_alloc();
     s->picture.f = av_frame_alloc();
-    if (!s->last_picture.f || !s->picture.f)
+    if (!s->previous_picture.f || !s->last_picture.f || !s->picture.f)
         return AVERROR(ENOMEM);
 
     if (!avctx->internal->is_copy) {
@@ -1219,6 +1258,11 @@ static av_cold int png_dec_init(AVCodecContext *avctx)
         ff_pngdsp_init(&s->dsp);
     }
 
+#if CONFIG_APNG_DECODER
+    s->pframe_id = &s->frame_id;
+    ff_mutex_init(&s->mutex, NULL);
+#endif
+
     return 0;
 }
 
@@ -1226,6 +1270,8 @@ static av_cold int png_dec_end(AVCodecContext *avctx)
 {
     PNGDecContext *s = avctx->priv_data;
 
+    ff_thread_release_buffer(avctx, &s->previous_picture);
+    av_frame_free(&s->previous_picture.f);
     ff_thread_release_buffer(avctx, &s->last_picture);
     av_frame_free(&s->last_picture.f);
     ff_thread_release_buffer(avctx, &s->picture);
@@ -1236,6 +1282,9 @@ static av_cold int png_dec_end(AVCodecContext *avctx)
     s->last_row_size = 0;
     av_freep(&s->tmp_row);
     s->tmp_row_size = 0;
+#if CONFIG_APNG_DECODER
+    ff_mutex_destroy(&s->mutex);
+#endif
 
     return 0;
 }
-- 
2.2.0



More information about the ffmpeg-devel mailing list