Instance tables refactor part 1: wrap graphical data in the new Instances<T> struct (#2230)

* Port VectorData to Instances<VectorData>

* Port ImageFrame<P> and TextureFrame to Instances<ImageFrame<P>> and Instances<TextureFrame>

* Avoid mutation with the TransformMut trait

* Port GraphicGroup to Instances<GraphicGroup>

* It compiles!

* Organize debugging

* Document upgrading

* Fix Brush node

* Restore TransformMut in lieu of TransformSet trait

* Fix tests

* Final code review
This commit is contained in:
Keavon Chambers 2025-01-28 23:51:12 -08:00 committed by GitHub
parent 408f9bffa1
commit eb0ff20d3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 1855 additions and 1221 deletions

View file

@ -13,7 +13,6 @@ use graphene_core::vector::style::ViewMode;
use graphene_core::Color; use graphene_core::Color;
use graphene_std::renderer::ClickTarget; use graphene_std::renderer::ClickTarget;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::VectorData;
use glam::DAffine2; use glam::DAffine2;
@ -181,9 +180,6 @@ pub enum DocumentMessage {
UpdateClipTargets { UpdateClipTargets {
clip_targets: HashSet<NodeId>, clip_targets: HashSet<NodeId>,
}, },
UpdateVectorModify {
vector_modify: HashMap<NodeId, VectorData>,
},
Undo, Undo,
UngroupSelectedLayers, UngroupSelectedLayers,
UngroupLayer { UngroupLayer {

View file

@ -27,7 +27,8 @@ use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork}; use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork};
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::image::ImageFrame;
use graphene_core::raster::BlendMode;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::renderer::{ClickTarget, Quad};
use graphene_std::vector::path_bool_lib; use graphene_std::vector::path_bool_lib;
@ -1159,9 +1160,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::UpdateClipTargets { clip_targets } => { DocumentMessage::UpdateClipTargets { clip_targets } => {
self.network_interface.update_clip_targets(clip_targets); self.network_interface.update_clip_targets(clip_targets);
} }
DocumentMessage::UpdateVectorModify { vector_modify } => {
self.network_interface.update_vector_modify(vector_modify);
}
DocumentMessage::Undo => { DocumentMessage::Undo => {
if self.network_interface.transaction_status() != TransactionStatus::Finished { if self.network_interface.transaction_status() != TransactionStatus::Finished {
return; return;
@ -2126,7 +2124,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export /// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface { fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default(); let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "", &[]); network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::default()), -1, "", &[]);
network_interface network_interface
} }

View file

@ -5,7 +5,8 @@ use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::image::ImageFrame;
use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};

View file

@ -8,16 +8,17 @@ use bezier_rs::Subpath;
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::{PointId, VectorModificationType}; use graphene_core::vector::{PointId, VectorModificationType};
use graphene_core::{Artboard, Color}; use graphene_core::{Artboard, Color};
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::GraphicGroupTable;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use graphene_std::vector::VectorData;
use graphene_std::GraphicGroup;
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn { pub enum TransformIn {
@ -125,8 +126,8 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network /// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier { pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([ let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::EMPTY), true)), Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroup::EMPTY), true)), Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)), Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)), Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)), Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
@ -138,7 +139,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) { pub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) {
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([ let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroup::EMPTY), true)), Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)), Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]); ]);
@ -148,7 +149,7 @@ impl<'a> ModifyInputsContext<'a> {
} }
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) { pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector_data = VectorData::from_subpaths(subpaths, true); let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true));
let shape = resolve_document_node_type("Path") let shape = resolve_document_node_type("Path")
.expect("Path node does not exist") .expect("Path node does not exist")
@ -213,9 +214,10 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: LayerNodeIdentifier) { pub fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: LayerNodeIdentifier) {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template(); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image") let image = resolve_document_node_type("Image").expect("Image node does not exist").node_template_input_override([
.expect("Image node does not exist") Some(NodeInput::value(TaggedValue::None, false)),
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))]); Some(NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::new(image_frame)), false)),
]);
let image_id = NodeId::new(); let image_id = NodeId::new();
self.network_interface.insert_node(image_id, image, &[]); self.network_interface.insert_node(image_id, image, &[]);
@ -289,7 +291,7 @@ impl<'a> ModifyInputsContext<'a> {
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group. // TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
if node_definition.identifier == "Path" { if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type(); let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type();
if layer_input_type == concrete!(GraphicGroup) { if layer_input_type == concrete!(GraphicGroupTable) {
let Some(flatten_vector_elements_definition) = resolve_document_node_type("Flatten Vector Elements") else { let Some(flatten_vector_elements_definition) = resolve_document_node_type("Flatten Vector Elements") else {
log::error!("Flatten Vector Elements does not exist in ModifyInputsContext::existing_node_id"); log::error!("Flatten Vector Elements does not exist in ModifyInputsContext::existing_node_id");
return None; return None;

View file

@ -15,13 +15,13 @@ use graph_craft::document::*;
use graph_craft::imaginate_input::ImaginateSamplingMethod; use graph_craft::imaginate_input::ImaginateSamplingMethod;
use graph_craft::ProtoNodeIdentifier; use graph_craft::ProtoNodeIdentifier;
use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, NoiseType, RedGreenBlue, RedGreenBlueAlpha}; use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha};
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData; use graphene_core::vector::VectorDataTable;
use graphene_core::*; use graphene_core::*;
use graphene_std::wasm_application_io::WasmEditorApi; use graphene_std::wasm_application_io::WasmEditorApi;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput}; use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput};
@ -40,9 +40,7 @@ pub struct NodePropertiesContext<'a> {
impl NodePropertiesContext<'_> { impl NodePropertiesContext<'_> {
pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {
let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?;
return None;
};
if let Some(widget_override) = &input_properties_row.widget_override { if let Some(widget_override) = &input_properties_row.widget_override {
let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else { let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else {
log::error!("Could not get widget override lambda in call_widget_override"); log::error!("Could not get widget override lambda in call_widget_override");
@ -204,8 +202,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -312,8 +310,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroup::EMPTY), true), NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroup::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false), NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false), NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false), NodeInput::value(TaggedValue::Color(Color::WHITE), false),
@ -545,8 +543,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(3), 0)], exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0)], inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame>")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame>")), // TODO: Possibly change `ImageFrame` to something else
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@ -573,7 +571,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -664,7 +662,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorData::default()), true), NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value( NodeInput::value(
TaggedValue::Footprint(Footprint { TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)), transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)),
@ -811,8 +809,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -834,8 +832,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
], ],
..Default::default() ..Default::default()
@ -858,10 +856,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -889,7 +887,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
@ -898,7 +896,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
@ -907,7 +905,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
@ -916,7 +914,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
@ -931,7 +929,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -997,8 +995,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(0), 0)], exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode { nodes: vec![DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(graphene_core::raster::ImageFrame<Color>), 0), NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::network(concrete!(graphene_core::raster::ImageFrame<Color>), 1), NodeInput::network(concrete!(ImageFrameTable<Color>), 1),
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2), NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
NodeInput::network(concrete!(BrushCache), 3), NodeInput::network(concrete!(BrushCache), 3),
], ],
@ -1013,8 +1011,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
], ],
@ -1063,7 +1061,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(())), manual_composition: Some(concrete!(())),
..Default::default() ..Default::default()
}, },
@ -1082,7 +1080,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(concrete!(Footprint)),
..Default::default() ..Default::default()
}, },
@ -1103,7 +1101,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0)], exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode { nodes: vec![DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 1)], inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(concrete!(Footprint)),
..Default::default() ..Default::default()
@ -1114,7 +1112,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), false)], inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), false)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1809,7 +1807,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")),
..Default::default() ..Default::default()
@ -1827,7 +1825,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1883,7 +1881,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true),
], ],
..Default::default() ..Default::default()
@ -1925,7 +1923,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"), implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false), NodeInput::value(TaggedValue::Bool(false), false),
@ -1956,7 +1954,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::Curve(Default::default()), false), NodeInput::value(TaggedValue::Curve(Default::default()), false),
], ],
..Default::default() ..Default::default()
@ -2046,7 +2044,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorData), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
@ -2066,7 +2064,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorModification(Default::default()), false), NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
], ],
..Default::default() ..Default::default()
@ -2189,7 +2187,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
@ -2200,7 +2198,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorData), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
@ -2296,7 +2294,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorData), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode")),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
@ -2315,7 +2313,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false), NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false),
], ],
..Default::default() ..Default::default()
@ -2366,8 +2364,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: Wrap this implementation with a document node that has a cache node so the output is cached? // TODO: Wrap this implementation with a document node that has a cache node so the output is cached?
implementation: DocumentNodeImplementation::proto("graphene_core::vector::CopyToPointsNode"), implementation: DocumentNodeImplementation::proto("graphene_core::vector::CopyToPointsNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(1.), false),
NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(1.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
@ -2463,14 +2461,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(2), 0)], // Taken from output 0 of Sample Points exports: vec![NodeInput::node(NodeId(2), 0)], // Taken from output 0 of Sample Points
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)], inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(graphene_core::vector::VectorData), 0), NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0),
NodeInput::network(concrete!(f64), 1), // From the document node's parameters NodeInput::network(concrete!(f64), 1), // From the document node's parameters
NodeInput::network(concrete!(f64), 2), // From the document node's parameters NodeInput::network(concrete!(f64), 2), // From the document node's parameters
NodeInput::network(concrete!(f64), 3), // From the document node's parameters NodeInput::network(concrete!(f64), 3), // From the document node's parameters
@ -2495,7 +2493,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::F64(100.), false), NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
@ -2586,7 +2584,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(graphene_core::vector::VectorData), 0), NodeInput::network(concrete!(graphene_core::vector::VectorDataTable), 0),
NodeInput::network(concrete!(f64), 1), NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2), NodeInput::network(concrete!(u32), 2),
], ],
@ -2608,7 +2606,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), NodeInput::value(TaggedValue::VectorData(graphene_core::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::F64(10.), false), NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::U32(0), false), NodeInput::value(TaggedValue::U32(0), false),
], ],
@ -2667,29 +2665,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
}, },
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
identifier: "Index",
category: "Debug",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::IndexNode"),
inputs: vec![NodeInput::value(TaggedValue::Segments(vec![ImageFrame::empty()]), true), NodeInput::value(TaggedValue::U32(0), false)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
"Segmentation".into(),
PropertiesRow::with_override("Index", WidgetOverride::Number(NumberInputSettings { min: Some(0.), ..Default::default() })),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"), description: Cow::Borrowed("TODO"),
properties: None, properties: None,
}, },
@ -2807,7 +2782,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0)], inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
manual_composition: Some(concrete!(())), manual_composition: Some(concrete!(())),
skip_deduplication: true, skip_deduplication: true,
@ -2845,7 +2820,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
NodeInput::value(TaggedValue::ImaginateController(Default::default()), false), NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index

View file

@ -11,8 +11,9 @@ use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, No
use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::Type; use graph_craft::Type;
use graphene_core::raster::curve::Curve; use graphene_core::raster::curve::Curve;
use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{ use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice, SelectiveColorChoice,
}; };
use graphene_core::text::Font; use graphene_core::text::Font;
@ -22,8 +23,8 @@ use graphene_std::application_io::TextureFrame;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::misc::BooleanOperation; use graphene_std::vector::misc::BooleanOperation;
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops}; use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
use graphene_std::vector::VectorData; use graphene_std::vector::VectorDataTable;
use graphene_std::{GraphicGroup, Raster}; use graphene_std::{GraphicGroupTable, RasterFrame};
use glam::{DAffine2, DVec2, IVec2, UVec2}; use glam::{DAffine2, DVec2, IVec2, UVec2};
@ -152,11 +153,11 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, numbe
} }
Some(x) if x == TypeId::of::<Curve>() => curves_widget(document_node, node_id, index, name, true), Some(x) if x == TypeId::of::<Curve>() => curves_widget(document_node, node_id, index, name, true),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), Some(x) if x == TypeId::of::<GradientStops>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true),
Some(x) if x == TypeId::of::<VectorData>() => vector_widget(document_node, node_id, index, name, true).into(), Some(x) if x == TypeId::of::<VectorDataTable>() => vector_widget(document_node, node_id, index, name, true).into(),
Some(x) if x == TypeId::of::<Raster>() || x == TypeId::of::<ImageFrame<Color>>() || x == TypeId::of::<TextureFrame>() => { Some(x) if x == TypeId::of::<RasterFrame>() || x == TypeId::of::<ImageFrameTable<Color>>() || x == TypeId::of::<TextureFrame>() => {
raster_widget(document_node, node_id, index, name, true).into() raster_widget(document_node, node_id, index, name, true).into()
} }
Some(x) if x == TypeId::of::<GraphicGroup>() => group_widget(document_node, node_id, index, name, true).into(), Some(x) if x == TypeId::of::<GraphicGroupTable>() => group_widget(document_node, node_id, index, name, true).into(),
Some(x) if x == TypeId::of::<Footprint>() => { Some(x) if x == TypeId::of::<Footprint>() => {
let widgets = footprint_widget(document_node, node_id, index); let widgets = footprint_widget(document_node, node_id, index);
let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows"); let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows");

View file

@ -30,7 +30,7 @@ impl FrontendGraphDataType {
| TaggedValue::F64Array4(_) | TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_) | TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) => Self::Number, | TaggedValue::VecDVec2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
TaggedValue::ArtboardGroup(_) => Self::Artboard, TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General, _ => Self::General,
} }

View file

@ -3,8 +3,7 @@ use graph_craft::document::NodeId;
use graphene_core::renderer::ClickTarget; use graphene_core::renderer::ClickTarget;
use graphene_core::renderer::Quad; use graphene_core::renderer::Quad;
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_std::vector::PointId; use graphene_std::vector::{PointId, VectorData};
use graphene_std::vector::VectorData;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};

View file

