Do a better job of approximating cubic Bézier curves with quadratics
This still isn't great, but it's a lot better than the old approximation, which was just bogus.
This commit is contained in:
parent
2039051f01
commit
8c518deebe
|
@ -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<PartitionSvgPathsRequest>)
|
|||
// 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<PartitionSvgPathsRequest>)
|
|||
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<PartitionSvgPathsRequest>)
|
|||
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)),
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
// pathfinder/path-utils/src/cubic.rs
|
||||
//
|
||||
// Copyright © 2017 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<f32>; 2],
|
||||
pub control_points: [Point2D<f32>; 2],
|
||||
}
|
||||
|
||||
impl CubicCurve {
|
||||
#[inline]
|
||||
pub fn new(endpoint_0: &Point2D<f32>,
|
||||
control_point_0: &Point2D<f32>,
|
||||
control_point_1: &Point2D<f32>,
|
||||
endpoint_1: &Point2D<f32>)
|
||||
-> CubicCurve {
|
||||
CubicCurve {
|
||||
endpoints: [*endpoint_0, *endpoint_1],
|
||||
control_points: [*control_point_0, *control_point_1],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample(&self, t: f32) -> Point2D<f32> {
|
||||
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<Curve> {
|
||||
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]))
|
||||
}
|
||||
}
|
|
@ -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])
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue