From patchwork Mon Oct 2 17:24:36 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Roche X-Patchwork-Id: 5385 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.61.8 with SMTP id n8csp1759157jan; Mon, 2 Oct 2017 10:32:49 -0700 (PDT) X-Received: by 10.223.136.170 with SMTP id f39mr15287717wrf.164.1506965569258; Mon, 02 Oct 2017 10:32:49 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1506965569; cv=none; d=google.com; s=arc-20160816; b=qi8DrgfnOPqjfiFXchs7N9IB23SlRKDnKSBgOLBoGZ/yBjtdvm4/l+ALCUkdJ8QUKO O4jVpr9kpOmHbcAmHLEwNnFK9YaktAoeDRWXp468UCmhVXh0q5jfDhkhCxOMtaiKPIR2 axxJakZjJ7LwEzBkdKwOX6FloKl0VpFbHkv/S5wvsfeVPMj4wFUrVarL4UkhYXXAKXVL sY4OSMuxGRhGNsVLKgd7APKSVUOSP4/0OLQMq0NOmxY1CznuIMbVLYDEdkoGD4yhvZc1 /cOHzfn34kJrjDn/BPDnrv+JvRUzp99m/6CttwGlDyDzFdX4X31QgJ+o0E5WZqwdmohv A8aA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:references:in-reply-to:message-id:date :to:from:dkim-signature:delivered-to:arc-authentication-results; bh=dG6S+oM4waqupNK+4/5Ytwcdt0XtnT70s8PZ9s1TgfE=; b=q0AW/HtGFIOL8zjJ4fUodNV28Ms6YB07b+q1xu8ey1smRXvywIiLGXBo4eA2go/Ohu v/G6Xzmkwgc9nf1EzEe88LQMCrxWu+2a/DeDyQl2sG/riawEinhGGYyogt8BfJpAjtl2 7SEEJ1Lj/XbjjKANeGsx7pGcn/5cscBEtaKzFUl4hZ+RzROXpNTyF2Ma4L3Id4m0D3sI 4vb59X0o/MV5F9ZInYa2IZHKjgKbgENE9fk7QxSbbemnn8e5IC6d4yuLvqMY72DrX9C8 9cizCs6SxOnYZ5TJA4jY2R/dBAB96zpsltYMbrPWCB/dxJwNxWYI/GAu0VsP/oEpread JBhw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@giphy.com header.s=google header.b=HyAJsDTZ; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 189si8321945wmv.21.2017.10.02.10.32.48; Mon, 02 Oct 2017 10:32:49 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@giphy.com header.s=google header.b=HyAJsDTZ; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 45010689F17; Mon, 2 Oct 2017 20:32:32 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qt0-f176.google.com (mail-qt0-f176.google.com [209.85.216.176]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 18898680404 for ; Mon, 2 Oct 2017 20:32:26 +0300 (EEST) Received: by mail-qt0-f176.google.com with SMTP id z50so3217158qtj.4 for ; Mon, 02 Oct 2017 10:32:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=giphy.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=R6aTEzNKYdtFYMACCgNKZxZw5ggK8fZmcnD5qttt6xA=; b=HyAJsDTZ/SpPebmJRxp6xCkWT1XEuOTgNgUNcRReKeSBVEKL2MALuSG2j6DN3PYuds w5fGLX0N1LlYwJ41T0l6Q3CH73mlfm1qHVsFKLnlFd5chiRZ4JW9VNc1/o243Z3PnsnP GDc7oWu2YCg44MjzrtOX0wnejFLxC5wXCnX5E= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=R6aTEzNKYdtFYMACCgNKZxZw5ggK8fZmcnD5qttt6xA=; b=R0IwyI/INgSAaU+M+G2V+JCGRyXnGbn8+IXamCNi81/mgrOiE3NJlOWmSG4PtKlOlh BsvtKr1eiYBUPlnz/CDfi91MrxJTdfdxzEW1tkD4qlQX/dpqTSGyFwr6N5TIChZnYNTR v3VbxtNzoVHohokQ2f+f1/rzG7BHaZeSjNixKMAUxyBnTr16WEQhdppCVGnEK+whJZ7k DgzS4k6SukDcYNyVObRnoT/6O662U13FCdXhkz/Xi1hm8hIXwZCVetpEmDSawBbRqG/J xabkPq4+xOYk7ImZaNz7dQbNmxtppUi11IvgN5/esbpM2ZDeGf/heVA6dNom12MOejQL BFmQ== X-Gm-Message-State: AMCzsaUQi4Z9arkAo/CwvOYS0aWLYvIGFQQ9KfTuHLhsuqhrYheVkdpE cTA1ivXegVSB1G3U0+jlOEoiJtC3JX0= X-Google-Smtp-Source: AOwi7QCjWjZB7eDnDe8NSXZSN/8nldt5+y28j1JjYUyX9fv2CRyK+haQZ74Y4W8L3oRJA/xwuYUH7g== X-Received: by 10.200.52.118 with SMTP id v51mr1568748qtb.266.1506965103596; Mon, 02 Oct 2017 10:25:03 -0700 (PDT) Received: from localhost.localdomain ([208.184.100.82]) by smtp.gmail.com with ESMTPSA id p25sm7339001qte.28.2017.10.02.10.25.02 (version=TLS1 cipher=AES128-SHA bits=128/128); Mon, 02 Oct 2017 10:25:03 -0700 (PDT) From: Bjorn Roche To: ffmpeg-devel@ffmpeg.org Date: Mon, 2 Oct 2017 13:24:36 -0400 Message-Id: <20171002172439.65628-5-bjorn@giphy.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20171002172439.65628-1-bjorn@giphy.com> References: <20171002172439.65628-1-bjorn@giphy.com> Subject: [FFmpeg-devel] [PATCH 4/7] First pass at making the gif support transparency with the right disposal methods. Needs more optimization. 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 Cc: Bjorn Roche MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Bjorn Roche --- libavcodec/avcodec.h | 6 ++ libavcodec/gif.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++-- libavformat/gif.c | 16 ++++- 3 files changed, 193 insertions(+), 6 deletions(-) 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; ppriv_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);