mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 20:18:11 +08:00
Thumbnails for the layer node (#1210)
* Thumbnails for the layer node * Raster node graph frames * Downscale to a random resolution * Cleanup and bug fixes * Generate paths before duplicating outputs * Fix stable id test * Code review changes * Code review pass with minor changes --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
727e5bdeb1
commit
6400953af5
@@ -54,7 +54,7 @@ impl LayerData for LayerLayer {
|
|||||||
match &self.cached_output_data {
|
match &self.cached_output_data {
|
||||||
CachedOutputData::VectorPath(vector_data) => {
|
CachedOutputData::VectorPath(vector_data) => {
|
||||||
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
|
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
|
||||||
let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
|
let transformed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
|
||||||
|
|
||||||
let _ = write!(svg, "<path d=\"");
|
let _ = write!(svg, "<path d=\"");
|
||||||
for subpath in &vector_data.subpaths {
|
for subpath in &vector_data.subpaths {
|
||||||
@@ -62,7 +62,7 @@ impl LayerData for LayerLayer {
|
|||||||
}
|
}
|
||||||
svg.push('"');
|
svg.push('"');
|
||||||
|
|
||||||
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transfomed_bounds));
|
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds));
|
||||||
let _ = write!(svg, "/>");
|
let _ = write!(svg, "/>");
|
||||||
}
|
}
|
||||||
CachedOutputData::BlobURL(blob_url) => {
|
CachedOutputData::BlobURL(blob_url) => {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ pub struct FrontendImageData {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub image_data: std::sync::Arc<Vec<u8>>,
|
pub image_data: std::sync::Arc<Vec<u8>>,
|
||||||
pub transform: Option<[f64; 6]>,
|
pub transform: Option<[f64; 6]>,
|
||||||
|
#[serde(rename = "nodeId")]
|
||||||
|
pub node_id: Option<graph_craft::document::NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
|
||||||
|
|||||||
@@ -187,8 +187,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||||||
}
|
}
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
NodeGraph(message) => {
|
NodeGraph(message) => {
|
||||||
let selected_layers = &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()));
|
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, executor));
|
||||||
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, selected_layers));
|
|
||||||
}
|
}
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
|
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::messages::input_mapper::utility_types::macros::action_keys;
|
|||||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
|
|
||||||
use document_legacy::document::Document;
|
use document_legacy::document::Document;
|
||||||
use document_legacy::layers::layer_layer::LayerLayer;
|
use document_legacy::layers::layer_layer::LayerLayer;
|
||||||
@@ -84,6 +85,8 @@ pub struct FrontendNode {
|
|||||||
pub position: (i32, i32),
|
pub position: (i32, i32),
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
pub previewed: bool,
|
pub previewed: bool,
|
||||||
|
#[serde(rename = "thumbnailSvg")]
|
||||||
|
pub thumbnail_svg: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// (link_start, link_end, link_end_input_index)
|
// (link_start, link_end, link_end_input_index)
|
||||||
@@ -254,9 +257,11 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_graph(network: &NodeNetwork, responses: &mut VecDeque<Message>) {
|
fn send_graph(network: &NodeNetwork, executor: &NodeGraphExecutor, layer_path: &Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
|
||||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||||
|
|
||||||
|
let layer_id = layer_path.as_ref().and_then(|path| path.last().copied());
|
||||||
|
|
||||||
// List of links in format (link_start, link_end, link_end_input_index)
|
// List of links in format (link_start, link_end, link_end_input_index)
|
||||||
let links = network
|
let links = network
|
||||||
.nodes
|
.nodes
|
||||||
@@ -288,37 +293,49 @@ impl NodeGraphMessageHandler {
|
|||||||
warn!("Node '{}' does not exist in library", node.name);
|
warn!("Node '{}' does not exist in library", node.name);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let primary_input = node
|
||||||
|
.inputs
|
||||||
|
.first()
|
||||||
|
.filter(|input| input.is_exposed())
|
||||||
|
.and_then(|_| node_type.inputs.get(0))
|
||||||
|
.map(|input_type| input_type.data_type);
|
||||||
|
let exposed_inputs = node
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.zip(node_type.inputs.iter())
|
||||||
|
.skip(1)
|
||||||
|
.filter(|(input, _)| input.is_exposed())
|
||||||
|
.map(|(_, input_type)| NodeGraphInput {
|
||||||
|
data_type: input_type.data_type,
|
||||||
|
name: input_type.name.to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let outputs = node_type
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.map(|output_type| NodeGraphOutput {
|
||||||
|
data_type: output_type.data_type,
|
||||||
|
name: output_type.name.to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let thumbnail_svg = layer_id
|
||||||
|
.and_then(|layer_id| executor.thumbnails.get(&layer_id))
|
||||||
|
.and_then(|layer| layer.get(id))
|
||||||
|
.map(|svg| svg.to_string());
|
||||||
|
|
||||||
nodes.push(FrontendNode {
|
nodes.push(FrontendNode {
|
||||||
id: *id,
|
id: *id,
|
||||||
display_name: node.name.clone(),
|
display_name: node.name.clone(),
|
||||||
primary_input: node
|
primary_input,
|
||||||
.inputs
|
exposed_inputs,
|
||||||
.first()
|
outputs,
|
||||||
.filter(|input| input.is_exposed())
|
|
||||||
.and_then(|_| node_type.inputs.get(0))
|
|
||||||
.map(|input_type| input_type.data_type),
|
|
||||||
exposed_inputs: node
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.zip(node_type.inputs.iter())
|
|
||||||
.skip(1)
|
|
||||||
.filter(|(input, _)| input.is_exposed())
|
|
||||||
.map(|(_, input_type)| NodeGraphInput {
|
|
||||||
data_type: input_type.data_type,
|
|
||||||
name: input_type.name.to_string(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
outputs: node_type
|
|
||||||
.outputs
|
|
||||||
.iter()
|
|
||||||
.map(|output_type| NodeGraphOutput {
|
|
||||||
data_type: output_type.data_type,
|
|
||||||
name: output_type.name.to_string(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
position: node.metadata.position.into(),
|
position: node.metadata.position.into(),
|
||||||
previewed: network.outputs_contain(*id),
|
previewed: network.outputs_contain(*id),
|
||||||
disabled: network.disabled.contains(id),
|
disabled: network.disabled.contains(id),
|
||||||
|
thumbnail_svg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
|
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
|
||||||
@@ -405,9 +422,9 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &[LayerId]>)> for NodeGraphMessageHandler {
|
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> for NodeGraphMessageHandler {
|
||||||
#[remain::check]
|
#[remain::check]
|
||||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, _selected): (&mut Document, &mut dyn Iterator<Item = &[LayerId]>)) {
|
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor): (&mut Document, &NodeGraphExecutor)) {
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
NodeGraphMessage::CloseNodeGraph => {
|
NodeGraphMessage::CloseNodeGraph => {
|
||||||
@@ -536,7 +553,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(network) = self.get_active_network(document) {
|
if let Some(network) = self.get_active_network(document) {
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
}
|
}
|
||||||
self.collect_nested_addresses(document, responses);
|
self.collect_nested_addresses(document, responses);
|
||||||
self.update_selected(document, responses);
|
self.update_selected(document, responses);
|
||||||
@@ -561,7 +578,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
|
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
self.update_selected(document, responses);
|
self.update_selected(document, responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +588,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
self.nested_path.pop();
|
self.nested_path.pop();
|
||||||
}
|
}
|
||||||
if let Some(network) = self.get_active_network(document) {
|
if let Some(network) = self.get_active_network(document) {
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
}
|
}
|
||||||
self.collect_nested_addresses(document, responses);
|
self.collect_nested_addresses(document, responses);
|
||||||
self.update_selected(document, responses);
|
self.update_selected(document, responses);
|
||||||
@@ -622,7 +639,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
node.metadata.position += IVec2::new(displacement_x, displacement_y)
|
node.metadata.position += IVec2::new(displacement_x, displacement_y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::OpenNodeGraph { layer_path } => {
|
NodeGraphMessage::OpenNodeGraph { layer_path } => {
|
||||||
self.layer_path = Some(layer_path);
|
self.layer_path = Some(layer_path);
|
||||||
@@ -630,7 +647,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
if let Some(network) = self.get_active_network(document) {
|
if let Some(network) = self.get_active_network(document) {
|
||||||
self.selected_nodes.clear();
|
self.selected_nodes.clear();
|
||||||
|
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
|
|
||||||
let node_types = document_node_types::collect_node_types();
|
let node_types = document_node_types::collect_node_types();
|
||||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||||
@@ -689,7 +706,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
}
|
}
|
||||||
NodeGraphMessage::SendGraph { should_rerender } => {
|
NodeGraphMessage::SendGraph { should_rerender } => {
|
||||||
if let Some(network) = self.get_active_network(document) {
|
if let Some(network) = self.get_active_network(document) {
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
if should_rerender {
|
if should_rerender {
|
||||||
if let Some(layer_path) = self.layer_path.clone() {
|
if let Some(layer_path) = self.layer_path.clone() {
|
||||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||||
@@ -816,7 +833,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
.disabled
|
.disabled
|
||||||
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
||||||
}
|
}
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
|
|
||||||
// Only generate node graph if one of the selected nodes is connected to the output
|
// Only generate node graph if one of the selected nodes is connected to the output
|
||||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) {
|
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) {
|
||||||
@@ -842,7 +859,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||||
}
|
}
|
||||||
self.update_selection_action_buttons(document, responses);
|
self.update_selection_action_buttons(document, responses);
|
||||||
if let Some(layer_path) = self.layer_path.clone() {
|
if let Some(layer_path) = self.layer_path.clone() {
|
||||||
|
|||||||
+36
-4
@@ -121,7 +121,40 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Layer",
|
name: "Layer",
|
||||||
category: "General",
|
category: "General",
|
||||||
identifier: NodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
|
inputs: vec![0; 8],
|
||||||
|
outputs: vec![NodeOutput::new(1, 0)],
|
||||||
|
nodes: [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::Network(concrete!(graphene_core::vector::VectorData)),
|
||||||
|
NodeInput::Network(concrete!(String)),
|
||||||
|
NodeInput::Network(concrete!(BlendMode)),
|
||||||
|
NodeInput::Network(concrete!(f32)),
|
||||||
|
NodeInput::Network(concrete!(bool)),
|
||||||
|
NodeInput::Network(concrete!(bool)),
|
||||||
|
NodeInput::Network(concrete!(bool)),
|
||||||
|
NodeInput::Network(concrete!(graphene_core::GraphicGroup)),
|
||||||
|
],
|
||||||
|
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// The monitor node is used to display a thumbnail in the UI.
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::node(0, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||||
DocumentInputType::value("Name", TaggedValue::String(String::new()), false),
|
DocumentInputType::value("Name", TaggedValue::String(String::new()), false),
|
||||||
@@ -528,7 +561,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
outputs: vec![NodeOutput::new(1, 0)],
|
outputs: vec![NodeOutput::new(1, 0)],
|
||||||
nodes: vec![
|
nodes: [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -548,8 +581,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into(),
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||||
|
|||||||
+2
-2
@@ -727,12 +727,12 @@ pub fn adjust_selective_color_properties(document_node: &DocumentNode, node_id:
|
|||||||
} = &document_node.inputs[colors_index]
|
} = &document_node.inputs[colors_index]
|
||||||
{
|
{
|
||||||
use SelectiveColorChoice::*;
|
use SelectiveColorChoice::*;
|
||||||
let entries = [vec![Reds, Yellows, Greens, Cyans, Blues, Magentas], vec![Whites, Neutrals, Blacks]]
|
let entries = [[Reds, Yellows, Greens, Cyans, Blues, Magentas].as_slice(), [Whites, Neutrals, Blacks].as_slice()]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|section| {
|
.map(|section| {
|
||||||
section
|
section
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|choice| DropdownEntryData::new(choice.to_string()).on_update(update_value(move |_| TaggedValue::SelectiveColorChoice(choice), node_id, colors_index)))
|
.map(|choice| DropdownEntryData::new(choice.to_string()).on_update(update_value(move |_| TaggedValue::SelectiveColorChoice(*choice), node_id, colors_index)))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ pub enum PortfolioMessage {
|
|||||||
SetImageBlobUrl {
|
SetImageBlobUrl {
|
||||||
document_id: u64,
|
document_id: u64,
|
||||||
layer_path: Vec<LayerId>,
|
layer_path: Vec<LayerId>,
|
||||||
|
node_id: Option<NodeId>,
|
||||||
blob_url: String,
|
blob_url: String,
|
||||||
resolution: (f64, f64),
|
resolution: (f64, f64),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -503,9 +503,15 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||||||
PortfolioMessage::SetImageBlobUrl {
|
PortfolioMessage::SetImageBlobUrl {
|
||||||
document_id,
|
document_id,
|
||||||
layer_path,
|
layer_path,
|
||||||
|
node_id,
|
||||||
blob_url,
|
blob_url,
|
||||||
resolution,
|
resolution,
|
||||||
} => {
|
} => {
|
||||||
|
if let (Some(layer_id), Some(node_id)) = (layer_path.last().copied(), node_id) {
|
||||||
|
self.executor.insert_thumbnail_blob_url(blob_url, layer_id, node_id, responses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let message = DocumentMessage::SetImageBlobUrl {
|
let message = DocumentMessage::SetImageBlobUrl {
|
||||||
layer_path,
|
layer_path,
|
||||||
blob_url,
|
blob_url,
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ use crate::messages::prelude::*;
|
|||||||
use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType};
|
use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType};
|
||||||
use document_legacy::{LayerId, Operation};
|
use document_legacy::{LayerId, Operation};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||||
use graph_craft::executor::Compiler;
|
use graph_craft::executor::Compiler;
|
||||||
|
use graph_craft::imaginate_input::*;
|
||||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||||
use graphene_core::raster::{Image, ImageFrame};
|
use graphene_core::raster::{Image, ImageFrame};
|
||||||
|
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
||||||
|
use graphene_core::vector::style::ViewMode;
|
||||||
use graphene_core::vector::VectorData;
|
use graphene_core::vector::VectorData;
|
||||||
use graphene_core::{Color, EditorApi};
|
use graphene_core::{Color, EditorApi};
|
||||||
use interpreted_executor::executor::DynamicExecutor;
|
use interpreted_executor::executor::DynamicExecutor;
|
||||||
@@ -24,16 +27,33 @@ pub struct NodeGraphExecutor {
|
|||||||
pub(crate) executor: DynamicExecutor,
|
pub(crate) executor: DynamicExecutor,
|
||||||
// TODO: This is a memory leak since layers are never removed
|
// TODO: This is a memory leak since layers are never removed
|
||||||
pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>,
|
pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>,
|
||||||
|
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_imaginate_index(name: &str) -> usize {
|
||||||
|
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
|
||||||
|
IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeGraphExecutor {
|
impl NodeGraphExecutor {
|
||||||
/// Execute the network by flattening it and creating a borrow stack.
|
/// Wraps a network in a scope and returns the new network and the paths to the monitor nodes.
|
||||||
fn execute_network<'a>(&'a mut self, network: NodeNetwork, editor_api: EditorApi<'a>) -> Result<Box<dyn dyn_any::DynAny + 'a>, String> {
|
fn wrap_network(network: NodeNetwork) -> (NodeNetwork, Vec<Vec<NodeId>>) {
|
||||||
let mut scoped_network = wrap_network_in_scope(network);
|
let mut scoped_network = wrap_network_in_scope(network);
|
||||||
|
|
||||||
|
scoped_network.generate_node_paths(&[]);
|
||||||
|
let monitor_nodes = scoped_network
|
||||||
|
.recursive_nodes()
|
||||||
|
.filter(|(node, _, _)| node.implementation == DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"))
|
||||||
|
.map(|(_, _, path)| path)
|
||||||
|
.collect();
|
||||||
scoped_network.duplicate_outputs(&mut generate_uuid);
|
scoped_network.duplicate_outputs(&mut generate_uuid);
|
||||||
scoped_network.remove_dead_nodes();
|
scoped_network.remove_dead_nodes();
|
||||||
|
|
||||||
|
(scoped_network, monitor_nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the network by flattening it and creating a borrow stack.
|
||||||
|
fn execute_network<'a>(&'a mut self, scoped_network: NodeNetwork, editor_api: EditorApi<'a>) -> Result<Box<dyn dyn_any::DynAny + 'a>, String> {
|
||||||
// We assume only one output
|
// We assume only one output
|
||||||
assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
|
assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
|
||||||
let c = Compiler {};
|
let c = Compiler {};
|
||||||
@@ -100,6 +120,7 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (network, _) = Self::wrap_network(network);
|
||||||
let boxed = self.execute_network(network, editor_api.into_owned())?;
|
let boxed = self.execute_network(network, editor_api.into_owned())?;
|
||||||
|
|
||||||
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
||||||
@@ -125,73 +146,61 @@ impl NodeGraphExecutor {
|
|||||||
Ok::<_, String>((image_data, size))
|
Ok::<_, String>((image_data, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_imaginate(
|
fn imaginate_parameters(&mut self, network: &NodeNetwork, node_path: &[LayerId], resolution: DVec2, editor_api: &EditorApi) -> Result<ImaginateGenerationParameters, String> {
|
||||||
&mut self,
|
let get = get_imaginate_index;
|
||||||
network: NodeNetwork,
|
Ok(ImaginateGenerationParameters {
|
||||||
imaginate_node_path: Vec<NodeId>,
|
seed: self.compute_input::<f64>(network, node_path, get("Seed"), Cow::Borrowed(editor_api))? as u64,
|
||||||
(document, document_id): (&mut DocumentMessageHandler, u64),
|
|
||||||
layer_path: Vec<LayerId>,
|
|
||||||
mut editor_api: EditorApi<'_>,
|
|
||||||
(preferences, persistent_data): (&PreferencesMessageHandler, &PersistentData),
|
|
||||||
) -> Result<Message, String> {
|
|
||||||
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
|
|
||||||
use graph_craft::imaginate_input::*;
|
|
||||||
|
|
||||||
let image = editor_api.image_frame.take();
|
|
||||||
|
|
||||||
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
|
||||||
|
|
||||||
// Get the node graph layer
|
|
||||||
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
|
|
||||||
let transform = layer.transform;
|
|
||||||
|
|
||||||
let resolution: Option<glam::DVec2> = self.compute_input(&network, &imaginate_node_path, get("Resolution"), Cow::Borrowed(&editor_api))?;
|
|
||||||
let resolution = resolution.unwrap_or_else(|| {
|
|
||||||
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
|
|
||||||
DVec2::new(x as f64, y as f64)
|
|
||||||
});
|
|
||||||
|
|
||||||
let parameters = ImaginateGenerationParameters {
|
|
||||||
seed: self.compute_input::<f64>(&network, &imaginate_node_path, get("Seed"), Cow::Borrowed(&editor_api))? as u64,
|
|
||||||
resolution: resolution.as_uvec2().into(),
|
resolution: resolution.as_uvec2().into(),
|
||||||
samples: self.compute_input::<f64>(&network, &imaginate_node_path, get("Samples"), Cow::Borrowed(&editor_api))? as u32,
|
samples: self.compute_input::<f64>(network, node_path, get("Samples"), Cow::Borrowed(editor_api))? as u32,
|
||||||
sampling_method: self
|
sampling_method: self
|
||||||
.compute_input::<ImaginateSamplingMethod>(&network, &imaginate_node_path, get("Sampling Method"), Cow::Borrowed(&editor_api))?
|
.compute_input::<ImaginateSamplingMethod>(network, node_path, get("Sampling Method"), Cow::Borrowed(editor_api))?
|
||||||
.api_value()
|
.api_value()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
text_guidance: self.compute_input(&network, &imaginate_node_path, get("Prompt Guidance"), Cow::Borrowed(&editor_api))?,
|
text_guidance: self.compute_input(network, node_path, get("Prompt Guidance"), Cow::Borrowed(editor_api))?,
|
||||||
text_prompt: self.compute_input(&network, &imaginate_node_path, get("Prompt"), Cow::Borrowed(&editor_api))?,
|
text_prompt: self.compute_input(network, node_path, get("Prompt"), Cow::Borrowed(editor_api))?,
|
||||||
negative_prompt: self.compute_input(&network, &imaginate_node_path, get("Negative Prompt"), Cow::Borrowed(&editor_api))?,
|
negative_prompt: self.compute_input(network, node_path, get("Negative Prompt"), Cow::Borrowed(editor_api))?,
|
||||||
image_creativity: Some(self.compute_input::<f64>(&network, &imaginate_node_path, get("Image Creativity"), Cow::Borrowed(&editor_api))? / 100.),
|
image_creativity: Some(self.compute_input::<f64>(network, node_path, get("Image Creativity"), Cow::Borrowed(editor_api))? / 100.),
|
||||||
restore_faces: self.compute_input(&network, &imaginate_node_path, get("Improve Faces"), Cow::Borrowed(&editor_api))?,
|
restore_faces: self.compute_input(network, node_path, get("Improve Faces"), Cow::Borrowed(editor_api))?,
|
||||||
tiling: self.compute_input(&network, &imaginate_node_path, get("Tiling"), Cow::Borrowed(&editor_api))?,
|
tiling: self.compute_input(network, node_path, get("Tiling"), Cow::Borrowed(editor_api))?,
|
||||||
};
|
})
|
||||||
let use_base_image = self.compute_input::<bool>(&network, &imaginate_node_path, get("Adapt Input Image"), Cow::Borrowed(&editor_api))?;
|
}
|
||||||
|
|
||||||
editor_api.image_frame = image;
|
fn imaginate_base_image(&mut self, network: &NodeNetwork, imaginate_node_path: &[LayerId], resolution: DVec2, editor_api: &EditorApi) -> Result<Option<(ImaginateBaseImage, DAffine2)>, String> {
|
||||||
|
let use_base_image = self.compute_input::<bool>(&network, &imaginate_node_path, get_imaginate_index("Adapt Input Image"), Cow::Borrowed(editor_api))?;
|
||||||
let input_image_frame: Option<ImageFrame<Color>> = if use_base_image {
|
let input_image_frame: Option<ImageFrame<Color>> = if use_base_image {
|
||||||
Some(self.compute_input::<ImageFrame<Color>>(&network, &imaginate_node_path, get("Input Image"), Cow::Borrowed(&editor_api))?)
|
Some(self.compute_input::<ImageFrame<Color>>(&network, &imaginate_node_path, get_imaginate_index("Input Image"), Cow::Borrowed(editor_api))?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_transform = input_image_frame.as_ref().map(|image_frame| image_frame.transform);
|
let base_image = if let Some(ImageFrame { image, transform }) = input_image_frame {
|
||||||
let base_image = if let Some(ImageFrame { image, .. }) = input_image_frame {
|
|
||||||
// Only use if has size
|
// Only use if has size
|
||||||
if image.width > 0 && image.height > 0 {
|
if image.width > 0 && image.height > 0 {
|
||||||
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
|
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
|
||||||
let size = DVec2::new(size.0 as f64, size.1 as f64);
|
let size = DVec2::new(size.0 as f64, size.1 as f64);
|
||||||
let mime = "image/png".to_string();
|
let mime = "image/png".to_string();
|
||||||
Some(ImaginateBaseImage { image_data, size, mime })
|
Some((ImaginateBaseImage { image_data, size, mime }, transform))
|
||||||
} else {
|
} else {
|
||||||
|
info!("Base image is input but has no size.");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
Ok(base_image)
|
||||||
|
}
|
||||||
|
|
||||||
let mask_image = if let Some(transform) = image_transform {
|
fn imaginate_mask_image(
|
||||||
let mask_path: Option<Vec<LayerId>> = self.compute_input(&network, &imaginate_node_path, get("Masking Layer"), Cow::Borrowed(&editor_api))?;
|
&mut self,
|
||||||
|
network: &NodeNetwork,
|
||||||
|
node_path: &[LayerId],
|
||||||
|
editor_api: &EditorApi<'_>,
|
||||||
|
image_transform: Option<DAffine2>,
|
||||||
|
document: &mut DocumentMessageHandler,
|
||||||
|
persistent_data: &PersistentData,
|
||||||
|
) -> Result<Option<ImaginateMaskImage>, String> {
|
||||||
|
if let Some(transform) = image_transform {
|
||||||
|
let mask_path: Option<Vec<LayerId>> = self.compute_input(&network, &node_path, get_imaginate_index("Masking Layer"), Cow::Borrowed(&editor_api))?;
|
||||||
|
|
||||||
// Calculate the size of the frame
|
// Calculate the size of the frame
|
||||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||||
@@ -214,22 +223,53 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.restore_document_transform(old_transforms);
|
document.restore_document_transform(old_transforms);
|
||||||
mask_image
|
Ok(mask_image)
|
||||||
} else {
|
} else {
|
||||||
None
|
Ok(None)
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_imaginate(
|
||||||
|
&mut self,
|
||||||
|
network: NodeNetwork,
|
||||||
|
imaginate_node_path: Vec<NodeId>,
|
||||||
|
(document, document_id): (&mut DocumentMessageHandler, u64),
|
||||||
|
layer_path: Vec<LayerId>,
|
||||||
|
mut editor_api: EditorApi<'_>,
|
||||||
|
(preferences, persistent_data): (&PreferencesMessageHandler, &PersistentData),
|
||||||
|
) -> Result<Message, String> {
|
||||||
|
let image = editor_api.image_frame.take();
|
||||||
|
|
||||||
|
// Get the node graph layer
|
||||||
|
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
|
||||||
|
let transform = layer.transform;
|
||||||
|
|
||||||
|
let resolution: Option<glam::DVec2> = self.compute_input(&network, &imaginate_node_path, get_imaginate_index("Resolution"), Cow::Borrowed(&editor_api))?;
|
||||||
|
let resolution = resolution.unwrap_or_else(|| {
|
||||||
|
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
|
||||||
|
DVec2::new(x as f64, y as f64)
|
||||||
|
});
|
||||||
|
|
||||||
|
let parameters = self.imaginate_parameters(&network, &imaginate_node_path, resolution, &editor_api)?;
|
||||||
|
|
||||||
|
editor_api.image_frame = image;
|
||||||
|
let base = self.imaginate_base_image(&network, &imaginate_node_path, resolution, &editor_api)?;
|
||||||
|
let image_transform = base.as_ref().map(|base| base.1);
|
||||||
|
let base_image = base.map(|base| base.0);
|
||||||
|
|
||||||
|
let mask_image = self.imaginate_mask_image(&network, &imaginate_node_path, &editor_api, image_transform, document, persistent_data)?;
|
||||||
|
|
||||||
Ok(FrontendMessage::TriggerImaginateGenerate {
|
Ok(FrontendMessage::TriggerImaginateGenerate {
|
||||||
parameters: Box::new(parameters),
|
parameters: Box::new(parameters),
|
||||||
base_image: base_image.map(Box::new),
|
base_image: base_image.map(Box::new),
|
||||||
mask_image: mask_image.map(Box::new),
|
mask_image: mask_image.map(Box::new),
|
||||||
mask_paint_mode: if self.compute_input::<bool>(&network, &imaginate_node_path, get("Inpaint"), Cow::Borrowed(&editor_api))? {
|
mask_paint_mode: if self.compute_input::<bool>(&network, &imaginate_node_path, get_imaginate_index("Inpaint"), Cow::Borrowed(&editor_api))? {
|
||||||
ImaginateMaskPaintMode::Inpaint
|
ImaginateMaskPaintMode::Inpaint
|
||||||
} else {
|
} else {
|
||||||
ImaginateMaskPaintMode::Outpaint
|
ImaginateMaskPaintMode::Outpaint
|
||||||
},
|
},
|
||||||
mask_blur_px: self.compute_input::<f64>(&network, &imaginate_node_path, get("Mask Blur"), Cow::Borrowed(&editor_api))? as u32,
|
mask_blur_px: self.compute_input::<f64>(&network, &imaginate_node_path, get_imaginate_index("Mask Blur"), Cow::Borrowed(&editor_api))? as u32,
|
||||||
imaginate_mask_starting_fill: self.compute_input(&network, &imaginate_node_path, get("Mask Starting Fill"), Cow::Borrowed(&editor_api))?,
|
imaginate_mask_starting_fill: self.compute_input(&network, &imaginate_node_path, get_imaginate_index("Mask Starting Fill"), Cow::Borrowed(&editor_api))?,
|
||||||
hostname: preferences.imaginate_server_hostname.clone(),
|
hostname: preferences.imaginate_server_hostname.clone(),
|
||||||
refresh_frequency: preferences.imaginate_refresh_frequency,
|
refresh_frequency: preferences.imaginate_refresh_frequency,
|
||||||
document_id,
|
document_id,
|
||||||
@@ -239,6 +279,22 @@ impl NodeGraphExecutor {
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a new [`FrontendImageData`] from the [`Image`].
|
||||||
|
fn to_frontend_image_data(image: Image<Color>, transform: Option<[f64; 6]>, layer_path: &[LayerId], node_id: Option<u64>, resize: Option<DVec2>) -> Result<FrontendImageData, String> {
|
||||||
|
let (image_data, _size) = Self::encode_img(image, resize, image::ImageOutputFormat::Bmp)?;
|
||||||
|
|
||||||
|
let mime = "image/bmp".to_string();
|
||||||
|
let image_data = std::sync::Arc::new(image_data);
|
||||||
|
|
||||||
|
Ok(FrontendImageData {
|
||||||
|
path: layer_path.to_vec(),
|
||||||
|
node_id,
|
||||||
|
image_data,
|
||||||
|
mime,
|
||||||
|
transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluates a node graph, computing either the Imaginate node or the entire graph
|
/// Evaluates a node graph, computing either the Imaginate node or the entire graph
|
||||||
pub fn evaluate_node_graph(
|
pub fn evaluate_node_graph(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -276,6 +332,7 @@ impl NodeGraphExecutor {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Execute the node graph
|
// Execute the node graph
|
||||||
|
let (network, monitor_nodes) = Self::wrap_network(network);
|
||||||
let boxed_node_graph_output = self.execute_network(network, editor_api)?;
|
let boxed_node_graph_output = self.execute_network(network, editor_api)?;
|
||||||
|
|
||||||
// Check if the output is vector data
|
// Check if the output is vector data
|
||||||
@@ -303,29 +360,74 @@ impl NodeGraphExecutor {
|
|||||||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update the image data
|
let image_data = vec![Self::to_frontend_image_data(image, transform, &layer_path, None, None)?];
|
||||||
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
|
|
||||||
|
|
||||||
let mime = "image/bmp".to_string();
|
|
||||||
let image_data = std::sync::Arc::new(image_data);
|
|
||||||
let image_data = vec![FrontendImageData {
|
|
||||||
path: layer_path.clone(),
|
|
||||||
image_data,
|
|
||||||
mime,
|
|
||||||
transform,
|
|
||||||
}];
|
|
||||||
responses.add(FrontendMessage::UpdateImageData { document_id, image_data });
|
responses.add(FrontendMessage::UpdateImageData { document_id, image_data });
|
||||||
}
|
}
|
||||||
} else if core::any::TypeId::of::<graphene_core::Artboard>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
|
} else if core::any::TypeId::of::<graphene_core::Artboard>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
|
||||||
let artboard: graphene_core::Artboard = dyn_any::downcast(boxed_node_graph_output).map(|artboard| *artboard)?;
|
let artboard: graphene_core::Artboard = dyn_any::downcast(boxed_node_graph_output).map(|artboard| *artboard)?;
|
||||||
info!("{artboard:#?}");
|
info!("{artboard:#?}");
|
||||||
|
self.update_thumbnails(&layer_path, monitor_nodes, responses);
|
||||||
|
|
||||||
return Err(format!("Artboard (see console)"));
|
return Err(format!("Artboard (see console)"));
|
||||||
} else if core::any::TypeId::of::<graphene_core::GraphicGroup>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
|
} else if core::any::TypeId::of::<graphene_core::GraphicGroup>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
|
||||||
let graphic_group: graphene_core::GraphicGroup = dyn_any::downcast(boxed_node_graph_output).map(|graphic| *graphic)?;
|
let graphic_group: graphene_core::GraphicGroup = dyn_any::downcast(boxed_node_graph_output).map(|graphic| *graphic)?;
|
||||||
info!("{graphic_group:#?}");
|
info!("{graphic_group:#?}");
|
||||||
|
self.update_thumbnails(&layer_path, monitor_nodes, responses);
|
||||||
|
|
||||||
return Err(format!("Graphic group (see console)"));
|
return Err(format!("Graphic group (see console)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||||
|
pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: Vec<Vec<u64>>, responses: &mut VecDeque<Message>) {
|
||||||
|
let mut thumbnails_changed: bool = false;
|
||||||
|
let mut image_data: Vec<_> = Vec::new();
|
||||||
|
for node_path in monitor_nodes {
|
||||||
|
let Some(value) = self.executor.introspect(&node_path).flatten() else {
|
||||||
|
warn!("No introspect");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(graphic_group) = value.downcast_ref::<graphene_core::GraphicGroup>() else {
|
||||||
|
warn!("Not graphic");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
use graphene_core::renderer::*;
|
||||||
|
let bounds = graphic_group.bounding_box(DAffine2::IDENTITY);
|
||||||
|
let render_params = RenderParams::new(ViewMode::Normal, bounds, true);
|
||||||
|
let mut render = SvgRender::new();
|
||||||
|
graphic_group.render_svg(&mut render, &render_params);
|
||||||
|
let [min, max] = bounds.unwrap_or_default();
|
||||||
|
render.format_svg(min, max);
|
||||||
|
info!("SVG {}", render.svg);
|
||||||
|
|
||||||
|
if let (Some(layer_id), Some(node_id)) = (layer_path.last().copied(), node_path.get(node_path.len() - 2).copied()) {
|
||||||
|
let old_thumbnail = self.thumbnails.entry(layer_id).or_default().entry(node_id).or_default();
|
||||||
|
if *old_thumbnail != render.svg {
|
||||||
|
*old_thumbnail = render.svg;
|
||||||
|
thumbnails_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let resize = Some(DVec2::splat(100.));
|
||||||
|
let create_image_data = |(node_id, image)| Self::to_frontend_image_data(image, None, layer_path, Some(node_id), resize).ok();
|
||||||
|
image_data.extend(render.image_data.into_iter().filter_map(create_image_data))
|
||||||
|
}
|
||||||
|
if !image_data.is_empty() {
|
||||||
|
responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data });
|
||||||
|
} else if thumbnails_changed {
|
||||||
|
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a blob url for a thumbnail is loaded, update the state and the UI.
|
||||||
|
pub fn insert_thumbnail_blob_url(&mut self, blob_url: String, layer_id: LayerId, node_id: NodeId, responses: &mut VecDeque<Message>) {
|
||||||
|
if let Some(layer) = self.thumbnails.get_mut(&layer_id) {
|
||||||
|
if let Some(segment) = layer.values_mut().flat_map(|segments| segments.iter_mut()).find(|segment| **segment == SvgSegment::BlobUrl(node_id)) {
|
||||||
|
*segment = SvgSegment::String(blob_url);
|
||||||
|
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ fn handle_message(message: String) -> String {
|
|||||||
images.insert(format!("{:?}_{}", &image.path, document_id), image);
|
images.insert(format!("{:?}_{}", &image.path, document_id), image);
|
||||||
stub_data.push(FrontendImageData {
|
stub_data.push(FrontendImageData {
|
||||||
path,
|
path,
|
||||||
|
node_id: None,
|
||||||
mime,
|
mime,
|
||||||
image_data: Arc::new(Vec::new()),
|
image_data: Arc::new(Vec::new()),
|
||||||
transform,
|
transform,
|
||||||
|
|||||||
@@ -548,7 +548,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<IconLabel icon={nodeIcon(node.displayName)} />
|
{#if node.thumbnailSvg}
|
||||||
|
{@html node.thumbnailSvg}
|
||||||
|
{:else}
|
||||||
|
<IconLabel icon={nodeIcon(node.displayName)} />
|
||||||
|
{/if}
|
||||||
<TextLabel>{node.displayName}</TextLabel>
|
<TextLabel>{node.displayName}</TextLabel>
|
||||||
</div>
|
</div>
|
||||||
{#if exposedInputsOutputs.length > 0}
|
{#if exposedInputsOutputs.length > 0}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export function createPortfolioState(editor: Editor) {
|
|||||||
image.src = blobURL;
|
image.src = blobURL;
|
||||||
await image.decode();
|
await image.decode();
|
||||||
|
|
||||||
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, blobURL, image.naturalWidth, image.naturalHeight, element.transform);
|
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, element.nodeId, blobURL, image.naturalWidth, image.naturalHeight, element.transform);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(TriggerRasterizeRegionBelowLayer, async (triggerRasterizeRegionBelowLayer) => {
|
editor.subscriptions.subscribeJsMessage(TriggerRasterizeRegionBelowLayer, async (triggerRasterizeRegionBelowLayer) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type Editor = Readonly<ReturnType<typeof createEditor>>;
|
|||||||
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
|
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
|
||||||
let wasmImport: WebAssembly.Memory | undefined;
|
let wasmImport: WebAssembly.Memory | undefined;
|
||||||
|
|
||||||
export async function updateImage(path: BigUint64Array, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint): Promise<void> {
|
export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint): Promise<void> {
|
||||||
const blob = new Blob([imageData], { type: mime });
|
const blob = new Blob([imageData], { type: mime });
|
||||||
|
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
@@ -21,10 +21,10 @@ export async function updateImage(path: BigUint64Array, mime: string, imageData:
|
|||||||
image.src = blobURL;
|
image.src = blobURL;
|
||||||
await image.decode();
|
await image.decode();
|
||||||
|
|
||||||
window["editorInstance"]?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight, transform);
|
window["editorInstance"]?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchImage(path: BigUint64Array, mime: string, documentId: bigint, url: string): Promise<void> {
|
export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: string, documentId: bigint, url: string): Promise<void> {
|
||||||
const data = await fetch(url);
|
const data = await fetch(url);
|
||||||
const blob = await data.blob();
|
const blob = await data.blob();
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export async function fetchImage(path: BigUint64Array, mime: string, documentId:
|
|||||||
image.src = blobURL;
|
image.src = blobURL;
|
||||||
await image.decode();
|
await image.decode();
|
||||||
|
|
||||||
window["editorInstance"]?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight, undefined);
|
window["editorInstance"]?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
|
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ export class FrontendNode {
|
|||||||
readonly previewed!: boolean;
|
readonly previewed!: boolean;
|
||||||
|
|
||||||
readonly disabled!: boolean;
|
readonly disabled!: boolean;
|
||||||
|
|
||||||
|
readonly thumbnailSvg!: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNodeLink {
|
export class FrontendNodeLink {
|
||||||
@@ -771,6 +773,8 @@ export type LayerTypeData = {
|
|||||||
export class ImaginateImageData {
|
export class ImaginateImageData {
|
||||||
readonly path!: BigUint64Array;
|
readonly path!: BigUint64Array;
|
||||||
|
|
||||||
|
readonly nodeId!: bigint;
|
||||||
|
|
||||||
readonly mime!: string;
|
readonly mime!: string;
|
||||||
|
|
||||||
readonly imageData!: Uint8Array;
|
readonly imageData!: Uint8Array;
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ pub fn set_random_seed(seed: u64) {
|
|||||||
/// This avoids creating a json with a list millions of numbers long.
|
/// This avoids creating a json with a list millions of numbers long.
|
||||||
#[wasm_bindgen(module = "/../src/wasm-communication/editor.ts")]
|
#[wasm_bindgen(module = "/../src/wasm-communication/editor.ts")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn updateImage(path: Vec<u64>, mime: String, imageData: &[u8], transform: js_sys::Float64Array, document_id: u64);
|
fn updateImage(path: Vec<u64>, nodeId: Option<u64>, mime: String, imageData: &[u8], transform: js_sys::Float64Array, document_id: u64);
|
||||||
fn fetchImage(path: Vec<u64>, mime: String, document_id: u64, identifier: String);
|
fn fetchImage(path: Vec<u64>, nodeId: Option<u64>, mime: String, document_id: u64, identifier: String);
|
||||||
//fn dispatchTauri(message: String) -> String;
|
//fn dispatchTauri(message: String) -> String;
|
||||||
fn dispatchTauri(message: String);
|
fn dispatchTauri(message: String);
|
||||||
}
|
}
|
||||||
@@ -122,10 +122,13 @@ impl JsEditorHandle {
|
|||||||
} else {
|
} else {
|
||||||
js_sys::Float64Array::default()
|
js_sys::Float64Array::default()
|
||||||
};
|
};
|
||||||
updateImage(image.path, image.mime, &image.image_data, transform, document_id);
|
updateImage(image.path, image.node_id, image.mime, &image.image_data, transform, document_id);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "tauri")]
|
#[cfg(feature = "tauri")]
|
||||||
fetchImage(image.path.clone(), image.mime, document_id, format!("http://localhost:3001/image/{:?}_{}", &image.path, document_id));
|
{
|
||||||
|
let identifier = format!("http://localhost:3001/image/{:?}_{}", image.path, document_id);
|
||||||
|
fetchImage(image.path, image.node_id, image.mime, document_id, identifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -504,11 +507,12 @@ impl JsEditorHandle {
|
|||||||
|
|
||||||
/// Sends the blob URL generated by JS to the Image layer
|
/// Sends the blob URL generated by JS to the Image layer
|
||||||
#[wasm_bindgen(js_name = setImageBlobURL)]
|
#[wasm_bindgen(js_name = setImageBlobURL)]
|
||||||
pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec<LayerId>, blob_url: String, width: f64, height: f64, transform: Option<js_sys::Float64Array>) {
|
pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec<LayerId>, node_id: Option<NodeId>, blob_url: String, width: f64, height: f64, transform: Option<js_sys::Float64Array>) {
|
||||||
let resolution = (width, height);
|
let resolution = (width, height);
|
||||||
let message = PortfolioMessage::SetImageBlobUrl {
|
let message = PortfolioMessage::SetImageBlobUrl {
|
||||||
document_id,
|
document_id,
|
||||||
layer_path: layer_path.clone(),
|
layer_path: layer_path.clone(),
|
||||||
|
node_id,
|
||||||
blob_url,
|
blob_url,
|
||||||
resolution,
|
resolution,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use core::ops::{Deref, DerefMut};
|
|||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use node_macro::node_fn;
|
use node_macro::node_fn;
|
||||||
|
|
||||||
|
pub mod renderer;
|
||||||
|
|
||||||
/// A list of [`GraphicElement`]s
|
/// A list of [`GraphicElement`]s
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, Default)]
|
#[derive(Clone, Debug, Hash, PartialEq, DynAny, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
use crate::raster::{Image, ImageFrame};
|
||||||
|
use crate::{uuid::generate_uuid, vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup};
|
||||||
|
use quad::Quad;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
mod quad;
|
||||||
|
|
||||||
|
/// Mutable state used whilst rendering to an SVG
|
||||||
|
pub struct SvgRender {
|
||||||
|
pub svg: SvgSegmentList,
|
||||||
|
pub svg_defs: String,
|
||||||
|
pub transform: DAffine2,
|
||||||
|
pub image_data: Vec<(u64, Image<Color>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgRender {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
svg: SvgSegmentList::default(),
|
||||||
|
svg_defs: String::new(),
|
||||||
|
transform: DAffine2::IDENTITY,
|
||||||
|
image_data: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an outer `<svg />` tag with a `viewBox` and the `<defs />`
|
||||||
|
pub fn format_svg(&mut self, bounds_min: DVec2, bounds_max: DVec2) {
|
||||||
|
let (x, y) = bounds_min.into();
|
||||||
|
let (size_x, size_y) = (bounds_max - bounds_min).into();
|
||||||
|
let defs = &self.svg_defs;
|
||||||
|
let svg_header = format!(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{x} {y} {size_x} {size_y}"><defs>{defs}</defs>"#,);
|
||||||
|
self.svg.insert(0, svg_header.into());
|
||||||
|
self.svg.push("</svg>".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SvgRender {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static state used whilst rendering
|
||||||
|
pub struct RenderParams {
|
||||||
|
pub view_mode: crate::vector::style::ViewMode,
|
||||||
|
pub culling_bounds: Option<[DVec2; 2]>,
|
||||||
|
pub thumbnail: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderParams {
|
||||||
|
pub fn new(view_mode: crate::vector::style::ViewMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool) -> Self {
|
||||||
|
Self { view_mode, culling_bounds, thumbnail }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_transform_matrix(transform: DAffine2) -> String {
|
||||||
|
transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GraphicElementRendered {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicElementRendered for GraphicGroup {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
self.iter().for_each(|element| element.graphic_element_data.render_svg(render, render_params))
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||||
|
self.iter().filter_map(|element| element.graphic_element_data.bounding_box(transform)).reduce(Quad::combine_bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicElementRendered for VectorData {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
let layer_bounds = self.bounding_box().unwrap_or_default();
|
||||||
|
let transformed_bounds = self.bounding_box_with_transform(render.transform).unwrap_or_default();
|
||||||
|
|
||||||
|
render.svg.push("<path d=\"".into());
|
||||||
|
let mut path = String::new();
|
||||||
|
for subpath in &self.subpaths {
|
||||||
|
let _ = subpath.subpath_to_svg(&mut path, self.transform * render.transform);
|
||||||
|
}
|
||||||
|
render.svg.push(path.into());
|
||||||
|
render.svg.push("\"".into());
|
||||||
|
|
||||||
|
let style = self.style.render(render_params.view_mode, &mut render.svg_defs, render.transform, layer_bounds, transformed_bounds);
|
||||||
|
render.svg.push(style.into());
|
||||||
|
render.svg.push("/>".into());
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||||
|
self.bounding_box_with_transform(self.transform * transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicElementRendered for Artboard {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
self.graphic_group.render_svg(render, render_params)
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||||
|
let artboard_bounds = self.bounds.map(|[a, b]| (transform * Quad::from_box([a.as_dvec2(), b.as_dvec2()])).bounding_box());
|
||||||
|
[self.graphic_group.bounding_box(transform), artboard_bounds].into_iter().flatten().reduce(Quad::combine_bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicElementRendered for ImageFrame<Color> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
|
||||||
|
let transform: String = format_transform_matrix(self.transform * render.transform);
|
||||||
|
render
|
||||||
|
.svg
|
||||||
|
.push(format!(r#"<image width="1" height="1" preserveAspectRatio="none" transform="matrix({transform})" href=""#).into());
|
||||||
|
let uuid = generate_uuid();
|
||||||
|
render.svg.push(SvgSegment::BlobUrl(uuid));
|
||||||
|
render.svg.push("\" />".into());
|
||||||
|
render.image_data.push((uuid, self.image.clone()))
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||||
|
let transform = self.transform * transform;
|
||||||
|
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicElementRendered for GraphicElementData {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
match self {
|
||||||
|
GraphicElementData::VectorShape(vector_data) => vector_data.render_svg(render, render_params),
|
||||||
|
GraphicElementData::ImageFrame(image_frame) => image_frame.render_svg(render, render_params),
|
||||||
|
GraphicElementData::Text(_) => todo!("Render a text GraphicElementData"),
|
||||||
|
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
|
||||||
|
GraphicElementData::Artboard(artboard) => artboard.render_svg(render, render_params),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||||
|
match self {
|
||||||
|
GraphicElementData::VectorShape(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform),
|
||||||
|
GraphicElementData::ImageFrame(image_frame) => image_frame.bounding_box(transform),
|
||||||
|
GraphicElementData::Text(_) => todo!("Bounds of a text GraphicElementData"),
|
||||||
|
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform),
|
||||||
|
GraphicElementData::Artboard(artboard) => artboard.bounding_box(transform),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A segment of an svg string to allow for embedding blob urls
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SvgSegment {
|
||||||
|
Slice(&'static str),
|
||||||
|
String(String),
|
||||||
|
BlobUrl(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for SvgSegment {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for SvgSegment {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
Self::Slice(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of [`SvgSegment`]s.
|
||||||
|
///
|
||||||
|
/// Can be modified with `list.push("hello".into())`. Use `list.to_string()` to convert the segments into one string.
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SvgSegmentList(Vec<SvgSegment>);
|
||||||
|
|
||||||
|
impl core::ops::Deref for SvgSegmentList {
|
||||||
|
type Target = Vec<SvgSegment>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::ops::DerefMut for SvgSegmentList {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for SvgSegmentList {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
for segment in self.iter() {
|
||||||
|
f.write_str(match segment {
|
||||||
|
SvgSegment::Slice(x) => x,
|
||||||
|
SvgSegment::String(x) => x,
|
||||||
|
SvgSegment::BlobUrl(_) => "<!-- Blob url not yet loaded -->",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Copy)]
|
||||||
|
/// A quad defined by four vertices.
|
||||||
|
pub struct Quad([DVec2; 4]);
|
||||||
|
|
||||||
|
impl Quad {
|
||||||
|
/// Convert a box defined by two corner points to a quad.
|
||||||
|
pub fn from_box(bbox: [DVec2; 2]) -> Self {
|
||||||
|
let size = bbox[1] - bbox[0];
|
||||||
|
Self([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all the edges in the quad.
|
||||||
|
pub fn lines_glam(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||||
|
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
|
||||||
|
.into_iter()
|
||||||
|
.map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a [crate::vector::Subpath] of the quad
|
||||||
|
pub fn subpath(&self) -> crate::vector::Subpath {
|
||||||
|
crate::vector::Subpath::from_points(self.0.into_iter(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the axis aligned bounding box of the quad
|
||||||
|
pub fn bounding_box(&self) -> [DVec2; 2] {
|
||||||
|
[
|
||||||
|
self.0.into_iter().reduce(|a, b| a.min(b)).unwrap_or_default(),
|
||||||
|
self.0.into_iter().reduce(|a, b| a.max(b)).unwrap_or_default(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the center of a quad
|
||||||
|
pub fn center(&self) -> DVec2 {
|
||||||
|
self.0.iter().sum::<DVec2>() / 4.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take the outside bounds of two axis aligned rectangles, which are defined by two corner points.
|
||||||
|
pub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] {
|
||||||
|
[a[0].min(b[0]), a[1].max(b[1])]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::Mul<Quad> for DAffine2 {
|
||||||
|
type Output = Quad;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Quad) -> Self::Output {
|
||||||
|
Quad(rhs.0.map(|point| self.transform_point2(point)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ mod base64_serde {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Default, specta::Type)]
|
#[derive(Clone, PartialEq, Default, specta::Type)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Image<P: Pixel> {
|
pub struct Image<P: Pixel> {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
@@ -46,6 +46,17 @@ pub struct Image<P: Pixel> {
|
|||||||
pub data: Vec<P>,
|
pub data: Vec<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel + Debug> Debug for Image<P> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
let length = self.data.len();
|
||||||
|
f.debug_struct("Image")
|
||||||
|
.field("width", &self.width)
|
||||||
|
.field("height", &self.height)
|
||||||
|
.field("data", if length < 100 { &self.data } else { &length })
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl<P: StaticTypeSized + Pixel> StaticType for Image<P>
|
unsafe impl<P: StaticTypeSized + Pixel> StaticType for Image<P>
|
||||||
where
|
where
|
||||||
P::Static: Pixel,
|
P::Static: Pixel,
|
||||||
|
|||||||
@@ -243,6 +243,10 @@ impl DocumentNodeImplementation {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn proto(name: &'static str) -> Self {
|
||||||
|
Self::Unresolved(NodeIdentifier::new(name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, DynAny, specta::Type, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, DynAny, specta::Type, Hash)]
|
||||||
@@ -812,6 +816,28 @@ impl NodeNetwork {
|
|||||||
nodes: nodes.clone(),
|
nodes: nodes.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||||
|
pub fn recursive_nodes(&self) -> RecursiveNodeIter {
|
||||||
|
let nodes = self.nodes.iter().map(|(id, node)| (node, self, vec![*id])).collect();
|
||||||
|
RecursiveNodeIter { nodes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||||
|
pub struct RecursiveNodeIter<'a> {
|
||||||
|
nodes: Vec<(&'a DocumentNode, &'a NodeNetwork, Vec<NodeId>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for RecursiveNodeIter<'a> {
|
||||||
|
type Item = (&'a DocumentNode, &'a NodeNetwork, Vec<NodeId>);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let (node, network, path) = self.nodes.pop()?;
|
||||||
|
if let DocumentNodeImplementation::Network(network) = &node.implementation {
|
||||||
|
self.nodes.extend(network.nodes.iter().map(|(id, node)| (node, network, [path.as_slice(), &[*id]].concat())));
|
||||||
|
}
|
||||||
|
Some((node, network, path))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ pub struct Compiler {}
|
|||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
|
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
|
||||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||||
network.generate_node_paths(&[]);
|
|
||||||
network.resolve_extract_nodes();
|
network.resolve_extract_nodes();
|
||||||
println!("flattening");
|
println!("flattening");
|
||||||
for id in node_ids {
|
for id in node_ids {
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ impl ProtoNode {
|
|||||||
|
|
||||||
self.identifier.name.hash(&mut hasher);
|
self.identifier.name.hash(&mut hasher);
|
||||||
self.construction_args.hash(&mut hasher);
|
self.construction_args.hash(&mut hasher);
|
||||||
|
self.document_node_path.hash(&mut hasher);
|
||||||
match self.input {
|
match self.input {
|
||||||
ProtoNodeInput::None => "none".hash(&mut hasher),
|
ProtoNodeInput::None => "none".hash(&mut hasher),
|
||||||
ProtoNodeInput::ShortCircut(ref ty) => {
|
ProtoNodeInput::ShortCircut(ref ty) => {
|
||||||
@@ -629,12 +630,12 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
ids,
|
ids,
|
||||||
vec![
|
vec![
|
||||||
10739226043134366700,
|
4471348669260178714,
|
||||||
17332796976541881019,
|
12892313567093808068,
|
||||||
7897288931440576543,
|
6883586777044498729,
|
||||||
7388412494950743023,
|
13841339389284532934,
|
||||||
359700384277940942,
|
4412916056300566478,
|
||||||
12822947441562012352
|
15358108940336208665
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ impl BorrowTree {
|
|||||||
node.reset();
|
node.reset();
|
||||||
}
|
}
|
||||||
old_nodes.remove(&id);
|
old_nodes.remove(&id);
|
||||||
self.source_map.retain(|_, nid| !old_nodes.contains(nid));
|
|
||||||
}
|
}
|
||||||
|
self.source_map.retain(|_, nid| !old_nodes.contains(nid));
|
||||||
Ok(old_nodes.into_iter().collect())
|
Ok(old_nodes.into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
||||||
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||||
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||||
|
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image<Color>, params: [String]),
|
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image<Color>, params: [String]),
|
||||||
vec![(
|
vec![(
|
||||||
@@ -366,6 +367,26 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||||||
},
|
},
|
||||||
NodeIOTypes::new(generic!(T), concrete!(graphene_core::EditorApi), vec![value_fn!(VectorData)]),
|
NodeIOTypes::new(generic!(T), concrete!(graphene_core::EditorApi), vec![value_fn!(VectorData)]),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||||
|
|args| {
|
||||||
|
let input: DowncastBothNode<(), graphene_core::GraphicGroup> = DowncastBothNode::new(args[0]);
|
||||||
|
let node = graphene_std::memo::EndLetNode::new(input);
|
||||||
|
let any: DynAnyInRefNode<graphene_core::EditorApi, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||||
|
any.into_type_erased()
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(generic!(T), concrete!(graphene_core::EditorApi), vec![value_fn!(graphene_core::GraphicGroup)]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||||
|
|args| {
|
||||||
|
let input: DowncastBothNode<(), graphene_core::Artboard> = DowncastBothNode::new(args[0]);
|
||||||
|
let node = graphene_std::memo::EndLetNode::new(input);
|
||||||
|
let any: DynAnyInRefNode<graphene_core::EditorApi, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||||
|
any.into_type_erased()
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(generic!(T), concrete!(graphene_core::EditorApi), vec![value_fn!(graphene_core::Artboard)]),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
||||||
|args| {
|
|args| {
|
||||||
|
|||||||
Reference in New Issue
Block a user