mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 13:02:20 +00:00
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:
parent
408f9bffa1
commit
eb0ff20d3c
43 changed files with 1855 additions and 1221 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
87
node-graph/gcore/src/instances.rs
Normal file
87
node-graph/gcore/src/instances.rs
Normal 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>;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 = ℑ
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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]),
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue