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#[derive(PartialEq, Debug)]
10pub enum ColorOutput {
11 RGBA,
14 ColorMap,
16}
17
18pub 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}