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