1mod accumulate;
19mod cache;
20pub mod dense;
21mod flatten;
22
23pub use cache::{CoverageCache, coverage_key};
24
25use crate::IRect;
26use accumulate::{accumulate_line, clip_line};
27use alpha2_model::FillRule;
28use kurbo::{BezPath, Point};
29
30pub const TILE: usize = 16;
32
33pub const TILE_BYTES: usize = TILE * TILE;
35
36#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
38pub enum TileState {
39 Empty,
41 Full,
43 Partial(u32),
46}
47
48#[derive(Clone, Debug, Default, Eq, PartialEq)]
50pub struct TileCoverage {
51 pub origin: (i32, i32),
53 pub size: (u32, u32),
55 pub tiles: Vec<TileState>,
57 pub partial: Vec<[u8; TILE_BYTES]>,
59}
60
61impl TileCoverage {
62 #[must_use]
64 pub fn at(&self, x: i32, y: i32) -> u8 {
65 let tile = i32::try_from(TILE).unwrap_or(16);
66 let (tx, ty) = (
67 x.div_euclid(tile) - self.origin.0,
68 y.div_euclid(tile) - self.origin.1,
69 );
70 let (Ok(tx), Ok(ty)) = (u32::try_from(tx), u32::try_from(ty)) else {
71 return 0;
72 };
73 if tx >= self.size.0 || ty >= self.size.1 {
74 return 0;
75 }
76 match self.tiles[(ty * self.size.0 + tx) as usize] {
77 TileState::Empty => 0,
78 TileState::Full => 255,
79 TileState::Partial(index) => {
80 let (cx, cy) = (x.rem_euclid(tile), y.rem_euclid(tile));
81 self.partial[index as usize][usize::try_from(cy * tile + cx).unwrap_or(0)]
82 }
83 }
84 }
85
86 #[must_use]
88 pub fn rect(&self) -> IRect {
89 let tile = i32::try_from(TILE).unwrap_or(16);
90 let width = i32::try_from(self.size.0).unwrap_or(0);
91 let height = i32::try_from(self.size.1).unwrap_or(0);
92 IRect::new(
93 self.origin.0 * tile,
94 self.origin.1 * tile,
95 (self.origin.0 + width) * tile,
96 (self.origin.1 + height) * tile,
97 )
98 }
99
100 #[must_use]
102 pub fn heap_bytes(&self) -> usize {
103 self.tiles.len() * size_of::<TileState>() + self.partial.len() * TILE_BYTES
104 }
105
106 #[must_use]
108 pub fn counts(&self) -> (usize, usize, usize) {
109 let mut counts = (0, 0, 0);
110 for tile in &self.tiles {
111 match tile {
112 TileState::Empty => counts.0 += 1,
113 TileState::Full => counts.1 += 1,
114 TileState::Partial(_) => counts.2 += 1,
115 }
116 }
117 counts
118 }
119}
120
121#[must_use]
123pub fn tile_aligned(clip: IRect) -> IRect {
124 let tile = i32::try_from(TILE).unwrap_or(16);
125 if clip.is_empty() {
126 return IRect::default();
127 }
128 IRect::new(
129 clip.x0.div_euclid(tile) * tile,
130 clip.y0.div_euclid(tile) * tile,
131 (clip.x1 + tile - 1).div_euclid(tile) * tile,
132 (clip.y1 + tile - 1).div_euclid(tile) * tile,
133 )
134}
135
136#[allow(
140 clippy::cast_possible_truncation,
141 clippy::cast_sign_loss,
142 clippy::cast_precision_loss
143)]
144pub(crate) fn coverage_byte(rule: FillRule, winding: f32) -> u8 {
145 let coverage = match rule {
146 FillRule::Nonzero => winding.abs().min(1.0),
147 FillRule::Evenodd => {
148 let half = winding.abs() * 0.5;
149 let turn = half - (half as i32) as f32;
150 (1.0 - (2.0 * turn - 1.0).abs()).min(1.0)
151 }
152 };
153 (coverage * 255.0 + 0.5) as u8
154}
155
156#[derive(Clone, Copy)]
159enum Run {
160 Const { x0: usize, x1: usize, value: u8 },
161 Pixels { x0: usize, x1: usize, offset: usize },
162}
163
164impl Run {
165 const fn x1(self) -> usize {
166 match self {
167 Self::Const { x1, .. } | Self::Pixels { x1, .. } => x1,
168 }
169 }
170}
171
172#[derive(Debug, Default)]
174pub struct RasterScratch {
175 lines: Vec<[f64; 4]>,
176 bins: Vec<Vec<u32>>,
177 acc: Vec<f32>,
178 touched: Vec<Vec<(usize, usize)>>,
179 runs: Vec<Vec<RunSlot>>,
180 pixels: Vec<u8>,
181 cursor: Vec<usize>,
182}
183
184#[derive(Clone, Copy, Debug)]
187struct RunSlot(u8, usize, usize, usize);
188
189impl From<Run> for RunSlot {
190 fn from(run: Run) -> Self {
191 match run {
192 Run::Const { x0, x1, value } => Self(0, x0, x1, usize::from(value)),
193 Run::Pixels { x0, x1, offset } => Self(1, x0, x1, offset),
194 }
195 }
196}
197
198impl From<RunSlot> for Run {
199 #[allow(clippy::cast_possible_truncation)]
200 fn from(slot: RunSlot) -> Self {
201 if slot.0 == 0 {
202 Self::Const {
203 x0: slot.1,
204 x1: slot.2,
205 value: slot.3 as u8,
206 }
207 } else {
208 Self::Pixels {
209 x0: slot.1,
210 x1: slot.2,
211 offset: slot.3,
212 }
213 }
214 }
215}
216
217#[must_use]
223#[allow(clippy::too_many_lines)]
225pub fn rasterize(
226 path: &BezPath,
227 rule: FillRule,
228 clip: IRect,
229 scratch: &mut RasterScratch,
230) -> TileCoverage {
231 let clip = tile_aligned(clip);
232 let tile = i32::try_from(TILE).unwrap_or(16);
233 let (w, h) = (clip.width() as usize, clip.height() as usize);
234 let tiles_x = w / TILE;
235 let tiles_y = h / TILE;
236 let mut out = TileCoverage {
237 origin: (clip.x0.div_euclid(tile), clip.y0.div_euclid(tile)),
238 size: (
239 u32::try_from(tiles_x).unwrap_or(0),
240 u32::try_from(tiles_y).unwrap_or(0),
241 ),
242 tiles: Vec::with_capacity(tiles_x * tiles_y),
243 partial: Vec::new(),
244 };
245 if w == 0 || h == 0 {
246 return out;
247 }
248 flatten::flatten(
249 path,
250 Point::new(f64::from(clip.x0), f64::from(clip.y0)),
251 &mut scratch.lines,
252 );
253 #[allow(clippy::cast_precision_loss)]
254 let (wf, hf) = (w as f64, h as f64);
255 scratch.bins.resize_with(tiles_y, Vec::new);
257 for bin in &mut scratch.bins[..tiles_y] {
258 bin.clear();
259 }
260 for (index, line) in scratch.lines.iter().enumerate() {
261 let (lo, hi) = (line[1].min(line[3]).max(0.0), line[1].max(line[3]).min(hf));
262 if hi <= lo {
263 continue;
264 }
265 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
267 let (first, last) = (
268 (lo as usize) / TILE,
269 (hi.ceil() as usize).div_ceil(TILE).min(tiles_y),
270 );
271 let index = u32::try_from(index).unwrap_or(u32::MAX);
272 for bin in &mut scratch.bins[first..last] {
273 bin.push(index);
274 }
275 }
276 let stride = w + 2;
277 scratch.acc.clear();
278 scratch.acc.resize(stride * TILE, 0.0);
279 scratch.touched.resize_with(TILE, Vec::new);
280 scratch.runs.resize_with(TILE, Vec::new);
281 scratch.cursor.resize(TILE, 0);
282 #[allow(clippy::cast_precision_loss)]
283 let wf32 = w as f32;
284 for strip in 0..tiles_y {
285 let r0 = strip * TILE;
286 let r1 = r0 + TILE;
287 for list in &mut scratch.touched[..TILE] {
288 list.clear();
289 }
290 {
291 let RasterScratch {
292 lines,
293 bins,
294 acc,
295 touched,
296 ..
297 } = &mut *scratch;
298 for &index in &bins[strip] {
299 clip_line(lines[index as usize], wf, hf, |p0, p1| {
300 accumulate_line(acc, stride, r0, (r0, r1), wf32, p0, p1, |y, first, last| {
301 touched[y - r0].push((first, last));
302 });
303 });
304 }
305 }
306 scratch.pixels.clear();
308 for r in 0..TILE {
309 let row = r * stride;
310 let RasterScratch {
311 acc,
312 touched,
313 runs,
314 pixels,
315 ..
316 } = &mut *scratch;
317 let ranges = &mut touched[r];
318 ranges.sort_unstable();
319 let runs = &mut runs[r];
320 runs.clear();
321 let (mut winding, mut cursor) = (0.0f32, 0usize);
322 let mut index = 0;
323 while index < ranges.len() {
324 let (lo, mut hi) = ranges[index];
325 index += 1;
326 while index < ranges.len() && ranges[index].0 <= hi + 1 {
327 hi = hi.max(ranges[index].1);
328 index += 1;
329 }
330 if lo > cursor && cursor < w {
331 runs.push(
332 Run::Const {
333 x0: cursor,
334 x1: lo.min(w),
335 value: coverage_byte(rule, winding),
336 }
337 .into(),
338 );
339 }
340 let offset = pixels.len();
341 for x in lo..=hi {
342 winding += acc[row + x];
343 acc[row + x] = 0.0;
344 if x < w {
345 pixels.push(coverage_byte(rule, winding));
346 }
347 }
348 if lo < w {
349 runs.push(
350 Run::Pixels {
351 x0: lo,
352 x1: (hi + 1).min(w),
353 offset,
354 }
355 .into(),
356 );
357 }
358 cursor = hi + 1;
359 }
360 if cursor < w {
361 runs.push(
362 Run::Const {
363 x0: cursor,
364 x1: w,
365 value: coverage_byte(rule, winding),
366 }
367 .into(),
368 );
369 }
370 }
371 for cursor in &mut scratch.cursor[..TILE] {
373 *cursor = 0;
374 }
375 for tx in 0..tiles_x {
376 let (cx0, cx1) = (tx * TILE, tx * TILE + TILE);
377 let (mut all_full, mut all_empty) = (true, true);
378 for r in 0..TILE {
379 let runs = &scratch.runs[r];
380 let cursor = &mut scratch.cursor[r];
381 while *cursor < runs.len() && Run::from(runs[*cursor]).x1() <= cx0 {
382 *cursor += 1;
383 }
384 match runs.get(*cursor).map(|slot| Run::from(*slot)) {
385 Some(Run::Const { x0, x1, value }) if x0 <= cx0 && x1 >= cx1 => {
386 all_full &= value == 255;
387 all_empty &= value == 0;
388 }
389 _ => {
390 all_full = false;
391 all_empty = false;
392 }
393 }
394 }
395 let state = if all_full {
396 TileState::Full
397 } else if all_empty {
398 TileState::Empty
399 } else {
400 let mut bytes = [0u8; TILE_BYTES];
401 for r in 0..TILE {
402 let runs = &scratch.runs[r];
403 let mut at = scratch.cursor[r];
404 while at < runs.len() {
405 let run = Run::from(runs[at]);
406 let (x0, x1) = match run {
407 Run::Const { x0, x1, .. } | Run::Pixels { x0, x1, .. } => (x0, x1),
408 };
409 if x0 >= cx1 {
410 break;
411 }
412 for x in x0.max(cx0)..x1.min(cx1) {
413 bytes[r * TILE + (x - cx0)] = match run {
414 Run::Const { value, .. } => value,
415 Run::Pixels { x0, offset, .. } => scratch.pixels[offset + (x - x0)],
416 };
417 }
418 at += 1;
419 }
420 }
421 if bytes.iter().all(|byte| *byte == 255) {
423 TileState::Full
424 } else if bytes.iter().all(|byte| *byte == 0) {
425 TileState::Empty
426 } else {
427 out.partial.push(bytes);
428 TileState::Partial(u32::try_from(out.partial.len() - 1).unwrap_or(u32::MAX))
429 }
430 };
431 out.tiles.push(state);
432 }
433 }
434 out
435}
436
437#[must_use]
440pub fn flattened(path: &BezPath, clip: IRect) -> Vec<[f64; 4]> {
441 let clip = tile_aligned(clip);
442 let mut lines = Vec::new();
443 flatten::flatten(
444 path,
445 Point::new(f64::from(clip.x0), f64::from(clip.y0)),
446 &mut lines,
447 );
448 lines
449}
450
451#[must_use]
454pub fn winding_at(path: &BezPath, point: Point) -> i32 {
455 let mut lines = Vec::new();
456 flatten::flatten(path, Point::ZERO, &mut lines);
457 let mut winding = 0;
458 for [x0, y0, x1, y1] in lines {
459 if (y0 <= point.y) != (y1 <= point.y) {
461 let x = x0 + (point.y - y0) * (x1 - x0) / (y1 - y0);
462 if x > point.x {
463 winding += if y1 > y0 { 1 } else { -1 };
464 }
465 }
466 }
467 winding
468}
469
470#[cfg(test)]
471mod tests;