@ -1323,7 +1323,7 @@ impl NodeNetworkInterface {
} }
/// Layers excluding ones that are children of other layers in the list. /// Layers excluding ones that are children of other layers in the list.
/// TODO: Cache this // TODO: Cache this
pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> impl Iterator<Item = LayerNodeIdentifier> { pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> impl Iterator<Item = LayerNodeIdentifier> {
let mut sorted_layers = if let Some(selected_nodes) = self.selected_nodes(network_path) { let mut sorted_layers = if let Some(selected_nodes) = self.selected_nodes(network_path) {
selected_nodes selected_nodes
@ -5977,9 +5977,11 @@ pub struct NodeNetworkPersistentMetadata {
// Stores the transform and navigation state for the network // Stores the transform and navigation state for the network
pub navigation_metadata: NavigationMetadata, pub navigation_metadata: NavigationMetadata,
/// Stack of selection snapshots for previous history states. /// Stack of selection snapshots for previous history states.
// TODO: Use `#[serde(skip)]` here instead? @TrueDoctor claims this isn't valid but hasn't satisfactorily explained how it differs from the situation where `#[serde(default)]` fills in the default value. From brief testing, skip seems to work without issue.
#[serde(default)] #[serde(default)]
pub selection_undo_history: VecDeque<SelectedNodes>, pub selection_undo_history: VecDeque<SelectedNodes>,
/// Stack of selection snapshots for future history states. /// Stack of selection snapshots for future history states.
// TODO: Use `#[serde(skip)]` here instead? See above.
#[serde(default)] #[serde(default)]
pub selection_redo_history: VecDeque<SelectedNodes>, pub selection_redo_history: VecDeque<SelectedNodes>,
} }
@ -6016,7 +6018,7 @@ pub struct NodeNetworkTransientMetadata {
// node_group_bounding_box: Vec<(Subpath<ManipulatorGroupId>, Vec<Nodes>)>, // node_group_bounding_box: Vec<(Subpath<ManipulatorGroupId>, Vec<Nodes>)>,
/// Cache for all outward wire connections /// Cache for all outward wire connections
pub outward_wires: TransientMetadata<HashMap<OutputConnector, Vec<InputConnector>>>, pub outward_wires: TransientMetadata<HashMap<OutputConnector, Vec<InputConnector>>>,
/// TODO: Cache all wire paths instead of calculating in Graph.svelte // TODO: Cache all wire paths instead of calculating in Graph.svelte
// pub wire_paths: Vec<WirePath> // pub wire_paths: Vec<WirePath>
/// All export connector click targets /// All export connector click targets
pub import_export_ports: TransientMetadata<Ports>, pub import_export_ports: TransientMetadata<Ports>,
@ -6132,7 +6134,7 @@ pub enum WidgetOverride {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PropertiesRow { pub struct PropertiesRow {
/// A general datastore than can store key value pairs of any types for any input /// A general datastore than can store key value pairs of any types for any input
/// TODO: This could be simplified to just Value, and key value pairs could be stored as the Object variant // TODO: This could be simplified to just Value, and key value pairs could be stored as the Object variant
pub input_data: HashMap<String, Value>, pub input_data: HashMap<String, Value>,
// An input can override a widget, which would otherwise be automatically generated from the type // An input can override a widget, which would otherwise be automatically generated from the type
// The string is the identifier to the widget override function stored in INPUT_OVERRIDES // The string is the identifier to the widget override function stored in INPUT_OVERRIDES
@ -6220,13 +6222,36 @@ impl PropertiesRow {
} }
} }
// TODO: Eventually remove this migration document upgrade code
fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
use serde::Deserialize;
const REPLACEMENTS: [(&str, &str); 3] = [
("VectorData", "Instances<VectorData>"),
("GraphicGroup", "Instances<GraphicGroup>"),
("ImageFrame", "Instances<ImageFrame>"),
];
let mut names = Vec::<String>::deserialize(deserializer)?;
for name in names.iter_mut() {
for (old, new) in REPLACEMENTS.iter() {
if name == old {
*name = new.to_string();
}
}
}
Ok(names)
}
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DocumentNodePersistentMetadata { pub struct DocumentNodePersistentMetadata {
/// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties if no display name is set. /// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties if no display name is set.
/// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition. // TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition.
/// TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs. // TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs.
/// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition. // TODO: Implement node versioning so that references to old nodes can be updated to the new node definition.
pub reference: Option<String>, pub reference: Option<String>,
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics. /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics.
#[serde(default)] #[serde(default)]
@ -6234,6 +6259,7 @@ pub struct DocumentNodePersistentMetadata {
/// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function.
/// Must match the length of node inputs /// Must match the length of node inputs
pub input_properties: Vec<PropertiesRow>, pub input_properties: Vec<PropertiesRow>,
#[serde(deserialize_with = "migrate_output_names")]
pub output_names: Vec<String>, pub output_names: Vec<String>,
/// Indicates to the UI if a primary output should be drawn for this node. /// Indicates to the UI if a primary output should be drawn for this node.
/// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output.

View file

@ -428,7 +428,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} }
}; };
const REPLACEMENTS: [(&str, &str); 36] = [ const REPLACEMENTS: [(&str, &str); 35] = [
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
@ -445,8 +445,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"), ("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"), ("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"), ("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"),
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"), ("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
// ("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"),
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"), ("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"), ("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"), ("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),

View file

@ -3,7 +3,8 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::image::ImageFrame;
use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::style::Gradient; use graphene_core::vector::style::Gradient;
use graphene_core::vector::PointId; use graphene_core::vector::PointId;
@ -12,7 +13,7 @@ use graphene_core::Color;
use glam::DVec2; use glam::DVec2;
use std::collections::VecDeque; use std::collections::VecDeque;
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. /// Create a new vector layer.
pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index }); responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });
@ -21,7 +22,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
LayerNodeIdentifier::new_unchecked(id) LayerNodeIdentifier::new_unchecked(id)
} }
/// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`] /// Create a new bitmap layer.
pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;
responses.add(GraphOperationMessage::NewBitmapLayer { responses.add(GraphOperationMessage::NewBitmapLayer {
@ -33,7 +34,7 @@ pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: Layer
LayerNodeIdentifier::new_unchecked(id) LayerNodeIdentifier::new_unchecked(id)
} }
/// Create a new group layer from an svg /// Create a new group layer from an SVG string.
pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;
responses.add(GraphOperationMessage::NewSvg { responses.add(GraphOperationMessage::NewSvg {

View file

@ -372,7 +372,7 @@ impl Fsm for ArtboardToolFsmState {
responses.add(GraphOperationMessage::NewArtboard { responses.add(GraphOperationMessage::NewArtboard {
id, id,
artboard: graphene_core::Artboard { artboard: graphene_core::Artboard {
graphic_group: graphene_core::GraphicGroup::EMPTY, graphic_group: graphene_core::GraphicGroupTable::default(),
label: String::from("Artboard"), label: String::from("Artboard"),
location: start.round().as_ivec2(), location: start.round().as_ivec2(),
dimensions: IVec2::splat(1), dimensions: IVec2::splat(1),

View file

@ -502,7 +502,7 @@ impl PathToolData {
}); });
// Make handles colinear if opposite handle is zero length // Make handles colinear if opposite handle is zero length
if opposite_handle_length.map_or(false, |l| l == 0.) { if opposite_handle_length == Some(0.) {
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document); shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
return true; return true;
} }

View file

@ -1,3 +1,4 @@
use crate::instances::Instances;
use crate::text::FontCache; use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::ViewMode; use crate::vector::style::ViewMode;
@ -64,6 +65,8 @@ impl Size for web_sys::HtmlCanvasElement {
} }
} }
pub type TextureFrameTable = Instances<TextureFrame>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TextureFrame { pub struct TextureFrame {
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]

View file

@ -1,14 +1,17 @@
use crate::application_io::TextureFrame; use crate::application_io::{TextureFrame, TextureFrameTable};
use crate::raster::{BlendMode, ImageFrame}; use crate::instances::Instances;
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::BlendMode;
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
use crate::uuid::NodeId; use crate::uuid::NodeId;
use crate::vector::VectorData; use crate::vector::{VectorData, VectorDataTable};
use crate::Color; use crate::Color;
use dyn_any::DynAny; use dyn_any::DynAny;
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
use glam::{DAffine2, IVec2}; use glam::{DAffine2, IVec2};
use std::hash::Hash;
pub mod renderer; pub mod renderer;
@ -38,6 +41,25 @@ impl AlphaBlending {
} }
} }
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
GraphicGroup(GraphicGroup),
GraphicGroupTable(GraphicGroupTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group),
EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table,
})
}
pub type GraphicGroupTable = Instances<GraphicGroup>;
/// A list of [`GraphicElement`]s /// A list of [`GraphicElement`]s
#[derive(Clone, Debug, PartialEq, DynAny, Default)] #[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -56,12 +78,6 @@ impl core::hash::Hash for GraphicGroup {
} }
impl GraphicGroup { impl GraphicGroup {
pub const EMPTY: Self = Self {
elements: Vec::new(),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
};
pub fn new(elements: Vec<GraphicElement>) -> Self { pub fn new(elements: Vec<GraphicElement>) -> Self {
Self { Self {
elements: elements.into_iter().map(|element| (element, None)).collect(), elements: elements.into_iter().map(|element| (element, None)).collect(),
@ -71,127 +87,161 @@ impl GraphicGroup {
} }
} }
impl From<GraphicGroup> for GraphicGroupTable {
fn from(graphic_group: GraphicGroup) -> Self {
Self::new(graphic_group)
}
}
impl From<VectorData> for GraphicGroupTable {
fn from(vector_data: VectorData) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(VectorDataTable::new(vector_data))]))
}
}
impl From<VectorDataTable> for GraphicGroupTable {
fn from(vector_data: VectorDataTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(vector_data)]))
}
}
impl From<ImageFrame<Color>> for GraphicGroupTable {
fn from(image_frame: ImageFrame<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame)))]))
}
}
impl From<ImageFrameTable<Color>> for GraphicGroupTable {
fn from(image_frame: ImageFrameTable<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))]))
}
}
impl From<TextureFrame> for GraphicGroupTable {
fn from(texture_frame: TextureFrame) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture_frame)))]))
}
}
impl From<TextureFrameTable> for GraphicGroupTable {
fn from(texture_frame: TextureFrameTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame))]))
}
}
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`]. /// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
/// Can be another recursively nested [`GraphicGroup`], a [`VectorData`] shape, an [`ImageFrame`], or an [`Artboard`].
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GraphicElement { pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroup), GraphicGroup(GraphicGroupTable),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Box<VectorData>), VectorData(VectorDataTable),
Raster(Raster), RasterFrame(RasterFrame),
} }
// TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it. // TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it.
impl Default for GraphicElement { impl Default for GraphicElement {
fn default() -> Self { fn default() -> Self {
Self::VectorData(Box::new(VectorData::empty())) Self::VectorData(VectorDataTable::default())
} }
} }
impl GraphicElement { impl GraphicElement {
pub fn as_group(&self) -> Option<&GraphicGroup> { pub fn as_group(&self) -> Option<&GraphicGroupTable> {
match self { match self {
GraphicElement::GraphicGroup(group) => Some(group), GraphicElement::GraphicGroup(group) => Some(group),
_ => None, _ => None,
} }
} }
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroup> { pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> {
match self { match self {
GraphicElement::GraphicGroup(group) => Some(group), GraphicElement::GraphicGroup(group) => Some(group),
_ => None, _ => None,
} }
} }
pub fn as_vector_data(&self) -> Option<&VectorData> { pub fn as_vector_data(&self) -> Option<&VectorDataTable> {
match self { match self {
GraphicElement::VectorData(data) => Some(data), GraphicElement::VectorData(data) => Some(data),
_ => None, _ => None,
} }
} }
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorData> { pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> {
match self { match self {
GraphicElement::VectorData(data) => Some(data), GraphicElement::VectorData(data) => Some(data),
_ => None, _ => None,
} }
} }
pub fn as_raster(&self) -> Option<&Raster> { pub fn as_raster(&self) -> Option<&RasterFrame> {
match self { match self {
GraphicElement::Raster(raster) => Some(raster), GraphicElement::RasterFrame(raster) => Some(raster),
_ => None, _ => None,
} }
} }
pub fn as_raster_mut(&mut self) -> Option<&mut Raster> { pub fn as_raster_mut(&mut self) -> Option<&mut RasterFrame> {
match self { match self {
GraphicElement::Raster(raster) => Some(raster), GraphicElement::RasterFrame(raster) => Some(raster),
_ => None, _ => None,
} }
} }
} }
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
pub enum Raster { pub enum RasterFrame {
/// A bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrame<Color>), ImageFrame(ImageFrameTable<Color>),
Texture(TextureFrame), /// A GPU texture with a finite position and extent
TextureFrame(TextureFrameTable),
} }
impl<'de> serde::Deserialize<'de> for Raster { impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let frame = ImageFrame::deserialize(deserializer)?; Ok(RasterFrame::ImageFrame(ImageFrameTable::new(ImageFrame::deserialize(deserializer)?)))
Ok(Raster::ImageFrame(frame))
} }
} }
impl serde::Serialize for Raster { impl serde::Serialize for RasterFrame {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,
{ {
match self { match self {
Raster::ImageFrame(_) => self.serialize(serializer), RasterFrame::ImageFrame(_) => self.serialize(serializer),
Raster::Texture(_) => todo!(), RasterFrame::TextureFrame(_) => todo!(),
} }
} }
} }
impl Transform for Raster { impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
match self { match self {
Raster::ImageFrame(frame) => frame.transform(), RasterFrame::ImageFrame(frame) => frame.transform(),
Raster::Texture(frame) => frame.transform(), RasterFrame::TextureFrame(frame) => frame.transform(),
} }
} }
fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 { fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 {
match self { match self {
Raster::ImageFrame(frame) => frame.local_pivot(pivot), RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot),
Raster::Texture(frame) => frame.local_pivot(pivot), RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot),
} }
} }
} }
impl TransformMut for Raster { impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 { fn transform_mut(&mut self) -> &mut DAffine2 {
match self { match self {
Raster::ImageFrame(frame) => frame.transform_mut(), RasterFrame::ImageFrame(frame) => frame.transform_mut(),
Raster::Texture(frame) => frame.transform_mut(), RasterFrame::TextureFrame(frame) => frame.transform_mut(),
} }
} }
} }
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported. /// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
/// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Artboard { pub struct Artboard {
pub graphic_group: GraphicGroup, pub graphic_group: GraphicGroupTable,
pub label: String, pub label: String,
pub location: IVec2, pub location: IVec2,
pub dimensions: IVec2, pub dimensions: IVec2,
@ -202,7 +252,7 @@ pub struct Artboard {
impl Artboard { impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self { pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self { Self {
graphic_group: GraphicGroup::EMPTY, graphic_group: GraphicGroupTable::default(),
label: String::from("Artboard"), label: String::from("Artboard"),
location: location.min(location + dimensions), location: location.min(location + dimensions),
dimensions: dimensions.abs(), dimensions: dimensions.abs(),
@ -220,8 +270,6 @@ pub struct ArtboardGroup {
} }
impl ArtboardGroup { impl ArtboardGroup {
pub const EMPTY: Self = Self { artboards: Vec::new() };
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
@ -239,19 +287,22 @@ async fn layer<F: 'n + Send + Copy>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
stack: impl Node<F, Output = GraphicGroup>, stack: impl Node<F, Output = GraphicGroupTable>,
#[implementations( #[implementations(
() -> GraphicElement, () -> GraphicElement,
Footprint -> GraphicElement, Footprint -> GraphicElement,
)] )]
element: impl Node<F, Output = GraphicElement>, element: impl Node<F, Output = GraphicElement>,
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
) -> GraphicGroup { ) -> GraphicGroupTable {
let mut element = element.eval(footprint).await; let mut element = element.eval(footprint).await;
let mut stack = stack.eval(footprint).await; let stack = stack.eval(footprint).await;
let stack = stack.one_item();
let mut stack = stack.clone();
if stack.transform.matrix2.determinant() != 0. { if stack.transform.matrix2.determinant() != 0. {
*element.transform_mut() = stack.transform.inverse() * element.transform(); *element.transform_mut() = stack.transform.inverse() * element.transform();
} else { } else {
@ -262,7 +313,8 @@ async fn layer<F: 'n + Send + Copy>(
// Get the penultimate element of the node path, or None if the path is too short // Get the penultimate element of the node path, or None if the path is too short
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push((element, encapsulating_node_id)); stack.push((element, encapsulating_node_id));
stack
GraphicGroupTable::new(stack)
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
@ -276,14 +328,14 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> TextureFrame, () -> TextureFrameTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame, Footprint -> TextureFrameTable,
)] )]
data: impl Node<F, Output = Data>, data: impl Node<F, Output = Data>,
) -> GraphicElement { ) -> GraphicElement {
@ -291,7 +343,7 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
} }
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>( async fn to_group<F: 'n + Send, Data: Into<GraphicGroupTable> + 'n>(
#[implementations( #[implementations(
(), (),
(), (),
@ -301,17 +353,17 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> TextureFrame, () -> TextureFrameTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame, Footprint -> TextureFrameTable,
)] )]
element: impl Node<F, Output = Data>, element: impl Node<F, Output = Data>,
) -> GraphicGroup { ) -> GraphicGroupTable {
element.eval(footprint).await.into() element.eval(footprint).await.into()
} }
@ -323,20 +375,28 @@ async fn flatten_group<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
group: impl Node<F, Output = GraphicGroup>, group: impl Node<F, Output = GraphicGroupTable>,
fully_flatten: bool, fully_flatten: bool,
) -> GraphicGroup { ) -> GraphicGroupTable {
let nested_group = group.eval(footprint).await; let nested_group = group.eval(footprint).await;
let mut flat_group = GraphicGroup::EMPTY; let nested_group = nested_group.one_item();
let nested_group = nested_group.clone();
let mut flat_group = GraphicGroup::default();
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
let mut collection_group = GraphicGroup::EMPTY; let mut collection_group = GraphicGroup::default();
for (element, reference) in current_group.elements { for (element, reference) in current_group.elements {
if let GraphicElement::GraphicGroup(mut nested_group) = element { if let GraphicElement::GraphicGroup(nested_group) = element {
nested_group.transform *= current_group.transform; let nested_group = nested_group.one_item();
let mut sub_group = GraphicGroup::EMPTY; let mut nested_group = nested_group.clone();
*nested_group.transform_mut() = nested_group.transform() * current_group.transform;
let mut sub_group = GraphicGroup::default();
if fully_flatten { if fully_flatten {
flatten_group(&mut sub_group, nested_group, fully_flatten); flatten_group(&mut sub_group, nested_group, fully_flatten);
} else { } else {
@ -353,12 +413,14 @@ async fn flatten_group<F: 'n + Send>(
result_group.append(&mut collection_group.elements); result_group.append(&mut collection_group.elements);
} }
flatten_group(&mut flat_group, nested_group, fully_flatten); flatten_group(&mut flat_group, nested_group, fully_flatten);
flat_group
GraphicGroupTable::new(flat_group)
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>( async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable> + 'n>(
#[implementations( #[implementations(
(), (),
(), (),
@ -368,13 +430,13 @@ async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + '
)] )]
mut footprint: F, mut footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> TextureFrame, () -> TextureFrame,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame, Footprint -> TextureFrame,
)] )]
contents: impl Node<F, Output = Data>, contents: impl Node<F, Output = Data>,
@ -426,23 +488,47 @@ async fn append_artboard<F: 'n + Send + Copy>(
artboards artboards
} }
// TODO: Remove this one
impl From<ImageFrame<Color>> for GraphicElement { impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self { fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::Raster(Raster::ImageFrame(image_frame)) GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame)))
} }
} }
impl From<ImageFrameTable<Color>> for GraphicElement {
fn from(image_frame: ImageFrameTable<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))
}
}
// TODO: Remove this one
impl From<TextureFrame> for GraphicElement { impl From<TextureFrame> for GraphicElement {
fn from(texture: TextureFrame) -> Self { fn from(texture: TextureFrame) -> Self {
GraphicElement::Raster(Raster::Texture(texture)) GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture)))
} }
} }
impl From<TextureFrameTable> for GraphicElement {
fn from(texture: TextureFrameTable) -> Self {
GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture))
}
}
// TODO: Remove this one
impl From<VectorData> for GraphicElement { impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self { fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorData(Box::new(vector_data)) GraphicElement::VectorData(VectorDataTable::new(vector_data))
} }
} }
impl From<VectorDataTable> for GraphicElement {
fn from(vector_data: VectorDataTable) -> Self {
GraphicElement::VectorData(vector_data)
}
}
// TODO: Remove this one
impl From<GraphicGroup> for GraphicElement { impl From<GraphicGroup> for GraphicElement {
fn from(graphic_group: GraphicGroup) -> Self { fn from(graphic_group: GraphicGroup) -> Self {
GraphicElement::GraphicGroup(GraphicGroupTable::new(graphic_group))
}
}
impl From<GraphicGroupTable> for GraphicElement {
fn from(graphic_group: GraphicGroupTable) -> Self {
GraphicElement::GraphicGroup(graphic_group) GraphicElement::GraphicGroup(graphic_group)
} }
} }
@ -464,8 +550,8 @@ impl DerefMut for GraphicGroup {
/// as that would conflict with the implementation for `Self` /// as that would conflict with the implementation for `Self`
trait ToGraphicElement: Into<GraphicElement> {} trait ToGraphicElement: Into<GraphicElement> {}
impl ToGraphicElement for VectorData {} impl ToGraphicElement for VectorDataTable {}
impl ToGraphicElement for ImageFrame<Color> {} impl ToGraphicElement for ImageFrameTable<Color> {}
impl ToGraphicElement for TextureFrame {} impl ToGraphicElement for TextureFrame {}
impl<T> From<T> for GraphicGroup impl<T> From<T> for GraphicGroup

View file

@ -3,13 +3,13 @@ mod rect;
pub use quad::Quad; pub use quad::Quad;
pub use rect::Rect; pub use rect::Rect;
use crate::raster::{BlendMode, Image, ImageFrame}; use crate::raster::image::ImageFrameTable;
use crate::raster::{BlendMode, Image};
use crate::transform::{Footprint, Transform}; use crate::transform::{Footprint, Transform};
use crate::uuid::{generate_uuid, NodeId}; use crate::uuid::{generate_uuid, NodeId};
use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::PointId; use crate::vector::{PointId, VectorDataTable};
use crate::Raster; use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use dyn_any::DynAny; use dyn_any::DynAny;
@ -217,7 +217,7 @@ pub enum ImageRenderMode {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct RenderContext { pub struct RenderContext {
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
pub ressource_overrides: std::collections::HashMap<u64, alloc::sync::Arc<wgpu::Texture>>, pub resource_overrides: std::collections::HashMap<u64, alloc::sync::Arc<wgpu::Texture>>,
} }
/// Static state used whilst rendering /// Static state used whilst rendering
@ -406,60 +406,117 @@ impl GraphicElementRendered for GraphicGroup {
} }
} }
fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone()))
}
}
impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
instance.render_svg(render, render_params);
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
instance.collect_metadata(metadata, footprint, element_id);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
instance.add_upstream_click_targets(click_targets);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
for instance in self.instances() {
instance.render_to_vello(scene, transform, context);
}
}
fn contains_artboard(&self) -> bool {
self.instances().any(|instance| instance.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instances_mut() {
instance.new_ids_from_hash(None);
}
}
fn to_graphic_element(&self) -> GraphicElement { fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(self.clone()) GraphicElement::GraphicGroup(self.clone())
} }
} }
impl GraphicElementRendered for VectorData { impl GraphicElementRendered for VectorDataTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let multiplied_transform = render.transform * self.transform; for instance in self.instances() {
let set_stroke_transform = self.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let multiplied_transform = render.transform * instance.transform;
let applied_stroke_transform = set_stroke_transform.unwrap_or(self.transform); let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform);
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let layer_bounds = self.bounding_box().unwrap_or_default(); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let transformed_bounds = self.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default(); let layer_bounds = instance.bounding_box().unwrap_or_default();
let transformed_bounds = instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let mut path = String::new(); let mut path = String::new();
for subpath in self.stroke_bezier_paths() { for subpath in instance.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
}
render.leaf_tag("path", |attributes| {
attributes.push("d", path);
let matrix = format_transform_matrix(element_transform);
attributes.push("transform", matrix);
let defs = &mut attributes.0.svg_defs;
let fill_and_stroke = instance
.style
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke);
if instance.alpha_blending.opacity < 1. {
attributes.push("opacity", instance.alpha_blending.opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
}
});
} }
render.leaf_tag("path", |attributes| {
attributes.push("d", path);
let matrix = format_transform_matrix(element_transform);
attributes.push("transform", matrix);
let defs = &mut attributes.0.svg_defs;
let fill_and_stroke = self
.style
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke);
if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.alpha_blending.opacity.to_string());
}
if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.alpha_blending.blend_mode.render());
}
});
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let stroke_width = self.style.stroke().map(|s| s.weight()).unwrap_or_default(); self.instances()
let miter_limit = self.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.); .flat_map(|instance| {
let scale = transform.decompose_scale(); let stroke_width = instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
// We use the full line width here to account for different styles of line caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit); let miter_limit = instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
self.bounding_box_with_transform(transform * self.transform).map(|[a, b]| [a - offset, b + offset])
let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of line caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
instance.bounding_box_with_transform(transform * instance.transform).map(|[a, b]| [a - offset, b + offset])
})
.reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = self.style.fill() != &Fill::None; let filled = instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| { let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled { if filled {
subpath.set_closed(true); subpath.set_closed(true);
@ -467,7 +524,7 @@ impl GraphicElementRendered for VectorData {
subpath subpath
}; };
let click_targets = self let click_targets = instance
.stroke_bezier_paths() .stroke_bezier_paths()
.map(fill) .map(fill)
.map(|subpath| ClickTarget::new(subpath, stroke_width)) .map(|subpath| ClickTarget::new(subpath, stroke_width))
@ -476,145 +533,155 @@ impl GraphicElementRendered for VectorData {
metadata.click_targets.insert(element_id, click_targets); metadata.click_targets.insert(element_id, click_targets);
} }
if let Some(upstream_graphic_group) = &self.upstream_graphic_group { if let Some(upstream_graphic_group) = &instance.upstream_graphic_group {
footprint.transform *= self.transform; footprint.transform *= instance.transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None); upstream_graphic_group.collect_metadata(metadata, footprint, None);
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); for instance in self.instances() {
let filled = self.style.fill() != &Fill::None; let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let fill = |mut subpath: bezier_rs::Subpath<_>| { let filled = instance.style.fill() != &Fill::None;
if filled { let fill = |mut subpath: bezier_rs::Subpath<_>| {
subpath.set_closed(true); if filled {
} subpath.set_closed(true);
subpath }
}; subpath
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); };
click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
}
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext) { fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext) {
use crate::vector::style::GradientType; use crate::vector::style::GradientType;
use vello::peniko; use vello::peniko;
let mut layer = false;
let multiplied_transform = parent_transform * self.transform; for instance in self.instances() {
let set_stroke_transform = self.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let mut layer = false;
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = self.bounding_box().unwrap_or_default();
if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() { let multiplied_transform = parent_transform * instance.transform;
layer = true; let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
scene.push_layer( let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver), let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
self.alpha_blending.opacity, let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
kurbo::Affine::new(multiplied_transform.to_cols_array()), let layer_bounds = instance.bounding_box().unwrap_or_default();
&kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y),
);
}
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
let mut path = kurbo::BezPath::new(); layer = true;
for subpath in self.stroke_bezier_paths() { scene.push_layer(
subpath.to_vello_path(applied_stroke_transform, &mut path); peniko::BlendMode::new(instance.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver),
} instance.alpha_blending.opacity,
kurbo::Affine::new(multiplied_transform.to_cols_array()),
match self.style.fill() { &kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y),
Fill::Solid(color) => { );
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
} }
Fill::Gradient(gradient) => {
let mut stops = peniko::ColorStops::new(); let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
for &(offset, color) in &gradient.stops.0 { let mut path = kurbo::BezPath::new();
stops.push(peniko::ColorStop { for subpath in instance.stroke_bezier_paths() {
offset: offset as f32, subpath.to_vello_path(applied_stroke_transform, &mut path);
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])), }
});
match instance.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
} }
// Compute bounding box of the shape to determine the gradient start and end points Fill::Gradient(gradient) => {
let bounds = self.nonzero_bounding_box(); let mut stops = peniko::ColorStops::new();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); for &(offset, color) in &gradient.stops.0 {
stops.push(peniko::ColorStop {
offset: offset as f32,
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
});
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default(); let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
let mod_points = inverse_parent_transform * multiplied_transform * bound_transform; let mod_points = inverse_parent_transform * multiplied_transform * bound_transform;
let start = mod_points.transform_point2(gradient.start); let start = mod_points.transform_point2(gradient.start);
let end = mod_points.transform_point2(gradient.end); let end = mod_points.transform_point2(gradient.end);
let fill = peniko::Brush::Gradient(peniko::Gradient { let fill = peniko::Brush::Gradient(peniko::Gradient {
kind: match gradient.gradient_type { kind: match gradient.gradient_type {
GradientType::Linear => peniko::GradientKind::Linear { GradientType::Linear => peniko::GradientKind::Linear {
start: to_point(start), start: to_point(start),
end: to_point(end), end: to_point(end),
}, },
GradientType::Radial => { GradientType::Radial => {
let radius = start.distance(end); let radius = start.distance(end);
peniko::GradientKind::Radial { peniko::GradientKind::Radial {
start_center: to_point(start), start_center: to_point(start),
start_radius: 0., start_radius: 0.,
end_center: to_point(start), end_center: to_point(start),
end_radius: radius as f32, end_radius: radius as f32,
}
} }
} },
}, stops,
stops, ..Default::default()
..Default::default() });
}); // Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse.
// Vello does `elment_transform * brush_transform` internally. We don't want elment_transform to have any impact so we need to left multiply by the inverse. // This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder.
// This makes the final internal brush transform equal to `parent_transform`, allowing you to strech a gradient by transforming the parent folder. let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default();
let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default(); let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array()); scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path); }
} Fill::None => (),
Fill::None => (), };
};
if let Some(stroke) = self.style.stroke() { if let Some(stroke) = instance.style.stroke() {
let color = match stroke.color { let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]), Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT, None => peniko::Color::TRANSPARENT,
}; };
use crate::vector::style::{LineCap, LineJoin}; use crate::vector::style::{LineCap, LineJoin};
use vello::kurbo::{Cap, Join}; use vello::kurbo::{Cap, Join};
let cap = match stroke.line_cap { let cap = match stroke.line_cap {
LineCap::Butt => Cap::Butt, LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round, LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square, LineCap::Square => Cap::Square,
}; };
let join = match stroke.line_join { let join = match stroke.line_join {
LineJoin::Miter => Join::Miter, LineJoin::Miter => Join::Miter,
LineJoin::Bevel => Join::Bevel, LineJoin::Bevel => Join::Bevel,
LineJoin::Round => Join::Round, LineJoin::Round => Join::Round,
}; };
let stroke = kurbo::Stroke { let stroke = kurbo::Stroke {
width: stroke.weight, width: stroke.weight,
miter_limit: stroke.line_join_miter_limit, miter_limit: stroke.line_join_miter_limit,
join, join,
start_cap: cap, start_cap: cap,
end_cap: cap, end_cap: cap,
dash_pattern: stroke.dash_lengths.into(), dash_pattern: stroke.dash_lengths.into(),
dash_offset: stroke.dash_offset, dash_offset: stroke.dash_offset,
}; };
if stroke.width > 0. { if stroke.width > 0. {
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path); scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);
}
}
if layer {
scene.pop_layer();
} }
}
if layer {
scene.pop_layer();
} }
} }
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
self.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); for instance in self.instances_mut() {
instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}
} }
fn to_graphic_element(&self) -> GraphicElement { fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::VectorData(Box::new(self.clone())) let instance = self.one_item();
GraphicElement::VectorData(VectorDataTable::new(instance.clone()))
} }
} }
@ -690,8 +757,8 @@ impl GraphicElementRendered for Artboard {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
if self.graphic_group.transform.matrix2.determinant() != 0. { if self.graphic_group.transform().matrix2.determinant() != 0. {
subpath.apply_transform(self.graphic_group.transform.inverse()); subpath.apply_transform(self.graphic_group.transform().inverse());
click_targets.push(ClickTarget::new(subpath, 0.)); click_targets.push(ClickTarget::new(subpath, 0.));
} }
} }
@ -726,7 +793,7 @@ impl GraphicElementRendered for Artboard {
} }
} }
impl GraphicElementRendered for crate::ArtboardGroup { impl GraphicElementRendered for ArtboardGroup {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (artboard, _) in &self.artboards { for (artboard, _) in &self.artboards {
artboard.render_svg(render, render_params); artboard.render_svg(render, render_params);
@ -761,56 +828,64 @@ impl GraphicElementRendered for crate::ArtboardGroup {
} }
} }
impl GraphicElementRendered for ImageFrame<Color> { impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let transform = self.transform * render.transform; for instance in self.instances() {
let transform = instance.transform * render.transform;
match render_params.image_render_mode { match render_params.image_render_mode {
ImageRenderMode::Base64 => { ImageRenderMode::Base64 => {
let image = &self.image; let image = &instance.image;
if image.data.is_empty() { if image.data.is_empty() {
return; return;
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
let output = image.to_png();
let preamble = "data:image/png;base64,";
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if instance.alpha_blending.opacity < 1. {
attributes.push("opacity", instance.alpha_blending.opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
}
});
} }
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
let output = image.to_png();
let preamble = "data:image/png;base64,";
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.alpha_blending.opacity.to_string());
}
if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.alpha_blending.blend_mode.render());
}
});
} }
} }
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let transform = transform * self.transform; self.instances()
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) .flat_map(|instance| {
let transform = transform * instance.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
})
.reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
let Some(element_id) = element_id else { return }; let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]); metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, self.transform)); metadata.footprints.insert(element_id, (footprint, instance.transform));
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@ -822,55 +897,61 @@ impl GraphicElementRendered for ImageFrame<Color> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext) {
use vello::peniko; use vello::peniko;
let image = &self.image; for instance in self.instances() {
if image.data.is_empty() { let image = &instance.image;
return; if image.data.is_empty() {
} return;
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); }
let transform = transform * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let transform = transform * instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
}
} }
} }
impl GraphicElementRendered for Raster {
impl GraphicElementRendered for RasterFrame {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let transform = self.transform() * render.transform; let transform = self.transform() * render.transform;
match render_params.image_render_mode { match render_params.image_render_mode {
ImageRenderMode::Base64 => { ImageRenderMode::Base64 => {
let image = match self { let image = match self {
Raster::ImageFrame(ref image) => image, RasterFrame::ImageFrame(ref image) => image,
Raster::Texture(_) => return, RasterFrame::TextureFrame(_) => return,
}; };
let (image, blending) = (&image.image, image.alpha_blending);
if image.data.is_empty() {
return;
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| { for image in image.instances() {
let output = image.to_png(); let (image, blending) = (&image.image, image.alpha_blending);
let preamble = "data:image/png;base64,"; if image.data.is_empty() {
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); return;
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
} }
if blending.opacity < 1. {
attributes.push("opacity", blending.opacity.to_string()); let base64_string = image.base64_string.clone().unwrap_or_else(|| {
} let output = image.to_png();
if blending.blend_mode != BlendMode::default() { let preamble = "data:image/png;base64,";
attributes.push("style", blending.blend_mode.render()); let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
} base64_string.push_str(preamble);
}); base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if blending.opacity < 1. {
attributes.push("opacity", blending.opacity.to_string());
}
if blending.blend_mode != BlendMode::default() {
attributes.push("style", blending.blend_mode.render());
}
});
}
} }
} }
} }
@ -897,35 +978,46 @@ impl GraphicElementRendered for Raster {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
use vello::peniko; use vello::peniko;
let (image, blend_mode) = match self { let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
Raster::ImageFrame(image_frame) => { let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let image = &image_frame.image; let layer = blend_mode != Default::default();
if image.data.is_empty() {
return; let Some(bounds) = self.bounding_box(transform) else { return };
} let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver);
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
(image, image_frame.alpha_blending) if layer {
let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
} }
Raster::Texture(texture) => { scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array()));
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat); if layer {
let id = image.data.id(); scene.pop_layer()
context.ressource_overrides.insert(id, texture.texture.clone());
(image, texture.alpha_blend)
} }
}; };
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let layer = blend_mode != Default::default();
let Some(bounds) = self.bounding_box(transform) else { return }; match self {
let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver); RasterFrame::ImageFrame(image_frame) => {
for image_frame in image_frame.instances() {
let image = &image_frame.image;
if image.data.is_empty() {
return;
}
if layer { let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect); render_stuff(image, image_frame.alpha_blending);
} }
scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array())); }
if layer { RasterFrame::TextureFrame(texture) => {
scene.pop_layer() for texture in texture.instances() {
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat);
let id = image.data.id();
context.resource_overrides.insert(id, texture.texture.clone());
render_stuff(image, texture.alpha_blend);
}
}
} }
} }
} }
@ -934,15 +1026,15 @@ impl GraphicElementRendered for GraphicElement {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params), GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params),
GraphicElement::Raster(raster) => raster.render_svg(render, render_params), GraphicElement::RasterFrame(raster) => raster.render_svg(render, render_params),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
} }
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
match self { match self {
GraphicElement::VectorData(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform), GraphicElement::VectorData(vector_data) => vector_data.bounding_box(transform),
GraphicElement::Raster(raster) => raster.bounding_box(transform), GraphicElement::RasterFrame(raster) => raster.bounding_box(transform),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform), GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform),
} }
} }
@ -954,7 +1046,7 @@ impl GraphicElementRendered for GraphicElement {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id), GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
GraphicElement::Raster(raster) => raster.collect_metadata(metadata, footprint, element_id), GraphicElement::RasterFrame(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id), GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
} }
} }
@ -962,7 +1054,7 @@ impl GraphicElementRendered for GraphicElement {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets), GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
GraphicElement::Raster(raster) => raster.add_upstream_click_targets(click_targets), GraphicElement::RasterFrame(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets), GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
} }
} }
@ -972,7 +1064,7 @@ impl GraphicElementRendered for GraphicElement {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context), GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context),
GraphicElement::Raster(raster) => raster.render_to_vello(scene, transform, context), GraphicElement::RasterFrame(raster) => raster.render_to_vello(scene, transform, context),
} }
} }
@ -980,7 +1072,7 @@ impl GraphicElementRendered for GraphicElement {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(), GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(),
GraphicElement::Raster(raster) => raster.contains_artboard(), GraphicElement::RasterFrame(raster) => raster.contains_artboard(),
} }
} }
@ -988,7 +1080,7 @@ impl GraphicElementRendered for GraphicElement {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference), GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference), GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
GraphicElement::Raster(_) => (), GraphicElement::RasterFrame(_) => (),
} }
} }
} }

View file

@ -0,0 +1,87 @@
use crate::vector::InstanceId;
use crate::GraphicElement;
use dyn_any::StaticType;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Instances<T>
where
T: Into<GraphicElement> + StaticType + 'static,
{
id: Vec<InstanceId>,
instances: Vec<T>,
}
impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
pub fn new(instance: T) -> Self {
Self {
id: vec![InstanceId::generate()],
instances: vec![instance],
}
}
pub fn one_item(&self) -> &T {
self.instances.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item)", self.instances.len()))
}
pub fn one_item_mut(&mut self) -> &mut T {
let length = self.instances.len();
self.instances.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item_mut)", length))
}
pub fn instances(&self) -> impl Iterator<Item = &T> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len());
self.instances.iter()
}
pub fn instances_mut(&mut self) -> impl Iterator<Item = &mut T> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instances.len());
self.instances.iter_mut()
}
// pub fn id(&self) -> impl Iterator<Item = InstanceId> + '_ {
// self.id.iter().copied()
// }
// pub fn push(&mut self, id: InstanceId, instance: T) {
// self.id.push(id);
// self.instances.push(instance);
// }
// pub fn replace_all(&mut self, id: InstanceId, instance: T) {
// let mut instance = instance;
// for (old_id, old_instance) in self.id.iter_mut().zip(self.instances.iter_mut()) {
// let mut new_id = id;
// std::mem::swap(old_id, &mut new_id);
// std::mem::swap(&mut instance, old_instance);
// }
// }
}
impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
for instance in &self.instances {
instance.hash(state);
}
}
}
impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.instances.len() == other.instances.len() && { self.instances.iter().zip(other.instances.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> {
type Static = Instances<T>;
}

View file

@ -15,6 +15,7 @@ pub use ctor;
pub mod consts; pub mod consts;
pub mod generic; pub mod generic;
pub mod instances;
pub mod logic; pub mod logic;
pub mod ops; pub mod ops;
pub mod structural; pub mod structural;

View file

@ -1,13 +1,14 @@
use crate::transform::Footprint; use crate::transform::Footprint;
use crate::vector::VectorData; use crate::vector::VectorDataTable;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>( async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>(
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
#[implementations( #[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2, Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2,
)] )]
value: impl Node<F, Output = T>, value: impl Node<F, Output = T>,
) -> T { ) -> T {
@ -37,14 +38,14 @@ async fn switch<T, F: Send + 'n>(
condition: bool, condition: bool,
#[expose] #[expose]
#[implementations( #[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
)] )]
if_true: impl Node<F, Output = T>, if_true: impl Node<F, Output = T>,
#[expose] #[expose]
#[implementations( #[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
)] )]
if_false: impl Node<F, Output = T>, if_false: impl Node<F, Output = T>,
) -> T { ) -> T {

View file

@ -1,5 +1,5 @@
use crate::raster::image::ImageFrameTable;
use crate::raster::BlendMode; use crate::raster::BlendMode;
use crate::raster::ImageFrame;
use crate::registry::types::Percentage; use crate::registry::types::Percentage;
use crate::vector::style::GradientStops; use crate::vector::style::GradientStops;
use crate::{Color, Node}; use crate::{Color, Node};
@ -472,7 +472,7 @@ fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<
/// Meant for debugging purposes, not general use. Clones the input value. /// Meant for debugging purposes, not general use. Clones the input value.
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame<Color>)] value: &'i T) -> T { fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
value.clone() value.clone()
} }

View file

@ -1,7 +1,9 @@
pub use self::color::{Color, Luma, SRGBA8}; pub use self::color::{Color, Luma, SRGBA8};
use crate::vector::VectorData; use crate::raster::image::ImageFrameTable;
use crate::GraphicGroup; use crate::registry::types::Percentage;
use crate::{registry::types::Percentage, transform::Footprint}; use crate::transform::Footprint;
use crate::vector::VectorDataTable;
use crate::GraphicGroupTable;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use core::fmt::Debug; use core::fmt::Debug;
@ -283,27 +285,33 @@ impl<T: BitmapMut + Bitmap> BitmapMut for &mut T {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use self::image::{Image, ImageFrame}; pub use self::image::Image;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub(crate) mod image; pub mod image;
trait SetBlendMode { trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode); fn set_blend_mode(&mut self, blend_mode: BlendMode);
} }
impl SetBlendMode for VectorData { impl SetBlendMode for VectorDataTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode; for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
} }
} }
impl SetBlendMode for GraphicGroup { impl SetBlendMode for GraphicGroupTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode; for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
} }
} }
impl SetBlendMode for ImageFrame<Color> { impl SetBlendMode for ImageFrameTable<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode; for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
} }
} }
@ -317,12 +325,12 @@ async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
)] )]
value: impl Node<F, Output = T>, value: impl Node<F, Output = T>,
blend_mode: BlendMode, blend_mode: BlendMode,
@ -342,12 +350,12 @@ async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
)] )]
value: impl Node<F, Output = T>, value: impl Node<F, Output = T>,
#[default(100.)] factor: Percentage, #[default(100.)] factor: Percentage,

View file

@ -1,15 +1,15 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode}; use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use super::ImageFrame; use crate::raster::image::{ImageFrame, ImageFrameTable};
use super::{Channel, Color, Pixel}; use crate::raster::{Channel, Color, Pixel};
use crate::registry::types::{Angle, Percentage, SignedPercentage}; use crate::registry::types::{Angle, Percentage, SignedPercentage};
use crate::transform::Footprint; use crate::transform::Footprint;
use crate::vector::style::GradientStops; use crate::vector::style::GradientStops;
use crate::vector::VectorData; use crate::vector::VectorDataTable;
use crate::GraphicGroup; use crate::{GraphicElement, GraphicGroupTable};
use dyn_any::DynAny; use dyn_any::DynAny;
@ -294,10 +294,10 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -328,10 +328,10 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -361,10 +361,10 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -395,10 +395,10 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -472,10 +472,10 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -554,10 +554,10 @@ async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -598,10 +598,10 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -630,10 +630,10 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -666,7 +666,6 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
trait Blend<P: Pixel> { trait Blend<P: Pixel> {
fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;
} }
impl Blend<Color> for Color { impl Blend<Color> for Color {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
blend_fn(*self, *under) blend_fn(*self, *under)
@ -681,24 +680,28 @@ impl Blend<Color> for Option<Color> {
} }
} }
} }
impl Blend<Color> for ImageFrameTable<Color> {
impl Blend<Color> for ImageFrame<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let data = self.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); let mut result = self.clone();
ImageFrame { for (over, under) in result.instances_mut().zip(under.instances()) {
image: super::Image { let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
data,
width: self.image.width, *over = ImageFrame {
height: self.image.height, image: super::Image {
base64_string: None, data,
}, width: over.image.width,
transform: self.transform, height: over.image.height,
alpha_blending: self.alpha_blending, base64_string: None,
},
transform: over.transform,
alpha_blending: over.alpha_blending,
};
} }
result
} }
} }
impl Blend<Color> for GradientStops { impl Blend<Color> for GradientStops {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut combined_stops = self.0.iter().map(|(position, _)| position).chain(under.0.iter().map(|(position, _)| position)).collect::<Vec<_>>(); let mut combined_stops = self.0.iter().map(|(position, _)| position).chain(under.0.iter().map(|(position, _)| position)).collect::<Vec<_>>();
@ -730,20 +733,20 @@ async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
over: impl Node<F, Output = T>, over: impl Node<F, Output = T>,
#[expose] #[expose]
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
under: impl Node<F, Output = T>, under: impl Node<F, Output = T>,
@ -753,7 +756,7 @@ async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
let over = over.eval(footprint).await; let over = over.eval(footprint).await;
let under = under.eval(footprint).await; let under = under.eval(footprint).await;
Blend::blend(&over, &under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
@ -800,8 +803,8 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
} }
} }
trait Adjust<C> { trait Adjust<P> {
fn adjust(&mut self, map_fn: impl Fn(&C) -> C); fn adjust(&mut self, map_fn: impl Fn(&P) -> P);
} }
impl Adjust<Color> for Color { impl Adjust<Color> for Color {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
@ -822,10 +825,17 @@ impl Adjust<Color> for GradientStops {
} }
} }
} }
impl<C: Pixel> Adjust<C> for ImageFrame<C> { impl<P: Pixel> Adjust<P> for ImageFrameTable<P>
fn adjust(&mut self, map_fn: impl Fn(&C) -> C) { where
for c in self.image.data.iter_mut() { P: dyn_any::StaticType,
*c = map_fn(c); P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
for instance in self.instances_mut() {
for c in instance.image.data.iter_mut() {
*c = map_fn(c);
}
} }
} }
} }
@ -857,10 +867,10 @@ async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -896,10 +906,10 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -1196,10 +1206,10 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -1357,10 +1367,10 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -1490,19 +1500,30 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.)) *self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
} }
} }
impl MultiplyAlpha for VectorData { impl MultiplyAlpha for VectorDataTable {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32; for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
} }
} }
impl MultiplyAlpha for GraphicGroup { impl MultiplyAlpha for GraphicGroupTable {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32; for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
} }
} }
impl<P: Pixel> MultiplyAlpha for ImageFrame<P> { impl<P: Pixel> MultiplyAlpha for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32; for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
} }
} }
@ -1523,10 +1544,10 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -1566,10 +1587,10 @@ async fn exposure<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
input: impl Node<F, Output = T>, input: impl Node<F, Output = T>,
@ -1649,10 +1670,10 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
@ -1675,33 +1696,34 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
input input
} }
#[cfg(feature = "alloc")] // #[cfg(feature = "alloc")]
pub use index_node::IndexNode; // pub use index_node::IndexNode;
#[cfg(feature = "alloc")] // #[cfg(feature = "alloc")]
mod index_node { // mod index_node {
use crate::raster::{Color, ImageFrame}; // use crate::raster::{Color, ImageFrame};
#[node_macro::node(category(""))] // #[node_macro::node(category(""))]
pub fn index<T: Default + Clone>( // pub fn index<T: Default + Clone>(
_: (), // _: (),
#[implementations(Vec<ImageFrame<Color>>, Vec<Color>)] // #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
#[widget(ParsedWidgetOverride::Hidden)] // #[widget(ParsedWidgetOverride::Hidden)]
input: Vec<T>, // input: Vec<T>,
index: u32, // index: u32,
) -> T { // ) -> T {
if (index as usize) < input.len() { // if (index as usize) < input.len() {
input[index as usize].clone() // input[index as usize].clone()
} else { // } else {
warn!("The number of segments is {} but the requested segment is {}!", input.len(), index); // warn!("The number of segments is {} but the requested segment is {}!", input.len(), index);
Default::default() // Default::default()
} // }
} // }
} // }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::raster::{BlendMode, Image, ImageFrame}; use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::{BlendMode, Image};
use crate::{Color, Node}; use crate::{Color, Node};
use std::pin::Pin; use std::pin::Pin;
@ -1730,7 +1752,8 @@ mod test {
// 100% of the output should come from the multiplied value // 100% of the output should come from the multiplied value
let opacity = 100_f64; let opacity = 100_f64;
let result = super::color_overlay((), &FutureWrapperNode(image), overlay_color, BlendMode::Multiply, opacity).await; let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await;
let result = result.one_item();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) // 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.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View file

@ -5,8 +5,8 @@ use std::sync::Mutex;
use dyn_any::DynAny; use dyn_any::DynAny;
use crate::raster::image::ImageFrame;
use crate::raster::Image; use crate::raster::Image;
use crate::raster::ImageFrame;
use crate::vector::brush_stroke::BrushStroke; use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle; use crate::vector::brush_stroke::BrushStyle;
use crate::Color; use crate::Color;

View file

@ -1,6 +1,7 @@
use super::discrete_srgb::float_to_srgb_u8; use super::discrete_srgb::float_to_srgb_u8;
use super::Color; use super::Color;
use crate::AlphaBlending; use crate::instances::Instances;
use crate::{AlphaBlending, GraphicElement};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
use dyn_any::StaticType; use dyn_any::StaticType;
@ -216,7 +217,26 @@ impl<P: Pixel> IntoIterator for Image<P> {
} }
} }
#[derive(Clone, Debug, PartialEq, Default, specta::Type)] // TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ImageFrame(ImageFrame<Color>),
ImageFrameTable(ImageFrameTable<Color>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame),
EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
pub type ImageFrameTable<P> = Instances<ImageFrame<P>>;
#[derive(Clone, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> { pub struct ImageFrame<P: Pixel> {
pub image: Image<P>, pub image: Image<P>,
@ -233,6 +253,17 @@ pub struct ImageFrame<P: Pixel> {
pub alpha_blending: AlphaBlending, pub alpha_blending: AlphaBlending,
} }
impl<P: Pixel> Default for ImageFrame<P> {
fn default() -> Self {
Self {
image: Image::empty(),
alpha_blending: AlphaBlending::new(),
// Different from DAffine2::default() which is IDENTITY
transform: DAffine2::ZERO,
}
}
}
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> { impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
type Pixel = P; type Pixel = P;
@ -248,6 +279,22 @@ impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
} }
} }
impl<P: Debug + Copy + Pixel + dyn_any::StaticType> Sample for ImageFrameTable<P>
where
GraphicElement: From<ImageFrame<P>>,
P::Static: Pixel,
{
type Pixel = P;
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image = self.one_item();
Sample::sample(image, pos, area)
}
}
impl<P: Copy + Pixel> Bitmap for ImageFrame<P> { impl<P: Copy + Pixel> Bitmap for ImageFrame<P> {
type Pixel = P; type Pixel = P;
@ -264,12 +311,50 @@ impl<P: Copy + Pixel> Bitmap for ImageFrame<P> {
} }
} }
impl<P: Copy + Pixel + dyn_any::StaticType> Bitmap for ImageFrameTable<P>
where
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_item();
image.width()
}
fn height(&self) -> u32 {
let image = self.one_item();
image.height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_item();
image.get_pixel(x, y)
}
}
impl<P: Copy + Pixel> BitmapMut for ImageFrame<P> { impl<P: Copy + Pixel> BitmapMut for ImageFrame<P> {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
self.image.get_pixel_mut(x, y) self.image.get_pixel_mut(x, y)
} }
} }
impl<P: Copy + Pixel + dyn_any::StaticType> BitmapMut for ImageFrameTable<P>
where
GraphicElement: From<ImageFrame<P>>,
P::Static: Pixel,
{
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
let image = self.one_item_mut();
BitmapMut::get_pixel_mut(image, x, y)
}
}
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for ImageFrame<P> unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for ImageFrame<P>
where where
P::Static: Pixel, P::Static: Pixel,
@ -278,22 +363,6 @@ where
} }
impl<P: Copy + Pixel> ImageFrame<P> { impl<P: Copy + Pixel> ImageFrame<P> {
pub const fn empty() -> Self {
Self {
image: Image::empty(),
transform: DAffine2::ZERO,
alpha_blending: AlphaBlending::new(),
}
}
pub const fn identity() -> Self {
Self {
image: Image::empty(),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
}
}
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P { pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {
&mut self.image.data[y * (self.image.width as usize) + x] &mut self.image.data[y * (self.image.width as usize) + x]
} }

View file

@ -1,8 +1,9 @@
use crate::application_io::TextureFrame; use crate::application_io::{TextureFrame, TextureFrameTable};
use crate::raster::bbox::AxisAlignedBbox; use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::{ImageFrame, Pixel}; use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::vector::VectorData; use crate::raster::Pixel;
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup}; use crate::vector::{VectorData, VectorDataTable};
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@ -19,12 +20,6 @@ pub trait Transform {
} }
} }
impl<T: Transform> Transform for &T {
fn transform(&self) -> DAffine2 {
(*self).transform()
}
}
pub trait TransformMut: Transform { pub trait TransformMut: Transform {
fn transform_mut(&mut self) -> &mut DAffine2; fn transform_mut(&mut self) -> &mut DAffine2;
fn translate(&mut self, offset: DVec2) { fn translate(&mut self, offset: DVec2) {
@ -32,6 +27,14 @@ pub trait TransformMut: Transform {
} }
} }
// Implementation for references to anything that implements Transform
impl<T: Transform> Transform for &T {
fn transform(&self) -> DAffine2 {
(*self).transform()
}
}
// Implementations for ImageFrame<P>
impl<P: Pixel> Transform for ImageFrame<P> { impl<P: Pixel> Transform for ImageFrame<P> {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
self.transform self.transform
@ -45,6 +48,54 @@ impl<P: Pixel> TransformMut for ImageFrame<P> {
&mut self.transform &mut self.transform
} }
} }
// Implementations for ImageFrameTable<P>
impl<P: Pixel> Transform for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for TextureTable
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for GraphicGroup
impl Transform for GraphicGroup { impl Transform for GraphicGroup {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
self.transform self.transform
@ -55,19 +106,35 @@ impl TransformMut for GraphicGroup {
&mut self.transform &mut self.transform
} }
} }
// Implementations for GraphicGroupTable
impl Transform for GraphicGroupTable {
fn transform(&self) -> DAffine2 {
let graphic_group = self.one_item();
graphic_group.transform
}
}
impl TransformMut for GraphicGroupTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let graphic_group = self.one_item_mut();
&mut graphic_group.transform
}
}
// Implementations for GraphicElement
impl Transform for GraphicElement { impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
match self { match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform(), GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElement::Raster(raster) => raster.transform(), GraphicElement::RasterFrame(raster) => raster.transform(),
} }
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 { fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self { match self {
GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot), GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot), GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElement::Raster(raster) => raster.local_pivot(pivot), GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot),
} }
} }
} }
@ -76,11 +143,12 @@ impl TransformMut for GraphicElement {
match self { match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(), GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElement::Raster(raster) => raster.transform_mut(), GraphicElement::RasterFrame(raster) => raster.transform_mut(),
} }
} }
} }
// Implementations for VectorData
impl Transform for VectorData { impl Transform for VectorData {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
self.transform self.transform
@ -95,6 +163,25 @@ impl TransformMut for VectorData {
} }
} }
// Implementations for VectorDataTable
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
let vector_data = self.one_item();
vector_data.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let vector_data = self.one_item();
vector_data.local_pivot(pivot)
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let vector_data = self.one_item_mut();
&mut vector_data.transform
}
}
// Implementations for Artboard
impl Transform for Artboard { impl Transform for Artboard {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
DAffine2::from_translation(self.location.as_dvec2()) DAffine2::from_translation(self.location.as_dvec2())
@ -104,6 +191,7 @@ impl Transform for Artboard {
} }
} }
// Implementations for DAffine2
impl Transform for DAffine2 { impl Transform for DAffine2 {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
*self *self
@ -115,6 +203,18 @@ impl TransformMut for DAffine2 {
} }
} }
// Implementations for Footprint
impl Transform for Footprint {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for Footprint {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)] #[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RenderQuality { pub enum RenderQuality {
@ -177,7 +277,7 @@ impl From<()> for Footprint {
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<Color>, ArtboardGroup)] data: T) -> T { fn cull<T>(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
data data
} }
@ -188,17 +288,6 @@ impl core::hash::Hash for Footprint {
} }
} }
impl Transform for Footprint {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for Footprint {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
pub trait ApplyTransform { pub trait ApplyTransform {
fn apply_transform(&mut self, modification: &DAffine2); fn apply_transform(&mut self, modification: &DAffine2);
} }
@ -222,13 +311,13 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
)] )]
mut input: I, mut input: I,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> TextureFrame, () -> TextureFrame,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame, Footprint -> TextureFrame,
)] )]
transform_target: impl Node<I, Output = T>, transform_target: impl Node<I, Output = T>,
@ -255,7 +344,7 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn replace_transform<Data: TransformMut, TransformInput: Transform>( fn replace_transform<Data: TransformMut, TransformInput: Transform>(
_: (), _: (),
#[implementations(VectorData, ImageFrame<Color>, GraphicGroup)] mut data: Data, #[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
#[implementations(DAffine2)] transform: TransformInput, #[implementations(DAffine2)] transform: TransformInput,
) -> Data { ) -> Data {
let data_transform = data.transform_mut(); let data_transform = data.transform_mut();

View file

@ -131,12 +131,15 @@ impl From<String> for ProtoNodeIdentifier {
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> { fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
// Rename "f32" to "f64"
let name = String::deserialize(deserializer)?; let name = String::deserialize(deserializer)?;
let name = match name.as_str() { let name = match name.as_str() {
"f32" => "f64".to_string(), "f32" => "f64".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>" => "graphene_core::graphic_element::Instances<graphene_core::raster::image::ImageFrame<Color>>".to_string(),
_ => name, _ => name,
}; };
Ok(Cow::Owned(name)) Ok(Cow::Owned(name))
} }
@ -150,9 +153,9 @@ pub struct TypeDescriptor {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
#[serde(default)] #[serde(default)]
pub alias: Option<Cow<'static, str>>, pub alias: Option<Cow<'static, str>>,
#[serde(default)] #[serde(skip)]
pub size: usize, pub size: usize,
#[serde(default)] #[serde(skip)]
pub align: usize, pub align: usize,
} }

View file

@ -1,21 +1,20 @@
use super::HandleId;
use crate::transform::Footprint; use crate::transform::Footprint;
use crate::vector::{PointId, VectorData}; use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::DVec2; use glam::DVec2;
trait CornerRadius { trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData; fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
} }
impl CornerRadius for f64 { impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData { fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self }; let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])) VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
} }
} }
impl CornerRadius for [f64; 4] { impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData { fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped { let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap> // Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@ -31,28 +30,31 @@ impl CornerRadius for [f64; 4] {
} else { } else {
self self
}; };
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)) VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
} }
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData { fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))) VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData { fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
let radius = DVec2::new(radius_x, radius_y); let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius; let corner1 = -radius;
let corner2 = radius; let corner2 = radius;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let mut ellipse = VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len(); let len = ellipse.segment_domain.ids().len();
for i in 0..len { for i in 0..len {
ellipse ellipse
.colinear_manipulators .colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]); .push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
} }
ellipse
VectorDataTable::new(ellipse)
} }
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] #[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
@ -64,7 +66,7 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T, #[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool, #[default(true)] clamped: bool,
) -> VectorData { ) -> VectorDataTable {
corner_radius.generate(DVec2::new(width, height), clamped) corner_radius.generate(DVec2::new(width, height), clamped)
} }
@ -76,10 +78,10 @@ fn regular_polygon<F: 'n + Send>(
#[min(3.)] #[min(3.)]
sides: u32, sides: u32,
#[default(50)] radius: f64, #[default(50)] radius: f64,
) -> VectorData { ) -> VectorDataTable {
let points = sides.into(); let points = sides.into();
let radius: f64 = radius * 2.; let radius: f64 = radius * 2.;
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)) VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
@ -91,35 +93,39 @@ fn star<F: 'n + Send>(
sides: u32, sides: u32,
#[default(50)] radius: f64, #[default(50)] radius: f64,
#[default(25)] inner_radius: f64, #[default(25)] inner_radius: f64,
) -> VectorData { ) -> VectorDataTable {
let points = sides.into(); let points = sides.into();
let diameter: f64 = radius * 2.; let diameter: f64 = radius * 2.;
let inner_diameter = inner_radius * 2.; let inner_diameter = inner_radius * 2.;
super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)) VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData { fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
super::VectorData::from_subpath(Subpath::new_line(start, end)) VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorData { fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorDataTable {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points)); let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points));
for pair in spline.segment_domain.ids().windows(2) { for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]); spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
} }
spline
VectorDataTable::new(spline)
} }
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node // TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData { fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
let mut vector_data = super::VectorData::from_subpaths(path_data, false); let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators vector_data.colinear_manipulators = colinear_manipulators
.iter() .iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data)) .filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect(); .collect();
vector_data
VectorDataTable::new(vector_data)
} }

View file

@ -4,7 +4,8 @@ pub use attributes::*;
pub use modification::*; pub use modification::*;
use super::style::{PathStyle, Stroke}; use super::style::{PathStyle, Stroke};
use crate::{AlphaBlending, Color}; use crate::instances::Instances;
use crate::{AlphaBlending, Color, GraphicGroupTable};
use bezier_rs::ManipulatorGroup; use bezier_rs::ManipulatorGroup;
use dyn_any::DynAny; use dyn_any::DynAny;
@ -12,6 +13,26 @@ use dyn_any::DynAny;
use core::borrow::Borrow; use core::borrow::Borrow;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
VectorData(VectorData),
VectorDataTable(VectorDataTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
pub type VectorDataTable = Instances<VectorData>;
/// [VectorData] is passed between nodes. /// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information. /// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
#[derive(Clone, Debug, PartialEq, DynAny)] #[derive(Clone, Debug, PartialEq, DynAny)]
@ -29,7 +50,7 @@ pub struct VectorData {
pub region_domain: RegionDomain, pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<crate::GraphicGroup>, pub upstream_graphic_group: Option<GraphicGroupTable>,
} }
impl core::hash::Hash for VectorData { impl core::hash::Hash for VectorData {

View file

@ -1,4 +1,5 @@
use super::HandleId; use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable};
use crate::vector::ConcatElement;
use dyn_any::DynAny; use dyn_any::DynAny;
@ -46,7 +47,7 @@ macro_rules! create_ids {
}; };
} }
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId } create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId }
/// A no-op hasher that allows writing u64s (the id type). /// A no-op hasher that allows writing u64s (the id type).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@ -503,7 +504,7 @@ impl RegionDomain {
} }
} }
impl super::VectorData { impl VectorData {
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. /// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: bezier_rs::BezierHandles) -> bezier_rs::Bezier { fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: bezier_rs::BezierHandles) -> bezier_rs::Bezier {
let start = self.point_domain.positions()[start]; let start = self.point_domain.positions()[start];
@ -698,7 +699,7 @@ impl StrokePathIterPointMetadata {
#[derive(Clone)] #[derive(Clone)]
pub struct StrokePathIter<'a> { pub struct StrokePathIter<'a> {
vector_data: &'a super::VectorData, vector_data: &'a VectorData,
points: Vec<StrokePathIterPointMetadata>, points: Vec<StrokePathIterPointMetadata>,
skip: usize, skip: usize,
done_one: bool, done_one: bool,
@ -774,7 +775,7 @@ impl bezier_rs::Identifier for PointId {
} }
} }
impl crate::vector::ConcatElement for super::VectorData { impl ConcatElement for VectorData {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
let new_ids = other let new_ids = other
.point_domain .point_domain
@ -813,6 +814,14 @@ impl crate::vector::ConcatElement for super::VectorData {
} }
} }
impl ConcatElement for VectorDataTable {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
for (instance, other_instance) in self.instances_mut().zip(other.instances()) {
instance.concat(other_instance, transform, node_id);
}
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids. /// Represents the conversion of ids used when concatenating vector data with conflicting ids.
struct IdMap { struct IdMap {
point_offset: usize, point_offset: usize,

View file

@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::transform::Footprint;
use crate::uuid::generate_uuid; use crate::uuid::generate_uuid;
use bezier_rs::BezierHandles; use bezier_rs::BezierHandles;
@ -421,7 +422,6 @@ impl core::hash::Hash for VectorModification {
} }
} }
use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`]. /// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify<F: 'n + Send + Sync + Clone>( async fn path_modify<F: 'n + Send + Sync + Clone>(
@ -431,15 +431,18 @@ async fn path_modify<F: 'n + Send + Sync + Clone>(
)] )]
input: F, input: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
modification: Box<VectorModification>, modification: Box<VectorModification>,
) -> VectorData { ) -> VectorDataTable {
let mut vector_data = vector_data.eval(input).await; let mut vector_data = vector_data.eval(input).await;
modification.apply(&mut vector_data); let vector_data = vector_data.one_item_mut();
vector_data
modification.apply(vector_data);
VectorDataTable::new(vector_data.clone())
} }
#[test] #[test]

