diff --git a/Cargo.toml b/Cargo.toml index a41d243..935d5b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ tracing-web = "0.1.3" tracing-subscriber = "0.3.18" git-version = "0.3.9" async-trait = "0.1.80" +image = "0.25.1" [target.'cfg(target_family = "wasm")'.dependencies] web-sys = "0.3.69" diff --git a/src/infrastructure/services/mod.rs b/src/infrastructure/services/mod.rs index 6e340f5..4244fb5 100644 --- a/src/infrastructure/services/mod.rs +++ b/src/infrastructure/services/mod.rs @@ -1 +1,2 @@ +pub(crate) mod mozaik_builder; pub(crate) mod random_svg_generators; diff --git a/src/infrastructure/services/mozaik_builder.rs b/src/infrastructure/services/mozaik_builder.rs new file mode 100644 index 0000000..d9d5375 --- /dev/null +++ b/src/infrastructure/services/mozaik_builder.rs @@ -0,0 +1,92 @@ +use std::io::Cursor; + +use image::imageops::FilterType; +use image::io::Reader; +use image::{DynamicImage, ImageFormat}; +use image::{GenericImage, RgbImage}; +use tracing::{error, warn}; + +fn from_raw_to_image(raw: &Vec) -> Option { + match Reader::new(Cursor::new(raw)).with_guessed_format() { + Ok(reader) => match reader.decode() { + Ok(image) => return Some(image), + Err(err) => error!("Unable to decode the image: {}", err), + }, + Err(err) => { + error!("Unable to read the image: {}", err) + } + } + None +} + +pub fn create_mozaik( + width_px: u32, + height_px: u32, + images: &[Vec], + padding_image: &Option>, +) -> Vec { + let placeholder = DynamicImage::new_rgb8(128, 128); + + let images: Vec> = images.iter().map(from_raw_to_image).collect(); + let padding_image = if let Some(padding_image) = padding_image { + from_raw_to_image(padding_image) + } else { + None + }; + + let mut bytes: Vec = Vec::new(); + + let mut allocations: Vec<&Option> = vec![]; + let mut images_per_row = 1; + let mut images_per_col = 1; + + match images.len() { + 0 => { + allocations.push(&padding_image); + } + 1 => { + allocations.push(&images[0]); + } + 2 => { + allocations.extend_from_slice(&[&images[0], &images[1], &images[1], &images[0]]); + images_per_row = 2; + images_per_col = 2; + } + _ => { + // TODO: Manage other cases + warn!("For now, we only manage the rendering of mozaic with less than 3 images"); + return bytes; + } + } + + let image_width_px = width_px / images_per_row; + let image_height_px = height_px / images_per_col; + + let mut output = RgbImage::new(width_px, height_px); + + let mut row_pos = 0; + for (index, image) in allocations.iter().enumerate() { + if index > 0 && index % images_per_row as usize == 0 { + row_pos += 1; + } + + let col_pos = index - (images_per_row as usize * row_pos); + + let image = *image; + + let scaled = image + .as_ref() + .unwrap_or(&placeholder) + .resize_to_fill(image_width_px, image_height_px, FilterType::Nearest) + .into_rgb8(); + + let output_image_pos_x = col_pos as u32 * image_width_px; + let output_image_pos_y = row_pos as u32 * image_height_px; + + let _ = output.copy_from(&scaled, output_image_pos_x, output_image_pos_y); + } + + let _ = output.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Jpeg); + + bytes +}