mod error;
mod loader;
mod source;
pub use error::{ImageError, Result};
pub use loader::ImageData;
pub use source::ImageSource;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ObjectFit {
#[default]
Cover,
Contain,
Fill,
ScaleDown,
None,
}
pub type BoxFit = ObjectFit;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct ObjectPosition {
pub x: f32,
pub y: f32,
}
impl ObjectPosition {
pub const TOP_LEFT: Self = Self { x: 0.0, y: 0.0 };
pub const TOP_CENTER: Self = Self { x: 0.5, y: 0.0 };
pub const TOP_RIGHT: Self = Self { x: 1.0, y: 0.0 };
pub const CENTER_LEFT: Self = Self { x: 0.0, y: 0.5 };
pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
pub const CENTER_RIGHT: Self = Self { x: 1.0, y: 0.5 };
pub const BOTTOM_LEFT: Self = Self { x: 0.0, y: 1.0 };
pub const BOTTOM_CENTER: Self = Self { x: 0.5, y: 1.0 };
pub const BOTTOM_RIGHT: Self = Self { x: 1.0, y: 1.0 };
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
pub type ImageAlignment = ObjectPosition;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct ImageFilter {
pub grayscale: f32,
pub sepia: f32,
pub brightness: f32,
pub contrast: f32,
pub saturate: f32,
pub hue_rotate: f32,
pub invert: f32,
pub blur: f32,
}
impl ImageFilter {
pub fn none() -> Self {
Self {
brightness: 1.0,
contrast: 1.0,
saturate: 1.0,
..Default::default()
}
}
pub fn grayscale(mut self, amount: f32) -> Self {
self.grayscale = amount.clamp(0.0, 1.0);
self
}
pub fn sepia(mut self, amount: f32) -> Self {
self.sepia = amount.clamp(0.0, 1.0);
self
}
pub fn brightness(mut self, amount: f32) -> Self {
self.brightness = amount.max(0.0);
self
}
pub fn contrast(mut self, amount: f32) -> Self {
self.contrast = amount.max(0.0);
self
}
pub fn saturate(mut self, amount: f32) -> Self {
self.saturate = amount.max(0.0);
self
}
pub fn hue_rotate(mut self, degrees: f32) -> Self {
self.hue_rotate = degrees % 360.0;
self
}
pub fn invert(mut self, amount: f32) -> Self {
self.invert = amount.clamp(0.0, 1.0);
self
}
pub fn blur(mut self, radius: f32) -> Self {
self.blur = radius.max(0.0);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct ImageStyle {
pub object_fit: ObjectFit,
pub object_position: ObjectPosition,
pub opacity: f32,
pub border_radius: f32,
pub filter: ImageFilter,
pub tint: [f32; 4],
}
impl ImageStyle {
pub fn new() -> Self {
Self {
object_fit: ObjectFit::default(),
object_position: ObjectPosition::CENTER,
opacity: 1.0,
border_radius: 0.0,
filter: ImageFilter::none(),
tint: [1.0, 1.0, 1.0, 1.0],
}
}
pub fn fit(mut self, fit: ObjectFit) -> Self {
self.object_fit = fit;
self
}
pub fn position(mut self, position: ObjectPosition) -> Self {
self.object_position = position;
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
pub fn rounded(mut self, radius: f32) -> Self {
self.border_radius = radius;
self
}
pub fn filter(mut self, filter: ImageFilter) -> Self {
self.filter = filter;
self
}
pub fn tint(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.tint = [r, g, b, a];
self
}
}
pub fn calculate_fit_rects(
image_width: u32,
image_height: u32,
container_width: f32,
container_height: f32,
fit: ObjectFit,
position: ObjectPosition,
) -> ([f32; 4], [f32; 4]) {
let img_w = image_width as f32;
let img_h = image_height as f32;
match fit {
ObjectFit::Fill => {
(
[0.0, 0.0, img_w, img_h],
[0.0, 0.0, container_width, container_height],
)
}
ObjectFit::Contain => {
let scale = (container_width / img_w).min(container_height / img_h);
let dst_w = img_w * scale;
let dst_h = img_h * scale;
let dst_x = (container_width - dst_w) * position.x;
let dst_y = (container_height - dst_h) * position.y;
([0.0, 0.0, img_w, img_h], [dst_x, dst_y, dst_w, dst_h])
}
ObjectFit::Cover => {
let scale = (container_width / img_w).max(container_height / img_h);
let src_w = container_width / scale;
let src_h = container_height / scale;
let src_x = (img_w - src_w) * position.x;
let src_y = (img_h - src_h) * position.y;
(
[src_x, src_y, src_w, src_h],
[0.0, 0.0, container_width, container_height],
)
}
ObjectFit::ScaleDown => {
let scale = (container_width / img_w)
.min(container_height / img_h)
.min(1.0);
let dst_w = img_w * scale;
let dst_h = img_h * scale;
let dst_x = (container_width - dst_w) * position.x;
let dst_y = (container_height - dst_h) * position.y;
([0.0, 0.0, img_w, img_h], [dst_x, dst_y, dst_w, dst_h])
}
ObjectFit::None => {
let dst_x = (container_width - img_w) * position.x;
let dst_y = (container_height - img_h) * position.y;
([0.0, 0.0, img_w, img_h], [dst_x, dst_y, img_w, img_h])
}
}
}
pub fn src_rect_to_uv(src_rect: [f32; 4], image_width: u32, image_height: u32) -> [f32; 4] {
let img_w = image_width as f32;
let img_h = image_height as f32;
[
src_rect[0] / img_w, src_rect[1] / img_h, (src_rect[0] + src_rect[2]) / img_w, (src_rect[1] + src_rect[3]) / img_h, ]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_fit_contain() {
let (src, dst) = calculate_fit_rects(
100,
50,
200.0,
200.0,
ObjectFit::Contain,
ObjectPosition::CENTER,
);
assert_eq!(src, [0.0, 0.0, 100.0, 50.0]);
assert_eq!(dst, [0.0, 50.0, 200.0, 100.0]);
}
#[test]
fn test_object_fit_cover() {
let (src, dst) = calculate_fit_rects(
100,
50,
200.0,
200.0,
ObjectFit::Cover,
ObjectPosition::CENTER,
);
assert_eq!(src, [25.0, 0.0, 50.0, 50.0]);
assert_eq!(dst, [0.0, 0.0, 200.0, 200.0]);
}
#[test]
fn test_object_fit_fill() {
let (src, dst) = calculate_fit_rects(
100,
50,
200.0,
200.0,
ObjectFit::Fill,
ObjectPosition::CENTER,
);
assert_eq!(src, [0.0, 0.0, 100.0, 50.0]);
assert_eq!(dst, [0.0, 0.0, 200.0, 200.0]);
}
#[test]
fn test_src_rect_to_uv() {
let src_rect = [25.0, 0.0, 50.0, 50.0];
let uv = src_rect_to_uv(src_rect, 100, 50);
assert_eq!(uv[0], 0.25); assert_eq!(uv[1], 0.0); assert_eq!(uv[2], 0.75); assert_eq!(uv[3], 1.0); }
#[test]
fn test_image_filter_chain() {
let filter = ImageFilter::none().grayscale(0.5).brightness(1.2).blur(5.0);
assert_eq!(filter.grayscale, 0.5);
assert_eq!(filter.brightness, 1.2);
assert_eq!(filter.blur, 5.0);
}
}