Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more) (#2610)

* Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more)

* Fix test?

* Fix more tests?

* Fix moar test??

* Clean up instance method naming
This commit is contained in:
Keavon Chambers 2025-04-22 17:55:57 -07:00 committed by GitHub
parent a29802de36
commit ac9fb2b02d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 811 additions and 529 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -15,7 +15,7 @@ use graph_craft::document::value::*;
use graph_craft::document::*;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{Color, RedGreenBlue, RedGreenBlueAlpha};
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha};
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorDataTable;
@ -128,6 +128,87 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
properties: Some("identity_properties"),
},
DocumentNodeDefinition {
identifier: "Cache",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::FreezeRealTimeNode")),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::BoundlessFootprintNode")),
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::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".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![("Data", "TODO").into()],
output_names: vec!["Data".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Monitor",
@ -716,6 +797,87 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Rasterizes the given vector data"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Noise Pattern",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::Bool(true), false),
NodeInput::value(TaggedValue::U32(0), false),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::NoiseType(NoiseType::default()), false),
NodeInput::value(TaggedValue::DomainWarpType(DomainWarpType::default()), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::FractalType(FractalType::default()), false),
NodeInput::value(TaggedValue::U32(3), false),
NodeInput::value(TaggedValue::F64(2.), false),
NodeInput::value(TaggedValue::F64(0.5), false),
NodeInput::value(TaggedValue::F64(0.), false), // 0-1 range
NodeInput::value(TaggedValue::F64(2.), false),
NodeInput::value(TaggedValue::CellularDistanceFunction(CellularDistanceFunction::default()), false),
NodeInput::value(TaggedValue::CellularReturnType(CellularReturnType::default()), false),
NodeInput::value(TaggedValue::F64(1.), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("Spacer", "TODO").into(),
("Clip", "TODO").into(),
("Seed", "TODO").into(),
PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
PropertiesRow::with_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
PropertiesRow::with_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
PropertiesRow::with_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
PropertiesRow::with_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
PropertiesRow::with_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
PropertiesRow::with_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
PropertiesRow::with_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
PropertiesRow::with_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
PropertiesRow::with_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
PropertiesRow::with_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
PropertiesRow::with_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
PropertiesRow::with_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
],
output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Noise Pattern".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cull".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("Generates different noise patterns."),
properties: None,
},
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
@ -3559,7 +3721,7 @@ impl DocumentNodeDefinition {
};
nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default);
//Recurse over all sub nodes if the current node is a network implementation
// Recurse over all sub-nodes if the current node is a network implementation
let mut current_path = path.clone();
current_path.push(current_node);
let DocumentNodeImplementation::Network(template_network) = &node_template.document_node.implementation else {

View file

@ -54,7 +54,11 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
ParameterExposeButton::new()
.exposed(exposed)
.data_type(data_type)
.tooltip("Expose this parameter as a node input in the graph")
.tooltip(if exposed {
"Stop exposing this parameter as a node input in the graph"
} else {
"Expose this parameter as a node input in the graph"
})
.on_update(move |_parameter| {
Message::Batched(Box::new([
NodeGraphMessage::ExposeInput {

View file

@ -1141,7 +1141,7 @@ impl NodeNetworkInterface {
pub fn input_description<'a>(&'a self, node_id: NodeId, index: usize, network_path: &[NodeId]) -> Option<&'a str> {
let Some(input_row) = self.input_properties_row(&node_id, index, network_path) else {
log::error!("Could not get node_metadata in input_description");
log::error!("Could not get input_row in input_description");
return None;
};
let description = input_row.input_description.as_str();
@ -1162,7 +1162,7 @@ impl NodeNetworkInterface {
pub fn input_metadata(&self, node_id: &NodeId, index: usize, field: &str, network_path: &[NodeId]) -> Option<&Value> {
let Some(input_row) = self.input_properties_row(node_id, index, network_path) else {
log::error!("Could not get node_metadata in get_input_metadata");
log::error!("Could not get input_row in get_input_metadata");
return None;
};
input_row.input_data.get(field)
@ -3781,6 +3781,7 @@ impl NodeNetworkInterface {
self.unload_stack_dependents(network_path);
}
// TODO: Eventually remove this document upgrade code
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) {
let Some(network) = self.network_mut(network_path) else {

View file

@ -862,6 +862,21 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);
}
}
if reference == "Instance on Points" && inputs_count == 2 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
}
}
// TODO: Eventually remove this document upgrade code

View file

@ -258,7 +258,7 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
}
let mut rows = self
.instances()
.instance_ref_iter()
.enumerate()
.map(|(index, instance)| {
vec![

View file

@ -293,7 +293,7 @@ impl NodeRuntime {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_instance().instance.clone());
self.vector_modify.insert(parent_network_node_id, record.output.one_instance_ref().instance.clone());
} else {
log::warn!("failed to downcast monitor node output {parent_network_node_id:?}");
}

View file

@ -1,5 +1,5 @@
use crate::application_io::{ImageTexture, TextureFrameTable};
use crate::instances::Instances;
use crate::instances::{Instance, Instances};
use crate::raster::BlendMode;
use crate::raster::image::{Image, ImageFrameTable};
use crate::transform::TransformMut;
@ -67,10 +67,12 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::empty();
for (graphic_element, source_node_id) in old.elements {
let last = graphic_group_table.push(graphic_element);
*last.source_node_id = source_node_id;
*last.transform = old.transform;
*last.alpha_blending = old.alpha_blending;
graphic_group_table.push(Instance {
instance: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
@ -78,12 +80,14 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
let mut graphic_group_table = GraphicGroupTable::empty();
for instance in old_table.instances() {
for instance in old_table.instance_ref_iter() {
for (graphic_element, source_node_id) in &instance.instance.elements {
let new_row = graphic_group_table.push(graphic_element.clone());
*new_row.source_node_id = *source_node_id;
*new_row.transform = *instance.transform;
*new_row.alpha_blending = *instance.alpha_blending;
graphic_group_table.push(Instance {
instance: graphic_element.clone(),
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
@ -276,8 +280,12 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = ArtboardGroupTable::empty();
for (artboard, source_node_id) in artboard_group.artboards {
let pushed = table.push(artboard);
*pushed.source_node_id = source_node_id;
table.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
@ -290,38 +298,18 @@ pub type ArtboardGroupTable = Instances<Artboard>;
#[node_macro::node(category(""))]
async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
// Get the penultimate element of the node path, or None if the path is too short
let pushed = stack.push(element);
*pushed.source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push(Instance {
instance: element,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
stack
}
// // TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
// #[node_macro::node(category("Debug"))]
// async fn concatenate<T: Clone>(
// _: impl Ctx,
// #[implementations(
// GraphicGroupTable,
// VectorDataTable,
// ImageFrameTable<Color>,
// TextureFrameTable,
// )]
// from: Instances<T>,
// #[expose]
// #[implementations(
// GraphicGroupTable,
// VectorDataTable,
// ImageFrameTable<Color>,
// TextureFrameTable,
// )]
// mut to: Instances<T>,
// ) -> Instances<T> {
// for instance in from.instances() {
// to.push_instance(instance);
// }
// to
// }
#[node_macro::node(category("Debug"))]
async fn to_element<Data: Into<GraphicElement> + 'n>(
_: impl Ctx,
@ -354,7 +342,7 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
for current_instance in current_group_table.instances() {
for current_instance in current_group_table.instance_ref_iter() {
let current_element = current_instance.instance.clone();
let reference = *current_instance.source_node_id;
@ -364,7 +352,7 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
// If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.instances_mut() {
for graphic_element in current_element.instance_mut_iter() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
}
@ -372,10 +360,12 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
_ => {
let pushed = output_group_table.push(current_element);
*pushed.source_node_id = reference;
// Apply the parent group's transform to the leaf element
*pushed.transform = *current_instance.transform * *pushed.transform;
output_group_table.push(Instance {
instance: current_element,
transform: *current_instance.transform,
alpha_blending: *current_instance.alpha_blending,
source_node_id: reference,
});
}
}
}
@ -427,8 +417,12 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
let pushed = artboards.push(artboard);
*pushed.source_node_id = encapsulating_node_id;
artboards.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}

View file

@ -299,7 +299,7 @@ pub trait GraphicElementRendered {
impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
render.parent_tag(
"g",
|attributes| {
@ -325,12 +325,12 @@ impl GraphicElementRendered for GraphicGroupTable {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let transform = transform * *instance.transform;
let alpha_blending = *instance.alpha_blending;
let mut layer = false;
if let Some(bounds) = self.instances().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
if let Some(bounds) = self.instance_ref_iter().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
let blend_mode = match render_params.view_mode {
ViewMode::Outline => peniko::Mix::Normal,
_ => alpha_blending.blend_mode.into(),
@ -356,13 +356,13 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.filter_map(|element| element.instance.bounding_box(transform * *element.transform))
.reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
if let Some(element_id) = instance.source_node_id {
let mut footprint = footprint;
footprint.transform *= *instance.transform;
@ -374,7 +374,7 @@ impl GraphicElementRendered for GraphicGroupTable {
if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
@ -390,7 +390,7 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
@ -404,11 +404,11 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn contains_artboard(&self) -> bool {
self.instances().any(|instance| instance.instance.contains_artboard())
self.instance_ref_iter().any(|instance| instance.instance.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.instance.new_ids_from_hash(*instance.source_node_id);
}
}
@ -420,7 +420,7 @@ impl GraphicElementRendered for GraphicGroupTable {
impl GraphicElementRendered for VectorDataTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let multiplied_transform = render.transform * *instance.transform;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
@ -469,7 +469,7 @@ impl GraphicElementRendered for VectorDataTable {
use vello::kurbo::{Cap, Join};
use vello::peniko;
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let multiplied_transform = parent_transform * *instance.transform;
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
@ -614,7 +614,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.flat_map(|instance| {
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
@ -633,7 +633,7 @@ impl GraphicElementRendered for VectorDataTable {
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance_transform = self.transform();
for instance in self.instances().map(|instance| instance.instance) {
for instance in self.instance_ref_iter().map(|instance| instance.instance) {
if let Some(element_id) = element_id {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None;
@ -661,7 +661,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
@ -679,7 +679,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}
}
@ -796,42 +796,42 @@ impl GraphicElementRendered for Artboard {
impl GraphicElementRendered for ArtboardGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for artboard in self.instances() {
for artboard in self.instance_ref_iter() {
artboard.instance.render_svg(render, render_params);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.render_to_vello(scene, transform, context, render_params);
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
self.instance_ref_iter().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.add_upstream_click_targets(click_targets);
}
}
fn contains_artboard(&self) -> bool {
self.instances().count() > 0
self.instance_ref_iter().count() > 0
}
}
impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let transform = *instance.transform * render.transform;
let image = &instance.instance;
@ -870,7 +870,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let image = &instance.instance;
if image.data.is_empty() {
return;
@ -883,7 +883,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.flat_map(|instance| {
let transform = transform * *instance.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
@ -939,7 +939,7 @@ impl GraphicElementRendered for RasterFrame {
match self {
RasterFrame::ImageFrame(image) => {
for instance in image.instances() {
for instance in image.instance_ref_iter() {
let image = &instance.instance;
if image.data.is_empty() {
return;
@ -951,7 +951,7 @@ impl GraphicElementRendered for RasterFrame {
}
}
RasterFrame::TextureFrame(image_texture) => {
for instance in image_texture.instances() {
for instance in image_texture.instance_ref_iter() {
let image =
vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat);

View file

@ -40,39 +40,15 @@ impl<T> Instances<T> {
}
}
pub fn push(&mut self, instance: T) -> InstanceMut<T> {
self.instance.push(instance);
self.transform.push(DAffine2::IDENTITY);
self.alpha_blending.push(AlphaBlending::default());
self.source_node_id.push(None);
InstanceMut {
instance: self.instance.last_mut().expect("Shouldn't be empty"),
transform: self.transform.last_mut().expect("Shouldn't be empty"),
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
}
pub fn push(&mut self, instance: Instance<T>) {
self.instance.push(instance.instance);
self.transform.push(instance.transform);
self.alpha_blending.push(instance.alpha_blending);
self.source_node_id.push(instance.source_node_id);
}
pub fn push_instance(&mut self, instance: Instance<T>) -> InstanceMut<T>
where
T: Clone,
{
self.instance.push(instance.instance.clone());
self.transform.push(*instance.transform);
self.alpha_blending.push(*instance.alpha_blending);
self.source_node_id.push(*instance.source_node_id);
InstanceMut {
instance: self.instance.last_mut().expect("Shouldn't be empty"),
transform: self.transform.last_mut().expect("Shouldn't be empty"),
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
}
}
pub fn one_instance(&self) -> Instance<T> {
Instance {
pub fn one_instance_ref(&self) -> InstanceRef<T> {
InstanceRef {
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
@ -91,12 +67,12 @@ impl<T> Instances<T> {
}
}
pub fn instances(&self) -> impl DoubleEndedIterator<Item = Instance<T>> {
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
instance,
transform,
@ -105,7 +81,21 @@ impl<T> Instances<T> {
})
}
pub fn instances_mut(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
self.instance
.iter_mut()
.zip(self.transform.iter_mut())
@ -119,12 +109,12 @@ impl<T> Instances<T> {
})
}
pub fn get(&self, index: usize) -> Option<Instance<T>> {
pub fn get(&self, index: usize) -> Option<InstanceRef<T>> {
if index >= self.instance.len() {
return None;
}
Some(Instance {
Some(InstanceRef {
instance: &self.instance[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
@ -199,12 +189,13 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
}
#[derive(Copy, Clone, Debug)]
pub struct Instance<'a, T> {
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub instance: &'a mut T,
@ -213,10 +204,32 @@ pub struct InstanceMut<'a, T> {
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Debug)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> Instance<T> {
pub fn to_graphic_element<U>(self) -> Instance<U>
where
T: Into<U>,
{
Instance {
instance: self.instance.into(),
transform: self.transform,
alpha_blending: self.alpha_blending,
source_node_id: self.source_node_id,
}
}
}
// VECTOR DATA TABLE
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl TransformMut for VectorDataTable {
@ -228,7 +241,7 @@ impl TransformMut for VectorDataTable {
// TEXTURE FRAME TABLE
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl TransformMut for TextureFrameTable {
@ -243,7 +256,7 @@ where
GraphicElement: From<Image<P>>,
{
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>

View file

@ -365,6 +365,30 @@ fn not_equals<U: core::cmp::PartialEq<T>, T>(
other_value != value
}
/// The less-than operation (<) compares two values and returns true if the first value is less than the second, or false if it is not.
/// If enabled with "Or Equal", the less-than-or-equal operation (<=) will be used instead.
#[node_macro::node(category("Math: Logic"))]
fn less_than<T: core::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
or_equal: bool,
) -> bool {
if or_equal { value <= other_value } else { value < other_value }
}
/// The greater-than operation (>) compares two values and returns true if the first value is greater than the second, or false if it is not.
/// If enabled with "Or Equal", the greater-than-or-equal operation (>=) will be used instead.
#[node_macro::node(category("Math: Logic"))]
fn greater_than<T: core::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
or_equal: bool,
) -> bool {
if or_equal { value >= other_value } else { value > other_value }
}
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
#[node_macro::node(category("Math: Logic"))]
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {

View file

@ -300,21 +300,21 @@ trait SetBlendMode {
impl SetBlendMode for VectorDataTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for GraphicGroupTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for ImageFrameTable<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}

View file

@ -598,7 +598,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result = self.clone();
for (over, under) in result.instances_mut().zip(under.instances()) {
for (over, under) in result.instance_mut_iter().zip(under.instance_ref_iter()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.instance = Image {
@ -731,7 +731,7 @@ where
GraphicElement: From<Image<P>>,
{
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
for c in instance.instance.data.iter_mut() {
*c = map_fn(c);
}
@ -1360,14 +1360,14 @@ impl MultiplyAlpha for Color {
}
impl MultiplyAlpha for VectorDataTable {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for GraphicGroupTable {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
@ -1377,7 +1377,7 @@ where
GraphicElement: From<Image<P>>,
{
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
@ -1577,7 +1577,7 @@ mod test {
let opacity = 100_f64;
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
let result = result.instances().next().unwrap().instance;
let result = result.instance_ref_iter().next().unwrap().instance;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View file

@ -31,7 +31,7 @@ struct BrushCacheImpl {
impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation.
if background.one_instance().instance != self.background.one_instance().instance {
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
self.background = background.clone();
return BrushPlan {
strokes: input.to_vec(),

View file

@ -232,7 +232,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
image: image.one_instance().instance.clone(),
image: image.one_instance_ref().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@ -274,7 +274,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance().instance.image.clone()),
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
@ -315,8 +315,8 @@ where
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image_transform = self.one_instance().transform;
let image = self.one_instance().instance;
let image_transform = self.one_instance_ref().transform;
let image = self.one_instance_ref().instance;
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
@ -333,19 +333,19 @@ where
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_instance().instance;
let image = self.one_instance_ref().instance;
image.width()
}
fn height(&self) -> u32 {
let image = self.one_instance().instance;
let image = self.one_instance_ref().instance;
image.height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_instance().instance;
let image = self.one_instance_ref().instance;
image.get_pixel(x, y)
}

View file

@ -189,7 +189,7 @@ async fn transform<T: 'n + 'static>(
let mut transform_target = transform_target.eval(ctx.into_context()).await;
for data_transform in transform_target.instances_mut() {
for data_transform in transform_target.instance_mut_iter() {
*data_transform.transform = matrix * *data_transform.transform;
}
@ -202,7 +202,7 @@ fn replace_transform<Data, TransformInput: Transform>(
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Instances<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Instances<Data> {
for data_transform in data.instances_mut() {
for data_transform in data.instance_mut_iter() {
*data_transform.transform = transform.transform();
}
data

View file

@ -1,39 +1,74 @@
use crate::instances::Instance;
use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use crate::instances::{InstanceRef, Instances};
use crate::raster::Color;
use crate::raster::image::ImageFrameTable;
use crate::transform::TransformMut;
use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
async fn instance_on_points(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: VectorDataTable,
#[implementations(Context -> VectorDataTable)] instance_node: impl Node<'n, Context<'static>, Output = VectorDataTable>,
) -> VectorDataTable {
let mut result = VectorDataTable::empty();
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
reverse: bool,
) -> GraphicGroupTable {
let mut result_table = GraphicGroupTable::empty();
for Instance { instance: points, transform, .. } in points.instances() {
for (index, &point) in points.point_domain.positions().iter().enumerate() {
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let instanced = instance_node.eval(new_ctx.into_context()).await;
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in instanced.instances() {
let instanced = result.push_instance(instanced);
*instanced.transform *= DAffine2::from_translation(transformed_point);
for mut instanced in generated_instance.instance_iter() {
instanced.transform.translate(transformed_point);
result_table.push(instanced.to_graphic_element());
}
};
let range = points.point_domain.positions().iter().enumerate();
if reverse {
for (index, &point) in range.rev() {
iteration(index, point).await;
}
} else {
for (index, &point) in range {
iteration(index, point).await;
}
}
}
// TODO: Remove once we support empty tables, currently this is here to avoid crashing
if result.is_empty() {
return VectorDataTable::new(VectorData::empty());
}
result
result_table
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
#[default(1)] count: u64,
reverse: bool,
) -> GraphicGroupTable {
let count = count.max(1) as usize;
let mut result_table = GraphicGroupTable::empty();
for index in 0..count {
let index = if reverse { count - index - 1 } else { index };
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() {
result_table.push(instanced.to_graphic_element());
}
}
result_table
}
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
Ok(Some(position)) => return *position,
@ -43,7 +78,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
Default::default()
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() {
Some(index) => return index as f64,
@ -87,10 +122,17 @@ mod test {
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let repeated = super::instance_on_points(owned, points, &rect).await;
let repeated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(repeated.len(), positions.len());
for (position, instanced) in positions.into_iter().zip(repeated.instances()) {
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
let bounds = instanced
.instance
.as_vector_data()
.unwrap()
.one_instance_ref()
.instance
.bounding_box_with_transform(*instanced.transform)
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}

View file

@ -253,9 +253,9 @@ fn isometric_grid_test() {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@ -268,9 +268,9 @@ fn isometric_grid_test() {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;

View file

@ -433,29 +433,29 @@ impl VectorData {
}
}
pub fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64) {
let point_map = other
pub fn concat(&mut self, additional: &Self, transform_of_additional: DAffine2, collision_hash_seed: u64) {
let point_map = additional
.point_domain
.ids()
.iter()
.filter(|id| self.point_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let segment_map = other
let segment_map = additional
.segment_domain
.ids()
.iter()
.filter(|id| self.segment_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let region_map = other
let region_map = additional
.region_domain
.ids()
.iter()
.filter(|id| self.region_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let id_map = IdMap {
@ -465,14 +465,14 @@ impl VectorData {
region_map,
};
self.point_domain.concat(&other.point_domain, transform, &id_map);
self.segment_domain.concat(&other.segment_domain, transform, &id_map);
self.region_domain.concat(&other.region_domain, transform, &id_map);
self.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map);
self.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map);
self.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map);
// TODO: properly deal with fills such as gradients
self.style = other.style.clone();
self.style = additional.style.clone();
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
self.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied());
}
}

View file

@ -181,6 +181,14 @@ impl PointDomain {
}
}
pub fn len(&self) -> usize {
self.id.len()
}
pub fn is_empty(&self) -> bool {
self.id.is_empty()
}
/// Iterate over point IDs and positions
pub fn iter(&self) -> impl Iterator<Item = (PointId, DVec2)> + '_ {
self.ids().iter().copied().zip(self.positions().iter().copied())

View file

@ -424,7 +424,7 @@ impl core::hash::Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
let vector_data_transform = *vector_data.one_instance().transform;
let vector_data_transform = *vector_data.one_instance_ref().transform;
let vector_data = vector_data.one_instance_mut().instance;
modification.apply(vector_data);

View file

@ -2,7 +2,8 @@ use super::algorithms::offset_subpath::offset_subpath;
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::instances::{InstanceMut, Instances};
use crate::instances::{Instance, InstanceMut, Instances};
use crate::raster::image::ImageFrameTable;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
@ -11,8 +12,10 @@ use crate::vector::style::{LineCap, LineJoin};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2};
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
/// Implemented for types that can be converted to an iterator of vector data.
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
@ -23,15 +26,15 @@ trait VectorDataTableIterMut {
impl VectorDataTableIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
// Grab only the direct children
self.instances_mut()
self.instance_mut_iter()
.filter_map(|element| element.instance.as_vector_data_mut())
.flat_map(move |vector_data| vector_data.instances_mut())
.flat_map(move |vector_data| vector_data.instance_mut_iter())
}
}
impl VectorDataTableIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
self.instances_mut()
self.instance_mut_iter()
}
}
@ -198,7 +201,7 @@ where
async fn repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: DVec2,
@ -221,13 +224,14 @@ where
for index in 0..instances {
let angle = index as f64 * angle / total;
let translation = index as f64 * direction / total;
let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@ -237,7 +241,7 @@ where
async fn circular_repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[default(5)] instances: IntegerCount,
@ -256,13 +260,14 @@ where
for index in 0..instances {
let rotation = DAffine2::from_angle((std::f64::consts::TAU / instances as f64) * index as f64 + angle_offset.to_radians());
let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@ -274,7 +279,7 @@ async fn copy_to_points<I: 'n + Send>(
points: VectorDataTable,
#[expose]
/// Artwork to be copied and placed at each point.
#[implementations(VectorDataTable, GraphicGroupTable)]
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)]
instance: Instances<I>,
/// Minimum range of randomized sizes given to each instance.
#[default(1)]
@ -301,7 +306,7 @@ where
Instances<I>: GraphicElementRendered,
{
let points_transform = points.transform();
let points_list = points.instances().flat_map(|element| element.instance.point_domain.positions());
let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions());
let random_scale_difference = random_scale_max - random_scale_min;
@ -316,7 +321,7 @@ where
let mut result_table = GraphicGroupTable::default();
for (index, &point) in points_list.into_iter().enumerate() {
for &point in points_list.into_iter() {
let center_transform = DAffine2::from_translation(instance_center);
let translation = points_transform.transform_point2(point);
@ -342,11 +347,14 @@ where
random_scale_min
};
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@ -355,7 +363,7 @@ where
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn mirror<I: 'n + Send>(
_: impl Ctx,
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(0., 0.)] center: DVec2,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
@ -382,20 +390,25 @@ where
);
// Apply reflection around the center point
let modification = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
let transform = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
// Add original instance depending on the keep_original flag
if keep_original {
result_table.push(instance.to_graphic_element());
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform: DAffine2::IDENTITY,
alpha_blending: Default::default(),
source_node_id: None,
});
}
// Create and add mirrored instance
let mut mirrored_element = instance.to_graphic_element();
mirrored_element.new_ids_from_hash(None);
// Apply the transformation to the mirrored instance
let mirrored_instance = result_table.push(mirrored_element);
*mirrored_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
result_table
}
@ -417,7 +430,7 @@ async fn round_corners(
) -> VectorDataTable {
let source_transform = source.transform();
let source_transform_inverse = source_transform.inverse();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let upstream_graphics_group = source.upstream_graphic_group.clone();
// Flip the roundness to help with user intuition
@ -517,7 +530,7 @@ async fn spatial_merge_by_distance(
distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let point_count = vector_data.point_domain.positions().len();
// Find min x and y for grid cell normalization
@ -638,10 +651,10 @@ async fn spatial_merge_by_distance(
#[node_macro::node(category("Debug"), path(graphene_core::vector))]
async fn box_warp(_: impl Ctx, vector_data: VectorDataTable, #[expose] rectangle: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance.clone();
let vector_data = vector_data.one_instance_ref().instance.clone();
let target_transform = rectangle.transform();
let target = rectangle.one_instance().instance;
let target = rectangle.one_instance_ref().instance;
// Get the bounding box of the source vector data
let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
@ -727,7 +740,7 @@ async fn remove_handles(
max_handle_distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
for (_, handles, start, end) in vector_data.segment_domain.handles_mut() {
// Only convert to linear if handles are within the threshold distance
@ -773,7 +786,7 @@ async fn generate_handles(
curvature: f64,
) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorData::empty();
result.style = source.style.clone();
@ -955,7 +968,7 @@ async fn generate_handles(
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut result = vector_data
.bounding_box()
@ -972,7 +985,7 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.bounding_box_with_transform(vector_data_transform)
.map(|[top_left, bottom_right]| bottom_right - top_left)
@ -982,7 +995,7 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@ -1018,7 +1031,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let stroke = vector_data.style.stroke().clone().unwrap_or_default();
let bezpaths = vector_data.stroke_bezpath_iter();
@ -1072,15 +1085,20 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
// a flatten vector elements connected to an if else node, another connection from the cache directly
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut<VectorData>) {
for current_element in graphic_group_table.instances() {
for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() {
match current_element.instance {
GraphicElement::VectorData(vector_data_table) => {
// Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance.
for vector_data_instance in vector_data_table.instances() {
for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() {
let other = vector_data_instance.instance;
let transform = *current_element.transform * *vector_data_instance.transform;
let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
output.instance.concat(other, transform, node_id);
let mut hasher = DefaultHasher::new();
(group_index, vector_index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.instance.concat(other, transform, collision_hash_seed);
// Use the last encountered style as the output style
output.instance.style = vector_data_instance.instance.style.clone();
@ -1088,7 +1106,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
GraphicElement::GraphicGroup(graphic_group) => {
let mut graphic_group = graphic_group.clone();
for instance in graphic_group.instances_mut() {
for instance in graphic_group.instance_mut_iter() {
*instance.transform = *current_element.transform * *instance.transform;
}
@ -1100,7 +1118,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
let mut output_table = VectorDataTable::default();
let Some(mut output) = output_table.instances_mut().next() else { return output_table };
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
flatten_group(&graphic_group_input, &mut output);
@ -1113,7 +1131,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
let spacing = spacing.max(0.01);
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
// Create an iterator over the bezier segments with enumeration and peeking capability.
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@ -1274,7 +1292,7 @@ async fn position_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@ -1306,7 +1324,7 @@ async fn tangent_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@ -1340,7 +1358,7 @@ async fn poisson_disk_points(
seed: SeedValue,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
@ -1391,7 +1409,7 @@ async fn poisson_disk_points(
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.segment_bezier_iter()
@ -1447,7 +1465,7 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
@ -1500,14 +1518,14 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> VectorDataTable {
let time = time.clamp(0., 1.);
let source_alpha_blending = source.one_instance().alpha_blending;
let target_alpha_blending = target.one_instance().alpha_blending;
let source_alpha_blending = source.one_instance_ref().alpha_blending;
let target_alpha_blending = target.one_instance_ref().alpha_blending;
let source_transform = source.transform();
let target_transform = target.transform();
let source = source.one_instance().instance;
let target = target.one_instance().instance;
let source = source.one_instance_ref().instance;
let target = target.one_instance_ref().instance;
let mut result = VectorDataTable::default();
@ -1699,7 +1717,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
*result.transform_mut() = source_transform;
@ -1709,7 +1727,7 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let mut source = source.one_instance().instance.clone();
let mut source = source.one_instance_ref().instance.clone();
source.merge_by_distance(distance);
@ -1725,7 +1743,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut area = 0.;
let scale = vector_data_transform.decompose_scale();
@ -1742,7 +1760,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
if centroid_type == CentroidType::Area {
let mut area = 0.;
@ -1814,7 +1832,7 @@ mod test {
let instances = 3;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1826,7 +1844,7 @@ mod test {
let instances = 8;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1836,7 +1854,7 @@ mod test {
async fn circular_repeat() {
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
@ -1851,7 +1869,7 @@ mod test {
#[tokio::test]
async fn bounding_box() {
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
@ -1865,7 +1883,7 @@ mod test {
}
.eval(Footprint::default())
.await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let expected_bounding_box = [DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.)];
@ -1882,7 +1900,7 @@ mod test {
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flatten_vector_elements = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
let flattened_copy_to_points = flatten_vector_elements.instances().next().unwrap().instance;
let flattened_copy_to_points = flatten_vector_elements.instance_ref_iter().next().unwrap().instance;
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
@ -1898,7 +1916,7 @@ mod test {
async fn sample_points() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1908,7 +1926,7 @@ mod test {
async fn adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1923,7 +1941,7 @@ mod test {
0,
)
.await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@ -1942,7 +1960,7 @@ mod test {
#[tokio::test]
async fn spline() {
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.instances().next().unwrap().instance;
let spline = spline.instance_ref_iter().next().unwrap().instance;
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
}
@ -1951,7 +1969,7 @@ mod test {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(
&sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
@ -1974,7 +1992,7 @@ mod test {
async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8);
@ -1997,7 +2015,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel((), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -2021,7 +2039,7 @@ mod test {
*vector_data_table.one_instance_mut().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -2039,7 +2057,7 @@ mod test {
async fn bevel_too_high() {
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);
@ -2060,7 +2078,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);

View file

@ -15,7 +15,7 @@ use graphene_core::{Ctx, GraphicElement, Node};
#[node_macro::node(category("Debug"))]
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data.point_domain.positions().to_vec()
}
@ -96,8 +96,8 @@ where
return target;
}
let target_width = target.one_instance().instance.width;
let target_height = target.one_instance().instance.height;
let target_width = target.one_instance_ref().instance.width;
let target_height = target.one_instance_ref().instance.height;
let target_size = DVec2::new(target_width as f64, target_height as f64);
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
@ -122,7 +122,7 @@ where
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
assert!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target.one_instance().instance.data.len());
assert!(target_index(max_x, max_y) < target.one_instance_ref().instance.data.len());
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
@ -143,7 +143,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.one_instance().instance.clone()
image.one_instance_ref().instance.clone()
}
macro_rules! inline_blend_funcs {
@ -220,7 +220,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
let mut background_bounds = bbox.to_transform();
// If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
let bounds_empty = bounds.instances().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
let bounds_empty = bounds.instance_ref_iter().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
if bounds.transform().matrix2.determinant() != 0. && !bounds_empty {
background_bounds = bounds.transform();
}

View file

@ -9,9 +9,9 @@ use std::cmp::{max, min};
#[node_macro::node(category("Raster"))]
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance().instance;
let image = image_frame.one_instance_ref().instance;
// Prepare the image data for processing
let image_data = bytemuck::cast_vec(image.data.clone());

View file

@ -62,7 +62,7 @@ impl Clone for ComputePass {
#[node_macro::old_node_impl(MapGpuNode)]
async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
let image_frame_table = &image;
let image = image.one_instance().instance;
let image = image.one_instance_ref().instance;
log::debug!("Executing gpu node");
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
@ -113,7 +113,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
};
let mut result = ImageFrameTable::new(new_image);
*result.transform_mut() = image_frame_table.transform();
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance().alpha_blending;
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
result
}
@ -133,7 +133,7 @@ where
GraphicElement: From<Image<T>>,
T::Static: Pixel,
{
let image = image.one_instance().instance;
let image = image.one_instance_ref().instance;
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
@ -278,10 +278,10 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let foreground_transform = foreground.transform();
let background_transform = background.transform();
let background_alpha_blending = background.one_instance().alpha_blending;
let background_alpha_blending = background.one_instance_ref().alpha_blending;
let foreground = foreground.one_instance().instance;
let background = background.one_instance().instance;
let foreground = foreground.one_instance_ref().instance;
let background = background.one_instance_ref().instance;
let foreground_size = DVec2::new(foreground.width as f64, foreground.height as f64);
let background_size = DVec2::new(background.width as f64, background.height as f64);

View file

@ -16,7 +16,7 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
let image = image.one_instance().instance;
let image = image.one_instance_ref().instance;
for pixel in image.data.iter() {
let r = pixel.r() * GRID;

View file

@ -29,9 +29,9 @@ impl From<std::io::Error> for Error {
#[node_macro::node(category("Debug: Raster"))]
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance().instance;
let image = image_frame.one_instance_ref().instance;
// Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone());
@ -325,7 +325,7 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
return image;
}
let image_instance = image.one_instance().instance;
let image_instance = image.one_instance_ref().instance;
if image_instance.width == 0 || image_instance.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT);
}
@ -355,7 +355,7 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
let mut result = ImageFrameTable::new(new_img);
*result.transform_mut() = new_texture_to_layer_space;
*result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
*result.one_instance_mut().alpha_blending = *image.one_instance_ref().alpha_blending;
result
}

View file

@ -14,11 +14,11 @@ use std::ops::Mul;
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
graphic_group_table
.instances()
.instance_ref_iter()
.map(|element| match element.instance.clone() {
GraphicElement::VectorData(mut vector_data) => {
// Apply the parent group's transform to each element of vector data
for sub_vector_data in vector_data.instances_mut() {
for sub_vector_data in vector_data.instance_mut_iter() {
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
}
@ -28,12 +28,12 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
// Apply the parent group's transform to each element of raster data
match &mut image {
graphene_core::RasterFrame::ImageFrame(image) => {
for instance in image.instances_mut() {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
graphene_core::RasterFrame::TextureFrame(image) => {
for instance in image.instances_mut() {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
@ -50,7 +50,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instances_mut() {
for sub_element in graphic_group.instance_mut_iter() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
@ -72,7 +72,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
@ -105,7 +105,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
@ -136,7 +136,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@ -160,12 +160,12 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
let all_other_vector_data_instance = all_other_vector_data.one_instance();
let all_other_vector_data_instance = all_other_vector_data.one_instance_ref();
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };

View file

@ -187,7 +187,7 @@ where
..Default::default()
};
for instance in data.instances_mut() {
for instance in data.instance_mut_iter() {
*instance.transform = DAffine2::from_translation(-aabb.start) * *instance.transform;
}
data.render_svg(&mut render, &render_params);

View file

@ -17,6 +17,7 @@ use graphene_std::GraphicElement;
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::ImageTexture;
use graphene_std::wasm_application_io::*;
use node_registry_macros::{async_node, into_node};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
@ -24,109 +25,27 @@ use std::sync::Arc;
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
use wgpu_executor::{WgpuSurface, WindowHandle};
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
async_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {
(
ProtoNodeIdentifier::new(stringify!($path)),
|mut args| {
Box::pin(async move {
args.reverse();
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <$path>::new($(
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + Send>>>::new()
),*);
let params = vec![$(fn_type_fut!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<$input as StaticType>::Static);
node_io
},
)
};
}
macro_rules! into_node {
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|mut args| {
Box::pin(async move {
args.reverse();
let node = graphene_core::ops::IntoNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = graphene_core::ops::IntoNode::<$to>::new();
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static);
node_io
},
)
};
}
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
// (
// ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
// ),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
into_node!(from: f64, to: f64),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: f64,to: f64),
into_node!(from: u32,to: f64),
into_node!(from: u8,to: u32),
into_node!(from: ImageFrameTable<Color>,to: GraphicGroupTable),
into_node!(from: VectorDataTable,to: GraphicGroupTable),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi,to: &WgpuExecutor),
into_node!(from: VectorDataTable,to: GraphicElement),
into_node!(from: ImageFrameTable<Color>,to: GraphicElement),
into_node!(from: GraphicGroupTable,to: GraphicElement),
into_node!(from: VectorDataTable,to: GraphicGroupTable),
into_node!(from: ImageFrameTable<Color>,to: GraphicGroupTable),
into_node!(from: f64, to: f64),
into_node!(from: u32, to: f64),
into_node!(from: u8, to: u32),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: GraphicElement),
into_node!(from: ImageFrameTable<Color>, to: GraphicElement),
into_node!(from: GraphicGroupTable, to: GraphicElement),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
let any: DynAnyNode<Context, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
},
),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::RasterFrame]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::instances::Instances<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
@ -135,6 +54,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => bool]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u64]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]),
@ -149,40 +69,27 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_core::uuid::NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::Color]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrameTable<Color>),
concrete!(ImageFrameTable<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
),
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
// This is how we can generically define composition of two nodes.
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
NodeIOTypes::new(
generic!(T),
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
// Filters
// TODO: Move these filters to the new node macro and put them in `graphene_core::raster::adjustments`, then add them to the document upgrade script which moves many of the adjustment nodes from `graphene_core::raster` to `graphene_core::raster::adjustments`
(
@ -214,118 +121,77 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
},
NodeIOTypes::new(concrete!(ImageFrameTable<Color>), concrete!(ImageFrameTable<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
),
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ClonedNode::new(curve.eval(()).await);
// let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
// ),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32)));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![fn_type!(graphene_core::raster::curve::Curve)],
// ),
// ),
// (
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
// Box::pin(async move {
// use graphene_std::raster::ImaginateNode;
// macro_rules! instantiate_imaginate_node {
// ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) };
// }
// let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
// let any = graphene_std::any::DynAnyNode::new(node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![
// fn_type!(&WasmEditorApi),
// fn_type!(ImaginateController),
// fn_type!(f64),
// fn_type!(Option<DVec2>),
// fn_type!(u32),
// fn_type!(ImaginateSamplingMethod),
// fn_type!(f64),
// fn_type!(String),
// fn_type!(String),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(ImaginateMaskStartingFill),
// fn_type!(bool),
// fn_type!(bool),
// fn_type!(u64),
// ],
// ),
// ),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
// This is how we can generically define composition of two nodes.
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
NodeIOTypes::new(
generic!(T),
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrameTable<Color>),
concrete!(ImageFrameTable<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
let any: DynAnyNode<Context, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
},
),
];
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
for (constructor, types) in entry.iter() {
map.entry(id.clone().into()).or_default().insert(types.clone(), *constructor);
}
}
for (id, c, types) in node_types.into_iter() {
// TODO: this is a hack to remove the newline from the node new_name
// This occurs for the ChannelMixerNode presumably because of the long name.
@ -340,12 +206,67 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let nid = ProtoNodeIdentifier { name: Cow::Owned(new_name) };
map.entry(nid).or_default().insert(types.clone(), c);
}
map
}
pub static NODE_REGISTRY: Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = Lazy::new(|| node_registry());
#[cfg(test)]
mod protograph_testing {
// TODO: add tests testing the node registry
mod node_registry_macros {
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
async_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {
(
ProtoNodeIdentifier::new(stringify!($path)),
|mut args| {
Box::pin(async move {
args.reverse();
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <$path>::new($(
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + Send>>>::new()
),*);
let params = vec![$(fn_type_fut!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<$input as StaticType>::Static);
node_io
},
)
};
}
macro_rules! into_node {
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|mut args| {
Box::pin(async move {
args.reverse();
let node = graphene_core::ops::IntoNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = graphene_core::ops::IntoNode::<$to>::new();
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static);
node_io
},
)
};
}
pub(crate) use async_node;
pub(crate) use into_node;
}

View file

@ -0,0 +1,80 @@
//! This has all been copied out of node_registry.rs to avoid leaving many lines of commented out code in that file. It's left here instead for future reference.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ClonedNode::new(curve.eval(()).await);
// let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
// ),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32)));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![fn_type!(graphene_core::raster::curve::Curve)],
// ),
// ),
// (
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
// Box::pin(async move {
// use graphene_std::raster::ImaginateNode;
// macro_rules! instantiate_imaginate_node {
// ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) };
// }
// let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
// let any = graphene_std::any::DynAnyNode::new(node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![
// fn_type!(&WasmEditorApi),
// fn_type!(ImaginateController),
// fn_type!(f64),
// fn_type!(Option<DVec2>),
// fn_type!(u32),
// fn_type!(ImaginateSamplingMethod),
// fn_type!(f64),
// fn_type!(String),
// fn_type!(String),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(ImaginateMaskStartingFill),
// fn_type!(bool),
// fn_type!(bool),
// fn_type!(u64),
// ],
// ),
// ),

View file

@ -913,7 +913,7 @@ async fn render_texture<'a: 'n>(
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> ImageTexture {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let input = input.one_instance().instance;
let input = input.one_instance_ref().instance;
let new_data: Vec<SRGBA8> = input.data.iter().map(|x| (*x).into()).collect();
let new_image = Image {
width: input.width,