116 lines
3.2 KiB
Rust
116 lines
3.2 KiB
Rust
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<u8>) -> Option<DynamicImage> {
|
|
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<u8>],
|
|
padding_image: &Option<Vec<u8>>,
|
|
) -> Vec<u8> {
|
|
let placeholder = DynamicImage::new_rgb8(128, 128);
|
|
|
|
let images: Vec<Option<DynamicImage>> = 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<u8> = Vec::new();
|
|
|
|
let mut allocations: Vec<&Option<DynamicImage>> = 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<Vec<u8>>,
|
|
padding_image: Option<Vec<u8>>,
|
|
) -> Vec<u8> {
|
|
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()
|
|
}
|
|
}
|
|
}
|