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 @@
{"files":{"Cargo.toml":"f7d373c9cbba5c61b664ed8f459cbfb22257829ca978d2ac894dfefa62a38845","LICENSE":"6d41e05f1d54fe726997cb0334ec539f1dff50600b1d5d90462ec2b392770ced","README.md":"14f7ef2a676cd7dc14b68f93cc3cb153b5d2b519c2afe78fdc5ff9cef54a1b74","src/dash.rs":"2ce96c653bab805e2f59006c18a010d8fb3ddf087e0a735361c9966e6b483f87","src/f32x2_t.rs":"cd99d6e1fa00ed9c544af718b3f01b68a6eb500d183d3eeced804effb96878f8","src/f32x4_t.rs":"6328e5670e9dffa3443b4ab2d7c7f1ab36e2ac14511c54c824e782c89a955607","src/floating_point.rs":"ef655a1eb3146fd0a77c57b584f164af73b51e56d36c485ef67fe139bbde11af","src/lib.rs":"a29d208d851570446c4717a052290d62ffde9f32c7c76ed32e7cac4028a6eda3","src/path.rs":"b157f9e0ab41fcd51a28f359c6fb227b67665f4d9a4af357ba61b8692434faaf","src/path_builder.rs":"3fddd39df51881c2f27a83a1086d6f7f2eccab0b5f274124f1ad24ae2211c66b","src/path_geometry.rs":"3203df234be68484f7b4fd2e0ace6a4a868b234010e146b9b2a9305188ee19cb","src/rect.rs":"9f7c2b18d24427021797a686da47c0176d9826603256946002743b66afb7ea6a","src/scalar.rs":"282a132a33215f09fae4c08146b4d9ea856636be7a773dc7c85f25a4474fd562","src/stroker.rs":"ad7eaef02688bcd14f9017a3fd82f28ae80fd1e23498623bd4ea64be5423dc92","src/transform.rs":"54856af81c8dc208a9964e9a86cb76ddb494a6ced932432246b6cf68b1e1265f"},"package":"adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c"}

View file

@ -0,0 +1,48 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "tiny-skia-path"
version = "0.8.4"
authors = ["Yevhenii Reizner <razrfalcon@gmail.com>"]
description = "A tiny-skia Bezier path implementation"
documentation = "https://docs.rs/tiny-skia-path/"
readme = "README.md"
keywords = [
"graphics",
"bezier",
"path",
"dash",
"stroke",
]
categories = ["graphics"]
license = "BSD-3-Clause"
repository = "https://github.com/RazrFalcon/tiny-skia/path"
[dependencies.arrayref]
version = "0.3.6"
[dependencies.bytemuck]
version = "1.4"
[dependencies.libm]
version = "0.2.1"
optional = true
[dependencies.strict-num]
version = "0.1"
default-features = false
[features]
default = ["std"]
no-std-float = ["libm"]
std = []

View file

@ -0,0 +1,30 @@
Copyright (c) 2011 Google Inc. All rights reserved.
Copyright (c) 2020 Yevhenii Reizner All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,17 @@
# tiny-skia-path
![Build Status](https://github.com/RazrFalcon/tiny-skia/workflows/Rust/badge.svg)
[![Crates.io](https://img.shields.io/crates/v/tiny-skia-path.svg)](https://crates.io/crates/tiny-skia-path)
[![Documentation](https://docs.rs/tiny-skia-path/badge.svg)](https://docs.rs/tiny-skia-path)
[![Rust 1.51+](https://img.shields.io/badge/rust-1.51+-orange.svg)](https://www.rust-lang.org)
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/).
## License
The same as used by [Skia](https://skia.org/): [New BSD License](./LICENSE)

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));
}
}