maple_render_core/
webp_anim.rs1#[cfg(not(target_arch = "wasm32"))]
11use std::{fs::File, io::Write, path::Path};
12
13#[cfg(not(target_arch = "wasm32"))]
14use webp_animation::Encoder as WebPEncoder;
15
16#[cfg(not(target_arch = "wasm32"))]
17use crate::{
18 error::{Error, Result},
19 renders::Renders,
20};
21
22pub const DEFAULT_WEBP_QUALITY: f32 = 95.0;
24
25pub const DEFAULT_WEBP_METHOD: usize = 4;
27
28#[cfg(not(target_arch = "wasm32"))]
29pub struct WebpOptions {
30 pub quality: f32,
32 pub lossless: bool,
34 pub method: usize,
36}
37
38impl Default for WebpOptions {
39 fn default() -> Self {
40 WebpOptions { quality: DEFAULT_WEBP_QUALITY, lossless: false, method: DEFAULT_WEBP_METHOD }
41 }
42}
43
44#[cfg(not(target_arch = "wasm32"))]
45pub struct WebpAnim {
46 renders: Renders,
47 period: f64,
48 hold: f64,
49 first_frame: i32,
50 options: WebpOptions,
51}
52
53#[cfg(not(target_arch = "wasm32"))]
54impl WebpAnim {
55 pub fn new(renders: Renders) -> Self {
56 WebpAnim {
57 renders,
58 period: 0.1,
59 hold: 5.0,
60 first_frame: -1,
61 options: WebpOptions::default(),
62 }
63 }
64
65 pub fn set_first_frame(&mut self, index: i32) {
66 self.first_frame = index;
67 }
68
69 pub fn set_timing(&mut self, period: f64, hold: f64) {
70 self.period = period;
71 self.hold = hold;
72 }
73
74 pub fn set_options(&mut self, options: WebpOptions) {
75 self.options = WebpOptions {
76 quality: options.quality.clamp(0.0, 100.0),
77 lossless: options.lossless,
78 method: options.method.min(6),
79 };
80 }
81
82 pub fn encode_single(img: &image::RgbaImage, options: &WebpOptions) -> Result<Vec<u8>> {
87 let (width, height) = (img.width(), img.height());
88 let rgba = img.as_raw().to_vec();
89
90 let enc_options = if options.lossless {
91 webp_animation::EncoderOptions {
92 encoding_config: Some(webp_animation::EncodingConfig {
93 encoding_type: webp_animation::EncodingType::Lossless,
94 quality: options.quality,
95 method: options.method,
96 ..Default::default()
97 }),
98 color_mode: webp_animation::ColorMode::Rgba,
99 ..Default::default()
100 }
101 } else {
102 let mut cfg = webp_animation::EncodingConfig::new_lossy(options.quality);
103 cfg.method = options.method;
104 webp_animation::EncoderOptions {
105 encoding_config: Some(cfg),
106 color_mode: webp_animation::ColorMode::Rgba,
107 ..Default::default()
108 }
109 };
110
111 let mut enc = WebPEncoder::new_with_options((width, height), enc_options)
112 .map_err(|e| Error::VideoEncode(format!("WebP encoder init: {}", e)))?;
113 enc.add_frame(&rgba, 0)
114 .map_err(|e| Error::VideoEncode(format!("WebP add_frame: {}", e)))?;
115 let data =
116 enc.finalize(1).map_err(|e| Error::VideoEncode(format!("WebP finalize: {}", e)))?;
117 Ok(data.as_ref().to_vec())
118 }
119
120 pub fn encode(&mut self) -> Result<Vec<u8>> {
122 let frames = self.renders.length() as i32;
123 if frames == 0 {
124 return Err(Error::VideoEncode("No frames to encode".to_string()));
125 }
126
127 let first = self.renders.get_render(0)?;
129 let (width, height) = (first.get().width(), first.get().height());
130
131 let encoding_config = if self.options.lossless {
132 webp_animation::EncodingConfig {
133 encoding_type: webp_animation::EncodingType::Lossless,
134 quality: self.options.quality,
135 method: self.options.method,
136 ..Default::default()
137 }
138 } else {
139 let mut cfg = webp_animation::EncodingConfig::new_lossy(self.options.quality);
140 cfg.method = self.options.method;
141 cfg
142 };
143
144 let enc_options = webp_animation::EncoderOptions {
145 encoding_config: Some(encoding_config),
146 color_mode: webp_animation::ColorMode::Rgba,
147 kmin: 3,
148 kmax: 5,
149 ..Default::default()
150 };
151
152 let mut enc = WebPEncoder::new_with_options((width, height), enc_options)
153 .map_err(|e| Error::VideoEncode(format!("WebP encoder init: {}", e)))?;
154
155 let mut curr_ms: f64 = 0.0;
156 let mut prev_ts: i32 = -1;
157
158 for base in 0..frames {
159 let i = if self.first_frame >= 0 { (base + self.first_frame) % frames } else { base };
160
161 let step = if i == frames - 1 { self.period + self.hold } else { self.period };
162
163 let render = self.renders.get_render(i)?;
164 let img = render.get();
165 let rgba: Vec<u8> = img.as_raw().to_vec();
167
168 let mut ts = (curr_ms).round() as i32;
169 if ts <= prev_ts {
170 ts = prev_ts + 1;
171 }
172 enc.add_frame(&rgba, ts)
173 .map_err(|e| Error::VideoEncode(format!("WebP add_frame: {}", e)))?;
174 prev_ts = ts;
175
176 curr_ms += step * 1000.0;
177 self.renders.remove_render(i);
178 }
179
180 let mut final_ts = curr_ms.round() as i32;
182 if final_ts <= prev_ts {
183 final_ts = prev_ts + 1;
184 }
185
186 let webp_data = enc
187 .finalize(final_ts)
188 .map_err(|e| Error::VideoEncode(format!("WebP finalize: {}", e)))?;
189
190 Ok(webp_data.as_ref().to_vec())
191 }
192
193 pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
195 let data = self.encode()?;
196 let file = File::create(path.as_ref()).map_err(|e| Error::Io(e))?;
197 let mut writer = std::io::BufWriter::new(file);
198 writer.write_all(&data).map_err(|e| Error::Io(e))?;
199 Ok(())
200 }
201}