1use std::collections::BTreeMap;
22use std::sync::Arc;
23
24use vello_cpu::kurbo::{Affine, BezPath, Ellipse as KEllipse, Point, Rect, RoundedRect, Shape};
25use vello_cpu::peniko::{Blob, Color as PColor, Gradient, ImageAlphaType, ImageData, ImageFormat};
26use vello_cpu::{Pixmap, RenderContext, Resources};
27
28use crate::render::{Draw, Paint};
29use crate::resolve::Color;
30
31const REACH: usize = 6;
35pub const SINGLE: usize = 1 << 16;
37const SINGLE_SIDE: usize = 1024;
38const ROUND: f64 = 16.0;
41const TILE: usize = 128;
43const MARGIN: f64 = 128.0;
47
48#[derive(Clone, Copy)]
51pub enum Outline {
52 Rounded(Rect, [f64; 4]),
53 Ellipse(Rect),
54}
55
56impl Outline {
57 pub fn rounded(rect: Rect, radii: [f64; 4]) -> Outline {
60 let r = RoundedRect::from_rect(rect, (radii[0], radii[1], radii[2], radii[3])).radii();
61 Outline::Rounded(
62 rect,
63 [r.top_left, r.top_right, r.bottom_right, r.bottom_left],
64 )
65 }
66
67 pub fn ellipse(rect: Rect) -> Outline {
68 if rect.width() == rect.height() {
69 Outline::rounded(rect, [rect.width() / 2.0; 4])
70 } else {
71 Outline::Ellipse(rect)
72 }
73 }
74
75 pub fn path(&self) -> BezPath {
76 match *self {
77 Outline::Rounded(r, [tl, tr, br, bl]) => {
78 RoundedRect::from_rect(r, (tl, tr, br, bl)).to_path(0.1)
79 }
80 Outline::Ellipse(r) => KEllipse::from_rect(r).to_path(0.1),
81 }
82 }
83
84 fn lines(&self, tolerance: f64) -> BezPath {
89 let mut out = BezPath::new();
90 let shape = match *self {
91 Outline::Rounded(r, [tl, tr, br, bl]) => {
92 RoundedRect::from_rect(r, (tl, tr, br, bl)).to_path(tolerance / 10.0)
93 }
94 Outline::Ellipse(r) => KEllipse::from_rect(r).to_path(tolerance / 10.0),
95 };
96 vello_cpu::kurbo::flatten(shape, tolerance, |el| out.push(el));
97 out
98 }
99
100 fn rect(&self) -> Rect {
101 match *self {
102 Outline::Rounded(r, _) | Outline::Ellipse(r) => r,
103 }
104 }
105
106 fn distance(&self, p: Point) -> f64 {
108 let r = self.rect();
109 let c = r.center();
110 let (hx, hy) = (r.width() / 2.0, r.height() / 2.0);
111 let (dx, dy) = (p.x - c.x, p.y - c.y);
112 match *self {
113 Outline::Rounded(_, [tl, tr, br, bl]) => {
114 let radius = match (dx >= 0.0, dy >= 0.0) {
115 (false, false) => tl,
116 (true, false) => tr,
117 (true, true) => br,
118 (false, true) => bl,
119 };
120 let (qx, qy) = (dx.abs() - hx + radius, dy.abs() - hy + radius);
121 qx.max(qy).min(0.0) + qx.max(0.0).hypot(qy.max(0.0)) - radius
122 }
123 Outline::Ellipse(_) => ellipse_distance(hx, hy, dx, dy),
124 }
125 }
126
127 fn holds(&self, r: Rect) -> bool {
129 [(r.x0, r.y0), (r.x1, r.y0), (r.x1, r.y1), (r.x0, r.y1)]
130 .iter()
131 .all(|&(x, y)| self.distance(Point::new(x, y)) <= 0.0)
132 }
133
134 fn misses(&self, r: Rect) -> bool {
136 if let Outline::Ellipse(e) = *self {
137 let c = e.center();
140 let (a, b) = (e.width() / 2.0, e.height() / 2.0);
141 let nx = (c.x.clamp(r.x0, r.x1) - c.x) / a;
142 let ny = (c.y.clamp(r.y0, r.y1) - c.y) / b;
143 return nx.hypot(ny) > 1.0;
144 }
145 self.distance(r.center()) > r.width().hypot(r.height()) / 2.0
146 }
147}
148
149fn kernel() -> [f32; 2 * REACH + 1] {
152 let mut k = [0f32; 2 * REACH + 1];
153 for (i, w) in k.iter_mut().enumerate() {
154 let x = i as f64 - REACH as f64;
155 *w = (-x * x / 8.0).exp() as f32;
156 }
157 let total: f32 = k.iter().sum();
158 k.map(|w| w / total)
159}
160
161#[derive(Clone, Copy)]
164struct Grid {
165 origin: Point,
166 step: f64,
167}
168
169impl Grid {
170 fn x(&self, i: isize) -> f64 {
171 self.origin.x + i as f64 * self.step
172 }
173 fn y(&self, j: isize) -> f64 {
174 self.origin.y + j as f64 * self.step
175 }
176 fn rect(&self, i0: isize, j0: isize, i1: isize, j1: isize) -> Rect {
177 Rect::new(self.x(i0), self.y(j0), self.x(i1), self.y(j1))
178 }
179 fn place(&self, i: isize, j: isize) -> Affine {
181 Affine::translate((self.x(i), self.y(j))) * Affine::scale(self.step)
182 }
183}
184
185fn blurred(path: &BezPath, grid: Grid, i0: isize, j0: isize, w: usize, h: usize) -> Vec<f32> {
189 let (cw, ch) = (w + 2 * REACH, h + 2 * REACH);
190 let mut ctx = RenderContext::new(cw as u16, ch as u16);
191 ctx.set_transform(
192 Affine::scale(1.0 / grid.step)
193 * Affine::translate((-grid.x(i0 - REACH as isize), -grid.y(j0 - REACH as isize))),
194 );
195 ctx.set_paint(PColor::from_rgba8(0, 0, 0, 255));
196 ctx.fill_path(path);
197 ctx.flush();
198 let mut coverage = Pixmap::new(cw as u16, ch as u16);
199 ctx.render(&mut coverage, &mut Resources::default());
200 let cov: Vec<f32> = coverage
201 .data_as_u8_slice()
202 .iter()
203 .skip(3)
204 .step_by(4)
205 .map(|&a| a as f32 / 255.0)
206 .collect();
207 let k = kernel();
208 let mut down = vec![0f32; h * cw];
209 for y in 0..h {
210 let row = &mut down[y * cw..][..cw];
211 for (t, weight) in k.iter().enumerate() {
212 let from = &cov[(y + t) * cw..][..cw];
213 for (o, c) in row.iter_mut().zip(from) {
214 *o += weight * c;
215 }
216 }
217 }
218 let mut out = vec![0f32; w * h];
219 for y in 0..h {
220 let from = &down[y * cw..][..cw];
221 for (x, o) in out[y * w..][..w].iter_mut().enumerate() {
222 *o = k.iter().zip(&from[x..]).map(|(a, b)| a * b).sum();
223 }
224 }
225 out
226}
227
228fn profile(lo: f64, hi: f64, origin: f64, step: f64, from: isize, len: usize) -> Vec<f32> {
231 let cov: Vec<f32> = (0..len + 2 * REACH)
232 .map(|n| {
233 let a = origin + (from + n as isize - REACH as isize) as f64 * step;
234 ((hi.min(a + step) - lo.max(a)) / step).clamp(0.0, 1.0) as f32
235 })
236 .collect();
237 let k = kernel();
238 (0..len)
239 .map(|i| k.iter().zip(&cov[i..]).map(|(a, b)| a * b).sum())
240 .collect()
241}
242
243fn image(values: &[f32], w: usize, h: usize, c: Color, alpha: f32) -> ImageData {
246 let pixels: Vec<u8> = values
247 .iter()
248 .flat_map(|v| [c.r, c.g, c.b, (v.clamp(0.0, 1.0) * alpha).round() as u8])
249 .collect();
250 ImageData {
251 data: Blob::new(Arc::new(pixels)),
252 format: ImageFormat::Rgba8,
253 alpha_type: ImageAlphaType::Alpha,
254 width: w as u32,
255 height: h as u32,
256 }
257}
258
259fn phi_cdf(x: f64) -> f64 {
262 let z = x.abs() / std::f64::consts::SQRT_2;
263 let t = 1.0 / (1.0 + 0.3275911 * z);
264 let poly = t
265 * (0.254829592
266 + t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
267 let erf = 1.0 - poly * (-z * z).exp();
268 0.5 * (1.0 + if x < 0.0 { -erf } else { erf })
269}
270
271enum Piece {
273 Solid(BezPath),
275 Down(BezPath, isize, isize),
277 Across(BezPath, isize, isize),
279 Disc(BezPath, Point, f64),
282 Patch(Option<BezPath>, isize, isize, isize, isize),
285}
286
287pub fn shadow(
290 outline: Outline,
291 std_dev: f64,
292 c: Color,
293 list: &mut Vec<Draw>,
294 images: &mut Vec<ImageData>,
295) {
296 shadow_with(outline, std_dev, c, list, images, SINGLE)
297}
298
299pub fn shadow_with(
301 outline: Outline,
302 std_dev: f64,
303 c: Color,
304 list: &mut Vec<Draw>,
305 images: &mut Vec<ImageData>,
306 single: usize,
307) {
308 let rect = outline.rect();
309 if rect.width() <= 0.0 || rect.height() <= 0.0 {
310 return;
311 }
312 let color = PColor::from_rgba8(c.r, c.g, c.b, c.a);
313 if std_dev <= 0.0 {
314 list.push(Draw::Fill(outline.path(), Paint::Solid(color)));
315 return;
316 }
317 let step = std_dev / 2.0;
318 let reach = REACH as f64 * step;
319 let grid = Grid {
320 origin: Point::new(rect.x0 - reach, rect.y0 - reach),
321 step,
322 };
323 let nx = ((rect.width() + 2.0 * reach) / step).ceil() as usize;
324 let ny = ((rect.height() + 2.0 * reach) / step).ceil() as usize;
325 let path = outline.lines(step / 100.0);
327 if nx * ny <= single && nx <= SINGLE_SIDE && ny <= SINGLE_SIDE {
328 let values = blurred(&path, grid, 0, 0, nx, ny);
329 let data = image(&values, nx, ny, c, c.a as f32);
330 images.push(data.clone());
331 list.push(Draw::Fill(
332 grid.rect(0, 0, nx as isize, ny as isize).to_path(0.1),
333 Paint::Image(data, grid.place(0, 0)),
334 ));
335 return;
336 }
337 let (nx, ny) = (nx as isize, ny as isize);
338 let pieces = match outline {
339 Outline::Rounded(r, radii) => rounded(r, radii, std_dev, grid, nx, ny),
340 Outline::Ellipse(e) => ellipse(e, std_dev, grid, nx, ny),
341 };
342 let extent = grid.rect(0, 0, nx, ny);
343 list.push(Draw::PushOpacity(
344 c.a as f32 / 255.0,
345 Some(extent.to_path(0.1)),
346 ));
347 let opaque = Color { a: 255, ..c };
348 let paint_over = |clip: &BezPath| clip.bounding_box().inflate(MARGIN, MARGIN).to_path(0.1);
349 for piece in pieces {
350 let (clip, paint) = match piece {
351 Piece::Solid(clip) => (clip, Paint::Solid(color.with_alpha(1.0))),
352 Piece::Down(clip, j0, j1) => {
353 let len = (j1 - j0 + 2) as usize;
354 let values = profile(rect.y0, rect.y1, grid.origin.y, step, j0 - 1, len);
355 let data = image(&values, 1, len, opaque, 255.0);
356 images.push(data.clone());
357 let place = Affine::translate((0.0, grid.y(j0 - 1))) * Affine::scale(step);
358 (clip, Paint::Image(data, place))
359 }
360 Piece::Across(clip, i0, i1) => {
361 let len = (i1 - i0 + 2) as usize;
362 let values = profile(rect.x0, rect.x1, grid.origin.x, step, i0 - 1, len);
363 let data = image(&values, len, 1, opaque, 255.0);
364 images.push(data.clone());
365 let place = Affine::translate((grid.x(i0 - 1), 0.0)) * Affine::scale(step);
366 (clip, Paint::Image(data, place))
367 }
368 Piece::Disc(clip, center, radius) => {
369 (clip, Paint::Gradient(disc(center, radius, std_dev, c)))
370 }
371 Piece::Patch(clip, i0, j0, i1, j1) => {
372 let (w, h) = ((i1 - i0 + 2) as usize, (j1 - j0 + 2) as usize);
373 let values = blurred(&path, grid, i0 - 1, j0 - 1, w, h);
374 let data = image(&values, w, h, opaque, 255.0);
375 images.push(data.clone());
376 (
377 clip.unwrap_or_else(|| grid.rect(i0, j0, i1, j1).to_path(0.1)),
378 Paint::Image(data, grid.place(i0 - 1, j0 - 1)),
379 )
380 }
381 };
382 list.push(Draw::PushAdd(paint_over(&clip)));
385 list.push(Draw::Fill(clip, paint));
386 list.push(Draw::Pop);
387 }
388 list.push(Draw::Pop);
389}
390
391fn disc(center: Point, radius: f64, std_dev: f64, c: Color) -> Gradient {
397 let reach = REACH as f64 * std_dev / 2.0;
398 let (r0, r1) = (radius - reach, radius + reach);
399 const STOPS: usize = 49;
400 let stops: Vec<(f32, PColor)> = (0..STOPS)
401 .map(|k| {
402 let t = k as f64 / (STOPS - 1) as f64;
403 let d = (r0 + t * (r1 - r0) - radius) / std_dev;
404 let density = (-d * d / 2.0).exp() / (2.0 * std::f64::consts::PI).sqrt();
405 let v = (phi_cdf(-d) - density * std_dev / (2.0 * radius)).clamp(0.0, 1.0);
406 (
407 t as f32,
408 PColor::new([
409 c.r as f32 / 255.0,
410 c.g as f32 / 255.0,
411 c.b as f32 / 255.0,
412 v as f32,
413 ]),
414 )
415 })
416 .collect();
417 Gradient::new_two_point_radial(center, r0 as f32, center, r1 as f32)
418 .with_stops(stops.as_slice())
419}
420
421fn rounded(
426 rect: Rect,
427 radii: [f64; 4],
428 std_dev: f64,
429 grid: Grid,
430 nx: isize,
431 ny: isize,
432) -> Vec<Piece> {
433 let reach = REACH as f64 * grid.step;
434 let (sx, sy) = (
435 |x: f64| (x - grid.origin.x) / grid.step,
436 |y: f64| (y - grid.origin.y) / grid.step,
437 );
438 let clamp_x = |v: f64| (v as isize).clamp(0, nx);
439 let clamp_y = |v: f64| (v as isize).clamp(0, ny);
440 let (xl, xr) = (2 * REACH as isize, clamp_x(sx(rect.x1 - reach).floor()));
442 let (yt, yb) = (2 * REACH as isize, clamp_y(sy(rect.y1 - reach).floor()));
443 let [tl, tr, br, bl] = radii;
444 let (w, h) = (rect.width(), rect.height());
445 let (top, right, bottom, left) = (w - tl - tr, h - tr - br, w - br - bl, h - tl - bl);
447 let disc_ok = |r: f64, a: (f64, f64), b: (f64, f64)| {
451 let run = |(len, other): (f64, f64)| len >= reach || (len <= 1e-9 && other == r);
452 r >= ROUND * std_dev && run(a) && run(b)
453 };
454 struct Corner {
455 zone: [isize; 4],
456 disc: Option<Point>,
457 radius: f64,
458 }
459 let mut corners = Vec::new();
460 for (r, at_right, at_bottom, a, b) in [
461 (tl, false, false, (top, tr), (left, bl)),
462 (tr, true, false, (top, tl), (right, br)),
463 (br, true, true, (bottom, bl), (right, tr)),
464 (bl, false, true, (bottom, br), (left, tl)),
465 ] {
466 let disc = disc_ok(r, a, b);
467 let inner = if disc { r } else { r + reach };
470 let (x0, x1) = if at_right {
471 let x = sx(rect.x1 - inner);
472 (clamp_x(if disc { x.round() } else { x.floor() }), nx)
473 } else {
474 let x = sx(rect.x0 + inner);
475 (0, clamp_x(if disc { x.round() } else { x.ceil() }))
476 };
477 let (y0, y1) = if at_bottom {
478 let y = sy(rect.y1 - inner);
479 (clamp_y(if disc { y.round() } else { y.floor() }), ny)
480 } else {
481 let y = sy(rect.y0 + inner);
482 (0, clamp_y(if disc { y.round() } else { y.ceil() }))
483 };
484 let center = Point::new(
485 if at_right { rect.x1 - r } else { rect.x0 + r },
486 if at_bottom { rect.y1 - r } else { rect.y0 + r },
487 );
488 corners.push(Corner {
489 zone: [x0, y0, x1, y1],
490 disc: disc.then_some(center),
491 radius: r,
492 });
493 }
494 let mut xs = vec![0, nx, xl.min(nx), xr];
495 let mut ys = vec![0, ny, yt.min(ny), yb];
496 for c in &corners {
497 xs.extend([c.zone[0], c.zone[2]]);
498 ys.extend([c.zone[1], c.zone[3]]);
499 }
500 xs.sort_unstable();
501 xs.dedup();
502 ys.sort_unstable();
503 ys.dedup();
504 let mut solid = BezPath::new();
505 let mut down: BTreeMap<(isize, isize), BezPath> = BTreeMap::new();
506 let mut across: BTreeMap<(isize, isize), BezPath> = BTreeMap::new();
507 let mut discs: BTreeMap<usize, BezPath> = BTreeMap::new();
508 let mut patches = Vec::new();
509 let meets =
511 |a: &[isize; 4], b: &[isize; 4]| a[0] < b[2] && b[0] < a[2] && a[1] < b[3] && b[1] < a[3];
512 let whole: Vec<bool> = corners
513 .iter()
514 .enumerate()
515 .map(|(n, c)| {
516 c.disc.is_none()
517 && corners
518 .iter()
519 .enumerate()
520 .all(|(m, o)| m == n || !meets(&c.zone, &o.zone))
521 })
522 .collect();
523 for (c, _) in corners.iter().zip(&whole).filter(|(_, w)| **w) {
524 let [i0, j0, i1, j1] = c.zone;
525 if i0 < i1 && j0 < j1 {
526 patches.push((i0, j0, i1, j1));
527 }
528 }
529 let add = |path: &mut BezPath, r: Rect| path.extend(r.path_elements(0.1));
530 for ys in ys.windows(2) {
531 let (j0, j1) = (ys[0], ys[1]);
532 for xs in xs.windows(2) {
533 let (i0, i1) = (xs[0], xs[1]);
534 let cell = grid.rect(i0, j0, i1, j1);
535 let within = |z: &[isize; 4]| z[0] <= i0 && i1 <= z[2] && z[1] <= j0 && j1 <= z[3];
536 if corners
537 .iter()
538 .zip(&whole)
539 .any(|(c, w)| *w && within(&c.zone))
540 {
541 } else if corners.iter().any(|c| c.disc.is_none() && within(&c.zone)) {
543 patches.push((i0, j0, i1, j1));
544 } else if let Some(n) = corners
545 .iter()
546 .position(|c| c.disc.is_some() && within(&c.zone))
547 {
548 add(discs.entry(n).or_default(), cell);
549 } else {
550 let central_x = xl <= i0 && i1 <= xr;
551 let central_y = yt <= j0 && j1 <= yb;
552 match (central_x, central_y) {
553 (true, true) => add(&mut solid, cell),
554 (true, false) => add(down.entry((j0, j1)).or_default(), cell),
555 (false, true) => add(across.entry((i0, i1)).or_default(), cell),
556 (false, false) => patches.push((i0, j0, i1, j1)),
559 }
560 }
561 }
562 }
563 let outline = Outline::Rounded(rect, radii);
564 let mut pieces = Vec::new();
565 if !solid.elements().is_empty() {
566 pieces.push(Piece::Solid(solid));
567 }
568 pieces.extend(runs(down).map(|((j0, j1), p)| Piece::Down(p, j0, j1)));
572 pieces.extend(runs(across).map(|((i0, i1), p)| Piece::Across(p, i0, i1)));
573 pieces.extend(
574 discs
575 .into_iter()
576 .map(|(n, p)| Piece::Disc(p, corners[n].disc.expect("a disc"), corners[n].radius)),
577 );
578 for (i0, j0, i1, j1) in merged(patches) {
579 pieces.extend(tiles(&outline, grid, i0, j0, i1, j1));
580 }
581 pieces
582}
583
584fn runs(
586 bands: BTreeMap<(isize, isize), BezPath>,
587) -> impl Iterator<Item = ((isize, isize), BezPath)> {
588 let mut out: Vec<((isize, isize), BezPath)> = Vec::new();
589 for ((from, to), path) in bands {
590 match out.last_mut() {
591 Some(((_, end), run)) if *end == from => {
592 *end = to;
593 run.extend(path);
594 }
595 _ => out.push(((from, to), path)),
596 }
597 }
598 out.into_iter()
599}
600
601fn merged(mut cells: Vec<(isize, isize, isize, isize)>) -> Vec<(isize, isize, isize, isize)> {
604 cells.sort_unstable_by_key(|&(i0, j0, _, j1)| (j0, j1, i0));
605 let mut rows: Vec<(isize, isize, isize, isize)> = Vec::new();
606 for c in cells {
607 match rows.last_mut() {
608 Some(r) if (r.1, r.3, r.2) == (c.1, c.3, c.0) => r.2 = c.2,
609 _ => rows.push(c),
610 }
611 }
612 rows.sort_unstable_by_key(|&(i0, j0, i1, _)| (i0, i1, j0));
613 let mut out: Vec<(isize, isize, isize, isize)> = Vec::new();
614 for c in rows {
615 match out.last_mut() {
616 Some(r) if (r.0, r.2, r.3) == (c.0, c.2, c.1) => r.3 = c.3,
617 _ => out.push(c),
618 }
619 }
620 out
621}
622
623fn tiles(outline: &Outline, grid: Grid, i0: isize, j0: isize, i1: isize, j1: isize) -> Vec<Piece> {
628 let tile = TILE as isize;
629 if (i1 - i0) <= tile && (j1 - j0) <= tile {
630 return vec![Piece::Patch(None, i0, j0, i1, j1)];
631 }
632 let reach = REACH as isize + 1;
634 let mut solid = BezPath::new();
635 let mut pieces = Vec::new();
636 let mut j = j0;
637 while j < j1 {
638 let je = (j + tile).min(j1);
639 let mut run: Option<isize> = None;
640 let mut i = i0;
641 while i < i1 {
642 let ie = (i + tile).min(i1);
643 let reached = grid.rect(i - reach, j - reach, ie + reach, je + reach);
644 let full = outline.holds(reached);
645 if full {
646 run.get_or_insert(i);
647 } else {
648 if let Some(start) = run.take() {
649 solid.extend(grid.rect(start, j, i, je).path_elements(0.1));
650 }
651 if !outline.misses(reached) {
652 pieces.push(Piece::Patch(None, i, j, ie, je));
653 }
654 }
655 i = ie;
656 }
657 if let Some(start) = run {
658 solid.extend(grid.rect(start, j, i1, je).path_elements(0.1));
659 }
660 j = je;
661 }
662 if !solid.elements().is_empty() {
663 pieces.insert(0, Piece::Solid(solid));
664 }
665 pieces
666}
667
668fn ellipse_distance(a: f64, b: f64, x: f64, y: f64) -> f64 {
672 let (px, py) = (x.abs(), y.abs());
674 let mut t = (a * py).atan2(b * px);
675 for _ in 0..40 {
676 let (sin, cos) = t.sin_cos();
677 let (ex, ey) = (a * cos, b * sin);
678 let f = (px - ex) * -a * sin + (py - ey) * b * cos;
680 let df =
681 (a * a * sin * sin + b * b * cos * cos) - (px - ex) * a * cos - (py - ey) * b * sin;
682 if df.abs() < 1e-12 {
683 break;
684 }
685 let next = (t - f / df).clamp(0.0, std::f64::consts::FRAC_PI_2);
686 if (next - t).abs() < 1e-12 {
687 t = next;
688 break;
689 }
690 t = next;
691 }
692 let (sin, cos) = t.sin_cos();
693 let d = (px - a * cos).hypot(py - b * sin);
694 if (px / a).hypot(py / b) <= 1.0 { -d } else { d }
695}
696
697fn ellipse(e: Rect, std_dev: f64, grid: Grid, nx: isize, ny: isize) -> Vec<Piece> {
704 let outline = Outline::Ellipse(e);
705 let step = grid.step;
706 let (a, b) = (e.width() / 2.0, e.height() / 2.0);
707 let c = e.center();
708 let inside = (REACH as f64 * step + step) * std::f64::consts::SQRT_2;
711 let outside = inside + step;
712 if a.min(b) <= 4.0 * inside {
713 return tiles(&outline, grid, 0, 0, nx, ny);
714 }
715 let at = |t: f64| Point::new(c.x + a * t.cos(), c.y + b * t.sin());
716 let normal = |t: f64| {
717 let n = vello_cpu::kurbo::Vec2::new(b * t.cos(), a * t.sin());
718 n / n.hypot()
719 };
720 let rho = |t: f64| {
721 let (sin, cos) = t.sin_cos();
722 (a * a * sin * sin + b * b * cos * cos).powf(1.5) / (a * b)
723 };
724 let distance = |p: Point| ellipse_distance(a, b, p.x - c.x, p.y - c.y);
725 let cut = |t: f64| -> Option<(Point, Point)> {
728 let p = at(t);
729 let ray = p - c;
730 let u = ray / ray.hypot();
731 let cos = u.dot(normal(t)).max(1e-6);
732 let mut s = inside / cos;
733 while distance(p - u * s) > -inside {
734 s += step;
735 if s >= ray.hypot() {
736 return None;
737 }
738 }
739 Some((p - u * s, p + u * (outside / cos)))
740 };
741 let tolerance = std_dev / 100.0;
742 let tile = TILE as f64 * step;
743 let fine = |t0: f64, t1: f64| -> bool {
745 let tm = (t0 + t1) / 2.0;
746 let (p0, p1) = (at(t0), at(t1));
747 let chord = (p1 - p0).hypot();
748 let tightest = rho(t0).min(rho(t1)).min(rho(tm));
749 if chord * chord / (8.0 * (tightest + outside)) > step {
751 return false;
752 }
753 if tightest >= ROUND * std_dev {
754 let center = at(tm) - normal(tm) * rho(tm);
755 let r = rho(tm);
756 [t0, (t0 + tm) / 2.0, (tm + t1) / 2.0, t1]
757 .iter()
758 .all(|&t| ((at(t) - center).hypot() - r).abs() <= tolerance)
759 } else {
760 chord <= tile
761 }
762 };
763 let mut ts = Vec::new();
766 let quarter = std::f64::consts::FRAC_PI_2;
767 for q in 0..4 {
768 let mut todo = vec![(q as f64 * quarter, (q + 1) as f64 * quarter, 0)];
769 let mut done = Vec::new();
770 while let Some((t0, t1, depth)) = todo.pop() {
771 if depth >= 24 || fine(t0, t1) {
772 done.push(t0);
773 } else {
774 let tm = (t0 + t1) / 2.0;
775 todo.push((tm, t1, depth + 1));
776 todo.push((t0, tm, depth + 1));
777 }
778 }
779 done.sort_by(f64::total_cmp);
780 ts.extend(done);
781 }
782 let mut cuts = Vec::with_capacity(ts.len());
783 for &t in &ts {
784 match cut(t) {
785 Some(c) => cuts.push(c),
786 None => return tiles(&outline, grid, 0, 0, nx, ny),
787 }
788 }
789 let mut pieces = Vec::new();
790 let mut solid = BezPath::new();
791 for (n, (inner, _)) in cuts.iter().enumerate() {
792 if n == 0 {
793 solid.move_to(*inner);
794 } else {
795 solid.line_to(*inner);
796 }
797 }
798 solid.close_path();
799 pieces.push(Piece::Solid(solid));
800 let tau = std::f64::consts::TAU;
801 for n in 0..ts.len() {
802 let (t0, t1) = (ts[n], if n + 1 < ts.len() { ts[n + 1] } else { tau });
803 let ((i0, o0), (i1, o1)) = (cuts[n], cuts[(n + 1) % cuts.len()]);
804 let mut wedge = BezPath::new();
805 wedge.move_to(i0);
806 wedge.line_to(i1);
807 wedge.line_to(o1);
808 wedge.line_to(o0);
809 wedge.close_path();
810 let tm = (t0 + t1) / 2.0;
811 if rho(t0).min(rho(t1)).min(rho(tm)) >= ROUND * std_dev {
812 pieces.push(Piece::Disc(wedge, at(tm) - normal(tm) * rho(tm), rho(tm)));
813 } else {
814 let bounds = wedge.bounding_box();
815 let i = |x: f64| ((x - grid.origin.x) / step).floor() as isize;
816 let j = |y: f64| ((y - grid.origin.y) / step).floor() as isize;
817 pieces.push(Piece::Patch(
818 Some(wedge),
819 i(bounds.x0),
820 j(bounds.y0),
821 i(bounds.x1) + 1,
822 j(bounds.y1) + 1,
823 ));
824 }
825 }
826 pieces
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832 use crate::render::{Frame, compare, paint_list};
833
834 fn bound(std_dev: f64, scale: f32) -> u8 {
843 match std_dev * scale as f64 {
844 s if s >= 1.0 => 4,
845 s if s >= 0.5 => 6,
846 s if s >= 0.25 => 11,
847 _ => 18,
848 }
849 }
850
851 fn pieces_match_one_image(outline: Outline, std_dev: f64, case: &str, scales: &[f32]) {
855 let color = Color {
856 r: 20,
857 g: 30,
858 b: 40,
859 a: 200,
860 };
861 let paint = |single: usize, scale: f32| {
862 let mut list = Vec::new();
863 shadow_with(outline, std_dev, color, &mut list, &mut Vec::new(), single);
864 let area = Frame {
865 x: 0.0,
866 y: 0.0,
867 width: 360.0,
868 height: 220.0,
869 };
870 let p = paint_list(area, &list, scale);
871 (
872 p.data_as_u8_slice().to_vec(),
873 p.width() as usize,
874 list.len(),
875 )
876 };
877 for &scale in scales {
878 let (one, width, single_list) = paint(usize::MAX, scale);
879 let (pieces, _, pieced_list) = paint(0, scale);
880 assert_eq!(single_list, 1, "{case}");
881 assert!(pieced_list > 3, "{case} isn't in pieces");
882 let d = compare(&one, &pieces, width).unwrap();
883 assert!(
884 d.within_tolerance() && d.max <= bound(std_dev, scale),
885 "{case}, σ {std_dev}, at {scale}: {d:?}"
886 );
887 }
888 }
889
890 #[test]
898 fn a_shadow_in_pieces_is_the_shadow_in_one() {
899 let r = |x0, y0, x1, y1| Rect::new(x0, y0, x1, y1);
900 let cases = [
901 (Outline::rounded(r(30.3, 20.7, 250.1, 170.9), [0.0; 4]), 4.0),
902 (
903 Outline::rounded(r(30.3, 20.7, 250.1, 170.9), [3.0, 80.0, 12.0, 45.0]),
904 4.0,
905 ),
906 (
907 Outline::rounded(r(30.0, 40.0, 330.0, 120.0), [40.0; 4]),
908 2.0,
909 ),
910 (Outline::rounded(r(30.0, 40.0, 330.0, 43.0), [1.0; 4]), 6.0),
911 (Outline::ellipse(r(25.5, 30.25, 300.5, 140.75)), 3.0),
912 (Outline::ellipse(r(25.0, 30.0, 185.0, 190.0)), 2.0),
913 (Outline::ellipse(r(15.3, 30.6, 345.2, 190.1)), 2.0),
914 (Outline::ellipse(r(20.0, 60.0, 340.0, 150.0)), 1.0),
915 (
916 Outline::rounded(
917 r(50.0986, 43.2465, 329.1681, 44.7226),
918 [0.738, 0.738, 0.738, 0.0],
919 ),
920 0.869,
921 ),
922 (
923 Outline::rounded(r(63.287, 18.532, 289.892, 187.314), [80.0; 4]),
924 0.584,
925 ),
926 ];
927 for (n, (outline, std_dev)) in cases.into_iter().enumerate() {
928 pieces_match_one_image(outline, std_dev, &format!("case {n}"), &[1.0, 0.73, 1.37]);
929 }
930 let mut seed = 0x2545_f491_4f6c_dd1du64;
931 for n in 0..12 {
932 let (outline, std_dev) = random(&mut seed);
933 pieces_match_one_image(outline, std_dev, &format!("random {n}"), &[0.5, 1.0]);
934 }
935 }
936
937 fn random(seed: &mut u64) -> (Outline, f64) {
942 let mut next = || {
943 *seed = seed
944 .wrapping_mul(6364136223846793005)
945 .wrapping_add(1442695040888963407);
946 (*seed >> 11) as f64 / (1u64 << 53) as f64
947 };
948 let (aw, ah) = (360.0, 220.0);
949 let sd = 0.5 + next() * 5.5;
950 let reach = 3.0 * sd;
951 let most = SINGLE_SIDE as f64 * sd / 2.0 - 2.0 * reach - sd - 1.0;
953 let w = 1.0 + next() * (aw - 2.0 * reach - 20.0).min(most);
954 let h = 1.0 + next() * (ah - 2.0 * reach - 20.0).min(most);
955 let x0 = reach + 5.0 + next() * (aw - 2.0 * reach - 10.0 - w);
956 let y0 = reach + 5.0 + next() * (ah - 2.0 * reach - 10.0 - h);
957 let rect = Rect::new(x0, y0, x0 + w, y0 + h);
958 let half = w.min(h) / 2.0;
959 let kind = (next() * 6.0) as usize;
960 if kind == 5 {
961 return (Outline::ellipse(rect), sd);
962 }
963 let mut corners = [(0, 0.0); 4];
964 for c in &mut corners {
965 *c = ((next() * 5.0) as usize, next());
966 }
967 let alike = next() < 0.5;
968 let radius = |(kind, u): (usize, f64)| match kind {
969 0 => 0.0,
970 1 => u * ROUND * sd,
971 2 => ROUND * sd * (0.98 + 0.04 * u),
972 3 => half,
973 4 => half - u * 3.0 * sd,
974 _ => u * half,
975 };
976 let radii = if alike {
977 [radius(corners[0]); 4]
978 } else {
979 corners.map(radius)
980 };
981 (Outline::rounded(rect, radii), sd)
982 }
983}