1 | // Copyright 2006 The Android Open Source Project |
2 | // Copyright 2020 Yevhenii Reizner |
3 | // |
4 | // Use of this source code is governed by a BSD-style license that can be |
5 | // found in the LICENSE file. |
6 | |
7 | use core::convert::TryFrom; |
8 | |
9 | use tiny_skia_path::SaturateCast; |
10 | |
11 | use crate::{FillRule, IntRect, LengthU32, Path, Rect}; |
12 | |
13 | use crate::blitter::Blitter; |
14 | use crate::edge::{Edge, LineEdge}; |
15 | use crate::edge_builder::{BasicEdgeBuilder, ShiftedIntRect}; |
16 | use crate::fixed_point::{fdot16, fdot6, FDot16}; |
17 | use crate::geom::{IntRectExt, ScreenIntRect}; |
18 | |
19 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
20 | use tiny_skia_path::NoStdFloat; |
21 | |
22 | pub fn fill_path( |
23 | path: &Path, |
24 | fill_rule: FillRule, |
25 | clip: &ScreenIntRect, |
26 | blitter: &mut dyn Blitter, |
27 | ) { |
28 | let ir = match conservative_round_to_int(&path.bounds()) { |
29 | Some(v) => v, |
30 | None => return, |
31 | }; |
32 | |
33 | let path_contained_in_clip = if let Some(bounds) = ir.to_screen_int_rect() { |
34 | clip.contains(&bounds) |
35 | } else { |
36 | // If bounds cannot be converted into ScreenIntRect, |
37 | // the path is out of clip. |
38 | false |
39 | }; |
40 | |
41 | // TODO: SkScanClipper |
42 | |
43 | fill_path_impl( |
44 | path, |
45 | fill_rule, |
46 | clip, |
47 | ir.y(), |
48 | ir.bottom(), |
49 | 0, |
50 | path_contained_in_clip, |
51 | blitter, |
52 | ); |
53 | } |
54 | |
55 | // Conservative rounding function, which effectively nudges the int-rect to be slightly larger |
56 | // than Rect::round() might have produced. This is a safety-net for the scan-converter, which |
57 | // inspects the returned int-rect, and may disable clipping (for speed) if it thinks all of the |
58 | // edges will fit inside the clip's bounds. The scan-converter introduces slight numeric errors |
59 | // due to accumulated += of the slope, so this function is used to return a conservatively large |
60 | // int-bounds, and thus we will only disable clipping if we're sure the edges will stay in-bounds. |
61 | fn conservative_round_to_int(src: &Rect) -> Option<IntRect> { |
62 | // We must use `from_ltrb`, otherwise rounding will be incorrect. |
63 | IntRect::from_ltrb( |
64 | left:round_down_to_int(src.left()), |
65 | top:round_down_to_int(src.top()), |
66 | right:round_up_to_int(src.right()), |
67 | bottom:round_up_to_int(src.bottom()), |
68 | ) |
69 | } |
70 | |
71 | // Bias used for conservative rounding of float rects to int rects, to nudge the irects a little |
72 | // larger, so we don't "think" a path's bounds are inside a clip, when (due to numeric drift in |
73 | // the scan-converter) we might walk beyond the predicted limits. |
74 | // |
75 | // This value has been determined trial and error: pick the smallest value (after the 0.5) that |
76 | // fixes any problematic cases (e.g. crbug.com/844457) |
77 | // NOTE: cubics appear to be the main reason for needing this slop. If we could (perhaps) have a |
78 | // more accurate walker for cubics, we may be able to reduce this fudge factor. |
79 | const CONSERVATIVE_ROUND_BIAS: f64 = 0.5 + 1.5 / fdot6::ONE as f64; |
80 | |
81 | // Round the value down. This is used to round the top and left of a rectangle, |
82 | // and corresponds to the way the scan converter treats the top and left edges. |
83 | // It has a slight bias to make the "rounded" int smaller than a normal round, to create a more |
84 | // conservative int-bounds (larger) from a float rect. |
85 | fn round_down_to_int(x: f32) -> i32 { |
86 | let mut xx: f64 = x as f64; |
87 | xx -= CONSERVATIVE_ROUND_BIAS; |
88 | i32::saturate_from(xx.ceil()) |
89 | } |
90 | |
91 | // Round the value up. This is used to round the right and bottom of a rectangle. |
92 | // It has a slight bias to make the "rounded" int smaller than a normal round, to create a more |
93 | // conservative int-bounds (larger) from a float rect. |
94 | fn round_up_to_int(x: f32) -> i32 { |
95 | let mut xx: f64 = x as f64; |
96 | xx += CONSERVATIVE_ROUND_BIAS; |
97 | i32::saturate_from(xx.floor()) |
98 | } |
99 | |
100 | pub fn fill_path_impl( |
101 | path: &Path, |
102 | fill_rule: FillRule, |
103 | clip_rect: &ScreenIntRect, |
104 | mut start_y: i32, |
105 | mut stop_y: i32, |
106 | shift_edges_up: i32, |
107 | path_contained_in_clip: bool, |
108 | blitter: &mut dyn Blitter, |
109 | ) { |
110 | let shifted_clip = match ShiftedIntRect::new(clip_rect, shift_edges_up) { |
111 | Some(v) => v, |
112 | None => return, |
113 | }; |
114 | |
115 | let clip = if path_contained_in_clip { |
116 | None |
117 | } else { |
118 | Some(&shifted_clip) |
119 | }; |
120 | let mut edges = match BasicEdgeBuilder::build_edges(path, clip, shift_edges_up) { |
121 | Some(v) => v, |
122 | None => return, // no edges to render, just return |
123 | }; |
124 | |
125 | edges.sort_by(|a, b| { |
126 | let mut value_a = a.as_line().first_y; |
127 | let mut value_b = b.as_line().first_y; |
128 | |
129 | if value_a == value_b { |
130 | value_a = a.as_line().x; |
131 | value_b = b.as_line().x; |
132 | } |
133 | |
134 | value_a.cmp(&value_b) |
135 | }); |
136 | |
137 | for i in 0..edges.len() { |
138 | // 0 will be set later, so start with 1. |
139 | edges[i].prev = Some(i as u32 + 0); |
140 | edges[i].next = Some(i as u32 + 2); |
141 | } |
142 | |
143 | const EDGE_HEAD_Y: i32 = i32::MIN; |
144 | const EDGE_TAIL_Y: i32 = i32::MAX; |
145 | |
146 | edges.insert( |
147 | 0, |
148 | Edge::Line(LineEdge { |
149 | prev: None, |
150 | next: Some(1), |
151 | x: i32::MIN, |
152 | first_y: EDGE_HEAD_Y, |
153 | ..LineEdge::default() |
154 | }), |
155 | ); |
156 | |
157 | edges.push(Edge::Line(LineEdge { |
158 | prev: Some(edges.len() as u32 - 1), |
159 | next: None, |
160 | first_y: EDGE_TAIL_Y, |
161 | ..LineEdge::default() |
162 | })); |
163 | |
164 | start_y <<= shift_edges_up; |
165 | stop_y <<= shift_edges_up; |
166 | |
167 | let top = shifted_clip.shifted().y() as i32; |
168 | if !path_contained_in_clip && start_y < top { |
169 | start_y = top; |
170 | } |
171 | |
172 | let bottom = shifted_clip.shifted().bottom() as i32; |
173 | if !path_contained_in_clip && stop_y > bottom { |
174 | stop_y = bottom; |
175 | } |
176 | |
177 | let start_y = match u32::try_from(start_y) { |
178 | Ok(v) => v, |
179 | Err(_) => return, |
180 | }; |
181 | let stop_y = match u32::try_from(stop_y) { |
182 | Ok(v) => v, |
183 | Err(_) => return, |
184 | }; |
185 | |
186 | // TODO: walk_simple_edges |
187 | |
188 | walk_edges( |
189 | fill_rule, |
190 | start_y, |
191 | stop_y, |
192 | shifted_clip.shifted().right(), |
193 | &mut edges, |
194 | blitter, |
195 | ); |
196 | } |
197 | |
198 | // TODO: simplify! |
199 | fn walk_edges( |
200 | fill_rule: FillRule, |
201 | start_y: u32, |
202 | stop_y: u32, |
203 | right_clip: u32, |
204 | edges: &mut [Edge], |
205 | blitter: &mut dyn Blitter, |
206 | ) { |
207 | let mut curr_y = start_y; |
208 | let winding_mask = if fill_rule == FillRule::EvenOdd { |
209 | 1 |
210 | } else { |
211 | -1 |
212 | }; |
213 | |
214 | loop { |
215 | let mut w = 0i32; |
216 | let mut left = 0u32; |
217 | let mut prev_x = edges[0].x; |
218 | |
219 | let mut curr_idx = edges[0].next.unwrap() as usize; |
220 | while edges[curr_idx].first_y <= curr_y as i32 { |
221 | debug_assert!(edges[curr_idx].last_y >= curr_y as i32); |
222 | |
223 | let x = fdot16::round_to_i32(edges[curr_idx].x) as u32; // TODO: check |
224 | |
225 | if (w & winding_mask) == 0 { |
226 | // we're starting interval |
227 | left = x; |
228 | } |
229 | |
230 | w += i32::from(edges[curr_idx].winding); |
231 | |
232 | if (w & winding_mask) == 0 { |
233 | // we finished an interval |
234 | if let Some(width) = LengthU32::new(x - left) { |
235 | blitter.blit_h(left, curr_y, width); |
236 | } |
237 | } |
238 | |
239 | let next_idx = edges[curr_idx].next.unwrap(); |
240 | let new_x; |
241 | |
242 | if edges[curr_idx].last_y == curr_y as i32 { |
243 | // are we done with this edge? |
244 | match &mut edges[curr_idx] { |
245 | Edge::Line(_) => { |
246 | remove_edge(curr_idx, edges); |
247 | } |
248 | Edge::Quadratic(ref mut quad) => { |
249 | if quad.curve_count > 0 && quad.update() { |
250 | new_x = quad.line.x; |
251 | |
252 | if new_x < prev_x { |
253 | // ripple current edge backwards until it is x-sorted |
254 | backward_insert_edge_based_on_x(curr_idx, edges); |
255 | } else { |
256 | prev_x = new_x; |
257 | } |
258 | } else { |
259 | remove_edge(curr_idx, edges); |
260 | } |
261 | } |
262 | Edge::Cubic(ref mut cubic) => { |
263 | if cubic.curve_count < 0 && cubic.update() { |
264 | debug_assert!(cubic.line.first_y == curr_y as i32 + 1); |
265 | |
266 | new_x = cubic.line.x; |
267 | |
268 | if new_x < prev_x { |
269 | // ripple current edge backwards until it is x-sorted |
270 | backward_insert_edge_based_on_x(curr_idx, edges); |
271 | } else { |
272 | prev_x = new_x; |
273 | } |
274 | } else { |
275 | remove_edge(curr_idx, edges); |
276 | } |
277 | } |
278 | } |
279 | } else { |
280 | debug_assert!(edges[curr_idx].last_y > curr_y as i32); |
281 | new_x = edges[curr_idx].x + edges[curr_idx].dx; |
282 | edges[curr_idx].x = new_x; |
283 | |
284 | if new_x < prev_x { |
285 | // ripple current edge backwards until it is x-sorted |
286 | backward_insert_edge_based_on_x(curr_idx, edges); |
287 | } else { |
288 | prev_x = new_x; |
289 | } |
290 | } |
291 | |
292 | curr_idx = next_idx as usize; |
293 | } |
294 | |
295 | if (w & winding_mask) != 0 { |
296 | // was our right-edge culled away? |
297 | if let Some(width) = LengthU32::new(right_clip - left) { |
298 | blitter.blit_h(left, curr_y, width); |
299 | } |
300 | } |
301 | |
302 | curr_y += 1; |
303 | if curr_y >= stop_y { |
304 | break; |
305 | } |
306 | |
307 | // now current edge points to the first edge with a Yint larger than curr_y |
308 | insert_new_edges(curr_idx, curr_y as i32, edges); |
309 | } |
310 | } |
311 | |
312 | fn remove_edge(curr_idx: usize, edges: &mut [Edge]) { |
313 | let prev: u32 = edges[curr_idx].prev.unwrap(); |
314 | let next: u32 = edges[curr_idx].next.unwrap(); |
315 | |
316 | edges[prev as usize].next = Some(next); |
317 | edges[next as usize].prev = Some(prev); |
318 | } |
319 | |
320 | fn backward_insert_edge_based_on_x(curr_idx: usize, edges: &mut [Edge]) { |
321 | let x: i32 = edges[curr_idx].x; |
322 | let mut prev_idx: usize = edges[curr_idx].prev.unwrap() as usize; |
323 | while prev_idx != 0 { |
324 | if edges[prev_idx].x > x { |
325 | prev_idx = edges[prev_idx].prev.unwrap() as usize; |
326 | } else { |
327 | break; |
328 | } |
329 | } |
330 | |
331 | let next_idx: usize = edges[prev_idx].next.unwrap() as usize; |
332 | if next_idx != curr_idx { |
333 | remove_edge(curr_idx, edges); |
334 | insert_edge_after(curr_idx, after_idx:prev_idx, edges); |
335 | } |
336 | } |
337 | |
338 | fn insert_edge_after(curr_idx: usize, after_idx: usize, edges: &mut [Edge]) { |
339 | edges[curr_idx].prev = Some(after_idx as u32); |
340 | edges[curr_idx].next = edges[after_idx].next; |
341 | |
342 | let after_next_idx: usize = edges[after_idx].next.unwrap() as usize; |
343 | edges[after_next_idx].prev = Some(curr_idx as u32); |
344 | edges[after_idx].next = Some(curr_idx as u32); |
345 | } |
346 | |
347 | // Start from the right side, searching backwards for the point to begin the new edge list |
348 | // insertion, marching forwards from here. The implementation could have started from the left |
349 | // of the prior insertion, and search to the right, or with some additional caching, binary |
350 | // search the starting point. More work could be done to determine optimal new edge insertion. |
351 | fn backward_insert_start(mut prev_idx: usize, x: FDot16, edges: &mut [Edge]) -> usize { |
352 | while let Some(prev: u32) = edges[prev_idx].prev { |
353 | prev_idx = prev as usize; |
354 | if edges[prev_idx].x <= x { |
355 | break; |
356 | } |
357 | } |
358 | |
359 | prev_idx |
360 | } |
361 | |
362 | fn insert_new_edges(mut new_idx: usize, curr_y: i32, edges: &mut [Edge]) { |
363 | if edges[new_idx].first_y != curr_y { |
364 | return; |
365 | } |
366 | |
367 | let prev_idx = edges[new_idx].prev.unwrap() as usize; |
368 | if edges[prev_idx].x <= edges[new_idx].x { |
369 | return; |
370 | } |
371 | |
372 | // find first x pos to insert |
373 | let mut start_idx = backward_insert_start(prev_idx, edges[new_idx].x, edges); |
374 | // insert the lot, fixing up the links as we go |
375 | loop { |
376 | let next_idx = edges[new_idx].next.unwrap() as usize; |
377 | let mut keep_edge = false; |
378 | loop { |
379 | let after_idx = edges[start_idx].next.unwrap() as usize; |
380 | if after_idx == new_idx { |
381 | keep_edge = true; |
382 | break; |
383 | } |
384 | |
385 | if edges[after_idx].x >= edges[new_idx].x { |
386 | break; |
387 | } |
388 | |
389 | start_idx = after_idx; |
390 | } |
391 | |
392 | if !keep_edge { |
393 | remove_edge(new_idx, edges); |
394 | insert_edge_after(new_idx, start_idx, edges); |
395 | } |
396 | |
397 | start_idx = new_idx; |
398 | new_idx = next_idx; |
399 | |
400 | if edges[new_idx].first_y != curr_y { |
401 | break; |
402 | } |
403 | } |
404 | } |
405 | |