[FFmpeg-devel] [PATCH] libavcodec/gifenc: Only write palette entries that actually used

Nuo Mi nuomi2021 at gmail.com
Sat Feb 6 06:02:01 EET 2021


On Fri, Feb 5, 2021 at 12:57 AM Derek Buitenhuis <derek.buitenhuis at gmail.com>
wrote:

> GIF palette entries are not compressed, and writing 256 entries,
> which can be up to every frame, uses a significant amount of
> space, especially in extreme cases, where palettes can be very
> small.
>
> Example, first six seconds of Tears of Steel, palette generated
> with libimagequant, 320x240 resolution, and with transparency
> optimization + per frame palette:
>
>     * Before patch: 186765 bytes
>     * After patch: 77901 bytes
>
> Signed-off-by: Derek Buitenhuis <derek.buitenhuis at gmail.com>
> ---
>  libavcodec/gif.c | 81 +++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 69 insertions(+), 12 deletions(-)
>
> diff --git a/libavcodec/gif.c b/libavcodec/gif.c
> index de41992851..c52db57edd 100644
> --- a/libavcodec/gif.c
> +++ b/libavcodec/gif.c
> @@ -52,6 +52,7 @@ typedef struct GIFContext {
>      int flags;
>      int image;
>      uint32_t palette[AVPALETTE_COUNT];  ///< local reference palette for
> !pal8
> +    size_t palette_count;
>      int palette_loaded;
>      int transparent_index;
>      uint8_t *tmpl;                      ///< temporary line buffer
> @@ -62,6 +63,27 @@ enum {
>      GF_TRANSDIFF  = 1<<1,
>  };
>
> +static void shrink_palette(const uint32_t *src, 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];
> +            colors_seen++;
> +        }
> +    }
> +
> +    *palette_count = colors_seen;
> +}
> +
>  static int is_image_translucent(AVCodecContext *avctx,
>                                  const uint8_t *buf, const int linesize)
>  {
> @@ -83,7 +105,7 @@ static int is_image_translucent(AVCodecContext *avctx,
>      return 0;
>  }
>
> -static int get_palette_transparency_index(const uint32_t *palette)
> +static int get_palette_transparency_index(const uint32_t *palette, int
> palette_count)
>  {
>      int transparent_color_index = -1;
>      unsigned i, smallest_alpha = 0xff;
> @@ -91,7 +113,7 @@ static int get_palette_transparency_index(const
> uint32_t *palette)
>      if (!palette)
>          return -1;
>
> -    for (i = 0; i < AVPALETTE_COUNT; i++) {
> +    for (i = 0; i < palette_count; i++) {
>          const uint32_t v = palette[i];
>          if (v >> 24 < smallest_alpha) {
>              smallest_alpha = v >> 24;
> @@ -266,6 +288,10 @@ 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;
> +    uint32_t shrunk_palette[AVPALETTE_COUNT] = { 0 };
> +    size_t shrunk_palette_count;
> +
> +    memset(shrunk_palette, 0xff, AVPALETTE_SIZE);

This will memset each frame. Could we avoid it?
Is memset memory between [global_palette_count, 1<<(
av_log2(global_palette_count - 1) + 1)]  enough?




>      if (!s->image && is_image_translucent(avctx, buf, linesize)) {
>          gif_crop_translucent(avctx, buf, linesize, &width, &height,
> &x_start, &y_start);
> @@ -277,10 +303,21 @@ static int gif_image_write_image(AVCodecContext
> *avctx,
>      }
>
>      if (s->image || !avctx->frame_number) { /* GIF header */
> -        const uint32_t *global_palette = palette ? palette : s->palette;
> +        uint32_t *global_palette;
> +        uint32_t shrunk_global_palette[AVPALETTE_COUNT];
> +        size_t global_palette_count;
> +        unsigned pow2_global_palette_count;
>          const AVRational sar = avctx->sample_aspect_ratio;
>          int64_t aspect = 0;
>
> +        if (palette) {
> +            shrink_palette(palette, shrunk_global_palette,
> &global_palette_count);
> +            global_palette = shrunk_global_palette;
> +        } else {
> +            global_palette = s->palette;
> +            global_palette_count = s->palette_count;
> +        }
> +
>          if (sar.num > 0 && sar.den > 0) {
>              aspect = sar.num * 64LL / sar.den - 15;
>              if (aspect < 0 || aspect > 255)
> @@ -291,17 +328,22 @@ static int gif_image_write_image(AVCodecContext
> *avctx,
>          bytestream_put_le16(bytestream, avctx->width);
>          bytestream_put_le16(bytestream, avctx->height);
>
> -        bcid = get_palette_transparency_index(global_palette);
> +        bcid = get_palette_transparency_index(global_palette,
> global_palette_count);
>
> -        bytestream_put_byte(bytestream, 0xf7); /* flags: global clut, 256
> entries */
> +        pow2_global_palette_count = av_log2(global_palette_count - 1);
> +
> +        bytestream_put_byte(bytestream, 0xf0 |
> pow2_global_palette_count); /* flags: global clut, 256 entries */
>
The comment may be misleading, it's not 256 anymore.

>          bytestream_put_byte(bytestream, bcid < 0 ?
> DEFAULT_TRANSPARENCY_INDEX : bcid); /* background color index */
>
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


More information about the ffmpeg-devel mailing list