Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,873 @@
// Copyright 2014 Google Inc.
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This module is a mix of SkDashPath, SkDashPathEffect, SkContourMeasure and SkPathMeasure.
use alloc::vec::Vec;
use arrayref::array_ref;
use crate::{Path, Point};
use crate::floating_point::{FiniteF32, NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive};
use crate::path::{PathSegment, PathSegmentsIter, PathVerb};
use crate::path_builder::PathBuilder;
use crate::path_geometry;
use crate::scalar::Scalar;
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
/// A stroke dashing properties.
///
/// Contains an array of pairs, where the first number indicates an "on" interval
/// and the second one indicates an "off" interval;
/// a dash offset value and internal properties.
///
/// # Guarantees
///
/// - The dash array always have an even number of values.
/// - All dash array values are finite and >= 0.
/// - There is at least two dash array values.
/// - The sum of all dash array values is positive and finite.
/// - Dash offset is finite.
#[derive(Clone, PartialEq, Debug)]
pub struct StrokeDash {
array: Vec<f32>,
offset: f32,
interval_len: NonZeroPositiveF32,
first_len: f32, // TODO: PositiveF32
first_index: usize,
}
impl StrokeDash {
/// Creates a new stroke dashing object.
pub fn new(dash_array: Vec<f32>, dash_offset: f32) -> Option<Self> {
let dash_offset = FiniteF32::new(dash_offset)?;
if dash_array.len() < 2 || dash_array.len() % 2 != 0 {
return None;
}
if dash_array.iter().any(|n| *n < 0.0) {
return None;
}
let interval_len: f32 = dash_array.iter().sum();
let interval_len = NonZeroPositiveF32::new(interval_len)?;
let dash_offset = adjust_dash_offset(dash_offset.get(), interval_len.get());
debug_assert!(dash_offset >= 0.0);
debug_assert!(dash_offset < interval_len.get());
let (first_len, first_index) = find_first_interval(&dash_array, dash_offset);
debug_assert!(first_len >= 0.0);
debug_assert!(first_index < dash_array.len());
Some(StrokeDash {
array: dash_array,
offset: dash_offset,
interval_len,
first_len,
first_index,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test() {
assert_eq!(StrokeDash::new(vec![], 0.0), None);
assert_eq!(StrokeDash::new(vec![1.0], 0.0), None);
assert_eq!(StrokeDash::new(vec![1.0, 2.0, 3.0], 0.0), None);
assert_eq!(StrokeDash::new(vec![1.0, -2.0], 0.0), None);
assert_eq!(StrokeDash::new(vec![0.0, 0.0], 0.0), None);
assert_eq!(StrokeDash::new(vec![1.0, -1.0], 0.0), None);
assert_eq!(StrokeDash::new(vec![1.0, 1.0], f32::INFINITY), None);
assert_eq!(StrokeDash::new(vec![1.0, f32::INFINITY], 0.0), None);
}
#[test]
fn bug_26() {
let mut pb = PathBuilder::new();
pb.move_to(665.54, 287.3);
pb.line_to(675.67, 273.04);
pb.line_to(675.52, 271.32);
pb.line_to(674.79, 269.61);
pb.line_to(674.05, 268.04);
pb.line_to(672.88, 266.47);
pb.line_to(671.27, 264.9);
let path = pb.finish().unwrap();
let stroke_dash = StrokeDash::new(vec![6.0, 4.5], 0.0).unwrap();
assert!(path.dash(&stroke_dash, 1.0).is_some());
}
}
// Adjust phase to be between 0 and len, "flipping" phase if negative.
// e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80.
fn adjust_dash_offset(mut offset: f32, len: f32) -> f32 {
if offset < 0.0 {
offset = -offset;
if offset > len {
offset %= len;
}
offset = len - offset;
// Due to finite precision, it's possible that phase == len,
// even after the subtract (if len >>> phase), so fix that here.
debug_assert!(offset <= len);
if offset == len {
offset = 0.0;
}
offset
} else if offset >= len {
offset % len
} else {
offset
}
}
fn find_first_interval(dash_array: &[f32], mut dash_offset: f32) -> (f32, usize) {
for (i, gap) in dash_array.iter().copied().enumerate() {
if dash_offset > gap || (dash_offset == gap && gap != 0.0) {
dash_offset -= gap;
} else {
return (gap - dash_offset, i);
}
}
// If we get here, phase "appears" to be larger than our length. This
// shouldn't happen with perfect precision, but we can accumulate errors
// during the initial length computation (rounding can make our sum be too
// big or too small. In that event, we just have to eat the error here.
(dash_array[0], 0)
}
impl Path {
/// Converts the current path into a dashed one.
///
/// `resolution_scale` can be obtained via
/// [`compute_resolution_scale`](crate::PathStroker::compute_resolution_scale).
pub fn dash(&self, dash: &StrokeDash, resolution_scale: f32) -> Option<Path> {
dash_impl(self, dash, resolution_scale)
}
}
fn dash_impl(src: &Path, dash: &StrokeDash, res_scale: f32) -> Option<Path> {
// We do not support the `cull_path` branch here.
// Skia has a lot of code for cases when a path contains only a single zero-length line
// or when a path is a rect. Not sure why.
// We simply ignoring it for the sake of simplicity.
// We also doesn't support the `SpecialLineRec` case.
// I have no idea what the point in it.
fn is_even(x: usize) -> bool {
x % 2 == 0
}
let mut pb = PathBuilder::new();
let mut dash_count = 0.0;
for contour in ContourMeasureIter::new(src, res_scale) {
let mut skip_first_segment = contour.is_closed;
let mut added_segment = false;
let length = contour.length;
let mut index = dash.first_index;
// Since the path length / dash length ratio may be arbitrarily large, we can exert
// significant memory pressure while attempting to build the filtered path. To avoid this,
// we simply give up dashing beyond a certain threshold.
//
// The original bug report (http://crbug.com/165432) is based on a path yielding more than
// 90 million dash segments and crashing the memory allocator. A limit of 1 million
// segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
// maximum dash memory overhead at roughly 17MB per path.
const MAX_DASH_COUNT: usize = 1000000;
dash_count += length * (dash.array.len() >> 1) as f32 / dash.interval_len.get();
if dash_count > MAX_DASH_COUNT as f32 {
return None;
}
// Using double precision to avoid looping indefinitely due to single precision rounding
// (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
let mut distance = 0.0;
let mut d_len = dash.first_len;
while distance < length {
debug_assert!(d_len >= 0.0);
added_segment = false;
if is_even(index) && !skip_first_segment {
added_segment = true;
contour.push_segment(distance as f32, (distance + d_len) as f32, true, &mut pb);
}
distance += d_len;
// clear this so we only respect it the first time around
skip_first_segment = false;
// wrap around our intervals array if necessary
index += 1;
debug_assert!(index <= dash.array.len());
if index == dash.array.len() {
index = 0;
}
// fetch our next d_len
d_len = dash.array[index];
}
// extend if we ended on a segment and we need to join up with the (skipped) initial segment
if contour.is_closed && is_even(dash.first_index) && dash.first_len >= 0.0 {
contour.push_segment(0.0, dash.first_len, !added_segment, &mut pb);
}
}
pb.finish()
}
const MAX_T_VALUE: u32 = 0x3FFFFFFF;
struct ContourMeasureIter<'a> {
iter: PathSegmentsIter<'a>,
tolerance: f32,
}
impl<'a> ContourMeasureIter<'a> {
fn new(path: &'a Path, res_scale: f32) -> Self {
// can't use tangents, since we need [0..1..................2] to be seen
// as definitely not a line (it is when drawn, but not parametrically)
// so we compare midpoints
const CHEAP_DIST_LIMIT: f32 = 0.5; // just made this value up
ContourMeasureIter {
iter: path.segments(),
tolerance: CHEAP_DIST_LIMIT * res_scale.invert(),
}
}
}
impl Iterator for ContourMeasureIter<'_> {
type Item = ContourMeasure;
// If it encounters a zero-length contour, it is skipped.
fn next(&mut self) -> Option<Self::Item> {
// Note:
// as we accumulate distance, we have to check that the result of +=
// actually made it larger, since a very small delta might be > 0, but
// still have no effect on distance (if distance >>> delta).
//
// We do this check below, and in compute_quad_segs and compute_cubic_segs
let mut contour = ContourMeasure::default();
let mut point_index = 0;
let mut distance = 0.0;
let mut have_seen_close = false;
let mut prev_p = Point::zero();
while let Some(seg) = self.iter.next() {
match seg {
PathSegment::MoveTo(p0) => {
contour.points.push(p0);
prev_p = p0;
}
PathSegment::LineTo(p0) => {
let prev_d = distance;
distance = contour.compute_line_seg(prev_p, p0, distance, point_index);
if distance > prev_d {
contour.points.push(p0);
point_index += 1;
}
prev_p = p0;
}
PathSegment::QuadTo(p0, p1) => {
let prev_d = distance;
distance = contour.compute_quad_segs(
prev_p,
p0,
p1,
distance,
0,
MAX_T_VALUE,
point_index,
self.tolerance,
);
if distance > prev_d {
contour.points.push(p0);
contour.points.push(p1);
point_index += 2;
}
prev_p = p1;
}
PathSegment::CubicTo(p0, p1, p2) => {
let prev_d = distance;
distance = contour.compute_cubic_segs(
prev_p,
p0,
p1,
p2,
distance,
0,
MAX_T_VALUE,
point_index,
self.tolerance,
);
if distance > prev_d {
contour.points.push(p0);
contour.points.push(p1);
contour.points.push(p2);
point_index += 3;
}
prev_p = p2;
}
PathSegment::Close => {
have_seen_close = true;
}
}
// TODO: to contour iter?
if self.iter.next_verb() == Some(PathVerb::Move) {
break;
}
}
if !distance.is_finite() {
return None;
}
if have_seen_close {
let prev_d = distance;
let first_pt = contour.points[0];
distance = contour.compute_line_seg(
contour.points[point_index],
first_pt,
distance,
point_index,
);
if distance > prev_d {
contour.points.push(first_pt);
}
}
contour.length = distance;
contour.is_closed = have_seen_close;
if contour.points.is_empty() {
None
} else {
Some(contour)
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
enum SegmentType {
Line,
Quad,
Cubic,
}
#[derive(Copy, Clone, Debug)]
struct Segment {
distance: f32, // total distance up to this point
point_index: usize, // index into the ContourMeasure::points array
t_value: u32,
kind: SegmentType,
}
impl Segment {
fn scalar_t(&self) -> f32 {
debug_assert!(self.t_value <= MAX_T_VALUE);
// 1/kMaxTValue can't be represented as a float, but it's close and the limits work fine.
const MAX_T_RECIPROCAL: f32 = 1.0 / MAX_T_VALUE as f32;
self.t_value as f32 * MAX_T_RECIPROCAL
}
}
#[derive(Default, Debug)]
struct ContourMeasure {
segments: Vec<Segment>,
points: Vec<Point>,
length: f32,
is_closed: bool,
}
impl ContourMeasure {
fn push_segment(
&self,
mut start_d: f32,
mut stop_d: f32,
start_with_move_to: bool,
pb: &mut PathBuilder,
) -> Option<()> {
if start_d < 0.0 {
start_d = 0.0;
}
if stop_d > self.length {
stop_d = self.length;
}
if !(start_d <= stop_d) {
// catch NaN values as well
return None;
}
if self.segments.is_empty() {
return None;
}
let (seg_index, mut start_t) = self.distance_to_segment(start_d)?;
let mut seg = self.segments[seg_index];
let (stop_seg_index, stop_t) = self.distance_to_segment(stop_d)?;
let stop_seg = self.segments[stop_seg_index];
debug_assert!(stop_seg_index <= stop_seg_index);
let mut p = Point::zero();
if start_with_move_to {
compute_pos_tan(
&self.points[seg.point_index..],
seg.kind,
start_t,
Some(&mut p),
None,
);
pb.move_to(p.x, p.y);
}
if seg.point_index == stop_seg.point_index {
segment_to(
&self.points[seg.point_index..],
seg.kind,
start_t,
stop_t,
pb,
);
} else {
let mut new_seg_index = seg_index;
loop {
segment_to(
&self.points[seg.point_index..],
seg.kind,
start_t,
NormalizedF32::ONE,
pb,
);
let old_point_index = seg.point_index;
loop {
new_seg_index += 1;
if self.segments[new_seg_index].point_index != old_point_index {
break;
}
}
seg = self.segments[new_seg_index];
start_t = NormalizedF32::ZERO;
if seg.point_index >= stop_seg.point_index {
break;
}
}
segment_to(
&self.points[seg.point_index..],
seg.kind,
NormalizedF32::ZERO,
stop_t,
pb,
);
}
Some(())
}
fn distance_to_segment(&self, distance: f32) -> Option<(usize, NormalizedF32)> {
debug_assert!(distance >= 0.0 && distance <= self.length);
let mut index = find_segment(&self.segments, distance);
// don't care if we hit an exact match or not, so we xor index if it is negative
index ^= index >> 31;
let index = index as usize;
let seg = self.segments[index];
// now interpolate t-values with the prev segment (if possible)
let mut start_t = 0.0;
let mut start_d = 0.0;
// check if the prev segment is legal, and references the same set of points
if index > 0 {
start_d = self.segments[index - 1].distance;
if self.segments[index - 1].point_index == seg.point_index {
debug_assert!(self.segments[index - 1].kind == seg.kind);
start_t = self.segments[index - 1].scalar_t();
}
}
debug_assert!(seg.scalar_t() > start_t);
debug_assert!(distance >= start_d);
debug_assert!(seg.distance > start_d);
let t =
start_t + (seg.scalar_t() - start_t) * (distance - start_d) / (seg.distance - start_d);
let t = NormalizedF32::new(t)?;
Some((index, t))
}
fn compute_line_seg(
&mut self,
p0: Point,
p1: Point,
mut distance: f32,
point_index: usize,
) -> f32 {
let d = p0.distance(p1);
debug_assert!(d >= 0.0);
let prev_d = distance;
distance += d;
if distance > prev_d {
debug_assert!(point_index < self.points.len());
self.segments.push(Segment {
distance,
point_index,
t_value: MAX_T_VALUE,
kind: SegmentType::Line,
});
}
distance
}
fn compute_quad_segs(
&mut self,
p0: Point,
p1: Point,
p2: Point,
mut distance: f32,
min_t: u32,
max_t: u32,
point_index: usize,
tolerance: f32,
) -> f32 {
if t_span_big_enough(max_t - min_t) != 0 && quad_too_curvy(p0, p1, p2, tolerance) {
let mut tmp = [Point::zero(); 5];
let half_t = (min_t + max_t) >> 1;
path_geometry::chop_quad_at(&[p0, p1, p2], NormalizedF32Exclusive::HALF, &mut tmp);
distance = self.compute_quad_segs(
tmp[0],
tmp[1],
tmp[2],
distance,
min_t,
half_t,
point_index,
tolerance,
);
distance = self.compute_quad_segs(
tmp[2],
tmp[3],
tmp[4],
distance,
half_t,
max_t,
point_index,
tolerance,
);
} else {
let d = p0.distance(p2);
let prev_d = distance;
distance += d;
if distance > prev_d {
debug_assert!(point_index < self.points.len());
self.segments.push(Segment {
distance,
point_index,
t_value: max_t,
kind: SegmentType::Quad,
});
}
}
distance
}
fn compute_cubic_segs(
&mut self,
p0: Point,
p1: Point,
p2: Point,
p3: Point,
mut distance: f32,
min_t: u32,
max_t: u32,
point_index: usize,
tolerance: f32,
) -> f32 {
if t_span_big_enough(max_t - min_t) != 0 && cubic_too_curvy(p0, p1, p2, p3, tolerance) {
let mut tmp = [Point::zero(); 7];
let half_t = (min_t + max_t) >> 1;
path_geometry::chop_cubic_at2(
&[p0, p1, p2, p3],
NormalizedF32Exclusive::HALF,
&mut tmp,
);
distance = self.compute_cubic_segs(
tmp[0],
tmp[1],
tmp[2],
tmp[3],
distance,
min_t,
half_t,
point_index,
tolerance,
);
distance = self.compute_cubic_segs(
tmp[3],
tmp[4],
tmp[5],
tmp[6],
distance,
half_t,
max_t,
point_index,
tolerance,
);
} else {
let d = p0.distance(p3);
let prev_d = distance;
distance += d;
if distance > prev_d {
debug_assert!(point_index < self.points.len());
self.segments.push(Segment {
distance,
point_index,
t_value: max_t,
kind: SegmentType::Cubic,
});
}
}
distance
}
}
fn find_segment(base: &[Segment], key: f32) -> i32 {
let mut lo = 0u32;
let mut hi = (base.len() - 1) as u32;
while lo < hi {
let mid = (hi + lo) >> 1;
if base[mid as usize].distance < key {
lo = mid + 1;
} else {
hi = mid;
}
}
if base[hi as usize].distance < key {
hi += 1;
hi = !hi;
} else if key < base[hi as usize].distance {
hi = !hi;
}
hi as i32
}
fn compute_pos_tan(
points: &[Point],
seg_kind: SegmentType,
t: NormalizedF32,
pos: Option<&mut Point>,
tangent: Option<&mut Point>,
) {
match seg_kind {
SegmentType::Line => {
if let Some(pos) = pos {
*pos = Point::from_xy(
interp(points[0].x, points[1].x, t),
interp(points[0].y, points[1].y, t),
);
}
if let Some(tangent) = tangent {
tangent.set_normalize(points[1].x - points[0].x, points[1].y - points[0].y);
}
}
SegmentType::Quad => {
let src = array_ref![points, 0, 3];
if let Some(pos) = pos {
*pos = path_geometry::eval_quad_at(src, t);
}
if let Some(tangent) = tangent {
*tangent = path_geometry::eval_quad_tangent_at(src, t);
tangent.normalize();
}
}
SegmentType::Cubic => {
let src = array_ref![points, 0, 4];
if let Some(pos) = pos {
*pos = path_geometry::eval_cubic_pos_at(src, t);
}
if let Some(tangent) = tangent {
*tangent = path_geometry::eval_cubic_tangent_at(src, t);
tangent.normalize();
}
}
}
}
fn segment_to(
points: &[Point],
seg_kind: SegmentType,
start_t: NormalizedF32,
stop_t: NormalizedF32,
pb: &mut PathBuilder,
) {
debug_assert!(start_t <= stop_t);
if start_t == stop_t {
if let Some(pt) = pb.last_point() {
// If the dash as a zero-length on segment, add a corresponding zero-length line.
// The stroke code will add end caps to zero length lines as appropriate.
pb.line_to(pt.x, pt.y);
}
return;
}
match seg_kind {
SegmentType::Line => {
if stop_t == NormalizedF32::ONE {
pb.line_to(points[1].x, points[1].y);
} else {
pb.line_to(
interp(points[0].x, points[1].x, stop_t),
interp(points[0].y, points[1].y, stop_t),
);
}
}
SegmentType::Quad => {
let mut tmp0 = [Point::zero(); 5];
let mut tmp1 = [Point::zero(); 5];
if start_t == NormalizedF32::ZERO {
if stop_t == NormalizedF32::ONE {
pb.quad_to_pt(points[1], points[2]);
} else {
let stop_t = NormalizedF32Exclusive::new_bounded(stop_t.get());
path_geometry::chop_quad_at(points, stop_t, &mut tmp0);
pb.quad_to_pt(tmp0[1], tmp0[2]);
}
} else {
let start_tt = NormalizedF32Exclusive::new_bounded(start_t.get());
path_geometry::chop_quad_at(points, start_tt, &mut tmp0);
if stop_t == NormalizedF32::ONE {
pb.quad_to_pt(tmp0[3], tmp0[4]);
} else {
let new_t = (stop_t.get() - start_t.get()) / (1.0 - start_t.get());
let new_t = NormalizedF32Exclusive::new_bounded(new_t);
path_geometry::chop_quad_at(&tmp0[2..], new_t, &mut tmp1);
pb.quad_to_pt(tmp1[1], tmp1[2]);
}
}
}
SegmentType::Cubic => {
let mut tmp0 = [Point::zero(); 7];
let mut tmp1 = [Point::zero(); 7];
if start_t == NormalizedF32::ZERO {
if stop_t == NormalizedF32::ONE {
pb.cubic_to_pt(points[1], points[2], points[3]);
} else {
let stop_t = NormalizedF32Exclusive::new_bounded(stop_t.get());
path_geometry::chop_cubic_at2(array_ref![points, 0, 4], stop_t, &mut tmp0);
pb.cubic_to_pt(tmp0[1], tmp0[2], tmp0[3]);
}
} else {
let start_tt = NormalizedF32Exclusive::new_bounded(start_t.get());
path_geometry::chop_cubic_at2(array_ref![points, 0, 4], start_tt, &mut tmp0);
if stop_t == NormalizedF32::ONE {
pb.cubic_to_pt(tmp0[4], tmp0[5], tmp0[6]);
} else {
let new_t = (stop_t.get() - start_t.get()) / (1.0 - start_t.get());
let new_t = NormalizedF32Exclusive::new_bounded(new_t);
path_geometry::chop_cubic_at2(array_ref![tmp0, 3, 4], new_t, &mut tmp1);
pb.cubic_to_pt(tmp1[1], tmp1[2], tmp1[3]);
}
}
}
}
}
fn t_span_big_enough(t_span: u32) -> u32 {
debug_assert!(t_span <= MAX_T_VALUE);
t_span >> 10
}
fn quad_too_curvy(p0: Point, p1: Point, p2: Point, tolerance: f32) -> bool {
// diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
// diff = -a/4 + b/2 - c/4
let dx = (p1.x).half() - (p0.x + p2.x).half().half();
let dy = (p1.y).half() - (p0.y + p2.y).half().half();
let dist = dx.abs().max(dy.abs());
dist > tolerance
}
fn cubic_too_curvy(p0: Point, p1: Point, p2: Point, p3: Point, tolerance: f32) -> bool {
let n0 = cheap_dist_exceeds_limit(
p1,
interp_safe(p0.x, p3.x, 1.0 / 3.0),
interp_safe(p0.y, p3.y, 1.0 / 3.0),
tolerance,
);
let n1 = cheap_dist_exceeds_limit(
p2,
interp_safe(p0.x, p3.x, 2.0 / 3.0),
interp_safe(p0.y, p3.y, 2.0 / 3.0),
tolerance,
);
n0 || n1
}
fn cheap_dist_exceeds_limit(pt: Point, x: f32, y: f32, tolerance: f32) -> bool {
let dist = (x - pt.x).abs().max((y - pt.y).abs());
// just made up the 1/2
dist > tolerance
}
/// Linearly interpolate between A and B, based on t.
///
/// If t is 0, return A. If t is 1, return B else interpolate.
fn interp(a: f32, b: f32, t: NormalizedF32) -> f32 {
a + (b - a) * t.get()
}
fn interp_safe(a: f32, b: f32, t: f32) -> f32 {
debug_assert!(t >= 0.0 && t <= 1.0);
a + (b - a) * t
}

View file

@ -0,0 +1,109 @@
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
// Right now, there are no visible benefits of using SIMD for f32x2. So we don't.
/// A pair of f32 numbers.
///
/// Mainly for internal use. Do not rely on it!
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Default, PartialEq, Debug)]
pub struct f32x2(pub [f32; 2]);
impl f32x2 {
/// Creates a new pair.
pub fn new(a: f32, b: f32) -> f32x2 {
f32x2([a, b])
}
/// Creates a new pair from a single value.
pub fn splat(x: f32) -> f32x2 {
f32x2([x, x])
}
/// Returns an absolute value.
pub fn abs(self) -> f32x2 {
f32x2([self.x().abs(), self.y().abs()])
}
/// Returns a minimum value.
pub fn min(self, other: f32x2) -> f32x2 {
f32x2([pmin(self.x(), other.x()), pmin(self.y(), other.y())])
}
/// Returns a maximum value.
pub fn max(self, other: f32x2) -> f32x2 {
f32x2([pmax(self.x(), other.x()), pmax(self.y(), other.y())])
}
/// Returns a maximum of both values.
pub fn max_component(self) -> f32 {
pmax(self.x(), self.y())
}
/// Returns the first value.
pub fn x(&self) -> f32 {
self.0[0]
}
/// Returns the second value.
pub fn y(&self) -> f32 {
self.0[1]
}
}
impl core::ops::Add<f32x2> for f32x2 {
type Output = f32x2;
fn add(self, other: f32x2) -> f32x2 {
f32x2([self.x() + other.x(), self.y() + other.y()])
}
}
impl core::ops::Sub<f32x2> for f32x2 {
type Output = f32x2;
fn sub(self, other: f32x2) -> f32x2 {
f32x2([self.x() - other.x(), self.y() - other.y()])
}
}
impl core::ops::Mul<f32x2> for f32x2 {
type Output = f32x2;
fn mul(self, other: f32x2) -> f32x2 {
f32x2([self.x() * other.x(), self.y() * other.y()])
}
}
impl core::ops::Div<f32x2> for f32x2 {
type Output = f32x2;
fn div(self, other: f32x2) -> f32x2 {
f32x2([self.x() / other.x(), self.y() / other.y()])
}
}
// A faster and more forgiving f32 min/max implementation.
//
// Unlike std one, we do not care about NaN.
fn pmax(a: f32, b: f32) -> f32 {
if a < b {
b
} else {
a
}
}
fn pmin(a: f32, b: f32) -> f32 {
if b < a {
b
} else {
a
}
}

View file

@ -0,0 +1,80 @@
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Right now, there are no visible benefits of using SIMD for f32x4. So we don't.
#[derive(Default, Clone, Copy, PartialEq, Debug)]
#[repr(C, align(16))]
pub struct f32x4(pub [f32; 4]);
impl f32x4 {
pub fn max(self, rhs: Self) -> Self {
Self([
self.0[0].max(rhs.0[0]),
self.0[1].max(rhs.0[1]),
self.0[2].max(rhs.0[2]),
self.0[3].max(rhs.0[3]),
])
}
pub fn min(self, rhs: Self) -> Self {
Self([
self.0[0].min(rhs.0[0]),
self.0[1].min(rhs.0[1]),
self.0[2].min(rhs.0[2]),
self.0[3].min(rhs.0[3]),
])
}
}
impl core::ops::Add for f32x4 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self([
self.0[0] + rhs.0[0],
self.0[1] + rhs.0[1],
self.0[2] + rhs.0[2],
self.0[3] + rhs.0[3],
])
}
}
impl core::ops::AddAssign for f32x4 {
fn add_assign(&mut self, rhs: f32x4) {
*self = *self + rhs;
}
}
impl core::ops::Sub for f32x4 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self([
self.0[0] - rhs.0[0],
self.0[1] - rhs.0[1],
self.0[2] - rhs.0[2],
self.0[3] - rhs.0[3],
])
}
}
impl core::ops::Mul for f32x4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self([
self.0[0] * rhs.0[0],
self.0[1] * rhs.0[1],
self.0[2] * rhs.0[2],
self.0[3] * rhs.0[3],
])
}
}
impl core::ops::MulAssign for f32x4 {
fn mul_assign(&mut self, rhs: f32x4) {
*self = *self * rhs;
}
}

