Replace the image layer type with an Image node (#948)

* Use builder pattern for widgets

* Arguments to new function

* Add node graph when dragging in image

* Fix duplicate import

* Skip processing under node graph frame if unused

* Reduce node graph rerenders

* DUPLICATE ALL frontend changes into other frontend

* DUPLICATE more changes to another frontend

* Code review

* Allow importing SVG files as bitmaps

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-01-27 10:01:09 +00:00 committed by Keavon Chambers
parent 66e8325362
commit 64e62699fc
32 changed files with 444 additions and 261 deletions

View file

@ -459,6 +459,11 @@ mod image {
data: self.data.as_slice(),
}
}
/// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array)
pub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self {
let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
Image { width, height, data }
}
}
impl IntoIterator for Image {

View file

@ -47,8 +47,8 @@ fn bilt_subpath(base_image: Image, path_data: Subpath) -> Image {
let composition = Composition::new();
let mut renderer = cpu::Renderer::new();
let mut path_builder = PathBuilder::new();
for path_segement in path_data.bezier_iter() {
let points = path_segement.internal.get_points().collect::<Vec<_>>();
for path_segment in path_data.bezier_iter() {
let points = path_segment.internal.get_points().collect::<Vec<_>>();
match points.len() {
2 => path_builder.line_to(points[1].into()),
3 => path_builder.quad_to(points[1].into(), points[2].into()),

View file

@ -11,7 +11,7 @@ serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
graphene-core = { path = "../gcore", features = ["async", "std", "alloc"] }
graphene-core = { path = "../gcore", features = ["alloc"] }
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
num-traits = "0.2"
dyn-clone = "1.0"

View file

@ -1,9 +1,6 @@
use crate::document::value::TaggedValue;
use crate::generic;
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput, Type};
use std::collections::HashMap;
use std::sync::Mutex;
pub mod value;
use dyn_any::{DynAny, StaticType};
use glam::IVec2;
@ -11,6 +8,10 @@ use rand_chacha::{
rand_core::{RngCore, SeedableRng},
ChaCha20Rng,
};
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
pub mod value;
pub type NodeId = u64;
static RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);
@ -226,7 +227,7 @@ impl NodeNetwork {
self.flatten_with_fns(node, merge_ids, generate_uuid)
}
/// Recursively dissolve non primitive document nodes and return a single flattened network of nodes.
/// Recursively dissolve non-primitive document nodes and return a single flattened network of nodes.
pub fn flatten_with_fns(&mut self, node: NodeId, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, gen_id: impl Fn() -> NodeId + Copy) {
let (id, mut node) = self
.nodes
@ -258,10 +259,18 @@ impl NodeNetwork {
network_input.populate_first_network_input(node, *offset);
}
NodeInput::Value { tagged_value, exposed } => {
let name = format!("Value: {:?}", tagged_value.clone().to_value());
// Skip formatting very large values for seconds in performance speedup
let name = if matches!(
tagged_value,
TaggedValue::Image(_) | TaggedValue::RcImage(_) | TaggedValue::Color(_) | TaggedValue::Subpath(_) | TaggedValue::RcSubpath(_)
) {
"Value".to_string()
} else {
format!("Value: {:?}", tagged_value.clone().to_value())
};
let new_id = map_ids(id, gen_id());
let value_node = DocumentNode {
name: name.clone(),
name,
inputs: vec![NodeInput::Value { tagged_value, exposed }],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])),
metadata: DocumentNodeMetadata::default(),
@ -305,6 +314,95 @@ impl NodeNetwork {
pub fn original_output(&self) -> NodeId {
self.previous_output.unwrap_or(self.output)
}
/// A graph with just an input and output node
pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self {
Self {
inputs: vec![0],
output: 1,
nodes: [
(
0,
DocumentNode {
name: "Input".into(),
inputs: vec![NodeInput::Network],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
metadata: DocumentNodeMetadata { position: (8, 4).into() },
},
),
(
1,
DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::Node(output_node_id)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
},
),
]
.into_iter()
.collect(),
..Default::default()
}
}
/// Get the nested network given by the path of node ids
pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {
let mut network = Some(self);
for segment in nested_path {
network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());
}
network
}
/// Get the mutable nested network given by the path of node ids
pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
let mut network = Some(self);
for segment in nested_path {
network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());
}
network
}
/// Check if the specified node id is connected to the output
pub fn connected_to_output(&self, node_id: NodeId) -> bool {
// If the node is the output then return true
if self.output == node_id {
return true;
}
// Get the output
let Some(output_node) = self.nodes.get(&self.output) else {
return false;
};
let mut stack = vec![output_node];
let mut already_visited = HashSet::new();
already_visited.insert(self.output);
while let Some(node) = stack.pop() {
for input in &node.inputs {
if let &NodeInput::Node(ref_id) = input {
// Skip if already viewed
if already_visited.contains(&ref_id) {
continue;
}
// If the target node is used as input then return true
if ref_id == node_id {
return true;
}
// Add the referenced node to the stack
let Some(ref_node) = self.nodes.get(&ref_id) else {
continue;
};
already_visited.insert(ref_id);
stack.push(ref_node);
}
}
}
false
}
}
#[cfg(test)]