Lay groundwork for adaptive resolution system (#1395)

* Make transform node accept footprint as input and pass it along to its input

use f32 instead of f64 and add default to document node definition

* Add cull node

* Fix types for Transform and Cull Nodes

* Add render config struct

* Add Render Node skeleton

* Add Render Node to node_registry

* Make types macro use macro hygiene

* Place Render Node as output

* Start making DownresNode footprint aware

* Correctly calculate footprint in Transform Node

* Add cropping and resizing to downres node

* Fix Output node declaration

* Fix image transform

* Fix Vector Data rendering

* Add concept of ImageRenderMode

* Take base image size into account when calculating the final image size

* Supply viewport transform to the node graph

* Start adapting document graph to resolution agnosticism

* Make document node short circuting not shift the input index

* Apply clippy lints
This commit is contained in:
Dennis Kobert
2023-10-17 11:02:07 -07:00
committed by Keavon Chambers
parent 239ca698e5
commit d82f133514
33 changed files with 836 additions and 305 deletions
@@ -86,19 +86,6 @@ impl LayoutHolder for ExportDialogMessageHandler {
.widget_holder(),
];
let resolution = vec![
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(self.scale_factor))
.unit("")
.min(0.)
.max((1u64 << std::f64::MANTISSA_DIGITS) as f64)
.disabled(self.file_type == FileType::Svg)
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
.min_width(200)
.widget_holder(),
];
let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false));
let mut export_area_options = vec![
(ExportBounds::AllArtwork, "All Artwork".to_string(), false),
@@ -6,6 +6,7 @@ use crate::messages::frontend::utility_types::ExportBounds;
use crate::messages::frontend::utility_types::FileType;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::new_raster_network;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
@@ -588,21 +589,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let image_size = DVec2::new(image.width as f64, image.height as f64);
let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else {
warn!("Image node should be in registry");
return;
};
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
warn!("Transform node should be in registry");
return;
};
let Some(downres_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Downres") else {
warn!("Downres node should be in registry");
return;
};
let path = vec![generate_uuid()];
let mut network = NodeNetwork::default();
// Transform of parent folder
let to_parent_folder = self.document_legacy.generate_transform_across_scope(&path[..path.len() - 1], None).unwrap_or_default();
@@ -622,19 +609,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(DocumentMessage::StartTransaction);
network.push_node(
image_node_type.to_document_node(
[graph_craft::document::NodeInput::value(
graph_craft::document::value::TaggedValue::ImageFrame(ImageFrame { image, transform: DAffine2::IDENTITY }),
false,
)],
graph_craft::document::DocumentNodeMetadata::position((8, 4)),
),
false,
);
network.push_node(transform_node_type.to_document_node_default_inputs([], Default::default()), true);
network.push_node(downres_node_type.to_document_node_default_inputs([], Default::default()), true);
network.push_output_node();
let image_frame = ImageFrame { image, transform: DAffine2::IDENTITY };
let network = new_raster_network(image_frame);
responses.add(DocumentOperation::AddFrame {
path: path.clone(),
@@ -97,20 +97,24 @@ impl<'a> ModifyInputsContext<'a> {
// Locate the node output of the first sibling layer to the new layer
let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] {
let sibling_node = &self.network.nodes.get(node_id)?;
let node_id = *node_id;
let output_index = *output_index;
let sibling_layer = if sibling_node.name == "Layer" {
// There is already a layer node
NodeOutput::new(*node_id, 0)
NodeOutput::new(node_id, 0)
} else {
// The user has connected another node to the output. Insert a layer node between the output and the node.
let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
let node_id = self.insert_between(generate_uuid(), NodeOutput::new(*node_id, *output_index), output, node, 0, 0, IVec2::new(-8, 0))?;
let mut node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
self.add_empty_stack(&mut node);
let node_id = self.insert_between(generate_uuid(), NodeOutput::new(node_id, output_index), output, node, 0, 0, IVec2::new(-8, 0))?;
NodeOutput::new(node_id, 0)
};
let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(0, 3))
} else {
let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node();
let mut layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node();
self.add_empty_stack(&mut layer_node);
self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-5, 3))
};
@@ -125,7 +129,15 @@ impl<'a> ModifyInputsContext<'a> {
new_id
}
fn add_empty_stack(&mut self, node: &mut DocumentNode) {
let empty_stack = resolve_document_node_type("Empty Stack").expect("EmptyStack node").default_document_node();
let empty_id = generate_uuid();
self.network.nodes.insert(empty_id, empty_stack);
*node.inputs.last_mut().unwrap() = NodeInput::node(empty_id, 0);
}
fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
let cull_node = resolve_document_node_type("Cull").expect("Node").default_document_node();
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
[
None,
@@ -137,7 +149,9 @@ impl<'a> ModifyInputsContext<'a> {
Default::default(),
);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0))
let cull_id = generate_uuid();
self.insert_node_before(cull_id, layer, 0, cull_node, IVec2::new(-8, 0));
self.insert_node_before(generate_uuid(), cull_id, 0, artboard_node, IVec2::new(-8, 0))
}
fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
@@ -145,6 +159,7 @@ impl<'a> ModifyInputsContext<'a> {
let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist");
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
};
let cull = resolve_document_node_type("Cull").expect("Cull node does not exist").default_document_node();
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node();
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_document_node();
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node();
@@ -155,8 +170,10 @@ impl<'a> ModifyInputsContext<'a> {
self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0));
let transform_id = generate_uuid();
self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0));
let cull_id = generate_uuid();
self.insert_node_before(cull_id, transform_id, 0, cull, IVec2::new(-8, 0));
let shape_id = generate_uuid();
self.insert_node_before(shape_id, transform_id, 0, shape, IVec2::new(-8, 0));
self.insert_node_before(shape_id, cull_id, 0, shape, IVec2::new(-8, 0));
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
@@ -126,7 +126,7 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(pivot),
..
} = inputs[5]
} = inputs[4]
{
pivot
} else {
@@ -13,6 +13,7 @@ use graphene_core::application_io::SurfaceHandle;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
use graphene_core::text::Font;
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData;
use graphene_core::*;
@@ -101,6 +102,7 @@ pub struct DocumentNodeType {
pub outputs: Vec<DocumentOutputType>,
pub primary_output: bool,
pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>,
pub manual_composition: Option<graphene_core::Type>,
}
impl Default for DocumentNodeType {
@@ -113,6 +115,7 @@ impl Default for DocumentNodeType {
outputs: Default::default(),
primary_output: Default::default(),
properties: node_properties::no_properties,
manual_composition: Default::default(),
}
}
}
@@ -187,7 +190,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
(
0,
DocumentNode {
inputs: vec![NodeInput::Network(concrete!(graphene_core::vector::VectorData))],
name: "To Graphic Element".to_string(),
inputs: vec![NodeInput::Network(generic!(T))],
implementation: DocumentNodeImplementation::proto("graphene_core::ToGraphicElementData"),
..Default::default()
},
@@ -196,6 +200,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
(
1,
DocumentNode {
name: "Monitor".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"),
skip_deduplication: true,
@@ -205,6 +210,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
(
2,
DocumentNode {
name: "ConstructLayer".to_string(),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![
NodeInput::node(1, 0),
NodeInput::Network(concrete!(String)),
@@ -213,9 +220,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(graphene_core::GraphicGroup)),
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(graphene_core::GraphicGroup)))),
],
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>"),
..Default::default()
},
),
@@ -253,55 +260,14 @@ fn static_nodes() -> Vec<DocumentNodeType> {
..Default::default()
},
DocumentNodeType {
name: "Downres",
category: "Raster",
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(1, 0)],
nodes: [
DocumentNode {
name: "Downres".to_string(),
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::DownresNode<_>")),
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
// We currently just clone by default
/*DocumentNode {
name: "Clone".to_string(),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
..Default::default()
},*/
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), false)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("Downres the image to a lower resolution"),
name: "Empty Stack",
category: "Hidden",
identifier: NodeImplementation::proto("graphene_core::transform::CullNode<_>"),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), false)],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
..Default::default()
},
// DocumentNodeType {
// name: "Input Frame",
// category: "Ignore",
// identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
// inputs: vec![DocumentInputType {
// name: "In",
// data_type: FrontendGraphDataType::Raster,
// default: NodeInput::Network,
// }],
// outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Raster)],
// properties: node_properties::input_properties,
// },
DocumentNodeType {
name: "Input Frame",
category: "Ignore",
@@ -374,11 +340,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -417,11 +385,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -465,7 +435,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
nodes: [
DocumentNode {
name: "SetNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))],
manual_composition: Some(concrete!(WasmEditorApi)),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
..Default::default()
},
@@ -477,7 +447,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "RefNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::lambda(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")),
..Default::default()
},
@@ -533,12 +504,61 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType {
name: "Output",
category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
inputs: vec![DocumentInputType {
name: "Output",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
}],
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![3, 0],
outputs: vec![NodeOutput::new(4, 0)],
nodes: [
DocumentNode {
name: "EditorApi".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
..Default::default()
},
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "Conversion".to_string(),
inputs: vec![NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, GraphicGroup>")),
..Default::default()
},
DocumentNode {
name: "RenderNode".to_string(),
inputs: vec![NodeInput::node(0, 0), NodeInput::node(3, 0), NodeInput::node(2, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}),
inputs: vec![
DocumentInputType {
name: "Output",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::default()), true),
},
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::Network(concrete!(WasmEditorApi)),
},
],
outputs: vec![],
properties: node_properties::output_properties,
..Default::default()
@@ -866,11 +886,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Memoize",
category: "Structural",
identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"),
inputs: vec![
DocumentInputType::value("ShortCircut", TaggedValue::None, false),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
],
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
manual_composition: Some(concrete!(())),
..Default::default()
},
DocumentNodeType {
@@ -882,14 +900,6 @@ fn static_nodes() -> Vec<DocumentNodeType> {
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
..Default::default()
},
DocumentNodeType {
name: "Ref",
category: "Structural",
identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
..Default::default()
},
#[cfg(feature = "gpu")]
DocumentNodeType {
name: "Uniform",
@@ -912,7 +922,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -963,7 +974,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1014,7 +1026,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1076,7 +1089,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1172,7 +1186,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1223,7 +1238,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1268,7 +1284,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -1366,7 +1383,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
@@ -2065,6 +2083,24 @@ fn static_nodes() -> Vec<DocumentNodeType> {
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
..Default::default()
},
DocumentNodeType {
name: "Downres",
category: "Structural",
identifier: NodeImplementation::proto("graphene_std::raster::DownresNode<_>"),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![DocumentInputType::value("Raseter Data", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
..Default::default()
},
DocumentNodeType {
name: "Cull",
category: "Vector",
identifier: NodeImplementation::proto("graphene_core::transform::CullNode<_>"),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true)],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
..Default::default()
},
DocumentNodeType {
name: "Text",
category: "Vector",
@@ -2082,9 +2118,10 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType {
name: "Transform",
category: "Transform",
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _>"),
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![
DocumentInputType::value("Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true),
DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
DocumentInputType::value("Rotation", TaggedValue::F32(0.), false),
DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false),
@@ -2387,6 +2424,7 @@ impl DocumentNodeType {
inputs,
implementation: self.generate_implementation(),
metadata,
manual_composition: self.manual_composition.clone(),
..Default::default()
}
}
@@ -2421,7 +2459,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
if input_type.is_none() {
input_type = Some(input.clone());
}
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type");
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type {network:#?}");
network_inputs.push(*id);
}
}
@@ -2431,6 +2469,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
// if the network has no inputs, it doesn't need to be wrapped in a scope
if len == 0 {
log::warn!("Network has no inputs, not wrapping in scope");
return network;
}
@@ -2469,19 +2508,18 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
resolve_document_node_type("Input Frame")
.expect("Input Frame node does not exist")
.to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
false,
);
network.push_node(
resolve_document_node_type("Output")
.expect("Output node does not exist")
.to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
false,
);
network
}
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
let path_generator = resolve_document_node_type("Shape").expect("Shape node does not exist");
let cull_node = resolve_document_node_type("Cull").expect("Cull node does not exist");
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist");
@@ -2489,14 +2527,31 @@ pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGrou
let mut network = NodeNetwork::default();
network.push_node(
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], DocumentNodeMetadata::position((0, 4))),
false,
);
network.push_node(transform.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(fill.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(output.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], DocumentNodeMetadata::position((0, 4))));
network.push_node(cull_node.to_document_node_default_inputs([None], Default::default()));
network.push_node(transform.to_document_node_default_inputs([None], Default::default()));
network.push_node(fill.to_document_node_default_inputs([None], Default::default()));
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()));
network.push_node(output.to_document_node_default_inputs([None], Default::default()));
network
}
pub fn new_raster_network(image_frame: ImageFrame<Color>) -> NodeNetwork {
let sample_node = resolve_document_node_type("Downres").expect("Downres node does not exist");
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
let output = resolve_document_node_type("Output").expect("Output node does not exist");
let mut network = NodeNetwork::default();
let image_node_type = resolve_document_node_type("Image").expect("Image node should be in registry");
network.push_node(image_node_type.to_document_node(
[graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::ImageFrame(image_frame), false)],
Default::default(),
));
network.push_node(sample_node.to_document_node_default_inputs([None], Default::default()));
network.push_node(transform.to_document_node_default_inputs([None], Default::default()));
network.push_node(output.to_document_node_default_inputs([None], Default::default()));
network
}
@@ -2511,21 +2566,18 @@ pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
inputs: vec![0],
..Default::default()
};
network.push_node(
text_generator.to_document_node(
[
NodeInput::Network(concrete!(WasmEditorApi)),
NodeInput::value(TaggedValue::String(text), false),
NodeInput::value(TaggedValue::Font(font), false),
NodeInput::value(TaggedValue::F64(size), false),
],
DocumentNodeMetadata::position((0, 4)),
),
false,
);
network.push_node(transform.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(fill.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(output.to_document_node_default_inputs([None], Default::default()), true);
network.push_node(text_generator.to_document_node(
[
NodeInput::Network(concrete!(WasmEditorApi)),
NodeInput::value(TaggedValue::String(text), false),
NodeInput::value(TaggedValue::Font(font), false),
NodeInput::value(TaggedValue::F64(size), false),
],
DocumentNodeMetadata::position((0, 4)),
));
network.push_node(transform.to_document_node_default_inputs([None], Default::default()));
network.push_node(fill.to_document_node_default_inputs([None], Default::default()));
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()));
network.push_node(output.to_document_node_default_inputs([None], Default::default()));
network
}
@@ -13,7 +13,6 @@ use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingM
use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
use graphene_core::text::Font;
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
use graphene_core::{Cow, Type, TypeDescriptor};
use glam::{DVec2, IVec2};
@@ -1222,7 +1221,10 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
};
let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", add_blank_assist);
vec![translation, rotation, scale]
let vector_data = start_widgets(document_node, node_id, 0, "Data", FrontendGraphDataType::Vector, false);
let vector_data = LayoutGroup::Row { widgets: vector_data };
vec![vector_data, translation, rotation, scale]
}
pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
+28 -11
View File
@@ -13,12 +13,12 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler;
use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
use graph_craft::{concrete, Type};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache;
use graphene_core::transform::Transform;
use graphene_core::transform::{Footprint, Transform};
use graphene_core::vector::style::ViewMode;
use graphene_core::{Color, SurfaceFrame, SurfaceId};
@@ -26,7 +26,6 @@ use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor;
use glam::{DAffine2, DVec2};
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::mpsc::{Receiver, Sender};
@@ -72,6 +71,7 @@ pub(crate) struct GenerationRequest {
graph: NodeNetwork,
path: Vec<LayerId>,
image_frame: Option<ImageFrame<Color>>,
transform: DAffine2,
}
pub(crate) struct GenerationResponse {
@@ -140,6 +140,7 @@ impl NodeRuntime {
generation_id,
graph,
image_frame,
transform,
path,
..
}) => {
@@ -151,7 +152,7 @@ impl NodeRuntime {
.map(|node| node.path.clone().unwrap_or_default())
.collect();
let result = self.execute_network(&path, network, image_frame).await;
let result = self.execute_network(&path, network, image_frame, transform).await;
let mut responses = VecDeque::new();
self.update_thumbnails(&path, monitor_nodes, &mut responses);
let response = GenerationResponse {
@@ -168,7 +169,7 @@ impl NodeRuntime {
}
}
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>) -> Result<TaggedValue, String> {
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, transform: DAffine2) -> Result<TaggedValue, String> {
if self.wasm_io.is_none() {
self.wasm_io = Some(WasmApplicationIo::new().await);
}
@@ -179,6 +180,10 @@ impl NodeRuntime {
application_io: self.wasm_io.as_ref().unwrap(),
node_graph_message_sender: &self.sender,
imaginate_preferences: &self.imaginate_preferences,
render_config: RenderConfig {
viewport: Footprint { transform, ..Default::default() },
..Default::default()
},
};
// We assume only one output
@@ -197,7 +202,8 @@ impl NodeRuntime {
let result = match self.executor.input_type() {
Some(t) if t == concrete!(WasmEditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()),
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
_ => Err("Invalid input type".to_string()),
Some(t) => Err(format!("Invalid input type {:?}", t)),
_ => Err("No input type".to_string()),
}?;
if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
@@ -231,7 +237,7 @@ impl NodeRuntime {
};
use graphene_core::renderer::*;
let bounds = graphic_element_data.bounding_box(DAffine2::IDENTITY);
let render_params = RenderParams::new(ViewMode::Normal, bounds, true);
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, bounds, true);
let mut render = SvgRender::new();
graphic_element_data.render_svg(&mut render, &render_params);
let [min, max] = bounds.unwrap_or_default();
@@ -339,13 +345,14 @@ impl Default for NodeGraphExecutor {
impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, layer_path: Vec<LayerId>) -> u64 {
fn queue_execution(&self, network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, layer_path: Vec<LayerId>, transform: DAffine2) -> u64 {
let generation_id = generate_uuid();
let request = GenerationRequest {
path: layer_path,
graph: network,
image_frame,
generation_id,
transform,
};
self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request");
@@ -452,9 +459,10 @@ impl NodeGraphExecutor {
// Construct the input image frame
let transform = DAffine2::IDENTITY;
let image_frame = ImageFrame { image, transform };
let document_transform = document.document_legacy.metadata.document_to_viewport;
// Execute the node graph
let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone());
let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone(), document_transform);
self.futures.insert(generation_id, ExecutionContext { layer_path, document_id });
@@ -533,12 +541,21 @@ impl NodeGraphExecutor {
warn!("Rendered graph produced artboard (which is not currently rendered): {artboard:#?}");
return Err("Artboard (see console)".to_string());
}
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
// Send to frontend
log::debug!("svg: {svg}");
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
responses.add(DocumentMessage::RenderScrollbars);
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
//return Err("Graphic group (see console)".to_string());
}
TaggedValue::GraphicGroup(graphic_group) => {
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, None, false);
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::BlobUrl, None, false);
// Render svg
graphic_group.render_svg(&mut render, &render_params);