From patchwork Sat Feb 20 19:23:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derek Buitenhuis X-Patchwork-Id: 25848 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id D029744A933 for ; Sat, 20 Feb 2021 21:24:12 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id AA7D468A78A; Sat, 20 Feb 2021 21:24:12 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 304AD68A17B for ; Sat, 20 Feb 2021 21:24:06 +0200 (EET) Received: by mail-wm1-f51.google.com with SMTP id o15so9902326wmq.5 for ; Sat, 20 Feb 2021 11:24:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=r/3C/RTvRvu0/3tN0s8hKtPGvOREtXiQAel/oOX9jJw=; b=jvdNjRKZ/PC9WMS76zKtW2yxNgkk2FLIa2k2mCqUWTNiS9nyWluMuGt2iGis1RiZ5T u5Fddjbwgnc7f/l3KKLm8/dvzieh/eVXBkBf90RpihaYw6E3g5pBTg7Y/Mz8+FQ0yr/R MONrDK7EVIPoXA3uqgycOGDd2U6djlYvQ3gfnHlSj2cSbASrrVgAwzz1kJomhfGnebAU TdOFCl4ZgPvpdmF9Y9DU3vSZ8HZDwwOxvLC19ZIbDyKMZsJzz3Sc9QSnB7TssXx6Aw2F 1GvD8xWUmYW0vXo1cZoDogdi7RkRq/dCKD1kUDgtqgD4WuAmP11nJnca5i1DctWBsM9l pWFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=r/3C/RTvRvu0/3tN0s8hKtPGvOREtXiQAel/oOX9jJw=; b=RXXc8faVfVhWzyXA8xzm+3YJp9HE2yKzkDl+tucIX/zVyZf/MrqyV6pT5Nk4V1cdub WHLyPQm+k7j+50ksJ0Fn/zfutRxlfGnLreOTrk+rIXmPw8gPB8l/74qs7cdbI5NKNr+3 ykQCXuPVvSZsm0STpmw678F9BfloHSsknCEcMQVzxPcgqY8wO8eVr9j2d2URpb2CfO7n vuxL7/2Y7W+D8plnjr5mXrJny8D1GzgfnLhqXN0n9sC2Syf7rJi2C9HA9WYzA2dqZKRu thbFEhbyg2Me3vQnW1xZ8RaVn2A/VJXyv2AYEo6acjnoP3Zd+wwB7cAfTs27acIQuyt9 ffQw== X-Gm-Message-State: AOAM53009Wax3DRNX6orED4fdXSSyAI+ykYwPDCbZvVBzr+KR4yvezro fgqCwUaHqYOhbfVxLYKkIXLCqkgM87o= X-Google-Smtp-Source: ABdhPJw7xAStBgfpcCApKhj0oG/90QQJbX8SDh7PiHnRi2vVMupxkCzt5y3RPibuUX/WFz8DbwCfFg== X-Received: by 2002:a7b:c5d6:: with SMTP id n22mr13094793wmk.70.1613849045280; Sat, 20 Feb 2021 11:24:05 -0800 (PST) Received: from localhost.localdomain ([82.129.110.36]) by smtp.gmail.com with ESMTPSA id c133sm8517114wme.46.2021.02.20.11.24.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 20 Feb 2021 11:24:04 -0800 (PST) From: Derek Buitenhuis To: ffmpeg-devel@ffmpeg.org Date: Sat, 20 Feb 2021 19:23:55 +0000 Message-Id: <20210220192355.509497-1-derek.buitenhuis@gmail.com> X-Mailer: git-send-email 2.30.0 In-Reply-To: <20210220185050.508373-1-derek.buitenhuis@gmail.com> References: <20210220185050.508373-1-derek.buitenhuis@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 2/2 v3] avcodec/gifenc: Only write frame palette entries that actually used X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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: 77895 bytes Signed-off-by: Derek Buitenhuis --- Changes since v2: * shrunk_buf allocated only once per encoder instance. * Does not use temporary pointers during remapping. --- libavcodec/gif.c | 68 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/libavcodec/gif.c b/libavcodec/gif.c index 8c07ee2769..ff0d50cb44 100644 --- a/libavcodec/gif.c +++ b/libavcodec/gif.c @@ -47,6 +47,7 @@ typedef struct GIFContext { const AVClass *class; LZWState *lzw; uint8_t *buf; + uint8_t *shrunk_buf; int buf_size; AVFrame *last_frame; int flags; @@ -63,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) { @@ -267,6 +300,17 @@ 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]; + 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); @@ -335,9 +379,14 @@ static int gif_image_write_image(AVCodecContext *avctx, if (palette || !s->use_global_palette) { const uint32_t *pal = palette ? palette : s->palette; + unsigned pow2_count; unsigned i; - bytestream_put_byte(bytestream, 1<<7 | 0x7); /* flags */ - for (i = 0; i < AVPALETTE_COUNT; i++) { + + shrink_palette(pal, map, shrunk_palette, &shrunk_palette_count); + pow2_count = av_log2(shrunk_palette_count - 1); + + bytestream_put_byte(bytestream, 1<<7 | pow2_count); /* flags */ + for (i = 0; i < 1 << (pow2_count + 1); i++) { const uint32_t v = pal[i]; bytestream_put_be24(bytestream, v); } @@ -350,7 +399,19 @@ static int gif_image_write_image(AVCodecContext *avctx, ff_lzw_encode_init(s->lzw, s->buf, s->buf_size, 12, FF_LZW_GIF, 1); - ptr = buf + y_start*linesize + x_start; + 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; @@ -464,6 +525,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);