View file

@ -1,12 +1,12 @@
use super::misc::CentroidType; use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke}; use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData}; use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue}; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered; use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::LineJoin; use crate::vector::style::LineJoin;
use crate::vector::PointDomain; use crate::vector::PointDomain;
use crate::{Color, GraphicElement, GraphicGroup}; use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@ -18,21 +18,27 @@ trait VectorIterMut {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)>; fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)>;
} }
impl VectorIterMut for GraphicGroup { impl VectorIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> { fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let parent_transform = self.transform; let instance = self.one_item_mut();
// Grab only the direct children (perhaps unintuitive?)
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector| { let parent_transform = instance.transform;
let transform = parent_transform * vector.transform;
(vector, transform) // Grab only the direct children
instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| {
let vector_data = vector_data.one_item_mut();
let transform = parent_transform * vector_data.transform;
(vector_data, transform)
}) })
} }
} }
impl VectorIterMut for VectorData { impl VectorIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> { fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let transform = self.transform; self.instances_mut().map(|instance| {
std::iter::once((self, transform)) let transform = instance.transform;
(instance, transform)
})
} }
} }
@ -45,10 +51,10 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
() -> VectorData, () -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
#[widget(ParsedWidgetOverride::Hidden)] #[widget(ParsedWidgetOverride::Hidden)]
vector_group: impl Node<F, Output = T>, vector_group: impl Node<F, Output = T>,
@ -60,13 +66,14 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue, #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32, #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
) -> T { ) -> T {
let mut input = vector_group.eval(footprint).await; let mut vector_group = vector_group.eval(footprint).await;
let length = input.vector_iter_mut().count();
let length = vector_group.vector_iter_mut().count();
let gradient = if reverse { gradient.reversed() } else { gradient }; let gradient = if reverse { gradient.reversed() } else { gradient };
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
for (i, (vector_data, _)) in input.vector_iter_mut().enumerate() { for (i, (vector_data, _)) in vector_group.vector_iter_mut().enumerate() {
let factor = match randomize { let factor = match randomize {
true => rng.gen::<f64>(), true => rng.gen::<f64>(),
false => match repeat_every { false => match repeat_every {
@ -87,7 +94,8 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
} }
} }
} }
input
vector_group
} }
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
@ -112,22 +120,22 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> VectorData, () -> VectorDataTable,
() -> VectorData, () -> VectorDataTable,
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
() -> GraphicGroup, () -> GraphicGroupTable,
() -> GraphicGroup, () -> GraphicGroupTable,
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
vector_data: impl Node<F, Output = TargetTy>, vector_data: impl Node<F, Output = TargetTy>,
#[implementations( #[implementations(
@ -176,14 +184,14 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
vector_data: impl Node<F, Output = TargetTy>, vector_data: impl Node<F, Output = TargetTy>,
#[implementations( #[implementations(
@ -234,10 +242,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
footprint: F, footprint: F,
// TODO: Implement other GraphicElementRendered types. // TODO: Implement other GraphicElementRendered types.
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
instance: impl Node<F, Output = I>, instance: impl Node<F, Output = I>,
#[default(100., 100.)] #[default(100., 100.)]
@ -245,7 +253,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
direction: DVec2, direction: DVec2,
angle: Angle, angle: Angle,
#[default(4)] instances: IntegerCount, #[default(4)] instances: IntegerCount,
) -> GraphicGroup { ) -> GraphicGroupTable {
let instance = instance.eval(footprint).await; let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform(); let first_vector_transform = instance.transform();
@ -253,10 +261,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
let instances = instances.max(1); let instances = instances.max(1);
let total = (instances - 1) as f64; let total = (instances - 1) as f64;
let mut result = GraphicGroup::EMPTY; let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result; return GraphicGroupTable::new(result);
}; };
let center = (bounding_box[0] + bounding_box[1]) / 2.; let center = (bounding_box[0] + bounding_box[1]) / 2.;
@ -273,7 +281,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
result.push((new_instance, None)); result.push((new_instance, None));
} }
result GraphicGroupTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -287,24 +295,24 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
footprint: F, footprint: F,
// TODO: Implement other GraphicElementRendered types. // TODO: Implement other GraphicElementRendered types.
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
instance: impl Node<F, Output = I>, instance: impl Node<F, Output = I>,
angle_offset: Angle, angle_offset: Angle,
#[default(5)] radius: f64, #[default(5)] radius: f64,
#[default(5)] instances: IntegerCount, #[default(5)] instances: IntegerCount,
) -> GraphicGroup { ) -> GraphicGroupTable {
let instance = instance.eval(footprint).await; let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform(); let first_vector_transform = instance.transform();
let instances = instances.max(1); let instances = instances.max(1);
let mut result = GraphicGroup::EMPTY; let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result; return GraphicGroupTable::new(result);
}; };
let center = (bounding_box[0] + bounding_box[1]) / 2.; let center = (bounding_box[0] + bounding_box[1]) / 2.;
@ -322,7 +330,7 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
result.push((new_instance, None)); result.push((new_instance, None));
} }
result GraphicGroupTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -334,17 +342,17 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
points: impl Node<F, Output = VectorData>, points: impl Node<F, Output = VectorDataTable>,
#[expose] #[expose]
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
instance: impl Node<F, Output = I>, instance: impl Node<F, Output = I>,
#[default(1)] random_scale_min: f64, #[default(1)] random_scale_min: f64,
@ -353,9 +361,12 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
random_scale_seed: SeedValue, random_scale_seed: SeedValue,
random_rotation: Angle, random_rotation: Angle,
random_rotation_seed: SeedValue, random_rotation_seed: SeedValue,
) -> GraphicGroup { ) -> GraphicGroupTable {
let points = points.eval(footprint).await; let points = points.eval(footprint).await;
let points = points.one_item();
let instance = instance.eval(footprint).await; let instance = instance.eval(footprint).await;
let instance_transform = instance.transform(); let instance_transform = instance.transform();
let random_scale_difference = random_scale_max - random_scale_min; let random_scale_difference = random_scale_max - random_scale_min;
@ -406,7 +417,7 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
result.push((new_instance, None)); result.push((new_instance, None));
} }
result GraphicGroupTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -417,18 +428,20 @@ async fn bounding_box<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorData { ) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])); let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]));
result.style = vector_data.style.clone(); result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] #[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
@ -439,23 +452,23 @@ async fn offset_path<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
distance: f64, distance: f64,
line_join: LineJoin, line_join: LineJoin,
#[default(4.)] miter_limit: f64, #[default(4.)] miter_limit: f64,
) -> VectorData { ) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty(); let mut result = VectorData::empty();
result.style = vector_data.style.clone(); result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
// Perform operation on all subpaths in this shape. // Perform operation on all subpaths in this shape.
for mut subpath in subpaths { for mut subpath in vector_data.stroke_bezier_paths() {
subpath.apply_transform(vector_data.transform); subpath.apply_transform(vector_data.transform);
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths. // Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
@ -472,7 +485,7 @@ async fn offset_path<F: 'n + Send>(
result.append_subpath(subpath_out, false); result.append_subpath(subpath_out, false);
} }
result VectorDataTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -483,14 +496,17 @@ async fn solidify_stroke<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorData { ) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let transform = &vector_data.transform;
let style = &vector_data.style;
let VectorData { transform, style, .. } = &vector_data;
let subpaths = vector_data.stroke_bezier_paths(); let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty(); let mut result = VectorData::empty();
@ -531,7 +547,7 @@ async fn solidify_stroke<F: 'n + Send>(
result.style.set_stroke(Stroke::default()); result.style.set_stroke(Stroke::default());
} }
result VectorDataTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -542,12 +558,14 @@ async fn flatten_vector_elements<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
graphic_group_input: impl Node<F, Output = GraphicGroup>, graphic_group_input: impl Node<F, Output = GraphicGroupTable>,
) -> VectorData { ) -> VectorDataTable {
let graphic_group = graphic_group_input.eval(footprint).await; let graphic_group = graphic_group_input.eval(footprint).await;
let graphic_group = graphic_group.one_item();
// A node based solution to support passing through vector data could be a network node with a cache node connected to // A node based solution to support passing through vector data could be a network node with a cache node connected to
// a flatten vector elements connected to an if else node, another connection from the cache directly // 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. // To the if else node, and another connection from the cache to a matches type node connected to the if else node.
@ -555,9 +573,12 @@ async fn flatten_vector_elements<F: 'n + Send>(
for (element, reference) in graphic_group.iter() { for (element, reference) in graphic_group.iter() {
match element { match element {
GraphicElement::VectorData(vector_data) => { GraphicElement::VectorData(vector_data) => {
result.concat(vector_data, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default()); for instance in vector_data.instances() {
result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
}
} }
GraphicElement::GraphicGroup(graphic_group) => { GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item();
concat_group(graphic_group, current_transform * graphic_group.transform, result); concat_group(graphic_group, current_transform * graphic_group.transform, result);
} }
_ => {} _ => {}
@ -566,25 +587,29 @@ async fn flatten_vector_elements<F: 'n + Send>(
} }
let mut result = VectorData::empty(); let mut result = VectorData::empty();
concat_group(&graphic_group, DAffine2::IDENTITY, &mut result); concat_group(graphic_group, DAffine2::IDENTITY, &mut result);
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms. // TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
result VectorDataTable::new(result)
} }
pub trait ConcatElement { pub trait ConcatElement {
fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64); fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64);
} }
impl ConcatElement for GraphicGroup { impl ConcatElement for GraphicGroupTable {
fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) { fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) {
let own = self.one_item_mut();
let other = other.one_item();
// TODO: Decide if we want to keep this behavior whereby the layers are flattened // TODO: Decide if we want to keep this behavior whereby the layers are flattened
for (mut element, footprint_mapping) in other.iter().cloned() { for (mut element, footprint_mapping) in other.iter().cloned() {
*element.transform_mut() = transform * element.transform() * other.transform(); *element.transform_mut() = transform * element.transform() * other.transform();
self.push((element, footprint_mapping)); own.push((element, footprint_mapping));
} }
self.alpha_blending = other.alpha_blending;
own.alpha_blending = other.alpha_blending;
} }
} }
@ -596,10 +621,10 @@ async fn sample_points<F: 'n + Send + Copy>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
spacing: f64, spacing: f64,
start_offset: f64, start_offset: f64,
stop_offset: f64, stop_offset: f64,
@ -609,9 +634,10 @@ async fn sample_points<F: 'n + Send + Copy>(
Footprint -> Vec<f64>, Footprint -> Vec<f64>,
)] )]
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>, subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
) -> VectorData { ) -> VectorDataTable {
// Evaluate vector data and subpath segment lengths asynchronously. // Evaluate vector data and subpath segment lengths asynchronously.
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await; let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
// Create an iterator over the bezier segments with enumeration and peeking capability. // Create an iterator over the bezier segments with enumeration and peeking capability.
@ -753,7 +779,7 @@ async fn sample_points<F: 'n + Send + Copy>(
result.style.set_stroke_transform(vector_data.transform); result.style.set_stroke_transform(vector_data.transform);
// Return the resulting vector data with newly generated points and segments. // Return the resulting vector data with newly generated points and segments.
result VectorDataTable::new(result)
} }
#[node_macro::node(category(""), path(graphene_core::vector))] #[node_macro::node(category(""), path(graphene_core::vector))]
@ -764,22 +790,23 @@ async fn poisson_disk_points<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
#[default(10.)] #[default(10.)]
#[min(0.01)] #[min(0.01)]
separation_disk_diameter: f64, separation_disk_diameter: f64,
seed: SeedValue, seed: SeedValue,
) -> VectorData { ) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty(); let mut result = VectorData::empty();
if separation_disk_diameter <= 0.01 { if separation_disk_diameter <= 0.01 {
return result; return VectorDataTable::new(result);
} }
for mut subpath in vector_data.stroke_bezier_paths() { for mut subpath in vector_data.stroke_bezier_paths() {
@ -812,7 +839,7 @@ async fn poisson_disk_points<F: 'n + Send>(
result.style = vector_data.style.clone(); result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
result VectorDataTable::new(result)
} }
#[node_macro::node(category(""), path(graphene_core::vector))] #[node_macro::node(category(""), path(graphene_core::vector))]
@ -823,12 +850,13 @@ async fn subpath_segment_lengths<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
) -> Vec<f64> { ) -> Vec<f64> {
let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
vector_data vector_data
.segment_bezier_iter() .segment_bezier_iter()
@ -844,17 +872,18 @@ async fn splines_from_points<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorData { ) -> VectorDataTable {
// Evaluate the vector data within the given footprint. // Evaluate the vector data within the given footprint.
let mut vector_data = vector_data.eval(footprint).await; let mut vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item_mut();
// Exit early if there are no points to generate splines from. // Exit early if there are no points to generate splines from.
if vector_data.point_domain.positions().is_empty() { if vector_data.point_domain.positions().is_empty() {
return vector_data; return VectorDataTable::new(vector_data.clone());
} }
let mut segment_domain = SegmentDomain::default(); let mut segment_domain = SegmentDomain::default();
@ -887,7 +916,7 @@ async fn splines_from_points<F: 'n + Send>(
} }
vector_data.segment_domain = segment_domain; vector_data.segment_domain = segment_domain;
vector_data VectorDataTable::new(vector_data.clone())
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -898,14 +927,15 @@ async fn jitter_points<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
vector_data: impl Node<F, Output = VectorData>, vector_data: impl Node<F, Output = VectorDataTable>,
#[default(5.)] amount: f64, #[default(5.)] amount: f64,
seed: SeedValue, seed: SeedValue,
) -> VectorData { ) -> VectorDataTable {
let mut vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.eval(footprint).await;
let mut vector_data = vector_data.one_item().clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
@ -949,7 +979,7 @@ async fn jitter_points<F: 'n + Send>(
vector_data.transform = DAffine2::IDENTITY; vector_data.transform = DAffine2::IDENTITY;
vector_data.style.set_stroke_transform(DAffine2::IDENTITY); vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
vector_data VectorDataTable::new(vector_data)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -960,23 +990,26 @@ async fn morph<F: 'n + Send + Copy>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
source: impl Node<F, Output = VectorData>, source: impl Node<F, Output = VectorDataTable>,
#[expose] #[expose]
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
target: impl Node<F, Output = VectorData>, target: impl Node<F, Output = VectorDataTable>,
#[range((0., 1.))] #[range((0., 1.))]
#[default(0.5)] #[default(0.5)]
time: Fraction, time: Fraction,
#[min(0.)] start_index: IntegerCount, #[min(0.)] start_index: IntegerCount,
) -> VectorData { ) -> VectorDataTable {
let source = source.eval(footprint).await; let source = source.eval(footprint).await;
let source = source.one_item();
let target = target.eval(footprint).await; let target = target.eval(footprint).await;
let target = target.one_item();
let mut result = VectorData::empty(); let mut result = VectorData::empty();
let time = time.clamp(0., 1.); let time = time.clamp(0., 1.);
@ -1055,7 +1088,7 @@ async fn morph<F: 'n + Send + Copy>(
} }
} }
result VectorDataTable::new(result)
} }
fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
@ -1173,18 +1206,24 @@ async fn bevel<F: 'n + Send + Copy>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> VectorData, () -> VectorDataTable,
Footprint -> VectorData, Footprint -> VectorDataTable,
)] )]
source: impl Node<F, Output = VectorData>, source: impl Node<F, Output = VectorDataTable>,
#[default(10.)] distance: Length, #[default(10.)] distance: Length,
) -> VectorData { ) -> VectorDataTable {
bevel_algorithm(source.eval(footprint).await, distance) let source = source.eval(footprint).await;
let source = source.one_item();
let result = bevel_algorithm(source.clone(), distance);
VectorDataTable::new(result)
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 { async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
let vector_data = vector_data.eval(Footprint::default()).await; let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
let mut area = 0.; let mut area = 0.;
let scale = vector_data.transform.decompose_scale(); let scale = vector_data.transform.decompose_scale();
@ -1195,8 +1234,9 @@ async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) ->
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 { async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
let vector_data = vector_data.eval(Footprint::default()).await; let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
if centroid_type == CentroidType::Area { if centroid_type == CentroidType::Area {
let mut area = 0.; let mut area = 0.;
@ -1260,8 +1300,8 @@ mod test {
} }
} }
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> { fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
FutureWrapperNode(VectorData::from_subpath(data)) FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
} }
#[tokio::test] #[tokio::test]
@ -1270,6 +1310,7 @@ mod test {
let instances = 3; let instances = 3;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; 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(), &FutureWrapperNode(repeated)).await; let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 3); assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { 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); assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1281,6 +1322,7 @@ mod test {
let instances = 8; let instances = 8;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; 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(), &FutureWrapperNode(repeated)).await; let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8); assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { 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); assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1290,6 +1332,7 @@ mod test {
async fn circle_repeat() { async fn circle_repeat() {
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; 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(), &FutureWrapperNode(repeated)).await; let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8); assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.; let expected_angle = (index as f64 + 1.) * 45.;
@ -1304,18 +1347,20 @@ mod test {
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
}; };
let bounding_box = bounding_box.eval(Footprint::default()).await; let bounding_box = bounding_box.eval(Footprint::default()).await;
let bounding_box = bounding_box.one_item();
assert_eq!(bounding_box.region_bezier_paths().count(), 1); assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().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.),]); assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
// test a VectorData with non-zero rotation // Test a VectorData with non-zero rotation
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)); let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode { let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(square), vector_data: FutureWrapperNode(VectorDataTable::new(square)),
} }
.eval(Footprint::default()) .eval(Footprint::default())
.await; .await;
let bounding_box = bounding_box.one_item();
assert_eq!(bounding_box.region_bezier_paths().count(), 1); assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1; let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let sqrt2 = core::f64::consts::SQRT_2; let sqrt2 = core::f64::consts::SQRT_2;
@ -1329,6 +1374,7 @@ mod test {
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await; let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await; let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
let flattened_copy_to_points = flattened_copy_to_points.one_item();
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
let offset = expected_points[index]; let offset = expected_points[index];
@ -1342,6 +1388,7 @@ mod test {
async fn sample_points() { async fn sample_points() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); 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, &FutureWrapperNode(vec![100.])).await; let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
let sample_points = sample_points.one_item();
assert_eq!(sample_points.point_domain.positions().len(), 4); 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.]) { 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}"); assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1351,6 +1398,7 @@ mod test {
async fn adaptive_spacing() { async fn adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); 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, &FutureWrapperNode(vec![100.])).await; let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
let sample_points = sample_points.one_item();
assert_eq!(sample_points.point_domain.positions().len(), 4); 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.]) { 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}"); assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1365,6 +1413,7 @@ mod test {
0, 0,
) )
.await; .await;
let sample_points = sample_points.one_item();
assert!( assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()), (20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}", "actual len {}",
@ -1383,6 +1432,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn spline() { async fn spline() {
let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.one_item();
assert_eq!(spline.stroke_bezier_paths().count(), 1); 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.)]); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
} }
@ -1391,6 +1441,7 @@ mod test {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO); 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 = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
let sample_points = sample_points.one_item();
assert_eq!( assert_eq!(
&sample_points.point_domain.positions()[..4], &sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)] vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
@ -1408,6 +1459,8 @@ mod test {
async fn bevel_rect() { async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8);
@ -1429,6 +1482,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); 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 source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3); assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -1449,7 +1503,8 @@ mod test {
let mut vector_data = VectorData::from_subpath(source); let mut vector_data = VectorData::from_subpath(source);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
vector_data.transform = transform; vector_data.transform = transform;
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(vector_data), 5.).await; let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3); assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -1468,6 +1523,8 @@ mod test {
async fn bevel_too_high() { 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 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.).await; let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5); assert_eq!(beveled.segment_domain.ids().len(), 5);
@ -1487,6 +1544,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO); 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 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.).await; let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5); assert_eq!(beveled.segment_domain.ids().len(), 5);

