maple_render_core/
gif_anim.rs1use std::{
2 fs::File,
3 io::{BufWriter, Cursor},
4 path::Path,
5};
6
7use gif::{Encoder, Frame, Repeat};
8use image::RgbaImage;
9
10use crate::{
11 error::{Error, Result},
12 quantize::{Palette, Quantizer},
13 renders::Renders,
14};
15
16pub struct GifAnim {
17 renders: Renders,
18 palette_frames: Vec<i32>,
19 period: f64,
20 hold: f64,
21 first_frame: i32,
22 blocks: Vec<GifBlock>,
23 palette: Option<Palette>,
24 dither: bool,
25}
26
27struct GifBlock {
28 data: Vec<u8>,
29 delay: u16,
30 width: u16,
31 height: u16,
32}
33
34impl GifAnim {
35 pub fn new(renders: Renders) -> Self {
36 GifAnim {
37 renders,
38 palette_frames: vec![0],
39 period: 0.1,
40 hold: 5.0,
41 first_frame: -1,
42 blocks: Vec::new(),
43 palette: None,
44 dither: false,
45 }
46 }
47
48 pub fn set_dither(&mut self, dither: bool) {
49 self.dither = dither;
50 }
51
52 pub fn set_palette(&mut self, index: i32) {
53 self.palette_frames = vec![index];
54 }
55
56 pub fn set_palette_frames(&mut self, frames: Vec<i32>) {
57 self.palette_frames = frames;
58 }
59
60 pub fn set_first_frame(&mut self, index: i32) {
61 self.first_frame = index;
62 }
63
64 pub fn set_timing(&mut self, period: f64, hold: f64) {
65 self.period = period;
66 self.hold = hold;
67 }
68
69 pub fn apply(&mut self) -> Result<()> {
70 let step = (self.period * 100.0 + 0.5) as u16;
71 let last_step = (self.hold * 100.0 + 0.5) as u16;
72
73 let pals = self.palette_frames.len();
74 if pals < 1 {
75 return Err(Error::MissingData("No palette frames specified".to_string()));
76 }
77
78 let mut pal_image: Option<RgbaImage> = None;
79 let mut pal_index: i32 = -1;
80
81 for (i, &idx) in self.palette_frames.iter().enumerate() {
82 if pals == 1 {
83 let render = self.renders.get_render(idx)?;
84 pal_image = Some(render.get().clone());
85 pal_index = idx;
86 self.renders.remove_mapping(idx);
87 continue;
88 }
89
90 {
91 let render = self.renders.get_render(idx)?;
92 let img = render.get();
93 let w = img.width() as usize;
94 let h = img.height() as usize;
95
96 if pal_image.is_none() {
97 pal_image = Some(img.clone());
98 } else {
99 let pal_img = pal_image.as_mut().unwrap();
100 for k in (i..(w * h)).step_by(pals) {
101 let xx = (k % w) as u32;
102 let yy = (k / w) as u32;
103 let pixel = *img.get_pixel(xx, yy);
104 pal_img.put_pixel(xx, yy, pixel);
105 }
106 }
107 }
108
109 self.renders.remove_mapping(idx);
110 }
111
112 let pal_image = pal_image.ok_or(Error::MissingData("No palette image".to_string()))?;
113 let ww = pal_image.width() as u16;
114 let hh = pal_image.height() as u16;
115
116 let mut quantizer = Quantizer::new(&pal_image);
117 self.palette = Some(quantizer.palette().clone());
118
119 let mut prev_indices = quantizer.quantize(&pal_image, self.dither);
120
121 let frames = self.renders.length() as i32;
122
123 let mut _emit_ct = 0;
124 let mut step_pending: u16 = 0;
125 let mut pending_frame_number = pal_index;
126
127 for base in 0..frames {
128 let i = if self.first_frame >= 0 { (base + self.first_frame) % frames } else { base };
129
130 step_pending = step_pending.saturating_add(step);
131
132 let curr_indices = if i != pal_index {
133 let quantized = {
134 let render = self.renders.get_render(i)?;
135 quantizer.quantize(render.get(), self.dither)
136 };
137 self.renders.remove_mapping(i);
138 quantized
139 } else {
140 prev_indices.clone()
141 };
142
143 self.renders.remove_render(i);
144
145 let mut change = false;
146 let mut _emitting_frame_number = -1;
147
148 if base > 0 {
149 for (p0, p1) in prev_indices.iter().zip(curr_indices.iter()) {
150 if p0 != p1 {
151 change = true;
152 _emitting_frame_number = pending_frame_number;
153 pending_frame_number = i;
154 break;
155 }
156 }
157 }
158
159 if change {
160 let delay = step_pending.saturating_sub(step);
161 let emitted_indices = std::mem::replace(&mut prev_indices, curr_indices);
162 self.blocks.push(GifBlock { data: emitted_indices, delay, width: ww, height: hh });
163 step_pending = step;
164 _emit_ct += 1;
165 } else {
166 prev_indices = curr_indices;
167 pending_frame_number = i;
168 }
169
170 if i == frames - 1 {
171 step_pending = step_pending.saturating_add(last_step);
172 }
173 }
174
175 if frames > 0 {
176 self.blocks.push(GifBlock {
177 data: prev_indices,
178 delay: step_pending,
179 width: ww,
180 height: hh,
181 });
182 }
183
184 Ok(())
185 }
186
187 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
188 let (width, height, color_table) = self.build_palette_table()?;
189 let file = File::create(path.as_ref())?;
190 let writer = BufWriter::new(file);
191 self.write_blocks(writer, width, height, &color_table)?;
192
193 Ok(())
194 }
195
196 pub fn encode(&self) -> Result<Vec<u8>> {
197 let (width, height, color_table) = self.build_palette_table()?;
198 let mut out = Vec::new();
199 let writer = BufWriter::new(Cursor::new(&mut out));
200 self.write_blocks(writer, width, height, &color_table)?;
201 Ok(out)
202 }
203
204 fn build_palette_table(&self) -> Result<(u16, u16, Vec<u8>)> {
205 if self.blocks.is_empty() {
206 return Err(Error::GifEncode("No frames to save".to_string()));
207 }
208
209 let palette = self
210 .palette
211 .as_ref()
212 .ok_or_else(|| Error::GifEncode("No palette available".to_string()))?;
213
214 let first_block = &self.blocks[0];
215 let width = first_block.width;
216 let height = first_block.height;
217
218 let mut color_table = Vec::with_capacity(palette.colors_total * 3);
219 for i in 0..palette.colors_total {
220 color_table.push(palette.red[i]);
221 color_table.push(palette.green[i]);
222 color_table.push(palette.blue[i]);
223 }
224 while color_table.len() < 768 {
225 color_table.push(0);
226 }
227
228 Ok((width, height, color_table))
229 }
230
231 fn write_blocks<W: std::io::Write>(
232 &self,
233 writer: W,
234 width: u16,
235 height: u16,
236 color_table: &[u8],
237 ) -> Result<()> {
238 let mut encoder = Encoder::new(writer, width, height, color_table)
239 .map_err(|e| Error::GifEncode(e.to_string()))?;
240
241 encoder.set_repeat(Repeat::Infinite).map_err(|e| Error::GifEncode(e.to_string()))?;
242
243 for block in &self.blocks {
244 let mut frame =
245 Frame::from_indexed_pixels(block.width, block.height, block.data.clone(), None);
246 frame.delay = block.delay;
247 frame.dispose = gif::DisposalMethod::Keep;
248
249 encoder.write_frame(&frame).map_err(|e| Error::GifEncode(e.to_string()))?;
250 }
251
252 Ok(())
253 }
254}