tinygif/
lib.rs

1//! tinygif - A tiny GIF decoder for embedded systems.
2
3#![no_std]
4
5use core::fmt::{self, Debug};
6use core::marker::PhantomData;
7
8use embedded_graphics::prelude::{DrawTarget, ImageDrawable, OriginDimensions, Point, Size};
9use embedded_graphics::Pixel;
10use embedded_graphics::{pixelcolor::Rgb888, prelude::PixelColor};
11use parser::eat_len_prefixed_subblocks;
12
13use crate::parser::{le_u16, take, take1, take_slice};
14
15mod bitstream;
16pub mod lzw;
17mod parser;
18
19/// Len byte prefixed raw bytes, as used in GIFs.
20struct LenPrefixRawDataView<'a> {
21    remains: &'a [u8],
22    current_block: &'a [u8],
23    cursor: u8,
24}
25
26impl<'a> LenPrefixRawDataView<'a> {
27    pub fn new(data: &'a [u8]) -> Self {
28        let len = data[0] as usize;
29        Self {
30            remains: &data[1 + len..],
31            current_block: &data[1..1 + len],
32            cursor: 0,
33        }
34    }
35
36    #[inline]
37    fn shift_cursor(&mut self) {
38        if self.current_block.is_empty() {
39            // nop
40        } else if self.cursor < self.current_block.len() as u8 - 1 {
41            self.cursor += 1;
42        } else {
43            self.cursor = 0;
44            self.shift_next_block();
45        }
46    }
47
48    // leave cursor untouched
49    #[inline]
50    fn shift_next_block(&mut self) {
51        if self.current_block.is_empty() {
52            // no more blocks
53            return;
54        }
55        let len = self.remains[0] as usize;
56        if len == 0 {
57            self.remains = &[];
58            self.current_block = &[];
59        } else {
60            self.current_block = &self.remains[1..1 + len];
61            self.remains = &self.remains[1 + len..];
62        }
63    }
64}
65
66impl Iterator for LenPrefixRawDataView<'_> {
67    type Item = u8;
68
69    #[inline(always)]
70    fn next(&mut self) -> Option<Self::Item> {
71        if self.current_block.is_empty() {
72            return None;
73        }
74
75        let current = self.current_block[self.cursor as usize];
76        self.shift_cursor();
77        Some(current)
78    }
79}
80
81#[non_exhaustive]
82#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
83pub enum Version {
84    V87a,
85    V89a,
86}
87
88#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
89pub struct Header {
90    version: Version,
91    pub width: u16,
92    pub height: u16,
93    has_global_color_table: bool,
94    color_resolution: u8, // 3 bits
95    // _is_sorted: bool,
96    pub bg_color_index: u8,
97    // _pixel_aspect_ratio: u8
98}
99
100impl Header {
101    pub fn parse(input: &[u8]) -> Result<(&[u8], (Header, Option<ColorTable<'_>>)), ParseError> {
102        let (input, magic) = take::<3>(input)?;
103
104        if &magic != b"GIF" {
105            return Err(ParseError::InvalidFileSignature(magic));
106        }
107
108        let (input, ver) = take::<3>(input)?;
109        let version = if &ver == b"87a" {
110            Version::V87a
111        } else if &ver == b"89a" {
112            Version::V89a
113        } else {
114            return Err(ParseError::InvalidFileSignature(magic));
115        };
116
117        let (intput, screen_width) = le_u16(input)?;
118        let (intput, screen_height) = le_u16(intput)?;
119
120        let (input, flags) = take1(intput)?;
121        let has_global_color_table = flags & 0b1000_0000 != 0;
122        let global_color_table_size = if has_global_color_table {
123            2_usize.pow(((flags & 0b0000_0111) + 1) as u32)
124        } else {
125            0
126        };
127        let color_resolution = (flags & 0b0111_0000) >> 4;
128        let _is_sorted = flags & 0b0000_1000 != 0;
129
130        let (input, bg_color_index) = take1(input)?;
131        let (input, _pixel_aspect_ratio) = take1(input)?;
132
133        let (input, color_table) = if global_color_table_size > 0 {
134            // Each color table entry is 3 bytes long
135            let (input, table) = take_slice(input, global_color_table_size * 3)?;
136            (input, Some(ColorTable::new(table)))
137        } else {
138            (input, None)
139        };
140
141        Ok((
142            input,
143            (
144                Header {
145                    version,
146                    width: screen_width,
147                    height: screen_height,
148                    has_global_color_table,
149                    color_resolution,
150                    bg_color_index,
151                },
152                color_table,
153            ),
154        ))
155    }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
159pub struct ColorTable<'a> {
160    data: &'a [u8],
161}
162
163impl<'a> ColorTable<'a> {
164    pub(crate) const fn new(data: &'a [u8]) -> Self {
165        Self { data }
166    }
167
168    /// Returns the number of entries.
169    pub const fn len(&self) -> usize {
170        self.data.len() / 3
171    }
172
173    /// Returns a color table entry.
174    ///
175    /// `None` is returned if `index` is out of bounds.
176    pub fn get(&self, index: u8) -> Option<Rgb888> {
177        let base = 3 * (index as usize);
178        if base >= self.data.len() {
179            return None;
180        }
181
182        Some(Rgb888::new(
183            self.data[base],
184            self.data[base + 1],
185            self.data[base + 2],
186        ))
187    }
188}
189
190#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
191pub struct RawGif<'a> {
192    /// Image header.
193    header: Header,
194
195    global_color_table: Option<ColorTable<'a>>,
196
197    /// Image data.
198    raw_block_data: &'a [u8],
199}
200
201impl<'a> RawGif<'a> {
202    fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> {
203        let (remaining, (header, color_table)) = Header::parse(bytes)?;
204
205        Ok(Self {
206            header,
207            global_color_table: color_table,
208            raw_block_data: remaining,
209        })
210    }
211}
212
213#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
214pub struct GraphicControl {
215    pub is_transparent: bool,
216    pub transparent_color_index: u8,
217    // centisecond
218    pub delay_centis: u16,
219}
220
221#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
222
223pub struct ImageBlock<'a> {
224    pub left: u16,
225    pub top: u16,
226    pub width: u16,
227    pub height: u16,
228    pub is_interlaced: bool,
229    pub lzw_min_code_size: u8,
230    local_color_table: Option<ColorTable<'a>>,
231    image_data: &'a [u8],
232}
233
234impl<'a> ImageBlock<'a> {
235    // parse after 0x2c separator
236    pub fn parse(input: &'a [u8]) -> Result<(&[u8], Self), ParseError> {
237        let (input, left) = le_u16(input)?;
238        let (input, top) = le_u16(input)?;
239        let (input, width) = le_u16(input)?;
240        let (input, height) = le_u16(input)?;
241        let (input, flags) = take1(input)?;
242        let is_interlaced = flags & 0b0100_0000 != 0;
243        let has_local_color_table = flags & 0b1000_0000 != 0;
244        let local_color_table_size = if has_local_color_table {
245            2_usize.pow(((flags & 0b0000_0111) + 1) as u32)
246        } else {
247            0
248        };
249
250        let (input, local_color_table) = if local_color_table_size > 0 {
251            // Each color table entry is 4 bytes long
252            let (input, table) = take_slice(input, local_color_table_size * 3)?;
253            (input, Some(ColorTable::new(table)))
254        } else {
255            (input, None)
256        };
257
258        let (input, lzw_min_code_size) = take1(input)?;
259
260        let mut input0 = input;
261        let mut n = 1;
262        loop {
263            let (input, block_size) = take1(input0)?;
264            if block_size == 0 {
265                input0 = input;
266                break;
267            }
268            let (input, _) = take_slice(input, block_size as usize)?;
269            n += block_size as usize + 1;
270            input0 = input;
271        }
272
273        Ok((
274            input0,
275            Self {
276                left,
277                top,
278                width,
279                height,
280                is_interlaced,
281                lzw_min_code_size,
282                local_color_table,
283                image_data: &input[..n],
284            },
285        ))
286    }
287}
288
289#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
290pub enum ExtensionBlock<'a> {
291    GraphicControl(GraphicControl),
292    Comment(&'a [u8]),
293    // TODO: handle PlainTextExtension
294    PlainText,
295    NetscapeApplication { repetitions: u16 },
296    Application, // ignore content
297    Unknown(u8, &'a [u8]),
298}
299
300impl<'a> ExtensionBlock<'a> {
301    pub fn parse(input: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
302        let (input, ext_label) = take1(input)?;
303        match ext_label {
304            0xff => {
305                let (input, block_size_1) = take1(input)?;
306                let (input, app_id) = take::<8>(input)?;
307                let (input, app_auth_code) = take::<3>(input)?;
308                if block_size_1 == 11 && &app_id == b"NETSCAPE" && &app_auth_code == b"2.0" {
309                    let (input, block_size_2) = take1(input)?;
310                    if block_size_2 == 3 {
311                        let (input, always_one) = take1(input)?;
312                        if always_one == 1 {
313                            let (input, repetitions) = le_u16(input)?;
314                            let (input, eob) = take1(input)?;
315                            if eob == 0 {
316                                return Ok((
317                                    input,
318                                    ExtensionBlock::NetscapeApplication { repetitions },
319                                ));
320                            }
321                        }
322                    }
323                }
324                let mut input0 = input;
325                loop {
326                    let (input, block_size_2) = take1(input0)?;
327                    if block_size_2 == 0 {
328                        input0 = input;
329                        break;
330                    }
331                    let (input, _data) = take_slice(input, block_size_2 as usize)?;
332                    input0 = input;
333                }
334                Ok((input0, ExtensionBlock::Application))
335            }
336            0xfe => {
337                // Comment Extension
338                let mut input0 = input;
339                let mut size = 0;
340                loop {
341                    let (input, block_size) = take1(input0)?;
342                    size += 1;
343                    if block_size == 0 {
344                        input0 = input;
345                        break;
346                    }
347                    let (input, _data) = take_slice(input, block_size as usize)?;
348                    input0 = input;
349                    size += block_size as usize;
350                }
351                Ok((input0, ExtensionBlock::Comment(&input[..size])))
352            }
353            0xf9 => {
354                // Graphic Control Extension
355                let (input, block_size) = take1(input)?; // 4
356                if block_size != 4 {
357                    return Err(ParseError::InvalidConstSizeBytes); // invalid block size
358                }
359                let (input, flags) = take1(input)?;
360                let is_transparent = flags & 0b0000_0001 != 0;
361                let (input, delay_centis) = le_u16(input)?;
362                let (input, transparent_color_index) = take1(input)?;
363                let (input, block_terminator) = take1(input)?;
364                if block_terminator != 0 {
365                    return Err(ParseError::InvalidByte);
366                }
367
368                Ok((
369                    input,
370                    ExtensionBlock::GraphicControl(GraphicControl {
371                        is_transparent,
372                        transparent_color_index,
373                        delay_centis,
374                    }),
375                ))
376            }
377            0x01 => {
378                let (input, _) = take::<13>(input)?;
379                let input = eat_len_prefixed_subblocks(input)?;
380                Ok((input, ExtensionBlock::PlainText))
381            }
382            _ => unimplemented!(),
383        }
384    }
385}
386
387#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
388pub enum Segment<'a> {
389    Image(ImageBlock<'a>),
390    Extension(ExtensionBlock<'a>),
391    // 0x3b
392    Trailer,
393}
394
395impl<'a> Segment<'a> {
396    pub fn parse(input: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
397        let (input, ext_magic) = take1(input)?;
398
399        if ext_magic == 0x21 {
400            let (input, ext) = ExtensionBlock::parse(input)?;
401            Ok((input, Segment::Extension(ext)))
402        } else if ext_magic == 0x2c {
403            // Image Block
404            let (input, image_block) = ImageBlock::parse(input)?;
405            Ok((input, Segment::Image(image_block)))
406        } else if ext_magic == 0x3b {
407            if input.is_empty() {
408                Ok((input, Segment::Trailer))
409            } else {
410                Err(ParseError::JunkAfterTrailerByte)
411            }
412        } else {
413            return Err(ParseError::InvalidByte);
414        }
415    }
416
417    fn skip_to_next_graphic_control(mut input: &[u8]) -> Result<&[u8], ParseError> {
418        loop {
419            let (input0, ext_magic) = take1(input)?;
420            if ext_magic == 0x21 {
421                let (input1, label) = take1(input0)?;
422                if label == 0xF9 {
423                    return Ok(input);
424                } else if label == 0xFF {
425                    // Application Extension
426                    input = eat_len_prefixed_subblocks(input1)?;
427                } else if label == 0x01 {
428                    // Plain Text Extension
429                    // Ref: https://www.w3.org/Graphics/GIF/spec-gif89a.txt
430                    let (input2, _) = take::<13>(input1)?;
431                    input = eat_len_prefixed_subblocks(input2)?;
432                } else if label == 0xFE {
433                    // Comment Extension
434                    input = eat_len_prefixed_subblocks(input1)?;
435                } else {
436                    return Err(ParseError::InvalidExtensionLabel);
437                }
438            } else if ext_magic == 0x2c {
439                // Image Descriptor
440                // parse image, optional local color table
441                let (input1, _) = ImageBlock::parse(input0)?;
442                input = input1;
443            } else if ext_magic == 0x3b {
444                // Trailer
445                return Ok(&[]);
446            } else if ext_magic == 0x00 {
447                // Block Terminator
448                return Ok(input0);
449            } else {
450                return Err(ParseError::InvalidByte);
451            }
452        }
453    }
454
455    pub const fn type_name(&self) -> &'static str {
456        match self {
457            Segment::Image(_) => "Image",
458            Segment::Extension(_) => "Extension",
459            Segment::Trailer => "Trailer",
460        }
461    }
462}
463
464#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
465pub struct Gif<'a, C = Rgb888> {
466    raw_gif: RawGif<'a>,
467    color_type: PhantomData<C>,
468}
469
470impl<'a, C> Gif<'a, C> {
471    pub fn from_slice(input: &'a [u8]) -> Result<Self, ParseError> {
472        let raw_gif = RawGif::from_slice(input)?;
473        Ok(Self {
474            raw_gif,
475            color_type: PhantomData,
476        })
477    }
478
479    pub fn frames(&'a self) -> FrameIterator<'a, C> {
480        FrameIterator::new(self)
481    }
482
483    pub fn width(&self) -> u16 {
484        self.raw_gif.header.width
485    }
486
487    pub fn height(&self) -> u16 {
488        self.raw_gif.header.height
489    }
490}
491
492pub struct FrameIterator<'a, C> {
493    gif: &'a Gif<'a, C>,
494    frame_index: usize,
495    remain_raw_data: &'a [u8],
496}
497
498impl<'a, C> FrameIterator<'a, C> {
499    fn new(gif: &'a Gif<'a, C>) -> Self {
500        Self {
501            gif,
502            frame_index: 0,
503            remain_raw_data: gif.raw_gif.raw_block_data,
504        }
505    }
506}
507
508impl<'a, C: PixelColor> Iterator for FrameIterator<'a, C> {
509    type Item = Frame<'a, C>;
510
511    fn next(&mut self) -> Option<Self::Item> {
512        if self.remain_raw_data.is_empty() {
513            return None;
514        }
515
516        let input = self.remain_raw_data;
517        let input0 = Segment::skip_to_next_graphic_control(input).ok()?;
518
519        let (input00, seg) = Segment::parse(input0).ok()?;
520        self.remain_raw_data = input00;
521
522        if let Segment::Extension(ExtensionBlock::GraphicControl(ctrl)) = seg {
523            let frame = Frame {
524                delay_centis: ctrl.delay_centis,
525                is_transparent: ctrl.is_transparent,
526                transparent_color_index: ctrl.transparent_color_index,
527                global_color_table: self.gif.raw_gif.global_color_table.clone(),
528                header: &self.gif.raw_gif.header,
529                raw_data: input00,
530                frame_index: self.frame_index,
531                _marker: PhantomData,
532            };
533            self.frame_index += 1;
534            return Some(frame);
535        } else {
536            None
537        }
538    }
539}
540
541#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
542pub struct Frame<'a, C> {
543    pub delay_centis: u16,
544    pub is_transparent: bool,
545    pub transparent_color_index: u8,
546    global_color_table: Option<ColorTable<'a>>,
547    header: &'a Header,
548    raw_data: &'a [u8],
549    frame_index: usize,
550    _marker: PhantomData<C>,
551}
552
553impl<'a, C> OriginDimensions for Frame<'a, C> {
554    fn size(&self) -> Size {
555        Size::new(self.header.width as _, self.header.height as _)
556    }
557}
558
559impl<'a, C> ImageDrawable for Frame<'a, C>
560where
561    C: PixelColor + From<Rgb888>,
562{
563    type Color = C;
564
565    fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
566    where
567        D: DrawTarget<Color = Self::Color>,
568    {
569        let mut input = self.raw_data;
570        while let Ok((input0, seg)) = Segment::parse(input) {
571            input = input0;
572            match seg {
573                Segment::Extension(ExtensionBlock::GraphicControl(_)) | Segment::Trailer => {
574                    // overflows to the next frame
575                    break;
576                }
577                Segment::Image(ImageBlock {
578                    left,
579                    top,
580                    width,
581                    lzw_min_code_size,
582                    local_color_table,
583                    image_data,
584                    ..
585                }) => {
586                    let transparent_color_index = if self.is_transparent {
587                        Some(self.transparent_color_index)
588                    } else {
589                        None
590                    };
591                    let color_table = local_color_table
592                        .or_else(|| self.global_color_table.clone())
593                        .unwrap();
594                    let raw_image_data = LenPrefixRawDataView::new(image_data);
595                    let mut decoder = lzw::Decoder::new(raw_image_data, lzw_min_code_size);
596
597                    let mut idx: u32 = 0;
598
599                    while let Ok(Some(decoded)) = decoder.decode_next() {
600                        target.draw_iter(decoded.iter().filter_map(|&color_index| {
601                            if transparent_color_index == Some(color_index) {
602                                // skip drawing transparent color
603                                idx += 1;
604                                return None;
605                            }
606                            let x = left + (idx % u32::from(width)) as u16;
607                            let y = top + (idx / u32::from(width)) as u16;
608                            idx += 1;
609
610                            let color = color_table.get(color_index).unwrap();
611                            Some(Pixel(Point::new(x as i32, y as i32), color.into()))
612                        }))?;
613                    }
614                }
615                _ => (),
616            }
617        }
618
619        Ok(())
620    }
621
622    fn draw_sub_image<D>(
623        &self,
624        target: &mut D,
625        area: &embedded_graphics::primitives::Rectangle,
626    ) -> Result<(), D::Error>
627    where
628        D: DrawTarget<Color = Self::Color>,
629    {
630        let mut input = self.raw_data;
631        while let Ok((input0, seg)) = Segment::parse(input) {
632            input = input0;
633            match seg {
634                Segment::Extension(ExtensionBlock::GraphicControl(_)) | Segment::Trailer => {
635                    // overflows to the next frame
636                    break;
637                }
638                Segment::Image(ImageBlock {
639                    left,
640                    top,
641                    width,
642                    lzw_min_code_size,
643                    local_color_table,
644                    image_data,
645                    ..
646                }) => {
647                    let transparent_color_index = if self.is_transparent {
648                        Some(self.transparent_color_index)
649                    } else {
650                        None
651                    };
652                    let color_table = local_color_table
653                        .or_else(|| self.global_color_table.clone())
654                        .unwrap();
655                    let raw_image_data = LenPrefixRawDataView::new(image_data);
656                    let mut decoder = lzw::Decoder::new(raw_image_data, lzw_min_code_size);
657
658                    let mut idx: u32 = 0;
659
660                    while let Ok(Some(decoded)) = decoder.decode_next() {
661                        target.draw_iter(decoded.iter().filter_map(|&color_index| {
662                            if transparent_color_index == Some(color_index) {
663                                idx += 1;
664                                return None;
665                            }
666
667                            let x = left + (idx % u32::from(width)) as u16;
668                            let y = top + (idx / u32::from(width)) as u16;
669                            idx += 1;
670
671                            let pt = Point::new(x as i32, y as i32);
672                            if area.contains(pt) {
673                                let color = color_table.get(color_index).unwrap();
674                                Some(Pixel(pt, color.into()))
675                            } else {
676                                None
677                            }
678                        }))?;
679                    }
680                }
681                _ => (),
682            }
683        }
684
685        Ok(())
686    }
687}
688
689impl<C> fmt::Debug for Frame<'_, C> {
690    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
691        f.debug_struct("Frame")
692            .field("frame_index", &self.frame_index)
693            .field("delay_centis", &self.delay_centis)
694            .field("is_transparent", &self.is_transparent)
695            .field("transparent_color_index", &self.transparent_color_index)
696            .field("len(remain_data)", &self.raw_data.len())
697            .finish()
698    }
699}
700
701#[cfg(feature = "defmt")]
702impl<C> defmt::Format for Frame<'_, C> {
703    fn format(&self, f: defmt::Formatter) {
704        defmt::write!(
705            f,
706            "Frame {{ frame_index: {}, delay_centis: {} remain: {}}}",
707            self.frame_index,
708            self.delay_centis,
709            self.raw_data.len()
710        );
711    }
712}
713
714/// Parse error.
715#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
716pub enum ParseError {
717    /// The image uses an unsupported bit depth.
718    UnsupportedBpp(u16),
719
720    /// Unexpected end of file.
721    UnexpectedEndOfFile,
722
723    /// Invalid file signatures.
724    InvalidFileSignature([u8; 3]),
725
726    /// Unsupported compression method.
727    UnsupportedCompressionMethod(u32),
728
729    /// Unsupported header length.
730    UnsupportedHeaderLength(u32),
731
732    /// Unsupported channel masks.
733    UnsupportedChannelMasks,
734
735    /// Invalid image dimensions.
736    InvalidImageDimensions,
737
738    InvalidByte,
739
740    JunkAfterTrailerByte,
741
742    /// Current size bytes should be a constant.
743    InvalidConstSizeBytes,
744
745    InvalidExtensionLabel,
746}