View file

@ -129,7 +129,8 @@ tagged_value! {
DAffine2(DAffine2), DAffine2(DAffine2),
Image(graphene_core::raster::Image<Color>), Image(graphene_core::raster::Image<Color>),
ImaginateCache(ImaginateCache), ImaginateCache(ImaginateCache),
ImageFrame(graphene_core::raster::ImageFrame<Color>), #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>),
Color(graphene_core::raster::color::Color), Color(graphene_core::raster::color::Color),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>), Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
BlendMode(BlendMode), BlendMode(BlendMode),
@ -137,7 +138,8 @@ tagged_value! {
ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateSamplingMethod(ImaginateSamplingMethod),
ImaginateMaskStartingFill(ImaginateMaskStartingFill), ImaginateMaskStartingFill(ImaginateMaskStartingFill),
ImaginateController(ImaginateController), ImaginateController(ImaginateController),
VectorData(graphene_core::vector::VectorData), #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code
VectorData(graphene_core::vector::VectorDataTable),
Fill(graphene_core::vector::style::Fill), Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke), Stroke(graphene_core::vector::style::Stroke),
F64Array4([f64; 4]), F64Array4([f64; 4]),
@ -169,9 +171,9 @@ tagged_value! {
Font(graphene_core::text::Font), Font(graphene_core::text::Font),
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>), BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
BrushCache(BrushCache), BrushCache(BrushCache),
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
DocumentNode(DocumentNode), DocumentNode(DocumentNode),
GraphicGroup(graphene_core::GraphicGroup), #[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable),
GraphicElement(graphene_core::GraphicElement), GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup), ArtboardGroup(graphene_core::ArtboardGroup),
Curve(graphene_core::raster::curve::Curve), Curve(graphene_core::raster::curve::Curve),

