use std::io::Cursor; use image::imageops::FilterType; use image::{DynamicImage, ImageFormat, ImageReader}; use image::{GenericImage, RgbImage}; use tracing::{error, warn}; cfg_if! { if #[cfg(not(target_family = "wasm"))] { use tokio::task::spawn_blocking; } } fn from_raw_to_image(raw: &Vec) -> Option { match ImageReader::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 } 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 } pub async fn create_mozaik( width_px: u32, height_px: u32, images: Vec>, padding_image: Option>, ) -> Vec { cfg_if! { if #[cfg(target_family = "wasm")] { create_mozaik_(width_px, height_px, &images, &padding_image) } else { spawn_blocking(move || { create_mozaik_(width_px, height_px, &images, &padding_image) }).await.unwrap() } } }