View file

@ -0,0 +1,148 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::scalar::Scalar;
pub use strict_num::{FiniteF32, NonZeroPositiveF32, NormalizedF32};
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
pub(crate) const FLOAT_PI: f32 = 3.14159265;
const MAX_I32_FITS_IN_F32: f32 = 2147483520.0;
const MIN_I32_FITS_IN_F32: f32 = -MAX_I32_FITS_IN_F32;
// TODO: is there an std alternative?
/// Custom float to integer conversion routines.
pub trait SaturateCast<T>: Sized {
/// Return the closest integer for the given float.
fn saturate_from(n: T) -> Self;
}
impl SaturateCast<f32> for i32 {
/// Return the closest integer for the given float.
///
/// Returns MAX_I32_FITS_IN_F32 for NaN.
fn saturate_from(mut x: f32) -> Self {
x = if x < MAX_I32_FITS_IN_F32 {
x
} else {
MAX_I32_FITS_IN_F32
};
x = if x > MIN_I32_FITS_IN_F32 {
x
} else {
MIN_I32_FITS_IN_F32
};
x as i32
}
}
impl SaturateCast<f64> for i32 {
/// Return the closest integer for the given double.
///
/// Returns i32::MAX for NaN.
fn saturate_from(mut x: f64) -> Self {
x = if x < i32::MAX as f64 {
x
} else {
i32::MAX as f64
};
x = if x > i32::MIN as f64 {
x
} else {
i32::MIN as f64
};
x as i32
}
}
/// Custom float to integer rounding routines.
#[allow(missing_docs)]
pub trait SaturateRound<T>: SaturateCast<T> {
fn saturate_floor(n: T) -> Self;
fn saturate_ceil(n: T) -> Self;
fn saturate_round(n: T) -> Self;
}
impl SaturateRound<f32> for i32 {
fn saturate_floor(x: f32) -> Self {
Self::saturate_from(x.floor())
}
fn saturate_ceil(x: f32) -> Self {
Self::saturate_from(x.ceil())
}
fn saturate_round(x: f32) -> Self {
Self::saturate_from(x.floor() + 0.5)
}
}
/// Return the float as a 2s compliment int. Just to be used to compare floats
/// to each other or against positive float-bit-constants (like 0). This does
/// not return the int equivalent of the float, just something cheaper for
/// compares-only.
pub(crate) fn f32_as_2s_compliment(x: f32) -> i32 {
sign_bit_to_2s_compliment(bytemuck::cast(x))
}
/// Convert a sign-bit int (i.e. float interpreted as int) into a 2s compliement
/// int. This also converts -0 (0x80000000) to 0. Doing this to a float allows
/// it to be compared using normal C operators (<, <=, etc.)
fn sign_bit_to_2s_compliment(mut x: i32) -> i32 {
if x < 0 {
x &= 0x7FFFFFFF;
x = -x;
}
x
}
/// An immutable `f32` that is larger than 0 but less then 1.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Debug)]
#[repr(transparent)]
pub struct NormalizedF32Exclusive(FiniteF32);
impl NormalizedF32Exclusive {
/// Just a random, valid number.
pub const ANY: Self = Self::HALF;
/// A predefined 0.5 value.
pub const HALF: Self = NormalizedF32Exclusive(unsafe { FiniteF32::new_unchecked(0.5) });
/// Creates a `NormalizedF32Exclusive`.
pub fn new(n: f32) -> Option<Self> {
if n > 0.0 && n < 1.0 {
// `n` is guarantee to be finite after the bounds check.
FiniteF32::new(n).map(NormalizedF32Exclusive)
} else {
None
}
}
/// Creates a `NormalizedF32Exclusive` clamping the given value.
///
/// Returns zero in case of NaN or infinity.
pub fn new_bounded(n: f32) -> Self {
let n = n.bound(core::f32::EPSILON, 1.0 - core::f32::EPSILON);
// `n` is guarantee to be finite after clamping.
debug_assert!(n.is_finite());
NormalizedF32Exclusive(unsafe { FiniteF32::new_unchecked(n) })
}
/// Returns the value as a primitive type.
pub fn get(self) -> f32 {
self.0.get()
}
/// Returns the value as a `FiniteF32`.
pub fn to_normalized(self) -> NormalizedF32 {
// NormalizedF32 is (0,1), while NormalizedF32 is [0,1], so it will always fit.
unsafe { NormalizedF32::new_unchecked(self.0.get()) }
}
}

