pub mod builder;
pub mod pen;
#[cfg(test)]
mod tests;
use crate::{
image::{DynamicImage, GenericImage},
stitch::pen::{HorizontalGridPen, ImageGridPen, ImageRect, VerticalGridPen},
StitchBuilder,
};
#[derive(PartialEq, Debug, Copy, Clone, Default)]
pub enum AlignmentMode {
#[default]
Grid,
Horizontal,
Vertical,
}
enum AspectType {
Wide,
Portrait,
Squarish,
}
impl AspectType {
pub fn get_aspect_from_dims(w: u32, h: u32) -> AspectType {
let aspect_ratio: f32 = (w as f32) / (h as f32);
if aspect_ratio > 1.25f32 {
AspectType::Wide
} else if aspect_ratio < 0.8f32 {
AspectType::Portrait
} else {
AspectType::Squarish
}
}
}
pub struct Stitch {
images: Vec<DynamicImage>,
axis_pen: Box<dyn ImageGridPen>,
image_rects: Vec<ImageRect>,
}
impl Stitch {
pub fn builder() -> StitchBuilder {
StitchBuilder::default()
}
pub(crate) fn new(
images: Vec<DynamicImage>,
alignment: AlignmentMode,
width_limit: u32,
height_limit: u32,
) -> Stitch {
let mut axis_pen = Self::make_axis_pen(alignment, &images);
let unscaled_image_rects = axis_pen.generate_output_rects(&images);
let image_rects = match width_limit > 0 || height_limit > 0 {
true => axis_pen.scale_image_rects(unscaled_image_rects, width_limit, height_limit),
false => unscaled_image_rects,
};
Stitch {
images,
axis_pen,
image_rects,
}
}
fn make_axis_pen(
alignment: AlignmentMode,
images: &Vec<DynamicImage>,
) -> Box<dyn ImageGridPen> {
let image_count = images.len();
if alignment == AlignmentMode::Horizontal {
let smallest_height = Self::find_smallest_image_height(&images);
let pen = HorizontalGridPen::new(image_count, 1, 1, smallest_height);
return Box::new(pen);
}
if alignment == AlignmentMode::Vertical {
let smallest_width = Self::find_smallest_image_width(&images);
let pen = VerticalGridPen::new(image_count, 1, 1, smallest_width);
return Box::new(pen);
}
let mut t: usize = 1;
while t * t < image_count {
t += 1;
}
let grid_size_main_axis = t;
let grid_size_cross_axis = image_count.div_ceil(t);
let main_lines_with_full_size = image_count / t;
let mut wide_count = 0;
let mut portrait_count = 0;
let mut squarish_count = 0;
for img in images {
let aspect_type = AspectType::get_aspect_from_dims(img.width(), img.height());
match aspect_type {
AspectType::Wide => wide_count += 1,
AspectType::Portrait => portrait_count += 1,
AspectType::Squarish => squarish_count += 1,
}
}
let draw_horizontal = portrait_count > wide_count || squarish_count >= wide_count;
if draw_horizontal {
let smallest_height = Self::find_smallest_image_height(&images);
let pen = HorizontalGridPen::new(
grid_size_main_axis,
grid_size_cross_axis,
main_lines_with_full_size,
smallest_height,
);
Box::new(pen)
} else {
let smallest_width = Self::find_smallest_image_width(&images);
let pen = VerticalGridPen::new(
grid_size_main_axis,
grid_size_cross_axis,
main_lines_with_full_size,
smallest_width,
);
Box::new(pen)
}
}
fn find_smallest_image_width(images: &Vec<DynamicImage>) -> u32 {
let mut smallest_size: u32 = 1024 * 1024;
for image in images {
smallest_size = smallest_size.min(image.width());
}
smallest_size
}
fn find_smallest_image_height(images: &Vec<DynamicImage>) -> u32 {
let mut smallest_size: u32 = 1024 * 1024;
for image in images {
smallest_size = smallest_size.min(image.height());
}
smallest_size
}
pub fn stitch(self) -> Result<DynamicImage, String> {
let out_dimensions = self.axis_pen.get_output_dimensions();
let mut output_image = DynamicImage::new_rgba8(out_dimensions.w, out_dimensions.h);
for i in 0..self.images.len() {
let img = &self.images[i];
let rect = &self.image_rects[i];
let scaled_image = img.resize_exact(rect.w, rect.h, image::imageops::Lanczos3);
if let Err(err) = output_image.copy_from(&scaled_image, rect.x, rect.y) {
return Err(format!("{} error while copying file #{}", err, i));
}
}
Ok(output_image)
}
}