Skip to main content

otf_pixels_codec_gif/
decoder.rs

1//! The GIF decoder.
2//!
3//! # Canvas, not image
4//!
5//! A GIF frame is not the image: it is a rectangle drawn onto a persistent
6//! canvas, whose size comes from the logical screen descriptor. A frame can be
7//! smaller than the canvas, offset within it, and partially transparent, and
8//! the canvas retains whatever earlier frames left behind — subject to the
9//! disposal method each declared.
10//!
11//! Getting that wrong produces images that look right on the first frame and
12//! accumulate garbage after it, which is why disposal is modelled explicitly
13//! rather than being treated as "clear between frames".
14//!
15//! # Memory
16//!
17//! GIF decode is **internally buffered**, at one canvas. Frames are
18//! LZW-compressed as a unit and composited onto shared state, so there is no
19//! row at which the canvas is final until the frame is complete. SPEC
20//! §Formats says "yes (per frame)", and this is what that means: the canvas is
21//! bounded by the image, not by the number of frames, and an animation of a
22//! thousand frames costs the same as one.
23
24use otf_pixels_compress::LzwDecoder;
25use otf_pixels_core::{
26    Animation, Codec, DecodeCapability, Decoder, Format, ImageDescriptor as CoreDescriptor, Limits,
27    PixelFormat, PixelsError, Result, Source,
28};
29
30use crate::format::{
31    Disposal, GraphicControl, ImageDescriptor, SIGNATURE_87A, SIGNATURE_89A, Screen,
32    interlaced_pass_rows, interlaced_row, label, read_sub_blocks, skip_sub_blocks,
33};
34
35/// The largest sub-block chain accepted for one frame's pixel data.
36///
37/// LZW output is bounded separately by the frame's pixel count; this bounds
38/// the *compressed* side, which a length-prefixed chain does not bound itself.
39const MAX_COMPRESSED: usize = 64 * 1024 * 1024;
40
41/// The largest GIF stream accepted. The stream is buffered to count its
42/// frames, so this bounds that buffer; a real GIF this size is a very long
43/// animation, and anything larger is refused rather than read into memory.
44const MAX_STREAM: usize = 512 * 1024 * 1024;
45
46/// The largest extension payload retained. Comments and application blocks are
47/// skipped rather than kept, so this only bounds the ones we read.
48const MAX_EXTENSION: usize = 4096;
49
50/// One decoded frame, with the animation metadata that came with it.
51#[derive(Debug, Clone)]
52#[non_exhaustive]
53pub struct Frame {
54    /// The whole canvas after this frame is composited, as RGBA8.
55    pub pixels: Vec<u8>,
56    /// Canvas width.
57    pub width: u32,
58    /// Canvas height.
59    pub height: u32,
60    /// Delay before the next frame, in hundredths of a second.
61    pub delay_centiseconds: u16,
62    /// How this frame is disposed of before the next is drawn.
63    pub disposal: Disposal,
64}
65
66/// Decodes a GIF stream.
67///
68/// [`Decoder`] presents the first frame; [`GifDecoder::next_frame`] walks the
69/// rest. See the crate docs for why the split is where it is.
70#[derive(Debug)]
71pub struct GifDecoder<S: Source> {
72    descriptor: CoreDescriptor,
73    screen: Screen,
74    /// The stream after the global colour table, read whole at construction
75    /// so its frames can be counted before the first is decoded.
76    source: Option<Bytes>,
77    /// Frame count and timing, when there is more than one frame.
78    animation: Option<Animation>,
79    /// The caller's source type, consumed in [`GifDecoder::new`].
80    _source: std::marker::PhantomData<fn() -> S>,
81    /// The global colour table, if the stream carries one.
82    global: Vec<[u8; 3]>,
83    /// The canvas, RGBA8, persisting across frames.
84    canvas: Vec<u8>,
85    /// Whether the first frame has been composited yet.
86    started: bool,
87    /// Rows of the first frame already served through [`Decoder::read_row`].
88    row: u32,
89    /// Set when the trailer is reached, so `next_frame` stops.
90    finished: bool,
91    /// Pending disposal from the frame just drawn.
92    pending: Option<Pending>,
93    /// The RGBA the canvas reverts to under `Disposal::Background`.
94    background: [u8; 4],
95}
96
97/// What the frame just drawn asked to happen before the next one.
98#[derive(Debug)]
99struct Pending {
100    disposal: Disposal,
101    area: ImageDescriptor,
102    /// The rectangle as it was before the frame was drawn, for `Previous`.
103    saved: Vec<u8>,
104    /// Whether that frame declared a transparent index, which changes what
105    /// `Background` means.
106    had_transparency: bool,
107}
108
109impl<S: Source> GifDecoder<S> {
110    /// Parse the header and logical screen descriptor, reading nothing more.
111    ///
112    /// # Errors
113    ///
114    /// Returns [`PixelsError::Malformed`] for a bad signature or screen
115    /// descriptor, or [`PixelsError::LimitExceeded`] if the canvas exceeds
116    /// `limits`.
117    pub fn new(mut source: S, limits: Limits) -> Result<Self> {
118        let mut header = [0_u8; 13];
119        source.read_exact(&mut header)?;
120
121        let signature = header.get(..6).unwrap_or(&[]);
122        if signature != SIGNATURE_87A && signature != SIGNATURE_89A {
123            return Err(PixelsError::malformed(
124                "gif",
125                "signature is neither GIF87a nor GIF89a",
126            ));
127        }
128        let mut screen_bytes = [0_u8; 7];
129        screen_bytes.copy_from_slice(header.get(6..13).unwrap_or(&[0; 7]));
130        let screen = Screen::parse(&screen_bytes)?;
131
132        let width = u32::from(screen.width);
133        let height = u32::from(screen.height);
134        // Enforced before any buffer exists (SPEC §Safety).
135        let descriptor = CoreDescriptor::with_limits(width, height, PixelFormat::Rgba8, &limits)?;
136
137        let mut global = Vec::new();
138        if screen.global_table_size > 0 {
139            global = read_table(&mut source, screen.global_table_size)?;
140        }
141
142        // The rest of the stream, buffered: a GIF's compressed data is small
143        // beside its RGBA canvas, and counting frames means reading past the
144        // first, which a forward-only source cannot give back.
145        let mut rest = Vec::new();
146        let mut chunk = [0_u8; 64 * 1024];
147        loop {
148            match source.read(&mut chunk)? {
149                0 => break,
150                n => rest.extend_from_slice(chunk.get(..n).unwrap_or_default()),
151            }
152            if rest.len() > MAX_STREAM {
153                return Err(PixelsError::unsupported(format!(
154                    "gif: streams over {} MiB are not read",
155                    MAX_STREAM >> 20
156                )));
157            }
158        }
159        let animation = scan_animation(&rest);
160
161        let canvas_len = descriptor
162            .byte_len()
163            .ok_or_else(|| PixelsError::malformed("gif", "canvas size overflows"))?;
164
165        // "Restore to background colour" means the entry the logical screen
166        // descriptor names, opaque. A stream with no global table has no
167        // background colour to restore to, so transparent is the only
168        // available answer.
169        let background = global
170            .get(screen.background as usize)
171            .map_or([0, 0, 0, 0], |c| [c[0], c[1], c[2], 255]);
172
173        Ok(Self {
174            descriptor,
175            screen,
176            source: Some(std::io::Cursor::new(rest)),
177            animation,
178            _source: std::marker::PhantomData,
179            global,
180            // A GIF canvas begins fully transparent, which is what makes a
181            // first frame smaller than the canvas render correctly.
182            canvas: vec![0_u8; canvas_len],
183            started: false,
184            row: 0,
185            finished: false,
186            pending: None,
187            background,
188        })
189    }
190
191    /// The logical screen descriptor.
192    #[must_use]
193    pub const fn screen(&self) -> Screen {
194        self.screen
195    }
196
197    /// Decode the next frame, compositing it onto the canvas.
198    ///
199    /// Returns `None` once the stream's trailer is reached. Each frame carries
200    /// the whole canvas, because that is what a viewer draws — a frame's own
201    /// rectangle is meaningless without what it was composited onto.
202    ///
203    /// # Errors
204    ///
205    /// Returns [`PixelsError::Malformed`] for a malformed block or a frame
206    /// that does not fit its canvas, or [`PixelsError::Io`] on source failure.
207    pub fn next_frame(&mut self) -> Result<Option<Frame>> {
208        if self.finished {
209            return Ok(None);
210        }
211        let Some(mut source) = self.source.take() else {
212            return Ok(None);
213        };
214        let result = self.decode_next(&mut source);
215        self.source = Some(source);
216        result
217    }
218
219    /// The body of [`GifDecoder::next_frame`], with the source borrowed out.
220    fn decode_next(&mut self, source: &mut Bytes) -> Result<Option<Frame>> {
221        // Apply the previous frame's disposal before drawing this one. It
222        // happens here rather than after drawing because `Previous` needs the
223        // saved rectangle, and saving it is only worth doing if a later frame
224        // actually arrives.
225        self.apply_disposal();
226
227        let mut control = GraphicControl::default();
228        loop {
229            let mut marker = [0_u8; 1];
230            source.read_exact(&mut marker)?;
231            match marker[0] {
232                label::TRAILER => {
233                    self.finished = true;
234                    return Ok(None);
235                }
236                label::EXTENSION => {
237                    let mut kind = [0_u8; 1];
238                    source.read_exact(&mut kind)?;
239                    match kind[0] {
240                        label::GRAPHIC_CONTROL => {
241                            let payload = read_sub_blocks(source, MAX_EXTENSION)?;
242                            control = GraphicControl::parse(&payload);
243                        }
244                        // Comments, plain text and application blocks carry no
245                        // pixels. Skipping them is required, not optional: an
246                        // unknown extension must not be an error (§Appendix A).
247                        _ => skip_sub_blocks(source)?,
248                    }
249                }
250                label::IMAGE => {
251                    let frame = self.decode_image(source, control)?;
252                    return Ok(Some(frame));
253                }
254                other => {
255                    return Err(PixelsError::malformed(
256                        "gif",
257                        format!("unknown block label {other:#04x}"),
258                    ));
259                }
260            }
261        }
262    }
263
264    /// Undo the previous frame according to its declared disposal.
265    fn apply_disposal(&mut self) {
266        let Some(pending) = self.pending.take() else {
267            return;
268        };
269        match pending.disposal {
270            // `None` is "unspecified", which viewers treat as leaving the
271            // frame in place. Clearing here would break the overwhelmingly
272            // common case of an optimised animation drawing only what changed.
273            Disposal::None | Disposal::Keep => {}
274            Disposal::Background => {
275                // §23.c.iv says "restore to background colour", and libgif
276                // does exactly that. Browsers instead restore to transparent,
277                // which is what an animation with a transparent index is
278                // authored against — so the frame's own transparency decides
279                // which reading applies. Picking one unconditionally makes
280                // one large class of real animations render wrongly.
281                let fill = if pending.had_transparency {
282                    [0, 0, 0, 0]
283                } else {
284                    self.background
285                };
286                self.fill_area(pending.area, &fill);
287            }
288            Disposal::Previous => {
289                self.restore_area(pending.area, &pending.saved);
290            }
291        }
292    }
293
294    /// Fill a frame's rectangle with one RGBA value.
295    fn fill_area(&mut self, area: ImageDescriptor, value: &[u8; 4]) {
296        let width = self.descriptor.width;
297        let height = self.descriptor.height;
298        for y in u32::from(area.top)..u32::from(area.top) + u32::from(area.height) {
299            if y >= height {
300                break;
301            }
302            for x in u32::from(area.left)..u32::from(area.left) + u32::from(area.width) {
303                if x >= width {
304                    break;
305                }
306                let at = ((y * width + x) * 4) as usize;
307                if let Some(slot) = self.canvas.get_mut(at..at + 4) {
308                    slot.copy_from_slice(value);
309                }
310            }
311        }
312    }
313
314    /// Restore a frame's rectangle from a saved copy.
315    fn restore_area(&mut self, area: ImageDescriptor, saved: &[u8]) {
316        let width = self.descriptor.width;
317        let height = self.descriptor.height;
318        let area_width = u32::from(area.width) as usize;
319        for row in 0..u32::from(area.height) {
320            let y = u32::from(area.top) + row;
321            if y >= height {
322                break;
323            }
324            for column in 0..u32::from(area.width) {
325                let x = u32::from(area.left) + column;
326                if x >= width {
327                    break;
328                }
329                let from = ((row as usize * area_width) + column as usize) * 4;
330                let to = ((y * width + x) * 4) as usize;
331                let (Some(source), Some(target)) =
332                    (saved.get(from..from + 4), self.canvas.get_mut(to..to + 4))
333                else {
334                    continue;
335                };
336                target.copy_from_slice(source);
337            }
338        }
339    }
340
341    /// Decode one image block and composite it onto the canvas.
342    fn decode_image(&mut self, source: &mut Bytes, control: GraphicControl) -> Result<Frame> {
343        let mut bytes = [0_u8; 9];
344        source.read_exact(&mut bytes)?;
345        let image = ImageDescriptor::parse(&bytes)?;
346
347        // A frame must lie within the canvas. Some encoders emit frames that
348        // overhang, and viewers clip; rejecting would fail files that display
349        // fine, so the compositing loops clip instead.
350        let local = if image.local_table_size > 0 {
351            read_table(source, image.local_table_size)?
352        } else {
353            Vec::new()
354        };
355        // Cloned rather than borrowed: compositing takes `&mut self`, and a
356        // 256-entry table is 768 bytes once per frame.
357        let palette: Vec<[u8; 3]> = if local.is_empty() {
358            self.global.clone()
359        } else {
360            local
361        };
362        if palette.is_empty() {
363            return Err(PixelsError::malformed(
364                "gif",
365                "frame has neither a local nor a global colour table",
366            ));
367        }
368
369        let mut minimum_width = [0_u8; 1];
370        source.read_exact(&mut minimum_width)?;
371        let compressed = read_sub_blocks(source, MAX_COMPRESSED)?;
372
373        let pixels = u32::from(image.width) as usize * u32::from(image.height) as usize;
374        let decoder =
375            LzwDecoder::gif(u32::from(minimum_width[0])).map_err(crate::compress_error)?;
376        // The limit is the frame's exact pixel count, which is what makes an
377        // LZW bomb a malformed-input error rather than an allocation.
378        let indices = decoder
379            .decode(&compressed, pixels)
380            .map_err(crate::compress_error)?;
381
382        // Save the rectangle *before* drawing, for a later `Previous`.
383        let saved = if control.disposal == Disposal::Previous {
384            self.save_area(image)
385        } else {
386            Vec::new()
387        };
388
389        self.composite(image, &indices, &palette, control.transparent);
390        self.pending = Some(Pending {
391            disposal: control.disposal,
392            area: image,
393            saved,
394            had_transparency: control.transparent.is_some(),
395        });
396        self.started = true;
397
398        Ok(Frame {
399            pixels: self.canvas.clone(),
400            width: self.descriptor.width,
401            height: self.descriptor.height,
402            delay_centiseconds: control.delay_centiseconds,
403            disposal: control.disposal,
404        })
405    }
406
407    /// Copy a rectangle of the canvas, for `Disposal::Previous`.
408    fn save_area(&self, area: ImageDescriptor) -> Vec<u8> {
409        let width = self.descriptor.width;
410        let height = self.descriptor.height;
411        let mut out =
412            vec![0_u8; u32::from(area.width) as usize * u32::from(area.height) as usize * 4];
413        let area_width = u32::from(area.width) as usize;
414        for row in 0..u32::from(area.height) {
415            let y = u32::from(area.top) + row;
416            if y >= height {
417                break;
418            }
419            for column in 0..u32::from(area.width) {
420                let x = u32::from(area.left) + column;
421                if x >= width {
422                    break;
423                }
424                let from = ((y * width + x) * 4) as usize;
425                let to = ((row as usize * area_width) + column as usize) * 4;
426                let (Some(source), Some(target)) =
427                    (self.canvas.get(from..from + 4), out.get_mut(to..to + 4))
428                else {
429                    continue;
430                };
431                target.copy_from_slice(source);
432            }
433        }
434        out
435    }
436
437    /// Draw palette indices onto the canvas, honouring transparency.
438    fn composite(
439        &mut self,
440        image: ImageDescriptor,
441        indices: &[u8],
442        palette: &[[u8; 3]],
443        transparent: Option<u8>,
444    ) {
445        let canvas_width = self.descriptor.width;
446        let canvas_height = self.descriptor.height;
447        let frame_width = u32::from(image.width);
448        let frame_height = u32::from(image.height);
449
450        for source_row in 0..frame_height {
451            // Interlaced frames store rows out of order; the mapping is GIF's
452            // four-pass row interlace, which is not PNG's Adam7.
453            let target_row = if image.interlaced {
454                match deinterlace(source_row, frame_height) {
455                    Some(row) => row,
456                    None => continue,
457                }
458            } else {
459                source_row
460            };
461            let y = u32::from(image.top) + target_row;
462            if y >= canvas_height {
463                continue;
464            }
465
466            for column in 0..frame_width {
467                let x = u32::from(image.left) + column;
468                if x >= canvas_width {
469                    continue;
470                }
471                let index = (source_row * frame_width + column) as usize;
472                let Some(&entry) = indices.get(index) else {
473                    // A short LZW stream leaves the rest of the frame
474                    // untouched, which is what viewers show. Truncating to an
475                    // error would reject files that display.
476                    continue;
477                };
478                if Some(entry) == transparent {
479                    // Transparent pixels let the canvas show through — the
480                    // whole point of frame-to-frame optimisation.
481                    continue;
482                }
483                let colour = palette.get(entry as usize).copied().unwrap_or([0, 0, 0]);
484                let at = ((y * canvas_width + x) * 4) as usize;
485                if let Some(slot) = self.canvas.get_mut(at..at + 4) {
486                    slot.copy_from_slice(&[colour[0], colour[1], colour[2], 255]);
487                }
488            }
489        }
490    }
491
492    /// Ensure the first frame has been decoded, for the [`Decoder`] path.
493    fn ensure_started(&mut self) -> Result<()> {
494        if self.started {
495            return Ok(());
496        }
497        match self.next_frame()? {
498            Some(_) => Ok(()),
499            None => Err(PixelsError::malformed("gif", "stream contains no frames")),
500        }
501    }
502}
503
504/// The buffered stream the frames are decoded from.
505type Bytes = std::io::Cursor<Vec<u8>>;
506
507/// Count the frames in `stream` (everything after the global colour table)
508/// and collect their delays and the `NETSCAPE2.0` loop count, skipping the
509/// pixel data. `None` for a still image. A stream that breaks off counts the
510/// frames before the break: that is a decode error, reported when pixels are
511/// read, not a reason to misreport what came before.
512fn scan_animation(stream: &[u8]) -> Option<Animation> {
513    let mut source = stream;
514    let mut delays = Vec::new();
515    let mut delay = 0_u32;
516    // No NETSCAPE2.0 block: the animation plays once.
517    let mut loop_count = 1;
518    let mut byte = [0_u8; 1];
519    while source.read_exact(&mut byte).is_ok() {
520        match byte[0] {
521            label::EXTENSION => {
522                if source.read_exact(&mut byte).is_err() {
523                    break;
524                }
525                let Ok(payload) = read_sub_blocks(&mut source, MAX_EXTENSION) else {
526                    break;
527                };
528                match byte[0] {
529                    label::GRAPHIC_CONTROL => {
530                        delay = u32::from(GraphicControl::parse(&payload).delay_centiseconds) * 10;
531                    }
532                    // The application block's identifier is its first
533                    // sub-block; the loop count is sub-block 1 of the data.
534                    0xFF if payload.starts_with(b"NETSCAPE2.0")
535                        || payload.starts_with(b"ANIMEXTS1.0") =>
536                    {
537                        if let [1, lo, hi, ..] = payload.get(11..).unwrap_or_default() {
538                            loop_count = u32::from(u16::from_le_bytes([*lo, *hi]));
539                        }
540                    }
541                    _ => {}
542                }
543            }
544            label::IMAGE => {
545                let mut bytes = [0_u8; 9];
546                if source.read_exact(&mut bytes).is_err() {
547                    break;
548                }
549                let Ok(image) = ImageDescriptor::parse(&bytes) else {
550                    break;
551                };
552                let table = image.local_table_size * 3;
553                let mut code_size = [0_u8; 1];
554                if source.len() < table + 1 {
555                    break;
556                }
557                source = source.get(table..).unwrap_or_default();
558                if source.read_exact(&mut code_size).is_err()
559                    || skip_sub_blocks(&mut source).is_err()
560                {
561                    break;
562                }
563                delays.push(delay);
564                delay = 0;
565            }
566            _ => break,
567        }
568    }
569    Animation::new(delays, loop_count)
570}
571
572/// Map a stored row index to its position in an interlaced frame.
573fn deinterlace(stored: u32, height: u32) -> Option<u32> {
574    let mut seen = 0;
575    for pass in 0..4 {
576        let rows = interlaced_pass_rows(pass, height);
577        if stored < seen + rows {
578            return interlaced_row(pass, stored - seen, height);
579        }
580        seen += rows;
581    }
582    None
583}
584
585/// Read a colour table of `entries` RGB triples.
586fn read_table<S: Source>(source: &mut S, entries: usize) -> Result<Vec<[u8; 3]>> {
587    let mut bytes = vec![0_u8; entries * 3];
588    source.read_exact(&mut bytes)?;
589    Ok(bytes
590        .chunks_exact(3)
591        .map(|rgb| {
592            [
593                rgb.first().copied().unwrap_or(0),
594                rgb.get(1).copied().unwrap_or(0),
595                rgb.get(2).copied().unwrap_or(0),
596            ]
597        })
598        .collect())
599}
600
601impl<S: Source + std::fmt::Debug> Decoder for GifDecoder<S> {
602    fn descriptor(&self) -> CoreDescriptor {
603        self.descriptor
604    }
605
606    fn capability(&self) -> DecodeCapability {
607        DecodeCapability::Sequential
608    }
609
610    fn animation(&self) -> Option<Animation> {
611        self.animation.clone()
612    }
613
614    fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
615        self.ensure_started()?;
616        if self.row >= self.descriptor.height {
617            return Err(PixelsError::invalid_argument(
618                "out",
619                format!("all {} rows have already been read", self.descriptor.height),
620            ));
621        }
622        let row_bytes = self.descriptor.row_bytes();
623        if out.len() != row_bytes {
624            return Err(PixelsError::invalid_argument(
625                "out",
626                format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
627            ));
628        }
629        let start = self.row as usize * row_bytes;
630        let row = self
631            .canvas
632            .get(start..start + row_bytes)
633            .ok_or_else(|| PixelsError::malformed("gif", "canvas is short"))?;
634        out.copy_from_slice(row);
635        self.row += 1;
636        Ok(())
637    }
638}
639
640/// Whether `prefix` starts with a GIF signature.
641///
642/// Detection is by magic bytes only (SPEC §Formats).
643#[must_use]
644pub fn probe(prefix: &[u8]) -> bool {
645    let head = prefix.get(..6);
646    head == Some(&SIGNATURE_87A[..]) || head == Some(&SIGNATURE_89A[..])
647}
648
649/// The GIF entry in a sniffing registry.
650#[derive(Debug, Clone, Copy, Default)]
651pub struct GifCodec;
652
653impl Codec for GifCodec {
654    fn format(&self) -> Format {
655        Format::Gif
656    }
657
658    fn magic_len(&self) -> usize {
659        6
660    }
661
662    fn probe(&self, prefix: &[u8]) -> bool {
663        probe(prefix)
664    }
665}