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::TryInto; |
8 | |
9 | use tiny_skia_path::{f32x2, PathVerb, SaturateCast, Scalar}; |
10 | |
11 | use crate::{IntRect, LineCap, Path, PathSegment, Point, Rect}; |
12 | |
13 | use crate::blitter::Blitter; |
14 | use crate::fixed_point::{fdot16, fdot6}; |
15 | use crate::geom::ScreenIntRect; |
16 | use crate::line_clipper; |
17 | use crate::math::LENGTH_U32_ONE; |
18 | use crate::path_geometry; |
19 | |
20 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
21 | use tiny_skia_path::NoStdFloat; |
22 | |
23 | const FLOAT_PI: f32 = 3.14159265; |
24 | |
25 | pub type LineProc = fn(&[Point], Option<&ScreenIntRect>, &mut dyn Blitter); |
26 | |
27 | const MAX_CUBIC_SUBDIVIDE_LEVEL: u8 = 9; |
28 | const MAX_QUAD_SUBDIVIDE_LEVEL: u8 = 5; |
29 | |
30 | pub fn stroke_path( |
31 | path: &Path, |
32 | line_cap: LineCap, |
33 | clip: &ScreenIntRect, |
34 | blitter: &mut dyn Blitter, |
35 | ) { |
36 | super::hairline::stroke_path_impl(path, line_cap, clip, line_proc:hair_line_rgn, blitter) |
37 | } |
38 | |
39 | fn hair_line_rgn(points: &[Point], clip: Option<&ScreenIntRect>, blitter: &mut dyn Blitter) { |
40 | let max = 32767.0; |
41 | let fixed_bounds = Rect::from_ltrb(-max, -max, max, max).unwrap(); |
42 | |
43 | let clip_bounds = clip.map(|c| c.to_rect()); |
44 | |
45 | for i in 0..points.len() - 1 { |
46 | let mut pts = [Point::zero(); 2]; |
47 | |
48 | // We have to pre-clip the line to fit in a Fixed, so we just chop the line. |
49 | if !line_clipper::intersect(&[points[i], points[i + 1]], &fixed_bounds, &mut pts) { |
50 | continue; |
51 | } |
52 | |
53 | if let Some(clip_bounds) = clip_bounds { |
54 | let tmp = pts.clone(); |
55 | // Perform a clip in scalar space, so we catch huge values which might |
56 | // be missed after we convert to FDot6 (overflow). |
57 | if !line_clipper::intersect(&tmp, &clip_bounds, &mut pts) { |
58 | continue; |
59 | } |
60 | } |
61 | |
62 | let mut x0 = fdot6::from_f32(pts[0].x); |
63 | let mut y0 = fdot6::from_f32(pts[0].y); |
64 | let mut x1 = fdot6::from_f32(pts[1].x); |
65 | let mut y1 = fdot6::from_f32(pts[1].y); |
66 | |
67 | debug_assert!(fdot6::can_convert_to_fdot16(x0)); |
68 | debug_assert!(fdot6::can_convert_to_fdot16(y0)); |
69 | debug_assert!(fdot6::can_convert_to_fdot16(x1)); |
70 | debug_assert!(fdot6::can_convert_to_fdot16(y1)); |
71 | |
72 | let dx = x1 - x0; |
73 | let dy = y1 - y0; |
74 | |
75 | if dx.abs() > dy.abs() { |
76 | // mostly horizontal |
77 | |
78 | if x0 > x1 { |
79 | // we want to go left-to-right |
80 | core::mem::swap(&mut x0, &mut x1); |
81 | core::mem::swap(&mut y0, &mut y1); |
82 | } |
83 | |
84 | let mut ix0 = fdot6::round(x0); |
85 | let ix1 = fdot6::round(x1); |
86 | if ix0 == ix1 { |
87 | // too short to draw |
88 | continue; |
89 | } |
90 | |
91 | let slope = fdot16::div(dy, dx); |
92 | #[allow (clippy::precedence)] |
93 | let mut start_y = fdot6::to_fdot16(y0) + (slope * ((32 - x0) & 63) >> 6); |
94 | |
95 | // In some cases, probably due to precision/rounding issues, |
96 | // `start_y` can become equal to the image height, |
97 | // which will lead to panic, because we would be accessing pixels outside |
98 | // the current memory buffer. |
99 | // This is tiny-skia specific issue. Skia handles this part differently. |
100 | let max_y = if let Some(clip_bounds) = clip_bounds { |
101 | fdot16::from_f32(clip_bounds.bottom()) |
102 | } else { |
103 | i32::MAX |
104 | }; |
105 | |
106 | debug_assert!(ix0 < ix1); |
107 | loop { |
108 | if ix0 >= 0 && start_y >= 0 && start_y < max_y { |
109 | blitter.blit_h(ix0 as u32, (start_y >> 16) as u32, LENGTH_U32_ONE); |
110 | } |
111 | |
112 | start_y += slope; |
113 | ix0 += 1; |
114 | if ix0 >= ix1 { |
115 | break; |
116 | } |
117 | } |
118 | } else { |
119 | // mostly vertical |
120 | |
121 | if y0 > y1 { |
122 | // we want to go top-to-bottom |
123 | core::mem::swap(&mut x0, &mut x1); |
124 | core::mem::swap(&mut y0, &mut y1); |
125 | } |
126 | |
127 | let mut iy0 = fdot6::round(y0); |
128 | let iy1 = fdot6::round(y1); |
129 | if iy0 == iy1 { |
130 | // too short to draw |
131 | continue; |
132 | } |
133 | |
134 | let slope = fdot16::div(dx, dy); |
135 | #[allow (clippy::precedence)] |
136 | let mut start_x = fdot6::to_fdot16(x0) + (slope * ((32 - y0) & 63) >> 6); |
137 | |
138 | debug_assert!(iy0 < iy1); |
139 | loop { |
140 | if start_x >= 0 && iy0 >= 0 { |
141 | blitter.blit_h((start_x >> 16) as u32, iy0 as u32, LENGTH_U32_ONE); |
142 | } |
143 | |
144 | start_x += slope; |
145 | iy0 += 1; |
146 | if iy0 >= iy1 { |
147 | break; |
148 | } |
149 | } |
150 | } |
151 | } |
152 | } |
153 | |
154 | pub fn stroke_path_impl( |
155 | path: &Path, |
156 | line_cap: LineCap, |
157 | clip: &ScreenIntRect, |
158 | line_proc: LineProc, |
159 | blitter: &mut dyn Blitter, |
160 | ) { |
161 | let mut inset_clip = None; |
162 | let mut outset_clip = None; |
163 | |
164 | { |
165 | let cap_out = if line_cap == LineCap::Butt { 1.0 } else { 2.0 }; |
166 | let ibounds = match path |
167 | .bounds() |
168 | .outset(cap_out, cap_out) |
169 | .and_then(|r| r.round_out()) |
170 | { |
171 | Some(v) => v, |
172 | None => return, |
173 | }; |
174 | if clip.to_int_rect().intersect(&ibounds).is_none() { |
175 | return; |
176 | } |
177 | |
178 | if !clip.to_int_rect().contains(&ibounds) { |
179 | // We now cache two scalar rects, to use for culling per-segment (e.g. cubic). |
180 | // Since we're hairlining, the "bounds" of the control points isn't necessairly the |
181 | // limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). |
182 | // |
183 | // Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust |
184 | // the culling bounds so we can just do a straight compare per segment. |
185 | // |
186 | // insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset |
187 | // it from the clip-bounds (since segment bounds can be off by 1). |
188 | // |
189 | // outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we |
190 | // outset it from the clip-bounds. |
191 | match clip.to_int_rect().make_outset(1, 1) { |
192 | Some(v) => outset_clip = Some(v), |
193 | None => return, |
194 | } |
195 | match clip.to_int_rect().inset(1, 1) { |
196 | Some(v) => inset_clip = Some(v), |
197 | None => return, |
198 | } |
199 | } |
200 | } |
201 | |
202 | let clip = Some(clip); |
203 | let mut prev_verb = PathVerb::Move; |
204 | let mut first_pt = Point::zero(); |
205 | let mut last_pt = Point::zero(); |
206 | |
207 | let mut iter = path.segments(); |
208 | while let Some(segment) = iter.next() { |
209 | let verb = iter.curr_verb(); |
210 | let next_verb = iter.next_verb(); |
211 | let last_pt2; |
212 | match segment { |
213 | PathSegment::MoveTo(p) => { |
214 | first_pt = p; |
215 | last_pt = p; |
216 | last_pt2 = p; |
217 | } |
218 | PathSegment::LineTo(p) => { |
219 | let mut points = [last_pt, p]; |
220 | if line_cap != LineCap::Butt { |
221 | extend_pts(line_cap, prev_verb, next_verb, &mut points); |
222 | } |
223 | |
224 | line_proc(&points, clip, blitter); |
225 | last_pt = p; |
226 | last_pt2 = points[0]; |
227 | } |
228 | PathSegment::QuadTo(p0, p1) => { |
229 | let mut points = [last_pt, p0, p1]; |
230 | if line_cap != LineCap::Butt { |
231 | extend_pts(line_cap, prev_verb, next_verb, &mut points); |
232 | } |
233 | |
234 | hair_quad( |
235 | &points, |
236 | clip, |
237 | inset_clip.as_ref(), |
238 | outset_clip.as_ref(), |
239 | compute_quad_level(&points), |
240 | line_proc, |
241 | blitter, |
242 | ); |
243 | |
244 | last_pt = p1; |
245 | last_pt2 = points[0]; |
246 | } |
247 | PathSegment::CubicTo(p0, p1, p2) => { |
248 | let mut points = [last_pt, p0, p1, p2]; |
249 | if line_cap != LineCap::Butt { |
250 | extend_pts(line_cap, prev_verb, next_verb, &mut points); |
251 | } |
252 | |
253 | hair_cubic( |
254 | &points, |
255 | clip, |
256 | inset_clip.as_ref(), |
257 | outset_clip.as_ref(), |
258 | line_proc, |
259 | blitter, |
260 | ); |
261 | |
262 | last_pt = p2; |
263 | last_pt2 = points[0]; |
264 | } |
265 | PathSegment::Close => { |
266 | let mut points = [last_pt, first_pt]; |
267 | if line_cap != LineCap::Butt && prev_verb == PathVerb::Move { |
268 | // cap moveTo/close to match svg expectations for degenerate segments |
269 | extend_pts(line_cap, prev_verb, next_verb, &mut points); |
270 | } |
271 | line_proc(&points, clip, blitter); |
272 | last_pt2 = points[0]; |
273 | } |
274 | } |
275 | |
276 | if line_cap != LineCap::Butt { |
277 | if prev_verb == PathVerb::Move |
278 | && matches!(verb, PathVerb::Line | PathVerb::Quad | PathVerb::Cubic) |
279 | { |
280 | first_pt = last_pt2; // the curve moved the initial point, so close to it instead |
281 | } |
282 | |
283 | prev_verb = verb; |
284 | } |
285 | } |
286 | } |
287 | |
288 | /// Extend the points in the direction of the starting or ending tangent by 1/2 unit to |
289 | /// account for a round or square cap. |
290 | /// |
291 | /// If there's no distance between the end point and |
292 | /// the control point, use the next control point to create a tangent. If the curve |
293 | /// is degenerate, move the cap out 1/2 unit horizontally. |
294 | fn extend_pts( |
295 | line_cap: LineCap, |
296 | prev_verb: PathVerb, |
297 | next_verb: Option<PathVerb>, |
298 | points: &mut [Point], |
299 | ) { |
300 | debug_assert!(!points.is_empty()); // TODO: use non-zero slice |
301 | debug_assert!(line_cap != LineCap::Butt); |
302 | |
303 | // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. |
304 | let cap_outset = if line_cap == LineCap::Square { |
305 | 0.5 |
306 | } else { |
307 | FLOAT_PI / 8.0 |
308 | }; |
309 | if prev_verb == PathVerb::Move { |
310 | let first = points[0]; |
311 | let mut offset = 0; |
312 | let mut controls = points.len() - 1; |
313 | let mut tangent; |
314 | loop { |
315 | offset += 1; |
316 | tangent = first - points[offset]; |
317 | |
318 | if !tangent.is_zero() { |
319 | break; |
320 | } |
321 | |
322 | controls -= 1; |
323 | if controls == 0 { |
324 | break; |
325 | } |
326 | } |
327 | |
328 | if tangent.is_zero() { |
329 | tangent = Point::from_xy(1.0, 0.0); |
330 | controls = points.len() - 1; // If all points are equal, move all but one. |
331 | } else { |
332 | tangent.normalize(); |
333 | } |
334 | |
335 | offset = 0; |
336 | loop { |
337 | // If the end point and control points are equal, loop to move them in tandem. |
338 | points[offset].x += tangent.x * cap_outset; |
339 | points[offset].y += tangent.y * cap_outset; |
340 | |
341 | offset += 1; |
342 | controls += 1; |
343 | if controls >= points.len() { |
344 | break; |
345 | } |
346 | } |
347 | } |
348 | |
349 | if matches!( |
350 | next_verb, |
351 | Some(PathVerb::Move) | Some(PathVerb::Close) | None |
352 | ) { |
353 | let last = points.last().unwrap().clone(); |
354 | let mut offset = points.len() - 1; |
355 | let mut controls = points.len() - 1; |
356 | let mut tangent; |
357 | loop { |
358 | offset -= 1; |
359 | tangent = last - points[offset]; |
360 | |
361 | if !tangent.is_zero() { |
362 | break; |
363 | } |
364 | |
365 | controls -= 1; |
366 | if controls == 0 { |
367 | break; |
368 | } |
369 | } |
370 | |
371 | if tangent.is_zero() { |
372 | tangent = Point::from_xy(-1.0, 0.0); |
373 | controls = points.len() - 1; |
374 | } else { |
375 | tangent.normalize(); |
376 | } |
377 | |
378 | offset = points.len() - 1; |
379 | loop { |
380 | points[offset].x += tangent.x * cap_outset; |
381 | points[offset].y += tangent.y * cap_outset; |
382 | |
383 | offset -= 1; |
384 | controls += 1; |
385 | if controls >= points.len() { |
386 | break; |
387 | } |
388 | } |
389 | } |
390 | } |
391 | |
392 | fn hair_quad( |
393 | points: &[Point; 3], |
394 | mut clip: Option<&ScreenIntRect>, |
395 | inset_clip: Option<&IntRect>, |
396 | outset_clip: Option<&IntRect>, |
397 | level: u8, |
398 | line_proc: LineProc, |
399 | blitter: &mut dyn Blitter, |
400 | ) { |
401 | if let Some(inset_clip: &IntRect) = inset_clip { |
402 | debug_assert!(outset_clip.is_some()); |
403 | let inset_clip: Rect = inset_clip.to_rect(); |
404 | let outset_clip: Rect = match outset_clip { |
405 | Some(v: &IntRect) => v.to_rect(), |
406 | None => return, |
407 | }; |
408 | |
409 | let bounds: Rect = match compute_nocheck_quad_bounds(points) { |
410 | Some(v: Rect) => v, |
411 | None => return, |
412 | }; |
413 | if !geometric_overlap(&outset_clip, &bounds) { |
414 | return; // nothing to do |
415 | } else if geometric_contains(&inset_clip, &bounds) { |
416 | clip = None; |
417 | } |
418 | } |
419 | |
420 | hair_quad2(points, clip, level, line_proc, blitter); |
421 | } |
422 | |
423 | fn compute_nocheck_quad_bounds(points: &[Point; 3]) -> Option<Rect> { |
424 | debug_assert!(points[0].is_finite()); |
425 | debug_assert!(points[1].is_finite()); |
426 | debug_assert!(points[2].is_finite()); |
427 | |
428 | let mut min: f32x2 = points[0].to_f32x2(); |
429 | let mut max: f32x2 = min; |
430 | for i: usize in 1..3 { |
431 | let pair: f32x2 = points[i].to_f32x2(); |
432 | min = min.min(pair); |
433 | max = max.max(pair); |
434 | } |
435 | |
436 | Rect::from_ltrb(left:min.x(), top:min.y(), right:max.x(), bottom:max.y()) |
437 | } |
438 | |
439 | fn geometric_overlap(a: &Rect, b: &Rect) -> bool { |
440 | a.left() < b.right() && b.left() < a.right() && a.top() < b.bottom() && b.top() < a.bottom() |
441 | } |
442 | |
443 | fn geometric_contains(outer: &Rect, inner: &Rect) -> bool { |
444 | inner.right() <= outer.right() |
445 | && inner.left() >= outer.left() |
446 | && inner.bottom() <= outer.bottom() |
447 | && inner.top() >= outer.top() |
448 | } |
449 | |
450 | fn hair_quad2( |
451 | points: &[Point; 3], |
452 | clip: Option<&ScreenIntRect>, |
453 | level: u8, |
454 | line_proc: LineProc, |
455 | blitter: &mut dyn Blitter, |
456 | ) { |
457 | debug_assert!(level <= MAX_QUAD_SUBDIVIDE_LEVEL); // TODO: to type |
458 | |
459 | let coeff: QuadCoeff = path_geometry::QuadCoeff::from_points(points); |
460 | |
461 | const MAX_POINTS: usize = (1 << MAX_QUAD_SUBDIVIDE_LEVEL) + 1; |
462 | let lines: usize = 1 << level; |
463 | debug_assert!(lines < MAX_POINTS); |
464 | |
465 | let mut tmp: [Point; 33] = [Point::zero(); MAX_POINTS]; |
466 | tmp[0] = points[0]; |
467 | |
468 | let mut t: f32x2 = f32x2::default(); |
469 | let dt: f32x2 = f32x2::splat(1.0 / lines as f32); |
470 | for i: usize in 1..lines { |
471 | t = t + dt; |
472 | let v: f32x2 = (coeff.a * t + coeff.b) * t + coeff.c; |
473 | tmp[i] = Point::from_xy(v.x(), v.y()); |
474 | } |
475 | |
476 | tmp[lines] = points[2]; |
477 | line_proc(&tmp[0..lines + 1], clip, blitter); |
478 | } |
479 | |
480 | fn compute_quad_level(points: &[Point; 3]) -> u8 { |
481 | let d: u32 = compute_int_quad_dist(points); |
482 | // Quadratics approach the line connecting their start and end points |
483 | // 4x closer with each subdivision, so we compute the number of |
484 | // subdivisions to be the minimum need to get that distance to be less |
485 | // than a pixel. |
486 | let mut level: u32 = (33 - d.leading_zeros()) >> 1; |
487 | // sanity check on level (from the previous version) |
488 | if level > MAX_QUAD_SUBDIVIDE_LEVEL as u32 { |
489 | level = MAX_QUAD_SUBDIVIDE_LEVEL as u32; |
490 | } |
491 | |
492 | level as u8 |
493 | } |
494 | |
495 | fn compute_int_quad_dist(points: &[Point; 3]) -> u32 { |
496 | // compute the vector between the control point ([1]) and the middle of the |
497 | // line connecting the start and end ([0] and [2]) |
498 | let dx: f32 = ((points[0].x + points[2].x).half() - points[1].x).abs(); |
499 | let dy: f32 = ((points[0].y + points[2].y).half() - points[1].y).abs(); |
500 | |
501 | // convert to whole pixel values (use ceiling to be conservative). |
502 | // assign to unsigned so we can safely add 1/2 of the smaller and still fit in |
503 | // u32, since T::saturate_from() returns 31 bits at most. |
504 | let idx: u32 = i32::saturate_from(dx.ceil()) as u32; |
505 | let idy: u32 = i32::saturate_from(dy.ceil()) as u32; |
506 | |
507 | // use the cheap approx for distance |
508 | if idx > idy { |
509 | idx + (idy >> 1) |
510 | } else { |
511 | idy + (idx >> 1) |
512 | } |
513 | } |
514 | |
515 | fn hair_cubic( |
516 | points: &[Point; 4], |
517 | mut clip: Option<&ScreenIntRect>, |
518 | inset_clip: Option<&IntRect>, |
519 | outset_clip: Option<&IntRect>, |
520 | line_proc: LineProc, |
521 | blitter: &mut dyn Blitter, |
522 | ) { |
523 | if let Some(inset_clip) = inset_clip { |
524 | debug_assert!(outset_clip.is_some()); |
525 | let inset_clip = inset_clip.to_rect(); |
526 | let outset_clip = match outset_clip { |
527 | Some(v) => v.to_rect(), |
528 | None => return, |
529 | }; |
530 | |
531 | let bounds = match compute_nocheck_cubic_bounds(points) { |
532 | Some(v) => v, |
533 | None => return, |
534 | }; |
535 | if !geometric_overlap(&outset_clip, &bounds) { |
536 | return; // noting to do |
537 | } else if geometric_contains(&inset_clip, &bounds) { |
538 | clip = None; |
539 | } |
540 | } |
541 | |
542 | if quick_cubic_niceness_check(points) { |
543 | hair_cubic2(points, clip, line_proc, blitter); |
544 | } else { |
545 | let mut tmp = [Point::zero(); 13]; |
546 | let mut t_values = path_geometry::new_t_values(); |
547 | |
548 | let count = path_geometry::chop_cubic_at_max_curvature(points, &mut t_values, &mut tmp); |
549 | for i in 0..count { |
550 | let offset = i * 3; |
551 | let new_points: [Point; 4] = tmp[offset..offset + 4].try_into().unwrap(); |
552 | hair_cubic2(&new_points, clip, line_proc, blitter); |
553 | } |
554 | } |
555 | } |
556 | |
557 | fn compute_nocheck_cubic_bounds(points: &[Point; 4]) -> Option<Rect> { |
558 | debug_assert!(points[0].is_finite()); |
559 | debug_assert!(points[1].is_finite()); |
560 | debug_assert!(points[2].is_finite()); |
561 | debug_assert!(points[3].is_finite()); |
562 | |
563 | let mut min: f32x2 = points[0].to_f32x2(); |
564 | let mut max: f32x2 = min; |
565 | for i: usize in 1..4 { |
566 | let pair: f32x2 = points[i].to_f32x2(); |
567 | min = min.min(pair); |
568 | max = max.max(pair); |
569 | } |
570 | |
571 | Rect::from_ltrb(left:min.x(), top:min.y(), right:max.x(), bottom:max.y()) |
572 | } |
573 | |
574 | // The off-curve points are "inside" the limits of the on-curve points. |
575 | fn quick_cubic_niceness_check(points: &[Point; 4]) -> bool { |
576 | lt_90(p0:points[1], pivot:points[0], p2:points[3]) |
577 | && lt_90(p0:points[2], pivot:points[0], p2:points[3]) |
578 | && lt_90(p0:points[1], pivot:points[3], p2:points[0]) |
579 | && lt_90(p0:points[2], pivot:points[3], p2:points[0]) |
580 | } |
581 | |
582 | fn lt_90(p0: Point, pivot: Point, p2: Point) -> bool { |
583 | (p0 - pivot).dot(p2 - pivot) >= 0.0 |
584 | } |
585 | |
586 | fn hair_cubic2( |
587 | points: &[Point; 4], |
588 | clip: Option<&ScreenIntRect>, |
589 | line_proc: LineProc, |
590 | blitter: &mut dyn Blitter, |
591 | ) { |
592 | let lines = compute_cubic_segments(points); |
593 | debug_assert!(lines > 0); |
594 | if lines == 1 { |
595 | line_proc(&[points[0], points[3]], clip, blitter); |
596 | return; |
597 | } |
598 | |
599 | let coeff = path_geometry::CubicCoeff::from_points(points); |
600 | |
601 | const MAX_POINTS: usize = (1 << MAX_CUBIC_SUBDIVIDE_LEVEL) + 1; |
602 | debug_assert!(lines < MAX_POINTS); |
603 | let mut tmp = [Point::zero(); MAX_POINTS]; |
604 | |
605 | let dt = f32x2::splat(1.0 / lines as f32); |
606 | let mut t = f32x2::default(); |
607 | |
608 | tmp[0] = points[0]; |
609 | for i in 1..lines { |
610 | t = t + dt; |
611 | tmp[i] = Point::from_f32x2(((coeff.a * t + coeff.b) * t + coeff.c) * t + coeff.d); |
612 | } |
613 | |
614 | if tmp.iter().all(|p| p.is_finite()) { |
615 | tmp[lines] = points[3]; |
616 | line_proc(&tmp[0..lines + 1], clip, blitter); |
617 | } else { |
618 | // else some point(s) are non-finite, so don't draw |
619 | } |
620 | } |
621 | |
622 | fn compute_cubic_segments(points: &[Point; 4]) -> usize { |
623 | let p0 = points[0].to_f32x2(); |
624 | let p1 = points[1].to_f32x2(); |
625 | let p2 = points[2].to_f32x2(); |
626 | let p3 = points[3].to_f32x2(); |
627 | |
628 | let one_third = f32x2::splat(1.0 / 3.0); |
629 | let two_third = f32x2::splat(2.0 / 3.0); |
630 | |
631 | let p13 = one_third * p3 + two_third * p0; |
632 | let p23 = one_third * p0 + two_third * p3; |
633 | |
634 | let diff = (p1 - p13).abs().max((p2 - p23).abs()).max_component(); |
635 | let mut tol = 1.0 / 8.0; |
636 | |
637 | for i in 0..MAX_CUBIC_SUBDIVIDE_LEVEL { |
638 | if diff < tol { |
639 | return 1 << i; |
640 | } |
641 | |
642 | tol *= 4.0; |
643 | } |
644 | |
645 | 1 << MAX_CUBIC_SUBDIVIDE_LEVEL |
646 | } |
647 | |