View file

@ -0,0 +1,389 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! A [tiny-skia](https://github.com/RazrFalcon/tiny-skia) Bezier path implementation.
//!
//! Provides a memory-efficient Bezier path container, path builder, path stroker and path dasher.
//!
//! Also provides some basic geometry types, but they will be moved to an external crate eventually.
//!
//! Note that all types use single precision floats (`f32`), just like [Skia](https://skia.org/).
#![no_std]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![allow(clippy::approx_constant)]
#![allow(clippy::collapsible_if)]
#![allow(clippy::eq_op)]
#![allow(clippy::excessive_precision)]
#![allow(clippy::identity_op)]
#![allow(clippy::manual_range_contains)]
#![allow(clippy::neg_cmp_op_on_partial_ord)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wrong_self_convention)]
#[cfg(not(any(feature = "std", feature = "no-std-float")))]
compile_error!("You have to activate either the `std` or the `no-std-float` feature.");
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
mod dash;
mod f32x2_t;
mod f32x4_t;
mod floating_point;
mod path;
mod path_builder;
pub mod path_geometry;
mod rect;
mod scalar;
mod stroker;
mod transform;
pub use dash::StrokeDash;
pub use f32x2_t::f32x2;
pub use floating_point::*;
pub use path::*;
pub use path_builder::*;
pub use rect::*;
pub use scalar::*;
pub use stroker::*;
pub use transform::*;
/// An integer length that is guarantee to be > 0
type LengthU32 = core::num::NonZeroU32;
/// A point.
///
/// Doesn't guarantee to be finite.
#[allow(missing_docs)]
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl From<(f32, f32)> for Point {
#[inline]
fn from(v: (f32, f32)) -> Self {
Point { x: v.0, y: v.1 }
}
}
impl Point {
/// Creates a new `Point`.
pub fn from_xy(x: f32, y: f32) -> Self {
Point { x, y }
}
/// Creates a new `Point` from `f32x2`.
pub fn from_f32x2(r: f32x2) -> Self {
Point::from_xy(r.x(), r.y())
}
/// Converts a `Point` into a `f32x2`.
pub fn to_f32x2(&self) -> f32x2 {
f32x2::new(self.x, self.y)
}
/// Creates a point at 0x0 position.
pub fn zero() -> Self {
Point { x: 0.0, y: 0.0 }
}
/// Returns true if x and y are both zero.
pub fn is_zero(&self) -> bool {
self.x == 0.0 && self.y == 0.0
}
/// Returns true if both x and y are measurable values.
///
/// Both values are other than infinities and NaN.
pub fn is_finite(&self) -> bool {
(self.x * self.y).is_finite()
}
/// Checks that two `Point`s are almost equal.
pub(crate) fn almost_equal(&self, other: Point) -> bool {
!(*self - other).can_normalize()
}
/// Checks that two `Point`s are almost equal using the specified tolerance.
pub(crate) fn equals_within_tolerance(&self, other: Point, tolerance: f32) -> bool {
(self.x - other.x).is_nearly_zero_within_tolerance(tolerance)
&& (self.y - other.y).is_nearly_zero_within_tolerance(tolerance)
}
/// Scales (fX, fY) so that length() returns one, while preserving ratio of fX to fY,
/// if possible.
///
/// If prior length is nearly zero, sets vector to (0, 0) and returns
/// false; otherwise returns true.
pub fn normalize(&mut self) -> bool {
self.set_length_from(self.x, self.y, 1.0)
}
/// Sets vector to (x, y) scaled so length() returns one, and so that (x, y)
/// is proportional to (x, y).
///
/// If (x, y) length is nearly zero, sets vector to (0, 0) and returns false;
/// otherwise returns true.
pub fn set_normalize(&mut self, x: f32, y: f32) -> bool {
self.set_length_from(x, y, 1.0)
}
pub(crate) fn can_normalize(&self) -> bool {
self.x.is_finite() && self.y.is_finite() && (self.x != 0.0 || self.y != 0.0)
}
/// Returns the Euclidean distance from origin.
pub fn length(&self) -> f32 {
let mag2 = self.x * self.x + self.y * self.y;
if mag2.is_finite() {
mag2.sqrt()
} else {
let xx = f64::from(self.x);
let yy = f64::from(self.y);
(xx * xx + yy * yy).sqrt() as f32
}
}
/// Scales vector so that distanceToOrigin() returns length, if possible.
///
/// If former length is nearly zero, sets vector to (0, 0) and return false;
/// otherwise returns true.
pub fn set_length(&mut self, length: f32) -> bool {
self.set_length_from(self.x, self.y, length)
}
/// Sets vector to (x, y) scaled to length, if possible.
///
/// If former length is nearly zero, sets vector to (0, 0) and return false;
/// otherwise returns true.
pub fn set_length_from(&mut self, x: f32, y: f32, length: f32) -> bool {
set_point_length(self, x, y, length, &mut None)
}
/// Returns the Euclidean distance from origin.
pub fn distance(&self, other: Point) -> f32 {
(*self - other).length()
}
/// Returns the dot product of two points.
pub fn dot(&self, other: Point) -> f32 {
self.x * other.x + self.y * other.y
}
/// Returns the cross product of vector and vec.
///
/// Vector and vec form three-dimensional vectors with z-axis value equal to zero.
/// The cross product is a three-dimensional vector with x-axis and y-axis values
/// equal to zero. The cross product z-axis component is returned.
pub fn cross(&self, other: Point) -> f32 {
self.x * other.y - self.y * other.x
}
pub(crate) fn distance_to_sqd(&self, pt: Point) -> f32 {
let dx = self.x - pt.x;
let dy = self.y - pt.y;
dx * dx + dy * dy
}
pub(crate) fn length_sqd(&self) -> f32 {
self.dot(*self)
}
/// Scales Point in-place by scale.
pub fn scale(&mut self, scale: f32) {
self.x *= scale;
self.y *= scale;
}
pub(crate) fn scaled(&self, scale: f32) -> Self {
Point::from_xy(self.x * scale, self.y * scale)
}
pub(crate) fn swap_coords(&mut self) {
core::mem::swap(&mut self.x, &mut self.y);
}
pub(crate) fn rotate_cw(&mut self) {
self.swap_coords();
self.x = -self.x;
}
pub(crate) fn rotate_ccw(&mut self) {
self.swap_coords();
self.y = -self.y;
}
}
// We have to worry about 2 tricky conditions:
// 1. underflow of mag2 (compared against nearlyzero^2)
// 2. overflow of mag2 (compared w/ isfinite)
//
// If we underflow, we return false. If we overflow, we compute again using
// doubles, which is much slower (3x in a desktop test) but will not overflow.
fn set_point_length(
pt: &mut Point,
mut x: f32,
mut y: f32,
length: f32,
orig_length: &mut Option<f32>,
) -> bool {
// our mag2 step overflowed to infinity, so use doubles instead.
// much slower, but needed when x or y are very large, other wise we
// divide by inf. and return (0,0) vector.
let xx = x as f64;
let yy = y as f64;
let dmag = (xx * xx + yy * yy).sqrt();
let dscale = length as f64 / dmag;
x *= dscale as f32;
y *= dscale as f32;
// check if we're not finite, or we're zero-length
if !x.is_finite() || !y.is_finite() || (x == 0.0 && y == 0.0) {
*pt = Point::zero();
return false;
}
let mut mag = 0.0;
if orig_length.is_some() {
mag = dmag as f32;
}
*pt = Point::from_xy(x, y);
if orig_length.is_some() {
*orig_length = Some(mag);
}
true
}
impl core::ops::Neg for Point {
type Output = Point;
fn neg(self) -> Self::Output {
Point {
x: -self.x,
y: -self.y,
}
}
}
impl core::ops::Add for Point {
type Output = Point;
fn add(self, other: Point) -> Self::Output {
Point::from_xy(self.x + other.x, self.y + other.y)
}
}
impl core::ops::AddAssign for Point {
fn add_assign(&mut self, other: Point) {
self.x += other.x;
self.y += other.y;
}
}
impl core::ops::Sub for Point {
type Output = Point;
fn sub(self, other: Point) -> Self::Output {
Point::from_xy(self.x - other.x, self.y - other.y)
}
}
impl core::ops::SubAssign for Point {
fn sub_assign(&mut self, other: Point) {
self.x -= other.x;
self.y -= other.y;
}
}
impl core::ops::Mul for Point {
type Output = Point;
fn mul(self, other: Point) -> Self::Output {
Point::from_xy(self.x * other.x, self.y * other.y)
}
}
impl core::ops::MulAssign for Point {
fn mul_assign(&mut self, other: Point) {
self.x *= other.x;
self.y *= other.y;
}
}
/// An integer size.
///
/// # Guarantees
///
/// - Width and height are positive and non-zero.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct IntSize {
width: LengthU32,
height: LengthU32,
}
impl IntSize {
/// Creates a new `IntSize` from width and height.
pub fn from_wh(width: u32, height: u32) -> Option<Self> {
Some(IntSize {
width: LengthU32::new(width)?,
height: LengthU32::new(height)?,
})
}
/// Returns width.
pub fn width(&self) -> u32 {
self.width.get()
}
/// Returns height.
pub fn height(&self) -> u32 {
self.height.get()
}
/// Converts the current size into a `IntRect` at a provided position.
pub fn to_int_rect(&self, x: i32, y: i32) -> IntRect {
IntRect::from_xywh(x, y, self.width.get(), self.height.get()).unwrap()
}
/// Converts the current size into a `IntRect` at a provided position.
pub fn to_screen_int_rect(&self, x: u32, y: u32) -> ScreenIntRect {
ScreenIntRect::from_xywh_safe(x, y, self.width, self.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn int_size_tests() {
assert_eq!(IntSize::from_wh(0, 0), None);
assert_eq!(IntSize::from_wh(1, 0), None);
assert_eq!(IntSize::from_wh(0, 1), None);
let size = IntSize::from_wh(3, 4).unwrap();
assert_eq!(
size.to_int_rect(1, 2),
IntRect::from_xywh(1, 2, 3, 4).unwrap()
);
assert_eq!(
size.to_screen_int_rect(1, 2),
ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
);
}
}

View file

@ -0,0 +1,288 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use alloc::vec::Vec;
use crate::path_builder::PathBuilder;
use crate::transform::Transform;
use crate::{Point, Rect};
/// A path verb.
#[allow(missing_docs)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum PathVerb {
Move,
Line,
Quad,
Cubic,
Close,
}
/// A Bezier path.
///
/// Can be created via [`PathBuilder`].
/// Where [`PathBuilder`] can be created from the [`Path`] using [`clear`] to reuse the allocation.
///
/// Path is immutable and uses compact storage, where segment types and numbers are stored
/// separately. Use can access path segments via [`Path::verbs`] and [`Path::points`],
/// or via [`Path::segments`]
///
/// # Guarantees
///
/// - Has a valid, precomputed bounds.
/// - All points are finite.
/// - Has at least two segments.
/// - Each contour starts with a MoveTo.
/// - No duplicated Move.
/// - No duplicated Close.
/// - Zero-length contours are allowed.
///
/// [`PathBuilder`]: struct.PathBuilder.html
/// [`clear`]: struct.Path.html#method.clear
#[derive(Clone, PartialEq)]
pub struct Path {
pub(crate) verbs: Vec<PathVerb>,
pub(crate) points: Vec<Point>,
pub(crate) bounds: Rect,
}
impl Path {
/// Returns the number of segments in the path.
pub fn len(&self) -> usize {
self.verbs.len()
}
/// Checks if path is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the bounds of the path's points.
///
/// The value is already calculated.
pub fn bounds(&self) -> Rect {
self.bounds
}
/// Returns an internal vector of verbs.
pub fn verbs(&self) -> &[PathVerb] {
&self.verbs
}
/// Returns an internal vector of points.
pub fn points(&self) -> &[Point] {
&self.points
}
/// Returns a transformed in-place path.
///
/// Some points may become NaN/inf therefore this method can fail.
pub fn transform(mut self, ts: Transform) -> Option<Self> {
if ts.is_identity() {
return Some(self);
}
ts.map_points(&mut self.points);
// Update bounds.
self.bounds = Rect::from_points(&self.points)?;
Some(self)
}
/// Returns an iterator over path's segments.
pub fn segments(&self) -> PathSegmentsIter {
PathSegmentsIter {
path: self,
verb_index: 0,
points_index: 0,
is_auto_close: false,
last_move_to: Point::zero(),
last_point: Point::zero(),
}
}
/// Clears the path and returns a `PathBuilder` that will reuse an allocated memory.
pub fn clear(mut self) -> PathBuilder {
self.verbs.clear();
self.points.clear();
PathBuilder {
verbs: self.verbs,
points: self.points,
last_move_to_index: 0,
move_to_required: true,
}
}
}
impl core::fmt::Debug for Path {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use core::fmt::Write;
let mut s = alloc::string::String::new();
for segment in self.segments() {
match segment {
PathSegment::MoveTo(p) => s.write_fmt(format_args!("M {} {} ", p.x, p.y))?,
PathSegment::LineTo(p) => s.write_fmt(format_args!("L {} {} ", p.x, p.y))?,
PathSegment::QuadTo(p0, p1) => {
s.write_fmt(format_args!("Q {} {} {} {} ", p0.x, p0.y, p1.x, p1.y))?
}
PathSegment::CubicTo(p0, p1, p2) => s.write_fmt(format_args!(
"C {} {} {} {} {} {} ",
p0.x, p0.y, p1.x, p1.y, p2.x, p2.y
))?,
PathSegment::Close => s.write_fmt(format_args!("Z "))?,
}
}
s.pop(); // ' '
f.debug_struct("Path")
.field("segments", &s)
.field("bounds", &self.bounds)
.finish()
}
}
/// A path segment.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PathSegment {
MoveTo(Point),
LineTo(Point),
QuadTo(Point, Point),
CubicTo(Point, Point, Point),
Close,
}
/// A path segments iterator.
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct PathSegmentsIter<'a> {
path: &'a Path,
verb_index: usize,
points_index: usize,
is_auto_close: bool,
last_move_to: Point,
last_point: Point,
}
impl<'a> PathSegmentsIter<'a> {
/// Sets the auto closing mode. Off by default.
///
/// When enabled, emits an additional `PathSegment::Line` from the current position
/// to the previous `PathSegment::Move`. And only then emits `PathSegment::Close`.
pub fn set_auto_close(&mut self, flag: bool) {
self.is_auto_close = flag;
}
pub(crate) fn auto_close(&mut self) -> PathSegment {
if self.is_auto_close && self.last_point != self.last_move_to {
self.verb_index -= 1;
PathSegment::LineTo(self.last_move_to)
} else {
PathSegment::Close
}
}
pub(crate) fn has_valid_tangent(&self) -> bool {
let mut iter = self.clone();
while let Some(segment) = iter.next() {
match segment {
PathSegment::MoveTo(_) => {
return false;
}
PathSegment::LineTo(p) => {
if iter.last_point == p {
continue;
}
return true;
}
PathSegment::QuadTo(p1, p2) => {
if iter.last_point == p1 && iter.last_point == p2 {
continue;
}
return true;
}
PathSegment::CubicTo(p1, p2, p3) => {
if iter.last_point == p1 && iter.last_point == p2 && iter.last_point == p3 {
continue;
}
return true;
}
PathSegment::Close => {
return false;
}
}
}
false
}
/// Returns the current verb.
pub fn curr_verb(&self) -> PathVerb {
self.path.verbs[self.verb_index - 1]
}
/// Returns the next verb.
pub fn next_verb(&self) -> Option<PathVerb> {
self.path.verbs.get(self.verb_index).cloned()
}
}
impl<'a> Iterator for PathSegmentsIter<'a> {
type Item = PathSegment;
fn next(&mut self) -> Option<Self::Item> {
if self.verb_index < self.path.verbs.len() {
let verb = self.path.verbs[self.verb_index];
self.verb_index += 1;
match verb {
PathVerb::Move => {
self.points_index += 1;
self.last_move_to = self.path.points[self.points_index - 1];
self.last_point = self.last_move_to;
Some(PathSegment::MoveTo(self.last_move_to))
}
PathVerb::Line => {
self.points_index += 1;
self.last_point = self.path.points[self.points_index - 1];
Some(PathSegment::LineTo(self.last_point))
}
PathVerb::Quad => {
self.points_index += 2;
self.last_point = self.path.points[self.points_index - 1];
Some(PathSegment::QuadTo(
self.path.points[self.points_index - 2],
self.last_point,
))
}
PathVerb::Cubic => {
self.points_index += 3;
self.last_point = self.path.points[self.points_index - 1];
Some(PathSegment::CubicTo(
self.path.points[self.points_index - 3],
self.path.points[self.points_index - 2],
self.last_point,
))
}
PathVerb::Close => {
let seg = self.auto_close();
self.last_point = self.last_move_to;
Some(seg)
}
}
} else {
None
}
}
}

