use pagesift_infer::Tensor;
use crate::Image;
const CHANNELS: usize = 3;
pub const SIZE: usize = 768;
const MEAN: f32 = 0.7931;
const STD: f32 = 0.1738;
const GRAY_THRESHOLD: f32 = 200.0;
const LUMA: [f32; CHANNELS] = [0.2989, 0.587, 0.114];
const MAX_MID_PIXELS: usize = 1 << 26;
struct Plane {
w: usize,
h: usize,
data: Vec<u8>,
}
pub fn preprocess(img: &Image) -> Tensor {
let n = SIZE * SIZE;
let pad_val = -MEAN / STD;
let mut out = vec![pad_val; CHANNELS * n];
if img.width == 0 || img.height == 0 {
return Tensor::new(vec![CHANNELS, SIZE, SIZE], out);
}
let (x0, y0, cw, ch) = crop_box(img);
let (mh, mw) = mid_size(ch, cw);
let (th, tw) = thumb_size(mh, mw);
let planes = if img.channels == 1 { 1 } else { CHANNELS };
let (top, left) = ((SIZE - th) / 2, (SIZE - tw) / 2);
for c in 0..planes {
let mut p = Plane {
w: cw,
h: ch,
data: Vec::with_capacity(cw * ch),
};
for y in y0..y0 + ch {
for x in x0..x0 + cw {
p.data.push(img.at(x, y, c));
}
}
let p = resize(&p, mh, mw, Filter::Triangle);
let p = resize(&p, th, tw, Filter::Cubic);
let dst = &mut out[c * n..(c + 1) * n];
for (y, row) in p.data.chunks_exact(tw).enumerate() {
let at = (top + y) * SIZE + left;
for (o, &v) in dst[at..at + tw].iter_mut().zip(row) {
*o = (f32::from(v) / 255.0 - MEAN) / STD;
}
}
}
if planes == 1 {
let (first, rest) = out.split_at_mut(n);
rest[..n].copy_from_slice(first);
rest[n..].copy_from_slice(first);
}
Tensor::new(vec![CHANNELS, SIZE, SIZE], out)
}
fn crop_box(img: &Image) -> (usize, usize, usize, usize) {
let (w, h) = (img.width, img.height);
let gray: Vec<u8> = (0..h)
.flat_map(|y| (0..w).map(move |x| (x, y)))
.map(|(x, y)| {
let l: f32 = (0..CHANNELS)
.map(|c| f32::from(img.at(x, y, c)) * LUMA[c])
.sum();
l.round_ties_even().clamp(0.0, 255.0) as u8
})
.collect();
let (lo, hi) = gray
.iter()
.fold((u8::MAX, u8::MIN), |(a, b), &v| (a.min(v), b.max(v)));
if lo == hi {
return (0, 0, w, h);
}
let range = f32::from(hi - lo);
let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0, 0);
for (i, &g) in gray.iter().enumerate() {
if f32::from(g - lo) / range * 255.0 < GRAY_THRESHOLD {
let (x, y) = (i % w, i / w);
x0 = x0.min(x);
y0 = y0.min(y);
x1 = x1.max(x);
y1 = y1.max(y);
}
}
(x0, y0, x1 - x0 + 1, y1 - y0 + 1)
}
fn mid_size(h: usize, w: usize) -> (usize, usize) {
let (short, long) = if w <= h { (w, h) } else { (h, w) };
let cap = (MAX_MID_PIXELS / SIZE).max(SIZE);
let new_long = (SIZE as f64 * long as f64 / short as f64) as usize;
let new_long = new_long.min(cap);
if w <= h {
(new_long, SIZE)
} else {
(SIZE, new_long)
}
}
fn thumb_size(h: usize, w: usize) -> (usize, usize) {
let (mut th, mut tw) = (h.min(SIZE), w.min(SIZE));
if (th, tw) == (h, w) {
return (h, w);
}
if h > w {
tw = w * th / h;
} else if w > h {
th = h * tw / w;
}
(th.max(1), tw.max(1))
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Filter {
Triangle,
Cubic,
}
impl Filter {
fn support(self) -> f64 {
match self {
Filter::Triangle => 1.0,
Filter::Cubic => 2.0,
}
}
fn weight(self, x: f64) -> f64 {
let x = x.abs();
match self {
Filter::Triangle => (1.0 - x).max(0.0),
Filter::Cubic => {
let a = -0.5;
if x < 1.0 {
((a + 2.0) * x - (a + 3.0)) * x * x + 1.0
} else if x < 2.0 {
(((x - 5.0) * x + 8.0) * x - 4.0) * a
} else {
0.0
}
}
}
}
}
struct Taps {
start: Vec<usize>,
weights: Vec<Vec<f32>>,
}
fn taps(inp: usize, out: usize, f: Filter) -> Taps {
let scale = inp as f64 / out as f64;
let support = f.support() * scale.max(1.0);
let inv = 1.0 / scale.max(1.0);
let mut start = Vec::with_capacity(out);
let mut weights = Vec::with_capacity(out);
for i in 0..out {
let center = scale * (i as f64 + 0.5);
let lo = (center - support + 0.5).floor().max(0.0) as usize;
let hi = ((center + support + 0.5).floor() as usize).min(inp);
let mut w: Vec<f64> = (lo..hi)
.map(|j| f.weight((j as f64 - center + 0.5) * inv))
.collect();
let sum: f64 = w.iter().sum();
if sum != 0.0 {
for v in &mut w {
*v /= sum;
}
}
start.push(lo);
weights.push(w.into_iter().map(|v| v as f32).collect());
}
Taps { start, weights }
}
fn to_u8(v: f32) -> u8 {
v.clamp(0.0, 255.0).round_ties_even() as u8
}
fn resize(p: &Plane, oh: usize, ow: usize, f: Filter) -> Plane {
if (p.h, p.w) == (oh, ow) {
return Plane {
w: ow,
h: oh,
data: p.data.clone(),
};
}
let tx = taps(p.w, ow, f);
let ty = taps(p.h, oh, f);
let round_mid = f == Filter::Triangle;
let mut mid = vec![0f32; p.h * ow];
for (srow, drow) in p.data.chunks_exact(p.w).zip(mid.chunks_exact_mut(ow)) {
for (x, d) in drow.iter_mut().enumerate() {
let s0 = tx.start[x];
let v: f32 = tx.weights[x]
.iter()
.zip(&srow[s0..])
.map(|(wt, &s)| f32::from(s) * wt)
.sum();
*d = if round_mid { f32::from(to_u8(v)) } else { v };
}
}
let mut acc = vec![0f32; ow];
let mut data = Vec::with_capacity(oh * ow);
for y in 0..oh {
acc.fill(0.0);
for (k, wt) in ty.weights[y].iter().enumerate() {
let r = ty.start[y] + k;
for (a, s) in acc.iter_mut().zip(&mid[r * ow..(r + 1) * ow]) {
*a += s * wt;
}
}
data.extend(acc.iter().map(|&v| to_u8(v)));
}
Plane { w: ow, h: oh, data }
}
#[cfg(test)]
mod tests {
use super::*;
fn gray(w: usize, h: usize, f: impl Fn(usize, usize) -> u8) -> Image {
let data = (0..h)
.flat_map(|y| (0..w).map(move |x| (x, y)))
.map(|(x, y)| f(x, y))
.collect();
Image {
width: w,
height: h,
channels: 1,
data,
}
}
#[test]
fn 裁白边取暗像素的外接框() {
let img = gray(50, 40, |x, y| {
if (10..20).contains(&x) && (5..30).contains(&y) {
0
} else {
255
}
});
assert_eq!(crop_box(&img), (10, 5, 10, 25));
assert_eq!(crop_box(&gray(7, 3, |_, _| 255)), (0, 0, 7, 3));
}
#[test]
fn 尺寸规则照参考实现() {
assert_eq!(mid_size(100, 400), (768, 3072));
assert_eq!(thumb_size(768, 3072), (192, 768));
assert_eq!(mid_size(300, 200), (1152, 768));
assert_eq!(thumb_size(1152, 768), (768, 512));
assert_eq!(mid_size(10, 10), (768, 768));
assert_eq!(thumb_size(768, 768), (768, 768));
let (h, w) = mid_size(1, 100_000);
assert_eq!(h, 768);
assert!(w * h <= MAX_MID_PIXELS && w >= 768);
}
#[test]
fn 缩放保常数_放大是半像素双线性() {
let p = Plane {
w: 5,
h: 3,
data: vec![77; 15],
};
for f in [Filter::Triangle, Filter::Cubic] {
assert!(resize(&p, 7, 2, f).data.iter().all(|&v| v == 77));
}
let t = taps(2, 4, Filter::Triangle);
let right: Vec<f32> = (0..4)
.map(|i| {
let w = &t.weights[i];
(0..w.len())
.map(|k| if t.start[i] + k == 1 { w[k] } else { 0.0 })
.sum()
})
.collect();
for (a, b) in right.iter().zip([0.0, 0.25, 0.75, 1.0]) {
assert!((a - b).abs() < 1e-6, "{right:?}");
}
}
#[test]
fn 双三次核在整数点插值() {
assert!((Filter::Cubic.weight(0.0) - 1.0).abs() < 1e-12);
for x in [1.0, 2.0, -1.0, 3.0] {
assert!(Filter::Cubic.weight(x).abs() < 1e-12);
}
let t = taps(100, 7, Filter::Cubic);
for w in &t.weights {
assert!((w.iter().sum::<f32>() - 1.0).abs() < 1e-5);
}
}
#[test]
fn 输出形状_居中补边_归一化() {
let img = gray(100, 25, |x, y| {
if (10..90).contains(&x) && (2..22).contains(&y) {
0
} else {
255
}
});
let t = preprocess(&img);
assert_eq!(t.shape, vec![3, SIZE, SIZE]);
let pad = -MEAN / STD;
let black = (0.0 - MEAN) / STD;
assert_eq!(t.data[0], pad);
let mid = SIZE / 2 * SIZE;
assert!(
t.data[mid..mid + SIZE]
.iter()
.all(|&v| (v - black).abs() < 1e-6)
);
assert_eq!(t.data[287 * SIZE + 5], pad);
assert!((t.data[288 * SIZE + 5] - black).abs() < 1e-6);
let n = SIZE * SIZE;
assert_eq!(t.data[..n], t.data[n..2 * n]);
assert!(t.data.iter().all(|v| v.is_finite()));
}
}