1use sequoia_core::{Point, Rect};
2
3use crate::flatten::Contour;
4use crate::path::{FillRule, Path};
5use crate::{DEFAULT_OPTIONS, Span, union_rect};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct RasterOptions {
9 pub tolerance: f32,
10
11 pub row_height: f32,
12
13 pub merge_tol: f32,
14
15 pub aa_samples: u32,
16
17 pub analytic: bool,
18
19 pub parallel: bool,
22}
23
24impl Default for RasterOptions {
25 fn default() -> Self {
26 DEFAULT_OPTIONS
27 }
28}
29
30impl RasterOptions {
31 pub const SMOOTH: Self = Self {
32 tolerance: crate::TOLERANCE,
33 row_height: 1.0,
34 merge_tol: 0.35,
35 aa_samples: 4,
36 analytic: true,
37 parallel: true,
38 };
39
40 pub const CRISP: Self = Self {
41 tolerance: crate::TOLERANCE,
42 row_height: 1.0,
43 merge_tol: 0.35,
44 aa_samples: 1,
45 analytic: true,
46 parallel: true,
47 };
48
49 pub const BANDED: Self = Self {
50 tolerance: crate::TOLERANCE,
51 row_height: 1.0,
52 merge_tol: 0.35,
53 aa_samples: 4,
54 analytic: false,
55 parallel: true,
56 };
57
58 pub fn tolerance(mut self, tolerance: f32) -> Self {
59 self.tolerance = tolerance;
60 self
61 }
62
63 pub fn row_height(mut self, row_height: f32) -> Self {
64 self.row_height = row_height;
65 self
66 }
67
68 pub fn merge_tol(mut self, merge_tol: f32) -> Self {
69 self.merge_tol = merge_tol;
70 self
71 }
72
73 pub fn aa_samples(mut self, samples: u32) -> Self {
74 self.aa_samples = samples.clamp(1, 64);
75 self
76 }
77
78 pub fn analytic(mut self, analytic: bool) -> Self {
79 self.analytic = analytic;
80 self
81 }
82
83 pub fn parallel(mut self, parallel: bool) -> Self {
88 self.parallel = parallel;
89 self
90 }
91
92 pub fn for_scale(mut self, scale: f32) -> Self {
93 let s = if scale.is_finite() && scale > 0.0 {
94 scale
95 } else {
96 1.0
97 };
98 self.tolerance = (crate::TOLERANCE / s).max(0.05);
99 self.row_height = (1.0 / s).max(1.0 / 8.0);
100 self.merge_tol = (0.35 / s).max(0.02);
101 self
102 }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq)]
106pub struct FillStyle {
107 pub rule: FillRule,
108
109 pub opts: RasterOptions,
110}
111
112impl Default for FillStyle {
113 fn default() -> Self {
114 Self {
115 rule: FillRule::NonZero,
116 opts: RasterOptions::default(),
117 }
118 }
119}
120
121impl FillStyle {
122 pub fn new(rule: FillRule, opts: RasterOptions) -> Self {
123 Self { rule, opts }
124 }
125
126 pub fn with_rule(rule: FillRule) -> Self {
127 Self {
128 rule,
129 opts: RasterOptions::default(),
130 }
131 }
132
133 #[must_use]
134 pub fn with_opts(mut self, opts: RasterOptions) -> Self {
135 self.opts = opts;
136 self
137 }
138}
139
140#[derive(Clone, Copy)]
141struct Edge {
142 x0: f32,
143 y0: f32,
144 x1: f32,
145 y1: f32,
146
147 dir: i32,
148}
149
150#[derive(Clone, Copy, Debug)]
151struct Run {
152 x0: f32,
153 x1: f32,
154 y0: f32,
155 y1: f32,
156
157 alpha: f32,
158 claimed: bool,
159}
160
161pub fn fill_styled(path: &Path, style: FillStyle, out: &mut Vec<Span>) -> Rect {
162 fill(path, style.rule, style.opts, out)
163}
164
165pub fn fill(path: &Path, rule: FillRule, opts: RasterOptions, out: &mut Vec<Span>) -> Rect {
166 if opts.analytic {
167 if let Some(rect) = path.as_rect() {
168 if rect.is_empty() {
169 return Rect::ZERO;
170 }
171 out.push(Span::rect(rect));
172 return rect;
173 }
174 if let Some((rect, radius)) = path.as_rounded_rect() {
175 if rect.is_empty() {
176 return Rect::ZERO;
177 }
178 out.push(Span::rounded(rect, radius));
179 return rect;
180 }
181 if let Some((center, radius)) = path.as_circle() {
182 if radius <= 0.0 {
183 return Rect::ZERO;
184 }
185 let rect = Rect::from_xywh(
186 center.x - radius,
187 center.y - radius,
188 radius * 2.0,
189 radius * 2.0,
190 );
191 out.push(Span::rounded(rect, radius));
192 return rect;
193 }
194 }
195
196 let contours = path.contours(opts.tolerance);
197 fill_contours(&contours, rule, opts, out)
198}
199
200pub fn fill_contours(
201 contours: &[Contour],
202 rule: FillRule,
203 opts: RasterOptions,
204 out: &mut Vec<Span>,
205) -> Rect {
206 let edges = collect_edges(contours);
207 if edges.is_empty() {
208 return Rect::ZERO;
209 }
210
211 let cell = opts.row_height.clamp(0.05, 16.0);
212 let samples = opts.aa_samples.clamp(1, 64) as usize;
213 let merge_tol = opts.merge_tol.max(0.0);
214
215 let mut top = f32::INFINITY;
216 let mut bottom = f32::NEG_INFINITY;
217 let mut left = f32::INFINITY;
218 let mut right = f32::NEG_INFINITY;
219 for e in &edges {
220 top = top.min(e.y0.min(e.y1));
221 bottom = bottom.max(e.y0.max(e.y1));
222 left = left.min(e.x0.min(e.x1));
223 right = right.max(e.x0.max(e.x1));
224 }
225 if !top.is_finite() || !bottom.is_finite() || bottom <= top {
226 return Rect::ZERO;
227 }
228 if !left.is_finite() || !right.is_finite() || right <= left {
229 return Rect::ZERO;
230 }
231
232 let first_row = (top / cell).floor();
233 let last_row = (bottom / cell).ceil();
234 let first_col = (left / cell).floor();
235 let last_col = (right / cell).ceil();
236 let cols = (last_col - first_col).max(0.0) as usize;
237 if cols == 0 {
238 return Rect::ZERO;
239 }
240 let rows = ((last_row - first_row).max(0.0).ceil() as usize) + 1;
241
242 let bands = if opts.parallel {
246 let cores = std::thread::available_parallelism()
247 .map(|n| n.get())
248 .unwrap_or(1);
249 band_count(rows, cols, samples, cores)
250 } else {
251 1
252 };
253
254 if bands <= 1 {
255 return raster_rows(
256 &edges, rule, cell, samples, merge_tol, first_row, first_col, cols, 0, rows, out,
257 );
258 }
259
260 let band_rows = rows.div_ceil(bands);
261 let mut parts: Vec<(usize, Vec<Span>, Rect)> = Vec::with_capacity(bands);
262 std::thread::scope(|scope| {
263 let mut handles = Vec::with_capacity(bands);
264 for band in 0..bands {
265 let start = (band * band_rows).min(rows);
266 let end = (start + band_rows).min(rows);
267 if start >= end {
268 continue;
269 }
270 let edges = &edges;
271 handles.push((
272 start,
273 scope.spawn(move || {
274 let mut spans = Vec::new();
275 let coverage = raster_rows(
276 edges, rule, cell, samples, merge_tol, first_row, first_col, cols, start,
277 end, &mut spans,
278 );
279 (spans, coverage)
280 }),
281 ));
282 }
283 for (start, handle) in handles {
284 if let Ok((spans, coverage)) = handle.join() {
285 parts.push((start, spans, coverage));
286 }
287 }
288 });
289 parts.sort_by_key(|(start, _, _)| *start);
292
293 let mut coverage = Rect::ZERO;
294 for (_, spans, part_coverage) in parts {
295 out.extend(spans);
296 coverage = union_rect(coverage, part_coverage);
297 }
298 coverage
299}
300
301const PARALLEL_MIN_ROWS: usize = 64;
302
303const PARALLEL_MIN_WORK: usize = 256 * 1024 * 4;
310
311const MAX_BANDS: usize = 8;
312
313fn band_count(rows: usize, cols: usize, samples: usize, cores: usize) -> usize {
318 let work = rows.saturating_mul(cols).saturating_mul(samples.max(1));
319 if rows < PARALLEL_MIN_ROWS || work < PARALLEL_MIN_WORK {
320 return 1;
321 }
322 cores.min(MAX_BANDS).min(rows / PARALLEL_MIN_ROWS).max(1)
323}
324
325#[allow(clippy::too_many_arguments)]
326fn raster_rows(
327 edges: &[Edge],
328 rule: FillRule,
329 cell: f32,
330 samples: usize,
331 merge_tol: f32,
332 first_row: f32,
333 first_col: f32,
334 cols: usize,
335 row_start: usize,
336 row_end: usize,
337 out: &mut Vec<Span>,
338) -> Rect {
339 let mut coverage_per_cell = vec![0.0f32; cols];
340 let mut crossings: Vec<(f32, i32)> = Vec::new();
341 let mut spans: Vec<(f32, f32)> = Vec::new();
342 let mut row_runs: Vec<(f32, f32, f32)> = Vec::new();
343 let mut emitted: Vec<Run> = Vec::new();
344 let mut runs: Vec<Run> = Vec::new();
345 let mut coverage = Rect::ZERO;
346 let inv_samples = 1.0 / samples as f32;
347
348 for row_index in row_start..row_end {
349 let y0 = (first_row + row_index as f32) * cell;
350 let y1 = y0 + cell;
351 coverage_per_cell.fill(0.0);
352
353 for sample in 0..samples {
354 let y = y0 + (sample as f32 + 0.5) * inv_samples * cell;
355 crossings.clear();
356 for e in edges {
357 let (ymin, ymax) = (e.y0.min(e.y1), e.y0.max(e.y1));
358 if y < ymin || y >= ymax {
359 continue;
360 }
361 let t = (y - e.y0) / (e.y1 - e.y0);
362 crossings.push((e.x0 + (e.x1 - e.x0) * t, e.dir));
363 }
364 if crossings.len() < 2 {
365 continue;
366 }
367 crossings.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
368
369 spans.clear();
370 match rule {
371 FillRule::NonZero => {
372 let mut winding = 0;
373 let mut start = 0.0;
374 for &(x, dir) in &crossings {
375 let was = winding;
376 winding += dir;
377 if was == 0 && winding != 0 {
378 start = x;
379 } else if was != 0 && winding == 0 {
380 spans.push((start, x));
381 }
382 }
383 }
384 FillRule::EvenOdd => {
385 let mut i = 0;
386 while i + 1 < crossings.len() {
387 spans.push((crossings[i].0, crossings[i + 1].0));
388 i += 2;
389 }
390 }
391 }
392
393 for &(x0, x1) in &spans {
394 add_span_coverage(&mut coverage_per_cell, first_col, cell, x0, x1);
395 }
396 }
397
398 row_runs.clear();
399 let mut i = 0usize;
400 while i < cols {
401 let a = coverage_per_cell[i] * inv_samples;
402 if a <= 0.0 {
403 i += 1;
404 continue;
405 }
406 let alpha = quantize_alpha(a);
407 let start = i;
408 i += 1;
409 while i < cols {
410 let b = coverage_per_cell[i] * inv_samples;
411 if b <= 0.0 || quantize_alpha(b) != alpha {
412 break;
413 }
414 i += 1;
415 }
416 row_runs.push((
417 (first_col + start as f32) * cell,
418 (first_col + i as f32) * cell,
419 alpha,
420 ));
421 }
422
423 for run in runs.iter_mut() {
424 run.claimed = false;
425 }
426 for (x0, x1, alpha) in row_runs.drain(..) {
427 let mut extended = false;
428 for run in runs.iter_mut() {
429 if run.claimed || run.alpha != alpha {
430 continue;
431 }
432 if (run.x0 - x0).abs() <= merge_tol && (run.x1 - x1).abs() <= merge_tol {
433 run.x0 = run.x0.min(x0);
434 run.x1 = run.x1.max(x1);
435 run.y1 = y1;
436 run.claimed = true;
437 extended = true;
438 break;
439 }
440 }
441 if !extended {
442 runs.push(Run {
443 x0,
444 x1,
445 y0,
446 y1,
447 alpha,
448 claimed: true,
449 });
450 }
451 }
452
453 emitted.clear();
454 runs.retain_mut(|run| {
455 if run.claimed {
456 run.claimed = false;
457 true
458 } else {
459 emitted.push(*run);
460 false
461 }
462 });
463 for run in emitted.drain(..) {
464 coverage = union_rect(coverage, emit_run(run, out));
465 }
466 }
467 for run in runs.drain(..) {
468 coverage = union_rect(coverage, emit_run(run, out));
469 }
470 coverage
471}
472
473fn add_span_coverage(coverage: &mut [f32], first_col: f32, cell: f32, x0: f32, x1: f32) {
474 if x1 <= x0 {
475 return;
476 }
477 let start = ((x0 / cell).floor() - first_col).max(0.0) as usize;
478 let end = (((x1 / cell).ceil() - first_col).max(0.0) as usize).min(coverage.len());
479 for (i, cell_cov) in coverage[start..end].iter_mut().enumerate() {
480 let cell_left = (first_col + (start + i) as f32) * cell;
481 let lo = x0.max(cell_left);
482 let hi = x1.min(cell_left + cell);
483 if hi > lo {
484 *cell_cov += (hi - lo) / cell;
485 }
486 }
487}
488
489fn quantize_alpha(a: f32) -> f32 {
490 const LEVELS: f32 = 64.0;
493 if a >= 1.0 {
494 1.0
495 } else if a <= 0.0 {
496 0.0
497 } else {
498 ((a * LEVELS).round().max(1.0) / LEVELS).min(1.0)
499 }
500}
501
502fn emit_run(run: Run, out: &mut Vec<Span>) -> Rect {
503 let rect = Rect::from_xywh(
504 run.x0,
505 run.y0,
506 (run.x1 - run.x0).max(0.0),
507 (run.y1 - run.y0).max(0.0),
508 );
509 if rect.is_empty() {
510 return Rect::ZERO;
511 }
512 out.push(Span::rect(rect).with_alpha(run.alpha));
513 rect
514}
515
516fn collect_edges(contours: &[Contour]) -> Vec<Edge> {
517 let mut edges = Vec::new();
518 for contour in contours {
519 let pts = &contour.points;
520 if pts.len() < 2 {
521 continue;
522 }
523 let n = pts.len();
524 for i in 0..n {
525 let a = pts[i];
526 let b = pts[(i + 1) % n];
527 if a.y == b.y {
528 continue;
529 }
530 if a == b {
531 continue;
532 }
533 let dir = if b.y > a.y { 1 } else { -1 };
534 edges.push(Edge {
535 x0: a.x,
536 y0: a.y,
537 x1: b.x,
538 y1: b.y,
539 dir,
540 });
541 }
542 }
543 edges
544}
545
546pub(crate) fn contains(contours: &[Contour], p: Point, rule: FillRule) -> bool {
547 let edges = collect_edges(contours);
548 let mut winding = 0;
549 let mut crossings = 0;
550 for e in &edges {
551 let (ymin, ymax) = (e.y0.min(e.y1), e.y0.max(e.y1));
552 if p.y < ymin || p.y >= ymax {
553 continue;
554 }
555 let t = (p.y - e.y0) / (e.y1 - e.y0);
556 let x = e.x0 + (e.x1 - e.x0) * t;
557 if x > p.x {
558 winding += e.dir;
559 crossings += 1;
560 }
561 }
562 match rule {
563 FillRule::NonZero => winding != 0,
564 FillRule::EvenOdd => crossings % 2 == 1,
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::{MAX_BANDS, band_count, quantize_alpha};
571
572 #[test]
573 fn band_count_follows_work_not_cells() {
574 assert_eq!(band_count(1000, 1000, 4, 4), 4);
575 assert_eq!(band_count(1000, 1000, 1, 4), 1, "1 sample = 1/4 work");
576 assert_eq!(band_count(1000, 1000, 8, 4), 4);
577 }
578
579 #[test]
580 fn band_count_keeps_small_and_single_core_shapes_serial() {
581 assert_eq!(band_count(32, 4000, 4, 8), 1, "too few rows");
582 assert_eq!(band_count(1000, 1000, 4, 1), 1, "single core");
583 assert_eq!(band_count(1000, 1000, 4, 64), MAX_BANDS);
584 }
585
586 #[test]
587 fn alpha_quantization_keeps_64_levels() {
588 assert_eq!(quantize_alpha(0.0), 0.0);
589 assert_eq!(quantize_alpha(1.0), 1.0);
590 assert!((quantize_alpha(0.51) - 33.0 / 64.0).abs() < f32::EPSILON);
591 assert!(quantize_alpha(0.01) > 0.0, "thin coverage must survive");
592 }
593}