Skip to main content

rapiddoc_core/
pillow_resize.rs

1//! Pillow 12.2 RGB8 resampling/reduce arithmetic. MIT-CMU attribution: licenses/PILLOW.txt.
2use crate::{Error, ErrorCode};
3
4pub fn fail(code: ErrorCode) -> Error {
5    let message = if code == ErrorCode::InvalidInput {
6        "公式图像字节长度或尺寸无效"
7    } else {
8        "公式图像尺寸或工作量超过预算"
9    };
10    Error {
11        code,
12        message: message.into(),
13    }
14}
15pub struct Rgb {
16    pub data: Vec<u8>,
17    pub w: usize,
18    pub h: usize,
19}
20pub fn size(w: usize, h: usize) -> Result<usize, Error> {
21    w.checked_mul(h)
22        .filter(|n| {
23            w > 0
24                && h > 0
25                && w <= crate::resources::limit(65_536)
26                && h <= crate::resources::limit(65_536)
27                && *n <= crate::resources::limit(16_000_000)
28                && n.checked_mul(3)
29                    .is_some_and(|bytes| bytes <= isize::MAX as usize)
30        })
31        .ok_or_else(|| fail(ErrorCode::ResourceLimit))
32}
33pub struct Work(pub usize);
34impl Work {
35    fn add(&mut self, n: usize) -> Result<(), Error> {
36        self.0 = self
37            .0
38            .checked_add(n)
39            .filter(|n| *n <= crate::resources::limit(256_000_000))
40            .ok_or_else(|| fail(ErrorCode::ResourceLimit))?;
41        Ok(())
42    }
43}
44fn filter(x: f64, cubic: bool) -> f64 {
45    let x = x.abs();
46    if !cubic {
47        if x < 1. {
48            1. - x
49        } else {
50            0.
51        }
52    } else if x < 1. {
53        (1.5 * x - 2.5) * x * x + 1.
54    } else if x < 2. {
55        (((x - 5.) * x + 8.) * x - 4.) * -0.5
56    } else {
57        0.
58    }
59}
60fn coefficients(
61    input: usize,
62    end: f32,
63    output: usize,
64    cubic: bool,
65    work: &mut Work,
66) -> Result<Vec<(usize, Vec<i32>)>, Error> {
67    let scale = end as f64 / output as f64;
68    let fs = scale.max(1.);
69    let support = fs * if cubic { 2. } else { 1. };
70    let mut out = Vec::with_capacity(output);
71    for i in 0..output {
72        let center = (i as f64 + 0.5) * scale;
73        let start = ((center - support + 0.5) as isize).max(0) as usize;
74        let stop = ((center + support + 0.5) as usize).min(input);
75        let count = stop.saturating_sub(start);
76        work.add(count)?;
77        let mut weights: Vec<_> = (0..count)
78            .map(|j| filter((j as f64 + start as f64 - center + 0.5) * (1. / fs), cubic))
79            .collect();
80        let sum: f64 = weights.iter().sum();
81        for w in &mut weights {
82            if sum != 0. {
83                *w /= sum;
84            }
85        }
86        let fixed = weights
87            .into_iter()
88            .map(|w| ((w * (1u32 << 22) as f64) + if w < 0. { -0.5 } else { 0.5 }) as i32)
89            .collect();
90        out.push((start, fixed));
91    }
92    Ok(out)
93}
94fn axis(
95    im: Rgb,
96    output: usize,
97    end: f32,
98    vertical: bool,
99    cubic: bool,
100    work: &mut Work,
101) -> Result<Rgb, Error> {
102    let input = if vertical { im.h } else { im.w };
103    if output == input && end == input as f32 {
104        return Ok(im);
105    }
106    let (w, h) = if vertical {
107        (im.w, output)
108    } else {
109        (output, im.h)
110    };
111    let pixels = size(w, h)?;
112    let coeffs = coefficients(input, end, output, cubic, work)?;
113    let cross = if vertical { im.w } else { im.h };
114    let taps: usize = coeffs.iter().map(|(_, c)| c.len()).sum();
115    work.add(
116        taps.checked_mul(cross)
117            .and_then(|n| n.checked_mul(3))
118            .ok_or_else(|| fail(ErrorCode::ResourceLimit))?,
119    )?;
120    let mut data = vec![0; pixels * 3];
121    // Traverse horizontal output in row order, keeping source/output accesses
122    // local. Each channel retains the exact tap order and integer rounding.
123    for row in 0..h {
124        for col in 0..w {
125            let (start, ks) = &coeffs[if vertical { row } else { col }];
126            let mut sums = [1i64 << 21; 3];
127            for (k, &weight) in ks.iter().enumerate() {
128                let pos = if vertical {
129                    ((start + k) * im.w + col) * 3
130                } else {
131                    (row * im.w + start + k) * 3
132                };
133                let weight = weight as i64;
134                sums[0] += im.data[pos] as i64 * weight;
135                sums[1] += im.data[pos + 1] as i64 * weight;
136                sums[2] += im.data[pos + 2] as i64 * weight;
137            }
138            let dest = (row * w + col) * 3;
139            for c in 0..3 {
140                data[dest + c] = (sums[c] >> 22).clamp(0, 255) as u8;
141            }
142        }
143    }
144    Ok(Rgb { data, w, h })
145}
146pub fn resize(
147    im: Rgb,
148    w: usize,
149    h: usize,
150    end: (f32, f32),
151    cubic: bool,
152    work: &mut Work,
153) -> Result<Rgb, Error> {
154    size(w, h)?;
155    // Image.resize 在极窄高图上先纵向缩小,保留其两次量化次序。
156    if im.h > im.w.saturating_mul(100) && h < im.h {
157        let im = axis(im, h, end.1, true, cubic, work)?;
158        axis(im, w, end.0, false, cubic, work)
159    } else {
160        let im = axis(im, w, end.0, false, cubic, work)?;
161        axis(im, h, end.1, true, cubic, work)
162    }
163}
164pub fn reduce(im: Rgb, fx: usize, fy: usize, work: &mut Work) -> Result<Rgb, Error> {
165    let (w, h) = (im.w.div_ceil(fx), im.h.div_ceil(fy));
166    let mut data = vec![0; size(w, h)? * 3];
167    work.add(im.data.len())?;
168    for y in 0..h {
169        for x in 0..w {
170            let (nx, ny) = (fx.min(im.w - x * fx), fy.min(im.h - y * fy));
171            let n = nx * ny;
172            // 与 division_UINT32 一致,先做float32除法再截断;不是直接整数均值。
173            let multiplier = ((1u32 << 24) as f32 / n as f32) as u64;
174            for c in 0..3 {
175                let mut sum = n as u64 / 2;
176                for yy in 0..ny {
177                    for xx in 0..nx {
178                        sum += im.data[((y * fy + yy) * im.w + x * fx + xx) * 3 + c] as u64;
179                    }
180                }
181                data[(y * w + x) * 3 + c] = ((sum * multiplier) >> 24) as u8;
182            }
183        }
184    }
185    Ok(Rgb { data, w, h })
186}