1mod span;
16
17#[cfg(feature = "nostd")]
18use alloc::{vec, vec::Vec};
19
20use span::convert_span;
21
22pub struct Rasterizer {
25 width: usize,
26 height: usize,
27 stride: usize,
30 acc: Vec<f32>,
31 row_min: Vec<u32>,
36 row_max: Vec<u32>,
37}
38
39impl Rasterizer {
40 #[must_use]
42 pub fn new(width: usize, height: usize) -> Self {
43 let stride = width + 2;
44 let rows = height.max(1);
45 Self {
46 width,
47 height,
48 stride,
49 acc: vec![0.0; stride * rows],
50 row_min: vec![width as u32; rows],
51 row_max: vec![0; rows],
52 }
53 }
54
55 pub fn line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) {
60 let (dir, mut x, top_y, bot_x, bot_y) = if y0 <= y1 {
62 (1.0_f32, x0, y0, x1, y1)
63 } else {
64 (-1.0_f32, x1, y1, x0, y0)
65 };
66 let dy_total = bot_y - top_y;
67 if dy_total <= 0.0 {
68 return; }
70 let dxdy = (bot_x - x) / dy_total;
71
72 let mut y_top = top_y;
74 if y_top < 0.0 {
75 x += -y_top * dxdy;
76 y_top = 0.0;
77 }
78 let y_bot = bot_y.min(self.height as f32);
79 if y_bot <= y_top {
80 return;
81 }
82
83 let w = self.width as f32;
84 let first_row = y_top.floor() as usize;
85 let last_row = (y_bot.ceil() as usize).min(self.height);
86 for y in first_row..last_row {
87 let line_start = y * self.stride;
88 let dy = ((y + 1) as f32).min(y_bot) - (y as f32).max(y_top);
89 if dy <= 0.0 {
90 continue;
91 }
92 let x_next = x + dxdy * dy;
93 let d = dy * dir;
94
95 let xc = x.clamp(0.0, w);
98 let xnc = x_next.clamp(0.0, w);
99 let (xa, xb) = if xc < xnc { (xc, xnc) } else { (xnc, xc) };
100
101 let xa_floor = xa.floor();
102 let x0i = xa_floor as usize;
103 let x1ceil = xb.ceil();
104 let x1i = x1ceil as usize;
105
106 let rmin = &mut self.row_min[y];
110 *rmin = (*rmin).min(x0i as u32);
111 let rmax = &mut self.row_max[y];
112 *rmax = (*rmax).max(x1i.max(x0i + 1) as u32);
113
114 if x1i <= x0i + 1 {
115 let xmf = 0.5 * (xc + xnc) - xa_floor;
117 let i = line_start + x0i;
118 self.acc[i] += d * (1.0 - xmf);
119 self.acc[i + 1] += d * xmf;
120 } else {
121 let s = (xb - xa).recip();
124 let x0f = xa - xa_floor;
125 let a_m = 1.0 - x0f;
126 let am = 0.5 * s * a_m * a_m;
127 let x1f = xb - x1ceil + 1.0;
128 let bm = 0.5 * s * x1f * x1f;
129
130 let i0 = line_start + x0i;
131 self.acc[i0] += d * am;
132 if x1i == x0i + 2 {
133 self.acc[i0 + 1] += d * (1.0 - am - bm);
134 } else {
135 let a0 = s * (1.5 - x0f);
136 self.acc[i0 + 1] += d * (a0 - am);
137 for xi in (x0i + 2)..(x1i - 1) {
138 self.acc[line_start + xi] += d * s;
139 }
140 let a1 = a0 + ((x1i - x0i) as f32 - 3.0) * s;
141 self.acc[line_start + x1i - 1] += d * (1.0 - a1 - bm);
142 }
143 self.acc[line_start + x1i] += d * bm;
144 }
145
146 x = x_next;
147 }
148 }
149
150 pub fn quad(&mut self, x0: f32, y0: f32, cx: f32, cy: f32, x1: f32, y1: f32) {
152 let dev = ((x0 - 2.0 * cx + x1).powi(2) + (y0 - 2.0 * cy + y1).powi(2)).sqrt();
154 let n = (1 + (dev / 0.8).sqrt() as usize).clamp(1, 64);
155 let (mut px, mut py) = (x0, y0);
156 for i in 1..=n {
157 let t = i as f32 / n as f32;
158 let mt = 1.0 - t;
159 let a = mt * mt;
160 let b = 2.0 * mt * t;
161 let c = t * t;
162 let nx = a * x0 + b * cx + c * x1;
163 let ny = a * y0 + b * cy + c * y1;
164 self.line(px, py, nx, ny);
165 px = nx;
166 py = ny;
167 }
168 }
169
170 #[allow(clippy::too_many_arguments)]
172 pub fn cubic(
173 &mut self,
174 x0: f32,
175 y0: f32,
176 c1x: f32,
177 c1y: f32,
178 c2x: f32,
179 c2y: f32,
180 x1: f32,
181 y1: f32,
182 ) {
183 let d1 = ((x0 - 2.0 * c1x + c2x).powi(2) + (y0 - 2.0 * c1y + c2y).powi(2)).sqrt();
184 let d2 = ((c1x - 2.0 * c2x + x1).powi(2) + (c1y - 2.0 * c2y + y1).powi(2)).sqrt();
185 let n = (1 + ((d1 + d2) / 0.8).sqrt() as usize).clamp(1, 96);
186 let (mut px, mut py) = (x0, y0);
187 for i in 1..=n {
188 let t = i as f32 / n as f32;
189 let mt = 1.0 - t;
190 let a = mt * mt * mt;
191 let b = 3.0 * mt * mt * t;
192 let c = 3.0 * mt * t * t;
193 let e = t * t * t;
194 let nx = a * x0 + b * c1x + c * c2x + e * x1;
195 let ny = a * y0 + b * c1y + c * c2y + e * y1;
196 self.line(px, py, nx, ny);
197 px = nx;
198 py = ny;
199 }
200 }
201
202 #[must_use]
205 pub fn finish(&self) -> Vec<u8> {
206 let mut out = vec![0u8; self.width * self.height];
207 if self.width == 0 {
208 return out;
209 }
210 let mut psum = vec![0.0_f32; self.width];
212 for y in 0..self.height {
213 let lo = self.row_min[y] as usize;
214 let hi = (self.row_max[y] as usize).min(self.width - 1);
217 if lo > hi {
218 continue; }
220 let row = y * self.stride;
221 let out_row = y * self.width;
222 let psum_row = &mut psum[lo..=hi];
223 let mut sum = 0.0_f32;
224 for (p, &a) in psum_row.iter_mut().zip(&self.acc[row + lo..=row + hi]) {
225 sum += a;
226 *p = sum;
227 }
228 convert_span(psum_row, &mut out[out_row + lo..=out_row + hi]);
229 }
230 out
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::Rasterizer;
237 use tiny_skia::{FillRule, Mask, PathBuilder, Transform};
238
239 fn ours(width: usize, height: usize, build: impl Fn(&mut Rasterizer)) -> Vec<u8> {
241 let mut r = Rasterizer::new(width, height);
242 build(&mut r);
243 r.finish()
244 }
245
246 fn skia(width: u32, height: u32, path: &tiny_skia::Path) -> Vec<u8> {
248 let mut mask = Mask::new(width, height).unwrap();
249 mask.fill_path(path, FillRule::Winding, true, Transform::identity());
250 mask.data().to_vec()
251 }
252
253 fn diff(a: &[u8], b: &[u8]) -> (f64, u8) {
255 let mut sum = 0u64;
256 let mut max = 0u8;
257 for (x, y) in a.iter().zip(b.iter()) {
258 let d = x.abs_diff(*y);
259 sum += u64::from(d);
260 max = max.max(d);
261 }
262 (sum as f64 / a.len() as f64, max)
263 }
264
265 #[test]
266 fn solid_rect_is_fully_covered() {
267 let cov = ours(10, 10, |r| {
269 r.line(2.0, 2.0, 8.0, 2.0);
270 r.line(8.0, 2.0, 8.0, 8.0);
271 r.line(8.0, 8.0, 2.0, 8.0);
272 r.line(2.0, 8.0, 2.0, 2.0);
273 });
274 assert_eq!(cov[5 * 10 + 5], 255, "centre of rect must be solid");
275 assert_eq!(cov[0], 0, "outside the rect must be empty");
276 }
277
278 #[test]
279 fn matches_tiny_skia_on_rect() {
280 let mut pb = PathBuilder::new();
281 pb.move_to(2.3, 2.7);
282 pb.line_to(8.6, 2.7);
283 pb.line_to(8.6, 8.1);
284 pb.line_to(2.3, 8.1);
285 pb.close();
286 let path = pb.finish().unwrap();
287 let s = skia(12, 12, &path);
288 let o = ours(12, 12, |r| {
289 r.line(2.3, 2.7, 8.6, 2.7);
290 r.line(8.6, 2.7, 8.6, 8.1);
291 r.line(8.6, 8.1, 2.3, 8.1);
292 r.line(2.3, 8.1, 2.3, 2.7);
293 });
294 let (mean, max) = diff(&o, &s);
297 assert!(mean < 4.0, "mean A8 diff vs tiny-skia too high: {mean}");
298 assert!(max < 40, "max A8 diff vs tiny-skia too high: {max}");
299 }
300
301 #[test]
302 fn matches_tiny_skia_on_triangle() {
303 let mut pb = PathBuilder::new();
304 pb.move_to(4.0, 1.5);
305 pb.line_to(14.2, 12.8);
306 pb.line_to(1.7, 13.3);
307 pb.close();
308 let path = pb.finish().unwrap();
309 let s = skia(16, 16, &path);
310 let o = ours(16, 16, |r| {
311 r.line(4.0, 1.5, 14.2, 12.8);
312 r.line(14.2, 12.8, 1.7, 13.3);
313 r.line(1.7, 13.3, 4.0, 1.5);
314 });
315 let (mean, max) = diff(&o, &s);
316 assert!(mean < 4.0, "mean A8 diff vs tiny-skia too high: {mean}");
317 assert!(max < 40, "max A8 diff vs tiny-skia too high: {max}");
318 }
319
320 #[test]
321 fn matches_tiny_skia_with_curves() {
322 let mut pb = PathBuilder::new();
324 pb.move_to(6.0, 2.0);
325 pb.quad_to(14.0, 3.0, 13.0, 10.0);
326 pb.quad_to(12.0, 17.0, 5.0, 16.0);
327 pb.quad_to(2.0, 15.0, 3.0, 8.0);
328 pb.quad_to(3.5, 3.0, 6.0, 2.0);
329 pb.close();
330 let path = pb.finish().unwrap();
331 let s = skia(18, 18, &path);
332 let o = ours(18, 18, |r| {
333 r.quad(6.0, 2.0, 14.0, 3.0, 13.0, 10.0);
334 r.quad(13.0, 10.0, 12.0, 17.0, 5.0, 16.0);
335 r.quad(5.0, 16.0, 2.0, 15.0, 3.0, 8.0);
336 r.quad(3.0, 8.0, 3.5, 3.0, 6.0, 2.0);
337 });
338 let (mean, max) = diff(&o, &s);
339 assert!(mean < 3.0, "mean A8 diff vs tiny-skia too high: {mean}");
340 assert!(max < 40, "max A8 diff vs tiny-skia too high: {max}");
341 }
342}