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;
int channels;
int bits_per_pixel;
int bpp;
+ int has_trns;
- int frame_id;
uint8_t *image_buf;
int image_linesize;
uint32_t palette[256];
return AVERROR_INVALIDDATA;
}
+ if (s->state & PNG_IHDR) {
+ av_log(avctx, AV_LOG_ERROR, "Multiple IHDR\n");
+ return AVERROR_INVALIDDATA;
+ }
+
s->width = s->cur_w = bytestream2_get_be32(&s->gb);
s->height = s->cur_h = bytestream2_get_be32(&s->gb);
if (av_image_check_size(s->width, s->height, 0, avctx)) {
- s->width = s->height = 0;
+ s->cur_w = s->cur_h = s->width = s->height = 0;
av_log(avctx, AV_LOG_ERROR, "Invalid image size\n");
return AVERROR_INVALIDDATA;
}
}
bytestream2_skip(&s->gb, 4); /* crc */
+ s->has_trns = 1;
+
return 0;
}
uint32_t length)
{
uint32_t sequence_number;
+ int cur_w, cur_h, x_offset, y_offset, dispose_op, blend_op;
if (length != 26)
return AVERROR_INVALIDDATA;
+ if (!(s->state & PNG_IHDR)) {
+ av_log(avctx, AV_LOG_ERROR, "fctl before IHDR\n");
+ 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);
- s->x_offset = bytestream2_get_be32(&s->gb);
- s->y_offset = bytestream2_get_be32(&s->gb);
+ cur_w = bytestream2_get_be32(&s->gb);
+ cur_h = bytestream2_get_be32(&s->gb);
+ x_offset = bytestream2_get_be32(&s->gb);
+ y_offset = bytestream2_get_be32(&s->gb);
bytestream2_skip(&s->gb, 4); /* delay_num (2), delay_den (2) */
- s->dispose_op = bytestream2_get_byte(&s->gb);
- s->blend_op = bytestream2_get_byte(&s->gb);
+ dispose_op = bytestream2_get_byte(&s->gb);
+ blend_op = bytestream2_get_byte(&s->gb);
bytestream2_skip(&s->gb, 4); /* crc */
if (sequence_number == 0 &&
- (s->cur_w != s->width ||
- s->cur_h != s->height ||
- s->x_offset != 0 ||
- s->y_offset != 0) ||
- s->cur_w <= 0 || s->cur_h <= 0 ||
- s->x_offset < 0 || s->y_offset < 0 ||
- s->cur_w > s->width - s->x_offset|| s->cur_h > s->height - s->y_offset)
+ (cur_w != s->width ||
+ cur_h != s->height ||
+ x_offset != 0 ||
+ y_offset != 0) ||
+ cur_w <= 0 || cur_h <= 0 ||
+ x_offset < 0 || y_offset < 0 ||
+ cur_w > s->width - x_offset|| cur_h > s->height - 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;
}
+ if (s->dispose_op == APNG_BLEND_OP_OVER && !s->has_trns && (
+ avctx->pix_fmt == AV_PIX_FMT_RGB24 ||
+ avctx->pix_fmt == AV_PIX_FMT_RGB48BE ||
+ avctx->pix_fmt == AV_PIX_FMT_PAL8 ||
+ avctx->pix_fmt == AV_PIX_FMT_GRAY8 ||
+ avctx->pix_fmt == AV_PIX_FMT_GRAY16BE ||
+ avctx->pix_fmt == AV_PIX_FMT_MONOBLACK
+ )) {
+ // APNG_DISPOSE_OP_OVER is the same as APNG_DISPOSE_OP_SOURCE when there is no alpha channel
+ s->dispose_op = APNG_BLEND_OP_SOURCE;
+ }
+
+ s->cur_w = cur_w;
+ s->cur_h = cur_h;
+ s->x_offset = x_offset;
+ s->y_offset = y_offset;
+ s->dispose_op = dispose_op;
+ s->blend_op = blend_op;
+
return 0;
}
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) {
+ avctx->pix_fmt != AV_PIX_FMT_RGBA &&
+ avctx->pix_fmt != AV_PIX_FMT_GRAY8A &&
+ avctx->pix_fmt != AV_PIX_FMT_PAL8) {
avpriv_request_sample(avctx, "Blending with pixel format %s",
av_get_pix_fmt_name(avctx->pix_fmt));
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);
}
-
- 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];
-
- /* 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;
+ } 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];
+
+ // 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
+
+ switch (avctx->pix_fmt) {
+ case AV_PIX_FMT_RGBA:
+ foreground_alpha = foreground[3];
+ background_alpha = background[3];
break;
- case 255:
+
+ case AV_PIX_FMT_GRAY8A:
+ foreground_alpha = foreground[1];
+ background_alpha = background[1];
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;
+
+ case AV_PIX_FMT_PAL8:
+ foreground_alpha = s->palette[foreground[0]] >> 24;
+ background_alpha = s->palette[background[0]] >> 24;
break;
}
+
+ if (foreground_alpha == 0)
+ continue;
+
+ if (foreground_alpha == 255) {
+ memcpy(background, foreground, s->bpp);
+ continue;
+ }
+
+ if (avctx->pix_fmt == AV_PIX_FMT_PAL8) {
+ // TODO: Alpha blending with PAL8 will likely need the entire image converted over to RGBA first
+ avpriv_request_sample(avctx, "Alpha blending palette samples");
+ background[0] = foreground[0];
+ 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;
}
uint32_t tag, length;
int decode_next_dat = 0;
int ret;
- AVFrame *ref;
for (;;) {
length = bytestream2_get_bytes_left(&s->gb);
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);
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->width = psrc->width;
+ pdst->height = psrc->height;
+ pdst->bit_depth = psrc->bit_depth;
+ pdst->color_type = psrc->color_type;
+ pdst->compression_type = psrc->compression_type;
+ pdst->interlace_type = psrc->interlace_type;
+ pdst->filter_type = psrc->filter_type;
+ 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;
+
+ memcpy(pdst->palette, psrc->palette, sizeof(pdst->palette));
+
+ pdst->state |= psrc->state & (PNG_IHDR | PNG_PLTE);
+
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);