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, sample};
6use crate::fine::macros::u8x16_painter;
7use vello_common::encode::EncodedImage;
8use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x16};
9use vello_common::pixmap::Pixmap;
10use vello_common::simd::element_wise_splat;
11use vello_common::util::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        // Note that this `fract` has different behavior for negative numbers than the normal,
53        // one.
54        #[inline(always)]
55        fn fract<S: Simd>(val: f32x4<S>) -> f32x4<S> {
56            val - val.floor()
57        }
58
59        let extend_x = |x_pos: f32x4<S>| {
60            extend(
61                self.simd,
62                x_pos,
63                self.data.image.sampler.x_extend,
64                self.data.width,
65                self.data.width_inv,
66            )
67        };
68
69        let extend_y = |y_pos: f32x4<S>| {
70            extend(
71                self.simd,
72                y_pos,
73                self.data.image.sampler.y_extend,
74                self.data.height,
75                self.data.height_inv,
76            )
77        };
78
79        let fx = f32_to_u8(element_wise_splat(
80            self.simd,
81            fract(x_positions + 0.5) * 256.0,
82        ));
83        let fy = f32_to_u8(element_wise_splat(
84            self.simd,
85            fract(y_positions + 0.5) * 256.0,
86        ));
87        let fx_inv = self.simd.widen_u8x16(u8x16::splat(self.simd, 255) - fx);
88        let fy_inv = self.simd.widen_u8x16(u8x16::splat(self.simd, 255) - fy);
89
90        let fx = self.simd.widen_u8x16(fx);
91        let fy = self.simd.widen_u8x16(fy);
92
93        let x_pos1 = extend_x(x_positions - 0.5);
94        let x_pos2 = extend_x(x_positions + 0.5);
95        let y_pos1 = extend_y(y_positions - 0.5);
96        let y_pos2 = extend_y(y_positions + 0.5);
97
98        let p00 = self
99            .simd
100            .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos1));
101        let p10 = self
102            .simd
103            .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos1));
104        let p01 = self
105            .simd
106            .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos2));
107        let p11 = self
108            .simd
109            .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos2));
110
111        let ip1 = (p00 * fx_inv + p10 * fx).shr(8);
112        let ip2 = (p01 * fx_inv + p11 * fx).shr(8);
113        let res = self.simd.narrow_u16x16((ip1 * fy_inv + ip2 * fy).shr(8));
114
115        self.data.cur_pos += self.data.image.x_advance;
116
117        Some(res)
118    }
119}
120
121u8x16_painter!(BilinearImagePainter<'_, S>);