View file

@ -3,18 +3,21 @@ use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, Ex
use graphene_core::raster::adjustments::blend_colors; use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, ImageFrame, Pixel, Sample}; use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample};
use graphene_core::transform::{Footprint, Transform, TransformMut}; use graphene_core::transform::{Footprint, Transform, TransformMut};
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::vector::VectorData; use graphene_core::vector::VectorDataTable;
use graphene_core::Node; use graphene_core::Node;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn vector_points(_: (), vector_data: VectorData) -> Vec<DVec2> { fn vector_points(_: (), vector_data: VectorDataTable) -> Vec<DVec2> {
let vector_data = vector_data.one_item();
vector_data.point_domain.positions().to_vec() vector_data.point_domain.positions().to_vec()
} }
@ -199,7 +202,9 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrame<Color> { fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
let image = image.one_item().clone();
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox(); let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox();
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
@ -211,8 +216,8 @@ fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Col
let mut background_bounds = bbox.to_transform(); let mut background_bounds = bbox.to_transform();
if bounds.transform != DAffine2::ZERO { if bounds.transform() != DAffine2::ZERO {
background_bounds = bounds.transform; background_bounds = bounds.transform();
} }
let mut actual_image = ExtendImageToBoundsNode::new(ClonedNode::new(background_bounds)).eval(brush_plan.background); let mut actual_image = ExtendImageToBoundsNode::new(ClonedNode::new(background_bounds)).eval(brush_plan.background);
@ -236,8 +241,7 @@ fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Col
bbox.start = bbox.start.floor(); bbox.start = bbox.start.floor();
bbox.end = bbox.end.floor(); bbox.end = bbox.end.floor();
let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter); let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter);
// For numerical stability we want to place the first blit point at a stable, integer offset // For numerical stability we want to place the first blit point at a stable, integer offset in layer space.
// in layer space.
let snap_offset = positions[0].floor() - positions[0]; let snap_offset = positions[0].floor() - positions[0];
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.); let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size); let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
@ -250,6 +254,7 @@ fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Col
} else { } else {
EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(()) EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
}; };
blit_node.eval(blit_target) blit_node.eval(blit_target)
}; };
@ -301,7 +306,8 @@ fn brush(_footprint: Footprint, image: ImageFrame<Color>, bounds: ImageFrame<Col
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params)); let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, erase_restore_mask)); actual_image = blend_executor.eval((actual_image, erase_restore_mask));
} }
actual_image
ImageFrameTable::new(actual_image)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,5 +1,6 @@
use graph_craft::proto::types::Percentage; use graph_craft::proto::types::Percentage;
use graphene_core::raster::{Image, ImageFrame}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image;
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::Color; use graphene_core::Color;
@ -15,18 +16,19 @@ async fn dehaze<F: 'n + Send + Sync>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
)] )]
image_frame: impl Node<F, Output = ImageFrame<Color>>, image_frame: impl Node<F, Output = ImageFrameTable<Color>>,
strength: Percentage, strength: Percentage,
) -> ImageFrame<Color> { ) -> ImageFrameTable<Color> {
let image_frame = image_frame.eval(footprint).await; let image_frame = image_frame.eval(footprint).await;
let image_frame = image_frame.one_item();
// Prepare the image data for processing // Prepare the image data for processing
let image = image_frame.image; let image = &image_frame.image;
let image_data = bytemuck::cast_vec(image.data); let image_data = bytemuck::cast_vec(image.data.clone());
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal ImageFrame into image-rs data type."); let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
let dynamic_image: image::DynamicImage = image_buffer.into(); let dynamic_image: image::DynamicImage = image_buffer.into();
// Run the dehaze algorithm // Run the dehaze algorithm
@ -42,11 +44,13 @@ async fn dehaze<F: 'n + Send + Sync>(
base64_string: None, base64_string: None,
}; };
ImageFrame { let result = ImageFrame {
image: dehazed_image, image: dehazed_image,
transform: image_frame.transform, transform: image_frame.transform,
alpha_blending: image_frame.alpha_blending, alpha_blending: image_frame.alpha_blending,
} };
ImageFrameTable::new(result)
} }
// There is no real point in modifying these values because they do not change the final result all that much. // There is no real point in modifying these values because they do not change the final result all that much.

