mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Add new node "Decimate"
This commit is contained in:
parent
a182a7347e
commit
c48c704ebe
2 changed files with 188 additions and 0 deletions
|
@ -2005,6 +2005,103 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."),
|
||||
properties: Some("sample_polyline_properties"),
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Decimate",
|
||||
category: "Vector: Modifier",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(f64), 1)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::DecimateNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::F64(1.0), false), // Tolerance
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Monitor".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Decimate".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Memoize".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
("Vector Data", "The polyline to be simplified.").into(),
|
||||
PropertiesRow::with_override(
|
||||
"Tolerance",
|
||||
"Maximum allowed deviation from the original polyline.",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(0.0),
|
||||
step: Some(0.1),
|
||||
unit: Some(" px".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Simplifies a polyline using the Ramer–Douglas–Peucker algorithm."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Scatter Points",
|
||||
category: "Vector: Modifier",
|
||||
|
|
|
@ -1138,6 +1138,97 @@ where
|
|||
output_table
|
||||
}
|
||||
|
||||
/// Simplifies a polyline using the Ramer–Douglas–Peucker algorithm.
|
||||
#[node_macro::node(category("Vector: Modifier"), name("Decimate"), path(graphene_core::vector))]
|
||||
async fn decimate(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[default(1.0)]
|
||||
#[hard_min(0.0)]
|
||||
tolerance: f64,
|
||||
) -> VectorDataTable {
|
||||
let mut result_table = VectorDataTable::default();
|
||||
|
||||
for vector_data_instance in vector_data.instance_iter() {
|
||||
let mut result = VectorData {
|
||||
style: vector_data_instance.instance.style.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for subpath in vector_data_instance.instance.stroke_bezier_paths() {
|
||||
let points: Vec<DVec2> = subpath.manipulator_groups().iter().map(|g| g.anchor).collect();
|
||||
let closed = subpath.closed();
|
||||
|
||||
let simplified = if points.len() > 2 { douglas_peucker(&points, tolerance, closed) } else { points };
|
||||
|
||||
// Rebuild the subpath from the simplified points
|
||||
let mut new_groups = Vec::new();
|
||||
let mut id_gen = PointId::generate();
|
||||
for pt in simplified {
|
||||
new_groups.push(bezier_rs::ManipulatorGroup {
|
||||
anchor: pt,
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: id_gen,
|
||||
});
|
||||
id_gen = id_gen.next_id();
|
||||
}
|
||||
let new_subpath = Subpath::new(new_groups, closed);
|
||||
result.append_subpath(new_subpath, closed);
|
||||
}
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: result,
|
||||
transform: vector_data_instance.transform,
|
||||
alpha_blending: vector_data_instance.alpha_blending,
|
||||
source_node_id: vector_data_instance.source_node_id,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
fn douglas_peucker(points: &[DVec2], epsilon: f64, closed: bool) -> Vec<DVec2> {
|
||||
if points.len() < 2 {
|
||||
return points.to_vec();
|
||||
}
|
||||
|
||||
let (_first, _lastt) = (0, points.len() - 1);
|
||||
|
||||
// For closed paths, treat as open for simplification, then close at the end
|
||||
let (_first, _last, _is_closed) = if closed && points.len() > 2 { (0, points.len() - 1, true) } else { (0, points.len() - 1, false) };
|
||||
|
||||
let mut dmax = 0.0;
|
||||
let mut index = 0;
|
||||
|
||||
for i in (_first + 1).._last {
|
||||
let d = perpendicular_distance(points[i], points[_first], points[_last]);
|
||||
if d > dmax {
|
||||
index = i;
|
||||
dmax = d;
|
||||
}
|
||||
}
|
||||
|
||||
if dmax > epsilon {
|
||||
let mut rec_results1 = douglas_peucker(&points[_first..=index], epsilon, false);
|
||||
let mut rec_results2 = douglas_peucker(&points[index..=_last], epsilon, false);
|
||||
|
||||
// Remove the last point of the first list to avoid duplication
|
||||
rec_results1.pop();
|
||||
rec_results1.append(&mut rec_results2);
|
||||
rec_results1
|
||||
} else {
|
||||
vec![points[_first], points[_last]]
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the perpendicular distance from a point to a line defined by two points.
|
||||
fn perpendicular_distance(point: DVec2, line_start: DVec2, line_end: DVec2) -> f64 {
|
||||
let num = ((line_end.y - line_start.y) * point.x - (line_end.x - line_start.x) * point.y + line_end.x * line_start.y - line_end.y * line_start.x).abs();
|
||||
let den = (line_end - line_start).length();
|
||||
if den.abs() < 1e-10 { (point - line_start).length() } else { num / den }
|
||||
}
|
||||
|
||||
/// Convert vector geometry into a polyline composed of evenly spaced points.
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn sample_polyline(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue