[FFmpeg-devel] [PATCH v2 2/7] Prepare gif decoder for use in conjunction with gif demuxer.
Paul B Mahol
onemda at gmail.com
Fri Nov 30 13:51:18 CET 2012
On 11/30/12, Vitaliy E Sugrobov <vsugrob at hotmail.com> wrote:
> Add capability of reading multiple frames instead of only first.
> Implement support for different gif frame 'disposal methods'.
> Add option that allows to change background color resulting from
> conversion of gif with transparency to any other format which
> not support it.
> Also bump lavc minor version.
>
> Signed-off-by: Vitaliy E Sugrobov <vsugrob at hotmail.com>
> ---
> libavcodec/gifdec.c | 283
> +++++++++++++++++++++++++++++++++++++++++--------
> libavcodec/version.h | 4 +-
> 2 files changed, 238 insertions(+), 49 deletions(-)
> mode change 100644 => 100755 libavcodec/gifdec.c
>
> diff --git a/libavcodec/gifdec.c b/libavcodec/gifdec.c
> old mode 100644
> new mode 100755
> index 91139e1..6a18910
> --- a/libavcodec/gifdec.c
> +++ b/libavcodec/gifdec.c
> @@ -23,6 +23,7 @@
> //#define DEBUG
>
> #include "libavutil/imgutils.h"
> +#include "libavutil/opt.h"
> #include "avcodec.h"
> #include "bytestream.h"
> #include "lzw.h"
> @@ -35,21 +36,37 @@
> #define GIF_EXTENSION_INTRODUCER 0x21
> #define GIF_IMAGE_SEPARATOR 0x2c
> #define GIF_GCE_EXT_LABEL 0xf9
> +/* This value is intentionally set to "transparent white" color.
> + * It is much better to have white background instead of black
> + * when gif image converted to format which not support transparency.
> + */
> +#define GIF_TRANSPARENT_COLOR 0x00ffffff
>
> typedef struct GifState {
> + const AVClass *class;
> AVFrame picture;
> int screen_width;
> int screen_height;
> + int has_global_palette;
> int bits_per_pixel;
> + uint32_t bg_color;
> int background_color_index;
> int transparent_color_index;
> int color_resolution;
> - uint32_t *image_palette;
> + /* intermediate buffer for storing color indices
> + * obtained from lzw-encoded data stream */
> + uint8_t *idx_line;
>
> /* after the frame is displayed, the disposal method is used */
> + int gce_prev_disposal;
> int gce_disposal;
> - /* delay during which the frame is shown */
> - int gce_delay;
> + /* rectangle describing area that must be disposed */
> + int gce_l, gce_t, gce_w, gce_h;
> + /* depending on disposal method we store either part of the image
> + * drawn on the canvas or background color that
> + * should be used upon disposal */
> + uint32_t * stored_img;
> + int stored_bg_color;
>
> /* LZW compatible decoder */
> const uint8_t *bytestream;
> @@ -57,21 +74,76 @@ typedef struct GifState {
> LZWState *lzw;
>
> /* aux buffers */
> - uint8_t global_palette[256 * 3];
> - uint8_t local_palette[256 * 3];
> + uint32_t global_palette[256];
> + uint32_t local_palette[256];
>
> AVCodecContext *avctx;
> + int is_first_frame;
> + int trans_color; /**< color value that is used instead of
> transparent color */
> } GifState;
>
> static const uint8_t gif87a_sig[6] = "GIF87a";
> static const uint8_t gif89a_sig[6] = "GIF89a";
> +static void gif_read_palette(const uint8_t **buf, uint32_t *pal, int nb)
> +{
> + const uint8_t *pal_end = *buf + nb * 3;
> +
> + for (; *buf < pal_end; *buf += 3, pal++)
> + *pal = (0xffu << 24) | AV_RB24(*buf);
> +}
> +
> +static void gif_fill(AVFrame *picture, uint32_t color)
> +{
> + uint32_t *p = (uint32_t *)picture->data[0];
> + uint32_t *p_end = p + (picture->linesize[0] / sizeof(uint32_t)) *
> picture->height;
> +
> + for (; p < p_end; p++)
> + *p = color;
> +}
> +
> +static void gif_fill_rect(AVFrame *picture, uint32_t color, int l, int t,
> int w, int h)
> +{
> + const int linesize = picture->linesize[0] / sizeof(uint32_t);
> + const uint32_t *py = (uint32_t *)picture->data[0] + t * linesize;
> + const uint32_t *pr, *pb = py + (t + h) * linesize;
> + uint32_t *px;
> +
> + for (; py < pb; py += linesize) {
> + px = (uint32_t *)py + l;
> + pr = px + w;
> +
> + for (; px < pr; px++)
> + *px = color;
> + }
> +}
> +
> +static void gif_copy_img_rect(const uint32_t *src, uint32_t *dst,
> + int linesize, int l, int t, int w, int h)
> +{
> + const int y_start = t * linesize;
> + const uint32_t *src_px, *src_pr,
> + *src_py = src + y_start,
> + *dst_py = dst + y_start;
> + const uint32_t *src_pb = src_py + (t + h) * linesize;
> + uint32_t *dst_px;
> +
> + for (; src_py < src_pb; src_py += linesize, dst_py += linesize) {
> + src_px = src_py + l;
> + dst_px = (uint32_t *)dst_py + l;
> + src_pr = src_px + w;
> +
> + for (; src_px < src_pr; src_px++, dst_px++)
> + *dst_px = *src_px;
> + }
> +}
>
> static int gif_read_image(GifState *s)
> {
> int left, top, width, height, bits_per_pixel, code_size, flags;
> - int is_interleaved, has_local_palette, y, pass, y1, linesize, n, i;
> - uint8_t *ptr, *spal, *palette, *ptr1;
> + int is_interleaved, has_local_palette, y, pass, y1, linesize,
> pal_size;
> + uint32_t *ptr, *pal, *px, *pr, *ptr1;
> int ret;
> + uint8_t *idx;
>
> left = bytestream_get_le16(&s->bytestream);
> top = bytestream_get_le16(&s->bytestream);
> @@ -85,11 +157,31 @@ static int gif_read_image(GifState *s)
> av_dlog(s->avctx, "image x=%d y=%d w=%d h=%d\n", left, top, width,
> height);
>
> if (has_local_palette) {
> - bytestream_get_buffer(&s->bytestream, s->local_palette, 3 * (1 <<
> bits_per_pixel));
> - palette = s->local_palette;
> + pal_size = 1 << bits_per_pixel;
> +
> + if (s->bytestream_end < s->bytestream + pal_size * 3)
> + return AVERROR_INVALIDDATA;
> +
> + gif_read_palette(&s->bytestream, s->local_palette, pal_size);
> + pal = s->local_palette;
> } else {
> - palette = s->global_palette;
> - bits_per_pixel = s->bits_per_pixel;
> + if (!s->has_global_palette) {
> + av_log(s->avctx, AV_LOG_FATAL, "picture doesn't have either
> global or local palette.\n");
> + return AVERROR_INVALIDDATA;
> + }
> +
> + pal = s->global_palette;
> + }
> +
> + if (s->is_first_frame) {
> + if (s->transparent_color_index == -1 && s->has_global_palette) {
> + /* transparency wasn't set before the first frame, fill with
> background color */
> + gif_fill(&s->picture, s->bg_color);
> + } else {
> + /* otherwise fill with transparent color.
> + * this is necessary since by default picture filled with
> 0x80808080. */
> + gif_fill(&s->picture, s->trans_color);
> + }
> }
>
> /* verify that all the image is inside the screen dimensions */
> @@ -97,18 +189,37 @@ static int gif_read_image(GifState *s)
> top + height > s->screen_height)
> return AVERROR(EINVAL);
>
> - /* build the palette */
> - n = (1 << bits_per_pixel);
> - spal = palette;
> - for(i = 0; i < n; i++) {
> - s->image_palette[i] = (0xffu << 24) | AV_RB24(spal);
> - spal += 3;
> + /* process disposal method */
> + if (s->gce_prev_disposal == GCE_DISPOSAL_BACKGROUND) {
> + gif_fill_rect(&s->picture, s->stored_bg_color, s->gce_l, s->gce_t,
> s->gce_w, s->gce_h);
> + } else if (s->gce_prev_disposal == GCE_DISPOSAL_RESTORE) {
> + gif_copy_img_rect(s->stored_img, (uint32_t *)s->picture.data[0],
> + s->picture.linesize[0] / sizeof(uint32_t), s->gce_l, s->gce_t,
> s->gce_w, s->gce_h);
> + }
> +
> + s->gce_prev_disposal = s->gce_disposal;
> +
> + if (s->gce_disposal != GCE_DISPOSAL_NONE) {
> + s->gce_l = left; s->gce_t = top;
> + s->gce_w = width; s->gce_h = height;
> +
> + if (s->gce_disposal == GCE_DISPOSAL_BACKGROUND) {
> + if (s->background_color_index == s->transparent_color_index)
> + s->stored_bg_color = s->trans_color;
> + else
> + s->stored_bg_color = s->bg_color;
> + } else if (s->gce_disposal == GCE_DISPOSAL_RESTORE) {
> + if (!s->stored_img) {
> + s->stored_img = av_malloc(s->picture.linesize[0] *
> s->picture.height);
> +
> + if (!s->stored_img)
> + return AVERROR(ENOMEM);
> + }
> +
> + gif_copy_img_rect((uint32_t *)s->picture.data[0],
> s->stored_img,
> + s->picture.linesize[0] / sizeof(uint32_t), left, top,
> width, height);
> + }
> }
> - for(; i < 256; i++)
> - s->image_palette[i] = (0xffu << 24);
> - /* handle transparency */
> - if (s->transparent_color_index >= 0)
> - s->image_palette[s->transparent_color_index] = 0;
>
> /* now get the image data */
> code_size = bytestream_get_byte(&s->bytestream);
> @@ -119,13 +230,22 @@ static int gif_read_image(GifState *s)
> }
>
> /* read all the image */
> - linesize = s->picture.linesize[0];
> - ptr1 = s->picture.data[0] + top * linesize + left;
> + linesize = s->picture.linesize[0] / sizeof(uint32_t);
> + ptr1 = (uint32_t *)s->picture.data[0] + top * linesize + left;
> ptr = ptr1;
> pass = 0;
> y1 = 0;
> for (y = 0; y < height; y++) {
> - ff_lzw_decode(s->lzw, ptr, width);
> + if (ff_lzw_decode(s->lzw, s->idx_line, width) == 0)
> + goto decode_tail;
> +
> + pr = ptr + width;
> +
> + for (px = ptr, idx = s->idx_line; px < pr; px++, idx++) {
> + if (*idx != s->transparent_color_index)
> + *px = pal[*idx];
> + }
> +
> if (is_interleaved) {
> switch(pass) {
> default:
> @@ -157,9 +277,17 @@ static int gif_read_image(GifState *s)
> ptr += linesize;
> }
> }
> +
> + decode_tail:
> /* read the garbage data until end marker is found */
> ff_lzw_decode_tail(s->lzw);
> s->bytestream = ff_lzw_cur_ptr(s->lzw);
> +
> + /* Graphic Control Extension's scope is single frame.
> + * Remove its influence. */
> + s->transparent_color_index = -1;
> + s->gce_disposal = GCE_DISPOSAL_NONE;
> +
> return 0;
> }
>
> @@ -179,7 +307,7 @@ static int gif_read_extension(GifState *s)
> goto discard_ext;
> s->transparent_color_index = -1;
> gce_flags = bytestream_get_byte(&s->bytestream);
> - s->gce_delay = bytestream_get_le16(&s->bytestream);
> + bytestream_get_le16(&s->bytestream); // delay during which the
> frame is shown
> gce_transparent_index = bytestream_get_byte(&s->bytestream);
> if (gce_flags & 0x01)
> s->transparent_color_index = gce_transparent_index;
> @@ -187,10 +315,15 @@ static int gif_read_extension(GifState *s)
> s->transparent_color_index = -1;
> s->gce_disposal = (gce_flags >> 2) & 0x7;
>
> - av_dlog(s->avctx, "gce_flags=%x delay=%d tcolor=%d disposal=%d\n",
> - gce_flags, s->gce_delay,
> + av_dlog(s->avctx, "gce_flags=%x tcolor=%d disposal=%d\n",
> + gce_flags,
> s->transparent_color_index, s->gce_disposal);
>
> + if (s->gce_disposal > 3) {
> + s->gce_disposal = GCE_DISPOSAL_NONE;
> + av_dlog(s->avctx, "invalid value in gce_disposal (%d). Using
> default value of 0.\n", ext_len);
> + }
> +
> ext_len = bytestream_get_byte(&s->bytestream);
> break;
> }
> @@ -211,7 +344,7 @@ static int gif_read_header1(GifState *s)
> {
> uint8_t sig[6];
> int v, n;
> - int has_global_palette;
> + int background_color_index;
>
> if (s->bytestream_end < s->bytestream + 13)
> return AVERROR_INVALIDDATA;
> @@ -232,23 +365,32 @@ static int gif_read_header1(GifState *s)
> return AVERROR_INVALIDDATA;
> }
>
> + s->idx_line = av_malloc(s->screen_width);
> + if (!s->idx_line)
> + return AVERROR(ENOMEM);
> +
> v = bytestream_get_byte(&s->bytestream);
> s->color_resolution = ((v & 0x70) >> 4) + 1;
> - has_global_palette = (v & 0x80);
> + s->has_global_palette = (v & 0x80);
> s->bits_per_pixel = (v & 0x07) + 1;
> - s->background_color_index = bytestream_get_byte(&s->bytestream);
> + background_color_index = bytestream_get_byte(&s->bytestream);
> bytestream_get_byte(&s->bytestream); /* ignored */
>
> av_dlog(s->avctx, "screen_w=%d screen_h=%d bpp=%d
> global_palette=%d\n",
> s->screen_width, s->screen_height, s->bits_per_pixel,
> - has_global_palette);
> + s->has_global_palette);
>
> - if (has_global_palette) {
> + if (s->has_global_palette) {
> + s->background_color_index = background_color_index;
> n = 1 << s->bits_per_pixel;
> if (s->bytestream_end < s->bytestream + n * 3)
> return AVERROR_INVALIDDATA;
> - bytestream_get_buffer(&s->bytestream, s->global_palette, n * 3);
> - }
> +
> + gif_read_palette(&s->bytestream, s->global_palette, n);
> + s->bg_color = s->global_palette[s->background_color_index];
> + } else
> + s->background_color_index = -1;
> +
> return 0;
> }
>
> @@ -286,6 +428,7 @@ static av_cold int gif_decode_init(AVCodecContext
> *avctx)
>
> s->avctx = avctx;
>
> + avctx->pix_fmt = AV_PIX_FMT_RGB32;
> avcodec_get_frame_defaults(&s->picture);
> avctx->coded_frame= &s->picture;
> s->picture.data[0] = NULL;
> @@ -301,23 +444,48 @@ static int gif_decode_frame(AVCodecContext *avctx,
> void *data, int *got_picture,
> AVFrame *picture = data;
> int ret;
>
> + s->picture.pts = avpkt->pts;
> + s->picture.pkt_pts = avpkt->pts;
> + s->picture.pkt_dts = avpkt->dts;
> + s->picture.pkt_duration = avpkt->duration;
> +
> s->bytestream = buf;
> s->bytestream_end = buf + buf_size;
> - if ((ret = gif_read_header1(s)) < 0)
> - return ret;
>
> - avctx->pix_fmt = AV_PIX_FMT_PAL8;
> - if ((ret = av_image_check_size(s->screen_width, s->screen_height, 0,
> avctx)) < 0)
> - return ret;
> - avcodec_set_dimensions(avctx, s->screen_width, s->screen_height);
> + if (buf_size >= 6) {
> + s->is_first_frame = memcmp(s->bytestream, gif87a_sig, 6) == 0 ||
> + memcmp(s->bytestream, gif89a_sig, 6) == 0;
> + } else
> + s->is_first_frame = 0;
Should use flag for keyframe from demuxer instead.
Concatenated gifs should be supported.
>
> - if (s->picture.data[0])
> - avctx->release_buffer(avctx, &s->picture);
> - if ((ret = avctx->get_buffer(avctx, &s->picture)) < 0) {
> - av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
> - return ret;
> + if (s->is_first_frame) {
> + if ((ret = gif_read_header1(s)) < 0)
> + return ret;
> +
> + if ((ret = av_image_check_size(s->screen_width, s->screen_height,
> 0, avctx)) < 0)
> + return ret;
> + avcodec_set_dimensions(avctx, s->screen_width, s->screen_height);
> +
> + if (s->picture.data[0])
> + avctx->release_buffer(avctx, &s->picture);
> +
> + if ((ret = avctx->get_buffer(avctx, &s->picture)) < 0) {
> + av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
> + return ret;
> + }
> +
> + s->picture.pict_type = AV_PICTURE_TYPE_I;
> + s->picture.key_frame = 1;
> + } else {
> + if ((ret = avctx->reget_buffer(avctx, &s->picture)) < 0) {
> + av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n");
> + return ret;
> + }
> +
> + s->picture.pict_type = AV_PICTURE_TYPE_P;
> + s->picture.key_frame = 0;
> }
> - s->image_palette = (uint32_t *)s->picture.data[1];
> +
> ret = gif_parse_next_image(s, got_picture);
> if (ret < 0)
> return ret;
> @@ -334,9 +502,29 @@ static av_cold int gif_decode_close(AVCodecContext
> *avctx)
> ff_lzw_decode_close(&s->lzw);
> if(s->picture.data[0])
> avctx->release_buffer(avctx, &s->picture);
> +
> + av_freep(&s->idx_line);
> + av_freep(&s->stored_img);
> +
> return 0;
> }
>
> +static const AVOption options[] = {
> + { "trans_color", "color value (ARGB) that is used instead of
> transparent color",
> + offsetof(GifState, trans_color), AV_OPT_TYPE_INT,
> + {.i64 = GIF_TRANSPARENT_COLOR}, 0, 0xffffffff,
> + AV_OPT_FLAG_DECODING_PARAM|AV_OPT_FLAG_VIDEO_PARAM },
> + { NULL },
> +};
> +
> +static const AVClass decoder_class = {
> + .class_name = "GifState",
> + .item_name = av_default_item_name,
> + .option = options,
> + .version = LIBAVUTIL_VERSION_INT,
> + .category = AV_CLASS_CATEGORY_DECODER,
> +};
> +
> AVCodec ff_gif_decoder = {
> .name = "gif",
> .type = AVMEDIA_TYPE_VIDEO,
> @@ -347,4 +535,5 @@ AVCodec ff_gif_decoder = {
> .decode = gif_decode_frame,
> .capabilities = CODEC_CAP_DR1,
> .long_name = NULL_IF_CONFIG_SMALL("GIF (Graphics Interchange
> Format)"),
> + .priv_class = &decoder_class,
> };
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index 197e8e3..2148c4f 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -29,8 +29,8 @@
> #include "libavutil/avutil.h"
>
> #define LIBAVCODEC_VERSION_MAJOR 54
> -#define LIBAVCODEC_VERSION_MINOR 77
> -#define LIBAVCODEC_VERSION_MICRO 101
> +#define LIBAVCODEC_VERSION_MINOR 78
> +#define LIBAVCODEC_VERSION_MICRO 100
>
> #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
> LIBAVCODEC_VERSION_MINOR, \
> --
> 1.7.2.5
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
More information about the ffmpeg-devel
mailing list