185 lines
4.3 KiB
Rust
185 lines
4.3 KiB
Rust
use std::{fmt::Display, str::FromStr};
|
|
|
|
use image::DynamicImage;
|
|
use serde::{Deserialize, Serialize};
|
|
use strum::{Display, EnumString};
|
|
|
|
use crate::{pixeldim::PixelDim, transformers::ImageTransformer};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Serialize, Deserialize, Display)]
|
|
pub enum Direction {
|
|
#[serde(rename = "n")]
|
|
#[strum(to_string = "n")]
|
|
#[strum(serialize = "north")]
|
|
North,
|
|
|
|
#[serde(rename = "e")]
|
|
#[strum(serialize = "e")]
|
|
#[strum(serialize = "east")]
|
|
East,
|
|
|
|
#[serde(rename = "s")]
|
|
#[strum(serialize = "s")]
|
|
#[strum(serialize = "south")]
|
|
South,
|
|
|
|
#[serde(rename = "w")]
|
|
#[strum(to_string = "w")]
|
|
#[strum(serialize = "west")]
|
|
West,
|
|
|
|
#[serde(rename = "c")]
|
|
#[strum(serialize = "c")]
|
|
#[strum(serialize = "center")]
|
|
Center,
|
|
|
|
#[serde(rename = "ne")]
|
|
#[strum(serialize = "ne")]
|
|
#[strum(serialize = "northeast")]
|
|
NorthEast,
|
|
|
|
#[serde(rename = "se")]
|
|
#[strum(serialize = "se")]
|
|
#[strum(serialize = "southeast")]
|
|
SouthEast,
|
|
|
|
#[serde(rename = "nw")]
|
|
#[strum(serialize = "nw")]
|
|
#[strum(serialize = "northwest")]
|
|
NorthWest,
|
|
|
|
#[serde(rename = "sw")]
|
|
#[strum(serialize = "sw")]
|
|
#[strum(serialize = "southwest")]
|
|
SouthWest,
|
|
}
|
|
|
|
/// Crop an image to the given size.
|
|
/// - does not crop width if `w` is greater than image width
|
|
/// - does not crop height if `h` is greater than image height
|
|
/// - does nothing if `w` or `h` are less than or equal to zero.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct CropTransformer {
|
|
w: PixelDim,
|
|
h: PixelDim,
|
|
float: Direction,
|
|
}
|
|
|
|
impl CropTransformer {
|
|
pub fn new(w: PixelDim, h: PixelDim, float: Direction) -> Self {
|
|
Self { w, h, float }
|
|
}
|
|
|
|
fn crop_dim(&self, img_width: u32, img_height: u32) -> (u32, u32) {
|
|
let crop_width = match self.w {
|
|
PixelDim::Pixels(w) => w,
|
|
PixelDim::WidthPercent(pct) => ((img_width as f32) * pct / 100.0) as u32,
|
|
PixelDim::HeightPercent(pct) => ((img_height as f32) * pct / 100.0) as u32,
|
|
};
|
|
|
|
let crop_height = match self.h {
|
|
PixelDim::Pixels(h) => h,
|
|
PixelDim::WidthPercent(pct) => ((img_width as f32) * pct / 100.0) as u32,
|
|
PixelDim::HeightPercent(pct) => ((img_height as f32) * pct / 100.0) as u32,
|
|
};
|
|
|
|
(crop_width, crop_height)
|
|
}
|
|
|
|
#[expect(clippy::integer_division)]
|
|
fn crop_pos(
|
|
&self,
|
|
img_width: u32,
|
|
img_height: u32,
|
|
crop_width: u32,
|
|
crop_height: u32,
|
|
) -> (u32, u32) {
|
|
match self.float {
|
|
Direction::North => {
|
|
let x = (img_width - crop_width) / 2;
|
|
let y = 0;
|
|
(x, y)
|
|
}
|
|
Direction::East => {
|
|
let x = img_width - crop_width;
|
|
let y = (img_height - crop_height) / 2;
|
|
(x, y)
|
|
}
|
|
Direction::South => {
|
|
let x = (img_width - crop_width) / 2;
|
|
let y = img_height - crop_height;
|
|
(x, y)
|
|
}
|
|
Direction::West => {
|
|
let x = 0;
|
|
let y = (img_height - crop_height) / 2;
|
|
(x, y)
|
|
}
|
|
Direction::Center => {
|
|
let x = (img_width - crop_width) / 2;
|
|
let y = (img_height - crop_height) / 2;
|
|
(x, y)
|
|
}
|
|
Direction::NorthEast => {
|
|
let x = img_width - crop_width;
|
|
let y = 0;
|
|
(x, y)
|
|
}
|
|
Direction::SouthEast => {
|
|
let x = img_width - crop_width;
|
|
let y = img_height - crop_height;
|
|
(x, y)
|
|
}
|
|
Direction::NorthWest => {
|
|
let x = 0;
|
|
let y = 0;
|
|
(x, y)
|
|
}
|
|
Direction::SouthWest => {
|
|
let x = 0;
|
|
let y = img_height - crop_height;
|
|
(x, y)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for CropTransformer {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "crop({},{},{})", self.w, self.h, self.float)
|
|
}
|
|
}
|
|
|
|
impl ImageTransformer for CropTransformer {
|
|
fn parse_args(args: &str) -> Result<Self, String> {
|
|
let args: Vec<&str> = args.split(",").collect();
|
|
if args.len() != 3 {
|
|
return Err(format!("expected 3 args, got {}", args.len()));
|
|
}
|
|
|
|
let w = args[0].trim().parse::<PixelDim>()?;
|
|
let h = args[1].trim().parse::<PixelDim>()?;
|
|
|
|
let direction = args[2].trim();
|
|
let direction = Direction::from_str(direction)
|
|
.map_err(|_err| format!("invalid direction {direction}"))?;
|
|
|
|
Ok(Self {
|
|
w,
|
|
h,
|
|
float: direction,
|
|
})
|
|
}
|
|
|
|
fn transform(&self, input: &mut DynamicImage) {
|
|
let (img_width, img_height) = (input.width(), input.height());
|
|
let (crop_width, crop_height) = self.crop_dim(img_width, img_height);
|
|
|
|
if (crop_width < img_width || crop_height < img_height) && crop_width > 0 && crop_height > 0
|
|
{
|
|
let (x, y) = self.crop_pos(img_width, img_height, crop_width, crop_height);
|
|
*input = input.crop(x, y, crop_width, crop_height);
|
|
}
|
|
}
|
|
}
|