use crate::pixmap::Pixmap;
pub const DET_MAX_SIDE: u32 = 960;
pub const DET_STRIDE: u32 = 32;
pub const DET_MEAN: [f32; 3] = [0.485, 0.456, 0.406];
pub const DET_STD: [f32; 3] = [0.229, 0.224, 0.225];
pub fn det_input_size(width: u32, height: u32) -> (u32, u32) {
let long = width.max(height).max(1);
let scale_dim = |dim: u32| -> u32 {
let scaled = if long <= DET_MAX_SIDE {
u64::from(dim)
} else {
(u64::from(dim) * u64::from(DET_MAX_SIDE) + u64::from(long) / 2) / u64::from(long)
};
let stride = u64::from(DET_STRIDE);
let rounded = ((scaled + stride / 2) / stride) * stride;
rounded.max(stride) as u32
};
(scale_dim(width), scale_dim(height))
}
struct AxisTap {
i0: usize,
i1: usize,
frac: u64,
}
fn axis_taps(src_len: u32, dst_len: u32) -> Vec<AxisTap> {
let src = i64::from(src_len);
let dst = i64::from(dst_len);
let max_pos = (src - 1) << 16;
(0..dst)
.map(|d| {
let pos = (((2 * d + 1) * src) << 15) / dst - (1 << 15);
let pos = pos.clamp(0, max_pos);
let i0 = (pos >> 16) as usize;
AxisTap {
i0,
i1: (i0 + 1).min(src_len as usize - 1),
frac: (pos & 0xffff) as u64,
}
})
.collect()
}
pub fn resize_bilinear_rgb(src: &Pixmap, dst_w: u32, dst_h: u32) -> Vec<u8> {
assert!(dst_w > 0 && dst_h > 0, "destination size must be non-zero");
let sw = src.width.max(1) as usize;
let xtaps = axis_taps(src.width.max(1), dst_w);
let ytaps = axis_taps(src.height.max(1), dst_h);
let mut out = Vec::with_capacity(dst_w as usize * dst_h as usize * 3);
for ty in &ytaps {
let row0 = &src.data[ty.i0 * sw * 4..(ty.i0 * sw + sw) * 4];
let row1 = &src.data[ty.i1 * sw * 4..(ty.i1 * sw + sw) * 4];
let (fy, inv_fy) = (ty.frac, 65536 - ty.frac);
for tx in &xtaps {
let (fx, inv_fx) = (tx.frac, 65536 - tx.frac);
let (p00, p01) = (tx.i0 * 4, tx.i1 * 4);
for c in 0..3 {
let top = u64::from(row0[p00 + c]) * inv_fx + u64::from(row0[p01 + c]) * fx;
let bot = u64::from(row1[p00 + c]) * inv_fx + u64::from(row1[p01 + c]) * fx;
let v = (top * inv_fy + bot * fy + (1 << 31)) >> 32;
out.push(v as u8);
}
}
}
out
}
pub fn det_tensor(src: &Pixmap, dst_w: u32, dst_h: u32) -> Vec<f32> {
let rgb = resize_bilinear_rgb(src, dst_w, dst_h);
let plane = dst_w as usize * dst_h as usize;
let mut out = vec![0.0f32; 3 * plane];
for (i, px) in rgb.as_chunks::<3>().0.iter().enumerate() {
for c in 0..3 {
out[c * plane + i] = (f32::from(px[c]) / 255.0 - DET_MEAN[c]) / DET_STD[c];
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn det_input_size_keeps_small_pages_and_rounds_to_stride() {
assert_eq!(det_input_size(640, 480), (640, 480));
assert_eq!(det_input_size(650, 470), (640, 480));
assert_eq!(det_input_size(10, 10), (32, 32));
}
#[test]
fn det_input_size_scales_long_side_down_to_cap() {
assert_eq!(det_input_size(1000, 500), (960, 480));
let (w, h) = det_input_size(4000, 40);
assert_eq!(w, 960);
assert_eq!(h, 32);
assert_eq!(det_input_size(320, 960), (320, 960));
}
#[test]
fn det_input_size_is_deterministic_and_stride_aligned() {
for &(w, h) in &[(1u32, 1u32), (123, 4567), (960, 960), (5000, 3000)] {
let a = det_input_size(w, h);
let b = det_input_size(w, h);
assert_eq!(a, b);
assert_eq!(a.0 % DET_STRIDE, 0);
assert_eq!(a.1 % DET_STRIDE, 0);
assert!(a.0 >= DET_STRIDE && a.1 >= DET_STRIDE);
}
}
fn checker_2x2() -> Pixmap {
let mut pm = Pixmap::white(2, 2);
let px = [
[10u8, 20, 30],
[50, 60, 70],
[90, 100, 110],
[130, 140, 150],
];
for (i, rgb) in px.iter().enumerate() {
pm.data[i * 4..i * 4 + 3].copy_from_slice(rgb);
}
pm
}
#[test]
fn resize_same_size_is_identity() {
let pm = checker_2x2();
let rgb = resize_bilinear_rgb(&pm, 2, 2);
let expected: Vec<u8> = pm
.data
.as_chunks::<4>()
.0
.iter()
.flat_map(|p| p[..3].to_vec())
.collect();
assert_eq!(rgb, expected);
}
#[test]
fn resize_2x2_to_1x1_averages_all_pixels() {
let rgb = resize_bilinear_rgb(&checker_2x2(), 1, 1);
assert_eq!(rgb, vec![70, 80, 90]);
}
#[test]
fn resize_is_deterministic() {
let mut pm = Pixmap::white(17, 13);
for (i, b) in pm.data.iter_mut().enumerate() {
*b = (i * 7 % 251) as u8;
}
let a = resize_bilinear_rgb(&pm, 32, 32);
let b = resize_bilinear_rgb(&pm, 32, 32);
assert_eq!(a, b);
assert_eq!(a.len(), 32 * 32 * 3);
}
#[test]
fn det_tensor_normalizes_uniform_gray() {
let mut pm = Pixmap::white(8, 8);
for b in pm.data.iter_mut() {
*b = 128;
}
let t = det_tensor(&pm, 32, 32);
assert_eq!(t.len(), 3 * 32 * 32);
let plane = 32 * 32;
for c in 0..3 {
let expected = (128.0 / 255.0 - DET_MEAN[c]) / DET_STD[c];
for i in 0..plane {
assert!((t[c * plane + i] - expected).abs() < 1e-6);
}
}
}
}