[FFmpeg-devel] [PATCH 4/7] First pass at making the gif support transparency with the right disposal methods. Needs more optimization.
Bjorn Roche
bjorn at giphy.com
Mon Oct 2 20:24:36 EEST 2017
From: Bjorn Roche <bjorn at xowave.com>
---
libavcodec/avcodec.h | 6 ++
libavcodec/gif.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++--
libavformat/gif.c | 16 ++++-
3 files changed, 193 insertions(+), 6 deletions(-)
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 5c84974e03..3c64e8f7ee 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -1599,6 +1599,12 @@ enum AVPacketSideDataType {
*/
AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+ /**
+ * The disposal method that should be used with the frame. If missing,
+ * the frame will not be disposed. This contains exactly one byte.
+ */
+ AV_PKT_DATA_GIF_FRAME_DISPOSAL,
+
/**
* The number of side data elements (in fact a bit more than it).
* This is not part of the public API/ABI in the sense that it may
diff --git a/libavcodec/gif.c b/libavcodec/gif.c
index d9c99d52cf..db2193a718 100644
--- a/libavcodec/gif.c
+++ b/libavcodec/gif.c
@@ -74,11 +74,36 @@ static int pick_palette_entry(const uint8_t *buf, int linesize, int w, int h)
return -1;
}
-static int gif_image_write_image(AVCodecContext *avctx,
- uint8_t **bytestream, uint8_t *end,
- const uint32_t *palette,
- const uint8_t *buf, const int linesize,
- AVPacket *pkt)
+// returns true if any of the pixels are transparent
+static int is_image_translucent(AVCodecContext *avctx,
+ const uint32_t *palette,
+ const uint8_t *buf, const int linesize)
+{
+ GIFContext *s = avctx->priv_data;
+ int trans = s->transparent_index;
+ int p;
+ const int m = avctx->width * avctx->height ;
+
+ if( trans < 0 ) {
+ return 0;
+ }
+
+ // FIXME: this might be faster with strchr
+ for( p=0; p<m; ++p ) {
+ if( buf[p] == trans ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// writes an opaque image. ie an image with no transparency.
+// it also works, and should be used, for a first image.
+static int gif_image_write_opaque(AVCodecContext *avctx,
+ uint8_t **bytestream, uint8_t *end,
+ const uint32_t *palette,
+ const uint8_t *buf, const int linesize,
+ AVPacket *pkt)
{
GIFContext *s = avctx->priv_data;
int len = 0, height = avctx->height, width = avctx->width, x, y;
@@ -86,6 +111,7 @@ static int gif_image_write_image(AVCodecContext *avctx,
int honor_transparency = (s->flags & GF_TRANSDIFF) && s->last_frame && !palette;
const uint8_t *ptr;
+
/* Crop image */
if ((s->flags & GF_OFFSETTING) && s->last_frame && !palette) {
const uint8_t *ref = s->last_frame->data[0];
@@ -137,6 +163,11 @@ static int gif_image_write_image(AVCodecContext *avctx,
width, height, x_start, y_start, avctx->width, avctx->height);
}
+ uint8_t *frame_disposal = av_packet_new_side_data(pkt, AV_PKT_DATA_GIF_FRAME_DISPOSAL, 1);
+ if (!frame_disposal)
+ return AVERROR(ENOMEM);
+ *frame_disposal = GCE_DISPOSAL_INPLACE;
+
/* image block */
bytestream_put_byte(bytestream, GIF_IMAGE_SEPARATOR);
bytestream_put_le16(bytestream, x_start);
@@ -214,6 +245,142 @@ static int gif_image_write_image(AVCodecContext *avctx,
return 0;
}
+// wrtites an image that may contain transparency
+// this might work for opaque images as well, but will be less optimized.
+static int gif_image_write_translucent(AVCodecContext *avctx,
+ uint8_t **bytestream, uint8_t *end,
+ const uint32_t *palette,
+ const uint8_t *buf, const int linesize,
+ AVPacket *pkt)
+{
+ GIFContext *s = avctx->priv_data;
+ int len = 0, height = avctx->height, width = avctx->width, x, y;
+ int x_start = 0, y_start = 0, trans = s->transparent_index;
+ int honor_transparency = (s->flags & GF_TRANSDIFF) && s->last_frame && !palette;
+ const uint8_t *ptr;
+
+ // /* Crop image */
+ // if ((s->flags & GF_OFFSETTING) && s->last_frame && !palette) {
+ // const uint8_t *ref = s->last_frame->data[0];
+ // const int ref_linesize = s->last_frame->linesize[0];
+ // int x_end = avctx->width - 1,
+ // y_end = avctx->height - 1;
+
+ // /* skip common lines */
+ // while (y_start < y_end) {
+ // if (memcmp(ref + y_start*ref_linesize, buf + y_start*linesize, width))
+ // break;
+ // y_start++;
+ // }
+ // while (y_end > y_start) {
+ // if (memcmp(ref + y_end*ref_linesize, buf + y_end*linesize, width))
+ // break;
+ // y_end--;
+ // }
+ // height = y_end + 1 - y_start;
+
+ // /* skip common columns */
+ // while (x_start < x_end) {
+ // int same_column = 1;
+ // for (y = y_start; y <= y_end; y++) {
+ // if (ref[y*ref_linesize + x_start] != buf[y*linesize + x_start]) {
+ // same_column = 0;
+ // break;
+ // }
+ // }
+ // if (!same_column)
+ // break;
+ // x_start++;
+ // }
+ // while (x_end > x_start) {
+ // int same_column = 1;
+ // for (y = y_start; y <= y_end; y++) {
+ // if (ref[y*ref_linesize + x_end] != buf[y*linesize + x_end]) {
+ // same_column = 0;
+ // break;
+ // }
+ // }
+ // if (!same_column)
+ // break;
+ // x_end--;
+ // }
+ // width = x_end + 1 - x_start;
+
+ // av_log(avctx, AV_LOG_DEBUG,"%dx%d image at pos (%d;%d) [area:%dx%d]\n",
+ // width, height, x_start, y_start, avctx->width, avctx->height);
+ // }
+
+
+ uint8_t *frame_disposal = av_packet_new_side_data(pkt, AV_PKT_DATA_GIF_FRAME_DISPOSAL, 1);
+ if (!frame_disposal)
+ return AVERROR(ENOMEM);
+ *frame_disposal = GCE_DISPOSAL_BACKGROUND;
+
+ /* image block */
+ bytestream_put_byte(bytestream, GIF_IMAGE_SEPARATOR);
+ bytestream_put_le16(bytestream, x_start);
+ bytestream_put_le16(bytestream, y_start);
+ bytestream_put_le16(bytestream, width);
+ bytestream_put_le16(bytestream, height);
+
+ if (!palette) {
+ bytestream_put_byte(bytestream, 0x00); /* flags */
+ } else {
+ unsigned i;
+ bytestream_put_byte(bytestream, 1<<7 | 0x7); /* flags */
+ for (i = 0; i < AVPALETTE_COUNT; i++) {
+ const uint32_t v = palette[i];
+ bytestream_put_be24(bytestream, v);
+ }
+ }
+
+ bytestream_put_byte(bytestream, 0x08);
+
+ ff_lzw_encode_init(s->lzw, s->buf, s->buf_size,
+ 12, FF_LZW_GIF, put_bits);
+
+ ptr = buf + y_start*linesize + x_start;
+
+ for (y = 0; y < height; y++) {
+ len += ff_lzw_encode(s->lzw, ptr, width);
+ ptr += linesize;
+ }
+
+ len += ff_lzw_encode_flush(s->lzw, flush_put_bits);
+
+ ptr = s->buf;
+ while (len > 0) {
+ int size = FFMIN(255, len);
+ bytestream_put_byte(bytestream, size);
+ if (end - *bytestream < size)
+ return -1;
+ bytestream_put_buffer(bytestream, ptr, size);
+ ptr += size;
+ len -= size;
+ }
+ bytestream_put_byte(bytestream, 0x00); /* end of image block */
+
+ return 0;
+}
+
+static int gif_image_write_image(AVCodecContext *avctx,
+ uint8_t **bytestream, uint8_t *end,
+ const uint32_t *palette,
+ const uint8_t *buf, const int linesize,
+ AVPacket *pkt)
+{
+ GIFContext *s = avctx->priv_data;
+ if( !s->last_frame ) {
+ return gif_image_write_opaque(avctx, bytestream, end, palette, buf, linesize, pkt);
+ }
+
+ if( is_image_translucent(avctx, palette, buf, linesize ) ) {
+ return gif_image_write_translucent(avctx, bytestream, end, palette, buf, linesize, pkt);
+ } else {
+ return gif_image_write_opaque(avctx, bytestream, end, palette, buf, linesize, pkt);
+ }
+}
+
static av_cold int gif_encode_init(AVCodecContext *avctx)
{
GIFContext *s = avctx->priv_data;
diff --git a/libavformat/gif.c b/libavformat/gif.c
index 91cd40db5c..853e84430e 100644
--- a/libavformat/gif.c
+++ b/libavformat/gif.c
@@ -144,6 +144,8 @@ static int flush_packet(AVFormatContext *s, AVPacket *new)
AVIOContext *pb = s->pb;
const uint32_t *palette;
AVPacket *pkt = gif->prev_pkt;
+ uint8_t *disposal;
+ uint8_t packed;
if (!pkt)
return 0;
@@ -157,16 +159,28 @@ static int flush_packet(AVFormatContext *s, AVPacket *new)
}
bcid = get_palette_transparency_index(palette);
+ disposal = av_packet_get_side_data(pkt, AV_PKT_DATA_GIF_FRAME_DISPOSAL, &size);
+ if( disposal && size != 1 ) {
+ av_log(s, AV_LOG_ERROR, "Invalid gif frame disposal extradata\n");
+ return AVERROR_INVALIDDATA;
+ }
+
if (new && new->pts != AV_NOPTS_VALUE)
gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts);
else if (!new && gif->last_delay >= 0)
gif->duration = gif->last_delay;
/* graphic control extension block */
+ if( disposal ) {
+ packed = (0xff & (*disposal)<<2) | (bcid >= 0);
+ } else {
+ packed = 1<<2 | (bcid >= 0);
+ }
+ //FIXME: if disposal == 2, make sure backgrdoun color is specified appropriately.
avio_w8(pb, 0x21);
avio_w8(pb, 0xf9);
avio_w8(pb, 0x04); /* block size */
- avio_w8(pb, 1<<2 | (bcid >= 0));
+ avio_w8(pb, packed);
avio_wl16(pb, gif->duration);
avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid);
avio_w8(pb, 0x00);
--
2.14.1
More information about the ffmpeg-devel
mailing list