mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 14:28:11 +08:00
Add nondestructive vector editing (#1676)
* Initial vector modify node * Initial extraction of data from monitor nodes * Migrate to point id * Start converting to modify node * Non destructive spline tool (tout le reste est cassé) * Fix unconnected modify node * Fix freehand tool * Pen tool * Migrate demo art * Select points * Fix the demo artwork * Fix the X and Y inputs for path tool * G1 continous toggle * Delete points * Fix test * Insert point * Improve robustness of handles * Fix GRS shortcuts on path * Dragging points * Fix build * Preserve opposing handle lengths * Update demo art and snapping * Fix polygon tool * Double click end anchor * Improve dragging * Fix text shifting * Select only connected verts * Colinear alt * Cleanup * Fix imports * Improve pen tool avoiding handle placement * Improve disolve * Remove pivot widget from Transform node properties * Fix demo art * Fix bugs * Re-save demo artwork * Code review * Serialize hashmap as tuple vec to enable deserialize_inputs * Fix migrate * Add document upgrade function to editor_api.rs * Finalize document upgrading * Rename to the Path node * Remove smoothing from Freehand tool * Upgrade demo artwork * Propertly disable raw-rs tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Adam <adamgerhant@gmail.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Keavon Chambers
Adam
Dennis Kobert
parent
fd3613018a
commit
1652c713a6
@@ -356,5 +356,8 @@ mod tests {
|
||||
|
||||
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
||||
assert_eq!(bezier2.project(DVec2::new(100., 0.)), 0.);
|
||||
|
||||
let bezier3 = Bezier::from_cubic_coordinates(-50.0, -50.0, -50.0, -50.0, 50.0, -50.0, 50.0, -50.0);
|
||||
assert_eq!(DVec2::new(0., -50.), bezier3.evaluate(TValue::Parametric(bezier3.project(DVec2::new(0., -50.)))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,14 @@ impl BezierHandles {
|
||||
matches!(self, Self::Cubic { .. })
|
||||
}
|
||||
|
||||
pub fn is_finite(&self) -> bool {
|
||||
match self {
|
||||
BezierHandles::Linear => true,
|
||||
BezierHandles::Quadratic { handle } => handle.is_finite(),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => handle_start.is_finite() && handle_end.is_finite(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn start(&self) -> Option<DVec2> {
|
||||
match *self {
|
||||
@@ -64,6 +72,18 @@ impl BezierHandles {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_start(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } = self {
|
||||
*handle_start += delta
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_end(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_end, .. } = self {
|
||||
*handle_end += delta
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.
|
||||
#[must_use]
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {
|
||||
@@ -80,6 +100,17 @@ impl BezierHandles {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn flipped(self) -> Self {
|
||||
match self {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => Self::Cubic {
|
||||
handle_start: handle_end,
|
||||
handle_end: handle_start,
|
||||
},
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dyn-any")]
|
||||
|
||||
@@ -347,7 +347,7 @@ impl Bezier {
|
||||
/// - `distance` - The offset's distance from the curve. Positive values will offset the curve in the same direction as the endpoint normals,
|
||||
/// while negative values will offset in the opposite direction.
|
||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#bezier/offset/solo" title="Offset Demo"></iframe>
|
||||
pub fn offset<ManipulatorGroupId: crate::Identifier>(&self, distance: f64) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn offset<PointId: crate::Identifier>(&self, distance: f64) -> Subpath<PointId> {
|
||||
if self.is_point() {
|
||||
return Subpath::from_bezier(self);
|
||||
}
|
||||
@@ -375,7 +375,7 @@ impl Bezier {
|
||||
/// Version of the `offset` function which scales the offset such that the start of the offset is `start_distance` from the original curve, while the end of
|
||||
/// of the offset is `end_distance` from the original curve. The curve transitions from `start_distance` to `end_distance` gradually, proportional to the
|
||||
/// distance along the equation (`t`-value) of the curve. Similarly to the `offset` function, the returned result is an approximation.
|
||||
pub fn graduated_offset<ManipulatorGroupId: crate::Identifier>(&self, start_distance: f64, end_distance: f64) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn graduated_offset<PointId: crate::Identifier>(&self, start_distance: f64, end_distance: f64) -> Subpath<PointId> {
|
||||
let reduced = self.reduce(None);
|
||||
let mut next_start_distance = start_distance;
|
||||
let distance_difference = end_distance - start_distance;
|
||||
@@ -414,7 +414,7 @@ impl Bezier {
|
||||
/// Outline takes the following parameter:
|
||||
/// - `distance` - The outline's distance from the curve.
|
||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#bezier/outline/solo" title="Outline Demo"></iframe>
|
||||
pub fn outline<ManipulatorGroupId: crate::Identifier>(&self, distance: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn outline<PointId: crate::Identifier>(&self, distance: f64, cap: Cap) -> Subpath<PointId> {
|
||||
let (pos_offset, neg_offset) = if self.is_point() {
|
||||
(
|
||||
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance)], false),
|
||||
@@ -434,13 +434,13 @@ impl Bezier {
|
||||
/// Version of the `outline` function which draws the outline at the specified distances away from the curve.
|
||||
/// The outline begins `start_distance` away, and gradually move to being `end_distance` away.
|
||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#bezier/graduated-outline/solo" title="Graduated Outline Demo"></iframe>
|
||||
pub fn graduated_outline<ManipulatorGroupId: crate::Identifier>(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn graduated_outline<PointId: crate::Identifier>(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath<PointId> {
|
||||
self.skewed_outline(start_distance, end_distance, end_distance, start_distance, cap)
|
||||
}
|
||||
|
||||
/// Version of the `graduated_outline` function that allows for the 4 corners of the outline to be different distances away from the curve.
|
||||
/// <iframe frameBorder="0" width="100%" height="475px" src="https://graphite.rs/libraries/bezier-rs#bezier/skewed-outline/solo" title="Skewed Outline Demo"></iframe>
|
||||
pub fn skewed_outline<ManipulatorGroupId: crate::Identifier>(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn skewed_outline<PointId: crate::Identifier>(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath<PointId> {
|
||||
let (pos_offset, neg_offset) = if self.is_point() {
|
||||
(
|
||||
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance1)], false),
|
||||
@@ -776,7 +776,7 @@ mod tests {
|
||||
.all(|(curve, t_pair)| curve.abs_diff_eq(&bezier.trim(TValue::Parametric(t_pair[0]), TValue::Parametric(t_pair[1])), MAX_ABSOLUTE_DIFFERENCE)))
|
||||
}
|
||||
|
||||
fn assert_valid_offset<ManipulatorGroupId: crate::Identifier>(bezier: &Bezier, offset: &Subpath<ManipulatorGroupId>, expected_distance: f64) {
|
||||
fn assert_valid_offset<PointId: crate::Identifier>(bezier: &Bezier, offset: &Subpath<PointId>, expected_distance: f64) {
|
||||
// Verify that the offset is smooth
|
||||
if offset.len() > 1 {
|
||||
offset.iter().take(offset.len() - 2).zip(offset.iter().skip(1)).for_each(|beziers_pair| {
|
||||
|
||||
@@ -37,6 +37,6 @@ pub fn compare_arcs(arc1: CircleArc, arc2: CircleArc) -> bool {
|
||||
/// Compare Subpath by verifying that their bezier segments match.
|
||||
/// In this way, matching quadratic segments where the handles are on opposite manipulator groups will be considered equal.
|
||||
#[cfg(test)]
|
||||
pub fn compare_subpaths<ManipulatorGroupId: crate::Identifier>(subpath1: &Subpath<ManipulatorGroupId>, subpath2: &Subpath<ManipulatorGroupId>) -> bool {
|
||||
pub fn compare_subpaths<PointId: crate::Identifier>(subpath1: &Subpath<PointId>, subpath2: &Subpath<PointId>) -> bool {
|
||||
subpath1.len() == subpath2.len() && subpath1.closed() == subpath2.closed() && subpath1.iter().eq(subpath2.iter())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![allow(dead_code, unused_imports, unused_import_braces)]
|
||||
|
||||
pub(crate) mod compare;
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ use glam::DVec2;
|
||||
use std::fmt::Write;
|
||||
|
||||
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
|
||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
|
||||
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
|
||||
#[track_caller]
|
||||
pub fn new(manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>, closed: bool) -> Self {
|
||||
pub fn new(manipulator_groups: Vec<ManipulatorGroup<PointId>>, closed: bool) -> Self {
|
||||
assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
|
||||
Self { manipulator_groups, closed }
|
||||
}
|
||||
@@ -22,13 +22,13 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: bezier.start(),
|
||||
in_handle: None,
|
||||
out_handle: bezier.handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: bezier.end(),
|
||||
in_handle: bezier.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
@@ -48,17 +48,17 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: first.start(),
|
||||
in_handle: None,
|
||||
out_handle: first.handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
}];
|
||||
let mut inner_groups: Vec<ManipulatorGroup<ManipulatorGroupId>> = beziers
|
||||
let mut inner_groups: Vec<ManipulatorGroup<PointId>> = beziers
|
||||
.windows(2)
|
||||
.map(|bezier_pair| ManipulatorGroup {
|
||||
anchor: bezier_pair[1].start(),
|
||||
in_handle: bezier_pair[0].handle_end(),
|
||||
out_handle: bezier_pair[1].handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>();
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>();
|
||||
manipulator_groups.append(&mut inner_groups);
|
||||
|
||||
let last = beziers.last().unwrap();
|
||||
@@ -67,7 +67,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: last.end(),
|
||||
in_handle: last.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
});
|
||||
return Subpath::new(manipulator_groups, false);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
||||
pub fn iter(&self) -> SubpathIter<ManipulatorGroupId> {
|
||||
pub fn iter(&self) -> SubpathIter<PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
@@ -113,7 +113,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.
|
||||
pub fn iter_closed(&self) -> SubpathIter<ManipulatorGroupId> {
|
||||
pub fn iter_closed(&self) -> SubpathIter<PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
@@ -122,12 +122,12 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<ManipulatorGroupId>] {
|
||||
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<PointId>] {
|
||||
&self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
||||
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<PointId>> {
|
||||
&mut self.manipulator_groups
|
||||
}
|
||||
|
||||
@@ -232,9 +232,13 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
|
||||
/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.
|
||||
pub fn new_rounded_rect(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {
|
||||
if corner_radii.iter().all(|radii| radii.abs() < f64::EPSILON * 100.) {
|
||||
return Self::new_rect(corner1, corner2);
|
||||
}
|
||||
|
||||
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
||||
|
||||
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
||||
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<PointId>> {
|
||||
let point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
let point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
if radius == 0. {
|
||||
@@ -245,10 +249,8 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||
let handle_offset = radius * HANDLE_OFFSET_FACTOR;
|
||||
vec![
|
||||
ManipulatorGroup::new_anchor(point1),
|
||||
ManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),
|
||||
ManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),
|
||||
ManipulatorGroup::new_anchor(point2),
|
||||
]
|
||||
};
|
||||
Self::new(
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::utils::{SubpathTValue, TValue, TValueType};
|
||||
use glam::DVec2;
|
||||
|
||||
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
/// Return a selection of equidistant points on the bezier curve.
|
||||
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::utils::f64_compare;
|
||||
use crate::{SubpathTValue, TValue};
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
/// Get whether the subpath is closed.
|
||||
pub fn closed(&self) -> bool {
|
||||
self.closed
|
||||
@@ -14,40 +14,40 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
self.closed = new_closed;
|
||||
}
|
||||
|
||||
/// Access a [ManipulatorGroup] from a ManipulatorGroupId.
|
||||
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup<ManipulatorGroupId>> {
|
||||
/// Access a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_from_id(&self, id: PointId) -> Option<&ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access a mutable [ManipulatorGroup] from a ManipulatorGroupId.
|
||||
pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup<ManipulatorGroupId>> {
|
||||
/// Access a mutable [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_mut_from_id(&mut self, id: PointId) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access the index of a [ManipulatorGroup] from a ManipulatorGroupId.
|
||||
pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option<usize> {
|
||||
/// Access the index of a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_index_from_id(&self, id: PointId) -> Option<usize> {
|
||||
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Insert a manipulator group at an index.
|
||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Inserting non finite manipulator group");
|
||||
self.manipulator_groups.insert(index, group)
|
||||
}
|
||||
|
||||
/// Push a manipulator group to the end.
|
||||
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Pushing non finite manipulator group");
|
||||
self.manipulator_groups.push(group)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the last manipulator
|
||||
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<ManipulatorGroupId>> {
|
||||
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.last_mut()
|
||||
}
|
||||
|
||||
/// Remove a manipulator group at an index.
|
||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<ManipulatorGroupId> {
|
||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<PointId> {
|
||||
self.manipulator_groups.remove(index)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: first.end(),
|
||||
in_handle: first.handle_end(),
|
||||
out_handle: second.handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
};
|
||||
let number_of_groups = self.manipulator_groups.len() + 1;
|
||||
self.manipulator_groups.insert((segment_index) + 1, new_group);
|
||||
@@ -89,7 +89,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: bezier.start(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
}];
|
||||
}
|
||||
let mut last_index = self.manipulator_groups.len() - 1;
|
||||
@@ -114,7 +114,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: bezier.end(),
|
||||
in_handle: bezier.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,25 +15,25 @@ use std::ops::{Index, IndexMut};
|
||||
/// Structure used to represent a path composed of [Bezier] curves.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Subpath<ManipulatorGroupId: crate::Identifier> {
|
||||
manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>,
|
||||
pub struct Subpath<PointId: crate::Identifier> {
|
||||
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||
pub closed: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "dyn-any")]
|
||||
unsafe impl<ManipulatorGroupId: crate::Identifier> dyn_any::StaticType for Subpath<ManipulatorGroupId> {
|
||||
type Static = Subpath<ManipulatorGroupId>;
|
||||
unsafe impl<PointId: crate::Identifier> dyn_any::StaticType for Subpath<PointId> {
|
||||
type Static = Subpath<PointId>;
|
||||
}
|
||||
|
||||
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
|
||||
pub struct SubpathIter<'a, ManipulatorGroupId: crate::Identifier> {
|
||||
pub struct SubpathIter<'a, PointId: crate::Identifier> {
|
||||
index: usize,
|
||||
subpath: &'a Subpath<ManipulatorGroupId>,
|
||||
subpath: &'a Subpath<PointId>,
|
||||
is_always_closed: bool,
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Index<usize> for Subpath<ManipulatorGroupId> {
|
||||
type Output = ManipulatorGroup<ManipulatorGroupId>;
|
||||
impl<PointId: crate::Identifier> Index<usize> for Subpath<PointId> {
|
||||
type Output = ManipulatorGroup<PointId>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
|
||||
@@ -41,14 +41,14 @@ impl<ManipulatorGroupId: crate::Identifier> Index<usize> for Subpath<Manipulator
|
||||
}
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> IndexMut<usize> for Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> IndexMut<usize> for Subpath<PointId> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
|
||||
&mut self.manipulator_groups[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Iterator for SubpathIter<'_, ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Iterator for SubpathIter<'_, PointId> {
|
||||
type Item = Bezier;
|
||||
|
||||
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
|
||||
@@ -73,7 +73,7 @@ impl<ManipulatorGroupId: crate::Identifier> Iterator for SubpathIter<'_, Manipul
|
||||
}
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Debug for Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Debug for Subpath<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("Subpath").field("closed", &self.closed).field("manipulator_groups", &self.manipulator_groups).finish()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::TValue;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
/// Calculate the point on the subpath based on the parametric `t`-value provided.
|
||||
/// Expects `t` to be within the inclusive range `[0, 1]`.
|
||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/evaluate/solo" title="Evaluate Demo"></iframe>
|
||||
@@ -39,7 +39,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// This function expects the following:
|
||||
/// - other: a [Bezier] curve to check intersections against
|
||||
/// - error: an optional f64 value to provide an error bound
|
||||
pub fn subpath_intersections(&self, other: &Subpath<ManipulatorGroupId>, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||
pub fn subpath_intersections(&self, other: &Subpath<PointId>, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||
let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect();
|
||||
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
intersection_t_values
|
||||
@@ -302,6 +302,18 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>() != 0
|
||||
}
|
||||
|
||||
/// Does a path contain a point? Based on the non zero winding. Automatically adds a linear segment if the subpath is not closed.
|
||||
pub fn contains_point_autoclose(&self, target_point: DVec2) -> bool {
|
||||
let mut winding = self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>();
|
||||
if !self.closed {
|
||||
if let [Some(first), Some(last)] = [self.manipulator_groups.first(), self.manipulator_groups.last()] {
|
||||
winding += Bezier::from_linear_dvec2(first.anchor, last.anchor).winding(target_point);
|
||||
}
|
||||
}
|
||||
|
||||
winding != 0
|
||||
}
|
||||
|
||||
/// Randomly places points across the filled surface of this subpath (which is assumed to be closed).
|
||||
/// The `separation_disk_diameter` determines the minimum distance between all points from one another.
|
||||
/// Conceptually, this works by "throwing a dart" at the subpath's bounding box and keeping the dart only if:
|
||||
@@ -342,7 +354,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// Alternatively, this can be interpreted as limiting the angle that the miter can form.
|
||||
/// When the limit is exceeded, no manipulator group will be returned.
|
||||
/// This value should be at least 1. If not, the default of 4 will be used.
|
||||
pub(crate) fn miter_line_join(&self, other: &Subpath<ManipulatorGroupId>, miter_limit: Option<f64>) -> Option<ManipulatorGroup<ManipulatorGroupId>> {
|
||||
pub(crate) fn miter_line_join(&self, other: &Subpath<PointId>, miter_limit: Option<f64>) -> Option<ManipulatorGroup<PointId>> {
|
||||
let miter_limit = match miter_limit {
|
||||
Some(miter_limit) if miter_limit >= 1. => miter_limit,
|
||||
_ => 4.,
|
||||
@@ -371,7 +383,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: intersection,
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -384,7 +396,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// - The `out_handle` for the last manipulator group of `self`
|
||||
/// - The new manipulator group to be added
|
||||
/// - The `in_handle` for the first manipulator group of `other`
|
||||
pub(crate) fn round_line_join(&self, other: &Subpath<ManipulatorGroupId>, center: DVec2) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
||||
pub(crate) fn round_line_join(&self, other: &Subpath<PointId>, center: DVec2) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||
let right = other.manipulator_groups[0].anchor;
|
||||
|
||||
@@ -410,7 +422,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// - The `out_handle` for the last manipulator group of `self`
|
||||
/// - The new manipulator group to be added
|
||||
/// - The `in_handle` for the first manipulator group of `other`
|
||||
pub(crate) fn round_cap(&self, other: &Subpath<ManipulatorGroupId>) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
||||
pub(crate) fn round_cap(&self, other: &Subpath<PointId>) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||
let right = other.manipulator_groups[0].anchor;
|
||||
|
||||
@@ -423,7 +435,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns the two manipulator groups that create a square cap between the end of `self` and the beginning of `other`.
|
||||
pub(crate) fn square_cap(&self, other: &Subpath<ManipulatorGroupId>) -> [ManipulatorGroup<ManipulatorGroupId>; 2] {
|
||||
pub(crate) fn square_cap(&self, other: &Subpath<PointId>) -> [ManipulatorGroup<PointId>; 2] {
|
||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||
let right = other.manipulator_groups[0].anchor;
|
||||
|
||||
|
||||
@@ -26,15 +26,15 @@ impl Identifier for EmptyId {
|
||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroup<ManipulatorGroupId: crate::Identifier> {
|
||||
pub struct ManipulatorGroup<PointId: crate::Identifier> {
|
||||
pub anchor: DVec2,
|
||||
pub in_handle: Option<DVec2>,
|
||||
pub out_handle: Option<DVec2>,
|
||||
pub id: ManipulatorGroupId,
|
||||
pub id: PointId,
|
||||
}
|
||||
|
||||
// TODO: Remove once we no longer need to hash floats in Graphite
|
||||
impl<ManipulatorGroupId: crate::Identifier> Hash for ManipulatorGroup<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Hash for ManipulatorGroup<PointId> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.in_handle.is_some().hash(state);
|
||||
@@ -50,11 +50,11 @@ impl<ManipulatorGroupId: crate::Identifier> Hash for ManipulatorGroup<Manipulato
|
||||
}
|
||||
|
||||
#[cfg(feature = "dyn-any")]
|
||||
unsafe impl<ManipulatorGroupId: crate::Identifier> dyn_any::StaticType for ManipulatorGroup<ManipulatorGroupId> {
|
||||
type Static = ManipulatorGroup<ManipulatorGroupId>;
|
||||
unsafe impl<PointId: crate::Identifier> dyn_any::StaticType for ManipulatorGroup<PointId> {
|
||||
type Static = ManipulatorGroup<PointId>;
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> Debug for ManipulatorGroup<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Debug for ManipulatorGroup<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("ManipulatorGroup")
|
||||
.field("anchor", &self.anchor)
|
||||
@@ -64,10 +64,10 @@ impl<ManipulatorGroupId: crate::Identifier> Debug for ManipulatorGroup<Manipulat
|
||||
}
|
||||
}
|
||||
|
||||
impl<ManipulatorGroupId: crate::Identifier> ManipulatorGroup<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> ManipulatorGroup<PointId> {
|
||||
/// Construct a new manipulator group from an anchor, in handle and out handle
|
||||
pub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {
|
||||
let id = ManipulatorGroupId::new();
|
||||
let id = PointId::new();
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
@@ -77,17 +77,17 @@ impl<ManipulatorGroupId: crate::Identifier> ManipulatorGroup<ManipulatorGroupId>
|
||||
}
|
||||
|
||||
/// Construct a new manipulator group from an anchor, in handle, out handle and an id
|
||||
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: ManipulatorGroupId) -> Self {
|
||||
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: PointId) -> Self {
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
/// Construct a new manipulator point with just an anchor position and an id
|
||||
pub fn new_anchor_with_id(anchor: DVec2, id: ManipulatorGroupId) -> Self {
|
||||
pub fn new_anchor_with_id(anchor: DVec2, id: PointId) -> Self {
|
||||
Self::new_with_id(anchor, Some(anchor), Some(anchor), id)
|
||||
}
|
||||
|
||||
/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.
|
||||
pub fn to_bezier(&self, end_group: &ManipulatorGroup<ManipulatorGroupId>) -> Bezier {
|
||||
pub fn to_bezier(&self, end_group: &ManipulatorGroup<PointId>) -> Bezier {
|
||||
let start = self.anchor;
|
||||
let end = end_group.anchor;
|
||||
let out_handle = self.out_handle;
|
||||
|
||||
@@ -18,12 +18,12 @@ fn map_index_within_range(index: usize, t: f64, max_size: usize) -> (usize, f64)
|
||||
}
|
||||
|
||||
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
|
||||
/// If the original Subpath was closed, a single open Subpath will be returned.
|
||||
/// If the original Subpath was open, two open Subpaths will be returned.
|
||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/split/solo" title="Split Demo"></iframe>
|
||||
pub fn split(&self, t: SubpathTValue) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
|
||||
pub fn split(&self, t: SubpathTValue) -> (Subpath<PointId>, Option<Subpath<PointId>>) {
|
||||
let (segment_index, t) = self.t_value_to_parametric(t);
|
||||
let curve = self.get_segment(segment_index).unwrap();
|
||||
|
||||
@@ -48,7 +48,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: last_curve.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
});
|
||||
} else {
|
||||
if !first_split.is_empty() {
|
||||
@@ -68,7 +68,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: first_bezier.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: second_bezier.start(),
|
||||
in_handle: None,
|
||||
out_handle: second_bezier.handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns [ManipulatorGroup]s with a reversed winding order.
|
||||
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>]) -> Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
||||
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>]) -> Vec<ManipulatorGroup<PointId>> {
|
||||
manipulator_groups
|
||||
.iter()
|
||||
.rev()
|
||||
@@ -103,14 +103,14 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: group.anchor,
|
||||
in_handle: group.out_handle,
|
||||
out_handle: group.in_handle,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||
}
|
||||
|
||||
/// Returns a [Subpath] with a reversed winding order.
|
||||
/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction
|
||||
pub fn reverse(&self) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn reverse(&self) -> Subpath<PointId> {
|
||||
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
|
||||
if self.closed {
|
||||
reversed.rotate_right(1);
|
||||
@@ -127,7 +127,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`.
|
||||
/// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`.
|
||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/trim/solo" title="Trim Demo"></iframe>
|
||||
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<PointId> {
|
||||
// Return a clone of the Subpath if it is not long enough to be a valid Bezier
|
||||
if self.manipulator_groups.is_empty() {
|
||||
return Subpath {
|
||||
@@ -196,7 +196,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
|
||||
cloned_manipulator_groups
|
||||
.drain(range_start..range_end.min(cloned_manipulator_groups.len()))
|
||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||
};
|
||||
|
||||
// Adjust curve indices to match the cloned list
|
||||
@@ -255,7 +255,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: front_split.start(),
|
||||
in_handle: None,
|
||||
out_handle: front_split.handle_start(),
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
};
|
||||
|
||||
// Update the last two manipulator groups to match the back_split
|
||||
@@ -264,7 +264,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
anchor: back_split.end(),
|
||||
in_handle: back_split.handle_end(),
|
||||
out_handle: None,
|
||||
id: ManipulatorGroupId::new(),
|
||||
id: PointId::new(),
|
||||
};
|
||||
|
||||
Subpath {
|
||||
@@ -313,7 +313,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
// at the incorrect location. This can be avoided by first trimming the two Subpaths at any extrema, effectively ignoring loopbacks.
|
||||
/// Helper function to clip overlap of two intersecting open Subpaths. Returns an optional, as intersections may not exist for certain arrangements and distances.
|
||||
/// Assumes that the Subpaths represents simple Bezier segments, and clips the Subpaths at the last intersection of the first Subpath, and first intersection of the last Subpath.
|
||||
fn clip_simple_subpaths(subpath1: &Subpath<ManipulatorGroupId>, subpath2: &Subpath<ManipulatorGroupId>) -> Option<(Subpath<ManipulatorGroupId>, Subpath<ManipulatorGroupId>)> {
|
||||
fn clip_simple_subpaths(subpath1: &Subpath<PointId>, subpath2: &Subpath<PointId>) -> Option<(Subpath<PointId>, Subpath<PointId>)> {
|
||||
// Split the first subpath at its last intersection
|
||||
let intersections1 = subpath1.subpath_intersections(subpath2, None, None);
|
||||
if intersections1.is_empty() {
|
||||
@@ -335,7 +335,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
|
||||
/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).
|
||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#subpath/rotate/solo" title="Rotate Demo"></iframe>
|
||||
pub fn rotate(&self, angle: f64) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn rotate(&self, angle: f64) -> Subpath<PointId> {
|
||||
let mut rotated_subpath = self.clone();
|
||||
|
||||
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
|
||||
@@ -345,7 +345,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).
|
||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<PointId> {
|
||||
// Translate before and after the rotation to account for the pivot
|
||||
let translate: DAffine2 = DAffine2::from_translation(pivot);
|
||||
let rotate: DAffine2 = DAffine2::from_angle(angle);
|
||||
@@ -359,7 +359,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// Reduces the segments of the subpath into simple subcurves, then scales each subcurve a set `distance` away.
|
||||
/// The intersections of segments of the subpath are joined using the method specified by the `join` argument.
|
||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/offset/solo" title="Offset Demo"></iframe>
|
||||
pub fn offset(&self, distance: f64, join: Join) -> Subpath<ManipulatorGroupId> {
|
||||
pub fn offset(&self, distance: f64, join: Join) -> Subpath<PointId> {
|
||||
assert!(self.len_segments() > 1, "Cannot offset an empty Subpath.");
|
||||
|
||||
// An offset at a distance 0 from the curve is simply the same curve
|
||||
@@ -368,11 +368,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
let mut subpaths = self
|
||||
.iter()
|
||||
.filter(|bezier| !bezier.is_point())
|
||||
.map(|bezier| bezier.offset(distance))
|
||||
.collect::<Vec<Subpath<ManipulatorGroupId>>>();
|
||||
let mut subpaths = self.iter().filter(|bezier| !bezier.is_point()).map(|bezier| bezier.offset(distance)).collect::<Vec<Subpath<PointId>>>();
|
||||
let mut drop_common_point = vec![true; self.len()];
|
||||
|
||||
// Clip or join consecutive Subpaths
|
||||
@@ -489,8 +485,8 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
}
|
||||
|
||||
/// Helper function to combine the two offsets that make up an outline.
|
||||
pub(crate) fn combine_outline(&self, other: &Subpath<ManipulatorGroupId>, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
||||
let mut result_manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>> = vec![];
|
||||
pub(crate) fn combine_outline(&self, other: &Subpath<PointId>, cap: Cap) -> Subpath<PointId> {
|
||||
let mut result_manipulator_groups: Vec<ManipulatorGroup<PointId>> = vec![];
|
||||
result_manipulator_groups.extend_from_slice(self.manipulator_groups());
|
||||
match cap {
|
||||
Cap::Butt => {
|
||||
@@ -527,7 +523,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
/// - `distance` - The outline's distance from the curve.
|
||||
/// - `join` - The join type used to cap the endpoints of open bezier curves, and join successive subpath segments.
|
||||
/// <iframe frameBorder="0" width="100%" height="425px" src="https://graphite.rs/libraries/bezier-rs#subpath/outline/solo" title="Outline Demo"></iframe>
|
||||
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
|
||||
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath<PointId>, Option<Subpath<PointId>>) {
|
||||
let is_point = self.is_point();
|
||||
let (pos_offset, neg_offset) = if is_point {
|
||||
let point = self.manipulator_groups[0].anchor;
|
||||
|
||||
@@ -266,13 +266,7 @@ pub fn scale_point_from_origin(point: DVec2, origin: DVec2, should_flip_directio
|
||||
|
||||
/// Computes the necessary details to form a circular join from `left` to `right`, along a circle around `center`.
|
||||
/// By default, the angle is assumed to be 180 degrees.
|
||||
pub fn compute_circular_subpath_details<ManipulatorGroupId: crate::Identifier>(
|
||||
left: DVec2,
|
||||
arc_point: DVec2,
|
||||
right: DVec2,
|
||||
center: DVec2,
|
||||
angle: Option<f64>,
|
||||
) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
||||
pub fn compute_circular_subpath_details<PointId: crate::Identifier>(left: DVec2, arc_point: DVec2, right: DVec2, center: DVec2, angle: Option<f64>) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||
let center_to_arc_point = arc_point - center;
|
||||
|
||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#![doc(html_root_url = "http://docs.rs/const-default/1.0.0")]
|
||||
#![cfg_attr(feature = "unstable-docs", feature(doc_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "derive")))]
|
||||
pub use dyn_any_derive::DynAny;
|
||||
|
||||
/// Implement this trait for your `dyn Trait` types for all `T: Trait`
|
||||
|
||||
@@ -12,15 +12,15 @@ homepage = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw
|
||||
repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw-rs"
|
||||
documentation = "https://docs.rs/raw-rs"
|
||||
|
||||
[features]
|
||||
raw-rs-tests = []
|
||||
|
||||
[dependencies]
|
||||
bitstream-io = "2.3.0"
|
||||
num_enum = "0.7.2"
|
||||
thiserror = { workspace = true }
|
||||
tag-derive = { path = "tag-derive" }
|
||||
libraw-rs = { version = "0.0.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
libraw-rs = "0.0.4"
|
||||
downloader = "0.2.7"
|
||||
|
||||
[features]
|
||||
raw-rs-tests = ["libraw-rs"]
|
||||
|
||||
@@ -7,13 +7,13 @@ use std::path::Path;
|
||||
use raw_rs::RawImage;
|
||||
|
||||
use downloader::{Download, Downloader};
|
||||
use libraw::Processor;
|
||||
|
||||
const TEST_FILES: [&str; 3] = ["ILCE-7M3-ARW2.3.5-blossoms.arw", "ILCE-7RM4-ARW2.3.5-kestrel.arw", "ILCE-6000-ARW2.3.1-windsock.arw"];
|
||||
const BASE_URL: &str = "https://static.graphite.rs/test-data/libraries/raw-rs/";
|
||||
const BASE_PATH: &str = "./tests/images";
|
||||
|
||||
#[cfg_attr(feature = "raw-rs-tests", test)]
|
||||
#[test]
|
||||
#[cfg(feature = "raw-rs-tests")]
|
||||
fn test_images_match_with_libraw() {
|
||||
download_images();
|
||||
|
||||
@@ -54,6 +54,7 @@ fn test_images_match_with_libraw() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "raw-rs-tests")]
|
||||
fn download_images() {
|
||||
let mut path = Path::new(BASE_PATH).to_owned();
|
||||
let mut downloads: Vec<Download> = Vec::new();
|
||||
@@ -74,8 +75,9 @@ fn download_images() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "raw-rs-tests")]
|
||||
fn test_raw_data(content: &[u8]) -> Result<RawImage, String> {
|
||||
let processor = Processor::new();
|
||||
let processor = libraw::Processor::new();
|
||||
let libraw_raw_image = processor.decode(content).unwrap();
|
||||
|
||||
let mut content = Cursor::new(content);
|
||||
@@ -147,8 +149,9 @@ fn test_raw_data(content: &[u8]) -> Result<RawImage, String> {
|
||||
Ok(raw_image)
|
||||
}
|
||||
|
||||
#[cfg(feature = "raw-rs-tests")]
|
||||
fn test_final_image(content: &[u8], raw_image: RawImage) -> Result<(), String> {
|
||||
let processor = Processor::new();
|
||||
let processor = libraw::Processor::new();
|
||||
let libraw_image = processor.process_8bit(content).unwrap();
|
||||
|
||||
let image = raw_rs::process_8bit(raw_image);
|
||||
|
||||
Reference in New Issue
Block a user