View file

@ -4,7 +4,8 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::proto::*; use graph_craft::proto::*;
use graphene_core::application_io::ApplicationIo; use graphene_core::application_io::ApplicationIo;
use graphene_core::raster::*; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::{BlendMode, Image, Pixel};
use graphene_core::*; use graphene_core::*;
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput}; use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
@ -61,7 +62,10 @@ impl Clone for ComputePass {
} }
#[node_macro::old_node_impl(MapGpuNode)] #[node_macro::old_node_impl(MapGpuNode)]
async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrame<Color> { 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_item();
log::debug!("Executing gpu node"); log::debug!("Executing gpu node");
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap(); let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
@ -75,9 +79,9 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone() self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone()
} else { } else {
let name = "placeholder".to_string(); let name = "placeholder".to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor).await else { let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()"); log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrame::empty(); return ImageFrameTable::default();
}; };
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
log::error!("created compute pass"); log::error!("created compute pass");
@ -105,7 +109,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
#[cfg(feature = "image-compare")] #[cfg(feature = "image-compare")]
log::debug!("score: {:?}", score.score); log::debug!("score: {:?}", score.score);
ImageFrame { let result = ImageFrame {
image: Image { image: Image {
data: colors, data: colors,
width: image.image.width, width: image.image.width,
@ -114,7 +118,9 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
}, },
transform: image.transform, transform: image.transform,
alpha_blending: image.alpha_blending, alpha_blending: image.alpha_blending,
} };
ImageFrameTable::new(result)
} }
impl<Node, EditorApi> MapGpuNode<Node, EditorApi> { impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
@ -127,7 +133,13 @@ impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
} }
} }
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrame<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String> { async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrameTable<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String>
where
GraphicElement: From<ImageFrame<T>>,
T::Static: Pixel,
{
let image = image.one_item();
let compiler = graph_craft::graphene_compiler::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node); let inner_network = NodeNetwork::value_network(node);
@ -145,40 +157,37 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default() ..Default::default()
}, },
/*DocumentNode { // DocumentNode {
name: "Index".into(), // name: "Index".into(),
// inputs: vec![NodeInput::Network(concrete!(UVec3))], // // inputs: vec![NodeInput::Network(concrete!(UVec3))],
inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))], // inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
..Default::default() // ..Default::default()
},*/ // },
/* // DocumentNode {
DocumentNode { // name: "Get Node".into(),
name: "Get Node".into(), // inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)],
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)], // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()), // ..Default::default()
..Default::default() // },
},*/
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Network(inner_network), implementation: DocumentNodeImplementation::Network(inner_network),
..Default::default() ..Default::default()
}, },
/* // DocumentNode {
DocumentNode { // name: "Save Node".into(),
name: "Save Node".into(), // inputs: vec![
inputs: vec![ // NodeInput::node(NodeId(5), 0),
NodeInput::node(NodeId(5), 0), // NodeInput::Inline(InlineRust::new(
NodeInput::Inline(InlineRust::new( // "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(),
"|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(), // // "|x|()".into(),
// "|x|()".into(), // Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))),
Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))), // )),
)), // ],
], // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()), // ..Default::default()
..Default::default() // },
},
*/
] ]
.into_iter() .into_iter()
.enumerate() .enumerate()
@ -204,7 +213,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node
) )
.await .await
.unwrap(); .unwrap();
// return ImageFrame::empty();
let len: usize = image.image.data.len(); let len: usize = image.image.data.len();
let storage_buffer = executor let storage_buffer = executor
@ -218,21 +227,22 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node
}, },
) )
.unwrap(); .unwrap();
/*
let canvas = editor_api.application_io.create_surface();
let surface = unsafe { executor.create_surface(canvas) }.unwrap(); // let canvas = editor_api.application_io.create_surface();
let surface_id = surface.surface_id;
let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap(); // let surface = unsafe { executor.create_surface(canvas) }.unwrap();
// let surface_id = surface.surface_id;
// executor.create_render_pass(texture, surface).unwrap(); // let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap();
// // executor.create_render_pass(texture, surface).unwrap();
// let frame = SurfaceFrame {
// surface_id,
// transform: image.transform,
// };
// return frame;
let frame = SurfaceFrame {
surface_id,
transform: image.transform,
};
return frame;*/
log::debug!("creating buffer"); log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap(); let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
@ -269,9 +279,13 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node
} }
#[node_macro::node(category("Debug: GPU"))] #[node_macro::node(category("Debug: GPU"))]
async fn blend_gpu_image(_: (), foreground: ImageFrame<Color>, background: ImageFrame<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrame<Color> { async fn blend_gpu_image(_: (), foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
let foreground = foreground.one_item();
let background = background.one_item();
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64); let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64); let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
@ -320,7 +334,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame<Color>, background: Image
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect(); let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
let Ok(proto_networks_result) = proto_networks else { let Ok(proto_networks_result) = proto_networks else {
log::error!("Error compiling network in 'blend_gpu_image()"); log::error!("Error compiling network in 'blend_gpu_image()");
return ImageFrame::empty(); return ImageFrameTable::default();
}; };
let proto_networks = proto_networks_result; let proto_networks = proto_networks_result;
log::debug!("compiling shader"); log::debug!("compiling shader");
@ -430,7 +444,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame<Color>, background: Image
let result = executor.read_output_buffer(readback_buffer).await.unwrap(); let result = executor.read_output_buffer(readback_buffer).await.unwrap();
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice()); let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
ImageFrame { let result = ImageFrame {
image: Image { image: Image {
data: colors, data: colors,
width: background.image.width, width: background.image.width,
@ -439,5 +453,7 @@ async fn blend_gpu_image(_: (), foreground: ImageFrame<Color>, background: Image
}, },
transform: background.transform, transform: background.transform,
alpha_blending: background.alpha_blending, alpha_blending: background.alpha_blending,
} };
ImageFrameTable::new(result)
} }

