#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CropRect {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
impl CropRect {
pub fn new(x: u32, y: u32, w: u32, h: u32) -> Self {
Self { x, y, w, h }
}
pub fn width(&self) -> u32 {
self.w
}
pub fn height(&self) -> u32 {
self.h
}
pub fn area(&self) -> u64 {
self.w as u64 * self.h as u64
}
pub fn is_valid(&self) -> bool {
self.w > 0 && self.h > 0
}
pub fn fits_in(&self, frame_w: u32, frame_h: u32) -> bool {
self.x + self.w <= frame_w && self.y + self.h <= frame_h
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CropMode {
Manual,
Centered,
AspectRatio,
Smart,
}
impl CropMode {
pub fn preserves_aspect_ratio(&self) -> bool {
matches!(self, CropMode::AspectRatio | CropMode::Smart)
}
}
fn compute_saliency(pixels: &[u8], w: u32, h: u32) -> Vec<f32> {
let w = w as usize;
let h = h as usize;
let len = w * h;
if len == 0 {
return Vec::new();
}
let mut grad = vec![0.0f32; len];
let mut max_grad = 0.0f32;
for y in 1..h.saturating_sub(1) {
for x in 1..w.saturating_sub(1) {
let get = |dy: isize, dx: isize| -> f32 {
let row = (y as isize + dy) as usize;
let col = (x as isize + dx) as usize;
pixels[row * w + col] as f32 / 255.0
};
let gx = -get(-1, -1) - 2.0 * get(0, -1) - get(1, -1)
+ get(-1, 1)
+ 2.0 * get(0, 1)
+ get(1, 1);
let gy = -get(-1, -1) - 2.0 * get(-1, 0) - get(-1, 1)
+ get(1, -1)
+ 2.0 * get(1, 0)
+ get(1, 1);
let mag = gx.abs() + gy.abs(); grad[y * w + x] = mag;
if mag > max_grad {
max_grad = mag;
}
}
}
if max_grad > 1e-3 {
for g in &mut grad {
*g /= max_grad;
}
} else {
for g in &mut grad {
*g = 0.0;
}
}
let cx = w as f32 / 2.0;
let cy = h as f32 / 2.0;
let sigma = 0.3 * (w.min(h)) as f32;
let two_sigma2 = 2.0 * sigma * sigma;
let mut saliency = vec![0.0f32; len];
for y in 0..h {
for x in 0..w {
let dx = x as f32 - cx;
let dy = y as f32 - cy;
let weight = (-(dx * dx + dy * dy) / two_sigma2).exp();
saliency[y * w + x] = grad[y * w + x] * weight;
}
}
saliency
}
pub fn smart_crop(pixels: &[u8], src_w: u32, src_h: u32, crop_w: u32, crop_h: u32) -> CropRect {
let crop_w = crop_w.min(src_w);
let crop_h = crop_h.min(src_h);
if crop_w == src_w && crop_h == src_h {
return CropRect::new(0, 0, crop_w, crop_h);
}
let sw = src_w as usize;
let sh = src_h as usize;
let cw = crop_w as usize;
let ch = crop_h as usize;
let saliency = compute_saliency(pixels, src_w, src_h);
let ii_w = sw + 1;
let ii_h = sh + 1;
let mut ii = vec![0.0f64; ii_w * ii_h];
for y in 1..ii_h {
for x in 1..ii_w {
ii[y * ii_w + x] = saliency[(y - 1) * sw + (x - 1)] as f64
+ ii[(y - 1) * ii_w + x]
+ ii[y * ii_w + (x - 1)]
- ii[(y - 1) * ii_w + (x - 1)];
}
}
let max_ox = sw.saturating_sub(cw);
let max_oy = sh.saturating_sub(ch);
let mut best_x = max_ox / 2;
let mut best_y = max_oy / 2;
let mut best_sum = f64::NEG_INFINITY;
for oy in 0..=max_oy {
for ox in 0..=max_ox {
let x0 = ox;
let y0 = oy;
let x1 = ox + cw;
let y1 = oy + ch;
let sum =
ii[y1 * ii_w + x1] - ii[y0 * ii_w + x1] - ii[y1 * ii_w + x0] + ii[y0 * ii_w + x0];
if sum > best_sum {
best_sum = sum;
best_x = ox;
best_y = oy;
}
}
}
CropRect::new(best_x as u32, best_y as u32, crop_w, crop_h)
}
#[derive(Debug, Clone)]
pub struct CropOperation {
pub rect: CropRect,
pub mode: CropMode,
}
impl CropOperation {
pub fn new(rect: CropRect, mode: CropMode) -> Self {
Self { rect, mode }
}
pub fn apply_to_dimensions(&self, src_w: u32, src_h: u32) -> Option<(u32, u32)> {
if !self.rect.fits_in(src_w, src_h) || !self.rect.is_valid() {
return None;
}
Some((self.rect.w, self.rect.h))
}
pub fn centered(src_w: u32, src_h: u32, target_w: u32, target_h: u32) -> Option<CropRect> {
if target_w > src_w || target_h > src_h {
return None;
}
let x = (src_w - target_w) / 2;
let y = (src_h - target_h) / 2;
Some(CropRect::new(x, y, target_w, target_h))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crop_rect_dimensions() {
let r = CropRect::new(0, 0, 1920, 1080);
assert_eq!(r.width(), 1920);
assert_eq!(r.height(), 1080);
}
#[test]
fn test_crop_rect_area() {
let r = CropRect::new(0, 0, 100, 50);
assert_eq!(r.area(), 5000);
}
#[test]
fn test_crop_rect_is_valid_true() {
let r = CropRect::new(10, 10, 100, 100);
assert!(r.is_valid());
}
#[test]
fn test_crop_rect_is_valid_zero_width() {
let r = CropRect::new(0, 0, 0, 100);
assert!(!r.is_valid());
}
#[test]
fn test_crop_rect_is_valid_zero_height() {
let r = CropRect::new(0, 0, 100, 0);
assert!(!r.is_valid());
}
#[test]
fn test_fits_in_true() {
let r = CropRect::new(0, 0, 1920, 1080);
assert!(r.fits_in(1920, 1080));
assert!(r.fits_in(3840, 2160));
}
#[test]
fn test_fits_in_false() {
let r = CropRect::new(100, 0, 1920, 1080);
assert!(!r.fits_in(1920, 1080));
}
#[test]
fn test_crop_mode_preserves_aspect_ratio() {
assert!(CropMode::AspectRatio.preserves_aspect_ratio());
assert!(CropMode::Smart.preserves_aspect_ratio());
assert!(!CropMode::Manual.preserves_aspect_ratio());
assert!(!CropMode::Centered.preserves_aspect_ratio());
}
#[test]
fn test_apply_to_dimensions_valid() {
let rect = CropRect::new(0, 0, 640, 480);
let op = CropOperation::new(rect, CropMode::Manual);
assert_eq!(op.apply_to_dimensions(1920, 1080), Some((640, 480)));
}
#[test]
fn test_apply_to_dimensions_out_of_bounds() {
let rect = CropRect::new(0, 0, 2000, 1080);
let op = CropOperation::new(rect, CropMode::Manual);
assert!(op.apply_to_dimensions(1920, 1080).is_none());
}
#[test]
fn test_centered_crop() {
let rect = CropOperation::centered(1920, 1080, 1280, 720).expect("should succeed in test");
assert_eq!(rect.x, 320);
assert_eq!(rect.y, 180);
assert_eq!(rect.w, 1280);
assert_eq!(rect.h, 720);
}
#[test]
fn test_centered_crop_too_large() {
assert!(CropOperation::centered(1280, 720, 1920, 1080).is_none());
}
#[test]
fn test_smart_crop_exact_size() {
let pixels = vec![128u8; 100 * 80];
let rect = smart_crop(&pixels, 100, 80, 100, 80);
assert_eq!(rect.x, 0);
assert_eq!(rect.y, 0);
assert_eq!(rect.w, 100);
assert_eq!(rect.h, 80);
}
#[test]
fn test_smart_crop_clamps_to_source() {
let pixels = vec![128u8; 50 * 50];
let rect = smart_crop(&pixels, 50, 50, 200, 200);
assert!(rect.fits_in(50, 50));
}
#[test]
fn test_smart_crop_uniform_image_returns_valid_rect() {
let pixels = vec![128u8; 100 * 100];
let rect = smart_crop(&pixels, 100, 100, 60, 60);
assert!(rect.is_valid());
assert!(rect.fits_in(100, 100));
}
#[test]
fn test_smart_crop_prefers_high_saliency_region() {
let w = 20usize;
let h = 20usize;
let mut pixels = vec![0u8; w * h];
for y in 0..6 {
for x in 0..6 {
pixels[y * w + x] = 255;
}
}
let rect = smart_crop(&pixels, w as u32, h as u32, 10, 10);
assert!(
rect.x <= 10,
"expected crop to favour left side, got x={}",
rect.x
);
assert!(rect.fits_in(w as u32, h as u32));
}
#[test]
fn test_compute_saliency_uniform() {
let pixels = vec![128u8; 10 * 10];
let s = compute_saliency(&pixels, 10, 10);
for &v in &s {
assert!(v.abs() < f32::EPSILON, "expected zero saliency, got {v}");
}
}
}