mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Insert pasted images as layers in document graph (#1418)
Changes: Graph is evaluated on every viewport change move all navigation logic into the viewport reduce the number of js roundtrips add canvas rendering enable image pasting various cleanups Fix cache nodes being reset every evaluation
This commit is contained in:
committed by
Keavon Chambers
parent
d82f133514
commit
833f41bccb
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -473,11 +472,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
});
|
||||
}
|
||||
ImaginateClear { layer_path } => responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }),
|
||||
ImaginateGenerate { layer_path } => {
|
||||
if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data) {
|
||||
responses.add(message);
|
||||
}
|
||||
}
|
||||
ImaginateGenerate { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }),
|
||||
ImaginateRandom {
|
||||
layer_path,
|
||||
imaginate_node,
|
||||
@@ -500,13 +495,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.add(DocumentMessage::ImaginateGenerate { layer_path });
|
||||
}
|
||||
}
|
||||
InputFrameRasterizeRegionBelowLayer { layer_path } => {
|
||||
if layer_path.is_empty() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data) {
|
||||
responses.add(message);
|
||||
}
|
||||
}
|
||||
InputFrameRasterizeRegionBelowLayer { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }),
|
||||
LayerChanged { affected_layer_path } => {
|
||||
if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone(), &render_data) {
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry });
|
||||
@@ -589,15 +578,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
|
||||
let image_size = DVec2::new(image.width as f64, image.height as f64);
|
||||
|
||||
let path = vec![generate_uuid()];
|
||||
|
||||
// Transform of parent folder
|
||||
let to_parent_folder = self.document_legacy.generate_transform_across_scope(&path[..path.len() - 1], None).unwrap_or_default();
|
||||
|
||||
// Align the layer with the mouse or center of viewport
|
||||
let viewport_location = mouse.map_or(ipp.viewport_bounds.center(), |pos| pos.into());
|
||||
let center_in_viewport = DAffine2::from_translation(viewport_location - ipp.viewport_bounds.top_left);
|
||||
let center_in_viewport_layerspace = to_parent_folder.inverse() * center_in_viewport;
|
||||
let center_in_viewport_layerspace = center_in_viewport;
|
||||
|
||||
// Scale the image to fit into a 512x512 box
|
||||
let image_size = image_size / DVec2::splat((image_size.max_element() / 512.).max(1.));
|
||||
@@ -610,26 +594,23 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let image_frame = ImageFrame { image, transform: DAffine2::IDENTITY };
|
||||
let network = new_raster_network(image_frame);
|
||||
|
||||
responses.add(DocumentOperation::AddFrame {
|
||||
path: path.clone(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
let layer_path = self.get_path_for_new_layer();
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
graph_modification_utils::new_image_layer(image_frame, layer_path.clone(), responses);
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![path.clone()],
|
||||
replacement_selected_layers: vec![layer_path.clone()],
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: path.clone(),
|
||||
layer: layer_path.clone(),
|
||||
transform,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: path });
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
|
||||
// Force chosen tool to be Select Tool after importing image.
|
||||
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
|
||||
@@ -919,6 +900,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.add(FrontendMessage::UpdateDocumentTransform { transform });
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
UpdateLayerMetadata { layer_path, layer_metadata } => {
|
||||
self.layer_metadata.insert(layer_path, layer_metadata);
|
||||
@@ -982,45 +964,6 @@ impl DocumentMessageHandler {
|
||||
pub fn network(&self) -> &NodeNetwork {
|
||||
&self.document_legacy.document_network
|
||||
}
|
||||
pub fn rasterize_region_below_layer(&mut self, document_id: u64, layer_path: Vec<LayerId>, _preferences: &PreferencesMessageHandler, persistent_data: &PersistentData) -> Option<Message> {
|
||||
// Prepare the node graph input image
|
||||
|
||||
let Some(node_network) = self.document_legacy.layer(&layer_path).ok().and_then(|layer| layer.as_layer_network().ok()) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Check if we use the "Input Frame" node.
|
||||
// TODO: Remove once rasterization is moved into a node.
|
||||
let input_frame_node_id = node_network.nodes.iter().find(|(_, node)| node.name == "Input Frame").map(|(&id, _)| id);
|
||||
let input_frame_connected_to_graph_output = input_frame_node_id.map_or(false, |target_node_id| node_network.connected_to_output(target_node_id));
|
||||
|
||||
// If the Input Frame node is connected upstream, rasterize the artwork below this layer by calling into JS
|
||||
let response = if input_frame_connected_to_graph_output {
|
||||
let old_artwork_transform = self.remove_document_transform();
|
||||
|
||||
// Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region
|
||||
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||
// TODO: Test if this would be better to have a transparent background
|
||||
let svg = self.render_document(size, transform.inverse(), false, persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||
|
||||
self.restore_document_transform(old_artwork_transform);
|
||||
|
||||
// Once JS asynchronously rasterizes the SVG, it will call the `PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer` message with the rasterized image data
|
||||
FrontendMessage::TriggerRasterizeRegionBelowLayer { document_id, layer_path, svg, size }.into()
|
||||
}
|
||||
// Skip taking a round trip through JS since there's nothing to rasterize, and instead directly call the message which would otherwise be called asynchronously from JS
|
||||
else {
|
||||
PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer {
|
||||
document_id,
|
||||
layer_path,
|
||||
input_image_data: vec![],
|
||||
size: (0, 0),
|
||||
}
|
||||
.into()
|
||||
};
|
||||
Some(response)
|
||||
}
|
||||
|
||||
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
||||
pub(crate) fn remove_document_transform(&mut self) -> DAffine2 {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::consts::{
|
||||
};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::{ViewportBounds, ViewportPosition};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
@@ -94,7 +94,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
FitViewportToSelection => {
|
||||
if let Some(bounds) = selection_bounds {
|
||||
@@ -214,7 +214,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
}
|
||||
SetCanvasRotation { angle_radians } => {
|
||||
self.tilt = angle_radians;
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
@@ -224,7 +224,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
TransformCanvasEnd { abort_transform } => {
|
||||
if abort_transform {
|
||||
@@ -235,12 +235,12 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
}
|
||||
TransformOperation::Pan { pre_commit_pan, .. } => {
|
||||
self.pan = pre_commit_pan;
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
TransformOperation::Zoom { pre_commit_zoom, .. } => {
|
||||
self.zoom = pre_commit_zoom;
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
self.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
TranslateCanvasBegin => {
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
|
||||
@@ -285,7 +285,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
|
||||
self.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
self.create_document_transform(responses);
|
||||
}
|
||||
WheelCanvasTranslate { use_y_as_x } => {
|
||||
let delta = match use_y_as_x {
|
||||
@@ -402,11 +402,8 @@ impl NavigationMessageHandler {
|
||||
scale_transform * offset_transform * angle_transform * translation_transform
|
||||
}
|
||||
|
||||
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
|
||||
let half_viewport = viewport_bounds.size() / 2.;
|
||||
let scaled_half_viewport = half_viewport / self.snapped_scale();
|
||||
|
||||
let transform = self.calculate_offset_transform(scaled_half_viewport);
|
||||
fn create_document_transform(&self, responses: &mut VecDeque<Message>) {
|
||||
let transform = self.calculate_offset_transform(DVec2::ZERO);
|
||||
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
|
||||
}
|
||||
|
||||
@@ -414,7 +411,7 @@ impl NavigationMessageHandler {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
let mouse_fraction = mouse / viewport_bounds;
|
||||
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
|
||||
let delta = delta_size * (-mouse_fraction);
|
||||
|
||||
NavigationMessage::TranslateCanvas { delta }.into()
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
use graphene_core::Artboard;
|
||||
use graphene_core::{Artboard, Color};
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
@@ -59,6 +60,10 @@ pub enum GraphOperationMessage {
|
||||
id: NodeId,
|
||||
artboard: Artboard,
|
||||
},
|
||||
NewBitmapLayer {
|
||||
id: NodeId,
|
||||
image_frame: ImageFrame<Color>,
|
||||
},
|
||||
NewVectorLayer {
|
||||
id: NodeId,
|
||||
subpaths: Vec<Subpath<ManipulatorGroupId>>,
|
||||
|
||||
@@ -7,10 +7,11 @@ use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, FillType, Stroke};
|
||||
use graphene_core::Artboard;
|
||||
use graphene_core::{Artboard, Color};
|
||||
use transform_utils::LayerBounds;
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
@@ -177,6 +178,23 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
|
||||
}
|
||||
|
||||
fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: NodeId) {
|
||||
let image = {
|
||||
let node_type = resolve_document_node_type("Image").expect("Image node does not exist");
|
||||
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))], Default::default())
|
||||
};
|
||||
let sample = resolve_document_node_type("Sample").expect("Sample node does not exist").default_document_node();
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node();
|
||||
|
||||
let transform_id = generate_uuid();
|
||||
self.insert_node_before(transform_id, layer, 0, transform, IVec2::new(-8, 0));
|
||||
let sample_id = generate_uuid();
|
||||
self.insert_node_before(sample_id, transform_id, 0, sample, IVec2::new(-8, 0));
|
||||
let image_id = generate_uuid();
|
||||
self.insert_node_before(image_id, sample_id, 0, image, IVec2::new(-8, 0));
|
||||
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
|
||||
}
|
||||
|
||||
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
|
||||
let mut shift_nodes = HashSet::new();
|
||||
let mut stack = vec![node_id];
|
||||
@@ -525,6 +543,12 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
modify_inputs.insert_artboard(artboard, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewBitmapLayer { id, image_frame } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
modify_inputs.insert_image_data(image_frame, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewVectorLayer { id, subpaths } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
|
||||
@@ -750,11 +750,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer {
|
||||
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender {
|
||||
document_id: data.document_id,
|
||||
layer_path: Vec::new(),
|
||||
input_image_data: vec![],
|
||||
size: (0, 0),
|
||||
}),
|
||||
NodeGraphMessage::SelectNodes { nodes } => {
|
||||
self.selected_nodes = nodes;
|
||||
|
||||
@@ -2084,9 +2084,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Downres",
|
||||
name: "Sample",
|
||||
category: "Structural",
|
||||
identifier: NodeImplementation::proto("graphene_std::raster::DownresNode<_>"),
|
||||
identifier: NodeImplementation::proto("graphene_std::raster::SampleNode<_>"),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![DocumentInputType::value("Raseter Data", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
|
||||
@@ -2517,44 +2517,6 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
|
||||
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");
|
||||
let output = resolve_document_node_type("Output").expect("Output node does not exist");
|
||||
|
||||
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))));
|
||||
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
|
||||
}
|
||||
|
||||
pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
|
||||
let text_generator = resolve_document_node_type("Text").expect("Text node does not exist");
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||
|
||||
@@ -103,12 +103,6 @@ pub enum PortfolioMessage {
|
||||
data: String,
|
||||
},
|
||||
PrevDocument,
|
||||
RenderGraphUsingRasterizedRegionBelowLayer {
|
||||
document_id: u64,
|
||||
layer_path: Vec<LayerId>,
|
||||
input_image_data: Vec<u8>,
|
||||
size: (u32, u32),
|
||||
},
|
||||
SelectDocument {
|
||||
document_id: u64,
|
||||
},
|
||||
@@ -122,6 +116,10 @@ pub enum PortfolioMessage {
|
||||
blob_url: String,
|
||||
resolution: (f64, f64),
|
||||
},
|
||||
SubmitGraphRender {
|
||||
document_id: u64,
|
||||
layer_path: Vec<LayerId>,
|
||||
},
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
|
||||
@@ -451,27 +451,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
responses.add(PortfolioMessage::SelectDocument { document_id: prev_id });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer {
|
||||
document_id,
|
||||
layer_path,
|
||||
input_image_data,
|
||||
size,
|
||||
} => {
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
(document_id, &mut self.documents),
|
||||
layer_path,
|
||||
(input_image_data, size),
|
||||
(preferences, &self.persistent_data),
|
||||
responses,
|
||||
);
|
||||
|
||||
if let Err(description) = result {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
PortfolioMessage::SelectDocument { document_id } => {
|
||||
if let Some(document) = self.active_document() {
|
||||
if !document.is_auto_saved() {
|
||||
@@ -525,6 +504,20 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
};
|
||||
responses.add(PortfolioMessage::DocumentPassMessage { document_id, message });
|
||||
}
|
||||
PortfolioMessage::SubmitGraphRender { document_id, layer_path } => {
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
(document_id, self.documents.get_mut(&document_id).expect("Tried to render no existent Document")),
|
||||
layer_path,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
);
|
||||
|
||||
if let Err(description) = result {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
PortfolioMessage::UpdateDocumentWidgets => {
|
||||
if let Some(document) = self.active_document() {
|
||||
document.update_document_widgets(responses);
|
||||
|
||||
@@ -4,8 +4,10 @@ use crate::messages::prelude::*;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::{FillType, Gradient};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DAffine2;
|
||||
use std::collections::VecDeque;
|
||||
@@ -18,6 +20,14 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path:
|
||||
});
|
||||
}
|
||||
|
||||
/// Creat a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
|
||||
pub fn new_image_layer(image_frame: ImageFrame<Color>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.add(GraphOperationMessage::NewBitmapLayer {
|
||||
id: *layer_path.last().unwrap(),
|
||||
image_frame,
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a legacy node graph frame TODO: remove
|
||||
pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
@@ -393,8 +393,7 @@ impl Fsm for PathToolFsmState {
|
||||
};
|
||||
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
// Mouse up
|
||||
@@ -410,7 +409,8 @@ impl Fsm for PathToolFsmState {
|
||||
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::messages::frontend::utility_types::FrontendImageData;
|
||||
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::document::Document as DocumentLegacy;
|
||||
@@ -25,7 +24,7 @@ use graphene_core::{Color, SurfaceFrame, SurfaceId};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::{DAffine2, DVec2, UVec2};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
@@ -70,8 +69,8 @@ pub(crate) struct GenerationRequest {
|
||||
generation_id: u64,
|
||||
graph: NodeNetwork,
|
||||
path: Vec<LayerId>,
|
||||
image_frame: Option<ImageFrame<Color>>,
|
||||
transform: DAffine2,
|
||||
viewport_resolution: UVec2,
|
||||
}
|
||||
|
||||
pub(crate) struct GenerationResponse {
|
||||
@@ -139,9 +138,9 @@ impl NodeRuntime {
|
||||
NodeRuntimeMessage::GenerationRequest(GenerationRequest {
|
||||
generation_id,
|
||||
graph,
|
||||
image_frame,
|
||||
transform,
|
||||
path,
|
||||
viewport_resolution,
|
||||
..
|
||||
}) => {
|
||||
let network = wrap_network_in_scope(graph);
|
||||
@@ -152,7 +151,7 @@ impl NodeRuntime {
|
||||
.map(|node| node.path.clone().unwrap_or_default())
|
||||
.collect();
|
||||
|
||||
let result = self.execute_network(&path, network, image_frame, transform).await;
|
||||
let result = self.execute_network(&path, network, transform, viewport_resolution).await;
|
||||
let mut responses = VecDeque::new();
|
||||
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
||||
let response = GenerationResponse {
|
||||
@@ -169,21 +168,25 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>, transform: DAffine2) -> Result<TaggedValue, String> {
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> Result<TaggedValue, String> {
|
||||
if self.wasm_io.is_none() {
|
||||
self.wasm_io = Some(WasmApplicationIo::new().await);
|
||||
}
|
||||
|
||||
let editor_api = WasmEditorApi {
|
||||
font_cache: &self.font_cache,
|
||||
image_frame,
|
||||
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()
|
||||
viewport: Footprint {
|
||||
transform,
|
||||
resolution: viewport_resolution,
|
||||
..Default::default()
|
||||
},
|
||||
export_format: graphene_core::application_io::ExportFormat::Svg,
|
||||
},
|
||||
image_frame: None,
|
||||
};
|
||||
|
||||
// We assume only one output
|
||||
@@ -345,14 +348,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>, transform: DAffine2) -> u64 {
|
||||
fn queue_execution(&self, network: NodeNetwork, layer_path: Vec<LayerId>, transform: DAffine2, viewport_resolution: UVec2) -> u64 {
|
||||
let generation_id = generate_uuid();
|
||||
let request = GenerationRequest {
|
||||
path: layer_path,
|
||||
graph: network,
|
||||
image_frame,
|
||||
generation_id,
|
||||
transform,
|
||||
viewport_resolution,
|
||||
};
|
||||
self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request");
|
||||
|
||||
@@ -431,19 +434,8 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
|
||||
/// Evaluates a node graph, computing the entire graph
|
||||
pub fn submit_node_graph_evaluation(
|
||||
&mut self,
|
||||
(document_id, documents): (u64, &mut HashMap<u64, DocumentMessageHandler>),
|
||||
layer_path: Vec<LayerId>,
|
||||
(input_image_data, (width, height)): (Vec<u8>, (u32, u32)),
|
||||
_persistent_data: (&PreferencesMessageHandler, &PersistentData),
|
||||
_responses: &mut VecDeque<Message>,
|
||||
) -> Result<(), String> {
|
||||
// Reformat the input image data into an RGBA f32 image
|
||||
let image = graphene_core::raster::Image::from_image_data(&input_image_data, width, height);
|
||||
|
||||
pub fn submit_node_graph_evaluation(&mut self, (document_id, document): (u64, &mut DocumentMessageHandler), layer_path: Vec<LayerId>, viewport_resolution: UVec2) -> Result<(), String> {
|
||||
// Get the node graph layer
|
||||
let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
|
||||
let network = if layer_path.is_empty() {
|
||||
document.network().clone()
|
||||
} else {
|
||||
@@ -457,12 +449,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(), document_transform);
|
||||
let generation_id = self.queue_execution(network, layer_path.clone(), document_transform, viewport_resolution);
|
||||
|
||||
self.futures.insert(generation_id, ExecutionContext { layer_path, document_id });
|
||||
|
||||
@@ -543,13 +533,34 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
|
||||
// Send to frontend
|
||||
log::debug!("svg: {svg}");
|
||||
//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::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
|
||||
// Send to frontend
|
||||
//log::debug!("svg: {svg}");
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
let matrix = frame
|
||||
.transform
|
||||
.to_cols_array()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," }));
|
||||
let svg = format!(
|
||||
r#"
|
||||
<svg><foreignObject width="{}" height="{}" transform="matrix({})"><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>
|
||||
"#,
|
||||
1920, 1080, matrix, frame.surface_id.0
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
|
||||
//return Err("Graphic group (see console)".to_string());
|
||||
}
|
||||
TaggedValue::GraphicGroup(graphic_group) => {
|
||||
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user