#![warn(missing_docs, rust_2018_idioms, unreachable_pub, unsafe_code)]
pub mod adaptive_scaling;
pub mod aspect_preserve;
pub mod aspect_ratio;
pub mod batch_scale;
pub mod bicubic;
pub mod chroma_scale;
pub mod content_aware_scale;
pub mod crop;
pub mod crop_scale;
pub mod deinterlace;
pub mod ewa_resample;
pub mod field_scale;
pub mod half_pixel;
pub mod lanczos;
pub mod nearest_neighbor;
pub mod pad;
pub mod pad_scale;
pub mod perceptual_sharpening;
pub mod quality_metric;
pub mod quality_metrics;
pub mod resampler;
pub mod resolution_ladder;
pub mod roi_scale;
pub mod scale_config;
pub mod scale_filter;
pub mod scale_pipeline;
pub mod sharpness_scale;
pub mod super_res;
pub mod super_resolution;
pub mod thumbnail;
pub mod tile;
pub mod seam_carve;
#[allow(unsafe_code)]
pub mod simd_interp;
pub mod aspect_ratio_crop;
pub mod edge_directed_interpolation;
pub mod film_grain_scale;
pub mod hdr_scaling;
pub mod multi_pass_scale;
pub mod negotiate;
pub mod neural_upscale;
pub mod padding;
pub mod parallel_scale;
pub mod quality_regression;
pub mod resolution_recommender;
pub mod ring_buffer_cache;
pub mod scale_preview;
pub mod sharpness;
pub mod temporal_scaling;
pub mod thumbnail_generator;
pub mod watermark_safe_scale;
pub use ewa_resample::{lanczos_kernel, mitchell_filter, sinc, EwaFilter, EwaResampler};
pub use half_pixel::{
bilinear_interp, cubic_interp, cubic_interp_2d, CoordinateMapper, HalfPixelMode, ScaleInterp,
ScaleKernel,
};
pub use nearest_neighbor::{NearestNeighborConfig, NearestNeighborScaler};
pub use perceptual_sharpening::{
gaussian_blur_1d, local_laplacian, sharpen, AdaptiveSharpener, CasSharpener, SharpnessMode,
UnsharpMask,
};
pub use resolution_ladder::{
compute_optimal_ladder, ContentDifficultyScore, OptimalRung, PerTitleLadder, PerceptualLadder,
QualityTarget, RungSelector,
};
pub use seam_carve::{EnergyFunction, ScalingError, SeamCarver, SeamCarvingConfig};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScalingMode {
Bilinear,
Bicubic,
Lanczos,
NearestNeighbor,
}
impl fmt::Display for ScalingMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bilinear => write!(f, "Bilinear"),
Self::Bicubic => write!(f, "Bicubic"),
Self::Lanczos => write!(f, "Lanczos"),
Self::NearestNeighbor => write!(f, "NearestNeighbor"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AspectRatioMode {
Stretch,
Letterbox,
Crop,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PixelAspectRatio {
pub num: u32,
pub den: u32,
}
impl PixelAspectRatio {
pub fn new(num: u32, den: u32) -> Self {
Self {
num,
den: den.max(1),
}
}
pub fn square() -> Self {
Self { num: 1, den: 1 }
}
pub fn ntsc_4_3() -> Self {
Self { num: 10, den: 11 }
}
pub fn ntsc_16_9() -> Self {
Self { num: 40, den: 33 }
}
pub fn pal_4_3() -> Self {
Self { num: 12, den: 11 }
}
pub fn pal_16_9() -> Self {
Self { num: 16, den: 11 }
}
pub fn to_float(&self) -> f64 {
self.num as f64 / self.den as f64
}
pub fn is_square(&self) -> bool {
self.num == self.den
}
}
impl Default for PixelAspectRatio {
fn default() -> Self {
Self::square()
}
}
impl fmt::Display for PixelAspectRatio {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.num, self.den)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalingParams {
pub width: u32,
pub height: u32,
pub mode: ScalingMode,
pub aspect_ratio: AspectRatioMode,
pub source_par: PixelAspectRatio,
}
impl ScalingParams {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
mode: ScalingMode::Lanczos,
aspect_ratio: AspectRatioMode::Letterbox,
source_par: PixelAspectRatio::square(),
}
}
pub fn with_mode(mut self, mode: ScalingMode) -> Self {
self.mode = mode;
self
}
pub fn with_aspect_ratio(mut self, mode: AspectRatioMode) -> Self {
self.aspect_ratio = mode;
self
}
pub fn with_source_par(mut self, par: PixelAspectRatio) -> Self {
self.source_par = par;
self
}
}
pub struct VideoScaler {
params: ScalingParams,
}
impl VideoScaler {
pub fn new(params: ScalingParams) -> Self {
Self { params }
}
pub fn params(&self) -> &ScalingParams {
&self.params
}
pub fn calculate_dimensions(&self, src_width: u32, src_height: u32) -> (u32, u32) {
self.calculate_dimensions_with_par(src_width, src_height, &self.params.source_par)
}
pub fn calculate_dimensions_with_par(
&self,
src_width: u32,
src_height: u32,
par: &PixelAspectRatio,
) -> (u32, u32) {
match self.params.aspect_ratio {
AspectRatioMode::Stretch => (self.params.width, self.params.height),
AspectRatioMode::Letterbox | AspectRatioMode::Crop => {
let display_width = src_width as f64 * par.to_float();
let src_aspect = display_width / src_height as f64;
let dst_aspect = self.params.width as f64 / self.params.height as f64;
if (src_aspect - dst_aspect).abs() < f64::EPSILON {
(self.params.width, self.params.height)
} else if src_aspect > dst_aspect {
let w = (self.params.height as f64 * src_aspect) as u32;
(w, self.params.height)
} else {
let h = (self.params.width as f64 / src_aspect) as u32;
(self.params.width, h)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scaling_params_creation() {
let params = ScalingParams::new(1920, 1080);
assert_eq!(params.width, 1920);
assert_eq!(params.height, 1080);
assert_eq!(params.mode, ScalingMode::Lanczos);
assert!(params.source_par.is_square());
}
#[test]
fn test_scaling_mode_with_builder() {
let params = ScalingParams::new(1920, 1080)
.with_mode(ScalingMode::Bicubic)
.with_aspect_ratio(AspectRatioMode::Crop);
assert_eq!(params.mode, ScalingMode::Bicubic);
assert_eq!(params.aspect_ratio, AspectRatioMode::Crop);
}
#[test]
fn test_scaling_mode_nearest_neighbor() {
let params = ScalingParams::new(640, 480).with_mode(ScalingMode::NearestNeighbor);
assert_eq!(params.mode, ScalingMode::NearestNeighbor);
}
#[test]
fn test_scaler_creation() {
let params = ScalingParams::new(1920, 1080);
let scaler = VideoScaler::new(params);
assert_eq!(scaler.params().width, 1920);
}
#[test]
fn test_calculate_dimensions_stretch() {
let params = ScalingParams::new(1920, 1080).with_aspect_ratio(AspectRatioMode::Stretch);
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(3840, 2160);
assert_eq!((w, h), (1920, 1080));
}
#[test]
fn test_calculate_dimensions_letterbox() {
let params = ScalingParams::new(1920, 1080).with_aspect_ratio(AspectRatioMode::Letterbox);
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(1024, 768);
assert_eq!(w, 1920);
assert_eq!(h, 1440);
}
#[test]
fn test_scaling_mode_display() {
assert_eq!(ScalingMode::Bilinear.to_string(), "Bilinear");
assert_eq!(ScalingMode::Bicubic.to_string(), "Bicubic");
assert_eq!(ScalingMode::Lanczos.to_string(), "Lanczos");
assert_eq!(ScalingMode::NearestNeighbor.to_string(), "NearestNeighbor");
}
#[test]
fn test_par_square_default() {
let par = PixelAspectRatio::default();
assert!(par.is_square());
assert!((par.to_float() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_par_ntsc_4_3() {
let par = PixelAspectRatio::ntsc_4_3();
assert_eq!(par.num, 10);
assert_eq!(par.den, 11);
assert!(!par.is_square());
assert!((par.to_float() - 10.0 / 11.0).abs() < 1e-6);
}
#[test]
fn test_par_display() {
let par = PixelAspectRatio::new(16, 11);
assert_eq!(par.to_string(), "16:11");
}
#[test]
fn test_par_zero_den_clamped() {
let par = PixelAspectRatio::new(1, 0);
assert_eq!(par.den, 1);
}
#[test]
fn test_calculate_dimensions_square_par_same_as_no_par() {
let params = ScalingParams::new(1920, 1080)
.with_aspect_ratio(AspectRatioMode::Letterbox)
.with_source_par(PixelAspectRatio::square());
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(1024, 768);
assert_eq!(w, 1920);
assert_eq!(h, 1440);
}
#[test]
fn test_calculate_dimensions_ntsc_par_correction() {
let params = ScalingParams::new(1920, 1080)
.with_aspect_ratio(AspectRatioMode::Letterbox)
.with_source_par(PixelAspectRatio::ntsc_4_3());
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(720, 480);
assert_eq!(w, 1920);
assert!(
h > 1080,
"height {h} should exceed 1080 for 4:3 content in 16:9 target"
);
}
#[test]
fn test_calculate_dimensions_wide_par_correction() {
let params = ScalingParams::new(1920, 1080)
.with_aspect_ratio(AspectRatioMode::Letterbox)
.with_source_par(PixelAspectRatio::ntsc_16_9());
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(720, 480);
assert!(w >= 1920, "width {w} should be near 1920");
assert_eq!(h, 1080);
}
#[test]
fn test_calculate_dimensions_stretch_ignores_par() {
let params = ScalingParams::new(1920, 1080)
.with_aspect_ratio(AspectRatioMode::Stretch)
.with_source_par(PixelAspectRatio::ntsc_4_3());
let scaler = VideoScaler::new(params);
let (w, h) = scaler.calculate_dimensions(720, 480);
assert_eq!((w, h), (1920, 1080));
}
#[test]
fn test_calculate_dimensions_with_par_override() {
let params = ScalingParams::new(1920, 1080).with_aspect_ratio(AspectRatioMode::Letterbox);
let scaler = VideoScaler::new(params);
let par = PixelAspectRatio::pal_4_3();
let (w, h) = scaler.calculate_dimensions_with_par(720, 576, &par);
assert_eq!(w, 1920);
assert!(h > 1080);
}
#[test]
fn test_par_pal_16_9() {
let par = PixelAspectRatio::pal_16_9();
assert_eq!(par.num, 16);
assert_eq!(par.den, 11);
}
}