mirror of
https://github.com/xenia-project/FFmpeg.git
synced 2024-11-28 14:01:27 +00:00
avcodec/apng: Dispose previous frame properly
The spec specifies the dispose operation as how the current (i.e., currently being rendered) frame should be disposed when the next frame is blended onto it This is contrary to ffmpeg's current behaviour of interpreting the dispose operation as how the previous (i.e., already rendered) frame should be disposed This patch fixes ffmpeg's behaviour to match those of the spec, which involved a rewrite of the blending function Signed-off-by: Donny Yang <work@kota.moe> Reviewed-by: Paul B Mahol <onemda@gmail.com> Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
This commit is contained in:
parent
6dd5371e34
commit
ed09bb3782
@ -45,8 +45,11 @@ typedef struct PNGDecContext {
|
||||
int state;
|
||||
int width, height;
|
||||
int cur_w, cur_h;
|
||||
int last_w, last_h;
|
||||
int x_offset, y_offset;
|
||||
int last_x_offset, last_y_offset;
|
||||
uint8_t dispose_op, blend_op;
|
||||
uint8_t last_dispose_op;
|
||||
int bit_depth;
|
||||
int color_type;
|
||||
int compression_type;
|
||||
@ -56,7 +59,6 @@ typedef struct PNGDecContext {
|
||||
int bits_per_pixel;
|
||||
int bpp;
|
||||
|
||||
int frame_id;
|
||||
uint8_t *image_buf;
|
||||
int image_linesize;
|
||||
uint32_t palette[256];
|
||||
@ -539,13 +541,18 @@ static int decode_ihdr_chunk(AVCodecContext *avctx, PNGDecContext *s,
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
s->width = s->cur_w = bytestream2_get_be32(&s->gb);
|
||||
s->height = s->cur_h = bytestream2_get_be32(&s->gb);
|
||||
s->width = bytestream2_get_be32(&s->gb);
|
||||
s->height = bytestream2_get_be32(&s->gb);
|
||||
if (av_image_check_size(s->width, s->height, 0, avctx)) {
|
||||
s->width = s->height = 0;
|
||||
av_log(avctx, AV_LOG_ERROR, "Invalid image size\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if (s->cur_w == 0 && s->cur_h == 0) {
|
||||
// Only set cur_w/h if update_thread_context() has not set it
|
||||
s->cur_w = s->width;
|
||||
s->cur_h = s->height;
|
||||
}
|
||||
s->bit_depth = bytestream2_get_byte(&s->gb);
|
||||
s->color_type = bytestream2_get_byte(&s->gb);
|
||||
s->compression_type = bytestream2_get_byte(&s->gb);
|
||||
@ -809,6 +816,12 @@ static int decode_fctl_chunk(AVCodecContext *avctx, PNGDecContext *s,
|
||||
if (length != 26)
|
||||
return AVERROR_INVALIDDATA;
|
||||
|
||||
s->last_w = s->cur_w;
|
||||
s->last_h = s->cur_h;
|
||||
s->last_x_offset = s->x_offset;
|
||||
s->last_y_offset = s->y_offset;
|
||||
s->last_dispose_op = s->dispose_op;
|
||||
|
||||
sequence_number = bytestream2_get_be32(&s->gb);
|
||||
s->cur_w = bytestream2_get_be32(&s->gb);
|
||||
s->cur_h = bytestream2_get_be32(&s->gb);
|
||||
@ -829,15 +842,10 @@ static int decode_fctl_chunk(AVCodecContext *avctx, PNGDecContext *s,
|
||||
s->cur_w > s->width - s->x_offset|| s->cur_h > s->height - s->y_offset)
|
||||
return AVERROR_INVALIDDATA;
|
||||
|
||||
/* always (re)start with a clean frame */
|
||||
if (sequence_number == 0) {
|
||||
if (sequence_number == 0 && s->dispose_op == APNG_DISPOSE_OP_PREVIOUS) {
|
||||
// No previous frame to revert to for the first frame
|
||||
// Spec says to just treat it as a APNG_DISPOSE_OP_BACKGROUND
|
||||
s->dispose_op = APNG_DISPOSE_OP_BACKGROUND;
|
||||
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;
|
||||
@ -866,15 +874,11 @@ static void handle_p_frame_png(PNGDecContext *s, AVFrame *p)
|
||||
static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
|
||||
AVFrame *p)
|
||||
{
|
||||
int i, j;
|
||||
uint8_t *pd = p->data[0];
|
||||
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);
|
||||
size_t x, y;
|
||||
uint8_t *buffer = av_malloc(s->image_linesize * s->height);
|
||||
|
||||
if (ls < 0)
|
||||
return ls;
|
||||
if (!buffer)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if (s->blend_op == APNG_BLEND_OP_OVER &&
|
||||
avctx->pix_fmt != AV_PIX_FMT_RGBA) {
|
||||
@ -883,76 +887,76 @@ static int handle_p_frame_apng(AVCodecContext *avctx, PNGDecContext *s,
|
||||
return AVERROR_PATCHWELCOME;
|
||||
}
|
||||
|
||||
// Copy the previous frame to the buffer
|
||||
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);
|
||||
memcpy(buffer, s->last_picture.f->data[0], s->image_linesize * s->height);
|
||||
|
||||
for (j = 0; j < s->y_offset; j++) {
|
||||
memcpy(pd, pd_last, ls);
|
||||
pd += s->image_linesize;
|
||||
pd_last += s->image_linesize;
|
||||
// Do the disposal operation specified by the last frame on the frame
|
||||
if (s->last_dispose_op == APNG_DISPOSE_OP_BACKGROUND) {
|
||||
for (y = s->last_y_offset; y < s->last_y_offset + s->last_h; ++y)
|
||||
memset(buffer + s->image_linesize * y + s->bpp * s->last_x_offset, 0, s->bpp * s->last_w);
|
||||
} else if (s->last_dispose_op == APNG_DISPOSE_OP_PREVIOUS) {
|
||||
ff_thread_await_progress(&s->previous_picture, INT_MAX, 0);
|
||||
for (y = s->last_y_offset; y < s->last_y_offset + s->last_h; ++y) {
|
||||
size_t row_start = s->image_linesize * y + s->bpp * s->last_x_offset;
|
||||
memcpy(buffer + row_start, s->previous_picture.f->data[0] + row_start, s->bpp * s->last_w);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
bi = 2;
|
||||
ai = 3;
|
||||
// Perform blending
|
||||
if (s->blend_op == APNG_BLEND_OP_SOURCE) {
|
||||
for (y = s->y_offset; y < s->y_offset + s->cur_h; ++y) {
|
||||
size_t row_start = s->image_linesize * y + s->bpp * s->x_offset;
|
||||
memcpy(buffer + row_start, p->data[0] + row_start, s->bpp * s->cur_w);
|
||||
}
|
||||
} else { // APNG_BLEND_OP_OVER
|
||||
for (y = s->y_offset; y < s->y_offset + s->cur_h; ++y) {
|
||||
uint8_t *foreground = p->data[0] + s->image_linesize * y + s->bpp * s->x_offset;
|
||||
uint8_t *background = buffer + s->image_linesize * y + s->bpp * s->x_offset;
|
||||
for (x = s->x_offset; x < s->x_offset + s->cur_w; ++x, foreground += s->bpp, background += s->bpp) {
|
||||
size_t b;
|
||||
uint8_t foreground_alpha, background_alpha, output_alpha;
|
||||
uint8_t output[4];
|
||||
|
||||
for (j = s->y_offset; j < s->y_offset + s->cur_h; j++) {
|
||||
i = s->x_offset * s->bpp;
|
||||
if (i)
|
||||
memcpy(pd, pd_last, i);
|
||||
for (; i < (s->x_offset + s->cur_w) * s->bpp; i += s->bpp) {
|
||||
uint8_t alpha = pd[i+ai];
|
||||
// Since we might be blending alpha onto alpha, we use the following equations:
|
||||
// output_alpha = foreground_alpha + (1 - foreground_alpha) * background_alpha
|
||||
// output = (foreground_alpha * foreground + (1 - foreground_alpha) * background_alpha * background) / output_alpha
|
||||
|
||||
/* output = alpha * foreground + (1-alpha) * background */
|
||||
switch (alpha) {
|
||||
case 0:
|
||||
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_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;
|
||||
switch (avctx->pix_fmt) {
|
||||
case AV_PIX_FMT_RGBA:
|
||||
foreground_alpha = foreground[3];
|
||||
background_alpha = background[3];
|
||||
break;
|
||||
}
|
||||
|
||||
if (foreground_alpha == 0)
|
||||
continue;
|
||||
|
||||
if (foreground_alpha == 255) {
|
||||
memcpy(background, foreground, s->bpp);
|
||||
continue;
|
||||
}
|
||||
|
||||
output_alpha = foreground_alpha + FAST_DIV255((255 - foreground_alpha) * background_alpha);
|
||||
|
||||
for (b = 0; b < s->bpp - 1; ++b) {
|
||||
if (output_alpha == 0) {
|
||||
output[b] = 0;
|
||||
} else if (background_alpha == 255) {
|
||||
output[b] = FAST_DIV255(foreground_alpha * foreground[b] + (255 - foreground_alpha) * background[b]);
|
||||
} else {
|
||||
output[b] = (255 * foreground_alpha * foreground[b] + (255 - foreground_alpha) * background_alpha * background[b]) / (255 * output_alpha);
|
||||
}
|
||||
}
|
||||
output[b] = output_alpha;
|
||||
memcpy(background, output, s->bpp);
|
||||
}
|
||||
if (ls - i)
|
||||
memcpy(pd+i, pd_last+i, ls - 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++) {
|
||||
int end_offset = (s->x_offset + s->cur_w) * s->bpp;
|
||||
int end_len = ls - end_offset;
|
||||
if (s->x_offset)
|
||||
memcpy(pd, pd_last, s->x_offset * s->bpp);
|
||||
if (end_len)
|
||||
memcpy(pd+end_offset, pd_last+end_offset, end_len);
|
||||
pd += s->image_linesize;
|
||||
pd_last += s->image_linesize;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = s->y_offset + s->cur_h; j < s->height; j++) {
|
||||
memcpy(pd, pd_last, ls);
|
||||
pd += s->image_linesize;
|
||||
pd_last += s->image_linesize;
|
||||
}
|
||||
// Copy blended buffer into the frame and free
|
||||
memcpy(p->data[0], buffer, s->image_linesize * s->height);
|
||||
av_free(buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -964,7 +968,6 @@ static int decode_frame_common(AVCodecContext *avctx, PNGDecContext *s,
|
||||
uint32_t tag, length;
|
||||
int decode_next_dat = 0;
|
||||
int ret;
|
||||
AVFrame *ref;
|
||||
|
||||
for (;;) {
|
||||
length = bytestream2_get_bytes_left(&s->gb);
|
||||
@ -1068,13 +1071,11 @@ exit_loop:
|
||||
handle_small_bpp(s, p);
|
||||
|
||||
/* handle p-frames only if a predecessor frame is available */
|
||||
ref = s->dispose_op == APNG_DISPOSE_OP_PREVIOUS ?
|
||||
s->previous_picture.f : s->last_picture.f;
|
||||
if (ref->data[0] && s->last_picture.f->data[0]) {
|
||||
if (s->last_picture.f->data[0]) {
|
||||
if ( !(avpkt->flags & AV_PKT_FLAG_KEY) && avctx->codec_tag != AV_RL32("MPNG")
|
||||
&& ref->width == p->width
|
||||
&& ref->height== p->height
|
||||
&& ref->format== p->format
|
||||
&& s->last_picture.f->width == p->width
|
||||
&& s->last_picture.f->height== p->height
|
||||
&& s->last_picture.f->format== p->format
|
||||
) {
|
||||
if (CONFIG_PNG_DECODER && avctx->codec_id != AV_CODEC_ID_APNG)
|
||||
handle_p_frame_png(s, p);
|
||||
@ -1219,13 +1220,17 @@ static int update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
|
||||
if (dst == src)
|
||||
return 0;
|
||||
|
||||
pdst->frame_id = psrc->frame_id;
|
||||
|
||||
ff_thread_release_buffer(dst, &pdst->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) {
|
||||
pdst->cur_w = psrc->cur_w;
|
||||
pdst->cur_h = psrc->cur_h;
|
||||
pdst->x_offset = psrc->x_offset;
|
||||
pdst->y_offset = psrc->y_offset;
|
||||
pdst->dispose_op = psrc->dispose_op;
|
||||
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user