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 optimization_preserves_pixels_and_respects_bypass_conditions() {
62        use ImageOptimizationPolicy::{Optimize, Preserve};
63        use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
64
65        let pixels = vec![255; 64 * 64 * 4];
66        let mut bytes = Vec::new();
67        PngEncoder::new(&mut bytes)
68            .write_image(&pixels, 64, 64, ExtendedColorType::Rgba8)
69            .unwrap();
70        let optimized = optimize_image_bytes("image/png", bytes.clone(), Optimize)
71            .await
72            .unwrap();
73        assert!(optimized.optimized);
74        assert!(optimized.bytes.len() < bytes.len());
75        assert_eq!(
76            image::load_from_memory(&optimized.bytes)
77                .unwrap()
78                .to_rgba8()
79                .into_raw(),
80            pixels
81        );
82
83        // The declared media type and policy govern whether bytes are inspected.
84        for (media_type, input, policy) in [
85            ("image/png", bytes.as_slice(), Preserve),
86            ("image/gif", bytes.as_slice(), Optimize),
87            ("image/png", b"not a png".as_slice(), Optimize),
88        ] {
89            let result = optimize_image_bytes(media_type, input.to_vec(), policy)
90                .await
91                .unwrap();
92            assert_eq!(result.bytes, input, "{media_type} {policy:?}");
93            assert!(!result.optimized, "{media_type} {policy:?}");
94        }
95    }
96}