rasterrocket_render/shading/
mod.rs1pub mod axial;
18pub mod function;
19pub mod gouraud;
20pub mod radial;
21
22use crate::bitmap::Bitmap;
23use crate::clip::Clip;
24use crate::fill;
25use crate::path::Path;
26use crate::pipe::{Pattern, PipeSrc, PipeState};
27use color::Pixel;
28use color::convert::lerp_u8;
29
30#[inline]
35pub(super) fn lerp_color(a: [u8; 3], b: [u8; 3], frac: u32, out: &mut [u8]) {
36 debug_assert_eq!(out.len(), 3, "lerp_color: out must be exactly 3 bytes");
37 out[0] = lerp_u8(a[0], b[0], frac);
38 out[1] = lerp_u8(a[1], b[1], frac);
39 out[2] = lerp_u8(a[2], b[2], frac);
40}
41
42#[expect(
49 clippy::too_many_arguments,
50 reason = "mirrors shadedFill signature: bitmap+clip+path+pipe+pattern+matrix+flatness+aa+eo"
51)]
52pub fn shaded_fill<P: Pixel>(
53 bitmap: &mut Bitmap<P>,
54 clip: &Clip,
55 path: &Path,
56 pipe: &PipeState<'_>,
57 pattern: &dyn Pattern,
58 matrix: &[f64; 6],
59 flatness: f64,
60 vector_antialias: bool,
61 eo: bool,
62) {
63 let src = PipeSrc::Pattern(pattern);
64 if eo {
65 fill::eo_fill::<P>(
66 bitmap,
67 clip,
68 path,
69 pipe,
70 &src,
71 matrix,
72 flatness,
73 vector_antialias,
74 );
75 } else {
76 fill::fill::<P>(
77 bitmap,
78 clip,
79 path,
80 pipe,
81 &src,
82 matrix,
83 flatness,
84 vector_antialias,
85 );
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::bitmap::Bitmap;
93 use crate::clip::Clip;
94 use crate::path::PathBuilder;
95 use crate::shading::axial::AxialPattern;
96 use crate::testutil::{identity_matrix, rect_path, simple_pipe};
97 use color::Rgb8;
98
99 #[test]
100 fn shaded_fill_axial_paints_interior() {
101 let mut bmp: Bitmap<Rgb8> = Bitmap::new(8, 8, 4, false);
103 let clip = Clip::new(0.0, 0.0, 7.999, 7.999, false);
104 let pipe = simple_pipe();
105 let path = rect_path(1.0, 1.0, 6.0, 6.0);
106
107 let pattern = AxialPattern::new(
108 [0u8, 0, 0],
109 [255u8, 255, 255],
110 1.0,
111 3.5,
112 6.0,
113 3.5,
114 0.0,
115 1.0,
116 false,
117 false,
118 );
119
120 shaded_fill::<Rgb8>(
121 &mut bmp,
122 &clip,
123 &path,
124 &pipe,
125 &pattern,
126 &identity_matrix(),
127 1.0,
128 false,
129 false,
130 );
131
132 let r3 = bmp.row(3);
133 assert!(r3[1].r < 60, "x=1 should be near-black (got {})", r3[1].r);
134 assert!(r3[5].r > 180, "x=5 should be near-white (got {})", r3[5].r);
135 }
136
137 #[test]
138 fn shaded_fill_eo_and_nonzero_both_work() {
139 let mut bmp_nz: Bitmap<Rgb8> = Bitmap::new(8, 8, 4, false);
141 let mut bmp_eo: Bitmap<Rgb8> = Bitmap::new(8, 8, 4, false);
142 let clip = Clip::new(0.0, 0.0, 7.999, 7.999, false);
143 let pipe = simple_pipe();
144 let path = rect_path(1.0, 1.0, 6.0, 6.0);
145 let pattern = AxialPattern::new(
146 [200u8, 0, 0],
147 [200u8, 0, 0],
148 0.0,
149 0.0,
150 1.0,
151 0.0,
152 0.0,
153 1.0,
154 true,
155 true,
156 );
157 shaded_fill::<Rgb8>(
158 &mut bmp_nz,
159 &clip,
160 &path,
161 &pipe,
162 &pattern,
163 &identity_matrix(),
164 1.0,
165 false,
166 false,
167 );
168 shaded_fill::<Rgb8>(
169 &mut bmp_eo,
170 &clip,
171 &path,
172 &pipe,
173 &pattern,
174 &identity_matrix(),
175 1.0,
176 false,
177 true,
178 );
179 assert_eq!(
180 bmp_nz.row(3)[3].r,
181 bmp_eo.row(3)[3].r,
182 "non-zero and eo must agree for a simple convex path"
183 );
184 }
185
186 #[test]
187 fn shaded_fill_empty_path_is_noop() {
188 let mut bmp: Bitmap<Rgb8> = Bitmap::new(8, 8, 4, false);
189 let clip = Clip::new(0.0, 0.0, 7.999, 7.999, false);
190 let pipe = simple_pipe();
191 let path = PathBuilder::new().build();
192 let pattern = AxialPattern::new(
193 [255u8, 0, 0],
194 [0u8, 255, 0],
195 0.0,
196 0.0,
197 8.0,
198 0.0,
199 0.0,
200 1.0,
201 false,
202 false,
203 );
204 shaded_fill::<Rgb8>(
205 &mut bmp,
206 &clip,
207 &path,
208 &pipe,
209 &pattern,
210 &identity_matrix(),
211 1.0,
212 false,
213 false,
214 );
215 assert_eq!(bmp.row(4)[4].r, 0, "empty path must not paint");
216 }
217}