View file

@ -1,4 +1,4 @@
use graphene_core::raster::ImageFrame; use graphene_core::raster::image::ImageFrameTable;
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::Color; use graphene_core::Color;
@ -10,10 +10,10 @@ async fn image_color_palette<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
)] )]
image: impl Node<F, Output = ImageFrame<Color>>, image: impl Node<F, Output = ImageFrameTable<Color>>,
#[min(1.)] #[min(1.)]
#[max(28.)] #[max(28.)]
max_size: u32, max_size: u32,
@ -26,6 +26,8 @@ async fn image_color_palette<F: 'n + Send>(
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize]; let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
let image = image.eval(footprint).await; let image = image.eval(footprint).await;
let image = image.one_item();
for pixel in image.image.data.iter() { for pixel in image.image.data.iter() {
let r = pixel.r() * GRID; let r = pixel.r() * GRID;
let g = pixel.g() * GRID; let g = pixel.g() * GRID;
@ -74,7 +76,10 @@ mod test {
use super::*; use super::*;
use graph_craft::generic::FnNode; use graph_craft::generic::FnNode;
use graphene_core::{raster::Image, value::CopiedNode, Node}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image;
use graphene_core::value::CopiedNode;
use graphene_core::Node;
#[test] #[test]
fn test_image_color_palette() { fn test_image_color_palette() {
@ -82,7 +87,7 @@ mod test {
max_size: CopiedNode(1u32), max_size: CopiedNode(1u32),
image: FnNode::new(|_| { image: FnNode::new(|_| {
Box::pin(async move { Box::pin(async move {
ImageFrame { ImageFrameTable::new(ImageFrame {
image: Image { image: Image {
width: 100, width: 100,
height: 100, height: 100,
@ -90,7 +95,7 @@ mod test {
base64_string: None, base64_string: None,
}, },
..Default::default() ..Default::default()
} })
}) })
}), }),
}; };

View file

@ -1,12 +1,8 @@
use crate::wasm_application_io::WasmEditorApi;
use dyn_any::DynAny; use dyn_any::DynAny;
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::proto::DynFuture;
use graphene_core::raster::bbox::Bbox; use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::{ use graphene_core::raster::{
Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample,
Sample,
}; };
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::{Footprint, Transform};
use graphene_core::{AlphaBlending, Color, Node}; use graphene_core::{AlphaBlending, Color, Node};
@ -15,7 +11,6 @@ use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2}; use glam::{DAffine2, DVec2, Vec2};
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -33,10 +28,12 @@ impl From<std::io::Error> for Error {
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> { fn sample_image(footprint: Footprint, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame = image_frame.one_item();
// Resize the image using the image crate // Resize the image using the image crate
let image = image_frame.image; let image = &image_frame.image;
let data = bytemuck::cast_vec(image.data); let data = bytemuck::cast_vec(image.data.clone());
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
@ -47,10 +44,10 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFr
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty(); return ImageFrameTable::default();
} }
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type."); let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
let dynamic_image: image::DynamicImage = image_buffer.into(); let dynamic_image: image::DynamicImage = image_buffer.into();
let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);
@ -83,11 +80,14 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFr
// we need to adjust the offset if we truncate the offset calculation // we need to adjust the offset if we truncate the offset calculation
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
ImageFrame {
let result = ImageFrame {
image, image,
transform: new_transform, transform: new_transform,
alpha_blending: image_frame.alpha_blending, alpha_blending: image_frame.alpha_blending,
} };
ImageFrameTable::new(result)
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -244,6 +244,7 @@ where
MapFn: Fn(_P, _P) -> _P, MapFn: Fn(_P, _P) -> _P,
{ {
let background_size = DVec2::new(background.width() as f64, background.height() as f64); let background_size = DVec2::new(background.width() as f64, background.height() as f64);
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size); let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size);
@ -331,104 +332,104 @@ fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] c
} }
} }
#[cfg(feature = "serde")] // #[cfg(feature = "serde")]
macro_rules! generate_imaginate_node { // macro_rules! generate_imaginate_node {
($($val:ident: $t:ident: $o:ty,)*) => { // ($($val:ident: $t:ident: $o:ty,)*) => {
pub struct ImaginateNode<P: Pixel, E, C, G, $($t,)*> { // pub struct ImaginateNode<P: Pixel, E, C, G, $($t,)*> {
editor_api: E, // editor_api: E,
controller: C, // controller: C,
generation_id: G, // generation_id: G,
$($val: $t,)* // $($val: $t,)*
cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>, // cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
last_generation: std::sync::atomic::AtomicU64, // last_generation: std::sync::atomic::AtomicU64,
} // }
impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode<P, E, C, G, $($t,)*> // impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode<P, E, C, G, $($t,)*>
where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* // where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, // E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, // C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, // G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
{ // {
#[allow(clippy::too_many_arguments)] // #[allow(clippy::too_many_arguments)]
pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self { // pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self {
Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) } // Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) }
} // }
} // }
impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, G, $($t,)*> // impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, G, $($t,)*>
where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* // where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, // E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, // C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>, // G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
{ // {
type Output = DynFuture<'i, ImageFrame<P>>; // type Output = DynFuture<'i, ImageFrame<P>>;
fn eval(&'i self, frame: ImageFrame<P>) -> Self::Output { // fn eval(&'i self, frame: ImageFrame<P>) -> Self::Output {
let controller = self.controller.eval(()); // let controller = self.controller.eval(());
$(let $val = self.$val.eval(());)* // $(let $val = self.$val.eval(());)*
use std::hash::Hasher; // use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default(); // let mut hasher = rustc_hash::FxHasher::default();
frame.image.hash(&mut hasher); // frame.image.hash(&mut hasher);
let hash = hasher.finish(); // let hash = hasher.finish();
let editor_api = self.editor_api.eval(()); // let editor_api = self.editor_api.eval(());
let cache = self.cache.clone(); // let cache = self.cache.clone();
let generation_future = self.generation_id.eval(()); // let generation_future = self.generation_id.eval(());
let last_generation = &self.last_generation; // let last_generation = &self.last_generation;
Box::pin(async move { // Box::pin(async move {
let controller: ImaginateController = controller.await; // let controller: ImaginateController = controller.await;
let generation_id = generation_future.await; // let generation_id = generation_future.await;
if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) { // if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) {
let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; // let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
cache.lock().unwrap().insert(hash, image.clone()); // cache.lock().unwrap().insert(hash, image.clone());
return wrap_image_frame(image, frame.transform); // return wrap_image_frame(image, frame.transform);
} // }
let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); // let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
return wrap_image_frame(image, frame.transform); // return wrap_image_frame(image, frame.transform);
}) // })
} // }
} // }
} // }
} // }
fn wrap_image_frame<P: Pixel>(image: Image<P>, transform: DAffine2) -> ImageFrame<P> { // fn wrap_image_frame<P: Pixel>(image: Image<P>, transform: DAffine2) -> ImageFrame<P> {
if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) { // if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) {
ImageFrame { // ImageFrame {
image, // image,
transform, // transform,
alpha_blending: AlphaBlending::default(), // alpha_blending: AlphaBlending::default(),
} // }
} else { // } else {
let resolution = DVec2::new(image.height as f64, image.width as f64); // let resolution = DVec2::new(image.height as f64, image.width as f64);
ImageFrame { // ImageFrame {
image, // image,
transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation), // transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation),
alpha_blending: AlphaBlending::default(), // alpha_blending: AlphaBlending::default(),
} // }
} // }
} // }
#[cfg(feature = "serde")] // #[cfg(feature = "serde")]
generate_imaginate_node! { // generate_imaginate_node! {
seed: Seed: f64, // seed: Seed: f64,
res: Res: Option<DVec2>, // res: Res: Option<DVec2>,
samples: Samples: u32, // samples: Samples: u32,
sampling_method: SamplingMethod: ImaginateSamplingMethod, // sampling_method: SamplingMethod: ImaginateSamplingMethod,
prompt_guidance: PromptGuidance: f64, // prompt_guidance: PromptGuidance: f64,
prompt: Prompt: String, // prompt: Prompt: String,
negative_prompt: NegativePrompt: String, // negative_prompt: NegativePrompt: String,
adapt_input_image: AdaptInputImage: bool, // adapt_input_image: AdaptInputImage: bool,
image_creativity: ImageCreativity: f64, // image_creativity: ImageCreativity: f64,
inpaint: Inpaint: bool, // inpaint: Inpaint: bool,
mask_blur: MaskBlur: f64, // mask_blur: MaskBlur: f64,
mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill, // mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill,
improve_faces: ImproveFaces: bool, // improve_faces: ImproveFaces: bool,
tiling: Tiling: bool, // tiling: Tiling: bool,
} // }
#[node_macro::node(category("Raster: Generator"))] #[node_macro::node(category("Raster: Generator"))]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -450,7 +451,7 @@ fn noise_pattern(
cellular_distance_function: CellularDistanceFunction, cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType, cellular_return_type: CellularReturnType,
cellular_jitter: f64, cellular_jitter: f64,
) -> ImageFrame<Color> { ) -> ImageFrameTable<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
let mut size = viewport_bounds.size(); let mut size = viewport_bounds.size();
@ -467,7 +468,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty(); return ImageFrameTable::default();
} }
let footprint_scale = footprint.scale(); let footprint_scale = footprint.scale();
@ -511,11 +512,13 @@ fn noise_pattern(
} }
} }
return ImageFrame::<Color> { let result = ImageFrame {
image, image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(), alpha_blending: AlphaBlending::default(),
}; };
return ImageFrameTable::new(result);
} }
}; };
noise.set_noise_type(Some(noise_type)); noise.set_noise_type(Some(noise_type));
@ -573,16 +576,17 @@ fn noise_pattern(
} }
} }
// Return the coherent noise image let result = ImageFrame {
ImageFrame::<Color> {
image, image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(), alpha_blending: AlphaBlending::default(),
} };
ImageFrameTable::new(result)
} }
#[node_macro::node(category("Raster: Generator"))] #[node_macro::node(category("Raster: Generator"))]
fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> { fn mandelbrot(footprint: Footprint) -> ImageFrameTable<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
@ -593,7 +597,7 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty(); return ImageFrameTable::default();
} }
let scale = footprint.scale(); let scale = footprint.scale();
@ -614,7 +618,8 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
data.push(map_color(iter, max_iter)); data.push(map_color(iter, max_iter));
} }
} }
ImageFrame {
let result = ImageFrame {
image: Image { image: Image {
width, width,
height, height,
@ -623,7 +628,9 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
}, },
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default() ..Default::default()
} };
ImageFrameTable::new(result)
} }
#[inline(always)] #[inline(always)]

View file

@ -1,3 +1,5 @@
use crate::vector::{VectorData, VectorDataTable};
use graph_craft::wasm_application_io::WasmEditorApi; use graph_craft::wasm_application_io::WasmEditorApi;
use graphene_core::text::TypesettingConfig; use graphene_core::text::TypesettingConfig;
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache}; pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
@ -13,7 +15,7 @@ fn text<'i: 'n>(
#[default(1.)] character_spacing: f64, #[default(1.)] character_spacing: f64,
#[default(None)] max_width: Option<f64>, #[default(None)] max_width: Option<f64>,
#[default(None)] max_height: Option<f64>, #[default(None)] max_height: Option<f64>,
) -> crate::vector::VectorData { ) -> VectorDataTable {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data)); let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
let typesetting = TypesettingConfig { let typesetting = TypesettingConfig {
@ -23,5 +25,8 @@ fn text<'i: 'n>(
max_width, max_width,
max_height, max_height,
}; };
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false)
let result = VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false);
VectorDataTable::new(result)
} }

View file