View file

@ -0,0 +1,426 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// NOTE: this is not SkPathBuilder, but rather a reimplementation of SkPath.
use alloc::vec;
use alloc::vec::Vec;
use crate::{Path, Point, Rect};
use crate::path::PathVerb;
use crate::path_geometry;
use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2};
#[derive(Copy, Clone, PartialEq, Debug)]
pub(crate) enum PathDirection {
/// Clockwise direction for adding closed contours.
CW,
/// Counter-clockwise direction for adding closed contours.
CCW,
}
/// A path builder.
#[derive(Clone, Default, Debug)]
pub struct PathBuilder {
pub(crate) verbs: Vec<PathVerb>,
pub(crate) points: Vec<Point>,
pub(crate) last_move_to_index: usize,
pub(crate) move_to_required: bool,
}
impl PathBuilder {
/// Creates a new builder.
pub fn new() -> Self {
PathBuilder {
verbs: Vec::new(),
points: Vec::new(),
last_move_to_index: 0,
move_to_required: true,
}
}
/// Creates a new builder with a specified capacity.
///
/// Number of points depends on a verb type:
///
/// - Move - 1
/// - Line - 1
/// - Quad - 2
/// - Cubic - 3
/// - Close - 0
pub fn with_capacity(verbs_capacity: usize, points_capacity: usize) -> Self {
PathBuilder {
verbs: Vec::with_capacity(verbs_capacity),
points: Vec::with_capacity(points_capacity),
last_move_to_index: 0,
move_to_required: true,
}
}
/// Creates a new `Path` from `Rect`.
///
/// Never fails since `Rect` is always valid.
///
/// Segments are created clockwise: TopLeft -> TopRight -> BottomRight -> BottomLeft
///
/// The contour is closed.
pub fn from_rect(rect: Rect) -> Path {
let verbs = vec![
PathVerb::Move,
PathVerb::Line,
PathVerb::Line,
PathVerb::Line,
PathVerb::Close,
];
let points = vec![
Point::from_xy(rect.left(), rect.top()),
Point::from_xy(rect.right(), rect.top()),
Point::from_xy(rect.right(), rect.bottom()),
Point::from_xy(rect.left(), rect.bottom()),
];
Path {
bounds: rect,
verbs,
points,
}
}
/// Creates a new `Path` from a circle.
///
/// See [`PathBuilder::push_circle`] for details.
pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option<Path> {
let mut b = PathBuilder::new();
b.push_circle(cx, cy, radius);
b.finish()
}
/// Creates a new `Path` from an oval.
///
/// See [`PathBuilder::push_oval`] for details.
pub fn from_oval(oval: Rect) -> Option<Path> {
let mut b = PathBuilder::new();
b.push_oval(oval);
b.finish()
}
pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) {
self.verbs.reserve(additional_verbs);
self.points.reserve(additional_points);
}
/// Returns the current number of segments in the builder.
pub fn len(&self) -> usize {
self.verbs.len()
}
/// Checks if the builder has any segments added.
pub fn is_empty(&self) -> bool {
self.verbs.is_empty()
}
/// Adds beginning of a contour.
///
/// Multiple continuous MoveTo segments are not allowed.
/// If the previous segment was also MoveTo, it will be overwritten with the current one.
pub fn move_to(&mut self, x: f32, y: f32) {
if let Some(PathVerb::Move) = self.verbs.last() {
let last_idx = self.points.len() - 1;
self.points[last_idx] = Point::from_xy(x, y);
} else {
self.last_move_to_index = self.points.len();
self.move_to_required = false;
self.verbs.push(PathVerb::Move);
self.points.push(Point::from_xy(x, y));
}
}
fn inject_move_to_if_needed(&mut self) {
if self.move_to_required {
match self.points.get(self.last_move_to_index).cloned() {
Some(p) => self.move_to(p.x, p.y),
None => self.move_to(0.0, 0.0),
}
}
}
/// Adds a line from the last point.
///
/// - If `Path` is empty - adds Move(0, 0) first.
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
pub fn line_to(&mut self, x: f32, y: f32) {
self.inject_move_to_if_needed();
self.verbs.push(PathVerb::Line);
self.points.push(Point::from_xy(x, y));
}
/// Adds a quad curve from the last point to `x`, `y`.
///
/// - If `Path` is empty - adds Move(0, 0) first.
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
pub fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.inject_move_to_if_needed();
self.verbs.push(PathVerb::Quad);
self.points.push(Point::from_xy(x1, y1));
self.points.push(Point::from_xy(x, y));
}
pub(crate) fn quad_to_pt(&mut self, p1: Point, p: Point) {
self.quad_to(p1.x, p1.y, p.x, p.y);
}
// We do not support conic segments, but Skia still relies on them from time to time.
// This method will simply convert the input data into quad segments.
pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) {
// check for <= 0 or NaN with this test
if !(weight > 0.0) {
self.line_to(x, y);
} else if !weight.is_finite() {
self.line_to(x1, y1);
self.line_to(x, y);
} else if weight == 1.0 {
self.quad_to(x1, y1, x, y);
} else {
self.inject_move_to_if_needed();
let last = self.last_point().unwrap();
let quadder = path_geometry::AutoConicToQuads::compute(
last,
Point::from_xy(x1, y1),
Point::from_xy(x, y),
weight,
);
if let Some(quadder) = quadder {
// Points are ordered as: 0 - 1 2 - 3 4 - 5 6 - ..
// `count` is a number of pairs +1
let mut offset = 1;
for _ in 0..quadder.len {
let pt1 = quadder.points[offset + 0];
let pt2 = quadder.points[offset + 1];
self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
offset += 2;
}
}
}
}
pub(crate) fn conic_points_to(&mut self, pt1: Point, pt2: Point, weight: f32) {
self.conic_to(pt1.x, pt1.y, pt2.x, pt2.y, weight);
}
/// Adds a cubic curve from the last point to `x`, `y`.
///
/// - If `Path` is empty - adds Move(0, 0) first.
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.inject_move_to_if_needed();
self.verbs.push(PathVerb::Cubic);
self.points.push(Point::from_xy(x1, y1));
self.points.push(Point::from_xy(x2, y2));
self.points.push(Point::from_xy(x, y));
}
pub(crate) fn cubic_to_pt(&mut self, p1: Point, p2: Point, p: Point) {
self.cubic_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y);
}
/// Closes the current contour.
///
/// A closed contour connects the first and the last Point
/// with a line, forming a continuous loop.
///
/// Does nothing when `Path` is empty or already closed.
///
/// Open and closed contour will be filled the same way.
/// Stroking an open contour will add LineCap at contour's start and end.
/// Stroking an closed contour will add LineJoin at contour's start and end.
pub fn close(&mut self) {
// don't add a close if it's the first verb or a repeat
if !self.verbs.is_empty() {
if self.verbs.last().cloned() != Some(PathVerb::Close) {
self.verbs.push(PathVerb::Close);
}
}
self.move_to_required = true;
}
/// Returns the last point if any.
pub fn last_point(&self) -> Option<Point> {
self.points.last().cloned()
}
pub(crate) fn set_last_point(&mut self, pt: Point) {
match self.points.last_mut() {
Some(last) => *last = pt,
None => self.move_to(pt.x, pt.y),
}
}
pub(crate) fn is_zero_length_since_point(&self, start_pt_index: usize) -> bool {
let count = self.points.len() - start_pt_index;
if count < 2 {
return true;
}
let first = self.points[start_pt_index];
for i in 1..count {
if first != self.points[start_pt_index + i] {
return false;
}
}
true
}
/// Adds a rectangle contour.
///
/// The contour is closed and has a clock-wise direction.
///
/// Does nothing when:
/// - any value is not finite or really large
pub fn push_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
if let Some(rect) = Rect::from_xywh(x, y, w, h) {
self.move_to(rect.left(), rect.top());
self.line_to(rect.right(), rect.top());
self.line_to(rect.right(), rect.bottom());
self.line_to(rect.left(), rect.bottom());
self.close();
}
}
/// Adds an oval contour bounded by the provided rectangle.
///
/// The contour is closed and has a clock-wise direction.
pub fn push_oval(&mut self, oval: Rect) {
let cx = oval.left().half() + oval.right().half();
let cy = oval.top().half() + oval.bottom().half();
let oval_points = [
Point::from_xy(cx, oval.bottom()),
Point::from_xy(oval.left(), cy),
Point::from_xy(cx, oval.top()),
Point::from_xy(oval.right(), cy),
];
let rect_points = [
Point::from_xy(oval.right(), oval.bottom()),
Point::from_xy(oval.left(), oval.bottom()),
Point::from_xy(oval.left(), oval.top()),
Point::from_xy(oval.right(), oval.top()),
];
let weight = SCALAR_ROOT_2_OVER_2;
self.move_to(oval_points[3].x, oval_points[3].y);
for (p1, p2) in rect_points.iter().zip(oval_points.iter()) {
self.conic_points_to(*p1, *p2, weight);
}
self.close();
}
/// Adds a circle contour.
///
/// The contour is closed and has a clock-wise direction.
///
/// Does nothing when:
/// - `radius` <= 0
/// - any value is not finite or really large
pub fn push_circle(&mut self, x: f32, y: f32, r: f32) {
if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) {
self.push_oval(r);
}
}
pub(crate) fn push_path(&mut self, other: &PathBuilder) {
if other.is_empty() {
return;
}
if self.last_move_to_index != 0 {
self.last_move_to_index = self.points.len() + other.last_move_to_index;
}
self.verbs.extend_from_slice(&other.verbs);
self.points.extend_from_slice(&other.points);
}
/// Appends, in a reverse order, the first contour of path ignoring path's last point.
pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) {
if other.is_empty() {
return;
}
debug_assert_eq!(other.verbs[0], PathVerb::Move);
let mut points_offset = other.points.len() - 1;
for verb in other.verbs.iter().rev() {
match verb {
PathVerb::Move => {
// if the path has multiple contours, stop after reversing the last
break;
}
PathVerb::Line => {
// We're moving one point back manually, to prevent points_offset overflow.
let pt = other.points[points_offset - 1];
points_offset -= 1;
self.line_to(pt.x, pt.y);
}
PathVerb::Quad => {
let pt1 = other.points[points_offset - 1];
let pt2 = other.points[points_offset - 2];
points_offset -= 2;
self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
}
PathVerb::Cubic => {
let pt1 = other.points[points_offset - 1];
let pt2 = other.points[points_offset - 2];
let pt3 = other.points[points_offset - 3];
points_offset -= 3;
self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
}
PathVerb::Close => {}
}
}
}
/// Reset the builder.
///
/// Memory is not deallocated.
pub fn clear(&mut self) {
self.verbs.clear();
self.points.clear();
self.last_move_to_index = 0;
self.move_to_required = true;
}
/// Finishes the builder and returns a `Path`.
///
/// Returns `None` when `Path` is empty or has invalid bounds.
pub fn finish(self) -> Option<Path> {
if self.is_empty() {
return None;
}
// Just a move to? Bail.
if self.verbs.len() == 1 {
return None;
}
let bounds = Rect::from_points(&self.points)?;
Some(Path {
bounds,
verbs: self.verbs,
points: self.points,
})
}
}

