fn compute_normal(p0: Point, p1: Point) -> Option<Vector> { let ux = p1.x - p0.x; let uy = p1.y - p0.y;
// this could overflow f32. Skia checks for this and // uses a double in that situation let ulen = ux.hypot(uy); if ulen == 0. { return None;
} // the normal is perpendicular to the *unit* vector
Some(Vector::new(-uy / ulen, ux / ulen))
}
struct Target<'a, 'b> { last_point: GpPointR, last_normal: GpPointR, xc: f32, yc: f32, path: &an style='color:blue'>'a mut PathBuilder<'b> } impl<'a, 'b> CFlatteningSink for Target<'a, 'b> { fn AcceptPointAndTangent(&mutself,
pt: &GpPointR, // The point
vec: &GpPointR, // The tangent there
_last: bool // Is this the last point on the curve?
) -> HRESULT { ifself.path.aa { let len = vec.Norm(); let normal = *vec/len; let normal = GpPointR { x: -normal.y, y: normal.x }; // FIXME: we probably need more width here because // the normals are not perpendicular with the edge let width = 0.5;
fn AcceptPoint(&mutself,
pt: &GpPointR, // The point
_t: f32, // Parameter we're at
_aborted: &mut bool,
_last_point: bool) -> HRESULT { self.path.push_tri(self.last_point.x, self.last_point.y, pt.x, pt.y, self.xc, self.yc); self.last_point = pt.clone(); return S_OK;
} fn FirstTangent(&mutself, _: Option<GpPointR>) { }
} let bezier = CBezier::new([GpPointR { x: (xc + r_cos_a), y: (yc + r_sin_a), },
GpPointR { x: (xc + r_cos_a - h * r_sin_a), y: (yc + r_sin_a + h * r_cos_a), },
GpPointR { x: (xc + r_cos_b + h * r_sin_b), y: (yc + r_sin_b - h * r_cos_b), },
GpPointR { x: (xc + r_cos_b), y: (yc + r_sin_b), }]); if bezier.is_degenerate() { return;
} letmut t = Target{ last_point, last_normal: initial_normal, xc, yc, path }; letmut f = CBezierFlattener::new(&bezier, &mut t, 0.25); if f.GetFirstTangent().is_none() { // the curve is not completely degenerate but degenerate enough that we can't flatten it
// draw a single triangle for the curve let width = 0.5; let next_point = GpPointR { x: (xc + r_cos_b), y: (yc + r_sin_b) }; if path.aa { // XXX: This code will potentially run into trouble if the radius is very small // but the general case suffers the same problem.
path.ramp(
next_point.x - b.x * width,
next_point.y - b.y * width,
next_point.x + b.x * width,
next_point.y + b.y * width,
last_point.x + a.x * width,
last_point.y + a.y * width,
last_point.x - a.x * width,
last_point.y - a.y * width);
path.push_tri(
last_point.x - a.x * 0.5,
last_point.y - a.y * 0.5,
next_point.x - b.x * 0.5,
next_point.y - b.y * 0.5,
xc, yc);
} else {
path.push_tri(last_point.x, last_point.y, next_point.x, next_point.y, xc, yc);
}
} else {
f.Flatten(true);
}
}
/* The angle between the vectors must be <= pi */ fn bisect(a: Vector, b: Vector) -> Vector { letmut mid; if dot(a, b) >= 0. { /* if the angle between a and b is accute, then we can
* just add the vectors and normalize */
mid = a + b;
} else { /* otherwise, we can flip a, add it
* and then use the perpendicular of the result */
mid = flip(a) + b;
mid = perp(mid);
}
/* normalize */ /* because we assume that 'a' and 'b' are normalized, we can use
* sqrt instead of hypot because the range of mid is limited */ let mid_len = mid.x * mid.x + mid.y * mid.y; let len = mid_len.sqrt(); return mid / len;
}
/* The angle between the vectors must be <= 180 degrees */ fn arc(path: &mut PathBuilder, xc: f32, yc: f32, radius: f32, a: Vector, b: Vector) { // Depending on the magnitude of the angle use 0, 1 or 2 arc segments. if dot(a, b) == 1.0 { // the angle is 0 degrees, do nothing
} elseif dot(a, b) >= 0. { // the angle is less than 90 degrees
arc_segment_tri(path, xc, yc, radius, a, b);
} else { /* find a vector that bisects the angle between a and b */ let mid_v = bisect(a, b);
/* construct the arc using two curve segments */
arc_segment_tri(path, xc, yc, radius, a, mid_v);
arc_segment_tri(path, xc, yc, radius, mid_v, b);
}
}
/* fnjoin_round(path:&mutPathBuilder,center:Point,a:Vector,b:Vector,radius:f32){ /* intccw=dot(perp(b),a)>=0;// XXX: is this always true? yes,otherwisewehaveaninteriorangle. assert(ccw);
*/
arc(path, center.x, center.y, radius, a, b);
}*/
fn bevel(
dest: &mut PathBuilder,
style: &StrokeStyle,
pt: Point,
s1_normal: Vector,
s2_normal: Vector,
) { let offset = style.width / 2.; if dest.aa { let width = 1.; let offset = offset - width / 2.; //XXX: we should be able to just bisect the two norms to get this let diff = match (s2_normal - s1_normal).try_normalize() {
Some(diff) => diff,
None => return,
}; let edge_normal = perp(diff);
/* given a normal rotate the vector 90 degrees to the right clockwise
* This function has a period of 4. e.g. swap(swap(swap(swap(x) == x */ fn swap(a: Vector) -> Vector { /* one of these needs to be negative. We choose a.x so that we rotate to the right instead of negating */
Vector::new(a.y, -a.x)
}
fn unperp(a: Vector) -> Vector {
swap(a)
}
/* rotate a vector 90 degrees to the left */ fn perp(v: Vector) -> Vector {
Vector::new(-v.y, v.x)
}
/* Finds the intersection of two lines each defined by a point and a normal. From"Example2:Findtheintersectionoftwolines"of "ThePleasuresof"PerpDot"Products"
F. S. Hill, Jr. */ fn line_intersection(a: Point, a_perp: Vector, b: Point, b_perp: Vector) -> Option<Point> { let a_parallel = unperp(a_perp); let c = b - a; let denom = dot(b_perp, a_parallel); if denom == 0.0 { return None;
}
let t = dot(b_perp, c) / denom;
let intersection = Point::new(a.x + t * (a_parallel.x), a.y + t * (a_parallel.y));
Some(intersection)
}
fn is_interior_angle(a: Vector, b: Vector) -> bool { /* angles of 180 and 0 degress will evaluate to 0, however
* we to treat 0 as an interior angle and 180 as an exterior angle */
dot(perp(a), b) > 0. || a == b /* 0 degrees is interior */
}
// XXX: joining uses `pt` which can cause seams because it lies halfway on a line and the // rasterizer may not find exactly the same spot letmut offset = style.width / 2.;
match style.join {
LineJoin::Round => {
dest.arc_wedge(pt, offset, s1_normal, s2_normal);
}
LineJoin::Miter => { if dest.aa {
offset -= 0.5;
} let in_dot_out = -s1_normal.x * s2_normal.x + -s1_normal.y * s2_normal.y; if2. <= style.miter_limit * style.miter_limit * (1. - in_dot_out) { let start = pt + s1_normal * offset; let end = pt + s2_normal * offset; iflet Some(intersection) = line_intersection(start, s1_normal, end, s2_normal) { // We won't have an intersection if the segments are parallel if dest.aa { let ramp_start = pt + s1_normal * (offset + 1.); let ramp_end = pt + s2_normal * (offset + 1.);
// The following diagram is inspired by the DoLimitedMiter code // from WpfGfx. Their math doesn't make sense to me so // there's an original derivation from jgilbert below: // // offset point // --*---------------------- offset line // | * // |* // * clip point // *|s a spine // *-- offset ................ // q| point . // | . // | . // | . // | - r - . ------- // | . | // | . | // // b = a/2 // r/(q+r) = cos b => q + r = r/cos b => q = r/cos b - r // q/s = sin b/cos b => s = q cos b/sin b // s = (r/cos b - r) * cos b/sin b = (r - r cos b)/sin b // sub in r = 1 // s = (1 - cos b)/sin b // // rearrange so that we don't have denominator of 0 when b = 0 (parallel lines) // this prevents numerical instability in that case // // (1 - cos b)/sin b => (1 - cos b)(1 + cos b)/(sin b * (1 + cos b)) // => (1 - cos^2 b) / (sin b * (1 + cos b) // => sin^2 b / sin b * (1 + cos b) // => sin b / (1 + cos b)
let mid = bisect(s1_normal, s2_normal);
// cross = sin, dot = cos let cos = dot(s1_normal, mid); let s = cross(mid, s1_normal)/(1. + cos);
// compute the intersection in a more stable way let intersection = pt + mid * (offset / cos);
pubfn line_to_capped(&mutself, pt: Point) { iflet Some(cur_pt) = self.cur_pt { let normal = compute_normal(cur_pt, pt).unwrap_or(self.last_normal); // if we have a butt cap end the line half a pixel early so we have room to put the cap. // XXX: this will probably mess things up if the line is shorter than 1/2 pixel long self.line_to(ifself.stroked_path.aa && self.style.cap == LineCap::Butt { pt + perp(normal) * 0.5} else { pt }); iflet (Some(cur_pt), Some((_point, _normal))) = (self.cur_pt, self.start_point) { // cap end
cap_line(&mutself.stroked_path, &self.style, cur_pt, self.last_normal);
}
} self.start_point = None;
}
fn AcceptPoint(&mutself,
pt: &GpPointR, // The point
_t: f32, // Parameter we're at
_aborted: &mut bool,
last_point: bool) -> HRESULT { if last_point && self.end { self.stroker.line_to_capped(Point::new(pt.x, pt.y));
} else { self.stroker.line_to(Point::new(pt.x, pt.y));
} return S_OK;
}
} let cur_pt = self.cur_pt.unwrap_or(cx1); let bezier = CBezier::new([GpPointR { x: cur_pt.x, y: cur_pt.y, },
GpPointR { x: cx1.x, y: cx1.y, },
GpPointR { x: cx2.x, y: cx2.y, },
GpPointR { x: pt.x, y: pt.y, }]); letmut t = Target{ stroker: self, end }; letmut f = CBezierFlattener::new(&bezier, &mut t, 0.25);
f.Flatten(false);
}
// Stroke a curve by flattening to trapezoids insteads instead of rectangles. // This lets us make sure that the normal of the stroked path matches the normal // of the curve and we also avoid having to join the segments pubfn curve_to_direct(&mutself, cx1: Point, cx2: Point, pt: Point, end: bool) { struct Target<'a, 'b> { stroker: &'a mut Stroker<'b>, _end: bool, last_normal: Vector, last_point: GpPointR} impl<'a, 'b> CFlatteningSink for Target<'a, 'b> { fn FirstTangent(&mutself, tangent: Option<GpPointR>) { let tangent = tangent.unwrap(); let last_normal = flip(Vector::new(tangent.y, -tangent.x).normalize()); let stroked_path = &mutself.stroker.stroked_path; ifself.stroker.start_point.is_some() { iflet Some(cur_pt) = self.stroker.cur_pt {
join_line(stroked_path, &self.stroker.style, cur_pt, self.stroker.last_normal, last_normal);
}
} self.last_normal = last_normal;
} fn AcceptPointAndTangent(&mutself, pt: &GpPointR, tangent: &GpPointR, _last: bool) -> HRESULT { let normal = flip(Vector::new(tangent.y, -tangent.x).normalize()); let last_normal = self.last_normal; let cur_pt = Point::new(self.last_point.x, self.last_point.y); let half_width = self.stroker.half_width; let stroked_path = &mutself.stroker.stroked_path;
ifself.stroker.start_point.is_none() { if !self.stroker.closed_subpath { // cap beginning letmut cur_pt = cur_pt; if stroked_path.aa && self.stroker.style.cap == LineCap::Butt { // adjust the starting point to make room for the cap // XXX: this will probably mess things up if the line is shorter than 1/2 pixel long
cur_pt += perp(flip(last_normal)) * 0.5;
}
cap_line(stroked_path, &self.stroker.style, cur_pt, flip(last_normal));
} // last_normal will have been set by FirstTangent self.stroker.start_point = Some((cur_pt, last_normal));
} else { // we don't need to join the segments because the quads are sharing normals
} if stroked_path.aa {
stroked_path.ramp(
pt.x + normal.x * (half_width - 0.5),
pt.y + normal.y * (half_width - 0.5),
pt.x + normal.x * (half_width + 0.5),
pt.y + normal.y * (half_width + 0.5),
cur_pt.x + last_normal.x * (half_width + 0.5),
cur_pt.y + last_normal.y * (half_width + 0.5),
cur_pt.x + last_normal.x * (half_width - 0.5),
cur_pt.y + last_normal.y * (half_width - 0.5),
);
stroked_path.quad(
cur_pt.x + last_normal.x * (half_width - 0.5),
cur_pt.y + last_normal.y * (half_width - 0.5),
pt.x + normal.x * (half_width - 0.5), pt.y + normal.y * (half_width - 0.5),
pt.x + -normal.x * (half_width - 0.5), pt.y + -normal.y * (half_width - 0.5),
cur_pt.x - last_normal.x * (half_width - 0.5),
cur_pt.y - last_normal.y * (half_width - 0.5),
);
stroked_path.ramp(
cur_pt.x - last_normal.x * (half_width - 0.5),
cur_pt.y - last_normal.y * (half_width - 0.5),
cur_pt.x - last_normal.x * (half_width + 0.5),
cur_pt.y - last_normal.y * (half_width + 0.5),
pt.x - normal.x * (half_width + 0.5),
pt.y - normal.y * (half_width + 0.5),
pt.x - normal.x * (half_width - 0.5),
pt.y - normal.y * (half_width - 0.5),
);
} else { self.stroker.stroked_path.quad(
cur_pt.x + last_normal.x * half_width,
cur_pt.y + last_normal.y * half_width,
pt.x + normal.x * half_width, pt.y + normal.y * half_width,
pt.x + -normal.x * half_width, pt.y + -normal.y * half_width,
cur_pt.x - last_normal.x * half_width,
cur_pt.y - last_normal.y * half_width,
);
} self.last_normal = normal; self.last_point = *pt; return S_OK;
}
fn distance_from_line(p1: Point, p2: Point, x: Point) -> f32 {
((p2.x - p1.x)*(p1.y - x.y) - (p1.x - x.x)*(p2.y - p1.y)).abs() /
((p2.x - p1.x).powi(2) + (p2.y - p1.y).powi(2)).sqrt()
} let start = Point::new(512., 599.); let end = Point::new(513.51666, 597.47736);
stroker.move_to(start, false);
stroker.line_to(Point::new(512.3874, 598.6111));
stroker.line_to_capped(end); let result = stroker.finish(); for v in result.iter() {
assert!(distance_from_line(start, end, Point::new(v.x, v.y)) <= 21.);
}
// from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment fn minimum_distance(v: Point, w: Point, p: Point) -> f32 { // Return minimum distance between line segment vw and point p let l2 = (v-w).length().powi(2); // i.e. |w-v|^2 - avoid a sqrt if l2 == 0.0 { return (p - v).length(); } // v == w case // Consider the line extending the segment, parameterized as v + t (w - v). // We find projection of point p onto the line. // It falls where t = [(p-v) . (w-v)] / |w-v|^2 // We clamp t from [0,1] to handle points outside the segment vw. let t = 0_f32.max(1_f32.min(dot(p - v, w - v) / l2)); let projection = v + (w - v) * t; // Projection falls on the segment
(p - projection).length()
}
letmut stroker = Stroker::new(&StrokeStyle{
cap: LineCap::Square,
join: LineJoin::Miter,
width: 40.0,
miter_limit: 10.0,
..Default::default()}); let start = Point::new(689.3504, 434.5446); let end = Point::new(671.83203, 422.61914);
stroker.move_to(Point::new(689.3504, 434.5446), false);
stroker.line_to(Point::new(681.04254, 428.8891));
stroker.line_to_capped(Point::new(671.83203, 422.61914));
let result = stroker.finish(); let max_distance = (21_f32.powi(2) * 2.).sqrt(); for v in result.iter() {
assert!(minimum_distance(start, end, Point::new(v.x, v.y)) <= max_distance);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.