[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