X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fgif.c;h=e125d386bd2d2dda93278f47167dc6439890d3d8;hb=a247ac640df3da573cd661065bf53f37863e2b46;hp=01d98a27b0535b1f3f859df1b5d7d3b91cd21d14;hpb=94d98330ed6c5562341315c26c1af92771a2e6de;p=ffmpeg diff --git a/libavformat/gif.c b/libavformat/gif.c index 01d98a27b05..e125d386bd2 100644 --- a/libavformat/gif.c +++ b/libavformat/gif.c @@ -27,94 +27,21 @@ #include "libavutil/imgutils.h" #include "libavutil/log.h" #include "libavutil/opt.h" - -/* XXX: random value that shouldn't be taken into effect if there is no - * transparent color in the palette (the transparency bit will be set to 0) */ -#define DEFAULT_TRANSPARENCY_INDEX 0x1f - -static int get_palette_transparency_index(const uint32_t *palette) -{ - int transparent_color_index = -1; - unsigned i, smallest_alpha = 0xff; - - if (!palette) - return -1; - - for (i = 0; i < AVPALETTE_COUNT; i++) { - const uint32_t v = palette[i]; - if (v >> 24 < smallest_alpha) { - smallest_alpha = v >> 24; - transparent_color_index = i; - } - } - return smallest_alpha < 128 ? transparent_color_index : -1; -} - -static int gif_image_write_header(AVIOContext *pb, AVStream *st, - int loop_count, uint32_t *palette) -{ - int i; - int64_t aspect = 0; - const AVRational sar = st->sample_aspect_ratio; - - if (sar.num > 0 && sar.den > 0) { - aspect = sar.num * 64LL / sar.den - 15; - if (aspect < 0 || aspect > 255) - aspect = 0; - } - - avio_write(pb, "GIF", 3); - avio_write(pb, "89a", 3); - avio_wl16(pb, st->codecpar->width); - avio_wl16(pb, st->codecpar->height); - - if (palette) { - const int bcid = get_palette_transparency_index(palette); - - avio_w8(pb, 0xf7); /* flags: global clut, 256 entries */ - avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid); /* background color index */ - avio_w8(pb, aspect); - for (i = 0; i < 256; i++) { - const uint32_t v = palette[i] & 0xffffff; - avio_wb24(pb, v); - } - } else { - avio_w8(pb, 0); /* flags */ - avio_w8(pb, 0); /* background color index */ - avio_w8(pb, aspect); - } - - - if (loop_count >= 0 ) { - /* "NETSCAPE EXTENSION" for looped animation GIF */ - avio_w8(pb, 0x21); /* GIF Extension code */ - avio_w8(pb, 0xff); /* Application Extension Label */ - avio_w8(pb, 0x0b); /* Length of Application Block */ - avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); - avio_w8(pb, 0x03); /* Length of Data Sub-Block */ - avio_w8(pb, 0x01); - avio_wl16(pb, (uint16_t)loop_count); - avio_w8(pb, 0x00); /* Data Sub-block Terminator */ - } - - avio_flush(pb); - return 0; -} +#include "libavcodec/bytestream.h" +#include "libavcodec/gif.h" typedef struct GIFContext { AVClass *class; int loop; int last_delay; - AVPacket *prev_pkt; int duration; + int64_t last_pos; + int have_end; + AVPacket *prev_pkt; } GIFContext; static int gif_write_header(AVFormatContext *s) { - GIFContext *gif = s->priv_data; - AVCodecParameters *video_par; - uint32_t palette[AVPALETTE_COUNT]; - if (s->nb_streams != 1 || s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO || s->streams[0]->codecpar->codec_id != AV_CODEC_ID_GIF) { @@ -123,92 +50,123 @@ static int gif_write_header(AVFormatContext *s) return AVERROR(EINVAL); } - video_par = s->streams[0]->codecpar; - avpriv_set_pts_info(s->streams[0], 64, 1, 100); - if (avpriv_set_systematic_pal2(palette, video_par->format) < 0) { - av_assert0(video_par->format == AV_PIX_FMT_PAL8); - /* delay header writing: we wait for the first palette to put it - * globally */ - } else { - gif_image_write_header(s->pb, s->streams[0], gif->loop, palette); - } return 0; } -static int flush_packet(AVFormatContext *s, AVPacket *new) +static int gif_parse_packet(AVFormatContext *s, uint8_t *data, int size) { - GIFContext *gif = s->priv_data; - int size, bcid; - AVIOContext *pb = s->pb; - const uint32_t *palette; - AVPacket *pkt = gif->prev_pkt; - - if (!pkt) - return 0; + GetByteContext gb; + int x; + + bytestream2_init(&gb, data, size); + + while (bytestream2_get_bytes_left(&gb) > 0) { + x = bytestream2_get_byte(&gb); + if (x != GIF_EXTENSION_INTRODUCER) + return 0; + + x = bytestream2_get_byte(&gb); + while (x != GIF_GCE_EXT_LABEL && bytestream2_get_bytes_left(&gb) > 0) { + int block_size = bytestream2_get_byte(&gb); + if (!block_size) + break; + bytestream2_skip(&gb, block_size); + } - /* Mark one colour as transparent if the input palette contains at least - * one colour that is more than 50% transparent. */ - palette = (uint32_t*)av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); - if (palette && size != AVPALETTE_SIZE) { - av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); - return AVERROR_INVALIDDATA; + if (x == GIF_GCE_EXT_LABEL) + return bytestream2_tell(&gb) + 2; } - bcid = get_palette_transparency_index(palette); + return 0; +} + +static int gif_get_delay(GIFContext *gif, AVPacket *prev, AVPacket *new) +{ if (new && new->pts != AV_NOPTS_VALUE) - gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); + gif->duration = av_clip_uint16(new->pts - prev->pts); else if (!new && gif->last_delay >= 0) gif->duration = gif->last_delay; - /* graphic control extension block */ - avio_w8(pb, 0x21); - avio_w8(pb, 0xf9); - avio_w8(pb, 0x04); /* block size */ - avio_w8(pb, 1<<2 | (bcid >= 0)); - avio_wl16(pb, gif->duration); - avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid); - avio_w8(pb, 0x00); - - avio_write(pb, pkt->data, pkt->size); - - av_packet_unref(gif->prev_pkt); - if (new) - av_packet_ref(gif->prev_pkt, new); - - return 0; + return gif->duration; } -static int gif_write_packet(AVFormatContext *s, AVPacket *pkt) +static int gif_write_packet(AVFormatContext *s, AVPacket *new_pkt) { GIFContext *gif = s->priv_data; - AVStream *video_st = s->streams[0]; + AVIOContext *pb = s->pb; + AVPacket *pkt = gif->prev_pkt; if (!gif->prev_pkt) { gif->prev_pkt = av_packet_alloc(); if (!gif->prev_pkt) return AVERROR(ENOMEM); + return av_packet_ref(gif->prev_pkt, new_pkt); + } + + gif->last_pos = avio_tell(pb); + if (pkt->size > 0) + gif->have_end = pkt->data[pkt->size - 1] == GIF_TRAILER; + + if (!gif->last_pos) { + int delay_pos; + int off = 13; + + if (pkt->size < 13) + return AVERROR(EINVAL); - /* Write the first palette as global palette */ - if (video_st->codecpar->format == AV_PIX_FMT_PAL8) { - int size; - void *palette = av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); - - if (!palette) { - av_log(s, AV_LOG_ERROR, "PAL8 packet is missing palette in extradata\n"); - return AVERROR_INVALIDDATA; - } - if (size != AVPALETTE_SIZE) { - av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); - return AVERROR_INVALIDDATA; - } - gif_image_write_header(s->pb, video_st, gif->loop, palette); + if (pkt->data[10] & 0x80) + off += 3 * (1 << ((pkt->data[10] & 0x07) + 1)); + + if (pkt->size < off + 2) + return AVERROR(EINVAL); + + avio_write(pb, pkt->data, off); + + if (pkt->data[off] == GIF_EXTENSION_INTRODUCER && pkt->data[off + 1] == 0xff) + off += 19; + + if (pkt->size <= off) + return AVERROR(EINVAL); + + /* "NETSCAPE EXTENSION" for looped animation GIF */ + if (gif->loop >= 0) { + avio_w8(pb, GIF_EXTENSION_INTRODUCER); /* GIF Extension code */ + avio_w8(pb, GIF_APP_EXT_LABEL); /* Application Extension Label */ + avio_w8(pb, 0x0b); /* Length of Application Block */ + avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); + avio_w8(pb, 0x03); /* Length of Data Sub-Block */ + avio_w8(pb, 0x01); + avio_wl16(pb, (uint16_t)gif->loop); + avio_w8(pb, 0x00); /* Data Sub-block Terminator */ } - return av_packet_ref(gif->prev_pkt, pkt); + delay_pos = gif_parse_packet(s, pkt->data + off, pkt->size - off); + if (delay_pos > 0 && delay_pos < pkt->size - off - 2) { + avio_write(pb, pkt->data + off, delay_pos); + avio_wl16(pb, gif_get_delay(gif, pkt, new_pkt)); + avio_write(pb, pkt->data + off + delay_pos + 2, pkt->size - off - delay_pos - 2); + } else { + avio_write(pb, pkt->data + off, pkt->size - off); + } + } else { + int delay_pos = gif_parse_packet(s, pkt->data, pkt->size); + + if (delay_pos > 0 && delay_pos < pkt->size - 2) { + avio_write(pb, pkt->data, delay_pos); + avio_wl16(pb, gif_get_delay(gif, pkt, new_pkt)); + avio_write(pb, pkt->data + delay_pos + 2, pkt->size - delay_pos - 2); + } else { + avio_write(pb, pkt->data, pkt->size); + } } - return flush_packet(s, pkt); + + av_packet_unref(gif->prev_pkt); + if (new_pkt) + return av_packet_ref(gif->prev_pkt, new_pkt); + + return 0; } static int gif_write_trailer(AVFormatContext *s) @@ -216,9 +174,14 @@ static int gif_write_trailer(AVFormatContext *s) GIFContext *gif = s->priv_data; AVIOContext *pb = s->pb; - flush_packet(s, NULL); - av_freep(&gif->prev_pkt); - avio_w8(pb, 0x3b); + if (!gif->prev_pkt) + return AVERROR(EINVAL); + + gif_write_packet(s, NULL); + + if (!gif->have_end) + avio_w8(pb, GIF_TRAILER); + av_packet_free(&gif->prev_pkt); return 0; } @@ -242,7 +205,7 @@ static const AVClass gif_muxer_class = { AVOutputFormat ff_gif_muxer = { .name = "gif", - .long_name = NULL_IF_CONFIG_SMALL("GIF Animation"), + .long_name = NULL_IF_CONFIG_SMALL("CompuServe Graphics Interchange Format (GIF)"), .mime_type = "image/gif", .extensions = "gif", .priv_data_size = sizeof(GIFContext),