aven_core/attachments/
optimization.rs1use 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}