Skip to main content

fleischwolf_pdf/
resample.rs

1//! Pixel-exact reimplementations of the OpenCV resize kernels docling uses for
2//! TableFormer preprocessing, so the model sees byte-identical input. Verified
3//! against cv2 on docling's own bitmaps (INTER_AREA max diff 1/255, INTER_LINEAR
4//! < 1e-4 in float).
5
6use image::{Rgb, RgbImage};
7
8/// Per-output-pixel source spans + overlap weights for area resampling.
9fn area_weights(src: usize, dst: usize, scale: f64) -> Vec<Vec<(usize, f64)>> {
10    (0..dst)
11        .map(|d| {
12            let f1 = d as f64 * scale;
13            let f2 = (d + 1) as f64 * scale;
14            let s1 = f1.floor() as usize;
15            let s2 = (f2.ceil() as usize).min(src);
16            (s1..s2)
17                .map(|si| {
18                    let w = (((si + 1) as f64).min(f2) - (si as f64).max(f1)) / scale;
19                    (si, w)
20                })
21                .collect()
22        })
23        .collect()
24}
25
26/// `cv2.resize(..., interpolation=INTER_AREA)` for shrinking — area-weighted
27/// averaging, separable (horizontal then vertical), f64 accumulation.
28pub fn inter_area(src: &RgbImage, dw: u32, dh: u32) -> RgbImage {
29    let (sw, sh) = (src.width() as usize, src.height() as usize);
30    let (dwu, dhu) = (dw as usize, dh as usize);
31    let hw = area_weights(sw, dwu, sw as f64 / dw as f64);
32    let vw = area_weights(sh, dhu, sh as f64 / dh as f64);
33
34    let mut tmp = vec![[0f64; 3]; sh * dwu]; // (sh × dw)
35    for y in 0..sh {
36        let row = y * dwu;
37        for (dx, ws) in hw.iter().enumerate() {
38            let mut acc = [0f64; 3];
39            for &(si, w) in ws {
40                let p = src.get_pixel(si as u32, y as u32);
41                acc[0] += p[0] as f64 * w;
42                acc[1] += p[1] as f64 * w;
43                acc[2] += p[2] as f64 * w;
44            }
45            tmp[row + dx] = acc;
46        }
47    }
48    let mut out = RgbImage::new(dw, dh);
49    for (dy, ws) in vw.iter().enumerate() {
50        for dx in 0..dwu {
51            let mut acc = [0f64; 3];
52            for &(si, w) in ws {
53                let t = tmp[si * dwu + dx];
54                acc[0] += t[0] * w;
55                acc[1] += t[1] * w;
56                acc[2] += t[2] * w;
57            }
58            out.put_pixel(
59                dx as u32,
60                dy as u32,
61                Rgb([round_u8(acc[0]), round_u8(acc[1]), round_u8(acc[2])]),
62            );
63        }
64    }
65    out
66}
67
68fn round_u8(v: f64) -> u8 {
69    v.round().clamp(0.0, 255.0) as u8
70}