From 8c518deebef68ebdcaa9d21f6ca1b90b6db7220a Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 22 Sep 2017 17:15:19 -0700 Subject: [PATCH] =?UTF-8?q?Do=20a=20better=20job=20of=20approximating=20cu?= =?UTF-8?q?bic=20B=C3=A9zier=20curves=20with=20quadratics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This still isn't great, but it's a lot better than the old approximation, which was just bogus. --- demo/server/src/main.rs | 23 +++++++---- path-utils/src/cubic.rs | 92 +++++++++++++++++++++++++++++++++++++++++ path-utils/src/curve.rs | 2 +- path-utils/src/lib.rs | 1 + 4 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 path-utils/src/cubic.rs diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 017b016d..8c643b96 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -26,6 +26,7 @@ use bincode::Infinite; use euclid::{Point2D, Size2D, Transform2D}; use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey}; use pathfinder_partitioner::partitioner::Partitioner; +use pathfinder_path_utils::cubic::CubicCurve; use pathfinder_path_utils::monotonic::MonotonicPathSegmentStream; use pathfinder_path_utils::stroke; use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathSegment, Transform2DPathStream}; @@ -468,6 +469,8 @@ fn partition_svg_paths(request: Json) // commands. let mut path_buffer = PathBuffer::new(); let mut paths = vec![]; + let mut last_point = Point2D::zero(); + for path in &request.paths { let mut stream = vec![]; @@ -476,12 +479,12 @@ fn partition_svg_paths(request: Json) for segment in &path.segments { match segment.kind { 'M' => { - stream.push(PathSegment::MoveTo(Point2D::new(segment.values[0] as f32, - segment.values[1] as f32))) + last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); + stream.push(PathSegment::MoveTo(last_point)) } 'L' => { - stream.push(PathSegment::LineTo(Point2D::new(segment.values[0] as f32, - segment.values[1] as f32))) + last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); + stream.push(PathSegment::LineTo(last_point)) } 'C' => { // FIXME(pcwalton): Do real cubic-to-quadratic conversion. @@ -489,10 +492,14 @@ fn partition_svg_paths(request: Json) segment.values[1] as f32); let control_point_1 = Point2D::new(segment.values[2] as f32, segment.values[3] as f32); - let control_point = control_point_0.lerp(control_point_1, 0.5); - stream.push(PathSegment::CurveTo(control_point, - Point2D::new(segment.values[4] as f32, - segment.values[5] as f32))) + let endpoint_1 = Point2D::new(segment.values[4] as f32, + segment.values[5] as f32); + let cubic = CubicCurve::new(&last_point, + &control_point_0, + &control_point_1, + &endpoint_1); + last_point = endpoint_1; + stream.extend(cubic.approximate_curve().map(|curve| curve.to_path_segment())); } 'Z' => stream.push(PathSegment::ClosePath), _ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)), diff --git a/path-utils/src/cubic.rs b/path-utils/src/cubic.rs new file mode 100644 index 00000000..9b2c63c2 --- /dev/null +++ b/path-utils/src/cubic.rs @@ -0,0 +1,92 @@ +// pathfinder/path-utils/src/cubic.rs +// +// Copyright © 2017 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Utilities for cubic Bézier curves. + +use euclid::Point2D; + +use curve::Curve; + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct CubicCurve { + pub endpoints: [Point2D; 2], + pub control_points: [Point2D; 2], +} + +impl CubicCurve { + #[inline] + pub fn new(endpoint_0: &Point2D, + control_point_0: &Point2D, + control_point_1: &Point2D, + endpoint_1: &Point2D) + -> CubicCurve { + CubicCurve { + endpoints: [*endpoint_0, *endpoint_1], + control_points: [*control_point_0, *control_point_1], + } + } + + pub fn sample(&self, t: f32) -> Point2D { + let (p0, p3) = (&self.endpoints[0], &self.endpoints[1]); + let (p1, p2) = (&self.control_points[0], &self.control_points[1]); + let (p0p1, p1p2, p2p3) = (p0.lerp(*p1, t), p1.lerp(*p2, t), p2.lerp(*p3, t)); + let (p0p1p2, p1p2p3) = (p0p1.lerp(p1p2, t), p1p2.lerp(p2p3, t)); + p0p1p2.lerp(p1p2p3, t) + } + + pub fn subdivide(&self, t: f32) -> (CubicCurve, CubicCurve) { + let (p0, p3) = (&self.endpoints[0], &self.endpoints[1]); + let (p1, p2) = (&self.control_points[0], &self.control_points[1]); + let (p0p1, p1p2, p2p3) = (p0.lerp(*p1, t), p1.lerp(*p2, t), p2.lerp(*p3, t)); + let (p0p1p2, p1p2p3) = (p0p1.lerp(p1p2, t), p1p2.lerp(p2p3, t)); + let p0p1p2p3 = p0p1p2.lerp(p1p2p3, t); + (CubicCurve::new(&p0, &p0p1, &p0p1p2, &p0p1p2p3), + CubicCurve::new(&p0p1p2p3, &p1p2p3, &p2p3, &p3)) + } + + pub fn approximate_curve(&self) -> ApproximateCurveIter { + ApproximateCurveIter::new(self) + } +} + +// FIXME(pcwalton): Do better. See: https://github.com/googlei18n/cu2qu +pub struct ApproximateCurveIter { + curves: [CubicCurve; 2], + iteration: u8, +} + +impl ApproximateCurveIter { + fn new(cubic: &CubicCurve) -> ApproximateCurveIter { + let (curve_a, curve_b) = cubic.subdivide(0.5); + ApproximateCurveIter { + curves: [curve_a, curve_b], + iteration: 0, + } + } +} + +impl Iterator for ApproximateCurveIter { + type Item = Curve; + + fn next(&mut self) -> Option { + let cubic = match self.curves.get(self.iteration as usize) { + Some(cubic) => *cubic, + None => return None, + }; + self.iteration += 1; + + let approx_control_point_0 = (cubic.control_points[0] * 3.0 - cubic.endpoints[0]) * 0.5; + let approx_control_point_1 = (cubic.control_points[1] * 3.0 - cubic.endpoints[1]) * 0.5; + + Some(Curve::new(&cubic.endpoints[0], + &approx_control_point_0.lerp(approx_control_point_1, 0.5).to_point(), + &cubic.endpoints[1])) + } +} diff --git a/path-utils/src/curve.rs b/path-utils/src/curve.rs index a07b3c80..4bdfc10c 100644 --- a/path-utils/src/curve.rs +++ b/path-utils/src/curve.rs @@ -48,7 +48,7 @@ impl Curve { } #[inline] - pub(crate) fn to_path_segment(&self) -> PathSegment { + pub fn to_path_segment(&self) -> PathSegment { PathSegment::CurveTo(self.control_point, self.endpoints[1]) } diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs index 982aa42c..8c1eac00 100644 --- a/path-utils/src/lib.rs +++ b/path-utils/src/lib.rs @@ -17,6 +17,7 @@ extern crate serde_derive; use euclid::{Point2D, Transform2D}; use std::u32; +pub mod cubic; pub mod curve; pub mod freetype; pub mod intersection;