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 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 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}