1use crate::media::Frame;
13use crate::model::{ShapeKind, ShapeStyle};
14use std::collections::hash_map::DefaultHasher;
15use std::hash::{Hash, Hasher};
16use std::sync::Arc;
17
18const SUB: usize = 4;
20const MAX_DIM: f32 = 8192.0;
22const MAX_PIXELS: f32 = 16.0e6;
24const REVEAL_HZ: f64 = 30.0;
26const STAR_INNER: f32 = 0.45;
28const CACHE_MAX: usize = 48;
29
30#[derive(Default)]
31pub struct ShapeRasterizer {
32 cache: std::collections::HashMap<(u64, u32, u32, u32), Arc<Frame>>,
33 cov: Vec<f32>,
35 path: Path,
36 edges: Vec<Edge>,
37 active: Vec<usize>,
38 xs: Vec<(f32, i32)>,
39 outline: Vec<[f32; 2]>,
40}
41
42impl ShapeRasterizer {
43 pub fn new() -> Self {
44 Self::default()
45 }
46 pub fn size(style: &ShapeStyle, t: f64) -> (f32, f32) {
48 let (w, h) = half_size(style, t);
49 let sw = if style.stroke_width.is_finite() { style.stroke_width.max(0.0) } else { 0.0 };
50 let (fw, fh) = match style.kind {
51 ShapeKind::Draw => draw_half_size(style),
52 ShapeKind::Line => {
53 let pad = sw.max(1.0);
54 (w + pad * 0.5, h + pad * 0.5)
55 }
56 ShapeKind::Arrow => {
57 let pad = sw.max(1.0).max(arrow_head(style));
58 (w + pad * 0.5, h + pad * 0.5)
59 }
60 _ => {
62 let pad = if style.stroke[3] > 0 { sw * 0.5 } else { 0.0 };
63 (w + pad, h + pad)
64 }
65 };
66 ((fw * 2.0).max(1.0), (fh * 2.0).max(1.0))
67 }
68
69 pub fn render(&mut self, style: &ShapeStyle, scale: f32, t: f64) -> Arc<Frame> {
71 let (pw, ph) = Self::size(style, t);
72 let mut s = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
73 s = s.min(MAX_DIM / pw).min(MAX_DIM / ph);
74 let area = pw * s * ph * s;
75 if area > MAX_PIXELS {
76 s *= (MAX_PIXELS / area).sqrt();
77 }
78 s = s.max(1e-3);
79 let lw = (pw * s).round().clamp(1.0, MAX_DIM) as u32;
80 let lh = (ph * s).round().clamp(1.0, MAX_DIM) as u32;
81 let bucket = reveal_bucket(style, t);
82 let mut hh = DefaultHasher::new();
83 style.cache_key().hash(&mut hh);
84 for f in [s, pw, ph] {
85 f.to_bits().hash(&mut hh);
86 }
87 let key = (hh.finish(), lw, lh, bucket);
88 if let Some(f) = self.cache.get(&key) {
89 return f.clone();
90 }
91 let frame = Arc::new(self.rasterize(style, s, t, lw, lh, bucket));
92 if self.cache.len() >= CACHE_MAX {
93 self.cache.clear();
95 }
96 self.cache.insert(key, frame.clone());
97 frame
98 }
99
100 fn rasterize(&mut self, style: &ShapeStyle, s: f32, t: f64, lw: u32, lh: u32, bucket: u32) -> Frame {
101 let mut out = Frame::new(lw, lh);
102 let (cx, cy) = (lw as f32 * 0.5, lh as f32 * 0.5);
103 let (w, h) = half_size(style, t);
104 let sw = (style.stroke_width.max(0.0) * s).max(0.0);
105 self.path.clear();
106 match style.kind {
107 ShapeKind::Draw => {
108 if style.page[3] > 0 {
109 out.fill(style.page);
110 }
111 let reveal = bucket_time(bucket);
112 for st in &style.strokes {
113 revealed(&st.points, reveal, &mut self.outline);
114 if self.outline.is_empty() {
115 continue;
116 }
117 to_layer(&mut self.outline, cx, cy, s);
118 self.path.clear();
119 add_stroke(&mut self.path, &self.outline, false, st.width.max(0.1) * s * 0.5);
120 self.flush(&mut out, st.color);
121 }
122 return out;
123 }
124 ShapeKind::Line | ShapeKind::Arrow => {
125 let head = if style.kind == ShapeKind::Arrow { arrow_head(style) * s } else { 0.0 };
126 let (sw_, sh_) = signed_half(style, t);
129 let (a, b) = ([cx - sw_ * s, cy - sh_ * s], [cx + sw_ * s, cy + sh_ * s]);
130 let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
131 let len = (dx * dx + dy * dy).sqrt();
132 if len > 1e-4 {
133 let (ux, uy) = (dx / len, dy / len);
134 let end = [b[0] - ux * head * 0.6, b[1] - uy * head * 0.6];
136 self.outline.clear();
137 self.outline.push(a);
138 self.outline.push(end);
139 add_stroke(&mut self.path, &self.outline, false, (sw * 0.5).max(0.35));
140 if head > 0.0 {
141 let base = [b[0] - ux * head, b[1] - uy * head];
142 let (px, py) = (-uy * head * 0.5, ux * head * 0.5);
143 self.path.push(b);
144 self.path.push([base[0] + px, base[1] + py]);
145 self.path.push([base[0] - px, base[1] - py]);
146 self.path.end();
147 }
148 } else if sw > 0.0 {
149 add_disc(&mut self.path, [cx, cy], (sw * 0.5).max(0.35));
150 }
151 self.flush(&mut out, style.stroke);
152 return out;
153 }
154 _ => shape_outline(style, w, h, s, &mut self.outline),
155 }
156 to_layer(&mut self.outline, cx, cy, s);
157 if style.fill[3] > 0 {
158 for p in self.outline.iter() {
159 self.path.push(*p);
160 }
161 self.path.end();
162 self.flush(&mut out, style.fill);
163 }
164 if style.stroke[3] > 0 && sw > 0.0 {
165 self.path.clear();
166 add_stroke(&mut self.path, &self.outline, true, (sw * 0.5).max(0.35));
167 self.flush(&mut out, style.stroke);
168 }
169 out
170 }
171
172 fn flush(&mut self, out: &mut Frame, color: [u8; 4]) {
174 if color[3] == 0 {
175 self.path.clear();
176 return;
177 }
178 let (w, h) = (out.width as usize, out.height as usize);
179 let band = fill_path(&self.path, w, h, &mut self.cov, &mut self.edges, &mut self.active, &mut self.xs);
180 self.path.clear();
181 let Some((x0, y0, x1, y1)) = band else { return };
182 for y in y0..y1 {
183 for x in x0..x1 {
184 let c = self.cov[y * w + x];
185 if c > 0.0015 {
186 let i = (y * w + x) * 4;
187 blend(&mut out.rgba[i..i + 4], color, c);
188 }
189 }
190 }
191 }
192}
193
194fn half_size(style: &ShapeStyle, t: f64) -> (f32, f32) {
197 let f = |a: f64| {
198 let v = a as f32;
199 if v.is_finite() {
200 v.abs().min(20000.0)
201 } else {
202 0.0
203 }
204 };
205 if let Some(pts) = style.poly_points() {
207 let (mut hx, mut hy) = (0.0f32, 0.0f32);
208 for &(x, y) in pts {
209 if x.is_finite() && y.is_finite() {
210 hx = hx.max(x.abs());
211 hy = hy.max(y.abs());
212 }
213 }
214 return (hx.min(20000.0).max(0.5), hy.min(20000.0).max(0.5));
215 }
216 (f(style.w.at(t)), f(style.h.at(t)))
217}
218
219fn signed_half(style: &ShapeStyle, t: f64) -> (f32, f32) {
223 let f = |a: f64| {
224 let v = a as f32;
225 if v.is_finite() {
226 v.clamp(-20000.0, 20000.0)
227 } else {
228 0.0
229 }
230 };
231 (f(style.w.at(t)), f(style.h.at(t)))
232}
233
234fn arrow_head(style: &ShapeStyle) -> f32 {
236 if style.corner > 0.0 && style.corner.is_finite() {
237 style.corner
238 } else {
239 (style.stroke_width.max(0.0) * 4.0).max(8.0)
240 }
241}
242
243fn draw_half_size(style: &ShapeStyle) -> (f32, f32) {
245 let (mut hx, mut hy) = (0.0f32, 0.0f32);
246 for st in &style.strokes {
247 let r = st.width.max(0.5) * 0.5;
248 for p in &st.points {
249 if p.0.is_finite() && p.1.is_finite() {
250 hx = hx.max(p.0.abs() + r);
251 hy = hy.max(p.1.abs() + r);
252 }
253 }
254 }
255 if style.page[3] > 0 {
256 let (w, h) = (style.w.value as f32, style.h.value as f32);
257 hx = hx.max(w.abs());
258 hy = hy.max(h.abs());
259 }
260 (hx.max(0.5), hy.max(0.5))
261}
262
263fn shape_outline(style: &ShapeStyle, w: f32, h: f32, s: f32, out: &mut Vec<[f32; 2]>) {
266 out.clear();
267 match style.kind {
268 ShapeKind::Rect => {
269 let r = style.corner.max(0.0).min(w).min(h);
270 if r <= 0.01 {
271 out.extend_from_slice(&[[-w, -h], [w, -h], [w, h], [-w, h]]);
272 } else {
273 let n = arc_steps(r * s);
274 for (i, (cx, cy)) in
276 [(-w + r, -h + r), (w - r, -h + r), (w - r, h - r), (-w + r, h - r)].into_iter().enumerate()
277 {
278 let a0 = std::f32::consts::PI * (1.0 + 0.5 * i as f32);
279 for k in 0..=n {
280 let a = a0 + std::f32::consts::FRAC_PI_2 * (k as f32 / n as f32);
281 out.push([cx + r * a.cos(), cy + r * a.sin()]);
282 }
283 }
284 }
285 }
286 ShapeKind::Ellipse => {
287 let n = arc_steps(w.max(h) * s) * 4;
288 for i in 0..n {
289 let a = std::f32::consts::TAU * (i as f32 / n as f32);
290 out.push([w * a.cos(), h * a.sin()]);
291 }
292 }
293 ShapeKind::Triangle => out.extend_from_slice(&[[0.0, -h], [w, h], [-w, h]]),
294 ShapeKind::Star => out.extend(ngon(style.sides, w, h, true)),
295 _ => match style.poly_points() {
296 Some(pts) => out.extend(pts.iter().map(|&(x, y)| [x, y])),
297 None => out.extend(ngon(style.sides, w, h, false)),
298 },
299 }
300}
301
302fn ngon(sides: u32, w: f32, h: f32, star: bool) -> Vec<[f32; 2]> {
305 let n = sides.clamp(3, 64) as usize;
306 let count = if star { n * 2 } else { n };
307 (0..count)
308 .map(|i| {
309 let a = -std::f32::consts::FRAC_PI_2 + std::f32::consts::TAU * (i as f32 / count as f32);
310 let r = if star && i % 2 == 1 { STAR_INNER } else { 1.0 };
311 [w * r * a.cos(), h * r * a.sin()]
312 })
313 .collect()
314}
315
316fn arc_steps(r_px: f32) -> usize {
318 ((r_px.max(1.0) / 3.0) as usize).clamp(4, 64)
319}
320
321fn revealed(points: &[(f32, f32, f32)], reveal: f32, out: &mut Vec<[f32; 2]>) {
323 out.clear();
324 if points.is_empty() {
325 return;
326 }
327 if !reveal.is_finite() {
328 out.extend(points.iter().map(|p| [p.0, p.1]));
329 return;
330 }
331 for (i, p) in points.iter().enumerate() {
332 if p.2 <= reveal {
333 out.push([p.0, p.1]);
334 continue;
335 }
336 if i > 0 {
337 let q = points[i - 1];
338 let span = p.2 - q.2;
339 let f = if span > 0.0 { ((reveal - q.2) / span).clamp(0.0, 1.0) } else { 0.0 };
340 if f > 0.0 {
341 out.push([q.0 + (p.0 - q.0) * f, q.1 + (p.1 - q.1) * f]);
342 }
343 }
344 break;
345 }
346}
347
348fn to_layer(pts: &mut [[f32; 2]], cx: f32, cy: f32, s: f32) {
349 for p in pts {
350 p[0] = cx + p[0] * s;
351 p[1] = cy + p[1] * s;
352 }
353}
354
355fn reveal_bucket(style: &ShapeStyle, t: f64) -> u32 {
357 if style.kind != ShapeKind::Draw {
358 return 0;
359 }
360 let rate = style.draw_rate as f64;
361 if !(rate > 0.0) || !rate.is_finite() {
362 return u32::MAX;
363 }
364 let r = t.max(0.0) * rate;
365 if !r.is_finite() || r >= style.draw_duration() {
366 return u32::MAX;
367 }
368 ((r * REVEAL_HZ) as u32).min(u32::MAX - 1)
369}
370
371fn bucket_time(bucket: u32) -> f32 {
372 if bucket == u32::MAX {
373 f32::INFINITY
374 } else {
375 (bucket as f64 / REVEAL_HZ) as f32
376 }
377}
378
379#[derive(Default)]
384struct Path {
385 pts: Vec<[f32; 2]>,
386 ends: Vec<usize>,
387}
388
389impl Path {
390 fn clear(&mut self) {
391 self.pts.clear();
392 self.ends.clear();
393 }
394 fn push(&mut self, p: [f32; 2]) {
395 self.pts.push(p);
396 }
397 fn end(&mut self) {
399 let start = self.ends.last().copied().unwrap_or(0);
400 if self.pts.len() < start + 3 {
401 self.pts.truncate(start);
402 return;
403 }
404 let seg = &mut self.pts[start..];
405 let mut area = 0.0f32;
406 for i in 0..seg.len() {
407 let a = seg[i];
408 let b = seg[(i + 1) % seg.len()];
409 area += a[0] * b[1] - b[0] * a[1];
410 }
411 if area < 0.0 {
412 seg.reverse();
413 }
414 self.ends.push(self.pts.len());
415 }
416}
417
418fn add_disc(path: &mut Path, c: [f32; 2], r: f32) {
419 let n = ((r * 2.0) as usize).clamp(6, 40);
420 for i in 0..n {
421 let a = std::f32::consts::TAU * (i as f32 / n as f32);
422 path.push([c[0] + r * a.cos(), c[1] + r * a.sin()]);
423 }
424 path.end();
425}
426
427fn add_stroke(path: &mut Path, pts: &[[f32; 2]], closed: bool, half: f32) {
429 let half = half.max(0.35);
430 let n = pts.len();
431 if n == 0 {
432 return;
433 }
434 let segs = if closed { n } else { n.saturating_sub(1) };
435 for i in 0..segs {
436 let a = pts[i];
437 let b = pts[(i + 1) % n];
438 let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
439 let len = (dx * dx + dy * dy).sqrt();
440 if !(len > 1e-5) {
441 continue;
442 }
443 let (nx, ny) = (-dy / len * half, dx / len * half);
444 path.push([a[0] + nx, a[1] + ny]);
445 path.push([b[0] + nx, b[1] + ny]);
446 path.push([b[0] - nx, b[1] - ny]);
447 path.push([a[0] - nx, a[1] - ny]);
448 path.end();
449 }
450 if half > 0.6 || segs == 0 {
451 for p in pts {
452 add_disc(path, *p, half);
453 }
454 }
455}
456
457struct Edge {
461 y0: f32,
462 y1: f32,
463 x0: f32,
464 dxdy: f32,
465 dir: i32,
466}
467
468fn fill_path(
471 path: &Path,
472 w: usize,
473 h: usize,
474 cov: &mut Vec<f32>,
475 edges: &mut Vec<Edge>,
476 active: &mut Vec<usize>,
477 xs: &mut Vec<(f32, i32)>,
478) -> Option<(usize, usize, usize, usize)> {
479 if path.ends.is_empty() || w == 0 || h == 0 {
480 return None;
481 }
482 edges.clear();
483 let (mut mnx, mut mny, mut mxx, mut mxy) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
484 let mut start = 0;
485 for &end in &path.ends {
486 let sub = &path.pts[start..end];
487 for i in 0..sub.len() {
488 let a = sub[i];
489 let b = sub[(i + 1) % sub.len()];
490 if !(a[0].is_finite() && a[1].is_finite() && b[0].is_finite() && b[1].is_finite()) {
491 continue;
492 }
493 mnx = mnx.min(a[0]);
494 mxx = mxx.max(a[0]);
495 mny = mny.min(a[1]);
496 mxy = mxy.max(a[1]);
497 if a[1] == b[1] {
498 continue;
499 }
500 let (top, bot, dir) = if a[1] < b[1] { (a, b, 1) } else { (b, a, -1) };
501 edges.push(Edge { y0: top[1], y1: bot[1], x0: top[0], dxdy: (bot[0] - top[0]) / (bot[1] - top[1]), dir });
502 }
503 start = end;
504 }
505 if edges.is_empty() || mnx > mxx {
506 return None;
507 }
508 let x0 = (mnx.floor().max(0.0) as usize).min(w);
509 let x1 = ((mxx.ceil().max(0.0) as usize) + 1).min(w);
510 let y0 = (mny.floor().max(0.0) as usize).min(h);
511 let y1 = ((mxy.ceil().max(0.0) as usize) + 1).min(h);
512 if x1 <= x0 || y1 <= y0 {
513 return None;
514 }
515 if cov.len() < w * h {
516 cov.resize(w * h, 0.0);
517 }
518 for y in y0..y1 {
519 cov[y * w + x0..y * w + x1].fill(0.0);
520 }
521 edges.sort_by(|a, b| a.y0.partial_cmp(&b.y0).unwrap_or(std::cmp::Ordering::Equal));
522 active.clear();
523 let mut cursor = 0usize;
524 let amt = 1.0 / SUB as f32;
525 for y in y0..y1 {
526 for s in 0..SUB {
527 let sy = y as f32 + (s as f32 + 0.5) / SUB as f32;
528 while cursor < edges.len() && edges[cursor].y0 <= sy {
529 active.push(cursor);
530 cursor += 1;
531 }
532 active.retain(|&i| edges[i].y1 > sy);
533 if active.len() < 2 {
534 continue;
535 }
536 xs.clear();
537 for &i in active.iter() {
538 let e = &edges[i];
539 if e.y0 <= sy {
540 xs.push((e.x0 + (sy - e.y0) * e.dxdy, e.dir));
541 }
542 }
543 if xs.len() < 2 {
544 continue;
545 }
546 xs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
547 let row = &mut cov[y * w..y * w + w];
548 let mut wind = 0;
549 for i in 0..xs.len() - 1 {
550 wind += xs[i].1;
551 if wind != 0 {
552 add_span(row, xs[i].0, xs[i + 1].0, amt, x0, x1);
553 }
554 }
555 }
556 }
557 Some((x0, y0, x1, y1))
558}
559
560fn add_span(row: &mut [f32], a: f32, b: f32, amt: f32, lo: usize, hi: usize) {
561 let a = a.max(lo as f32);
562 let b = b.min(hi as f32);
563 if !(b > a) {
564 return;
565 }
566 let i0 = a.floor() as usize;
567 let i1 = (b.ceil() as usize).min(hi);
568 if i1 <= i0 || i0 >= hi {
569 return;
570 }
571 if i1 - i0 == 1 {
572 row[i0] += (b - a) * amt;
573 return;
574 }
575 row[i0] += ((i0 + 1) as f32 - a) * amt;
576 for c in &mut row[i0 + 1..i1 - 1] {
577 *c += amt;
578 }
579 row[i1 - 1] += (b - (i1 - 1) as f32) * amt;
580}
581
582fn blend(px: &mut [u8], color: [u8; 4], cov: f32) {
584 let sa = color[3] as f32 / 255.0 * cov.clamp(0.0, 1.0);
585 if sa <= 0.0 {
586 return;
587 }
588 let da = px[3] as f32 / 255.0;
589 let oa = sa + da * (1.0 - sa);
590 if oa <= 0.0 {
591 return;
592 }
593 for i in 0..3 {
594 let v = (color[i] as f32 * sa + px[i] as f32 * da * (1.0 - sa)) / oa;
595 px[i] = v.round().clamp(0.0, 255.0) as u8;
596 }
597 px[3] = (oa * 255.0).round().clamp(0.0, 255.0) as u8;
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 use crate::model::{Animated, Stroke};
604
605 fn style(kind: ShapeKind, w: f32, h: f32) -> ShapeStyle {
606 ShapeStyle {
607 w: Animated::new(w as f64),
608 h: Animated::new(h as f64),
609 stroke: [0, 0, 0, 0],
610 ..ShapeStyle::new(kind)
611 }
612 }
613 fn px(f: &Frame, x: u32, y: u32) -> [u8; 4] {
614 let i = ((y * f.width + x) * 4) as usize;
615 [f.rgba[i], f.rgba[i + 1], f.rgba[i + 2], f.rgba[i + 3]]
616 }
617
618 #[test]
619 fn rect_fills_its_tight_layer() {
620 let mut r = ShapeRasterizer::new();
621 let s = style(ShapeKind::Rect, 50.0, 30.0);
622 assert_eq!(ShapeRasterizer::size(&s, 0.0), (100.0, 60.0));
623 let f = r.render(&s, 1.0, 0.0);
624 assert_eq!((f.width, f.height), (100, 60));
625 assert_eq!(px(&f, 50, 30), [255, 255, 255, 255], "middle must be opaque fill");
626 assert_eq!(px(&f, 0, 0), [255, 255, 255, 255], "a tight rect covers its whole layer");
627 }
628
629 #[test]
630 fn ellipse_is_transparent_outside() {
631 let mut r = ShapeRasterizer::new();
632 let f = r.render(&style(ShapeKind::Ellipse, 50.0, 30.0), 1.0, 0.0);
633 assert_eq!((f.width, f.height), (100, 60));
634 assert_eq!(px(&f, 50, 30)[3], 255, "centre filled");
635 assert_eq!(px(&f, 0, 0)[3], 0, "corner outside the ellipse");
636 assert_eq!(px(&f, 99, 59)[3], 0, "corner outside the ellipse");
637 assert!(px(&f, 50, 1)[3] > 100, "top of the ellipse: {:?}", px(&f, 50, 1));
639 }
640
641 #[test]
642 fn rounded_rect_clears_its_corners() {
643 let mut r = ShapeRasterizer::new();
644 let mut s = style(ShapeKind::Rect, 50.0, 30.0);
645 s.corner = 20.0;
646 let f = r.render(&s, 1.0, 0.0);
647 assert_eq!(px(&f, 1, 1)[3], 0, "rounded corner must be transparent");
648 assert_eq!(px(&f, 50, 30)[3], 255);
649 }
650
651 #[test]
652 fn stroke_only_shape_is_hollow() {
653 let mut r = ShapeRasterizer::new();
654 let mut s = style(ShapeKind::Rect, 50.0, 30.0);
655 s.fill = [0, 0, 0, 0];
656 s.stroke = [255, 0, 0, 255];
657 s.stroke_width = 6.0;
658 assert_eq!(ShapeRasterizer::size(&s, 0.0), (106.0, 66.0));
660 let f = r.render(&s, 1.0, 0.0);
661 assert_eq!(px(&f, 53, 33)[3], 0, "centre must be hollow");
662 assert_eq!(px(&f, 53, 1), [255, 0, 0, 255], "top edge is stroked");
663 assert_eq!(px(&f, 1, 33), [255, 0, 0, 255], "left edge is stroked");
664 }
665
666 #[test]
667 fn scale_changes_the_pixel_size_only() {
668 let mut r = ShapeRasterizer::new();
669 let s = style(ShapeKind::Rect, 50.0, 30.0);
670 let f = r.render(&s, 2.0, 0.0);
671 assert_eq!((f.width, f.height), (200, 120));
672 assert_eq!(px(&f, 100, 60)[3], 255);
673 }
674
675 #[test]
676 fn draw_reveals_progressively() {
677 let mut r = ShapeRasterizer::new();
678 let mut s = style(ShapeKind::Draw, 0.0, 0.0);
679 s.strokes =
680 vec![Stroke { color: [0, 255, 0, 255], width: 8.0, points: vec![(-100.0, 0.0, 0.0), (100.0, 0.0, 1.0)] }];
681 s.draw_rate = 1.0;
682 assert_eq!(ShapeRasterizer::size(&s, 0.0), (208.0, 8.0));
683 let mid = r.render(&s, 1.0, 0.5);
684 let cy = mid.height / 2;
685 assert!(px(&mid, 20, cy)[3] > 200, "start of the stroke is drawn: {:?}", px(&mid, 20, cy));
686 assert!(px(&mid, 104, cy)[3] > 200, "halfway point is drawn: {:?}", px(&mid, 104, cy));
687 assert_eq!(px(&mid, 180, cy)[3], 0, "the tail is not revealed yet");
688 s.draw_rate = 0.0;
690 let all = r.render(&s, 1.0, 0.0);
691 assert!(px(&all, 180, cy)[3] > 200, "rate 0 draws everything: {:?}", px(&all, 180, cy));
692 assert!(px(&all, 20, cy)[3] > 200);
693 }
694
695 #[test]
696 fn draw_page_paints_the_background() {
697 let mut r = ShapeRasterizer::new();
698 let mut s = style(ShapeKind::Draw, 60.0, 40.0);
699 s.page = [10, 20, 30, 255];
700 s.strokes = vec![Stroke { color: [255, 0, 0, 255], width: 4.0, points: vec![(0.0, 0.0, 0.0)] }];
701 let f = r.render(&s, 1.0, 0.0);
702 assert_eq!((f.width, f.height), (120, 80));
703 assert_eq!(px(&f, 2, 2), [10, 20, 30, 255], "page fills the layer");
704 assert_eq!(px(&f, 60, 40), [255, 0, 0, 255], "the dot sits on the page");
705 }
706
707 #[test]
708 fn line_and_arrow_are_drawn() {
709 let mut r = ShapeRasterizer::new();
710 let mut s = style(ShapeKind::Line, 40.0, 0.0);
711 s.stroke = [255, 255, 255, 255];
712 s.stroke_width = 4.0;
713 let f = r.render(&s, 1.0, 0.0);
714 let (cx, cy) = (f.width / 2, f.height / 2);
715 assert!(px(&f, cx, cy)[3] > 200, "the line crosses the centre");
716 let mut a = style(ShapeKind::Arrow, 40.0, 0.0);
717 a.stroke = [255, 255, 255, 255];
718 a.stroke_width = 4.0;
719 a.corner = 16.0;
720 assert_eq!(ShapeRasterizer::size(&a, 0.0), (96.0, 16.0));
722 let g = r.render(&a, 1.0, 0.0);
723 assert!(px(&g, 84, 8)[3] > 200, "near the tip: {:?}", px(&g, 84, 8));
724 assert!(px(&g, 76, 4)[3] > 200, "the head flares wider than the shaft: {:?}", px(&g, 76, 4));
725 assert_eq!(px(&g, 20, 4)[3], 0, "the shaft is thin away from the head");
726 }
727
728 #[test]
733 fn diagonal_line_covers_the_right_corners() {
734 let mut r = ShapeRasterizer::new();
735 let mut up_right = style(ShapeKind::Line, 40.0, -30.0);
736 up_right.stroke = [255, 255, 255, 255];
737 up_right.stroke_width = 4.0;
738 let f = r.render(&up_right, 1.0, 0.0);
739 let (w, h) = (f.width, f.height);
740 assert!(px(&f, 3, h - 3)[3] > 150, "bottom-left must be covered: {:?}", px(&f, 3, h - 3));
741 assert!(px(&f, w - 3, 3)[3] > 150, "top-right must be covered: {:?}", px(&f, w - 3, 3));
742 assert_eq!(px(&f, 3, 3)[3], 0, "top-left must be empty: {:?}", px(&f, 3, 3));
743 assert_eq!(px(&f, w - 3, h - 3)[3], 0, "bottom-right must be empty: {:?}", px(&f, w - 3, h - 3));
744
745 let mut down_right = style(ShapeKind::Line, 40.0, 30.0);
746 down_right.stroke = [255, 255, 255, 255];
747 down_right.stroke_width = 4.0;
748 let g = r.render(&down_right, 1.0, 0.0);
749 assert!(px(&g, 3, 3)[3] > 150, "top-left must be covered: {:?}", px(&g, 3, 3));
750 assert!(px(&g, w - 3, h - 3)[3] > 150, "bottom-right must be covered: {:?}", px(&g, w - 3, h - 3));
751 assert_eq!(px(&g, w - 3, 3)[3], 0, "top-right must be empty: {:?}", px(&g, w - 3, 3));
752 assert_eq!(px(&g, 3, h - 3)[3], 0, "bottom-left must be empty: {:?}", px(&g, 3, h - 3));
753 }
754
755 #[test]
756 fn ngon_vertex_counts() {
757 assert_eq!(ngon(6, 10.0, 10.0, false).len(), 6);
758 assert_eq!(ngon(3, 10.0, 10.0, false).len(), 3);
759 assert_eq!(ngon(5, 10.0, 10.0, true).len(), 10, "a star has two vertices per side");
760 assert_eq!(ngon(8, 10.0, 10.0, true).len(), 16);
761 assert_eq!(ngon(1, 10.0, 10.0, false).len(), 3, "sides are clamped to a triangle");
762 assert_eq!(ngon(999, 10.0, 10.0, false).len(), 64, "and to 64");
763 let p = ngon(5, 10.0, 10.0, false)[0];
765 assert!(p[1] < -9.0, "{p:?}");
766 }
767
768 #[test]
769 fn star_and_polygon_render_inside_the_layer() {
770 let mut r = ShapeRasterizer::new();
771 for kind in [ShapeKind::Star, ShapeKind::Polygon, ShapeKind::Triangle] {
772 let f = r.render(&style(kind, 50.0, 50.0), 1.0, 0.0);
773 assert_eq!((f.width, f.height), (100, 100), "{kind:?}");
774 assert_eq!(px(&f, 50, 55)[3], 255, "{kind:?} centre filled");
775 assert_eq!(px(&f, 2, 2)[3], 0, "{kind:?} top-left corner is empty");
776 }
777 }
778
779 #[test]
780 fn explicit_points_draw_the_polygon() {
781 let mut r = ShapeRasterizer::new();
782 let regular = style(ShapeKind::Triangle, 50.0, 30.0);
785 let mut poly = style(ShapeKind::Polygon, 50.0, 30.0);
786 poly.points = vec![(0.0, -30.0), (50.0, 30.0), (-50.0, 30.0)];
787 assert_eq!(ShapeRasterizer::size(&poly, 0.0), ShapeRasterizer::size(®ular, 0.0));
788 let a = r.render(®ular, 1.0, 0.0);
789 let b = r.render(&poly, 1.0, 0.0);
790 assert_eq!((b.width, b.height), (a.width, a.height));
791 assert_eq!(b.rgba, a.rgba, "an explicit triangle covers what the regular one does");
792 let mut wrong_size = poly.clone();
794 wrong_size.w = Animated::new(5.0);
795 wrong_size.h = Animated::new(5.0);
796 assert_eq!(ShapeRasterizer::size(&wrong_size, 0.0), (100.0, 60.0));
797 poly.points.truncate(2);
799 assert!(!Arc::ptr_eq(&r.render(&poly, 1.0, 0.0), &b));
800 }
801
802 #[test]
803 fn identical_requests_hit_the_cache() {
804 let mut r = ShapeRasterizer::new();
805 let s = style(ShapeKind::Ellipse, 40.0, 40.0);
806 let a = r.render(&s, 1.0, 0.0);
807 let b = r.render(&s, 1.0, 0.0);
808 assert!(Arc::ptr_eq(&a, &b), "cache hit expected");
809 let c = r.render(&s, 2.0, 0.0);
810 assert!(!Arc::ptr_eq(&a, &c), "a different scale is a different layer");
811 }
812
813 #[test]
814 fn draw_reveal_buckets_share_cache_entries() {
815 let mut r = ShapeRasterizer::new();
816 let mut s = style(ShapeKind::Draw, 0.0, 0.0);
817 s.strokes =
818 vec![Stroke { color: [255, 255, 255, 255], width: 4.0, points: vec![(0.0, 0.0, 0.0), (50.0, 0.0, 2.0)] }];
819 let a = r.render(&s, 1.0, 0.5);
820 let b = r.render(&s, 1.0, 0.51); assert!(Arc::ptr_eq(&a, &b), "reveal is bucketed for the cache");
822 let c = r.render(&s, 1.0, 1.0);
823 assert!(!Arc::ptr_eq(&a, &c));
824 let d = r.render(&s, 1.0, 9.0);
826 let e = r.render(&s, 1.0, 99.0);
827 assert!(Arc::ptr_eq(&d, &e), "finished drawings share one entry");
828 }
829
830 #[test]
831 fn degenerate_styles_never_panic() {
832 let mut r = ShapeRasterizer::new();
833 for kind in ShapeKind::ALL {
834 let mut s = style(kind, 0.0, 0.0);
835 s.stroke_width = 0.0;
836 s.sides = 0;
837 let f = r.render(&s, 0.0, -5.0);
838 assert!(f.width >= 1 && f.height >= 1, "{kind:?}");
839 let mut odd = style(kind, f32::INFINITY, f32::NAN);
840 odd.stroke = [255, 255, 255, 255];
841 odd.corner = -3.0;
842 let g = r.render(&odd, 3.0, f64::NAN);
843 assert!(g.width >= 1 && g.height >= 1, "{kind:?}");
844 }
845 }
846
847 #[test]
848 fn oversized_layers_are_clamped() {
849 let mut r = ShapeRasterizer::new();
850 let s = style(ShapeKind::Rect, 100_000.0, 1.0);
851 assert_eq!(ShapeRasterizer::size(&s, 0.0), (40_000.0, 2.0));
853 let f = r.render(&s, 1.0, 0.0);
854 assert_eq!(f.width, MAX_DIM as u32);
855 assert!(f.height >= 1 && f.height < 4);
856 }
857
858 #[test]
859 fn revealed_cuts_the_last_segment() {
860 let pts = [(0.0f32, 0.0f32, 0.0f32), (100.0, 0.0, 1.0)];
861 let mut out = Vec::new();
862 revealed(&pts, 0.5, &mut out);
863 assert_eq!(out, vec![[0.0, 0.0], [50.0, 0.0]]);
864 revealed(&pts, f32::INFINITY, &mut out);
865 assert_eq!(out, vec![[0.0, 0.0], [100.0, 0.0]]);
866 revealed(&pts, -1.0, &mut out);
867 assert!(out.is_empty(), "nothing drawn before the first point");
868 }
869}