libavcodec/pngdec: support 'previous' dispose operation for APNG.
authorBenoit Fouet <benoit.fouet@free.fr>
Wed, 3 Dec 2014 13:16:55 +0000 (14:16 +0100)
committerMichael Niedermayer <michaelni@gmx.at>
Wed, 3 Dec 2014 23:30:38 +0000 (00:30 +0100)
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
libavcodec/pngdec.c

index 229a6d6..b1e77e5 100644 (file)
@@ -38,6 +38,7 @@ typedef struct PNGDecContext {
     AVCodecContext *avctx;
 
     GetByteContext gb;
+    ThreadFrame previous_picture;
     ThreadFrame last_picture;
     ThreadFrame picture;
 
@@ -55,6 +56,7 @@ typedef struct PNGDecContext {
     int bits_per_pixel;
     int bpp;
 
+    int frame_id;
     uint8_t *image_buf;
     int image_linesize;
     uint32_t palette[256];
@@ -827,13 +829,14 @@ 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");
-        s->dispose_op = APNG_DISPOSE_OP_NONE;
+        s->frame_id = 0;
+    } else {
+        s->frame_id++;
+        if (s->frame_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;
     }
 
     return 0;
@@ -864,8 +867,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 +880,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 +893,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 +915,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 +934,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 +964,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 +1063,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 +1153,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 +1209,22 @@ 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;
 
+    pdst->frame_id = psrc->frame_id;
+
     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 +1234,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) {
@@ -1226,6 +1252,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);