@ -1,13 +1,13 @@
use crate::transform::Footprint; use crate::transform::Footprint;
use bezier_rs::{ManipulatorGroup, Subpath}; use bezier_rs::{ManipulatorGroup, Subpath};
use graphene_core::transform::Transform;
use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::misc::BooleanOperation;
use graphene_core::vector::style::Fill;
pub use graphene_core::vector::*; pub use graphene_core::vector::*;
use graphene_core::{Color, GraphicElement, GraphicGroup}; use graphene_core::{transform::Transform, GraphicGroup};
use graphene_core::{Color, GraphicElement, GraphicGroupTable};
pub use path_bool as path_bool_lib; pub use path_bool as path_bool_lib;
use path_bool::FillRule; use path_bool::{FillRule, PathBooleanOperation};
use path_bool::PathBooleanOperation;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::ops::Mul; use std::ops::Mul;
@ -20,41 +20,49 @@ async fn boolean_operation<F: 'n + Send>(
)] )]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> GraphicGroup, () -> GraphicGroupTable,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
group_of_paths: impl Node<F, Output = GraphicGroup>, group_of_paths: impl Node<F, Output = GraphicGroupTable>,
operation: BooleanOperation, operation: BooleanOperation,
) -> VectorData { ) -> VectorDataTable {
let group_of_paths = group_of_paths.eval(footprint).await; let group_of_paths = group_of_paths.eval(footprint).await;
let group_of_paths = group_of_paths.one_item();
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData { fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
let corner1 = DVec2::ZERO; let corner1 = DVec2::ZERO;
let corner2 = DVec2::new(1., 1.); let corner2 = DVec2::new(1., 1.);
let mut subpath = Subpath::new_rect(corner1, corner2); let mut subpath = Subpath::new_rect(corner1, corner2);
subpath.apply_transform(image_frame.transform()); subpath.apply_transform(image_frame.transform());
let mut vector_data = VectorData::from_subpath(subpath); let mut vector_data = VectorData::from_subpath(subpath);
vector_data vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb()));
.style
.set_fill(graphene_core::vector::style::Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb()));
vector_data vector_data
} }
fn union_vector_data(graphic_element: &GraphicElement) -> VectorData { fn union_vector_data(graphic_element: &GraphicElement) -> VectorData {
match graphic_element { match graphic_element {
GraphicElement::VectorData(vector_data) => *vector_data.clone(), GraphicElement::VectorData(vector_data) => {
let vector_data = vector_data.one_item();
vector_data.clone()
}
// Union all vector data in the graphic group into a single vector // Union all vector data in the graphic group into a single vector
GraphicElement::GraphicGroup(graphic_group) => { GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item();
let vector_data = collect_vector_data(graphic_group); let vector_data = collect_vector_data(graphic_group);
boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union) boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union)
} }
GraphicElement::Raster(image) => vector_from_image(image), GraphicElement::RasterFrame(image) => vector_from_image(image),
} }
} }
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> { fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> {
// Ensure all non vector data in the graphic group is converted to vector data // Ensure all non vector data in the graphic group is converted to vector data
let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element)); let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element));
// Apply the transform from the parent graphic group // Apply the transform from the parent graphic group
let transformed_vector_data = vector_data.map(|mut vector_data| { let transformed_vector_data = vector_data.map(|mut vector_data| {
vector_data.transform = graphic_group.transform * vector_data.transform; vector_data.transform = graphic_group.transform * vector_data.transform;
@ -186,15 +194,15 @@ async fn boolean_operation<F: 'n + Send>(
} }
// The first index is the bottom of the stack // The first index is the bottom of the stack
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation); let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation);
let transform = boolean_operation_result.transform; let transform = boolean_operation_result.transform;
VectorData::transform(&mut boolean_operation_result, transform); VectorData::transform(&mut boolean_operation_result, transform);
boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY); boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY);
boolean_operation_result.transform = DAffine2::IDENTITY; boolean_operation_result.transform = DAffine2::IDENTITY;
boolean_operation_result.upstream_graphic_group = Some(group_of_paths); boolean_operation_result.upstream_graphic_group = Some(GraphicGroupTable::new(group_of_paths.clone()));
boolean_operation_result VectorDataTable::new(boolean_operation_result)
} }
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> { fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {

View file

@ -6,13 +6,13 @@ use graphene_core::application_io::SurfaceHandle;
use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig}; use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use graphene_core::raster::bbox::Bbox; use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData; use graphene_core::vector::VectorDataTable;
use graphene_core::GraphicGroup; use graphene_core::GraphicGroupTable;
use graphene_core::{Color, WasmNotSend}; use graphene_core::{Color, WasmNotSend};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -22,8 +22,6 @@ use glam::DAffine2;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::Clamped;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
@ -33,25 +31,33 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
Arc::new(editor.application_io.as_ref().unwrap().create_window()) Arc::new(editor.application_io.as_ref().unwrap().create_window())
} }
#[node_macro::node(category("Debug: GPU"))] // #[cfg(target_arch = "wasm32")]
#[cfg(target_arch = "wasm32")] // use wasm_bindgen::Clamped;
async fn draw_image_frame(_: (), image: ImageFrame<graphene_core::raster::SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> { //
let image_data = image.image.data; // #[node_macro::node(category("Debug: GPU"))]
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice())); // #[cfg(target_arch = "wasm32")]
if image.image.width > 0 && image.image.height > 0 { // async fn draw_image_frame(
let canvas = &surface_handle.surface; // _: (),
canvas.set_width(image.image.width); // image: ImageFrameTable<graphene_core::raster::SRGBA8>,
canvas.set_height(image.image.height); // surface_handle: Arc<WasmSurfaceHandle>,
// TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu) // ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap(); // let image = image.one_item();
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData"); // let image_data = image.image.data;
context.put_image_data(&image_data, 0., 0.).unwrap(); // let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
} // if image.image.width > 0 && image.image.height > 0 {
graphene_core::application_io::SurfaceHandleFrame { // let canvas = &surface_handle.surface;
surface_handle, // canvas.set_width(image.image.width);
transform: image.transform, // canvas.set_height(image.image.height);
} // // TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu)
} // let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
// let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData");
// context.put_image_data(&image_data, 0., 0.).unwrap();
// }
// graphene_core::application_io::SurfaceHandleFrame {
// surface_handle,
// transform: image.transform,
// }
// }
#[node_macro::node(category("Network"))] #[node_macro::node(category("Network"))]
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
@ -69,8 +75,10 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> { fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable<Color> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrame::default() }; let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return ImageFrameTable::default();
};
let image = image.to_rgba32f(); let image = image.to_rgba32f();
let image = ImageFrame { let image = ImageFrame {
image: Image { image: Image {
@ -81,7 +89,8 @@ fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> {
}, },
..Default::default() ..Default::default()
}; };
image
ImageFrameTable::new(image)
} }
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType { fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType {
@ -144,17 +153,17 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>( async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
_: (), _: (),
#[implementations( #[implementations(
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
)] )]
data: impl Node<Footprint, Output = T>, data: impl Node<Footprint, Output = T>,
footprint: Footprint, footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> { ) -> ImageFrameTable<Color> {
if footprint.transform.matrix2.determinant() == 0. { if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization"); log::trace!("Invalid footprint received for rasterization");
return ImageFrame::default(); return ImageFrameTable::default();
} }
let mut data = data.eval(footprint).await; let mut data = data.eval(footprint).await;
@ -192,12 +201,13 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap(); let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32); let result = ImageFrame {
ImageFrame { image: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
image,
transform: footprint.transform, transform: footprint.transform,
..Default::default() ..Default::default()
} };
ImageFrameTable::new(result)
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
@ -205,9 +215,9 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
render_config: RenderConfig, render_config: RenderConfig,
editor_api: &'a WasmEditorApi, editor_api: &'a WasmEditorApi,
#[implementations( #[implementations(
Footprint -> VectorData, Footprint -> VectorDataTable,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GraphicGroup, Footprint -> GraphicGroupTable,
Footprint -> graphene_core::Artboard, Footprint -> graphene_core::Artboard,
Footprint -> graphene_core::ArtboardGroup, Footprint -> graphene_core::ArtboardGroup,
Footprint -> Option<Color>, Footprint -> Option<Color>,

View file

@ -1,23 +1,23 @@
use dyn_any::StaticType; use dyn_any::StaticType;
use graph_craft::document::value::RenderOutput; use graph_craft::document::value::RenderOutput;
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::fn_type; use graphene_core::fn_type;
use graphene_core::ops::IdentityNode; use graphene_core::ops::IdentityNode;
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::structural::Then; use graphene_core::structural::Then;
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::value::{ClonedNode, ValueNode};
use graphene_core::vector::VectorData; use graphene_core::vector::VectorDataTable;
use graphene_core::{concrete, generic, Artboard, GraphicGroup}; use graphene_core::{concrete, generic, Artboard, GraphicGroupTable};
use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Cow, ProtoNodeIdentifier, Type};
use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::TextureFrame; use graphene_std::application_io::TextureFrame;
use graphene_std::raster::*; use graphene_std::raster::*;
use graphene_std::wasm_application_io::*; use graphene_std::wasm_application_io::*;
use graphene_std::GraphicElement; use graphene_std::{GraphicElement, GraphicGroup};
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
use wgpu_executor::{WgpuSurface, WindowHandle}; use wgpu_executor::{WgpuSurface, WindowHandle};
@ -112,23 +112,21 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
NodeIOTypes::new(generic!(I), generic!(I), vec![]), NodeIOTypes::new(generic!(I), generic!(I), vec![]),
), ),
async_node!(graphene_core::ops::IntoNode<ImageFrame<SRGBA8>>, input: ImageFrame<Color>, params: []), // async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<ImageFrame<Color>>, input: ImageFrame<SRGBA8>, params: []), // async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: ImageFrame<Color>, params: []), async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: VectorData, params: []), async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: VectorDataTable, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: GraphicGroup, params: []),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::ops::IntoNode<&WgpuExecutor>, input: &WasmEditorApi, params: []), async_node!(graphene_core::ops::IntoNode<&WgpuExecutor>, input: &WasmEditorApi, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: VectorData, params: []), async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: VectorDataTable, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: ImageFrame<Color>, params: []), async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: ImageFrameTable<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<GraphicElement>, input: GraphicGroupTable, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: VectorDataTable, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: VectorData, params: []), async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroup>, input: ImageFrame<Color>, params: []), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]), register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Color>, RedGreenBlue]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]), // register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>, RedGreenBlue]), // register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable<Color>, params: [ImageFrameTable<Luma>, RedGreenBlue]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>, RedGreenBlue]),
( (
ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"), ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"),
|args| { |args| {
@ -136,10 +134,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::value::*; use graphene_core::value::*;
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await; let channel_r: ImageFrameTable<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await; let channel_g: ImageFrameTable<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await;
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2].clone()).eval(()).await; let channel_b: ImageFrameTable<Color> = DowncastBothNode::new(args[2].clone()).eval(()).await;
let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3].clone()).eval(()).await; let channel_a: ImageFrameTable<Color> = DowncastBothNode::new(args[3].clone()).eval(()).await;
let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red)); let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red));
let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green)); let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green));
@ -147,6 +145,11 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let complete_node = insert_r.then(insert_g).then(insert_b); let complete_node = insert_r.then(insert_g).then(insert_b);
let complete_node = complete_node.then(MaskImageNode::new(ClonedNode::new(channel_a.clone()))); let complete_node = complete_node.then(MaskImageNode::new(ClonedNode::new(channel_a.clone())));
let channel_r = channel_r.one_item();
let channel_g = channel_g.one_item();
let channel_b = channel_b.one_item();
let channel_a = channel_a.one_item();
// TODO: Move to FN Node for better performance // TODO: Move to FN Node for better performance
let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO); let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO);
for image in [channel_a, channel_r, channel_g, channel_b] { for image in [channel_a, channel_r, channel_g, channel_b] {
@ -160,6 +163,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
transform, transform,
..Default::default() ..Default::default()
}; };
let empty_image = ImageFrameTable::new(empty_image);
let final_image = ClonedNode::new(empty_image).then(complete_node); let final_image = ClonedNode::new(empty_image).then(complete_node);
let final_image = FutureWrapperNode::new(final_image); let final_image = FutureWrapperNode::new(final_image);
@ -169,20 +173,25 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
}, },
NodeIOTypes::new( NodeIOTypes::new(
concrete!(()), concrete!(()),
concrete!(ImageFrame<Color>), concrete!(ImageFrameTable<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>)], vec![
fn_type!(ImageFrameTable<Color>),
fn_type!(ImageFrameTable<Color>),
fn_type!(ImageFrameTable<Color>),
fn_type!(ImageFrameTable<Color>),
],
), ),
), ),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrame<Color>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrame<Color>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorData]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorDataTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicGroup]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroupTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicGroup]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicGroupTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => graphene_core::GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => graphene_core::GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]), register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]),
@ -195,13 +204,13 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone()); let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
// let document_node = ClonedNode::new(document_node.eval(())); // let document_node = ClonedNode::new(document_node.eval(()));
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node); let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased() any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(
concrete!(ImageFrame<Color>), concrete!(ImageFrameTable<Color>),
concrete!(ImageFrame<Color>), concrete!(ImageFrameTable<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)], vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
), ),
), ),
@ -239,36 +248,36 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast); let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(()))); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased() any.into_type_erased()
} else { } else {
let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast); let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased() any.into_type_erased()
} }
}) })
}, },
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]), NodeIOTypes::new(concrete!(ImageFrameTable<Color>), concrete!(ImageFrameTable<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
), ),
( // (
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), // ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|args| { // |args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; // use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); // let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move { // Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await); // let curve = ClonedNode::new(curve.eval(()).await);
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); // 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 = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); // let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); // let any: DynAnyNode<ImageFrameTable<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased() // any.into_type_erased()
}) // })
}, // },
NodeIOTypes::new(concrete!(ImageFrame<Luma>), concrete!(ImageFrame<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]), // 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. // TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
( (
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
@ -281,52 +290,56 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); 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 = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::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!(ImageFrame<Color>), concrete!(ImageFrame<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() any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(
concrete!(ImageFrame<Color>), concrete!(ImageFrameTable<Color>),
concrete!(ImageFrame<Color>), concrete!(ImageFrameTable<Color>),
vec![ vec![fn_type!(graphene_core::raster::curve::Curve)],
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),
],
), ),
), ),
// (
// 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: (), params: [Image<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorData]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrame<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec<DVec2>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc<WasmSurfaceHandle>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]),
@ -339,8 +352,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorData]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrame<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec<DVec2>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc<WasmSurfaceHandle>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]),
@ -355,7 +368,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorData]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]),

View file

@ -825,7 +825,7 @@ mod tests {
fn test_node_with_implementations() { fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment")); let attr = quote!(category("Raster: Adjustment"));
let input = quote!( let input = quote!(
fn levels<P: Pixel>(image: ImageFrame<P>, #[implementations(f32, f64)] shadows: f64) -> ImageFrame<P> { fn levels<P: Pixel>(image: ImageFrameTable<P>, #[implementations(f32, f64)] shadows: f64) -> ImageFrameTable<P> {
// Implementation details... // Implementation details...
} }
); );
@ -846,10 +846,10 @@ mod tests {
where_clause: None, where_clause: None,
input: Input { input: Input {
pat_ident: pat_ident("image"), pat_ident: pat_ident("image"),
ty: parse_quote!(ImageFrame<P>), ty: parse_quote!(ImageFrameTable<P>),
implementations: Punctuated::new(), implementations: Punctuated::new(),
}, },
output_type: parse_quote!(ImageFrame<P>), output_type: parse_quote!(ImageFrameTable<P>),
is_async: false, is_async: false,
fields: vec![ParsedField::Regular { fields: vec![ParsedField::Regular {
pat_ident: pat_ident("shadows"), pat_ident: pat_ident("shadows"),
@ -939,7 +939,7 @@ mod tests {
fn test_async_node() { fn test_async_node() {
let attr = quote!(category("IO")); let attr = quote!(category("IO"));
let input = quote!( let input = quote!(
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> ImageFrame<Color> { async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> ImageFrameTable<Color> {
// Implementation details... // Implementation details...
} }
); );
@ -963,7 +963,7 @@ mod tests {
ty: parse_quote!(&WasmEditorApi), ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(), implementations: Punctuated::new(),
}, },
output_type: parse_quote!(ImageFrame<Color>), output_type: parse_quote!(ImageFrameTable<Color>),
is_async: true, is_async: true,
fields: vec![ParsedField::Regular { fields: vec![ParsedField::Regular {
pat_ident: pat_ident("path"), pat_ident: pat_ident("path"),
@ -1077,7 +1077,7 @@ mod tests {
fn test_invalid_implementation_syntax() { fn test_invalid_implementation_syntax() {
let attr = quote!(category("Test")); let attr = quote!(category("Test"));
let input = quote!( let input = quote!(
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>) -> T { fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, ImageFrameTable<Color>))] input: impl Node<Footprint, Output = T>) -> T {
// Implementation details... // Implementation details...
} }
); );
@ -1103,10 +1103,10 @@ mod tests {
#[implementations((), #tuples, Footprint)] footprint: F, #[implementations((), #tuples, Footprint)] footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> ImageFrame<Color>, () -> ImageFrameTable<Color>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> ImageFrame<Color>, Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,

View file

@ -6,11 +6,9 @@ pub use executor::GpuExecutor;
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame}; use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle};
use graphene_core::raster::{Image, ImageFrame, SRGBA8};
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::{Footprint, Transform};
use graphene_core::Type; use graphene_core::{Cow, Node, SurfaceFrame, Type};
use graphene_core::{Color, Cow, Node, SurfaceFrame};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use futures::Future; use futures::Future;
@ -154,7 +152,7 @@ impl WgpuExecutor {
let surface_texture = surface.get_current_texture()?; let surface_texture = surface.get_current_texture()?;
let render_params = RenderParams { let render_params = RenderParams {
// We are using an explicit opaque color here to eliminate the alpha premulitplication step // We are using an explicit opaque color here to eliminate the alpha premultiplication step
// which would be required to support a transparent webgpu canvas // which would be required to support a transparent webgpu canvas
base_color: vello::peniko::Color::from_rgba8(0x22, 0x22, 0x22, 0xff), base_color: vello::peniko::Color::from_rgba8(0x22, 0x22, 0x22, 0xff),
width, width,
@ -164,7 +162,7 @@ impl WgpuExecutor {
{ {
let mut renderer = self.vello_renderer.lock().await; let mut renderer = self.vello_renderer.lock().await;
for (id, texture) in context.ressource_overrides.iter() { for (id, texture) in context.resource_overrides.iter() {
let texture_view = wgpu::ImageCopyTextureBase { let texture_view = wgpu::ImageCopyTextureBase {
texture: texture.clone(), texture: texture.clone(),
mip_level: 0, mip_level: 0,
@ -905,32 +903,34 @@ async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node<Fo
} }
} }
#[node_macro::node(category(""))] // #[node_macro::node(category(""))]
async fn upload_texture<'a: 'n, F: Copy + Send + Sync + 'n>( // async fn upload_texture<'a: 'n, F: Copy + Send + Sync + 'n>(
#[implementations((), Footprint)] footprint: F, // #[implementations((), Footprint)] footprint: F,
#[implementations(() -> ImageFrame<Color>, Footprint -> ImageFrame<Color>)] input: impl Node<F, Output = ImageFrame<Color>>, // #[implementations(() -> ImageFrameTable<Color>, Footprint -> ImageFrameTable<Color>)] input: impl Node<F, Output = ImageFrameTable<Color>>,
executor: &'a WgpuExecutor, // executor: &'a WgpuExecutor,
) -> TextureFrame { // ) -> TextureFrame {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect(); // // let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let input = input.eval(footprint).await; // let input = input.eval(footprint).await;
let new_data = input.image.data.into_iter().map(SRGBA8::from).collect(); // let input = input.one_item();
let new_image = Image {
width: input.image.width,
height: input.image.height,
data: new_data,
base64_string: None,
};
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); // let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
let texture = match shader_input { // let new_image = Image {
ShaderInput::TextureBuffer(buffer, _) => buffer, // width: input.image.width,
ShaderInput::StorageTextureBuffer(buffer, _) => buffer, // height: input.image.height,
_ => unreachable!("Unsupported ShaderInput type"), // data: new_data,
}; // base64_string: None,
// };
TextureFrame { // let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
texture: texture.into(), // let texture = match shader_input {
transform: input.transform, // ShaderInput::TextureBuffer(buffer, _) => buffer,
alpha_blend: Default::default(), // ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
} // _ => unreachable!("Unsupported ShaderInput type"),
} // };
// TextureFrame {
// texture: texture.into(),
// transform: input.transform,
// alpha_blend: Default::default(),
// }
// }