View file

@ -0,0 +1,901 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! A collection of functions to work with Bezier paths.
//!
//! Mainly for internal use. Do not rely on it!
#![allow(missing_docs)]
use crate::{Point, Transform};
use crate::f32x2_t::f32x2;
use crate::floating_point::FLOAT_PI;
use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};
use crate::floating_point::{NormalizedF32, NormalizedF32Exclusive};
use crate::path_builder::PathDirection;
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
// use for : eval(t) == A * t^2 + B * t + C
#[derive(Clone, Copy, Default, Debug)]
pub struct QuadCoeff {
pub a: f32x2,
pub b: f32x2,
pub c: f32x2,
}
impl QuadCoeff {
pub fn from_points(points: &[Point; 3]) -> Self {
let c = points[0].to_f32x2();
let p1 = points[1].to_f32x2();
let p2 = points[2].to_f32x2();
let b = times_2(p1 - c);
let a = p2 - times_2(p1) + c;
QuadCoeff { a, b, c }
}
pub fn eval(&self, t: f32x2) -> f32x2 {
(self.a * t + self.b) * t + self.c
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct CubicCoeff {
pub a: f32x2,
pub b: f32x2,
pub c: f32x2,
pub d: f32x2,
}
impl CubicCoeff {
pub fn from_points(points: &[Point; 4]) -> Self {
let p0 = points[0].to_f32x2();
let p1 = points[1].to_f32x2();
let p2 = points[2].to_f32x2();
let p3 = points[3].to_f32x2();
let three = f32x2::splat(3.0);
CubicCoeff {
a: p3 + three * (p1 - p2) - p0,
b: three * (p2 - times_2(p1) + p0),
c: three * (p1 - p0),
d: p0,
}
}
pub fn eval(&self, t: f32x2) -> f32x2 {
((self.a * t + self.b) * t + self.c) * t + self.d
}
}
// TODO: to a custom type?
pub fn new_t_values() -> [NormalizedF32Exclusive; 3] {
[
NormalizedF32Exclusive::ANY,
NormalizedF32Exclusive::ANY,
NormalizedF32Exclusive::ANY,
]
}
pub fn chop_quad_at(src: &[Point], t: NormalizedF32Exclusive, dst: &mut [Point; 5]) {
let p0 = src[0].to_f32x2();
let p1 = src[1].to_f32x2();
let p2 = src[2].to_f32x2();
let tt = f32x2::splat(t.get());
let p01 = interp(p0, p1, tt);
let p12 = interp(p1, p2, tt);
dst[0] = Point::from_f32x2(p0);
dst[1] = Point::from_f32x2(p01);
dst[2] = Point::from_f32x2(interp(p01, p12, tt));
dst[3] = Point::from_f32x2(p12);
dst[4] = Point::from_f32x2(p2);
}
// From Numerical Recipes in C.
//
// Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C])
// x1 = Q / A
// x2 = C / Q
pub fn find_unit_quad_roots(
a: f32,
b: f32,
c: f32,
roots: &mut [NormalizedF32Exclusive; 3],
) -> usize {
if a == 0.0 {
if let Some(r) = valid_unit_divide(-c, b) {
roots[0] = r;
return 1;
} else {
return 0;
}
}
// use doubles so we don't overflow temporarily trying to compute R
let mut dr = f64::from(b) * f64::from(b) - 4.0 * f64::from(a) * f64::from(c);
if dr < 0.0 {
return 0;
}
dr = dr.sqrt();
let r = dr as f32;
if !r.is_finite() {
return 0;
}
let q = if b < 0.0 {
-(b - r) / 2.0
} else {
-(b + r) / 2.0
};
let mut roots_offset = 0;
if let Some(r) = valid_unit_divide(q, a) {
roots[roots_offset] = r;
roots_offset += 1;
}
if let Some(r) = valid_unit_divide(c, q) {
roots[roots_offset] = r;
roots_offset += 1;
}
if roots_offset == 2 {
if roots[0].get() > roots[1].get() {
roots.swap(0, 1);
} else if roots[0] == roots[1] {
// nearly-equal?
roots_offset -= 1; // skip the double root
}
}
roots_offset
}
pub fn chop_cubic_at2(src: &[Point; 4], t: NormalizedF32Exclusive, dst: &mut [Point]) {
let p0 = src[0].to_f32x2();
let p1 = src[1].to_f32x2();
let p2 = src[2].to_f32x2();
let p3 = src[3].to_f32x2();
let tt = f32x2::splat(t.get());
let ab = interp(p0, p1, tt);
let bc = interp(p1, p2, tt);
let cd = interp(p2, p3, tt);
let abc = interp(ab, bc, tt);
let bcd = interp(bc, cd, tt);
let abcd = interp(abc, bcd, tt);
dst[0] = Point::from_f32x2(p0);
dst[1] = Point::from_f32x2(ab);
dst[2] = Point::from_f32x2(abc);
dst[3] = Point::from_f32x2(abcd);
dst[4] = Point::from_f32x2(bcd);
dst[5] = Point::from_f32x2(cd);
dst[6] = Point::from_f32x2(p3);
}
pub fn valid_unit_divide(mut numer: f32, mut denom: f32) -> Option<NormalizedF32Exclusive> {
if numer < 0.0 {
numer = -numer;
denom = -denom;
}
if denom == 0.0 || numer == 0.0 || numer >= denom {
return None;
}
let r = numer / denom;
NormalizedF32Exclusive::new(r)
}
fn interp(v0: f32x2, v1: f32x2, t: f32x2) -> f32x2 {
v0 + (v1 - v0) * t
}
fn times_2(value: f32x2) -> f32x2 {
value + value
}
// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2
// F'(t) = 2 (b - a) + 2 (a - 2b + c) t
// F''(t) = 2 (a - 2b + c)
//
// A = 2 (b - a)
// B = 2 (a - 2b + c)
//
// Maximum curvature for a quadratic means solving
// Fx' Fx'' + Fy' Fy'' = 0
//
// t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2)
pub(crate) fn find_quad_max_curvature(src: &[Point; 3]) -> NormalizedF32 {
let ax = src[1].x - src[0].x;
let ay = src[1].y - src[0].y;
let bx = src[0].x - src[1].x - src[1].x + src[2].x;
let by = src[0].y - src[1].y - src[1].y + src[2].y;
let mut numer = -(ax * bx + ay * by);
let mut denom = bx * bx + by * by;
if denom < 0.0 {
numer = -numer;
denom = -denom;
}
if numer <= 0.0 {
return NormalizedF32::ZERO;
}
if numer >= denom {
// Also catches denom=0
return NormalizedF32::ONE;
}
let t = numer / denom;
NormalizedF32::new(t).unwrap()
}
pub(crate) fn eval_quad_at(src: &[Point; 3], t: NormalizedF32) -> Point {
Point::from_f32x2(QuadCoeff::from_points(src).eval(f32x2::splat(t.get())))
}
pub(crate) fn eval_quad_tangent_at(src: &[Point; 3], tol: NormalizedF32) -> Point {
// The derivative equation is 2(b - a +(a - 2b +c)t). This returns a
// zero tangent vector when t is 0 or 1, and the control point is equal
// to the end point. In this case, use the quad end points to compute the tangent.
if (tol == NormalizedF32::ZERO && src[0] == src[1])
|| (tol == NormalizedF32::ONE && src[1] == src[2])
{
return src[2] - src[0];
}
let p0 = src[0].to_f32x2();
let p1 = src[1].to_f32x2();
let p2 = src[2].to_f32x2();
let b = p1 - p0;
let a = p2 - p1 - b;
let t = a * f32x2::splat(tol.get()) + b;
Point::from_f32x2(t + t)
}
// Looking for F' dot F'' == 0
//
// A = b - a
// B = c - 2b + a
// C = d - 3c + 3b - a
//
// F' = 3Ct^2 + 6Bt + 3A
// F'' = 6Ct + 6B
//
// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
pub fn find_cubic_max_curvature<'a>(
src: &[Point; 4],
t_values: &'a mut [NormalizedF32; 3],
) -> &'a [NormalizedF32] {
let mut coeff_x = formulate_f1_dot_f2(&[src[0].x, src[1].x, src[2].x, src[3].x]);
let coeff_y = formulate_f1_dot_f2(&[src[0].y, src[1].y, src[2].y, src[3].y]);
for i in 0..4 {
coeff_x[i] += coeff_y[i];
}
let len = solve_cubic_poly(&coeff_x, t_values);
&t_values[0..len]
}
// Looking for F' dot F'' == 0
//
// A = b - a
// B = c - 2b + a
// C = d - 3c + 3b - a
//
// F' = 3Ct^2 + 6Bt + 3A
// F'' = 6Ct + 6B
//
// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
fn formulate_f1_dot_f2(src: &[f32; 4]) -> [f32; 4] {
let a = src[1] - src[0];
let b = src[2] - 2.0 * src[1] + src[0];
let c = src[3] + 3.0 * (src[1] - src[2]) - src[0];
[c * c, 3.0 * b * c, 2.0 * b * b + c * a, a * b]
}
/// Solve coeff(t) == 0, returning the number of roots that lie withing 0 < t < 1.
/// coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3]
///
/// Eliminates repeated roots (so that all t_values are distinct, and are always
/// in increasing order.
fn solve_cubic_poly(coeff: &[f32; 4], t_values: &mut [NormalizedF32; 3]) -> usize {
if coeff[0].is_nearly_zero() {
// we're just a quadratic
let mut tmp_t = new_t_values();
let count = find_unit_quad_roots(coeff[1], coeff[2], coeff[3], &mut tmp_t);
for i in 0..count {
t_values[i] = tmp_t[i].to_normalized();
}
return count;
}
debug_assert!(coeff[0] != 0.0);
let inva = coeff[0].invert();
let a = coeff[1] * inva;
let b = coeff[2] * inva;
let c = coeff[3] * inva;
let q = (a * a - b * 3.0) / 9.0;
let r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
let q3 = q * q * q;
let r2_minus_q3 = r * r - q3;
let adiv3 = a / 3.0;
if r2_minus_q3 < 0.0 {
// we have 3 real roots
// the divide/root can, due to finite precisions, be slightly outside of -1...1
let theta = (r / q3.sqrt()).bound(-1.0, 1.0).acos();
let neg2_root_q = -2.0 * q.sqrt();
t_values[0] = NormalizedF32::new_clamped(neg2_root_q * (theta / 3.0).cos() - adiv3);
t_values[1] = NormalizedF32::new_clamped(
neg2_root_q * ((theta + 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
);
t_values[2] = NormalizedF32::new_clamped(
neg2_root_q * ((theta - 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
);
// now sort the roots
sort_array3(t_values);
collapse_duplicates3(t_values)
} else {
// we have 1 real root
let mut a = r.abs() + r2_minus_q3.sqrt();
a = scalar_cube_root(a);
if r > 0.0 {
a = -a;
}
if a != 0.0 {
a += q / a;
}
t_values[0] = NormalizedF32::new_clamped(a - adiv3);
1
}
}
fn sort_array3(array: &mut [NormalizedF32; 3]) {
if array[0] > array[1] {
array.swap(0, 1);
}
if array[1] > array[2] {
array.swap(1, 2);
}
if array[0] > array[1] {
array.swap(0, 1);
}
}
fn collapse_duplicates3(array: &mut [NormalizedF32; 3]) -> usize {
let mut len = 3;
if array[1] == array[2] {
len = 2;
}
if array[0] == array[1] {
len = 1;
}
len
}
fn scalar_cube_root(x: f32) -> f32 {
x.powf(0.3333333)
}
// This is SkEvalCubicAt split into three functions.
pub(crate) fn eval_cubic_pos_at(src: &[Point; 4], t: NormalizedF32) -> Point {
Point::from_f32x2(CubicCoeff::from_points(src).eval(f32x2::splat(t.get())))
}
// This is SkEvalCubicAt split into three functions.
pub(crate) fn eval_cubic_tangent_at(src: &[Point; 4], t: NormalizedF32) -> Point {
// The derivative equation returns a zero tangent vector when t is 0 or 1, and the
// adjacent control point is equal to the end point. In this case, use the
// next control point or the end points to compute the tangent.
if (t.get() == 0.0 && src[0] == src[1]) || (t.get() == 1.0 && src[2] == src[3]) {
let mut tangent = if t.get() == 0.0 {
src[2] - src[0]
} else {
src[3] - src[1]
};
if tangent.x == 0.0 && tangent.y == 0.0 {
tangent = src[3] - src[0];
}
tangent
} else {
eval_cubic_derivative(src, t)
}
}
fn eval_cubic_derivative(src: &[Point; 4], t: NormalizedF32) -> Point {
let p0 = src[0].to_f32x2();
let p1 = src[1].to_f32x2();
let p2 = src[2].to_f32x2();
let p3 = src[3].to_f32x2();
let coeff = QuadCoeff {
a: p3 + f32x2::splat(3.0) * (p1 - p2) - p0,
b: times_2(p2 - times_2(p1) + p0),
c: p1 - p0,
};
Point::from_f32x2(coeff.eval(f32x2::splat(t.get())))
}
// http://www.faculty.idc.ac.il/arik/quality/appendixA.html
//
// Inflection means that curvature is zero.
// Curvature is [F' x F''] / [F'^3]
// So we solve F'x X F''y - F'y X F''y == 0
// After some canceling of the cubic term, we get
// A = b - a
// B = c - 2b + a
// C = d - 3c + 3b - a
// (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0
pub(crate) fn find_cubic_inflections<'a>(
src: &[Point; 4],
t_values: &'a mut [NormalizedF32Exclusive; 3],
) -> &'a [NormalizedF32Exclusive] {
let ax = src[1].x - src[0].x;
let ay = src[1].y - src[0].y;
let bx = src[2].x - 2.0 * src[1].x + src[0].x;
let by = src[2].y - 2.0 * src[1].y + src[0].y;
let cx = src[3].x + 3.0 * (src[1].x - src[2].x) - src[0].x;
let cy = src[3].y + 3.0 * (src[1].y - src[2].y) - src[0].y;
let len = find_unit_quad_roots(
bx * cy - by * cx,
ax * cy - ay * cx,
ax * by - ay * bx,
t_values,
);
&t_values[0..len]
}
// Return location (in t) of cubic cusp, if there is one.
// Note that classify cubic code does not reliably return all cusp'd cubics, so
// it is not called here.
pub(crate) fn find_cubic_cusp(src: &[Point; 4]) -> Option<NormalizedF32Exclusive> {
// When the adjacent control point matches the end point, it behaves as if
// the cubic has a cusp: there's a point of max curvature where the derivative
// goes to zero. Ideally, this would be where t is zero or one, but math
// error makes not so. It is not uncommon to create cubics this way; skip them.
if src[0] == src[1] {
return None;
}
if src[2] == src[3] {
return None;
}
// Cubics only have a cusp if the line segments formed by the control and end points cross.
// Detect crossing if line ends are on opposite sides of plane formed by the other line.
if on_same_side(src, 0, 2) || on_same_side(src, 2, 0) {
return None;
}
// Cubics may have multiple points of maximum curvature, although at most only
// one is a cusp.
let mut t_values = [NormalizedF32::ZERO; 3];
let max_curvature = find_cubic_max_curvature(src, &mut t_values);
for test_t in max_curvature {
if 0.0 >= test_t.get() || test_t.get() >= 1.0 {
// no need to consider max curvature on the end
continue;
}
// A cusp is at the max curvature, and also has a derivative close to zero.
// Choose the 'close to zero' meaning by comparing the derivative length
// with the overall cubic size.
let d_pt = eval_cubic_derivative(src, *test_t);
let d_pt_magnitude = d_pt.length_sqd();
let precision = calc_cubic_precision(src);
if d_pt_magnitude < precision {
// All three max curvature t values may be close to the cusp;
// return the first one.
return Some(NormalizedF32Exclusive::new_bounded(test_t.get()));
}
}
None
}
// Returns true if both points src[testIndex], src[testIndex+1] are in the same half plane defined
// by the line segment src[lineIndex], src[lineIndex+1].
fn on_same_side(src: &[Point; 4], test_index: usize, line_index: usize) -> bool {
let origin = src[line_index];
let line = src[line_index + 1] - origin;
let mut crosses = [0.0, 0.0];
for index in 0..2 {
let test_line = src[test_index + index] - origin;
crosses[index] = line.cross(test_line);
}
crosses[0] * crosses[1] >= 0.0
}
// Returns a constant proportional to the dimensions of the cubic.
// Constant found through experimentation -- maybe there's a better way....
fn calc_cubic_precision(src: &[Point; 4]) -> f32 {
(src[1].distance_to_sqd(src[0])
+ src[2].distance_to_sqd(src[1])
+ src[3].distance_to_sqd(src[2]))
* 1e-8
}
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct Conic {
pub points: [Point; 3],
pub weight: f32,
}
impl Conic {
pub fn new(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Self {
Conic {
points: [pt0, pt1, pt2],
weight,
}
}
pub fn from_points(points: &[Point], weight: f32) -> Self {
Conic {
points: [points[0], points[1], points[2]],
weight,
}
}
fn compute_quad_pow2(&self, tolerance: f32) -> Option<u8> {
if tolerance < 0.0 || !tolerance.is_finite() {
return None;
}
if !self.points[0].is_finite() || !self.points[1].is_finite() || !self.points[2].is_finite()
{
return None;
}
// Limit the number of suggested quads to approximate a conic
const MAX_CONIC_TO_QUAD_POW2: usize = 4;
// "High order approximation of conic sections by quadratic splines"
// by Michael Floater, 1993
let a = self.weight - 1.0;
let k = a / (4.0 * (2.0 + a));
let x = k * (self.points[0].x - 2.0 * self.points[1].x + self.points[2].x);
let y = k * (self.points[0].y - 2.0 * self.points[1].y + self.points[2].y);
let mut error = (x * x + y * y).sqrt();
let mut pow2 = 0;
for _ in 0..MAX_CONIC_TO_QUAD_POW2 {
if error <= tolerance {
break;
}
error *= 0.25;
pow2 += 1;
}
// Unlike Skia, we always expect `pow2` to be at least 1.
// Otherwise it produces ugly results.
Some(pow2.max(1))
}
// Chop this conic into N quads, stored continuously in pts[], where
// N = 1 << pow2. The amount of storage needed is (1 + 2 * N)
pub fn chop_into_quads_pow2(&self, pow2: u8, points: &mut [Point]) -> u8 {
debug_assert!(pow2 < 5);
points[0] = self.points[0];
subdivide(self, &mut points[1..], pow2);
let quad_count = 1 << pow2;
let pt_count = 2 * quad_count + 1;
if points.iter().take(pt_count).any(|n| !n.is_finite()) {
// if we generated a non-finite, pin ourselves to the middle of the hull,
// as our first and last are already on the first/last pts of the hull.
for p in points.iter_mut().take(pt_count - 1).skip(1) {
*p = self.points[1];
}
}
1 << pow2
}
fn chop(&self) -> (Conic, Conic) {
let scale = f32x2::splat((1.0 + self.weight).invert());
let new_w = subdivide_weight_value(self.weight);
let p0 = self.points[0].to_f32x2();
let p1 = self.points[1].to_f32x2();
let p2 = self.points[2].to_f32x2();
let ww = f32x2::splat(self.weight);
let wp1 = ww * p1;
let m = (p0 + times_2(wp1) + p2) * scale * f32x2::splat(0.5);
let mut m_pt = Point::from_f32x2(m);
if !m_pt.is_finite() {
let w_d = self.weight as f64;
let w_2 = w_d * 2.0;
let scale_half = 1.0 / (1.0 + w_d) * 0.5;
m_pt.x = ((self.points[0].x as f64
+ w_2 * self.points[1].x as f64
+ self.points[2].x as f64)
* scale_half) as f32;
m_pt.y = ((self.points[0].y as f64
+ w_2 * self.points[1].y as f64
+ self.points[2].y as f64)
* scale_half) as f32;
}
(
Conic {
points: [self.points[0], Point::from_f32x2((p0 + wp1) * scale), m_pt],
weight: new_w,
},
Conic {
points: [m_pt, Point::from_f32x2((wp1 + p2) * scale), self.points[2]],
weight: new_w,
},
)
}
pub fn build_unit_arc(
u_start: Point,
u_stop: Point,
dir: PathDirection,
user_transform: Transform,
dst: &mut [Conic; 5],
) -> Option<&[Conic]> {
// rotate by x,y so that u_start is (1.0)
let x = u_start.dot(u_stop);
let mut y = u_start.cross(u_stop);
let abs_y = y.abs();
// check for (effectively) coincident vectors
// this can happen if our angle is nearly 0 or nearly 180 (y == 0)
// ... we use the dot-prod to distinguish between 0 and 180 (x > 0)
if abs_y <= SCALAR_NEARLY_ZERO
&& x > 0.0
&& ((y >= 0.0 && dir == PathDirection::CW) || (y <= 0.0 && dir == PathDirection::CCW))
{
return None;
}
if dir == PathDirection::CCW {
y = -y;
}
// We decide to use 1-conic per quadrant of a circle. What quadrant does [xy] lie in?
// 0 == [0 .. 90)
// 1 == [90 ..180)
// 2 == [180..270)
// 3 == [270..360)
//
let mut quadrant = 0;
if y == 0.0 {
quadrant = 2; // 180
debug_assert!((x + 1.0) <= SCALAR_NEARLY_ZERO);
} else if x == 0.0 {
debug_assert!(abs_y - 1.0 <= SCALAR_NEARLY_ZERO);
quadrant = if y > 0.0 { 1 } else { 3 }; // 90 / 270
} else {
if y < 0.0 {
quadrant += 2;
}
if (x < 0.0) != (y < 0.0) {
quadrant += 1;
}
}
let quadrant_points = [
Point::from_xy(1.0, 0.0),
Point::from_xy(1.0, 1.0),
Point::from_xy(0.0, 1.0),
Point::from_xy(-1.0, 1.0),
Point::from_xy(-1.0, 0.0),
Point::from_xy(-1.0, -1.0),
Point::from_xy(0.0, -1.0),
Point::from_xy(1.0, -1.0),
];
const QUADRANT_WEIGHT: f32 = SCALAR_ROOT_2_OVER_2;
let mut conic_count = quadrant;
for i in 0..conic_count {
dst[i] = Conic::from_points(&quadrant_points[i * 2..], QUADRANT_WEIGHT);
}
// Now compute any remaing (sub-90-degree) arc for the last conic
let final_pt = Point::from_xy(x, y);
let last_q = quadrant_points[quadrant * 2]; // will already be a unit-vector
let dot = last_q.dot(final_pt);
debug_assert!(0.0 <= dot && dot <= 1.0 + SCALAR_NEARLY_ZERO);
if dot < 1.0 {
let mut off_curve = Point::from_xy(last_q.x + x, last_q.y + y);
// compute the bisector vector, and then rescale to be the off-curve point.
// we compute its length from cos(theta/2) = length / 1, using half-angle identity we get
// length = sqrt(2 / (1 + cos(theta)). We already have cos() when to computed the dot.
// This is nice, since our computed weight is cos(theta/2) as well!
let cos_theta_over_2 = ((1.0 + dot) / 2.0).sqrt();
off_curve.set_length(cos_theta_over_2.invert());
if !last_q.almost_equal(off_curve) {
dst[conic_count] = Conic::new(last_q, off_curve, final_pt, cos_theta_over_2);
conic_count += 1;
}
}
// now handle counter-clockwise and the initial unitStart rotation
let mut transform = Transform::from_sin_cos(u_start.y, u_start.x);
if dir == PathDirection::CCW {
transform = transform.pre_scale(1.0, -1.0);
}
transform = transform.post_concat(user_transform);
for conic in dst.iter_mut().take(conic_count) {
transform.map_points(&mut conic.points);
}
if conic_count == 0 {
None
} else {
Some(&dst[0..conic_count])
}
}
}
fn subdivide_weight_value(w: f32) -> f32 {
(0.5 + w * 0.5).sqrt()
}
fn subdivide<'a>(src: &Conic, mut points: &'a mut [Point], mut level: u8) -> &'a mut [Point] {
if level == 0 {
points[0] = src.points[1];
points[1] = src.points[2];
&mut points[2..]
} else {
let mut dst = src.chop();
let start_y = src.points[0].y;
let end_y = src.points[2].y;
if between(start_y, src.points[1].y, end_y) {
// If the input is monotonic and the output is not, the scan converter hangs.
// Ensure that the chopped conics maintain their y-order.
let mid_y = dst.0.points[2].y;
if !between(start_y, mid_y, end_y) {
// If the computed midpoint is outside the ends, move it to the closer one.
let closer_y = if (mid_y - start_y).abs() < (mid_y - end_y).abs() {
start_y
} else {
end_y
};
dst.0.points[2].y = closer_y;
dst.1.points[0].y = closer_y;
}
if !between(start_y, dst.0.points[1].y, dst.0.points[2].y) {
// If the 1st control is not between the start and end, put it at the start.
// This also reduces the quad to a line.
dst.0.points[1].y = start_y;
}
if !between(dst.1.points[0].y, dst.1.points[1].y, end_y) {
// If the 2nd control is not between the start and end, put it at the end.
// This also reduces the quad to a line.
dst.1.points[1].y = end_y;
}
// Verify that all five points are in order.
debug_assert!(between(start_y, dst.0.points[1].y, dst.0.points[2].y));
debug_assert!(between(
dst.0.points[1].y,
dst.0.points[2].y,
dst.1.points[1].y
));
debug_assert!(between(dst.0.points[2].y, dst.1.points[1].y, end_y));
}
level -= 1;
points = subdivide(&dst.0, points, level);
subdivide(&dst.1, points, level)
}
}
// This was originally developed and tested for pathops: see SkOpTypes.h
// returns true if (a <= b <= c) || (a >= b >= c)
fn between(a: f32, b: f32, c: f32) -> bool {
(a - b) * (c - b) <= 0.0
}
pub(crate) struct AutoConicToQuads {
pub points: [Point; 64],
pub len: u8, // the number of quads
}
impl AutoConicToQuads {
pub fn compute(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Option<Self> {
let conic = Conic::new(pt0, pt1, pt2, weight);
let pow2 = conic.compute_quad_pow2(0.25)?;
let mut points = [Point::zero(); 64];
let len = conic.chop_into_quads_pow2(pow2, &mut points);
Some(AutoConicToQuads { points, len })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eval_cubic_at_1() {
let src = [
Point::from_xy(30.0, 40.0),
Point::from_xy(30.0, 40.0),
Point::from_xy(171.0, 45.0),
Point::from_xy(180.0, 155.0),
];
assert_eq!(
eval_cubic_pos_at(&src, NormalizedF32::ZERO),
Point::from_xy(30.0, 40.0)
);
assert_eq!(
eval_cubic_tangent_at(&src, NormalizedF32::ZERO),
Point::from_xy(141.0, 5.0)
);
}
#[test]
fn find_cubic_max_curvature_1() {
let src = [
Point::from_xy(20.0, 160.0),
Point::from_xy(20.0001, 160.0),
Point::from_xy(160.0, 20.0),
Point::from_xy(160.0001, 20.0),
];
let mut t_values = [NormalizedF32::ZERO; 3];
let t_values = find_cubic_max_curvature(&src, &mut t_values);
assert_eq!(
&t_values,
&[
NormalizedF32::ZERO,
NormalizedF32::new_clamped(0.5),
NormalizedF32::ONE,
]
);
}
}

View file

@ -0,0 +1,657 @@
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use core::convert::TryFrom;
use crate::{FiniteF32, IntSize, LengthU32, Point, SaturateRound};
/// An integer rectangle.
///
/// # Guarantees
///
/// - Width and height are in 1..=i32::MAX range.
/// - x+width and y+height does not overflow.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct IntRect {
x: i32,
y: i32,
width: LengthU32,
height: LengthU32,
}
impl IntRect {
/// Creates a new `IntRect`.
pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
x.checked_add(i32::try_from(width).ok()?)?;
y.checked_add(i32::try_from(height).ok()?)?;
Some(IntRect {
x,
y,
width: LengthU32::new(width)?,
height: LengthU32::new(height)?,
})
}
/// Creates a new `IntRect`.
pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
let width = u32::try_from(right.checked_sub(left)?).ok()?;
let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
IntRect::from_xywh(left, top, width, height)
}
/// Returns rect's X position.
pub fn x(&self) -> i32 {
self.x
}
/// Returns rect's Y position.
pub fn y(&self) -> i32 {
self.y
}
/// Returns rect's width.
pub fn width(&self) -> u32 {
self.width.get()
}
/// Returns rect's height.
pub fn height(&self) -> u32 {
self.height.get()
}
/// Returns rect's left edge.
pub fn left(&self) -> i32 {
self.x
}
/// Returns rect's top edge.
pub fn top(&self) -> i32 {
self.y
}
/// Returns rect's right edge.
pub fn right(&self) -> i32 {
// No overflow is guaranteed by constructors.
self.x + self.width.get() as i32
}
/// Returns rect's bottom edge.
pub fn bottom(&self) -> i32 {
// No overflow is guaranteed by constructors.
self.y + self.height.get() as i32
}
/// Returns rect's size.
pub fn size(&self) -> IntSize {
IntSize {
width: self.width,
height: self.height,
}
}
/// Checks that the rect is completely includes `other` Rect.
pub fn contains(&self, other: &Self) -> bool {
self.x <= other.x
&& self.y <= other.y
&& self.right() >= other.right()
&& self.bottom() >= other.bottom()
}
/// Returns an intersection of two rectangles.
///
/// Returns `None` otherwise.
pub fn intersect(&self, other: &Self) -> Option<Self> {
let left = self.x.max(other.x);
let top = self.y.max(other.y);
let right = self.right().min(other.right());
let bottom = self.bottom().min(other.bottom());
let w = u32::try_from(right.checked_sub(left)?).ok()?;
let h = u32::try_from(bottom.checked_sub(top)?).ok()?;
IntRect::from_xywh(left, top, w, h)
}
/// Insets the rectangle.
pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> {
IntRect::from_ltrb(
self.left() + dx,
self.top() + dy,
self.right() - dx,
self.bottom() - dy,
)
}
/// Outsets the rectangle.
pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> {
IntRect::from_ltrb(
self.left().saturating_sub(dx),
self.top().saturating_sub(dy),
self.right().saturating_add(dx),
self.bottom().saturating_add(dy),
)
}
/// Converts into `Rect`.
pub fn to_rect(&self) -> Rect {
// Can't fail, because `IntRect` is always valid.
Rect::from_ltrb(
self.x as f32,
self.y as f32,
self.x as f32 + self.width.get() as f32,
self.y as f32 + self.height.get() as f32,
)
.unwrap()
}
/// Converts into `ScreenIntRect`.
///
/// # Checks
///
/// - x >= 0
/// - y >= 0
pub fn to_screen_int_rect(&self) -> Option<ScreenIntRect> {
let x = u32::try_from(self.x).ok()?;
let y = u32::try_from(self.y).ok()?;
Some(ScreenIntRect::from_xywh_safe(x, y, self.width, self.height))
}
}
#[cfg(test)]
mod int_rect_tests {
use super::*;
#[test]
fn tests() {
assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
assert_eq!(
IntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
None
);
assert_eq!(IntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
assert_eq!(IntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
assert_eq!(IntRect::from_xywh(core::i32::MAX, 0, 1, 1), None);
assert_eq!(IntRect::from_xywh(0, core::i32::MAX, 1, 1), None);
let r = IntRect::from_xywh(1, 2, 3, 4).unwrap();
assert_eq!(
r.to_screen_int_rect().unwrap(),
ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
);
let r = IntRect::from_xywh(-1, -1, 3, 4).unwrap();
assert_eq!(r.to_screen_int_rect(), None);
{
// No intersection.
let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
assert_eq!(r1.intersect(&r2), None);
}
{
// Second inside the first one.
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
}
{
// Partial overlap.
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
}
}
}
/// A screen `IntRect`.
///
/// # Guarantees
///
/// - X and Y are in 0..=i32::MAX range.
/// - Width and height are in 1..=i32::MAX range.
/// - x+width and y+height does not overflow.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ScreenIntRect {
x: u32,
y: u32,
width: LengthU32,
height: LengthU32,
}
impl ScreenIntRect {
/// Creates a new `ScreenIntRect`.
pub fn from_xywh(x: u32, y: u32, width: u32, height: u32) -> Option<Self> {
i32::try_from(x).ok()?;
i32::try_from(y).ok()?;
i32::try_from(width).ok()?;
i32::try_from(height).ok()?;
x.checked_add(width)?;
y.checked_add(height)?;
let width = LengthU32::new(width)?;
let height = LengthU32::new(height)?;
Some(ScreenIntRect {
x,
y,
width,
height,
})
}
/// Creates a new `ScreenIntRect`.
pub const fn from_xywh_safe(x: u32, y: u32, width: LengthU32, height: LengthU32) -> Self {
ScreenIntRect {
x,
y,
width,
height,
}
}
/// Returns rect's X position.
pub fn x(&self) -> u32 {
self.x
}
/// Returns rect's Y position.
pub fn y(&self) -> u32 {
self.y
}
/// Returns rect's width.
pub fn width(&self) -> u32 {
self.width.get()
}
/// Returns rect's height.
pub fn height(&self) -> u32 {
self.height.get()
}
/// Returns rect's width.
pub fn width_safe(&self) -> LengthU32 {
self.width
}
/// Returns rect's left edge.
pub fn left(&self) -> u32 {
self.x
}
/// Returns rect's top edge.
pub fn top(&self) -> u32 {
self.y
}
/// Returns rect's right edge.
///
/// The right edge is at least 1.
pub fn right(&self) -> u32 {
// No overflow is guaranteed by constructors.
self.x + self.width.get()
}
/// Returns rect's bottom edge.
///
/// The bottom edge is at least 1.
pub fn bottom(&self) -> u32 {
// No overflow is guaranteed by constructors.
self.y + self.height.get()
}
/// Returns rect's size.
pub fn size(&self) -> IntSize {
IntSize {
width: self.width,
height: self.height,
}
}
/// Checks that the rect is completely includes `other` Rect.
pub fn contains(&self, other: &Self) -> bool {
self.x <= other.x
&& self.y <= other.y
&& self.right() >= other.right()
&& self.bottom() >= other.bottom()
}
/// Converts into a `IntRect`.
pub fn to_int_rect(&self) -> IntRect {
// Everything is already checked by constructors.
IntRect::from_xywh(
self.x as i32,
self.y as i32,
self.width.get(),
self.height.get(),
)
.unwrap()
}
/// Converts into a `Rect`.
pub fn to_rect(&self) -> Rect {
// Can't fail, because `ScreenIntRect` is always valid.
// And u32 always fits into f32.
Rect::from_ltrb(
self.x as f32,
self.y as f32,
self.x as f32 + self.width.get() as f32,
self.y as f32 + self.height.get() as f32,
)
.unwrap()
}
}
#[cfg(test)]
mod screen_int_rect_tests {
use super::*;
#[test]
fn tests() {
assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 0), None);
assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, 0), None);
assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 1), None);
assert_eq!(
ScreenIntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
None
);
assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
assert_eq!(ScreenIntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
assert_eq!(ScreenIntRect::from_xywh(core::u32::MAX, 0, 1, 1), None);
assert_eq!(ScreenIntRect::from_xywh(0, core::u32::MAX, 1, 1), None);
assert_eq!(
ScreenIntRect::from_xywh(
core::u32::MAX,
core::u32::MAX,
core::u32::MAX,
core::u32::MAX
),
None
);
let r = ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap();
assert_eq!(r.x(), 1);
assert_eq!(r.y(), 2);
assert_eq!(r.width(), 3);
assert_eq!(r.height(), 4);
assert_eq!(r.right(), 4);
assert_eq!(r.bottom(), 6);
}
}
/// A rectangle defined by left, top, right and bottom edges.
///
/// Can have zero width and/or height. But not a negative one.
///
/// # Guarantees
///
/// - All values are finite.
/// - Left edge is <= right.
/// - Top edge is <= bottom.
/// - Width and height are <= f32::MAX.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub struct Rect {
left: FiniteF32,
top: FiniteF32,
right: FiniteF32,
bottom: FiniteF32,
}
impl core::fmt::Debug for Rect {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Rect")
.field("left", &self.left.get())
.field("top", &self.top.get())
.field("right", &self.right.get())
.field("bottom", &self.bottom.get())
.finish()
}
}
impl Rect {
/// Creates new `Rect`.
pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
let left = FiniteF32::new(left)?;
let top = FiniteF32::new(top)?;
let right = FiniteF32::new(right)?;
let bottom = FiniteF32::new(bottom)?;
if left.get() <= right.get() && top.get() <= bottom.get() {
// Width and height must not overflow.
checked_f32_sub(right.get(), left.get())?;
checked_f32_sub(bottom.get(), top.get())?;
Some(Rect {
left,
top,
right,
bottom,
})
} else {
None
}
}
/// Creates new `Rect`.
pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
Rect::from_ltrb(x, y, w + x, h + y)
}
/// Returns the left edge.
pub fn left(&self) -> f32 {
self.left.get()
}
/// Returns the top edge.
pub fn top(&self) -> f32 {
self.top.get()
}
/// Returns the right edge.
pub fn right(&self) -> f32 {
self.right.get()
}
/// Returns the bottom edge.
pub fn bottom(&self) -> f32 {
self.bottom.get()
}
/// Returns rect's X position.
pub fn x(&self) -> f32 {
self.left.get()
}
/// Returns rect's Y position.
pub fn y(&self) -> f32 {
self.top.get()
}
/// Returns rect's width.
#[inline]
pub fn width(&self) -> f32 {
self.right.get() - self.left.get()
}
/// Returns rect's height.
#[inline]
pub fn height(&self) -> f32 {
self.bottom.get() - self.top.get()
}
/// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion.
///
/// Width and height are guarantee to be >= 1.
pub fn round(&self) -> Option<IntRect> {
IntRect::from_xywh(
i32::saturate_round(self.x()),
i32::saturate_round(self.y()),
core::cmp::max(1, i32::saturate_round(self.width()) as u32),
core::cmp::max(1, i32::saturate_round(self.height()) as u32),
)
}
/// Converts into an `IntRect` rounding outwards.
///
/// Width and height are guarantee to be >= 1.
pub fn round_out(&self) -> Option<IntRect> {
IntRect::from_xywh(
i32::saturate_floor(self.x()),
i32::saturate_floor(self.y()),
core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
)
}
/// Returns an intersection of two rectangles.
///
/// Returns `None` otherwise.
pub fn intersect(&self, other: &Self) -> Option<Self> {
let left = self.x().max(other.x());
let top = self.y().max(other.y());
let right = self.right().min(other.right());
let bottom = self.bottom().min(other.bottom());
Rect::from_ltrb(left, top, right, bottom)
}
/// Creates a Rect from Point array.
///
/// Returns None if count is zero or if Point array contains an infinity or NaN.
pub fn from_points(points: &[Point]) -> Option<Self> {
use crate::f32x4_t::f32x4;
if points.is_empty() {
return None;
}
let mut offset = 0;
let mut min;
let mut max;
if points.len() & 1 != 0 {
let pt = points[0];
min = f32x4([pt.x, pt.y, pt.x, pt.y]);
max = min;
offset += 1;
} else {
let pt0 = points[0];
let pt1 = points[1];
min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
max = min;
offset += 2;
}
let mut accum = f32x4::default();
while offset != points.len() {
let pt0 = points[offset + 0];
let pt1 = points[offset + 1];
let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
accum *= xy;
min = min.min(xy);
max = max.max(xy);
offset += 2;
}
let all_finite = accum * f32x4::default() == f32x4::default();
let min: [f32; 4] = min.0;
let max: [f32; 4] = max.0;
if all_finite {
Rect::from_ltrb(
min[0].min(min[2]),
min[1].min(min[3]),
max[0].max(max[2]),
max[1].max(max[3]),
)
} else {
None
}
}
/// Insets the rectangle by the specified offset.
pub fn inset(&mut self, dx: f32, dy: f32) -> Option<Self> {
Rect::from_ltrb(
self.left() + dx,
self.top() + dy,
self.right() - dx,
self.bottom() - dy,
)
}
/// Outsets the rectangle by the specified offset.
pub fn outset(&mut self, dx: f32, dy: f32) -> Option<Self> {
self.inset(-dx, -dy)
}
}
fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
debug_assert!(a.is_finite());
debug_assert!(b.is_finite());
let n = a as f64 - b as f64;
// Not sure if this is perfectly correct.
if n > core::f32::MIN as f64 && n < core::f32::MAX as f64 {
Some(n as f32)
} else {
None
}
}
#[cfg(test)]
mod rect_tests {
use super::*;
#[test]
fn tests() {
assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
assert_eq!(Rect::from_ltrb(core::f32::NAN, 10.0, 10.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, core::f32::NAN, 10.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, core::f32::NAN, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::NAN), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::INFINITY), None);
let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
assert_eq!(rect.left(), 10.0);
assert_eq!(rect.top(), 20.0);
assert_eq!(rect.right(), 30.0);
assert_eq!(rect.bottom(), 40.0);
assert_eq!(rect.width(), 20.0);
assert_eq!(rect.height(), 20.0);
let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
assert_eq!(rect.width(), 20.0);
assert_eq!(rect.height(), 20.0);
}
#[test]
fn round_overflow() {
// minimum value that cause overflow
// because i32::MAX has no exact conversion to f32
let x = 128.0;
// maximum width
let width = i32::MAX as f32;
let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
assert_eq!(rect.round(), None);
assert_eq!(rect.round_out(), None);
}
}

