yaged/decoder/
mod.rs

1use crate::types::{ColorMap, ExtensionBlock, Frame, Gif, GraphicControlExtension};
2use std::io::Read;
3#[cfg(test)]
4use {std::fs::File, std::path::Path};
5
6mod steps;
7
8/// Color output mode.
9#[derive(PartialEq, Debug)]
10pub enum ColorOutput {
11    /// Every byte of the raster data is expanded to 4 bytes (R G B A).
12    /// Setting this color output the rgba_raster_data will be present in the resulting Gif.
13    RGBA,
14    /// Normal ColorMap index color mapping.
15    ColorMap,
16}
17
18/// Decode a gif encoded source.
19/// If RGBA ColorOutput is set, the rgba_raster_data is set in the resulting Gif.
20pub fn decode(
21    mut source: impl Read,
22    color_output: ColorOutput,
23) -> Result<Gif, Box<dyn std::error::Error>> {
24    let bytes: &mut Vec<u8> = &mut Vec::new();
25    source.read_to_end(bytes)?;
26
27    let (signature, cursor) = steps::signature::decode(&bytes, 0);
28    let (screen_descriptor, cursor) = steps::screen_descriptor::decode(&bytes, cursor);
29    let (global_color_map, cursor) = steps::color_map::decode(
30        &bytes,
31        screen_descriptor.pixel(),
32        screen_descriptor.m(),
33        cursor,
34    );
35    let (frames, _cursor) = frames(bytes, &color_output, global_color_map.as_ref(), cursor);
36
37    Ok(Gif::new(
38        signature,
39        screen_descriptor,
40        global_color_map,
41        frames,
42    ))
43}
44
45fn rgba_raster_data(
46    raster_data: &[u8],
47    graphic_control_extension: Option<&GraphicControlExtension>,
48    color_map: &ColorMap,
49) -> Vec<u8> {
50    raster_data
51        .iter()
52        .map(|index| {
53            table_index_to_rgba(
54                *index,
55                color_map,
56                graphic_control_extension.and_then(|ext| ext.transparent_color_index()),
57            )
58        })
59        .flatten()
60        .collect()
61}
62
63fn table_index_to_rgba(
64    index: u8,
65    color_map: &ColorMap,
66    maybe_transparent_color_index: Option<u8>,
67) -> Vec<u8> {
68    let rgba = color_map
69        .get(&(index as usize))
70        .expect("pixel index not found in color map");
71    let alpha = maybe_transparent_color_index
72        .filter(|alpha_index| index == *alpha_index)
73        .map(|_| 0x00u8)
74        .unwrap_or(0xFFu8);
75    vec![rgba.r(), rgba.g(), rgba.b(), alpha]
76}
77
78fn frames(
79    bytes: &[u8],
80    color_output: &ColorOutput,
81    global_color_map: Option<&ColorMap>,
82    cursor: usize,
83) -> (Vec<Frame>, usize) {
84    let mut mut_index = cursor;
85    let mut frames: Vec<Frame> = Vec::new();
86
87    while bytes[mut_index] != 0x3b {
88        let (frame, index) = frame(bytes, color_output, global_color_map, mut_index);
89        mut_index = index;
90        frames.push(frame);
91    }
92
93    (frames, mut_index)
94}
95
96fn frame(
97    bytes: &[u8],
98    color_output: &ColorOutput,
99    global_color_map: Option<&ColorMap>,
100    cursor: usize,
101) -> (Frame, usize) {
102    let mut index = cursor;
103    let mut graphic_control_extension: Option<GraphicControlExtension> = None;
104
105    while bytes[index] != 0x2c {
106        if bytes[index] == 0x21 {
107            let (block, cursor) = steps::extension_block::decode(bytes, index);
108            index = cursor;
109
110            if let Some(ExtensionBlock::GraphicControlExtension(extension)) = block {
111                graphic_control_extension = Some(extension);
112            }
113        } else {
114            index += 1;
115        }
116    }
117
118    let (image_descriptor, index) = steps::image_descriptor::decode(bytes, index);
119    let (color_map, index) =
120        steps::color_map::decode(bytes, image_descriptor.pixel(), image_descriptor.m(), index);
121    let (raster_data, index) = steps::raster_data::decode(bytes, index);
122
123    let rgba_rd = match color_output {
124        ColorOutput::RGBA => {
125            let color_map = if image_descriptor.m() {
126                color_map
127                    .as_ref()
128                    .expect("expected local color map not present")
129            } else {
130                global_color_map
131                    .as_ref()
132                    .expect("expected global color map not present")
133            };
134
135            Some(rgba_raster_data(
136                &raster_data,
137                graphic_control_extension.as_ref(),
138                color_map,
139            ))
140        }
141        ColorOutput::ColorMap => None,
142    };
143
144    (
145        Frame::new(
146            image_descriptor,
147            color_map,
148            raster_data,
149            rgba_rd,
150            graphic_control_extension,
151        ),
152        index,
153    )
154}
155
156#[test]
157pub fn should_decode_using_color_map_mode() {
158    let file = &mut File::open(Path::new("./ascii-gif-example.gif")).unwrap();
159    let gif = decode(file, ColorOutput::ColorMap).unwrap();
160
161    assert_eq!("GIF89a", gif.signature());
162    assert_eq!(106, gif.frames().len());
163    gif.frames().iter().for_each(|frame| {
164        assert_eq!(
165            frame.raster_data().len(),
166            (frame.image_descriptor().image_width() as u32
167                * frame.image_descriptor().image_height() as u32) as usize
168        );
169        assert_eq!(&None, frame.rgba_raster_data());
170        assert!(frame.local_color_map().is_some())
171    });
172}
173
174#[test]
175pub fn should_decode_using_rgba_mode() {
176    let file = &mut File::open(Path::new("./ascii-gif-example.gif")).unwrap();
177    let gif = decode(file, ColorOutput::RGBA).unwrap();
178
179    assert_eq!("GIF89a", gif.signature());
180    assert_eq!(106, gif.frames().len());
181    gif.frames().iter().for_each(|frame| {
182        assert_eq!(
183            frame.raster_data().len(),
184            (frame.image_descriptor().image_width() as u32
185                * frame.image_descriptor().image_height() as u32) as usize
186        );
187        assert_eq!(
188            frame.rgba_raster_data().as_ref().unwrap().len(),
189            (frame.image_descriptor().image_width() as u32
190                * frame.image_descriptor().image_height() as u32
191                * 4) as usize
192        );
193        assert!(frame.local_color_map().is_some())
194    });
195}