1use std::collections::BTreeMap;
22use std::sync::Arc;
23
24use kurbo::{Affine, BezPath, Ellipse as KEllipse, Point, Rect, RoundedRect, Shape};
25use peniko::{Blob, Color as PColor, Gradient, ImageAlphaType, ImageData, ImageFormat};
26
27use crate::display::{Blend, Builder};
28use crate::render::{Draw, Paint};
29use crate::resolve::Rgba;
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 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 coverage(path: &BezPath, to: Affine, w: usize, h: usize) -> Vec<f32> {
193 let stride = w + 2;
195 let mut acc = vec![0f64; stride * h + 2];
196 let mut cell = |p0: Point, p1: Point| {
197 if (p0.y - p1.y).abs() <= 1e-12 {
198 return;
199 }
200 let (dir, p0, p1) = if p0.y < p1.y {
201 (1.0, p0, p1)
202 } else {
203 (-1.0, p1, p0)
204 };
205 let dxdy = (p1.x - p0.x) / (p1.y - p0.y);
206 let mut x = p0.x;
207 if p0.y < 0.0 {
208 x -= p0.y * dxdy;
209 }
210 let first = p0.y.max(0.0) as usize;
211 for y in first..h.min(p1.y.ceil() as usize) {
212 let start = (y * stride) as isize;
213 let dy = ((y + 1) as f64).min(p1.y) - (y as f64).max(p0.y);
214 let xnext = x + dxdy * dy;
215 let d = dy * dir;
216 let (x0, x1) = if x < xnext { (x, xnext) } else { (xnext, x) };
217 let x0floor = x0.floor();
218 let x0i = x0floor as isize;
219 let x1ceil = x1.ceil();
220 let x1i = x1ceil as isize;
221 let at = start + x0i;
222 if at < 0 {
223 x = xnext;
224 continue;
225 }
226 let at = at as usize;
227 if x1i <= x0i + 1 {
228 let xmf = 0.5 * (x + xnext) - x0floor;
229 acc[at] += d - d * xmf;
230 acc[at + 1] += d * xmf;
231 } else {
232 let s = (x1 - x0).recip();
233 let x0f = x0 - x0floor;
234 let a0 = 0.5 * s * (1.0 - x0f) * (1.0 - x0f);
235 let x1f = x1 - x1ceil + 1.0;
236 let am = 0.5 * s * x1f * x1f;
237 acc[at] += d * a0;
238 if x1i == x0i + 2 {
239 acc[at + 1] += d * (1.0 - a0 - am);
240 } else {
241 let a1 = s * (1.5 - x0f);
242 acc[at + 1] += d * (a1 - a0);
243 for xi in x0i + 2..x1i - 1 {
244 acc[(start + xi) as usize] += d * s;
245 }
246 let a2 = a1 + (x1i - x0i - 3) as f64 * s;
247 acc[(start + x1i - 1) as usize] += d * (1.0 - a2 - am);
248 }
249 acc[(start + x1i) as usize] += d * am;
250 }
251 x = xnext;
252 }
253 };
254 let mut line = |p0: Point, p1: Point| {
256 let mut cuts = vec![0.0, 1.0];
257 for edge in [0.0, w as f64] {
258 if (p0.x - edge) * (p1.x - edge) < 0.0 {
259 cuts.push((edge - p0.x) / (p1.x - p0.x));
260 }
261 }
262 cuts.sort_by(f64::total_cmp);
263 let at = |t: f64| {
264 let p = p0.lerp(p1, t);
265 Point::new(p.x.clamp(0.0, w as f64), p.y)
266 };
267 for pair in cuts.windows(2) {
268 cell(at(pair[0]), at(pair[1]));
269 }
270 };
271 let (mut first, mut last) = (None, None);
272 for el in path.elements() {
273 match *el {
274 kurbo::PathEl::MoveTo(p) => {
275 if let (Some(a), Some(b)) = (last, first) {
276 line(a, b);
277 }
278 let p = to * p;
279 first = Some(p);
280 last = Some(p);
281 }
282 kurbo::PathEl::LineTo(p) => {
283 let p = to * p;
284 if let Some(a) = last {
285 line(a, p);
286 }
287 last = Some(p);
288 }
289 kurbo::PathEl::ClosePath => {
290 if let (Some(a), Some(b)) = (last, first) {
291 line(a, b);
292 }
293 last = first;
294 }
295 _ => unreachable!("an outline flattened to lines"),
296 }
297 }
298 if let (Some(a), Some(b)) = (last, first) {
299 line(a, b);
300 }
301 let mut out = Vec::with_capacity(w * h);
302 for row in acc.chunks(stride).take(h) {
303 let mut sum = 0.0;
304 out.extend(row[..w].iter().map(|a| {
305 sum += a;
306 ((sum.abs().min(1.0) * 255.0).round() / 255.0) as f32
307 }));
308 }
309 out
310}
311
312fn blurred(path: &BezPath, grid: Grid, i0: isize, j0: isize, w: usize, h: usize) -> Vec<f32> {
316 let (cw, ch) = (w + 2 * REACH, h + 2 * REACH);
317 let to = Affine::scale(1.0 / grid.step)
318 * Affine::translate((-grid.x(i0 - REACH as isize), -grid.y(j0 - REACH as isize)));
319 let cov = coverage(path, to, cw, ch);
320 let k = kernel();
321 let mut down = vec![0f32; h * cw];
322 for y in 0..h {
323 let row = &mut down[y * cw..][..cw];
324 for (t, weight) in k.iter().enumerate() {
325 let from = &cov[(y + t) * cw..][..cw];
326 for (o, c) in row.iter_mut().zip(from) {
327 *o += weight * c;
328 }
329 }
330 }
331 let mut out = vec![0f32; w * h];
332 for y in 0..h {
333 let from = &down[y * cw..][..cw];
334 for (x, o) in out[y * w..][..w].iter_mut().enumerate() {
335 *o = k.iter().zip(&from[x..]).map(|(a, b)| a * b).sum();
336 }
337 }
338 out
339}
340
341fn profile(lo: f64, hi: f64, origin: f64, step: f64, from: isize, len: usize) -> Vec<f32> {
344 let cov: Vec<f32> = (0..len + 2 * REACH)
345 .map(|n| {
346 let a = origin + (from + n as isize - REACH as isize) as f64 * step;
347 ((hi.min(a + step) - lo.max(a)) / step).clamp(0.0, 1.0) as f32
348 })
349 .collect();
350 let k = kernel();
351 (0..len)
352 .map(|i| k.iter().zip(&cov[i..]).map(|(a, b)| a * b).sum())
353 .collect()
354}
355
356fn image(values: &[f32], w: usize, h: usize, c: Rgba, alpha: f32) -> ImageData {
359 let pixels: Vec<u8> = values
360 .iter()
361 .flat_map(|v| [c.r, c.g, c.b, (v.clamp(0.0, 1.0) * alpha).round() as u8])
362 .collect();
363 ImageData {
364 data: Blob::new(Arc::new(pixels)),
365 format: ImageFormat::Rgba8,
366 alpha_type: ImageAlphaType::Alpha,
367 width: w as u32,
368 height: h as u32,
369 }
370}
371
372fn phi_cdf(x: f64) -> f64 {
375 let z = x.abs() / std::f64::consts::SQRT_2;
376 let t = 1.0 / (1.0 + 0.3275911 * z);
377 let poly = t
378 * (0.254829592
379 + t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
380 let erf = 1.0 - poly * (-z * z).exp();
381 0.5 * (1.0 + if x < 0.0 { -erf } else { erf })
382}
383
384enum Piece {
386 Solid(BezPath),
388 Down(BezPath, isize, isize),
390 Across(BezPath, isize, isize),
392 Disc(BezPath, Point, f64),
395 Patch(Option<BezPath>, isize, isize, isize, isize),
398}
399
400pub fn shadow(
403 outline: Outline,
404 std_dev: f64,
405 c: Rgba,
406 list: &mut Builder,
407 images: &mut Vec<ImageData>,
408) {
409 shadow_with(outline, std_dev, c, list, images, SINGLE)
410}
411
412pub fn shadow_with(
414 outline: Outline,
415 std_dev: f64,
416 c: Rgba,
417 list: &mut Builder,
418 images: &mut Vec<ImageData>,
419 single: usize,
420) {
421 let rect = outline.rect();
422 if rect.width() <= 0.0 || rect.height() <= 0.0 {
423 return;
424 }
425 let color = PColor::from_rgba8(c.r, c.g, c.b, c.a);
426 if std_dev <= 0.0 {
427 list.draw(Draw::Fill(outline.path(), Paint::Solid(color)));
428 return;
429 }
430 let step = std_dev / 2.0;
431 let reach = REACH as f64 * step;
432 let grid = Grid {
433 origin: Point::new(rect.x0 - reach, rect.y0 - reach),
434 step,
435 };
436 let nx = ((rect.width() + 2.0 * reach) / step).ceil() as usize;
437 let ny = ((rect.height() + 2.0 * reach) / step).ceil() as usize;
438 let path = outline.lines(step / 100.0);
440 if nx * ny <= single && nx <= SINGLE_SIDE && ny <= SINGLE_SIDE {
441 let values = blurred(&path, grid, 0, 0, nx, ny);
442 let data = image(&values, nx, ny, c, c.a as f32);
443 images.push(data.clone());
444 list.draw(Draw::Fill(
445 grid.rect(0, 0, nx as isize, ny as isize).to_path(0.1),
446 Paint::Image(data, grid.place(0, 0)),
447 ));
448 return;
449 }
450 let (nx, ny) = (nx as isize, ny as isize);
451 let pieces = match outline {
452 Outline::Rounded(r, radii) => rounded(r, radii, std_dev, grid, nx, ny),
453 Outline::Ellipse(e) => ellipse(e, std_dev, grid, nx, ny),
454 };
455 let extent = grid.rect(0, 0, nx, ny);
456 list.push_layer(
457 Blend::Opacity(c.a as f32 / 255.0),
458 Some(extent.to_path(0.1)),
459 );
460 let opaque = Rgba { a: 255, ..c };
461 let paint_over = |clip: &BezPath| clip.bounding_box().inflate(MARGIN, MARGIN).to_path(0.1);
462 for piece in pieces {
463 let (clip, paint) = match piece {
464 Piece::Solid(clip) => (clip, Paint::Solid(color.with_alpha(1.0))),
465 Piece::Down(clip, j0, j1) => {
466 let len = (j1 - j0 + 2) as usize;
467 let values = profile(rect.y0, rect.y1, grid.origin.y, step, j0 - 1, len);
468 let data = image(&values, 1, len, opaque, 255.0);
469 images.push(data.clone());
470 let place = Affine::translate((0.0, grid.y(j0 - 1))) * Affine::scale(step);
471 (clip, Paint::Image(data, place))
472 }
473 Piece::Across(clip, i0, i1) => {
474 let len = (i1 - i0 + 2) as usize;
475 let values = profile(rect.x0, rect.x1, grid.origin.x, step, i0 - 1, len);
476 let data = image(&values, len, 1, opaque, 255.0);
477 images.push(data.clone());
478 let place = Affine::translate((grid.x(i0 - 1), 0.0)) * Affine::scale(step);
479 (clip, Paint::Image(data, place))
480 }
481 Piece::Disc(clip, center, radius) => {
482 (clip, Paint::Gradient(disc(center, radius, std_dev, c)))
483 }
484 Piece::Patch(clip, i0, j0, i1, j1) => {
485 let (w, h) = ((i1 - i0 + 2) as usize, (j1 - j0 + 2) as usize);
486 let values = blurred(&path, grid, i0 - 1, j0 - 1, w, h);
487 let data = image(&values, w, h, opaque, 255.0);
488 images.push(data.clone());
489 (
490 clip.unwrap_or_else(|| grid.rect(i0, j0, i1, j1).to_path(0.1)),
491 Paint::Image(data, grid.place(i0 - 1, j0 - 1)),
492 )
493 }
494 };
495 list.push_layer(Blend::Add, Some(paint_over(&clip)));
498 list.draw(Draw::Fill(clip, paint));
499 list.pop();
500 }
501 list.pop();
502}
503
504fn disc(center: Point, radius: f64, std_dev: f64, c: Rgba) -> Gradient {
510 let reach = REACH as f64 * std_dev / 2.0;
511 let (r0, r1) = (radius - reach, radius + reach);
512 const STOPS: usize = 49;
513 let stops: Vec<(f32, PColor)> = (0..STOPS)
514 .map(|k| {
515 let t = k as f64 / (STOPS - 1) as f64;
516 let d = (r0 + t * (r1 - r0) - radius) / std_dev;
517 let density = (-d * d / 2.0).exp() / (2.0 * std::f64::consts::PI).sqrt();
518 let v = (phi_cdf(-d) - density * std_dev / (2.0 * radius)).clamp(0.0, 1.0);
519 (
520 t as f32,
521 PColor::new([
522 c.r as f32 / 255.0,
523 c.g as f32 / 255.0,
524 c.b as f32 / 255.0,
525 v as f32,
526 ]),
527 )
528 })
529 .collect();
530 Gradient::new_two_point_radial(center, r0 as f32, center, r1 as f32)
531 .with_stops(stops.as_slice())
532}
533
534fn rounded(
539 rect: Rect,
540 radii: [f64; 4],
541 std_dev: f64,
542 grid: Grid,
543 nx: isize,
544 ny: isize,
545) -> Vec<Piece> {
546 let reach = REACH as f64 * grid.step;
547 let (sx, sy) = (
548 |x: f64| (x - grid.origin.x) / grid.step,
549 |y: f64| (y - grid.origin.y) / grid.step,
550 );
551 let clamp_x = |v: f64| (v as isize).clamp(0, nx);
552 let clamp_y = |v: f64| (v as isize).clamp(0, ny);
553 let (xl, xr) = (2 * REACH as isize, clamp_x(sx(rect.x1 - reach).floor()));
555 let (yt, yb) = (2 * REACH as isize, clamp_y(sy(rect.y1 - reach).floor()));
556 let [tl, tr, br, bl] = radii;
557 let (w, h) = (rect.width(), rect.height());
558 let (top, right, bottom, left) = (w - tl - tr, h - tr - br, w - br - bl, h - tl - bl);
560 let disc_ok = |r: f64, a: (f64, f64), b: (f64, f64)| {
564 let run = |(len, other): (f64, f64)| len >= reach || (len <= 1e-9 && other == r);
565 r >= ROUND * std_dev && run(a) && run(b)
566 };
567 struct Corner {
568 zone: [isize; 4],
569 disc: Option<Point>,
570 radius: f64,
571 }
572 let mut corners = Vec::new();
573 for (r, at_right, at_bottom, a, b) in [
574 (tl, false, false, (top, tr), (left, bl)),
575 (tr, true, false, (top, tl), (right, br)),
576 (br, true, true, (bottom, bl), (right, tr)),
577 (bl, false, true, (bottom, br), (left, tl)),
578 ] {
579 let disc = disc_ok(r, a, b);
580 let inner = if disc { r } else { r + reach };
583 let (x0, x1) = if at_right {
584 let x = sx(rect.x1 - inner);
585 (clamp_x(if disc { x.round() } else { x.floor() }), nx)
586 } else {
587 let x = sx(rect.x0 + inner);
588 (0, clamp_x(if disc { x.round() } else { x.ceil() }))
589 };
590 let (y0, y1) = if at_bottom {
591 let y = sy(rect.y1 - inner);
592 (clamp_y(if disc { y.round() } else { y.floor() }), ny)
593 } else {
594 let y = sy(rect.y0 + inner);
595 (0, clamp_y(if disc { y.round() } else { y.ceil() }))
596 };
597 let center = Point::new(
598 if at_right { rect.x1 - r } else { rect.x0 + r },
599 if at_bottom { rect.y1 - r } else { rect.y0 + r },
600 );
601 corners.push(Corner {
602 zone: [x0, y0, x1, y1],
603 disc: disc.then_some(center),
604 radius: r,
605 });
606 }
607 let mut xs = vec![0, nx, xl.min(nx), xr];
608 let mut ys = vec![0, ny, yt.min(ny), yb];
609 for c in &corners {
610 xs.extend([c.zone[0], c.zone[2]]);
611 ys.extend([c.zone[1], c.zone[3]]);
612 }
613 xs.sort_unstable();
614 xs.dedup();
615 ys.sort_unstable();
616 ys.dedup();
617 let mut solid = BezPath::new();
618 let mut down: BTreeMap<(isize, isize), BezPath> = BTreeMap::new();
619 let mut across: BTreeMap<(isize, isize), BezPath> = BTreeMap::new();
620 let mut discs: BTreeMap<usize, BezPath> = BTreeMap::new();
621 let mut patches = Vec::new();
622 let meets =
624 |a: &[isize; 4], b: &[isize; 4]| a[0] < b[2] && b[0] < a[2] && a[1] < b[3] && b[1] < a[3];
625 let whole: Vec<bool> = corners
626 .iter()
627 .enumerate()
628 .map(|(n, c)| {
629 c.disc.is_none()
630 && corners
631 .iter()
632 .enumerate()
633 .all(|(m, o)| m == n || !meets(&c.zone, &o.zone))
634 })
635 .collect();
636 for (c, _) in corners.iter().zip(&whole).filter(|(_, w)| **w) {
637 let [i0, j0, i1, j1] = c.zone;
638 if i0 < i1 && j0 < j1 {
639 patches.push((i0, j0, i1, j1));
640 }
641 }
642 let add = |path: &mut BezPath, r: Rect| path.extend(r.path_elements(0.1));
643 for ys in ys.windows(2) {
644 let (j0, j1) = (ys[0], ys[1]);
645 for xs in xs.windows(2) {
646 let (i0, i1) = (xs[0], xs[1]);
647 let cell = grid.rect(i0, j0, i1, j1);
648 let within = |z: &[isize; 4]| z[0] <= i0 && i1 <= z[2] && z[1] <= j0 && j1 <= z[3];
649 if corners
650 .iter()
651 .zip(&whole)
652 .any(|(c, w)| *w && within(&c.zone))
653 {
654 } else if corners.iter().any(|c| c.disc.is_none() && within(&c.zone)) {
656 patches.push((i0, j0, i1, j1));
657 } else if let Some(n) = corners
658 .iter()
659 .position(|c| c.disc.is_some() && within(&c.zone))
660 {
661 add(discs.entry(n).or_default(), cell);
662 } else {
663 let central_x = xl <= i0 && i1 <= xr;
664 let central_y = yt <= j0 && j1 <= yb;
665 match (central_x, central_y) {
666 (true, true) => add(&mut solid, cell),
667 (true, false) => add(down.entry((j0, j1)).or_default(), cell),
668 (false, true) => add(across.entry((i0, i1)).or_default(), cell),
669 (false, false) => patches.push((i0, j0, i1, j1)),
672 }
673 }
674 }
675 }
676 let outline = Outline::Rounded(rect, radii);
677 let mut pieces = Vec::new();
678 if !solid.elements().is_empty() {
679 pieces.push(Piece::Solid(solid));
680 }
681 pieces.extend(runs(down).map(|((j0, j1), p)| Piece::Down(p, j0, j1)));
685 pieces.extend(runs(across).map(|((i0, i1), p)| Piece::Across(p, i0, i1)));
686 pieces.extend(
687 discs
688 .into_iter()
689 .map(|(n, p)| Piece::Disc(p, corners[n].disc.expect("a disc"), corners[n].radius)),
690 );
691 for (i0, j0, i1, j1) in merged(patches) {
692 pieces.extend(tiles(&outline, grid, i0, j0, i1, j1));
693 }
694 pieces
695}
696
697fn runs(
699 bands: BTreeMap<(isize, isize), BezPath>,
700) -> impl Iterator<Item = ((isize, isize), BezPath)> {
701 let mut out: Vec<((isize, isize), BezPath)> = Vec::new();
702 for ((from, to), path) in bands {
703 match out.last_mut() {
704 Some(((_, end), run)) if *end == from => {
705 *end = to;
706 run.extend(path);
707 }
708 _ => out.push(((from, to), path)),
709 }
710 }
711 out.into_iter()
712}
713
714fn merged(mut cells: Vec<(isize, isize, isize, isize)>) -> Vec<(isize, isize, isize, isize)> {
717 cells.sort_unstable_by_key(|&(i0, j0, _, j1)| (j0, j1, i0));
718 let mut rows: Vec<(isize, isize, isize, isize)> = Vec::new();
719 for c in cells {
720 match rows.last_mut() {
721 Some(r) if (r.1, r.3, r.2) == (c.1, c.3, c.0) => r.2 = c.2,
722 _ => rows.push(c),
723 }
724 }
725 rows.sort_unstable_by_key(|&(i0, j0, i1, _)| (i0, i1, j0));
726 let mut out: Vec<(isize, isize, isize, isize)> = Vec::new();
727 for c in rows {
728 match out.last_mut() {
729 Some(r) if (r.0, r.2, r.3) == (c.0, c.2, c.1) => r.3 = c.3,
730 _ => out.push(c),
731 }
732 }
733 out
734}
735
736fn tiles(outline: &Outline, grid: Grid, i0: isize, j0: isize, i1: isize, j1: isize) -> Vec<Piece> {
741 let tile = TILE as isize;
742 if (i1 - i0) <= tile && (j1 - j0) <= tile {
743 return vec![Piece::Patch(None, i0, j0, i1, j1)];
744 }
745 let reach = REACH as isize + 1;
747 let mut solid = BezPath::new();
748 let mut pieces = Vec::new();
749 let mut j = j0;
750 while j < j1 {
751 let je = (j + tile).min(j1);
752 let mut run: Option<isize> = None;
753 let mut i = i0;
754 while i < i1 {
755 let ie = (i + tile).min(i1);
756 let reached = grid.rect(i - reach, j - reach, ie + reach, je + reach);
757 let full = outline.holds(reached);
758 if full {
759 run.get_or_insert(i);
760 } else {
761 if let Some(start) = run.take() {
762 solid.extend(grid.rect(start, j, i, je).path_elements(0.1));
763 }
764 if !outline.misses(reached) {
765 pieces.push(Piece::Patch(None, i, j, ie, je));
766 }
767 }
768 i = ie;
769 }
770 if let Some(start) = run {
771 solid.extend(grid.rect(start, j, i1, je).path_elements(0.1));
772 }
773 j = je;
774 }
775 if !solid.elements().is_empty() {
776 pieces.insert(0, Piece::Solid(solid));
777 }
778 pieces
779}
780
781fn ellipse_distance(a: f64, b: f64, x: f64, y: f64) -> f64 {
785 let (px, py) = (x.abs(), y.abs());
787 let mut t = (a * py).atan2(b * px);
788 for _ in 0..40 {
789 let (sin, cos) = t.sin_cos();
790 let (ex, ey) = (a * cos, b * sin);
791 let f = (px - ex) * -a * sin + (py - ey) * b * cos;
793 let df =
794 (a * a * sin * sin + b * b * cos * cos) - (px - ex) * a * cos - (py - ey) * b * sin;
795 if df.abs() < 1e-12 {
796 break;
797 }
798 let next = (t - f / df).clamp(0.0, std::f64::consts::FRAC_PI_2);
799 if (next - t).abs() < 1e-12 {
800 t = next;
801 break;
802 }
803 t = next;
804 }
805 let (sin, cos) = t.sin_cos();
806 let d = (px - a * cos).hypot(py - b * sin);
807 if (px / a).hypot(py / b) <= 1.0 { -d } else { d }
808}
809
810fn ellipse(e: Rect, std_dev: f64, grid: Grid, nx: isize, ny: isize) -> Vec<Piece> {
817 let outline = Outline::Ellipse(e);
818 let step = grid.step;
819 let (a, b) = (e.width() / 2.0, e.height() / 2.0);
820 let c = e.center();
821 let inside = (REACH as f64 * step + step) * std::f64::consts::SQRT_2;
824 let outside = inside + step;
825 if a.min(b) <= 4.0 * inside {
826 return tiles(&outline, grid, 0, 0, nx, ny);
827 }
828 let at = |t: f64| Point::new(c.x + a * t.cos(), c.y + b * t.sin());
829 let normal = |t: f64| {
830 let n = kurbo::Vec2::new(b * t.cos(), a * t.sin());
831 n / n.hypot()
832 };
833 let rho = |t: f64| {
834 let (sin, cos) = t.sin_cos();
835 (a * a * sin * sin + b * b * cos * cos).powf(1.5) / (a * b)
836 };
837 let distance = |p: Point| ellipse_distance(a, b, p.x - c.x, p.y - c.y);
838 let cut = |t: f64| -> Option<(Point, Point)> {
841 let p = at(t);
842 let ray = p - c;
843 let u = ray / ray.hypot();
844 let cos = u.dot(normal(t)).max(1e-6);
845 let mut s = inside / cos;
846 while distance(p - u * s) > -inside {
847 s += step;
848 if s >= ray.hypot() {
849 return None;
850 }
851 }
852 Some((p - u * s, p + u * (outside / cos)))
853 };
854 let tolerance = std_dev / 100.0;
855 let tile = TILE as f64 * step;
856 let fine = |t0: f64, t1: f64| -> bool {
858 let tm = (t0 + t1) / 2.0;
859 let (p0, p1) = (at(t0), at(t1));
860 let chord = (p1 - p0).hypot();
861 let tightest = rho(t0).min(rho(t1)).min(rho(tm));
862 if chord * chord / (8.0 * (tightest + outside)) > step {
864 return false;
865 }
866 if tightest >= ROUND * std_dev {
867 let center = at(tm) - normal(tm) * rho(tm);
868 let r = rho(tm);
869 [t0, (t0 + tm) / 2.0, (tm + t1) / 2.0, t1]
870 .iter()
871 .all(|&t| ((at(t) - center).hypot() - r).abs() <= tolerance)
872 } else {
873 chord <= tile
874 }
875 };
876 let mut ts = Vec::new();
879 let quarter = std::f64::consts::FRAC_PI_2;
880 for q in 0..4 {
881 let mut todo = vec![(q as f64 * quarter, (q + 1) as f64 * quarter, 0)];
882 let mut done = Vec::new();
883 while let Some((t0, t1, depth)) = todo.pop() {
884 if depth >= 24 || fine(t0, t1) {
885 done.push(t0);
886 } else {
887 let tm = (t0 + t1) / 2.0;
888 todo.push((tm, t1, depth + 1));
889 todo.push((t0, tm, depth + 1));
890 }
891 }
892 done.sort_by(f64::total_cmp);
893 ts.extend(done);
894 }
895 let mut cuts = Vec::with_capacity(ts.len());
896 for &t in &ts {
897 match cut(t) {
898 Some(c) => cuts.push(c),
899 None => return tiles(&outline, grid, 0, 0, nx, ny),
900 }
901 }
902 let mut pieces = Vec::new();
903 let mut solid = BezPath::new();
904 for (n, (inner, _)) in cuts.iter().enumerate() {
905 if n == 0 {
906 solid.move_to(*inner);
907 } else {
908 solid.line_to(*inner);
909 }
910 }
911 solid.close_path();
912 pieces.push(Piece::Solid(solid));
913 let tau = std::f64::consts::TAU;
914 for n in 0..ts.len() {
915 let (t0, t1) = (ts[n], if n + 1 < ts.len() { ts[n + 1] } else { tau });
916 let ((i0, o0), (i1, o1)) = (cuts[n], cuts[(n + 1) % cuts.len()]);
917 let mut wedge = BezPath::new();
918 wedge.move_to(i0);
919 wedge.line_to(i1);
920 wedge.line_to(o1);
921 wedge.line_to(o0);
922 wedge.close_path();
923 let tm = (t0 + t1) / 2.0;
924 if rho(t0).min(rho(t1)).min(rho(tm)) >= ROUND * std_dev {
925 pieces.push(Piece::Disc(wedge, at(tm) - normal(tm) * rho(tm), rho(tm)));
926 } else {
927 let bounds = wedge.bounding_box();
928 let i = |x: f64| ((x - grid.origin.x) / step).floor() as isize;
929 let j = |y: f64| ((y - grid.origin.y) / step).floor() as isize;
930 pieces.push(Piece::Patch(
931 Some(wedge),
932 i(bounds.x0),
933 j(bounds.y0),
934 i(bounds.x1) + 1,
935 j(bounds.y1) + 1,
936 ));
937 }
938 }
939 pieces
940}
941
942#[cfg(test)]
943mod tests {
944 use super::*;
945 use crate::compare::compare;
946 use crate::cpu::paint_area;
947 use crate::display::List;
948 use crate::render::Frame;
949
950 fn bound(std_dev: f64, scale: f32) -> u8 {
959 match std_dev * scale as f64 {
960 s if s >= 1.0 => 4,
961 s if s >= 0.5 => 6,
962 s if s >= 0.25 => 11,
963 _ => 18,
964 }
965 }
966
967 fn pieces_match_one_image(outline: Outline, std_dev: f64, case: &str, scales: &[f32]) {
971 let color = Rgba {
972 r: 20,
973 g: 30,
974 b: 40,
975 a: 200,
976 };
977 let paint = |single: usize, scale: f32| {
978 let area = Frame {
979 x: 0.0,
980 y: 0.0,
981 width: 360.0,
982 height: 220.0,
983 };
984 let list = List::of(area, |b| {
985 shadow_with(outline, std_dev, color, b, &mut Vec::new(), single)
986 });
987 let p = paint_area(&list, Rect::new(0.0, 0.0, 360.0, 220.0), scale as f64);
988 let chunk = list.chunk(&Vec::new()).unwrap();
990 let shape = (chunk.commands.len(), chunk.made.effects.len());
991 (p.data_as_u8_slice().to_vec(), p.width() as usize, shape)
992 };
993 for &scale in scales {
994 let (one, width, single_list) = paint(usize::MAX, scale);
995 let (pieces, _, pieced_list) = paint(0, scale);
996 assert_eq!(single_list, (1, 0), "{case}");
997 assert!(pieced_list.1 > 1, "{case} isn't in pieces");
998 let d = compare(&one, &pieces, width).unwrap();
999 assert!(
1000 d.within_tolerance() && d.max <= bound(std_dev, scale),
1001 "{case}, σ {std_dev}, at {scale}: {d:?}"
1002 );
1003 }
1004 }
1005
1006 #[test]
1014 fn a_shadow_in_pieces_is_the_shadow_in_one() {
1015 let r = |x0, y0, x1, y1| Rect::new(x0, y0, x1, y1);
1016 let cases = [
1017 (Outline::rounded(r(30.3, 20.7, 250.1, 170.9), [0.0; 4]), 4.0),
1018 (
1019 Outline::rounded(r(30.3, 20.7, 250.1, 170.9), [3.0, 80.0, 12.0, 45.0]),
1020 4.0,
1021 ),
1022 (
1023 Outline::rounded(r(30.0, 40.0, 330.0, 120.0), [40.0; 4]),
1024 2.0,
1025 ),
1026 (Outline::rounded(r(30.0, 40.0, 330.0, 43.0), [1.0; 4]), 6.0),
1027 (Outline::ellipse(r(25.5, 30.25, 300.5, 140.75)), 3.0),
1028 (Outline::ellipse(r(25.0, 30.0, 185.0, 190.0)), 2.0),
1029 (Outline::ellipse(r(15.3, 30.6, 345.2, 190.1)), 2.0),
1030 (Outline::ellipse(r(20.0, 60.0, 340.0, 150.0)), 1.0),
1031 (
1032 Outline::rounded(
1033 r(50.0986, 43.2465, 329.1681, 44.7226),
1034 [0.738, 0.738, 0.738, 0.0],
1035 ),
1036 0.869,
1037 ),
1038 (
1039 Outline::rounded(r(63.287, 18.532, 289.892, 187.314), [80.0; 4]),
1040 0.584,
1041 ),
1042 ];
1043 for (n, (outline, std_dev)) in cases.into_iter().enumerate() {
1044 pieces_match_one_image(outline, std_dev, &format!("case {n}"), &[1.0, 0.73, 1.37]);
1045 }
1046 let mut seed = 0x2545_f491_4f6c_dd1du64;
1047 for n in 0..12 {
1048 let (outline, std_dev) = random(&mut seed);
1049 pieces_match_one_image(outline, std_dev, &format!("random {n}"), &[0.5, 1.0]);
1050 }
1051 }
1052
1053 fn random(seed: &mut u64) -> (Outline, f64) {
1058 let mut next = || {
1059 *seed = seed
1060 .wrapping_mul(6364136223846793005)
1061 .wrapping_add(1442695040888963407);
1062 (*seed >> 11) as f64 / (1u64 << 53) as f64
1063 };
1064 let (aw, ah) = (360.0, 220.0);
1065 let sd = 0.5 + next() * 5.5;
1066 let reach = 3.0 * sd;
1067 let most = SINGLE_SIDE as f64 * sd / 2.0 - 2.0 * reach - sd - 1.0;
1069 let w = 1.0 + next() * (aw - 2.0 * reach - 20.0).min(most);
1070 let h = 1.0 + next() * (ah - 2.0 * reach - 20.0).min(most);
1071 let x0 = reach + 5.0 + next() * (aw - 2.0 * reach - 10.0 - w);
1072 let y0 = reach + 5.0 + next() * (ah - 2.0 * reach - 10.0 - h);
1073 let rect = Rect::new(x0, y0, x0 + w, y0 + h);
1074 let half = w.min(h) / 2.0;
1075 let kind = (next() * 6.0) as usize;
1076 if kind == 5 {
1077 return (Outline::ellipse(rect), sd);
1078 }
1079 let mut corners = [(0, 0.0); 4];
1080 for c in &mut corners {
1081 *c = ((next() * 5.0) as usize, next());
1082 }
1083 let alike = next() < 0.5;
1084 let radius = |(kind, u): (usize, f64)| match kind {
1085 0 => 0.0,
1086 1 => u * ROUND * sd,
1087 2 => ROUND * sd * (0.98 + 0.04 * u),
1088 3 => half,
1089 4 => half - u * 3.0 * sd,
1090 _ => u * half,
1091 };
1092 let radii = if alike {
1093 [radius(corners[0]); 4]
1094 } else {
1095 corners.map(radius)
1096 };
1097 (Outline::rounded(rect, radii), sd)
1098 }
1099}