Skip to main content

hdim_core/transform/
mod.rs

1use crate::state::TransformState;
2use image::{DynamicImage, GenericImageView};
3
4pub fn apply_transform(image: &DynamicImage, transform: &TransformState) -> DynamicImage {
5    let mut transformed = image.clone();
6
7    // 1. Rotation
8    match transform.rotation {
9        90 => transformed = transformed.rotate90(),
10        180 => transformed = transformed.rotate180(),
11        270 => transformed = transformed.rotate270(),
12        _ => {}
13    }
14
15    // 2. Flip
16    if transform.flip_horizontal {
17        transformed = transformed.fliph();
18    }
19    if transform.flip_vertical {
20        transformed = transformed.flipv();
21    }
22
23    // 3. Crop
24    let (width, height) = transformed.dimensions();
25
26    // We don't have a way to store "is_percent" in TransformState yet,
27    // but the UI could pass very large values or we could change TransformState.
28    // However, the task said "Implement relative (%) and absolute (px) cropping (detect % in input)".
29    // Since TransformState only stores u32, I might need to change it or use a convention.
30    // Let's assume values > 10000 might be percentages * 100? No, that's messy.
31
32    // Better: change TransformState to store strings or have separate fields,
33    // or just handle it in the UI and convert to absolute pixels before calling this.
34    // But the requirement says "hdim-core: Implement... detect % in input".
35    // This implies hdim-core should handle the detection if we pass it a string,
36    // or we have a more complex state.
37
38    // Let's stick to absolute pixels for now as requested by the current struct,
39    // but I'll add a helper that takes the original dimensions.
40
41    let left = transform.left.min(width);
42    let right = transform.right.min(width - left);
43    let top = transform.top.min(height);
44    let bottom = transform.bottom.min(height - top);
45
46    if left > 0 || right > 0 || top > 0 || bottom > 0 {
47        let crop_width = width.saturating_sub(left).saturating_sub(right);
48        let crop_height = height.saturating_sub(top).saturating_sub(bottom);
49        if crop_width > 0 && crop_height > 0 {
50            transformed = transformed.crop_imm(left, top, crop_width, crop_height);
51        }
52    }
53
54    transformed
55}
56
57pub fn calculate_absolute_crop(value_str: &str, total: u32) -> u32 {
58    if let Some(stripped) = value_str.strip_suffix('%')
59        && let Ok(percent) = stripped.parse::<f32>()
60    {
61        return ((percent / 100.0) * total as f32).round() as u32;
62    }
63    value_str.parse::<u32>().unwrap_or(0)
64}
65
66/// Helper function to perform flip horizontally.
67pub fn flip_horizontal(image: &DynamicImage) -> DynamicImage {
68    image.fliph()
69}
70
71/// Helper function to perform flip vertically.
72pub fn flip_vertical(image: &DynamicImage) -> DynamicImage {
73    image.flipv()
74}
75
76/// Helper function to rotate 90 degrees clockwise.
77pub fn rotate_90(image: &DynamicImage) -> DynamicImage {
78    image.rotate90()
79}
80
81/// Helper function to rotate 270 degrees clockwise (90 degrees counter-clockwise).
82pub fn rotate_270(image: &DynamicImage) -> DynamicImage {
83    image.rotate270()
84}