#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldOrder {
TopFieldFirst,
BottomFieldFirst,
Progressive,
}
impl fmt::Display for FieldOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::TopFieldFirst => "TFF",
Self::BottomFieldFirst => "BFF",
Self::Progressive => "PROG",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldInterpolation {
Nearest,
Bilinear,
Bicubic,
}
#[derive(Debug, Clone)]
pub struct FieldScaleConfig {
pub src_width: u32,
pub src_height: u32,
pub dst_width: u32,
pub dst_height: u32,
pub field_order: FieldOrder,
pub interpolation: FieldInterpolation,
pub anti_alias: bool,
}
impl FieldScaleConfig {
pub fn new(
src_width: u32,
src_height: u32,
dst_width: u32,
dst_height: u32,
field_order: FieldOrder,
) -> Self {
Self {
src_width,
src_height,
dst_width,
dst_height,
field_order,
interpolation: FieldInterpolation::Bilinear,
anti_alias: true,
}
}
pub fn with_interpolation(mut self, interp: FieldInterpolation) -> Self {
self.interpolation = interp;
self
}
pub fn with_anti_alias(mut self, enabled: bool) -> Self {
self.anti_alias = enabled;
self
}
pub fn is_interlaced(&self) -> bool {
self.field_order != FieldOrder::Progressive
}
pub fn field_height(&self, frame_height: u32) -> u32 {
frame_height / 2
}
}
#[allow(clippy::cast_precision_loss)]
pub fn separate_fields(frame: &[u8], width: u32, height: u32, stride: u32) -> (Vec<u8>, Vec<u8>) {
let field_h = height / 2;
let row_bytes = width as usize;
let mut top = Vec::with_capacity(field_h as usize * row_bytes);
let mut bottom = Vec::with_capacity(field_h as usize * row_bytes);
for y in 0..height {
let start = (y * stride) as usize;
let end = start + row_bytes;
if end > frame.len() {
break;
}
let row = &frame[start..end];
if y % 2 == 0 {
top.extend_from_slice(row);
} else {
bottom.extend_from_slice(row);
}
}
(top, bottom)
}
pub fn interleave_fields(top: &[u8], bottom: &[u8], width: u32, height: u32) -> Vec<u8> {
let row_bytes = width as usize;
let field_h = (height / 2) as usize;
let mut frame = vec![0u8; (width * height) as usize];
for fy in 0..field_h {
let src_off = fy * row_bytes;
let dst_top = fy * 2 * row_bytes;
if src_off + row_bytes <= top.len() && dst_top + row_bytes <= frame.len() {
frame[dst_top..dst_top + row_bytes].copy_from_slice(&top[src_off..src_off + row_bytes]);
}
let dst_bot = (fy * 2 + 1) * row_bytes;
if src_off + row_bytes <= bottom.len() && dst_bot + row_bytes <= frame.len() {
frame[dst_bot..dst_bot + row_bytes]
.copy_from_slice(&bottom[src_off..src_off + row_bytes]);
}
}
frame
}
#[allow(clippy::cast_precision_loss)]
pub fn scale_field_bilinear(
src: &[u8],
src_width: u32,
src_height: u32,
dst_width: u32,
dst_height: u32,
) -> Vec<u8> {
let mut dst = vec![0u8; (dst_width * dst_height) as usize];
let x_ratio = if dst_width > 1 {
(src_width as f64 - 1.0) / (dst_width as f64 - 1.0)
} else {
0.0
};
let y_ratio = if dst_height > 1 {
(src_height as f64 - 1.0) / (dst_height as f64 - 1.0)
} else {
0.0
};
for dy in 0..dst_height {
for dx in 0..dst_width {
let sx = x_ratio * dx as f64;
let sy = y_ratio * dy as f64;
let x0 = sx.floor() as u32;
let y0 = sy.floor() as u32;
let x1 = (x0 + 1).min(src_width - 1);
let y1 = (y0 + 1).min(src_height - 1);
let xf = sx - sx.floor();
let yf = sy - sy.floor();
let idx = |x: u32, y: u32| -> u8 {
let i = (y * src_width + x) as usize;
if i < src.len() {
src[i]
} else {
0
}
};
let top_l = idx(x0, y0) as f64;
let top_r = idx(x1, y0) as f64;
let bot_l = idx(x0, y1) as f64;
let bot_r = idx(x1, y1) as f64;
let top = top_l * (1.0 - xf) + top_r * xf;
let bot = bot_l * (1.0 - xf) + bot_r * xf;
let val = top * (1.0 - yf) + bot * yf;
dst[(dy * dst_width + dx) as usize] = val.round().min(255.0).max(0.0) as u8;
}
}
dst
}
#[allow(clippy::cast_precision_loss)]
pub fn field_scale(frame: &[u8], config: &FieldScaleConfig) -> Vec<u8> {
if !config.is_interlaced() {
return scale_field_bilinear(
frame,
config.src_width,
config.src_height,
config.dst_width,
config.dst_height,
);
}
let (top, bottom) =
separate_fields(frame, config.src_width, config.src_height, config.src_width);
let src_fh = config.field_height(config.src_height);
let dst_fh = config.field_height(config.dst_height);
let scaled_top = scale_field_bilinear(&top, config.src_width, src_fh, config.dst_width, dst_fh);
let scaled_bottom =
scale_field_bilinear(&bottom, config.src_width, src_fh, config.dst_width, dst_fh);
interleave_fields(
&scaled_top,
&scaled_bottom,
config.dst_width,
config.dst_height,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_order_display() {
assert_eq!(FieldOrder::TopFieldFirst.to_string(), "TFF");
assert_eq!(FieldOrder::BottomFieldFirst.to_string(), "BFF");
assert_eq!(FieldOrder::Progressive.to_string(), "PROG");
}
#[test]
fn test_config_is_interlaced() {
let cfg = FieldScaleConfig::new(720, 480, 1920, 1080, FieldOrder::TopFieldFirst);
assert!(cfg.is_interlaced());
let cfg2 = FieldScaleConfig::new(720, 480, 1920, 1080, FieldOrder::Progressive);
assert!(!cfg2.is_interlaced());
}
#[test]
fn test_field_height() {
let cfg = FieldScaleConfig::new(720, 480, 1920, 1080, FieldOrder::TopFieldFirst);
assert_eq!(cfg.field_height(480), 240);
assert_eq!(cfg.field_height(1080), 540);
}
#[test]
fn test_config_builder() {
let cfg = FieldScaleConfig::new(720, 480, 1920, 1080, FieldOrder::TopFieldFirst)
.with_interpolation(FieldInterpolation::Bicubic)
.with_anti_alias(false);
assert_eq!(cfg.interpolation, FieldInterpolation::Bicubic);
assert!(!cfg.anti_alias);
}
#[test]
fn test_separate_fields() {
let frame: Vec<u8> = (0..16).collect();
let (top, bottom) = separate_fields(&frame, 4, 4, 4);
assert_eq!(top, vec![0, 1, 2, 3, 8, 9, 10, 11]);
assert_eq!(bottom, vec![4, 5, 6, 7, 12, 13, 14, 15]);
}
#[test]
fn test_interleave_fields() {
let top = vec![0, 1, 2, 3, 8, 9, 10, 11];
let bottom = vec![4, 5, 6, 7, 12, 13, 14, 15];
let frame = interleave_fields(&top, &bottom, 4, 4);
let expected: Vec<u8> = (0..16).collect();
assert_eq!(frame, expected);
}
#[test]
fn test_roundtrip_separate_interleave() {
let frame: Vec<u8> = (0..64).collect();
let (top, bottom) = separate_fields(&frame, 8, 8, 8);
let result = interleave_fields(&top, &bottom, 8, 8);
assert_eq!(frame, result);
}
#[test]
fn test_scale_field_identity() {
let field = vec![10, 20, 30, 40];
let scaled = scale_field_bilinear(&field, 2, 2, 2, 2);
assert_eq!(scaled, field);
}
#[test]
fn test_scale_field_upscale() {
let field = vec![0, 100, 0, 100];
let scaled = scale_field_bilinear(&field, 2, 2, 4, 4);
assert_eq!(scaled.len(), 16);
assert_eq!(scaled[0], 0);
assert_eq!(scaled[3], 100);
}
#[test]
fn test_scale_field_downscale() {
let field = vec![
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
];
let scaled = scale_field_bilinear(&field, 4, 4, 2, 2);
assert_eq!(scaled.len(), 4);
assert_eq!(scaled[0], 10);
assert_eq!(scaled[1], 40);
}
#[test]
fn test_field_scale_progressive() {
let frame = vec![10, 20, 30, 40];
let cfg = FieldScaleConfig::new(2, 2, 2, 2, FieldOrder::Progressive);
let result = field_scale(&frame, &cfg);
assert_eq!(result, frame);
}
#[test]
fn test_field_scale_interlaced_roundtrip_size() {
let frame: Vec<u8> = (0..16).collect();
let cfg = FieldScaleConfig::new(4, 4, 4, 4, FieldOrder::TopFieldFirst);
let result = field_scale(&frame, &cfg);
assert_eq!(result.len(), 16);
}
#[test]
fn test_field_scale_upscale_interlaced() {
let frame: Vec<u8> = vec![128; 16];
let cfg = FieldScaleConfig::new(4, 4, 8, 8, FieldOrder::BottomFieldFirst);
let result = field_scale(&frame, &cfg);
assert_eq!(result.len(), 64);
for &v in &result {
assert_eq!(v, 128);
}
}
}