]> git.sesse.net Git - ffmpeg/blobdiff - libavcodec/gif.c
avcodec: Constify AVCodecs
[ffmpeg] / libavcodec / gif.c
index 9f2f30d86318b0211e3135635e2c7c357d614ccf..f3ee15c2ac8a55b84aa4f76fb35a0254805a56c5 100644 (file)
@@ -47,10 +47,12 @@ typedef struct GIFContext {
     const AVClass *class;
     LZWState *lzw;
     uint8_t *buf;
+    uint8_t *shrunk_buf;
     int buf_size;
     AVFrame *last_frame;
     int flags;
     int image;
+    int use_global_palette;
     uint32_t palette[AVPALETTE_COUNT];  ///< local reference palette for !pal8
     int palette_loaded;
     int transparent_index;
@@ -62,6 +64,38 @@ enum {
     GF_TRANSDIFF  = 1<<1,
 };
 
+static void shrink_palette(const uint32_t *src, uint8_t *map,
+                           uint32_t *dst, size_t *palette_count)
+{
+    size_t colors_seen = 0;
+
+    for (size_t i = 0; i < AVPALETTE_COUNT; i++) {
+        int seen = 0;
+        for (size_t c = 0; c < colors_seen; c++) {
+            if (src[i] == dst[c]) {
+                seen = 1;
+                break;
+            }
+        }
+        if (!seen) {
+            dst[colors_seen] = src[i];
+            map[i] = colors_seen;
+            colors_seen++;
+        }
+    }
+
+    *palette_count = colors_seen;
+}
+
+static void remap_frame_to_palette(const uint8_t *src, int src_linesize,
+                                   uint8_t *dst, int dst_linesize,
+                                   int w, int h, uint8_t *map)
+{
+    for (int i = 0; i < h; i++)
+        for (int j = 0; j < w; j++)
+            dst[i * dst_linesize + j] = map[src[i * src_linesize + j]];
+}
+
 static int is_image_translucent(AVCodecContext *avctx,
                                 const uint8_t *buf, const int linesize)
 {
@@ -136,7 +170,7 @@ static void gif_crop_translucent(AVCodecContext *avctx,
         while (*y_start < y_end) {
             int is_trans = 1;
             for (int i = 0; i < w; i++) {
-                if (buf[w * *y_start + i] != trans) {
+                if (buf[linesize * *y_start + i] != trans) {
                     is_trans = 0;
                     break;
                 }
@@ -148,10 +182,10 @@ static void gif_crop_translucent(AVCodecContext *avctx,
         }
 
         // crop bottom
-        while (y_end < h) {
+        while (y_end > *y_start) {
             int is_trans = 1;
             for (int i = 0; i < w; i++) {
-                if (buf[w * y_end + i] != trans) {
+                if (buf[linesize * y_end + i] != trans) {
                     is_trans = 0;
                     break;
                 }
@@ -165,7 +199,7 @@ static void gif_crop_translucent(AVCodecContext *avctx,
         while (*x_start < x_end) {
             int is_trans = 1;
             for (int i = *y_start; i < y_end; i++) {
-                if (buf[w * i + *x_start] != trans) {
+                if (buf[linesize * i + *x_start] != trans) {
                     is_trans = 0;
                     break;
                 }
@@ -176,10 +210,10 @@ static void gif_crop_translucent(AVCodecContext *avctx,
         }
 
         // crop right
-        while (x_end < w) {
+        while (x_end > *x_start) {
             int is_trans = 1;
             for (int i = *y_start; i < y_end; i++) {
-                if (buf[w * i + x_end] != trans) {
+                if (buf[linesize * i + x_end] != trans) {
                     is_trans = 0;
                     break;
                 }
@@ -266,8 +300,19 @@ static int gif_image_write_image(AVCodecContext *avctx,
     int x_start = 0, y_start = 0, trans = s->transparent_index;
     int bcid = -1, honor_transparency = (s->flags & GF_TRANSDIFF) && s->last_frame && !palette;
     const uint8_t *ptr;
-
-    if (!s->image && avctx->frame_number && is_image_translucent(avctx, buf, linesize)) {
+    uint32_t shrunk_palette[AVPALETTE_COUNT];
+    uint8_t map[AVPALETTE_COUNT] = { 0 };
+    size_t shrunk_palette_count = 0;
+
+    /*
+     * We memset to 0xff instead of 0x00 so that the transparency detection
+     * doesn't pick anything after the palette entries as the transparency
+     * index, and because GIF89a requires us to always write a power-of-2
+     * number of palette entries.
+     */
+    memset(shrunk_palette, 0xff, AVPALETTE_SIZE);
+
+    if (!s->image && is_image_translucent(avctx, buf, linesize)) {
         gif_crop_translucent(avctx, buf, linesize, &width, &height, &x_start, &y_start);
         honor_transparency = 0;
         disposal = GCE_DISPOSAL_BACKGROUND;
@@ -293,12 +338,14 @@ static int gif_image_write_image(AVCodecContext *avctx,
 
         bcid = get_palette_transparency_index(global_palette);
 
-        bytestream_put_byte(bytestream, 0xf7); /* flags: global clut, 256 entries */
+        bytestream_put_byte(bytestream, ((uint8_t) s->use_global_palette << 7) | 0x70 | (s->use_global_palette ? 7 : 0)); /* flags: global clut, 256 entries */
         bytestream_put_byte(bytestream, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid); /* background color index */
         bytestream_put_byte(bytestream, aspect);
-        for (int i = 0; i < 256; i++) {
-            const uint32_t v = global_palette[i] & 0xffffff;
-            bytestream_put_be24(bytestream, v);
+        if (s->use_global_palette) {
+            for (int i = 0; i < 256; i++) {
+                const uint32_t v = global_palette[i] & 0xffffff;
+                bytestream_put_be24(bytestream, v);
+            }
         }
     }
 
@@ -312,6 +359,11 @@ static int gif_image_write_image(AVCodecContext *avctx,
     if (trans < 0)
         honor_transparency = 0;
 
+    if (palette || !s->use_global_palette) {
+        const uint32_t *pal = palette ? palette : s->palette;
+        shrink_palette(pal, map, shrunk_palette, &shrunk_palette_count);
+    }
+
     bcid = honor_transparency || disposal == GCE_DISPOSAL_BACKGROUND ? trans : get_palette_transparency_index(palette);
 
     /* graphic control extension */
@@ -320,7 +372,7 @@ static int gif_image_write_image(AVCodecContext *avctx,
     bytestream_put_byte(bytestream, 0x04); /* block size */
     bytestream_put_byte(bytestream, disposal<<2 | (bcid >= 0));
     bytestream_put_le16(bytestream, 5); // default delay
-    bytestream_put_byte(bytestream, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid);
+    bytestream_put_byte(bytestream, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : (shrunk_palette_count ? map[bcid] : bcid));
     bytestream_put_byte(bytestream, 0x00);
 
     /* image block */
@@ -330,23 +382,37 @@ static int gif_image_write_image(AVCodecContext *avctx,
     bytestream_put_le16(bytestream, width);
     bytestream_put_le16(bytestream, height);
 
-    if (!palette) {
-        bytestream_put_byte(bytestream, 0x00); /* flags */
-    } else {
+    if (palette || !s->use_global_palette) {
+        unsigned pow2_count = av_log2(shrunk_palette_count - 1);
         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_byte(bytestream, 1<<7 | pow2_count); /* flags */
+        for (i = 0; i < 1 << (pow2_count + 1); i++) {
+            const uint32_t v = shrunk_palette[i];
             bytestream_put_be24(bytestream, v);
         }
+    } else {
+        bytestream_put_byte(bytestream, 0x00); /* flags */
     }
 
     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;
+                       12, FF_LZW_GIF, 1);
+
+    if (shrunk_palette_count) {
+        if (!s->shrunk_buf) {
+            s->shrunk_buf = av_malloc(avctx->height * linesize);
+            if (!s->shrunk_buf) {
+                av_log(avctx, AV_LOG_ERROR, "Could not allocated remapped frame buffer.\n");
+                return AVERROR(ENOMEM);
+            }
+        }
+        remap_frame_to_palette(buf, linesize, s->shrunk_buf, linesize, avctx->width, avctx->height, map);
+        ptr = s->shrunk_buf + y_start*linesize + x_start;
+    } else {
+        ptr = buf + y_start*linesize + x_start;
+    }
     if (honor_transparency) {
         const int ref_linesize = s->last_frame->linesize[0];
         const uint8_t *ref = s->last_frame->data[0] + y_start*ref_linesize + x_start;
@@ -366,7 +432,7 @@ static int gif_image_write_image(AVCodecContext *avctx,
             ptr += linesize;
         }
     }
-    len += ff_lzw_encode_flush(s->lzw, flush_put_bits);
+    len += ff_lzw_encode_flush(s->lzw);
 
     ptr = s->buf;
     while (len > 0) {
@@ -460,6 +526,7 @@ static int gif_encode_close(AVCodecContext *avctx)
 
     av_freep(&s->lzw);
     av_freep(&s->buf);
+    av_freep(&s->shrunk_buf);
     s->buf_size = 0;
     av_frame_free(&s->last_frame);
     av_freep(&s->tmpl);
@@ -472,7 +539,8 @@ static const AVOption gif_options[] = {
     { "gifflags", "set GIF flags", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = GF_OFFSETTING|GF_TRANSDIFF}, 0, INT_MAX, FLAGS, "flags" },
         { "offsetting", "enable picture offsetting", 0, AV_OPT_TYPE_CONST, {.i64=GF_OFFSETTING}, INT_MIN, INT_MAX, FLAGS, "flags" },
         { "transdiff", "enable transparency detection between frames", 0, AV_OPT_TYPE_CONST, {.i64=GF_TRANSDIFF}, INT_MIN, INT_MAX, FLAGS, "flags" },
-    { "gifimage", "enable encoding only images per frame", OFFSET(image), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "flags" },
+    { "gifimage", "enable encoding only images per frame", OFFSET(image), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
+    { "global_palette", "write a palette to the global gif header where feasible", OFFSET(use_global_palette), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGS },
     { NULL }
 };
 
@@ -483,7 +551,7 @@ static const AVClass gif_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
-AVCodec ff_gif_encoder = {
+const AVCodec ff_gif_encoder = {
     .name           = "gif",
     .long_name      = NULL_IF_CONFIG_SMALL("GIF (Graphics Interchange Format)"),
     .type           = AVMEDIA_TYPE_VIDEO,
@@ -497,4 +565,5 @@ AVCodec ff_gif_encoder = {
         AV_PIX_FMT_GRAY8, AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE
     },
     .priv_class     = &gif_class,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
 };