View file

@ -0,0 +1,168 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::floating_point::f32_as_2s_compliment;
#[allow(missing_docs)]
pub const SCALAR_MAX: f32 = 3.402823466e+38;
#[allow(missing_docs)]
pub const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 12) as f32;
#[allow(missing_docs)]
pub const SCALAR_ROOT_2_OVER_2: f32 = 0.707106781;
/// Float number extension methods.
///
/// Mainly for internal use. Do not rely on it!
#[allow(missing_docs)]
pub trait Scalar {
fn half(self) -> Self;
fn ave(self, other: Self) -> Self;
fn sqr(self) -> Self;
fn invert(self) -> Self;
fn bound(self, min: Self, max: Self) -> Self;
fn is_nearly_equal(self, other: Self) -> bool;
fn is_nearly_zero(self) -> bool;
fn is_nearly_zero_within_tolerance(self, tolerance: Self) -> bool;
fn almost_dequal_ulps(self, other: Self) -> bool;
}
impl Scalar for f32 {
fn half(self) -> f32 {
self * 0.5
}
fn ave(self, other: Self) -> f32 {
(self + other) * 0.5
}
fn sqr(self) -> f32 {
self * self
}
fn invert(self) -> f32 {
1.0 / self
}
// Works just like SkTPin, returning `max` for NaN/inf
/// A non-panicking clamp.
fn bound(self, min: Self, max: Self) -> Self {
max.min(self).max(min)
}
fn is_nearly_equal(self, other: Self) -> bool {
(self - other).abs() <= SCALAR_NEARLY_ZERO
}
fn is_nearly_zero(self) -> bool {
self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
}
fn is_nearly_zero_within_tolerance(self, tolerance: Self) -> bool {
debug_assert!(tolerance >= 0.0);
self.abs() <= tolerance
}
// From SkPathOpsTypes.
fn almost_dequal_ulps(self, other: Self) -> bool {
const ULPS_EPSILON: i32 = 16;
let a_bits = f32_as_2s_compliment(self);
let b_bits = f32_as_2s_compliment(other);
// Find the difference in ULPs.
a_bits < b_bits + ULPS_EPSILON && b_bits < a_bits + ULPS_EPSILON
}
}
#[allow(missing_docs)]
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
pub trait NoStdFloat {
fn trunc(self) -> Self;
fn sqrt(self) -> Self;
fn abs(self) -> Self;
fn sin(self) -> Self;
fn cos(self) -> Self;
fn ceil(self) -> Self;
fn floor(self) -> Self;
fn powf(self, y: Self) -> Self;
fn acos(self) -> Self;
}
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
impl NoStdFloat for f32 {
fn trunc(self) -> Self {
libm::truncf(self)
}
fn sqrt(self) -> Self {
libm::sqrtf(self)
}
fn abs(self) -> Self {
libm::fabsf(self)
}
fn sin(self) -> Self {
libm::sinf(self)
}
fn cos(self) -> Self {
libm::cosf(self)
}
fn ceil(self) -> Self {
libm::ceilf(self)
}
fn floor(self) -> Self {
libm::floorf(self)
}
fn powf(self, y: Self) -> Self {
libm::powf(self, y)
}
fn acos(self) -> Self {
libm::acosf(self)
}
}
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
impl NoStdFloat for f64 {
fn trunc(self) -> Self {
libm::trunc(self)
}
fn sqrt(self) -> Self {
libm::sqrt(self)
}
fn abs(self) -> Self {
libm::fabs(self)
}
fn sin(self) -> Self {
libm::sin(self)
}
fn cos(self) -> Self {
libm::cos(self)
}
fn ceil(self) -> Self {
libm::ceil(self)
}
fn floor(self) -> Self {
libm::floor(self)
}
fn powf(self, y: Self) -> Self {
libm::pow(self, y)
}
fn acos(self) -> Self {
libm::acos(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bound() {
assert_eq!(core::f32::NAN.bound(0.0, 1.0), 1.0);
assert_eq!(core::f32::INFINITY.bound(0.0, 1.0), 1.0);
assert_eq!(core::f32::NEG_INFINITY.bound(0.0, 1.0), 0.0);
assert_eq!(core::f32::EPSILON.bound(0.0, 1.0), core::f32::EPSILON);
assert_eq!(0.5.bound(0.0, 1.0), 0.5);
assert_eq!((-1.0).bound(0.0, 1.0), 0.0);
assert_eq!(2.0.bound(0.0, 1.0), 1.0);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,435 @@
// Copyright 2006 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::Point;
use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO};
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
/// An affine transformation matrix.
///
/// Unlike other types, doesn't guarantee to be valid. This is Skia quirk.
/// Meaning Transform(0, 0, 0, 0, 0, 0) is ok, while it's technically not.
/// Non-finite values are also not an error.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Transform {
pub sx: f32,
pub kx: f32,
pub ky: f32,
pub sy: f32,
pub tx: f32,
pub ty: f32,
}
impl Default for Transform {
fn default() -> Self {
Transform {
sx: 1.0,
kx: 0.0,
ky: 0.0,
sy: 1.0,
tx: 0.0,
ty: 0.0,
}
}
}
impl Transform {
/// Creates an identity transform.
pub fn identity() -> Self {
Transform::default()
}
/// Creates a new `Transform`.
///
/// We are using column-major-column-vector matrix notation, therefore it's ky-kx, not kx-ky.
pub fn from_row(sx: f32, ky: f32, kx: f32, sy: f32, tx: f32, ty: f32) -> Self {
Transform {
sx,
ky,
kx,
sy,
tx,
ty,
}
}
/// Creates a new translating `Transform`.
pub fn from_translate(tx: f32, ty: f32) -> Self {
Transform::from_row(1.0, 0.0, 0.0, 1.0, tx, ty)
}
/// Creates a new scaling `Transform`.
pub fn from_scale(sx: f32, sy: f32) -> Self {
Transform::from_row(sx, 0.0, 0.0, sy, 0.0, 0.0)
}
/// Creates a new skewing `Transform`.
pub fn from_skew(kx: f32, ky: f32) -> Self {
Transform::from_row(1.0, ky, kx, 1.0, 0.0, 0.0)
}
/// Creates a new rotating `Transform`.
pub fn from_rotate(angle: f32) -> Self {
let v = angle.to_radians();
let a = v.cos();
let b = v.sin();
let c = -b;
let d = a;
Transform::from_row(a, b, c, d, 0.0, 0.0)
}
/// Creates a new rotating `Transform` at the specified position.
pub fn from_rotate_at(angle: f32, tx: f32, ty: f32) -> Self {
let mut ts = Self::default();
ts = ts.pre_translate(tx, ty);
ts = ts.pre_concat(Transform::from_rotate(angle));
ts = ts.pre_translate(-tx, -ty);
ts
}
/// Checks that transform is finite.
pub fn is_finite(&self) -> bool {
self.sx.is_finite()
&& self.ky.is_finite()
&& self.kx.is_finite()
&& self.sy.is_finite()
&& self.tx.is_finite()
&& self.ty.is_finite()
}
/// Checks that transform is identity.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn is_identity(&self) -> bool {
*self == Transform::default()
}
/// Checks that transform is scale-only.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn is_scale(&self) -> bool {
self.has_scale() && !self.has_skew() && !self.has_translate()
}
/// Checks that transform is skew-only.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn is_skew(&self) -> bool {
!self.has_scale() && self.has_skew() && !self.has_translate()
}
/// Checks that transform is translate-only.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn is_translate(&self) -> bool {
!self.has_scale() && !self.has_skew() && self.has_translate()
}
/// Checks that transform contains only scale and translate.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn is_scale_translate(&self) -> bool {
(self.has_scale() || self.has_translate()) && !self.has_skew()
}
/// Checks that transform contains a scale part.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn has_scale(&self) -> bool {
self.sx != 1.0 || self.sy != 1.0
}
/// Checks that transform contains a skew part.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn has_skew(&self) -> bool {
self.kx != 0.0 || self.ky != 0.0
}
/// Checks that transform contains a translate part.
///
/// The transform type is detected on creation, so this method is essentially free.
pub fn has_translate(&self) -> bool {
self.tx != 0.0 || self.ty != 0.0
}
/// Pre-scales the current transform.
#[must_use]
pub fn pre_scale(&self, sx: f32, sy: f32) -> Self {
self.pre_concat(Transform::from_scale(sx, sy))
}
/// Post-scales the current transform.
#[must_use]
pub fn post_scale(&self, sx: f32, sy: f32) -> Self {
self.post_concat(Transform::from_scale(sx, sy))
}
/// Pre-translates the current transform.
#[must_use]
pub fn pre_translate(&self, tx: f32, ty: f32) -> Self {
self.pre_concat(Transform::from_translate(tx, ty))
}
/// Post-translates the current transform.
#[must_use]
pub fn post_translate(&self, tx: f32, ty: f32) -> Self {
self.post_concat(Transform::from_translate(tx, ty))
}
/// Pre-concats the current transform.
#[must_use]
pub fn pre_concat(&self, other: Self) -> Self {
concat(*self, other)
}
/// Post-concats the current transform.
#[must_use]
pub fn post_concat(&self, other: Self) -> Self {
concat(other, *self)
}
pub(crate) fn from_sin_cos(sin: f32, cos: f32) -> Self {
Transform::from_row(cos, sin, -sin, cos, 0.0, 0.0)
}
/// Transforms a slice of points using the current transform.
pub fn map_points(&self, points: &mut [Point]) {
if points.is_empty() {
return;
}
// TODO: simd
if self.is_identity() {
// Do nothing.
} else if self.is_translate() {
for p in points {
p.x += self.tx;
p.y += self.ty;
}
} else if self.is_scale_translate() {
for p in points {
p.x = p.x * self.sx + self.tx;
p.y = p.y * self.sy + self.ty;
}
} else {
for p in points {
let x = p.x * self.sx + p.y * self.kx + self.tx;
let y = p.x * self.ky + p.y * self.sy + self.ty;
p.x = x;
p.y = y;
}
}
}
/// Returns an inverted transform.
pub fn invert(&self) -> Option<Self> {
// Allow the trivial case to be inlined.
if self.is_identity() {
return Some(*self);
}
invert(self)
}
}
fn invert(ts: &Transform) -> Option<Transform> {
debug_assert!(!ts.is_identity());
if ts.is_scale_translate() {
if ts.has_scale() {
let inv_x = ts.sx.invert();
let inv_y = ts.sy.invert();
Some(Transform::from_row(
inv_x,
0.0,
0.0,
inv_y,
-ts.tx * inv_x,
-ts.ty * inv_y,
))
} else {
// translate only
Some(Transform::from_translate(-ts.tx, -ts.ty))
}
} else {
let inv_det = inv_determinant(ts)?;
let inv_ts = compute_inv(ts, inv_det);
if inv_ts.is_finite() {
Some(inv_ts)
} else {
None
}
}
}
fn inv_determinant(ts: &Transform) -> Option<f64> {
let det = dcross(ts.sx as f64, ts.sy as f64, ts.kx as f64, ts.ky as f64);
// Since the determinant is on the order of the cube of the matrix members,
// compare to the cube of the default nearly-zero constant (although an
// estimate of the condition number would be better if it wasn't so expensive).
let tolerance = SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO;
if (det as f32).is_nearly_zero_within_tolerance(tolerance) {
None
} else {
Some(1.0 / det)
}
}
fn compute_inv(ts: &Transform, inv_det: f64) -> Transform {
Transform::from_row(
(ts.sy as f64 * inv_det) as f32,
(-ts.ky as f64 * inv_det) as f32,
(-ts.kx as f64 * inv_det) as f32,
(ts.sx as f64 * inv_det) as f32,
dcross_dscale(ts.kx, ts.ty, ts.sy, ts.tx, inv_det),
dcross_dscale(ts.ky, ts.tx, ts.sx, ts.ty, inv_det),
)
}
fn dcross(a: f64, b: f64, c: f64, d: f64) -> f64 {
a * b - c * d
}
fn dcross_dscale(a: f32, b: f32, c: f32, d: f32, scale: f64) -> f32 {
(dcross(a as f64, b as f64, c as f64, d as f64) * scale) as f32
}
fn concat(a: Transform, b: Transform) -> Transform {
if a.is_identity() {
b
} else if b.is_identity() {
a
} else if !a.has_skew() && !b.has_skew() {
// just scale and translate
Transform::from_row(
a.sx * b.sx,
0.0,
0.0,
a.sy * b.sy,
a.sx * b.tx + a.tx,
a.sy * b.ty + a.ty,
)
} else {
Transform::from_row(
mul_add_mul(a.sx, b.sx, a.kx, b.ky),
mul_add_mul(a.ky, b.sx, a.sy, b.ky),
mul_add_mul(a.sx, b.kx, a.kx, b.sy),
mul_add_mul(a.ky, b.kx, a.sy, b.sy),
mul_add_mul(a.sx, b.tx, a.kx, b.ty) + a.tx,
mul_add_mul(a.ky, b.tx, a.sy, b.ty) + a.ty,
)
}
}
fn mul_add_mul(a: f32, b: f32, c: f32, d: f32) -> f32 {
(f64::from(a) * f64::from(b) + f64::from(c) * f64::from(d)) as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transform() {
assert_eq!(
Transform::identity(),
Transform::from_row(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
);
assert_eq!(
Transform::from_scale(1.0, 2.0),
Transform::from_row(1.0, 0.0, 0.0, 2.0, 0.0, 0.0)
);
assert_eq!(
Transform::from_skew(2.0, 3.0),
Transform::from_row(1.0, 3.0, 2.0, 1.0, 0.0, 0.0)
);
assert_eq!(
Transform::from_translate(5.0, 6.0),
Transform::from_row(1.0, 0.0, 0.0, 1.0, 5.0, 6.0)
);
let ts = Transform::identity();
assert_eq!(ts.is_identity(), true);
assert_eq!(ts.is_scale(), false);
assert_eq!(ts.is_skew(), false);
assert_eq!(ts.is_translate(), false);
assert_eq!(ts.is_scale_translate(), false);
assert_eq!(ts.has_scale(), false);
assert_eq!(ts.has_skew(), false);
assert_eq!(ts.has_translate(), false);
let ts = Transform::from_scale(2.0, 3.0);
assert_eq!(ts.is_identity(), false);
assert_eq!(ts.is_scale(), true);
assert_eq!(ts.is_skew(), false);
assert_eq!(ts.is_translate(), false);
assert_eq!(ts.is_scale_translate(), true);
assert_eq!(ts.has_scale(), true);
assert_eq!(ts.has_skew(), false);
assert_eq!(ts.has_translate(), false);
let ts = Transform::from_skew(2.0, 3.0);
assert_eq!(ts.is_identity(), false);
assert_eq!(ts.is_scale(), false);
assert_eq!(ts.is_skew(), true);
assert_eq!(ts.is_translate(), false);
assert_eq!(ts.is_scale_translate(), false);
assert_eq!(ts.has_scale(), false);
assert_eq!(ts.has_skew(), true);
assert_eq!(ts.has_translate(), false);
let ts = Transform::from_translate(2.0, 3.0);
assert_eq!(ts.is_identity(), false);
assert_eq!(ts.is_scale(), false);
assert_eq!(ts.is_skew(), false);
assert_eq!(ts.is_translate(), true);
assert_eq!(ts.is_scale_translate(), true);
assert_eq!(ts.has_scale(), false);
assert_eq!(ts.has_skew(), false);
assert_eq!(ts.has_translate(), true);
let ts = Transform::from_row(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
assert_eq!(ts.is_identity(), false);
assert_eq!(ts.is_scale(), false);
assert_eq!(ts.is_skew(), false);
assert_eq!(ts.is_translate(), false);
assert_eq!(ts.is_scale_translate(), false);
assert_eq!(ts.has_scale(), true);
assert_eq!(ts.has_skew(), true);
assert_eq!(ts.has_translate(), true);
let ts = Transform::from_scale(1.0, 1.0);
assert_eq!(ts.has_scale(), false);
let ts = Transform::from_skew(0.0, 0.0);
assert_eq!(ts.has_skew(), false);
let ts = Transform::from_translate(0.0, 0.0);
assert_eq!(ts.has_translate(), false);
}
#[test]
fn concat() {
let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
ts = ts.pre_scale(2.0, -4.0);
assert_eq!(ts, Transform::from_row(2.4, 6.8, 22.4, 31.2, 1.2, 3.4));
let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
ts = ts.post_scale(2.0, -4.0);
assert_eq!(ts, Transform::from_row(2.4, -13.6, -11.2, 31.2, 2.4, -13.6));
}
}