webp_animation/
encoder_config.rs

1use std::mem;
2
3use crate::{ColorMode, Error};
4
5#[allow(unused_imports)]
6use crate::Encoder; // needed by docs
7
8use libwebp_sys as webp;
9
10/// An options struct for [`Encoder`] instance
11///
12/// See also [`EncodingConfig`] for frame encoding configuration. Can be set globally
13/// or per-frame.
14#[derive(Clone)]
15pub struct EncoderOptions {
16    /// Animation parameters
17    pub anim_params: AnimParams,
18
19    /// If true, minimize the output size (slow). Implicitly
20    /// disables key-frame insertion. Default `false`
21    pub minimize_size: bool,
22
23    /// Minimum and maximum distance between consecutive key
24    /// frames in the output. The library may insert some key
25    /// frames as needed to satisfy this criteria.
26    /// Note that these conditions should hold: `kmax > kmin`
27    /// and `kmin >= kmax / 2 + 1`. Also, if `kmax <= 0`, then
28    /// key-frame insertion is disabled; and if `kmax == 1`,
29    /// then all frames will be key-frames (kmin value does
30    /// not matter for these special cases). Defaults to zero
31    pub kmin: isize,
32    pub kmax: isize,
33
34    /// If true, use mixed compression mode; may choose
35    /// either lossy and lossless for each frame. Default `false`
36    pub allow_mixed: bool,
37
38    /// If true, print info and warning messages to stderr. Default `false`
39    pub verbose: bool,
40
41    /// Input colorspace. [`ColorMode::Rgba`] by default
42    pub color_mode: ColorMode,
43
44    /// Default per-frame encoding config, optional. Can also be added per-frame
45    /// by [`Encoder::add_frame_with_config`]
46    pub encoding_config: Option<EncodingConfig>,
47}
48
49impl Default for EncoderOptions {
50    fn default() -> Self {
51        Self {
52            anim_params: AnimParams::default(),
53            minimize_size: false,
54            kmin: 0,
55            kmax: 0,
56            allow_mixed: false,
57            verbose: false,
58            color_mode: ColorMode::Rgba,
59            encoding_config: None,
60        }
61    }
62}
63
64/// Animation parameters
65#[derive(Clone, Default)]
66pub struct AnimParams {
67    /// Number of times to repeat the animation [0 = infinite, default].
68    pub loop_count: i32,
69}
70
71/// Encoding type
72#[derive(Debug, Clone)]
73pub enum EncodingType {
74    /// Lossy encoding
75    Lossy(LossyEncodingConfig),
76
77    /// Losless encoding. Default.
78    Lossless,
79}
80
81impl EncodingType {
82    pub fn new_lossy() -> Self {
83        EncodingType::Lossy(LossyEncodingConfig::default())
84    }
85}
86
87/// Encoding configuration. Can be set for [`Encoder`] globally or per frame
88///
89/// Set globally as part of [`EncoderOptions`] when using [`Encoder::new_with_options`],
90/// or per frame through [`Encoder::add_frame_with_config`]
91#[derive(Debug, Clone)]
92pub struct EncodingConfig {
93    /// Encoding Type (lossless or lossy). Defaults to lossless
94    pub encoding_type: EncodingType,
95
96    /// Between 0 and 100. For lossy, 0 gives the smallest
97    /// size and 100 the largest. For lossless, this
98    /// parameter is the amount of effort put into the
99    /// compression: 0 is the fastest but gives larger
100    /// files compared to the slowest, but best, 100.
101    pub quality: f32,
102
103    /// Quality/speed trade-off (0=fast, 6=slower-better)
104    pub method: usize,
105    // image_hint todo?
106}
107
108impl EncodingConfig {
109    pub fn new_lossy(quality: f32) -> Self {
110        Self {
111            encoding_type: EncodingType::new_lossy(),
112            quality,
113            ..Default::default()
114        }
115    }
116
117    pub(crate) fn to_config_container(&self) -> Result<ConfigContainer, Error> {
118        ConfigContainer::new(self)
119    }
120
121    pub(crate) fn apply_to(&self, webp_config: &mut webp::WebPConfig) {
122        webp_config.lossless = match &self.encoding_type {
123            EncodingType::Lossy(lossless_config) => {
124                lossless_config.apply_to(webp_config);
125                0
126            }
127            EncodingType::Lossless => 1,
128        };
129        webp_config.quality = self.quality;
130    }
131}
132
133impl Default for EncodingConfig {
134    fn default() -> Self {
135        // src/enc/config_enc.c has defaults
136        Self {
137            encoding_type: EncodingType::Lossless,
138            quality: 1.,
139            method: 4,
140        }
141    }
142}
143
144/// Parameters related to lossy compression only
145#[derive(Debug, Clone)]
146pub struct LossyEncodingConfig {
147    /// if non-zero, set the desired target size in bytes.
148    /// Takes precedence over the 'compression' parameter.
149    pub target_size: usize,
150
151    /// if non-zero, specifies the minimal distortion to
152    /// try to achieve. Takes precedence over target_size.
153    pub target_psnr: f32,
154
155    /// maximum number of segments to use, in [1..4]
156    pub segments: usize,
157
158    /// Spatial Noise Shaping. 0=off, 100=maximum.
159    pub sns_strength: usize,
160
161    /// range: [0 = off .. 100 = strongest]
162    pub filter_strength: usize,
163
164    /// range: [0 = off .. 7 = least sharp]
165    pub filter_sharpness: usize,
166
167    /// filtering type: 0 = simple, 1 = strong (only used
168    /// if filter_strength > 0 or autofilter > 0)
169    pub filter_type: usize,
170
171    /// Auto adjust filter's strength [false = off, true = on]
172    pub autofilter: bool,
173
174    /// Algorithm for encoding the alpha plane (false = none,
175    /// true = compressed with WebP lossless). Default is true.
176    pub alpha_compression: bool,
177
178    /// Predictive filtering method for alpha plane.
179    /// 0: none, 1: fast, 2: best. Default if 1.
180    pub alpha_filtering: usize,
181
182    /// Between 0 (smallest size) and 100 (lossless).
183    /// Default is 100.
184    pub alpha_quality: usize,
185
186    /// number of entropy-analysis passes (in [1..10]).
187    pub pass: usize,
188
189    /// if true, export the compressed picture back.
190    /// In-loop filtering is not applied.
191    pub show_compressed: bool,
192
193    /// preprocessing filter (0=none, 1=segment-smooth)
194    pub preprocessing: bool,
195
196    /// log2(number of token partitions) in [0..3]
197    /// Default is set to 0 for easier progressive decoding.
198    pub partitions: usize,
199
200    /// quality degradation allowed to fit the 512k limit on
201    /// prediction modes coding (0: no degradation,
202    /// 100: maximum possible degradation).
203    pub partition_limit: isize,
204
205    /// if needed, use sharp (and slow) RGB->YUV conversion
206    pub use_sharp_yuv: bool,
207}
208
209impl Default for LossyEncodingConfig {
210    fn default() -> Self {
211        Self {
212            // src/enc/config_enc.c contains defaults
213            target_size: 0,
214            target_psnr: 0.,
215            segments: 1,
216            sns_strength: 50,
217            filter_strength: 60,
218            filter_sharpness: 0,
219            filter_type: 1,
220            partitions: 0,
221            pass: 1,
222            show_compressed: false,
223            autofilter: false,
224            alpha_compression: true,
225            alpha_filtering: 1,
226            alpha_quality: 100,
227            preprocessing: false,
228            partition_limit: 0,
229            use_sharp_yuv: false,
230        }
231    }
232}
233
234impl LossyEncodingConfig {
235    pub fn new_from_default_preset() -> Self {
236        Self {
237            ..Default::default()
238        }
239    }
240
241    pub fn new_from_picture_preset() -> Self {
242        Self {
243            sns_strength: 80,
244            filter_sharpness: 4,
245            filter_strength: 35,
246            preprocessing: false,
247            ..Default::default()
248        }
249    }
250
251    pub fn new_from_photo_preset() -> Self {
252        Self {
253            sns_strength: 80,
254            filter_sharpness: 3,
255            filter_strength: 30,
256            preprocessing: false,
257            ..Default::default()
258        }
259    }
260
261    pub fn new_from_drawing_preset() -> Self {
262        Self {
263            sns_strength: 25,
264            filter_sharpness: 6,
265            filter_strength: 10,
266            ..Default::default()
267        }
268    }
269
270    pub fn new_from_icon_preset() -> Self {
271        Self {
272            sns_strength: 0,
273            filter_strength: 0,
274            preprocessing: false,
275            ..Default::default()
276        }
277    }
278
279    pub fn new_from_text_preset() -> Self {
280        Self {
281            sns_strength: 0,
282            filter_strength: 0,
283            preprocessing: false,
284            segments: 2,
285            ..Default::default()
286        }
287    }
288
289    fn apply_to(&self, webp_config: &mut webp::WebPConfig) {
290        webp_config.target_size = self.target_size as i32;
291        webp_config.target_PSNR = self.target_psnr;
292        webp_config.segments = self.segments as i32;
293        webp_config.sns_strength = self.sns_strength as i32;
294        webp_config.filter_strength = self.filter_strength as i32;
295        webp_config.filter_sharpness = self.filter_sharpness as i32;
296        webp_config.filter_type = self.filter_type as i32;
297        webp_config.autofilter = self.autofilter as i32;
298        webp_config.alpha_compression = self.alpha_compression as i32;
299        webp_config.alpha_filtering = self.alpha_filtering as i32;
300        webp_config.alpha_quality = self.alpha_quality as i32;
301        webp_config.pass = self.pass as i32;
302        webp_config.show_compressed = self.show_compressed as i32;
303        webp_config.preprocessing = self.preprocessing as i32;
304        webp_config.partitions = self.partitions as i32;
305        webp_config.partition_limit = self.partition_limit as i32;
306        webp_config.use_sharp_yuv = self.use_sharp_yuv as i32;
307    }
308}
309
310pub(crate) struct ConfigContainer {
311    config: webp::WebPConfig,
312}
313
314impl ConfigContainer {
315    pub fn new(config: &EncodingConfig) -> Result<Self, Error> {
316        let mut webp_config = unsafe {
317            let mut config = mem::zeroed();
318            webp::WebPConfigInit(&mut config);
319            config
320        };
321
322        config.apply_to(&mut webp_config);
323
324        if unsafe { webp::WebPValidateConfig(&webp_config) } == 0 {
325            return Err(Error::InvalidEncodingConfig);
326        }
327
328        Ok(Self {
329            config: webp_config,
330        })
331    }
332
333    pub fn as_ptr(&self) -> &webp::WebPConfig {
334        &self.config
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn test_config_defaults() {
344        let default_webp_config = unsafe {
345            let mut config = mem::zeroed();
346            webp::WebPConfigInit(&mut config);
347            config
348        };
349
350        let config = ConfigContainer::new(&EncodingConfig::default()).unwrap();
351
352        let left = config.as_ptr();
353        let def = &default_webp_config;
354
355        // custom-set
356        assert_eq!(left.lossless, 1);
357        assert_eq!(left.quality, 1.0);
358
359        // matches libwebp
360        assert_eq!(left.method, def.method, "c.method");
361        assert_eq!(left.image_hint, def.image_hint, "c.image_hint");
362        assert_eq!(left.target_size, def.target_size, "c.target_size");
363        assert_eq!(left.target_PSNR, def.target_PSNR, "c.target_PSNR");
364        assert_eq!(left.segments, def.segments, "c.segments");
365        assert_eq!(left.sns_strength, def.sns_strength, "c.sns_strength");
366        assert_eq!(
367            left.filter_strength, def.filter_strength,
368            "c.filter_strength"
369        );
370        assert_eq!(
371            left.filter_sharpness, def.filter_sharpness,
372            "c.filter_sharpness"
373        );
374        assert_eq!(left.filter_type, def.filter_type, "c.filter_type");
375        assert_eq!(left.autofilter, def.autofilter, "c.autofilter");
376        assert_eq!(
377            left.alpha_compression, def.alpha_compression,
378            "c.alpha_compression"
379        );
380        assert_eq!(
381            left.alpha_filtering, def.alpha_filtering,
382            "c.alpha_filtering"
383        );
384        assert_eq!(left.alpha_quality, def.alpha_quality, "c.alpha_quality");
385        assert_eq!(left.pass, def.pass, "c.pass");
386        assert_eq!(
387            left.show_compressed, def.show_compressed,
388            "c.show_compressed"
389        );
390        assert_eq!(left.preprocessing, def.preprocessing, "c.preprocessing");
391        assert_eq!(left.partitions, def.partitions, "c.partitions");
392        assert_eq!(
393            left.partition_limit, def.partition_limit,
394            "c.partition_limit"
395        );
396        assert_eq!(
397            left.emulate_jpeg_size, def.emulate_jpeg_size,
398            "c.emulate_jpeg_size"
399        );
400        assert_eq!(left.thread_level, def.thread_level, "c.thread_level");
401        assert_eq!(left.low_memory, def.low_memory, "c.low_memory");
402
403        assert_eq!(left.near_lossless, def.near_lossless, "c.near_lossless");
404        assert_eq!(left.exact, def.exact, "c.exact");
405        assert_eq!(
406            left.use_delta_palette, def.use_delta_palette,
407            "c.use_delta_palette"
408        );
409        assert_eq!(left.use_sharp_yuv, def.use_sharp_yuv, "c.use_sharp_yuv");
410        assert_eq!(left.qmin, def.qmin, "c.qmin");
411        assert_eq!(left.qmax, def.qmax, "c.qmax");
412    }
413}