#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PadMode {
Color,
Replicate,
Mirror,
BlurredBackground,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PadColor {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl PadColor {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
pub const BLACK: PadColor = PadColor::new(0, 0, 0);
pub const WHITE: PadColor = PadColor::new(255, 255, 255);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PaddingConfig {
pub left: u32,
pub right: u32,
pub top: u32,
pub bottom: u32,
pub mode: PadMode,
pub color: PadColor,
}
impl PaddingConfig {
pub fn uniform(amount: u32, mode: PadMode) -> Self {
Self {
left: amount,
right: amount,
top: amount,
bottom: amount,
mode,
color: PadColor::BLACK,
}
}
pub fn symmetric(horizontal: u32, vertical: u32, mode: PadMode) -> Self {
Self {
left: horizontal,
right: horizontal,
top: vertical,
bottom: vertical,
mode,
color: PadColor::BLACK,
}
}
pub fn with_color(mut self, color: PadColor) -> Self {
self.color = color;
self
}
pub fn total_horizontal(&self) -> u32 {
self.left + self.right
}
pub fn total_vertical(&self) -> u32 {
self.top + self.bottom
}
}
#[derive(Debug, Clone)]
pub struct PadOperation {
pub config: PaddingConfig,
}
impl PadOperation {
pub fn new(config: PaddingConfig) -> Self {
Self { config }
}
pub fn output_dimensions(&self, src_w: u32, src_h: u32) -> (u32, u32) {
(
src_w + self.config.total_horizontal(),
src_h + self.config.total_vertical(),
)
}
pub fn letterbox(
src_w: u32,
src_h: u32,
target_w: u32,
target_h: u32,
) -> Option<PaddingConfig> {
if src_w > target_w || src_h > target_h {
return None;
}
let pad_v = target_h - src_h;
let top = pad_v / 2;
let bottom = pad_v - top;
Some(PaddingConfig {
left: (target_w - src_w) / 2,
right: target_w - src_w - (target_w - src_w) / 2,
top,
bottom,
mode: PadMode::Color,
color: PadColor::BLACK,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_color_black() {
assert_eq!(PadColor::BLACK, PadColor::new(0, 0, 0));
}
#[test]
fn test_pad_color_white() {
assert_eq!(PadColor::WHITE, PadColor::new(255, 255, 255));
}
#[test]
fn test_uniform_padding() {
let cfg = PaddingConfig::uniform(10, PadMode::Color);
assert_eq!(cfg.left, 10);
assert_eq!(cfg.right, 10);
assert_eq!(cfg.top, 10);
assert_eq!(cfg.bottom, 10);
}
#[test]
fn test_symmetric_padding() {
let cfg = PaddingConfig::symmetric(20, 5, PadMode::Replicate);
assert_eq!(cfg.total_horizontal(), 40);
assert_eq!(cfg.total_vertical(), 10);
}
#[test]
fn test_total_horizontal() {
let cfg = PaddingConfig {
left: 30,
right: 50,
top: 0,
bottom: 0,
mode: PadMode::Color,
color: PadColor::BLACK,
};
assert_eq!(cfg.total_horizontal(), 80);
}
#[test]
fn test_total_vertical() {
let cfg = PaddingConfig {
left: 0,
right: 0,
top: 45,
bottom: 45,
mode: PadMode::Color,
color: PadColor::BLACK,
};
assert_eq!(cfg.total_vertical(), 90);
}
#[test]
fn test_with_color() {
let cfg = PaddingConfig::uniform(0, PadMode::Color).with_color(PadColor::WHITE);
assert_eq!(cfg.color, PadColor::WHITE);
}
#[test]
fn test_output_dimensions() {
let cfg = PaddingConfig::symmetric(0, 45, PadMode::Color);
let op = PadOperation::new(cfg);
let (w, h) = op.output_dimensions(1920, 990);
assert_eq!(w, 1920);
assert_eq!(h, 1080);
}
#[test]
fn test_letterbox_valid() {
let cfg = PadOperation::letterbox(1920, 810, 1920, 1080).expect("should succeed in test");
assert_eq!(cfg.top + cfg.bottom, 270);
assert_eq!(cfg.left + cfg.right, 0);
}
#[test]
fn test_letterbox_exact_fit() {
let cfg = PadOperation::letterbox(1920, 1080, 1920, 1080).expect("should succeed in test");
assert_eq!(cfg.total_horizontal(), 0);
assert_eq!(cfg.total_vertical(), 0);
}
#[test]
fn test_letterbox_too_large() {
assert!(PadOperation::letterbox(3840, 2160, 1920, 1080).is_none());
}
#[test]
fn test_pad_mode_variants_exist() {
let modes = [
PadMode::Color,
PadMode::Replicate,
PadMode::Mirror,
PadMode::BlurredBackground,
];
assert_eq!(modes.len(), 4);
}
}