🚧 Add a first version of the mozaik builder service
This commit is contained in:
@@ -37,6 +37,7 @@ tracing-web = "0.1.3"
|
|||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
git-version = "0.3.9"
|
git-version = "0.3.9"
|
||||||
async-trait = "0.1.80"
|
async-trait = "0.1.80"
|
||||||
|
image = "0.25.1"
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
web-sys = "0.3.69"
|
web-sys = "0.3.69"
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
|
pub(crate) mod mozaik_builder;
|
||||||
pub(crate) mod random_svg_generators;
|
pub(crate) mod random_svg_generators;
|
||||||
|
92
src/infrastructure/services/mozaik_builder.rs
Normal file
92
src/infrastructure/services/mozaik_builder.rs
Normal file
@@ -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<u8>) -> Option<DynamicImage> {
|
||||||
|
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<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
|
||||||
|
}
|
Reference in New Issue
Block a user