Auto merge of #25801 - pylbrecht:arc.refactor, r=jdm

Refactor CanvasRenderingContext2D.arc() and .ellipse()

<!-- Please describe your changes on the following line: -->
Refactor `arc()` and `ellipse()` to make use of `lyon_geom::Arc` for approximating an arc with quadratic bezier curves.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix part of #25331

<!-- Either: -->
- [x] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2020-02-26 09:04:08 -05:00 committed by GitHub
commit ad9bfc2a62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 55 additions and 151 deletions

1
Cargo.lock generated
View file

@ -489,6 +489,7 @@ dependencies = [
"half", "half",
"ipc-channel", "ipc-channel",
"log", "log",
"lyon_geom 0.14.0",
"num-traits", "num-traits",
"pixels", "pixels",
"raqote", "raqote",

View file

@ -29,6 +29,7 @@ gleam = "0.6.7"
half = "1" half = "1"
ipc-channel = "0.14" ipc-channel = "0.14"
log = "0.4" log = "0.4"
lyon_geom = "0.14"
num-traits = "0.2" num-traits = "0.2"
raqote = {git = "https://github.com/jrmuizel/raqote"} raqote = {git = "https://github.com/jrmuizel/raqote"}
time = { version = "0.1.0", optional = true } time = { version = "0.1.0", optional = true }

View file

@ -12,8 +12,9 @@ use crate::canvas_paint_thread::AntialiasMode;
use canvas_traits::canvas::*; use canvas_traits::canvas::*;
use cssparser::RGBA; use cssparser::RGBA;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use euclid::Angle;
use lyon_geom::Arc;
use raqote::PathOp; use raqote::PathOp;
use std::f32::consts::PI;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct RaqoteBackend; pub struct RaqoteBackend;
@ -684,25 +685,19 @@ impl GenericPathBuilder for PathBuilder {
&mut self, &mut self,
origin: Point2D<f32>, origin: Point2D<f32>,
radius: f32, radius: f32,
mut start_angle: f32, start_angle: f32,
mut end_angle: f32, end_angle: f32,
anticlockwise: bool, anticlockwise: bool,
) { ) {
if (!anticlockwise && start_angle > end_angle + 2. * PI) || self.ellipse(
(anticlockwise && end_angle > start_angle + 2. * PI) origin,
{ radius,
start_angle = start_angle % (2. * PI); radius,
end_angle = end_angle % (2. * PI); 0.,
} start_angle,
end_angle,
if (anticlockwise && end_angle > 0.) || (!anticlockwise && end_angle < 0.) { anticlockwise,
end_angle = -end_angle; );
}
self.0
.as_mut()
.unwrap()
.arc(origin.x, origin.y, radius, start_angle, end_angle);
} }
fn bezier_curve_to( fn bezier_curve_to(
&mut self, &mut self,
@ -727,77 +722,57 @@ impl GenericPathBuilder for PathBuilder {
origin: Point2D<f32>, origin: Point2D<f32>,
radius_x: f32, radius_x: f32,
radius_y: f32, radius_y: f32,
_rotation_angle: f32, rotation_angle: f32,
start_angle: f32, start_angle: f32,
mut end_angle: f32, end_angle: f32,
anticlockwise: bool, anticlockwise: bool,
) { ) {
let start_point = Point2D::new( let mut start = Angle::radians(start_angle);
origin.x + start_angle.cos() * radius_x, let mut end = Angle::radians(end_angle);
origin.y + end_angle.sin() * radius_y,
);
self.line_to(start_point);
if !anticlockwise && (end_angle < start_angle) { // Wrap angles mod 2 * PI if necessary
let correction = ((start_angle - end_angle) / (2.0 * PI)).ceil(); if !anticlockwise && start > end + Angle::two_pi() ||
end_angle += correction * 2.0 * PI; anticlockwise && end > start + Angle::two_pi()
} else if anticlockwise && (start_angle < end_angle) { {
let correction = ((end_angle - start_angle) / (2.0 * PI)).ceil(); start = start.positive();
end_angle += correction * 2.0 * PI; end = end.positive();
}
// Sweeping more than 2 * pi is a full circle.
if !anticlockwise && (end_angle - start_angle > 2.0 * PI) {
end_angle = start_angle + 2.0 * PI;
} else if anticlockwise && (start_angle - end_angle > 2.0 * PI) {
end_angle = start_angle - 2.0 * PI;
} }
// Calculate the total arc we're going to sweep. // Calculate the total arc we're going to sweep.
let mut arc_sweep_left = (end_angle - start_angle).abs(); let sweep = match anticlockwise {
let sweep_direction = match anticlockwise { true => {
true => -1.0, if end - start == Angle::two_pi() {
false => 1.0, -Angle::two_pi()
}; } else if end > start {
let mut current_start_angle = start_angle; -(Angle::two_pi() - (end - start))
while arc_sweep_left > 0.0 {
// We guarantee here the current point is the start point of the next
// curve segment.
let current_end_angle;
if arc_sweep_left > PI / 2.0 {
current_end_angle = current_start_angle + PI / 2.0 * sweep_direction;
} else { } else {
current_end_angle = current_start_angle + arc_sweep_left * sweep_direction; -(start - end)
} }
let current_start_point = Point2D::new( },
origin.x + current_start_angle.cos() * radius_x, false => {
origin.y + current_start_angle.sin() * radius_y, if start - end == Angle::two_pi() {
); Angle::two_pi()
let current_end_point = Point2D::new( } else if start > end {
origin.x + current_end_angle.cos() * radius_x, Angle::two_pi() - (start - end)
origin.y + current_end_angle.sin() * radius_y, } else {
); end - start
// Calculate kappa constant for partial curve. The sign of angle in the
// tangent will actually ensure this is negative for a counter clockwise
// sweep, so changing signs later isn't needed.
let kappa_factor =
(4.0 / 3.0) * ((current_end_angle - current_start_angle) / 4.0).tan();
let kappa_x = kappa_factor * radius_x;
let kappa_y = kappa_factor * radius_y;
let tangent_start =
Point2D::new(-(current_start_angle.sin()), current_start_angle.cos());
let mut cp1 = current_start_point;
cp1 += Point2D::new(tangent_start.x * kappa_x, tangent_start.y * kappa_y).to_vector();
let rev_tangent_end = Point2D::new(current_end_angle.sin(), -(current_end_angle.cos()));
let mut cp2 = current_end_point;
cp2 +=
Point2D::new(rev_tangent_end.x * kappa_x, rev_tangent_end.y * kappa_y).to_vector();
self.bezier_curve_to(&cp1, &cp2, &current_end_point);
arc_sweep_left -= PI / 2.0;
current_start_angle = current_end_angle;
} }
},
};
let arc: Arc<f32> = Arc {
center: origin,
radii: Vector2D::new(radius_x, radius_y),
start_angle: start,
sweep_angle: sweep,
x_rotation: Angle::radians(rotation_angle),
};
self.line_to(arc.from());
arc.for_each_quadratic_bezier(&mut |q| {
self.quadratic_curve_to(&q.ctrl, &q.to);
});
} }
fn get_current_point(&mut self) -> Option<Point2D<f32>> { fn get_current_point(&mut self) -> Option<Point2D<f32>> {

View file

@ -1,4 +0,0 @@
[2d.line.cap.round.html]
[lineCap 'round' is rendered correctly]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.1.html]
[arc() draws nothing when end = start + 2pi-e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.3.html]
[arc() draws a full circle when end = start + 2pi+e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.scale.html]
[arcTo scales the curve, not just the control points]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.path.arcTo.shape.curve2.html]
type: testharness
[arcTo() curves in the right kind of shape]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.transformation.html]
[arcTo joins up to the last subpath point correctly]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.line.cap.round.html]
[lineCap 'round' is rendered correctly]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.line.cap.round.worker.html]
[lineCap 'round' is rendered correctly]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.1.html]
[arc() draws nothing when end = start + 2pi-e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.1.worker.html]
[arc() draws nothing when end = start + 2pi-e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.3.html]
[arc() draws a full circle when end = start + 2pi+e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arc.twopie.3.worker.html]
[arc() draws a full circle when end = start + 2pi+e and anticlockwise]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.scale.html]
[arcTo scales the curve, not just the control points]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.scale.worker.html]
[arcTo scales the curve, not just the control points]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.shape.curve2.html]
[arcTo() curves in the right kind of shape]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.shape.curve2.worker.html]
[arcTo() curves in the right kind of shape]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.transformation.html]
[arcTo joins up to the last subpath point correctly]
expected: FAIL

View file

@ -1,4 +0,0 @@
[2d.path.arcTo.transformation.worker.html]
[arcTo joins up to the last subpath point correctly]
expected: FAIL