[FFmpeg-devel,4/7] First pass at making the gif support transparency with the right disposal methods. Needs more optimization.

Submitted by Bjorn Roche on Oct. 2, 2017, 5:24 p.m.

Details

Message ID 20171002172439.65628-5-bjorn@giphy.com
State New
Headers show

Commit Message

Bjorn Roche Oct. 2, 2017, 5:24 p.m.
From: Bjorn Roche <bjorn@xowave.com>

---
 libavcodec/avcodec.h |   6 ++
 libavcodec/gif.c     | 177 +++++++++++++++++++++++++++++++++++++++++++++++++--
 libavformat/gif.c    |  16 ++++-
 3 files changed, 193 insertions(+), 6 deletions(-)

Patch hide | download patch | download mbox

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);