Skip to main content

aven_core/attachments/
optimization.rs

1use std::time::Duration;
2
3use anyhow::Result;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ImageOptimizationPolicy {
7    Preserve,
8    Optimize,
9}
10
11pub struct OptimizedBytes {
12    pub bytes: Vec<u8>,
13    pub optimized: bool,
14}
15
16pub async fn optimize_image_bytes(
17    media_type: &str,
18    bytes: Vec<u8>,
19    policy: ImageOptimizationPolicy,
20) -> Result<OptimizedBytes> {
21    if policy == ImageOptimizationPolicy::Preserve || media_type != "image/png" {
22        return Ok(OptimizedBytes {
23            bytes,
24            optimized: false,
25        });
26    }
27
28    let optimized = crate::attachments::blocking::run(move || Ok(optimize_png(bytes))).await?;
29    match optimized {
30        Ok((original, optimized)) if optimized.len() < original.len() => Ok(OptimizedBytes {
31            bytes: optimized,
32            optimized: true,
33        }),
34        Ok((original, _)) | Err((original, _)) => Ok(OptimizedBytes {
35            bytes: original,
36            optimized: false,
37        }),
38    }
39}
40
41type PngOptimizationResult = std::result::Result<(Vec<u8>, Vec<u8>), (Vec<u8>, oxipng::PngError)>;
42
43fn optimize_png(bytes: Vec<u8>) -> PngOptimizationResult {
44    let mut options = oxipng::Options::from_preset(4);
45    options.strip = oxipng::StripChunks::Safe;
46    options.timeout = Some(Duration::from_secs(10));
47    options.max_decompressed_size = Some(
48        usize::try_from(crate::attachments::decode::MAX_DECODER_ALLOCATION).unwrap_or(usize::MAX),
49    );
50    match oxipng::optimize_from_memory(&bytes, &options) {
51        Ok(optimized) => Ok((bytes, optimized)),
52        Err(error) => Err((bytes, error)),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[tokio::test]
61    async fn preserve_policy_keeps_bytes() {
62        let bytes = b"png bytes".to_vec();
63        let optimized = optimize_image_bytes(
64            "image/png",
65            bytes.clone(),
66            ImageOptimizationPolicy::Preserve,
67        )
68        .await
69        .unwrap();
70
71        assert_eq!(optimized.bytes, bytes);
72        assert!(!optimized.optimized);
73    }
74
75    #[tokio::test]
76    async fn non_png_keeps_bytes() {
77        let bytes = b"gif bytes".to_vec();
78        let optimized = optimize_image_bytes(
79            "image/gif",
80            bytes.clone(),
81            ImageOptimizationPolicy::Optimize,
82        )
83        .await
84        .unwrap();
85
86        assert_eq!(optimized.bytes, bytes);
87        assert!(!optimized.optimized);
88    }
89
90    #[tokio::test]
91    async fn invalid_png_keeps_bytes() {
92        let bytes = b"not a png".to_vec();
93        let optimized = optimize_image_bytes(
94            "image/png",
95            bytes.clone(),
96            ImageOptimizationPolicy::Optimize,
97        )
98        .await
99        .unwrap();
100
101        assert_eq!(optimized.bytes, bytes);
102        assert!(!optimized.optimized);
103    }
104}