Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* |
2 | * GIF decoder |
||
3 | * Copyright (c) 2003 Fabrice Bellard |
||
4 | * Copyright (c) 2006 Baptiste Coudurier |
||
5 | * Copyright (c) 2012 Vitaliy E Sugrobov |
||
6 | * |
||
7 | * This file is part of FFmpeg. |
||
8 | * |
||
9 | * FFmpeg is free software; you can redistribute it and/or |
||
10 | * modify it under the terms of the GNU Lesser General Public |
||
11 | * License as published by the Free Software Foundation; either |
||
12 | * version 2.1 of the License, or (at your option) any later version. |
||
13 | * |
||
14 | * FFmpeg is distributed in the hope that it will be useful, |
||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
17 | * Lesser General Public License for more details. |
||
18 | * |
||
19 | * You should have received a copy of the GNU Lesser General Public |
||
20 | * License along with FFmpeg; if not, write to the Free Software |
||
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
22 | */ |
||
23 | |||
24 | #include "libavutil/imgutils.h" |
||
25 | #include "libavutil/opt.h" |
||
26 | #include "avcodec.h" |
||
27 | #include "bytestream.h" |
||
28 | #include "internal.h" |
||
29 | #include "lzw.h" |
||
30 | #include "gif.h" |
||
31 | |||
32 | /* This value is intentionally set to "transparent white" color. |
||
33 | * It is much better to have white background instead of black |
||
34 | * when gif image converted to format which not support transparency. |
||
35 | */ |
||
36 | #define GIF_TRANSPARENT_COLOR 0x00ffffff |
||
37 | |||
38 | typedef struct GifState { |
||
39 | const AVClass *class; |
||
40 | AVFrame *frame; |
||
41 | int screen_width; |
||
42 | int screen_height; |
||
43 | int has_global_palette; |
||
44 | int bits_per_pixel; |
||
45 | uint32_t bg_color; |
||
46 | int background_color_index; |
||
47 | int transparent_color_index; |
||
48 | int color_resolution; |
||
49 | /* intermediate buffer for storing color indices |
||
50 | * obtained from lzw-encoded data stream */ |
||
51 | uint8_t *idx_line; |
||
52 | int idx_line_size; |
||
53 | |||
54 | /* after the frame is displayed, the disposal method is used */ |
||
55 | int gce_prev_disposal; |
||
56 | int gce_disposal; |
||
57 | /* rectangle describing area that must be disposed */ |
||
58 | int gce_l, gce_t, gce_w, gce_h; |
||
59 | /* depending on disposal method we store either part of the image |
||
60 | * drawn on the canvas or background color that |
||
61 | * should be used upon disposal */ |
||
62 | uint32_t * stored_img; |
||
63 | int stored_img_size; |
||
64 | int stored_bg_color; |
||
65 | |||
66 | GetByteContext gb; |
||
67 | /* LZW compatible decoder */ |
||
68 | LZWState *lzw; |
||
69 | |||
70 | /* aux buffers */ |
||
71 | uint32_t global_palette[256]; |
||
72 | uint32_t local_palette[256]; |
||
73 | |||
74 | AVCodecContext *avctx; |
||
75 | int keyframe; |
||
76 | int keyframe_ok; |
||
77 | int trans_color; /**< color value that is used instead of transparent color */ |
||
78 | } GifState; |
||
79 | |||
80 | static void gif_read_palette(GifState *s, uint32_t *pal, int nb) |
||
81 | { |
||
82 | int i; |
||
83 | |||
84 | for (i = 0; i < nb; i++, pal++) |
||
85 | *pal = (0xffu << 24) | bytestream2_get_be24u(&s->gb); |
||
86 | } |
||
87 | |||
88 | static void gif_fill(AVFrame *picture, uint32_t color) |
||
89 | { |
||
90 | uint32_t *p = (uint32_t *)picture->data[0]; |
||
91 | uint32_t *p_end = p + (picture->linesize[0] / sizeof(uint32_t)) * picture->height; |
||
92 | |||
93 | for (; p < p_end; p++) |
||
94 | *p = color; |
||
95 | } |
||
96 | |||
97 | static void gif_fill_rect(AVFrame *picture, uint32_t color, int l, int t, int w, int h) |
||
98 | { |
||
99 | const int linesize = picture->linesize[0] / sizeof(uint32_t); |
||
100 | const uint32_t *py = (uint32_t *)picture->data[0] + t * linesize; |
||
101 | const uint32_t *pr, *pb = py + h * linesize; |
||
102 | uint32_t *px; |
||
103 | |||
104 | for (; py < pb; py += linesize) { |
||
105 | px = (uint32_t *)py + l; |
||
106 | pr = px + w; |
||
107 | |||
108 | for (; px < pr; px++) |
||
109 | *px = color; |
||
110 | } |
||
111 | } |
||
112 | |||
113 | static void gif_copy_img_rect(const uint32_t *src, uint32_t *dst, |
||
114 | int linesize, int l, int t, int w, int h) |
||
115 | { |
||
116 | const int y_start = t * linesize; |
||
117 | const uint32_t *src_px, |
||
118 | *src_py = src + y_start, |
||
119 | *dst_py = dst + y_start; |
||
120 | const uint32_t *src_pb = src_py + h * linesize; |
||
121 | uint32_t *dst_px; |
||
122 | |||
123 | for (; src_py < src_pb; src_py += linesize, dst_py += linesize) { |
||
124 | src_px = src_py + l; |
||
125 | dst_px = (uint32_t *)dst_py + l; |
||
126 | |||
127 | memcpy(dst_px, src_px, w * sizeof(uint32_t)); |
||
128 | } |
||
129 | } |
||
130 | |||
131 | static int gif_read_image(GifState *s, AVFrame *frame) |
||
132 | { |
||
133 | int left, top, width, height, bits_per_pixel, code_size, flags; |
||
134 | int is_interleaved, has_local_palette, y, pass, y1, linesize, pal_size; |
||
135 | uint32_t *ptr, *pal, *px, *pr, *ptr1; |
||
136 | int ret; |
||
137 | uint8_t *idx; |
||
138 | |||
139 | /* At least 9 bytes of Image Descriptor. */ |
||
140 | if (bytestream2_get_bytes_left(&s->gb) < 9) |
||
141 | return AVERROR_INVALIDDATA; |
||
142 | |||
143 | left = bytestream2_get_le16u(&s->gb); |
||
144 | top = bytestream2_get_le16u(&s->gb); |
||
145 | width = bytestream2_get_le16u(&s->gb); |
||
146 | height = bytestream2_get_le16u(&s->gb); |
||
147 | flags = bytestream2_get_byteu(&s->gb); |
||
148 | is_interleaved = flags & 0x40; |
||
149 | has_local_palette = flags & 0x80; |
||
150 | bits_per_pixel = (flags & 0x07) + 1; |
||
151 | |||
152 | av_dlog(s->avctx, "image x=%d y=%d w=%d h=%d\n", left, top, width, height); |
||
153 | |||
154 | if (has_local_palette) { |
||
155 | pal_size = 1 << bits_per_pixel; |
||
156 | |||
157 | if (bytestream2_get_bytes_left(&s->gb) < pal_size * 3) |
||
158 | return AVERROR_INVALIDDATA; |
||
159 | |||
160 | gif_read_palette(s, s->local_palette, pal_size); |
||
161 | pal = s->local_palette; |
||
162 | } else { |
||
163 | if (!s->has_global_palette) { |
||
164 | av_log(s->avctx, AV_LOG_ERROR, "picture doesn't have either global or local palette.\n"); |
||
165 | return AVERROR_INVALIDDATA; |
||
166 | } |
||
167 | |||
168 | pal = s->global_palette; |
||
169 | } |
||
170 | |||
171 | if (s->keyframe) { |
||
172 | if (s->transparent_color_index == -1 && s->has_global_palette) { |
||
173 | /* transparency wasn't set before the first frame, fill with background color */ |
||
174 | gif_fill(frame, s->bg_color); |
||
175 | } else { |
||
176 | /* otherwise fill with transparent color. |
||
177 | * this is necessary since by default picture filled with 0x80808080. */ |
||
178 | gif_fill(frame, s->trans_color); |
||
179 | } |
||
180 | } |
||
181 | |||
182 | /* verify that all the image is inside the screen dimensions */ |
||
183 | if (left + width > s->screen_width || |
||
184 | top + height > s->screen_height) |
||
185 | return AVERROR_INVALIDDATA; |
||
186 | if (width <= 0 || height <= 0) |
||
187 | return AVERROR_INVALIDDATA; |
||
188 | |||
189 | /* process disposal method */ |
||
190 | if (s->gce_prev_disposal == GCE_DISPOSAL_BACKGROUND) { |
||
191 | gif_fill_rect(frame, s->stored_bg_color, s->gce_l, s->gce_t, s->gce_w, s->gce_h); |
||
192 | } else if (s->gce_prev_disposal == GCE_DISPOSAL_RESTORE) { |
||
193 | gif_copy_img_rect(s->stored_img, (uint32_t *)frame->data[0], |
||
194 | frame->linesize[0] / sizeof(uint32_t), s->gce_l, s->gce_t, s->gce_w, s->gce_h); |
||
195 | } |
||
196 | |||
197 | s->gce_prev_disposal = s->gce_disposal; |
||
198 | |||
199 | if (s->gce_disposal != GCE_DISPOSAL_NONE) { |
||
200 | s->gce_l = left; s->gce_t = top; |
||
201 | s->gce_w = width; s->gce_h = height; |
||
202 | |||
203 | if (s->gce_disposal == GCE_DISPOSAL_BACKGROUND) { |
||
204 | if (s->transparent_color_index >= 0) |
||
205 | s->stored_bg_color = s->trans_color; |
||
206 | else |
||
207 | s->stored_bg_color = s->bg_color; |
||
208 | } else if (s->gce_disposal == GCE_DISPOSAL_RESTORE) { |
||
209 | av_fast_malloc(&s->stored_img, &s->stored_img_size, frame->linesize[0] * frame->height); |
||
210 | if (!s->stored_img) |
||
211 | return AVERROR(ENOMEM); |
||
212 | |||
213 | gif_copy_img_rect((uint32_t *)frame->data[0], s->stored_img, |
||
214 | frame->linesize[0] / sizeof(uint32_t), left, top, width, height); |
||
215 | } |
||
216 | } |
||
217 | |||
218 | /* Expect at least 2 bytes: 1 for lzw code size and 1 for block size. */ |
||
219 | if (bytestream2_get_bytes_left(&s->gb) < 2) |
||
220 | return AVERROR_INVALIDDATA; |
||
221 | |||
222 | /* now get the image data */ |
||
223 | code_size = bytestream2_get_byteu(&s->gb); |
||
224 | if ((ret = ff_lzw_decode_init(s->lzw, code_size, s->gb.buffer, |
||
225 | bytestream2_get_bytes_left(&s->gb), FF_LZW_GIF)) < 0) { |
||
226 | av_log(s->avctx, AV_LOG_ERROR, "LZW init failed\n"); |
||
227 | return ret; |
||
228 | } |
||
229 | |||
230 | /* read all the image */ |
||
231 | linesize = frame->linesize[0] / sizeof(uint32_t); |
||
232 | ptr1 = (uint32_t *)frame->data[0] + top * linesize + left; |
||
233 | ptr = ptr1; |
||
234 | pass = 0; |
||
235 | y1 = 0; |
||
236 | for (y = 0; y < height; y++) { |
||
237 | if (ff_lzw_decode(s->lzw, s->idx_line, width) == 0) |
||
238 | goto decode_tail; |
||
239 | |||
240 | pr = ptr + width; |
||
241 | |||
242 | for (px = ptr, idx = s->idx_line; px < pr; px++, idx++) { |
||
243 | if (*idx != s->transparent_color_index) |
||
244 | *px = pal[*idx]; |
||
245 | } |
||
246 | |||
247 | if (is_interleaved) { |
||
248 | switch(pass) { |
||
249 | default: |
||
250 | case 0: |
||
251 | case 1: |
||
252 | y1 += 8; |
||
253 | ptr += linesize * 8; |
||
254 | if (y1 >= height) { |
||
255 | y1 = pass ? 2 : 4; |
||
256 | ptr = ptr1 + linesize * y1; |
||
257 | pass++; |
||
258 | } |
||
259 | break; |
||
260 | case 2: |
||
261 | y1 += 4; |
||
262 | ptr += linesize * 4; |
||
263 | if (y1 >= height) { |
||
264 | y1 = 1; |
||
265 | ptr = ptr1 + linesize; |
||
266 | pass++; |
||
267 | } |
||
268 | break; |
||
269 | case 3: |
||
270 | y1 += 2; |
||
271 | ptr += linesize * 2; |
||
272 | break; |
||
273 | } |
||
274 | } else { |
||
275 | ptr += linesize; |
||
276 | } |
||
277 | } |
||
278 | |||
279 | decode_tail: |
||
280 | /* read the garbage data until end marker is found */ |
||
281 | ff_lzw_decode_tail(s->lzw); |
||
282 | |||
283 | /* Graphic Control Extension's scope is single frame. |
||
284 | * Remove its influence. */ |
||
285 | s->transparent_color_index = -1; |
||
286 | s->gce_disposal = GCE_DISPOSAL_NONE; |
||
287 | |||
288 | return 0; |
||
289 | } |
||
290 | |||
291 | static int gif_read_extension(GifState *s) |
||
292 | { |
||
293 | int ext_code, ext_len, gce_flags, gce_transparent_index; |
||
294 | |||
295 | /* There must be at least 2 bytes: |
||
296 | * 1 for extension label and 1 for extension length. */ |
||
297 | if (bytestream2_get_bytes_left(&s->gb) < 2) |
||
298 | return AVERROR_INVALIDDATA; |
||
299 | |||
300 | ext_code = bytestream2_get_byteu(&s->gb); |
||
301 | ext_len = bytestream2_get_byteu(&s->gb); |
||
302 | |||
303 | av_dlog(s->avctx, "ext_code=0x%x len=%d\n", ext_code, ext_len); |
||
304 | |||
305 | switch(ext_code) { |
||
306 | case GIF_GCE_EXT_LABEL: |
||
307 | if (ext_len != 4) |
||
308 | goto discard_ext; |
||
309 | |||
310 | /* We need at least 5 bytes more: 4 is for extension body |
||
311 | * and 1 for next block size. */ |
||
312 | if (bytestream2_get_bytes_left(&s->gb) < 5) |
||
313 | return AVERROR_INVALIDDATA; |
||
314 | |||
315 | gce_flags = bytestream2_get_byteu(&s->gb); |
||
316 | bytestream2_skipu(&s->gb, 2); // delay during which the frame is shown |
||
317 | gce_transparent_index = bytestream2_get_byteu(&s->gb); |
||
318 | if (gce_flags & 0x01) |
||
319 | s->transparent_color_index = gce_transparent_index; |
||
320 | else |
||
321 | s->transparent_color_index = -1; |
||
322 | s->gce_disposal = (gce_flags >> 2) & 0x7; |
||
323 | |||
324 | av_dlog(s->avctx, "gce_flags=%x tcolor=%d disposal=%d\n", |
||
325 | gce_flags, |
||
326 | s->transparent_color_index, s->gce_disposal); |
||
327 | |||
328 | if (s->gce_disposal > 3) { |
||
329 | s->gce_disposal = GCE_DISPOSAL_NONE; |
||
330 | av_dlog(s->avctx, "invalid value in gce_disposal (%d). Using default value of 0.\n", ext_len); |
||
331 | } |
||
332 | |||
333 | ext_len = bytestream2_get_byteu(&s->gb); |
||
334 | break; |
||
335 | } |
||
336 | |||
337 | /* NOTE: many extension blocks can come after */ |
||
338 | discard_ext: |
||
339 | while (ext_len) { |
||
340 | /* There must be at least ext_len bytes and 1 for next block size byte. */ |
||
341 | if (bytestream2_get_bytes_left(&s->gb) < ext_len + 1) |
||
342 | return AVERROR_INVALIDDATA; |
||
343 | |||
344 | bytestream2_skipu(&s->gb, ext_len); |
||
345 | ext_len = bytestream2_get_byteu(&s->gb); |
||
346 | |||
347 | av_dlog(s->avctx, "ext_len1=%d\n", ext_len); |
||
348 | } |
||
349 | return 0; |
||
350 | } |
||
351 | |||
352 | static int gif_read_header1(GifState *s) |
||
353 | { |
||
354 | uint8_t sig[6]; |
||
355 | int v, n; |
||
356 | int background_color_index; |
||
357 | |||
358 | if (bytestream2_get_bytes_left(&s->gb) < 13) |
||
359 | return AVERROR_INVALIDDATA; |
||
360 | |||
361 | /* read gif signature */ |
||
362 | bytestream2_get_bufferu(&s->gb, sig, 6); |
||
363 | if (memcmp(sig, gif87a_sig, 6) && |
||
364 | memcmp(sig, gif89a_sig, 6)) |
||
365 | return AVERROR_INVALIDDATA; |
||
366 | |||
367 | /* read screen header */ |
||
368 | s->transparent_color_index = -1; |
||
369 | s->screen_width = bytestream2_get_le16u(&s->gb); |
||
370 | s->screen_height = bytestream2_get_le16u(&s->gb); |
||
371 | |||
372 | v = bytestream2_get_byteu(&s->gb); |
||
373 | s->color_resolution = ((v & 0x70) >> 4) + 1; |
||
374 | s->has_global_palette = (v & 0x80); |
||
375 | s->bits_per_pixel = (v & 0x07) + 1; |
||
376 | background_color_index = bytestream2_get_byteu(&s->gb); |
||
377 | n = bytestream2_get_byteu(&s->gb); |
||
378 | if (n) { |
||
379 | s->avctx->sample_aspect_ratio.num = n + 15; |
||
380 | s->avctx->sample_aspect_ratio.den = 64; |
||
381 | } |
||
382 | |||
383 | av_dlog(s->avctx, "screen_w=%d screen_h=%d bpp=%d global_palette=%d\n", |
||
384 | s->screen_width, s->screen_height, s->bits_per_pixel, |
||
385 | s->has_global_palette); |
||
386 | |||
387 | if (s->has_global_palette) { |
||
388 | s->background_color_index = background_color_index; |
||
389 | n = 1 << s->bits_per_pixel; |
||
390 | if (bytestream2_get_bytes_left(&s->gb) < n * 3) |
||
391 | return AVERROR_INVALIDDATA; |
||
392 | |||
393 | gif_read_palette(s, s->global_palette, n); |
||
394 | s->bg_color = s->global_palette[s->background_color_index]; |
||
395 | } else |
||
396 | s->background_color_index = -1; |
||
397 | |||
398 | return 0; |
||
399 | } |
||
400 | |||
401 | static int gif_parse_next_image(GifState *s, AVFrame *frame) |
||
402 | { |
||
403 | while (bytestream2_get_bytes_left(&s->gb)) { |
||
404 | int code = bytestream2_get_byte(&s->gb); |
||
405 | int ret; |
||
406 | |||
407 | av_log(s->avctx, AV_LOG_DEBUG, "code=%02x '%c'\n", code, code); |
||
408 | |||
409 | switch (code) { |
||
410 | case GIF_IMAGE_SEPARATOR: |
||
411 | return gif_read_image(s, frame); |
||
412 | case GIF_EXTENSION_INTRODUCER: |
||
413 | if ((ret = gif_read_extension(s)) < 0) |
||
414 | return ret; |
||
415 | break; |
||
416 | case GIF_TRAILER: |
||
417 | /* end of image */ |
||
418 | return AVERROR_EOF; |
||
419 | default: |
||
420 | /* erroneous block label */ |
||
421 | return AVERROR_INVALIDDATA; |
||
422 | } |
||
423 | } |
||
424 | return AVERROR_EOF; |
||
425 | } |
||
426 | |||
427 | static av_cold int gif_decode_init(AVCodecContext *avctx) |
||
428 | { |
||
429 | GifState *s = avctx->priv_data; |
||
430 | |||
431 | s->avctx = avctx; |
||
432 | |||
433 | avctx->pix_fmt = AV_PIX_FMT_RGB32; |
||
434 | s->frame = av_frame_alloc(); |
||
435 | if (!s->frame) |
||
436 | return AVERROR(ENOMEM); |
||
437 | ff_lzw_decode_open(&s->lzw); |
||
438 | return 0; |
||
439 | } |
||
440 | |||
441 | static int gif_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt) |
||
442 | { |
||
443 | GifState *s = avctx->priv_data; |
||
444 | int ret; |
||
445 | |||
446 | bytestream2_init(&s->gb, avpkt->data, avpkt->size); |
||
447 | |||
448 | s->frame->pts = avpkt->pts; |
||
449 | s->frame->pkt_pts = avpkt->pts; |
||
450 | s->frame->pkt_dts = avpkt->dts; |
||
451 | av_frame_set_pkt_duration(s->frame, avpkt->duration); |
||
452 | |||
453 | if (avpkt->size >= 6) { |
||
454 | s->keyframe = memcmp(avpkt->data, gif87a_sig, 6) == 0 || |
||
455 | memcmp(avpkt->data, gif89a_sig, 6) == 0; |
||
456 | } else { |
||
457 | s->keyframe = 0; |
||
458 | } |
||
459 | |||
460 | if (s->keyframe) { |
||
461 | s->keyframe_ok = 0; |
||
462 | s->gce_prev_disposal = GCE_DISPOSAL_NONE; |
||
463 | if ((ret = gif_read_header1(s)) < 0) |
||
464 | return ret; |
||
465 | |||
466 | if ((ret = av_image_check_size(s->screen_width, s->screen_height, 0, avctx)) < 0) |
||
467 | return ret; |
||
468 | avcodec_set_dimensions(avctx, s->screen_width, s->screen_height); |
||
469 | |||
470 | av_frame_unref(s->frame); |
||
471 | if ((ret = ff_get_buffer(avctx, s->frame, 0)) < 0) |
||
472 | return ret; |
||
473 | |||
474 | av_fast_malloc(&s->idx_line, &s->idx_line_size, s->screen_width); |
||
475 | if (!s->idx_line) |
||
476 | return AVERROR(ENOMEM); |
||
477 | |||
478 | s->frame->pict_type = AV_PICTURE_TYPE_I; |
||
479 | s->frame->key_frame = 1; |
||
480 | s->keyframe_ok = 1; |
||
481 | } else { |
||
482 | if (!s->keyframe_ok) { |
||
483 | av_log(avctx, AV_LOG_ERROR, "cannot decode frame without keyframe\n"); |
||
484 | return AVERROR_INVALIDDATA; |
||
485 | } |
||
486 | |||
487 | if ((ret = ff_reget_buffer(avctx, s->frame)) < 0) |
||
488 | return ret; |
||
489 | |||
490 | s->frame->pict_type = AV_PICTURE_TYPE_P; |
||
491 | s->frame->key_frame = 0; |
||
492 | } |
||
493 | |||
494 | ret = gif_parse_next_image(s, s->frame); |
||
495 | if (ret < 0) |
||
496 | return ret; |
||
497 | |||
498 | if ((ret = av_frame_ref(data, s->frame)) < 0) |
||
499 | return ret; |
||
500 | *got_frame = 1; |
||
501 | |||
502 | return avpkt->size; |
||
503 | } |
||
504 | |||
505 | static av_cold int gif_decode_close(AVCodecContext *avctx) |
||
506 | { |
||
507 | GifState *s = avctx->priv_data; |
||
508 | |||
509 | ff_lzw_decode_close(&s->lzw); |
||
510 | av_frame_free(&s->frame); |
||
511 | av_freep(&s->idx_line); |
||
512 | av_freep(&s->stored_img); |
||
513 | |||
514 | return 0; |
||
515 | } |
||
516 | |||
517 | static const AVOption options[] = { |
||
518 | { "trans_color", "color value (ARGB) that is used instead of transparent color", |
||
519 | offsetof(GifState, trans_color), AV_OPT_TYPE_INT, |
||
520 | {.i64 = GIF_TRANSPARENT_COLOR}, 0, 0xffffffff, |
||
521 | AV_OPT_FLAG_DECODING_PARAM|AV_OPT_FLAG_VIDEO_PARAM }, |
||
522 | { NULL }, |
||
523 | }; |
||
524 | |||
525 | static const AVClass decoder_class = { |
||
526 | .class_name = "gif decoder", |
||
527 | .item_name = av_default_item_name, |
||
528 | .option = options, |
||
529 | .version = LIBAVUTIL_VERSION_INT, |
||
530 | .category = AV_CLASS_CATEGORY_DECODER, |
||
531 | }; |
||
532 | |||
533 | AVCodec ff_gif_decoder = { |
||
534 | .name = "gif", |
||
535 | .long_name = NULL_IF_CONFIG_SMALL("GIF (Graphics Interchange Format)"), |
||
536 | .type = AVMEDIA_TYPE_VIDEO, |
||
537 | .id = AV_CODEC_ID_GIF, |
||
538 | .priv_data_size = sizeof(GifState), |
||
539 | .init = gif_decode_init, |
||
540 | .close = gif_decode_close, |
||
541 | .decode = gif_decode_frame, |
||
542 | .capabilities = CODEC_CAP_DR1, |
||
543 | .priv_class = &decoder_class, |
||
544 | };>>>>>>>>><>>>>>>>>>=>=>>><>>>>>>><>>> |