use std::io::Cursor;
use image::ImageFormat;
use super::{Asset, Environment, LayeredContext, MediaCategory, ProcessesAssets, ProcessingError};
pub struct ImageResizeProcessor {
width: u32,
height: u32,
}
impl ImageResizeProcessor {
pub fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
impl ProcessesAssets for ImageResizeProcessor {
fn process(
&self,
_env: &Environment,
_context: &LayeredContext,
asset: &mut Asset,
) -> Result<bool, ProcessingError> {
if asset.media_type().category() != MediaCategory::Image {
return Ok(false);
}
let asset_path = asset.path().clone();
let image_format = ImageFormat::from_path(asset_path.as_str()).map_err(|e| {
ProcessingError::Malformed {
message: e.to_string().into(),
}
})?;
let image_bytes = asset.as_mut_bytes()?;
let image =
image::load_from_memory(image_bytes).map_err(|e| ProcessingError::Malformed {
message: e.to_string().into(),
})?;
if image.width() <= self.width && image.height() <= self.height {
return Ok(false);
}
tracing::trace!("image: {}", asset_path);
let image = image.resize(
self.width,
self.height,
image::imageops::FilterType::Lanczos3,
);
image_bytes.clear();
let mut cursor = Cursor::new(image_bytes);
image
.write_to(&mut cursor, image_format)
.map_err(|e| ProcessingError::Malformed {
message: e.to_string().into(),
})?;
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proc::LayeredContext;
#[test_log::test]
#[test_log(default_log_filter = "debug")]
fn resizes_image() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
.try_init();
let source_bytes = std::fs::read("test/example.png").unwrap();
let mut asset = Asset::new("test/example.png".into(), source_bytes.clone());
let (width, height) = (300, 300);
ImageResizeProcessor { width, height }
.process(
&Environment::test(),
&LayeredContext::from_flat(Default::default()),
&mut asset,
)
.unwrap();
let resized_image = image::load_from_memory(asset.as_bytes()).unwrap();
assert_eq!(width, resized_image.width());
assert_eq!(243, resized_image.height());
}
#[test]
fn skips_image_already_within_bounds() {
let source_bytes = std::fs::read("test/example.png").unwrap();
let original_bytes = source_bytes.clone();
let mut asset = Asset::new("photo.png".into(), source_bytes);
let modified = ImageResizeProcessor::new(9999, 9999)
.process(
&Environment::test(),
&LayeredContext::from_flat(Default::default()),
&mut asset,
)
.unwrap();
assert!(!modified);
assert_eq!(asset.as_bytes(), &original_bytes);
}
#[test]
fn skips_non_image_assets() {
let mut asset = Asset::new("style.css".into(), b"body {}".to_vec());
let modified = ImageResizeProcessor::new(300, 300)
.process(
&Environment::test(),
&LayeredContext::from_flat(Default::default()),
&mut asset,
)
.unwrap();
assert!(!modified);
}
}