Skip to main content

vello_cpu/fine/lowp/
image.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::fine::PosExt;
5use crate::fine::common::image::{ImagePainterData, extend, fract_floor, sample};
6use crate::fine::macros::u8x16_painter;
7use vello_common::encode::EncodedImage;
8use vello_common::fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, u8x16, u16x16};
9use vello_common::pixmap::Pixmap;
10use vello_common::simd::element_wise_splat;
11use vello_common::util::{Div255Ext, f32_to_u8};
12
13/// A faster bilinear image renderer for the u8 pipeline.
14#[derive(Debug)]
15pub(crate) struct BilinearImagePainter<'a, S: Simd> {
16    data: ImagePainterData<'a, S>,
17    simd: S,
18}
19
20impl<'a, S: Simd> BilinearImagePainter<'a, S> {
21    pub(crate) fn new(
22        simd: S,
23        image: &'a EncodedImage,
24        pixmap: &'a Pixmap,
25        start_x: u16,
26        start_y: u16,
27    ) -> Self {
28        let data = ImagePainterData::new(simd, image, pixmap, start_x, start_y);
29
30        Self { data, simd }
31    }
32}
33
34impl<S: Simd> Iterator for BilinearImagePainter<'_, S> {
35    type Item = u8x16<S>;
36
37    fn next(&mut self) -> Option<Self::Item> {
38        let x_positions = f32x4::splat_pos(
39            self.simd,
40            self.data.cur_pos.x as f32,
41            self.data.x_advances.0,
42            self.data.y_advances.0,
43        );
44
45        let y_positions = f32x4::splat_pos(
46            self.simd,
47            self.data.cur_pos.y as f32,
48            self.data.x_advances.1,
49            self.data.y_advances.1,
50        );
51
52        let extend_x = |x_pos: f32x4<S>| {
53            extend(
54                self.simd,
55                x_pos,
56                self.data.image.sampler.x_extend,
57                self.data.width,
58                self.data.width_inv,
59            )
60        };
61
62        let extend_y = |y_pos: f32x4<S>| {
63            extend(
64                self.simd,
65                y_pos,
66                self.data.image.sampler.y_extend,
67                self.data.height,
68                self.data.height_inv,
69            )
70        };
71
72        let fx = f32_to_u8(element_wise_splat(
73            self.simd,
74            fract_floor(x_positions + 0.5).madd(255.0, 0.5),
75        ));
76        let fy = f32_to_u8(element_wise_splat(
77            self.simd,
78            fract_floor(y_positions + 0.5).madd(255.0, 0.5),
79        ));
80
81        let fx = self.simd.widen_u8x16(fx);
82        let fy = self.simd.widen_u8x16(fy);
83        let fx_inv = u16x16::splat(self.simd, 255) - fx;
84        let fy_inv = u16x16::splat(self.simd, 255) - fy;
85
86        let x_pos1 = extend_x(x_positions - 0.5);
87        let x_pos2 = extend_x(x_positions + 0.5);
88        let y_pos1 = extend_y(y_positions - 0.5);
89        let y_pos2 = extend_y(y_positions + 0.5);
90
91        let p00 = self
92            .simd
93            .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos1));
94        let p10 = self
95            .simd
96            .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos1));
97        let p01 = self
98            .simd
99            .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos2));
100        let p11 = self
101            .simd
102            .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos2));
103
104        let ip1 = (p00 * fx_inv + p10 * fx).div_255();
105        let ip2 = (p01 * fx_inv + p11 * fx).div_255();
106        let res = self.simd.narrow_u16x16((ip1 * fy_inv + ip2 * fy).div_255());
107
108        self.data.cur_pos += self.data.image.x_advance;
109
110        Some(res)
111    }
112}
113
114u8x16_painter!(BilinearImagePainter<'_, S>);
115
116/// A faster bilinear image renderer for axis-aligned images (no skew) in the u8 pipeline.
117///
118/// This is an optimized version of `BilinearImagePainter` that pre-computes y positions
119/// and interpolation weights since they don't change when there's no skew.
120#[derive(Debug)]
121pub(crate) struct PlainBilinearImagePainter<'a, S: Simd> {
122    data: ImagePainterData<'a, S>,
123    simd: S,
124    /// Pre-computed y sample positions (top row for bilinear grid)
125    y_pos1: f32x4<S>,
126    /// Pre-computed y sample positions (bottom row for bilinear grid)
127    y_pos2: f32x4<S>,
128    /// Pre-computed y interpolation weight
129    fy: u16x16<S>,
130    /// Pre-computed inverse y interpolation weight
131    fy_inv: u16x16<S>,
132    /// Current x position
133    cur_x_pos: f32x4<S>,
134    /// X advance per iteration
135    advance: f32,
136}
137
138impl<'a, S: Simd> PlainBilinearImagePainter<'a, S> {
139    pub(crate) fn new(
140        simd: S,
141        image: &'a EncodedImage,
142        pixmap: &'a Pixmap,
143        start_x: u16,
144        start_y: u16,
145    ) -> Self {
146        let data = ImagePainterData::new(simd, image, pixmap, start_x, start_y);
147
148        // For axis-aligned images, y doesn't change across the strip
149        let y_positions = f32x4::splat_pos(
150            simd,
151            data.cur_pos.y as f32,
152            data.x_advances.1,
153            data.y_advances.1,
154        );
155
156        // Pre-compute y extend positions
157        let y_pos1 = extend(
158            simd,
159            y_positions - 0.5,
160            image.sampler.y_extend,
161            data.height,
162            data.height_inv,
163        );
164        let y_pos2 = extend(
165            simd,
166            y_positions + 0.5,
167            image.sampler.y_extend,
168            data.height,
169            data.height_inv,
170        );
171
172        // Pre-compute y interpolation weights
173        let fy = f32_to_u8(element_wise_splat(
174            simd,
175            fract_floor(y_positions + 0.5).madd(255.0, 0.5),
176        ));
177        let fy = simd.widen_u8x16(fy);
178        let fy_inv = u16x16::splat(simd, 255) - fy;
179
180        let cur_x_pos = f32x4::splat_pos(
181            simd,
182            data.cur_pos.x as f32,
183            data.x_advances.0,
184            data.y_advances.0,
185        );
186
187        Self {
188            data,
189            y_pos1,
190            y_pos2,
191            fy,
192            fy_inv,
193            cur_x_pos,
194            advance: image.x_advance.x as f32,
195            simd,
196        }
197    }
198}
199
200impl<S: Simd> Iterator for PlainBilinearImagePainter<'_, S> {
201    type Item = u8x16<S>;
202
203    #[inline(always)]
204    fn next(&mut self) -> Option<Self::Item> {
205        let x_minus_half = self.cur_x_pos - 0.5;
206        let x_plus_half = self.cur_x_pos + 0.5;
207
208        // Only x needs to be extended per-iteration
209        let x_pos1 = extend(
210            self.simd,
211            x_minus_half,
212            self.data.image.sampler.x_extend,
213            self.data.width,
214            self.data.width_inv,
215        );
216        let x_pos2 = extend(
217            self.simd,
218            x_plus_half,
219            self.data.image.sampler.x_extend,
220            self.data.width,
221            self.data.width_inv,
222        );
223
224        // Compute x interpolation weights
225        let fx = f32_to_u8(element_wise_splat(
226            self.simd,
227            fract_floor(x_plus_half).madd(255.0, 0.5),
228        ));
229        let fx = self.simd.widen_u8x16(fx);
230        let fx_inv = u16x16::splat(self.simd, 255) - fx;
231
232        // Sample the 4 corners using pre-computed y positions
233        let p00 = self
234            .simd
235            .widen_u8x16(sample(self.simd, &self.data, x_pos1, self.y_pos1));
236        let p10 = self
237            .simd
238            .widen_u8x16(sample(self.simd, &self.data, x_pos2, self.y_pos1));
239        let p01 = self
240            .simd
241            .widen_u8x16(sample(self.simd, &self.data, x_pos1, self.y_pos2));
242        let p11 = self
243            .simd
244            .widen_u8x16(sample(self.simd, &self.data, x_pos2, self.y_pos2));
245
246        // Bilinear interpolation
247        let ip1 = (p00 * fx_inv + p10 * fx).div_255();
248        let ip2 = (p01 * fx_inv + p11 * fx).div_255();
249        let res = self
250            .simd
251            .narrow_u16x16((ip1 * self.fy_inv + ip2 * self.fy).div_255());
252
253        self.cur_x_pos += self.advance;
254
255        Some(res)
256    }
257}
258
259u8x16_painter!(PlainBilinearImagePainter<'_, S>);