Add some additional image effect nodes (#869)
* Move the Subpath type to graphene-std * Add the transform subpath node * Delete selected nodes * Inserting node list on right click * Add several bitmap manipulator nodes * Convert add node to use f64 * Add posterize node * Rename names randomly * Fix naming * Exposure node * Fix typo * Adjust exposure node range * Comment out vector nodes * Adjust exposure range again * Posterise as ints * Rename input * Use >= in the to hsl function
This commit is contained in:
parent
59b638e4e4
commit
eb9848365f
|
|
@ -223,6 +223,7 @@ name = "dyn-any"
|
|||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"dyn-any-derive",
|
||||
"glam",
|
||||
"log",
|
||||
]
|
||||
|
||||
|
|
@ -357,6 +358,7 @@ dependencies = [
|
|||
"borrow_stack",
|
||||
"dyn-any",
|
||||
"dyn-clone",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"graphene-std",
|
||||
"log",
|
||||
|
|
@ -390,12 +392,16 @@ dependencies = [
|
|||
name = "graphene-std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"borrow_stack",
|
||||
"dyn-any",
|
||||
"dyn-clone",
|
||||
"glam",
|
||||
"graph-proc-macros",
|
||||
"graphene-core",
|
||||
"image",
|
||||
"kurbo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -440,6 +446,7 @@ dependencies = [
|
|||
"bezier-rs",
|
||||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-std",
|
||||
"image",
|
||||
"kurbo",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ pub fn default_mapping() -> Mapping {
|
|||
),
|
||||
// NORMAL PRIORITY:
|
||||
//
|
||||
// NodeGraphMessage
|
||||
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||
entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
|
|
|
|||
|
|
@ -263,6 +263,18 @@ impl WidgetHolder {
|
|||
pub fn new(widget: Widget) -> Self {
|
||||
Self { widget_id: generate_uuid(), widget }
|
||||
}
|
||||
pub fn unrelated_seperator() -> Self {
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
}))
|
||||
}
|
||||
pub fn text_widget(text: impl Into<String>) -> Self {
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: text.into(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ use graphene::layers::imaginate_layer::{ImaginateBaseImage, ImaginateGenerationP
|
|||
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{Fill, RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::{Font, FontCache};
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -928,6 +928,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
}
|
||||
common.extend(self.navigation_handler.actions());
|
||||
common.extend(self.transform_layer_handler.actions());
|
||||
common.extend(self.node_graph_handler.actions());
|
||||
common
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ pub enum NodeGraphMessage {
|
|||
CreateNode {
|
||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
||||
node_id: Option<NodeId>,
|
||||
// I don't really know what this is for (perhaps a user identifiable name).
|
||||
node_type: String,
|
||||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
DeleteNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
DeleteSelectedNodes,
|
||||
ExposeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene::document::Document;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
|
|
@ -18,9 +18,13 @@ pub enum FrontendGraphDataType {
|
|||
#[serde(rename = "color")]
|
||||
Color,
|
||||
#[serde(rename = "vector")]
|
||||
Vector,
|
||||
Subpath,
|
||||
#[serde(rename = "number")]
|
||||
Number,
|
||||
#[serde(rename = "boolean")]
|
||||
Boolean,
|
||||
#[serde(rename = "vec2")]
|
||||
Vector,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
|
|
@ -57,10 +61,14 @@ pub struct FrontendNodeLink {
|
|||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FrontendNodeType {
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
}
|
||||
impl FrontendNodeType {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self { name: name.to_string() }
|
||||
pub fn new(name: &'static str, category: &'static str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
category: category.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +156,54 @@ impl NodeGraphMessageHandler {
|
|||
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
|
||||
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
|
||||
}
|
||||
|
||||
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
fn remove_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if network.inputs.iter().any(|&id| id == node_id) {
|
||||
warn!("Deleting input node");
|
||||
return false;
|
||||
}
|
||||
if network.output == node_id {
|
||||
warn!("Deleting the output node!");
|
||||
return false;
|
||||
}
|
||||
for (id, node) in network.nodes.iter_mut() {
|
||||
if *id == node_id {
|
||||
continue;
|
||||
}
|
||||
for (input_index, input) in node.inputs.iter_mut().enumerate() {
|
||||
let NodeInput::Node(id) = input else {
|
||||
continue;
|
||||
};
|
||||
if *id != node_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else{
|
||||
warn!("Removing input of invalid node type '{}'", node.name);
|
||||
return false;
|
||||
};
|
||||
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
|
||||
*input = NodeInput::Value {
|
||||
tagged_value: tagged_value.clone(),
|
||||
exposed: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||
remove_from_network(network, node_id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
if remove_from_network(network, node_id) {
|
||||
network.nodes.remove(&node_id);
|
||||
self.selected_nodes.retain(|&id| id != node_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageHandler)> for NodeGraphMessageHandler {
|
||||
|
|
@ -188,7 +244,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
NodeGraphMessage::CreateNode { node_id, node_type } => {
|
||||
NodeGraphMessage::CreateNode { node_id, node_type, x, y } => {
|
||||
let node_id = node_id.unwrap_or_else(crate::application::generate_uuid);
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
|
|
@ -218,7 +274,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
let far_right_node = network.nodes.iter().map(|node| node.1.metadata.position).max_by_key(|pos| pos.0).unwrap_or_default();
|
||||
|
||||
network.nodes.insert(
|
||||
node_id,
|
||||
|
|
@ -227,19 +282,29 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata {
|
||||
// TODO: Better position default
|
||||
position: (far_right_node.0 + 7, far_right_node.1 + 2),
|
||||
},
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y) },
|
||||
},
|
||||
);
|
||||
Self::send_graph(network, responses);
|
||||
}
|
||||
NodeGraphMessage::DeleteNode { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
network.nodes.remove(&node_id);
|
||||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
if self.remove_node(network, node_id) {
|
||||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::DeleteSelectedNodes => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
let mut modified = false;
|
||||
for node_id in self.selected_nodes.clone() {
|
||||
modified = modified || self.remove_node(network, node_id);
|
||||
}
|
||||
if modified {
|
||||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||
|
|
@ -314,5 +379,11 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
}
|
||||
}
|
||||
|
||||
advertise_actions!(NodeGraphMessageDiscriminant; DeleteNode,);
|
||||
fn actions(&self) -> ActionList {
|
||||
if self.layer_path.is_some() && !self.selected_nodes.is_empty() {
|
||||
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes,)
|
||||
} else {
|
||||
actions!(NodeGraphMessageDiscriminant;)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use super::{FrontendGraphDataType, FrontendNodeType};
|
||||
use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
|
||||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetHolder};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::proto::{NodeIdentifier, Type};
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -15,8 +17,19 @@ pub struct DocumentInputType {
|
|||
pub default: NodeInput,
|
||||
}
|
||||
|
||||
impl DocumentInputType {
|
||||
pub const fn none() -> Self {
|
||||
Self {
|
||||
name: "None",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::value(TaggedValue::None, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DocumentNodeType {
|
||||
pub name: &'static str,
|
||||
pub category: &'static str,
|
||||
pub identifier: NodeIdentifier,
|
||||
pub inputs: &'static [DocumentInputType],
|
||||
pub outputs: &'static [FrontendGraphDataType],
|
||||
|
|
@ -24,9 +37,10 @@ pub struct DocumentNodeType {
|
|||
}
|
||||
|
||||
// TODO: Dynamic node library
|
||||
static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
||||
static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
|
||||
DocumentNodeType {
|
||||
name: "Identity",
|
||||
category: "Meta",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
|
|
@ -45,6 +59,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
|||
},
|
||||
DocumentNodeType {
|
||||
name: "Input",
|
||||
category: "Meta",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
|
|
@ -52,129 +67,273 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
|||
default: NodeInput::Network,
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: |_document_node, _node_id| {
|
||||
vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "The input to the graph is the bitmap under the frame".to_string(),
|
||||
..Default::default()
|
||||
}))],
|
||||
}]
|
||||
},
|
||||
properties: |_document_node, _node_id| node_properties::string_properties("The input to the graph is the bitmap under the frame".to_string()),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Output",
|
||||
category: "Meta",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image::empty()),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
}],
|
||||
outputs: &[],
|
||||
properties: |_document_node, _node_id| {
|
||||
vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "The output to the graph is rendered in the frame".to_string(),
|
||||
..Default::default()
|
||||
}))],
|
||||
}]
|
||||
},
|
||||
properties: |_document_node, _node_id| node_properties::string_properties("The output to the graph is rendered in the frame".to_string()),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Grayscale Image",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image::empty()),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: |_document_node, _node_id| {
|
||||
vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "The output to the graph is rendered in the frame".to_string(),
|
||||
..Default::default()
|
||||
}))],
|
||||
}]
|
||||
},
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Brighten Image",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
name: "Invert Image Color",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::InvertImageColorNode", &[]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Shift Image HSL",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::ShiftImageHSLNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image::empty()),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Amount",
|
||||
name: "Hue Shift",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(10.),
|
||||
exposed: false,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Saturation Shift",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Luminance Shift",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: super::node_properties::brighten_image_properties,
|
||||
properties: node_properties::adjust_hsl_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Hue Shift Image",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
name: "Image Contrast and Brightness",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::ImageBrightnessAndContrast", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::Image(Image::empty()),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Amount",
|
||||
name: "Brightness",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(10.),
|
||||
exposed: false,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Contrast",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: super::node_properties::hue_shift_image_properties,
|
||||
properties: node_properties::brighten_image_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Adjust Image Gamma",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::ImageGammaNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Gamma",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(1.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::adjust_gamma_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Multiply Image Opactiy",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::ImageOpacityNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Factor",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(1.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::multiply_opacity,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Posterize",
|
||||
category: "Image Filters",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::Posterize", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Value",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(5.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::posterize_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Exposure",
|
||||
category: "Image Color Correction",
|
||||
identifier: NodeIdentifier::new("graphene_std::raster::ExposureNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Value",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(1.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::exposure_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Add",
|
||||
category: "Mathmatics",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Input",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(0.),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::F64(0.), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Addend",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(0.),
|
||||
exposed: true,
|
||||
},
|
||||
default: NodeInput::value(TaggedValue::F64(0.), true),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Number],
|
||||
properties: super::node_properties::add_properties,
|
||||
properties: node_properties::add_properties,
|
||||
},
|
||||
/*DocumentNodeType {
|
||||
name: "Unit Circle Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Unit Square Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Path Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "Path Data",
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Transform Subpath",
|
||||
category: "Vector",
|
||||
identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Subpath",
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Translation",
|
||||
data_type: FrontendGraphDataType::Vector,
|
||||
default: NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Rotation",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Scale",
|
||||
data_type: FrontendGraphDataType::Vector,
|
||||
default: NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Skew",
|
||||
data_type: FrontendGraphDataType::Vector,
|
||||
default: NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
properties: node_properties::transform_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Blit Subpath",
|
||||
category: "Vector",
|
||||
identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]),
|
||||
inputs: &[
|
||||
DocumentInputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Subpath",
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), true),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::no_properties,
|
||||
},*/
|
||||
];
|
||||
|
||||
pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> {
|
||||
|
|
@ -185,6 +344,6 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
|||
DOCUMENT_NODE_TYPES
|
||||
.iter()
|
||||
.filter(|node_type| !matches!(node_type.name, "Input" | "Output"))
|
||||
.map(|node_type| FrontendNodeType { name: node_type.name.to_string() })
|
||||
.map(|node_type| FrontendNodeType::new(node_type.name, node_type.category))
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +1,128 @@
|
|||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::ParameterExposeButton;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::prelude::NodeGraphMessage;
|
||||
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
|
||||
use super::FrontendGraphDataType;
|
||||
|
||||
pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let index = 1;
|
||||
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
let widget = WidgetHolder::text_widget(text);
|
||||
vec![LayoutGroup::Row { widgets: vec![widget] }]
|
||||
}
|
||||
|
||||
fn update_value<T, F: Fn(&T) -> TaggedValue + 'static>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> {
|
||||
WidgetCallback::new(move |number_input: &T| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index,
|
||||
value: value(number_input),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder {
|
||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
||||
exposed,
|
||||
data_type,
|
||||
tooltip: "Expose input parameter in node graph".into(),
|
||||
on_update: WidgetCallback::new(move |_parameter| {
|
||||
NodeGraphMessage::ExposeInput {
|
||||
node_id,
|
||||
input_index: index,
|
||||
new_exposed: !exposed,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn number_range_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, range_min: Option<f64>, range_max: Option<f64>, unit: String, is_integer: bool) -> Vec<WidgetHolder> {
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
let exposed = input.is_exposed();
|
||||
|
||||
let mut widgets = vec![
|
||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
||||
exposed,
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
tooltip: "Expose input parameter in node graph".into(),
|
||||
on_update: WidgetCallback::new(move |_parameter| {
|
||||
NodeGraphMessage::ExposeInput {
|
||||
node_id,
|
||||
input_index: index,
|
||||
new_exposed: !exposed,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Shift Degrees".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::text_widget(name),
|
||||
];
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(x),
|
||||
tagged_value: TaggedValue::F64(x),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(x as f64),
|
||||
unit: "°".into(),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-180.),
|
||||
range_max: Some(180.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
value: Some(x),
|
||||
mode: if range_max.is_some() { NumberInputMode::Range } else { NumberInputMode::Increment },
|
||||
range_min,
|
||||
range_max,
|
||||
unit,
|
||||
is_integer,
|
||||
on_update: update_value(|x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
])
|
||||
}
|
||||
widgets
|
||||
}
|
||||
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let hue_shift = number_range_widget(document_node, node_id, 1, "Hue Shift", Some(-180.), Some(180.), "°".into(), false);
|
||||
let saturation_shift = number_range_widget(document_node, node_id, 2, "Saturation Shift", Some(-100.), Some(100.), "%".into(), false);
|
||||
let luminance_shift = number_range_widget(document_node, node_id, 3, "Luminance Shift", Some(-100.), Some(100.), "%".into(), false);
|
||||
|
||||
vec![
|
||||
LayoutGroup::Row { widgets: hue_shift },
|
||||
LayoutGroup::Row { widgets: saturation_shift },
|
||||
LayoutGroup::Row { widgets: luminance_shift },
|
||||
]
|
||||
}
|
||||
|
||||
pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let index = 1;
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
let exposed = input.is_exposed();
|
||||
let brightness = number_range_widget(document_node, node_id, 1, "Brightness", Some(-255.), Some(255.), "".into(), false);
|
||||
let contrast = number_range_widget(document_node, node_id, 2, "Contrast", Some(-255.), Some(255.), "".into(), false);
|
||||
|
||||
let mut widgets = vec![
|
||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
||||
exposed,
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
tooltip: "Expose input parameter in node graph".into(),
|
||||
on_update: WidgetCallback::new(move |_parameter| {
|
||||
NodeGraphMessage::ExposeInput {
|
||||
node_id,
|
||||
input_index: index,
|
||||
new_exposed: !exposed,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Brighten Amount".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
vec![LayoutGroup::Row { widgets: brightness }, LayoutGroup::Row { widgets: contrast }]
|
||||
}
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F32(x),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(x as f64),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-255.),
|
||||
range_max: Some(255.),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index: 1,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
])
|
||||
}
|
||||
pub fn adjust_gamma_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let gamma = number_range_widget(document_node, node_id, 1, "Gamma", Some(0.01), None, "".into(), false);
|
||||
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
vec![LayoutGroup::Row { widgets: gamma }]
|
||||
}
|
||||
|
||||
pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let gamma = number_range_widget(document_node, node_id, 1, "Factor", Some(0.), Some(1.), "".into(), false);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: gamma }]
|
||||
}
|
||||
|
||||
pub fn posterize_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let value = number_range_widget(document_node, node_id, 1, "Levels", Some(2.), Some(255.), "".into(), true);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: value }]
|
||||
}
|
||||
|
||||
pub fn exposure_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let value = number_range_widget(document_node, node_id, 1, "Value", Some(-3.), Some(3.), "".into(), false);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: value }]
|
||||
}
|
||||
|
||||
pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let operand = |name: &str, index| {
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
let exposed = input.is_exposed();
|
||||
let mut widgets = vec![
|
||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
||||
exposed,
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
tooltip: "Expose input parameter in node graph".into(),
|
||||
on_update: WidgetCallback::new(move |_parameter| {
|
||||
NodeGraphMessage::ExposeInput {
|
||||
node_id,
|
||||
input_index: index,
|
||||
new_exposed: !exposed,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: name.into(),
|
||||
..Default::default()
|
||||
})),
|
||||
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::text_widget(name),
|
||||
];
|
||||
|
||||
if let NodeInput::Value {
|
||||
|
|
@ -166,21 +131,11 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
|
|||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(x as f64),
|
||||
mode: NumberInputMode::Increment,
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node: node_id,
|
||||
input_index: index,
|
||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
on_update: update_value(|number_input: &NumberInput| TaggedValue::F32(number_input.value.unwrap() as f32), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
]);
|
||||
|
|
@ -191,13 +146,123 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
|
|||
vec![operand("Input", 0), operand("Addend", 1)]
|
||||
}
|
||||
|
||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
let translation = {
|
||||
let index = 1;
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
|
||||
let mut widgets = vec![
|
||||
expose_widget(node_id, index, FrontendGraphDataType::Vector, input.is_exposed()),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::text_widget("Translation"),
|
||||
];
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(vec2),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(vec2.x),
|
||||
label: "X".into(),
|
||||
unit: " px".into(),
|
||||
on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(number_input.value.unwrap(), vec2.y)), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(vec2.y),
|
||||
label: "Y".into(),
|
||||
unit: " px".into(),
|
||||
on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, number_input.value.unwrap())), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
LayoutGroup::Row { widgets }
|
||||
};
|
||||
|
||||
let rotation = {
|
||||
let index = 2;
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
|
||||
let mut widgets = vec![
|
||||
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::text_widget("Rotation"),
|
||||
];
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(val),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(val.to_degrees()),
|
||||
unit: "°".into(),
|
||||
mode: NumberInputMode::Range,
|
||||
range_min: Some(-180.),
|
||||
range_max: Some(180.),
|
||||
on_update: update_value(|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap().to_radians()), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
LayoutGroup::Row { widgets }
|
||||
};
|
||||
|
||||
let scale = {
|
||||
let index = 3;
|
||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||
|
||||
let mut widgets = vec![
|
||||
expose_widget(node_id, index, FrontendGraphDataType::Vector, input.is_exposed()),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::text_widget("Scale"),
|
||||
];
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(vec2),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(vec2.x),
|
||||
label: "X".into(),
|
||||
unit: "".into(),
|
||||
on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(number_input.value.unwrap(), vec2.y)), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_seperator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(vec2.y),
|
||||
label: "Y".into(),
|
||||
unit: "".into(),
|
||||
on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, number_input.value.unwrap())), node_id, index),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
LayoutGroup::Row { widgets }
|
||||
};
|
||||
vec![translation, rotation, scale]
|
||||
}
|
||||
|
||||
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("Node '{}' cannot be found in library", document_node.name),
|
||||
..Default::default()
|
||||
}))],
|
||||
}]
|
||||
string_properties(format!("Node '{}' cannot be found in library", document_node.name))
|
||||
}
|
||||
|
||||
pub fn no_properties(document_node: &DocumentNode, _node_id: NodeId) -> Vec<LayoutGroup> {
|
||||
string_properties(format!("The {} node requires no properties.", document_node.name.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId) -> LayoutGroup {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(NodeGraphMessage::CloseNodeGraph.into());
|
||||
}
|
||||
}
|
||||
ResendActiveProperties => {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ use crate::messages::prelude::*;
|
|||
use graphene::color::Color;
|
||||
use graphene::document::Document;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::layers::vector::consts::ManipulatorType;
|
||||
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene::layers::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use graphene::intersection::Quad;
|
|||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{LayerId, Operation};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::ComputeType;
|
||||
use graphene::layers::vector::consts::ManipulatorType;
|
||||
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene::layers::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene::layers::vector::subpath::{BezierId, Subpath};
|
||||
use graphene::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::{BezierId, Subpath};
|
||||
|
||||
use glam::DVec2;
|
||||
use graphene::document::Document;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use crate::messages::prelude::*;
|
|||
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||
use graphene::layers::style::{self, Stroke};
|
||||
use graphene::layers::vector::consts::ManipulatorType;
|
||||
use graphene::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::f64::consts::PI;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan
|
|||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan
|
|||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::layers::vector::consts::ManipulatorType;
|
||||
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene::LayerId;
|
||||
use graphene::Operation;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
|||
|
|
@ -40,27 +40,18 @@
|
|||
--color-f-white-rgb: 255, 255, 255;
|
||||
|
||||
--color-data-general: #c5c5c5;
|
||||
--color-data-general-rgb: 197, 197, 197;
|
||||
--color-data-general-dim: #767676;
|
||||
--color-data-general-dim-rgb: 118, 118, 118;
|
||||
--color-data-vector: #65bbe5;
|
||||
--color-data-vector-rgb: 101, 187, 229;
|
||||
--color-data-vector-dim: #4b778c;
|
||||
--color-data-vector-dim-rgb: 75, 119, 140;
|
||||
--color-data-raster: #e4bb72;
|
||||
--color-data-raster-rgb: 228, 187, 114;
|
||||
--color-data-raster-dim: #8b7752;
|
||||
--color-data-raster-dim-rgb: 139, 119, 82;
|
||||
--color-data-mask: #8d85c7;
|
||||
--color-data-mask-rgb: 141, 133, 199;
|
||||
--color-data-number: #d6536e;
|
||||
--color-data-number-rgb: 214, 83, 110;
|
||||
--color-data-number-dim: #803242;
|
||||
--color-data-number-dim-rgb: 128, 50, 66;
|
||||
--color-data-vec2: #cc00ff;
|
||||
--color-data-vec2-dim: #71008d;
|
||||
--color-data-color: #70a898;
|
||||
--color-data-color-rgb: 112, 168, 152;
|
||||
--color-data-color-dim: #43645b;
|
||||
--color-data-color-dim-rgb: 67, 100, 91;
|
||||
|
||||
--color-none: white;
|
||||
--color-none-repeat: no-repeat;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
<template>
|
||||
<LayoutCol class="node-graph">
|
||||
<LayoutRow class="options-bar"></LayoutRow>
|
||||
<div class="node-list">
|
||||
<LayoutRow>Nodes:</LayoutRow>
|
||||
<LayoutRow>
|
||||
<TextButton v-for="nodeType in nodeTypes" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)"></TextButton>
|
||||
</LayoutRow>
|
||||
</div>
|
||||
<LayoutRow
|
||||
class="graph"
|
||||
ref="graph"
|
||||
|
|
@ -21,6 +15,14 @@
|
|||
'--dot-radius': `${dotRadius}px`,
|
||||
}"
|
||||
>
|
||||
<LayoutCol class="node-list" v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
|
||||
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
|
||||
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
|
||||
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
|
||||
<TextButton v-for="nodeType in nodeCategory[1]" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)" />
|
||||
</LayoutCol>
|
||||
<TextLabel v-if="nodeCategories.length === 0">No search results :(</TextLabel>
|
||||
</LayoutCol>
|
||||
<div
|
||||
class="nodes"
|
||||
ref="nodesContainer"
|
||||
|
|
@ -37,8 +39,6 @@
|
|||
:style="{
|
||||
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
|
||||
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
|
||||
'--data-color': 'var(--color-data-raster)',
|
||||
'--data-color-dim': 'var(--color-data-raster-dim)',
|
||||
}"
|
||||
:data-node="node.id"
|
||||
>
|
||||
|
|
@ -111,10 +111,9 @@
|
|||
.node-list {
|
||||
width: max-content;
|
||||
position: fixed;
|
||||
padding: 20px;
|
||||
margin: 40px 10px;
|
||||
padding: 5px;
|
||||
z-index: 3;
|
||||
background-color: var(--color-4-dimgray);
|
||||
background-color: var(--color-3-darkgray);
|
||||
}
|
||||
|
||||
.options-bar {
|
||||
|
|
@ -181,7 +180,7 @@
|
|||
border-radius: 4px;
|
||||
background: var(--color-4-dimgray);
|
||||
left: calc((var(--offset-left) + 0.5) * 24px);
|
||||
top: calc((var(--offset-top) + 0.5) * 24px);
|
||||
top: calc((var(--offset-top) - 0.5) * 24px);
|
||||
|
||||
&.selected {
|
||||
border: 1px solid var(--color-e-nearwhite);
|
||||
|
|
@ -209,6 +208,7 @@
|
|||
|
||||
.arguments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
|
|
@ -297,6 +297,7 @@ import type { IconName } from "@/utility-functions/icons";
|
|||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
||||
import TextInput from "@/components/widgets/inputs/TextInput.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||
|
||||
|
|
@ -316,6 +317,8 @@ export default defineComponent({
|
|||
linkInProgressFromConnector: undefined as HTMLDivElement | undefined,
|
||||
linkInProgressToConnector: undefined as HTMLDivElement | DOMRect | undefined,
|
||||
nodeLinkPaths: [] as [string, string][],
|
||||
searchTerm: "",
|
||||
nodeListLocation: undefined as { x: number; y: number } | undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -335,8 +338,24 @@ export default defineComponent({
|
|||
nodes() {
|
||||
return this.nodeGraph.state.nodes;
|
||||
},
|
||||
nodeTypes() {
|
||||
return this.nodeGraph.state.nodeTypes;
|
||||
nodeCategories() {
|
||||
const categories = new Map();
|
||||
this.nodeGraph.state.nodeTypes.forEach((node) => {
|
||||
if (this.searchTerm.length && !node.name.toLowerCase().includes(this.searchTerm.toLowerCase()) && !node.category.toLowerCase().includes(this.searchTerm.toLowerCase())) return;
|
||||
|
||||
const category = categories.get(node.category);
|
||||
if (category) category.push(node);
|
||||
else categories.set(node.category, [node]);
|
||||
});
|
||||
|
||||
const result = Array.from(categories);
|
||||
return result;
|
||||
},
|
||||
nodeListX() {
|
||||
return ((this.nodeListLocation?.x || 0) * GRID_SIZE + this.transform.x) * this.transform.scale;
|
||||
},
|
||||
nodeListY() {
|
||||
return ((this.nodeListLocation?.y || 0) * GRID_SIZE + this.transform.y) * this.transform.scale;
|
||||
},
|
||||
linkPathInProgress(): [string, string] | undefined {
|
||||
if (this.linkInProgressFromConnector && this.linkInProgressToConnector) {
|
||||
|
|
@ -461,8 +480,19 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
pointerDown(e: PointerEvent) {
|
||||
if (e.button === 2) {
|
||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||
const graph = graphDiv?.getBoundingClientRect() || new DOMRect();
|
||||
this.nodeListLocation = {
|
||||
x: Math.round(((e.clientX - graph.x) / this.transform.scale - this.transform.x) / GRID_SIZE),
|
||||
y: Math.round(((e.clientY - graph.y) / this.transform.scale - this.transform.y) / GRID_SIZE),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
|
||||
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
||||
const nodeList = (e.target as HTMLElement).closest(".node-list") as HTMLElement | undefined;
|
||||
|
||||
if (port) {
|
||||
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
||||
|
|
@ -488,7 +518,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||
} else {
|
||||
} else if (!nodeList) {
|
||||
this.selected = [];
|
||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||
|
|
@ -560,7 +590,10 @@ export default defineComponent({
|
|||
this.linkInProgressToConnector = undefined;
|
||||
},
|
||||
createNode(nodeType: string): void {
|
||||
this.editor.instance.createNode(nodeType);
|
||||
if (!this.nodeListLocation) return;
|
||||
|
||||
this.editor.instance.createNode(nodeType, this.nodeListLocation.x, this.nodeListLocation.y);
|
||||
this.nodeListLocation = undefined;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -578,6 +611,7 @@ export default defineComponent({
|
|||
LayoutRow,
|
||||
TextLabel,
|
||||
TextButton,
|
||||
TextInput,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<LayoutRow class="parameter-expose-button">
|
||||
<button :class="{ exposed }" :style="{ '--data-type-color': dataTypeColor }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
|
||||
<button :class="{ exposed }" :style="{ '--data-type-color': `var(--color-data-${dataType})` }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
|
||||
</LayoutRow>
|
||||
</template>
|
||||
|
||||
|
|
@ -53,21 +53,6 @@ export default defineComponent({
|
|||
// Callbacks
|
||||
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||
},
|
||||
computed: {
|
||||
dataTypeColor(): string {
|
||||
// TODO: Move this function somewhere where it can be reused by other components
|
||||
const colorsMap = {
|
||||
general: "var(--color-data-general)",
|
||||
vector: "var(--color-data-vector)",
|
||||
raster: "var(--color-data-raster)",
|
||||
mask: "var(--color-data-mask)",
|
||||
number: "var(--color-data-number)",
|
||||
color: "var(--color-data-color)",
|
||||
} as const;
|
||||
|
||||
return colorsMap[this.dataType as keyof typeof colorsMap] || colorsMap.general;
|
||||
},
|
||||
},
|
||||
components: { LayoutRow },
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
v-model="inputValue"
|
||||
:spellcheck="spellcheck"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
@focus="() => $emit('textFocused')"
|
||||
@blur="() => $emit('textChanged')"
|
||||
@change="() => $emit('textChanged')"
|
||||
|
|
@ -148,6 +149,7 @@ export default defineComponent({
|
|||
textarea: { type: Boolean as PropType<boolean>, default: false },
|
||||
tooltip: { type: String as PropType<string | undefined>, required: false },
|
||||
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
|
||||
placeholder: { type: String as PropType<string>, required: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
:spellcheck="true"
|
||||
:disabled="disabled"
|
||||
:tooltip="tooltip"
|
||||
:placeholder="placeholder"
|
||||
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
|
||||
:sharpRightCorners="sharpRightCorners"
|
||||
@textFocused="() => onTextFocused()"
|
||||
|
|
@ -41,6 +42,7 @@ export default defineComponent({
|
|||
// Label
|
||||
label: { type: String as PropType<string>, required: false },
|
||||
tooltip: { type: String as PropType<string | undefined>, required: false },
|
||||
placeholder: { type: String as PropType<string>, required: false },
|
||||
|
||||
// Disabled
|
||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||
|
|
|
|||
|
|
@ -17,5 +17,20 @@ import App from "@/App.vue";
|
|||
await initWasm();
|
||||
|
||||
// Initialize the Vue application
|
||||
createApp(App).mount("#app");
|
||||
createApp(App)
|
||||
.directive("focus", {
|
||||
// When the bound element is inserted into the DOM
|
||||
mounted(el) {
|
||||
let focus = el;
|
||||
|
||||
// Find actual relevant child
|
||||
while (focus.children.length) focus = focus.children[0];
|
||||
|
||||
// Random timeout needed?
|
||||
setTimeout(() => {
|
||||
focus.focus(); // Focus the element
|
||||
}, 0);
|
||||
},
|
||||
})
|
||||
.mount("#app");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ export class FrontendNodeLink {
|
|||
|
||||
export class FrontendNodeType {
|
||||
readonly name!: string;
|
||||
|
||||
readonly category!: string;
|
||||
}
|
||||
|
||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||
|
|
|
|||
|
|
@ -552,8 +552,8 @@ impl JsEditorHandle {
|
|||
|
||||
/// Creates a new document node in the node graph
|
||||
#[wasm_bindgen(js_name = createNode)]
|
||||
pub fn create_node(&self, node_type: String) {
|
||||
let message = NodeGraphMessage::CreateNode { node_id: None, node_type };
|
||||
pub fn create_node(&self, node_type: String, x: i32, y: i32) {
|
||||
let message = NodeGraphMessage::CreateNode { node_id: None, node_type, x, y };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ license = "Apache-2.0"
|
|||
|
||||
[dependencies]
|
||||
graph-craft = { path = "../node-graph/graph-craft", features = ["serde"] }
|
||||
graphene-std = { path = "../node-graph/gstd", features = ["serde"] }
|
||||
image = { version = "0.24", default-features = false }
|
||||
|
||||
log = "0.4"
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ use crate::layers::nodegraph_layer::NodeGraphFrameLayer;
|
|||
use crate::layers::shape_layer::ShapeLayer;
|
||||
use crate::layers::style::RenderData;
|
||||
use crate::layers::text_layer::{Font, FontCache, TextLayer};
|
||||
use crate::layers::vector::subpath::Subpath;
|
||||
use crate::{DocumentError, DocumentResponse, Operation};
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::boolean_ops::{split_path_seg, subdivide_path_seg};
|
||||
use crate::consts::{F64LOOSE, F64PRECISE};
|
||||
use crate::layers::vector::subpath::Subpath;
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, ParamCurveExtrema, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ use super::nodegraph_layer::NodeGraphFrameLayer;
|
|||
use super::shape_layer::ShapeLayer;
|
||||
use super::style::{PathStyle, RenderData};
|
||||
use super::text_layer::TextLayer;
|
||||
use super::vector::subpath::Subpath;
|
||||
use crate::intersection::Quad;
|
||||
use crate::layers::text_layer::FontCache;
|
||||
use crate::DocumentError;
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use core::fmt;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -96,6 +97,34 @@ impl From<&LayerDataType> for LayerDataTypeDiscriminant {
|
|||
}
|
||||
}
|
||||
|
||||
// ** CONVERSIONS **
|
||||
|
||||
impl<'a> TryFrom<&'a mut Layer> for &'a mut Subpath {
|
||||
type Error = &'static str;
|
||||
/// Convert a mutable layer into a mutable [Subpath].
|
||||
fn try_from(layer: &'a mut Layer) -> Result<&'a mut Subpath, Self::Error> {
|
||||
match &mut layer.data {
|
||||
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
|
||||
// TODO Resolve converting text into a Subpath at the layer level
|
||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||
_ => Err("Did not find any shape data in the layer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Layer> for &'a Subpath {
|
||||
type Error = &'static str;
|
||||
/// Convert a reference to a layer into a reference of a [Subpath].
|
||||
fn try_from(layer: &'a Layer) -> Result<&'a Subpath, Self::Error> {
|
||||
match &layer.data {
|
||||
LayerDataType::Shape(layer) => Ok(&layer.shape),
|
||||
// TODO Resolve converting text into a Subpath at the layer level
|
||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||
_ => Err("Did not find any shape data in the layer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines shared behavior for every layer type.
|
||||
pub trait LayerData {
|
||||
/// Render the layer as an SVG tag to a given string.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ pub mod base64_serde;
|
|||
pub mod blend_mode;
|
||||
/// Contains the [FolderLayer](folder_layer::FolderLayer) type that encapsulates other layers, including more folders.
|
||||
pub mod folder_layer;
|
||||
pub mod id_vec;
|
||||
/// Contains the [ImageLayer](image_layer::ImageLayer) type that contains a bitmap image.
|
||||
pub mod image_layer;
|
||||
/// Contains the [ImaginateLayer](imaginate_layer::ImaginateLayer) type that contains a bitmap image generated based on a prompt and optionally the layers beneath it.
|
||||
|
|
@ -34,4 +33,3 @@ pub mod shape_layer;
|
|||
pub mod style;
|
||||
/// Contains the [TextLayer](text_layer::TextLayer) type.
|
||||
pub mod text_layer;
|
||||
pub mod vector;
|
||||
|
|
|
|||
|
|
@ -119,49 +119,12 @@ impl Default for NodeGraphFrameLayer {
|
|||
fn default() -> Self {
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::proto::NodeIdentifier;
|
||||
let brighten_network = NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Brighten Image Node".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_std::raster::BrightenImageNode",
|
||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let hue_shift_network = NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Hue Shift Image Node".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||
"graphene_std::raster::HueShiftImage",
|
||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||
)),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Self {
|
||||
mime: String::new(),
|
||||
network: NodeNetwork {
|
||||
inputs: vec![0],
|
||||
output: 3,
|
||||
output: 1,
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
|
|
@ -174,41 +137,11 @@ impl Default for NodeGraphFrameLayer {
|
|||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Hue Shift Image".into(),
|
||||
inputs: vec![
|
||||
NodeInput::Node(0),
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::F32(50.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7, 4 + 2) },
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
DocumentNode {
|
||||
name: "Brighten Image".into(),
|
||||
inputs: vec![
|
||||
NodeInput::Node(1),
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::F32(10.),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(brighten_network),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 2, 4 + 2 * 2) },
|
||||
},
|
||||
),
|
||||
(
|
||||
3,
|
||||
DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![NodeInput::Node(2)],
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 3, 4) },
|
||||
metadata: DocumentNodeMetadata { position: (20, 4) },
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use super::layer_info::LayerData;
|
||||
use super::style::{self, PathStyle, RenderData, ViewMode};
|
||||
use super::vector::subpath::Subpath;
|
||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||
use crate::layers::text_layer::FontCache;
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use super::layer_info::LayerData;
|
||||
use super::style::{PathStyle, RenderData, ViewMode};
|
||||
use super::vector::subpath::Subpath;
|
||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||
use crate::LayerId;
|
||||
pub use font_cache::{Font, FontCache};
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use rustybuzz::Face;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::layers::vector::consts::ManipulatorType;
|
||||
use crate::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use crate::layers::vector::manipulator_point::ManipulatorPoint;
|
||||
use crate::layers::vector::subpath::Subpath;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::DVec2;
|
||||
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ use crate::layers::blend_mode::BlendMode;
|
|||
use crate::layers::imaginate_layer::{ImaginateMaskFillContent, ImaginateMaskPaintMode, ImaginateSamplingMethod, ImaginateStatus};
|
||||
use crate::layers::layer_info::Layer;
|
||||
use crate::layers::style::{self, Stroke};
|
||||
use crate::layers::vector::consts::ManipulatorType;
|
||||
use crate::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use crate::layers::vector::subpath::Subpath;
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@ documentation = "https://docs.rs/dyn-any"
|
|||
[dependencies]
|
||||
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
glam = { version = "0.17", optional = true }
|
||||
|
||||
[features]
|
||||
derive = ["dyn-any-derive"]
|
||||
log-bad-types = ["log"]
|
||||
# Opt into impls for Rc<T> and Arc<T>.
|
||||
rc = []
|
||||
# Opt into impls for some glam types
|
||||
glam = ["dep:glam"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
|||
|
|
@ -190,13 +190,30 @@ use std::{
|
|||
vec::Vec,
|
||||
};
|
||||
|
||||
impl_type!(Option<T>,Result<T, E>,Cell<T>,UnsafeCell<T>,RefCell<T>,MaybeUninit<T>,
|
||||
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
|
||||
BinaryHeap<T>, ManuallyDrop<T>, PhantomData<T>, PhantomPinned,Empty<T>,
|
||||
Wrapping<T>, Duration, Once, Mutex<T>, RwLock<T>, bool, f32, f64, char,
|
||||
u8, AtomicU8, u16,AtomicU16, u32,AtomicU32, u64,AtomicU64, usize,AtomicUsize,
|
||||
i8,AtomicI8, i16,AtomicI16, i32,AtomicI32, i64,AtomicI64, isize,AtomicIsize,
|
||||
i128, u128, AtomicBool, AtomicPtr<T>
|
||||
impl_type!(
|
||||
Option<T>, Result<T, E>, Cell<T>, UnsafeCell<T>, RefCell<T>, MaybeUninit<T>,
|
||||
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
|
||||
BinaryHeap<T>, ManuallyDrop<T>, PhantomData<T>, PhantomPinned, Empty<T>,
|
||||
Wrapping<T>, Duration, Once, Mutex<T>, RwLock<T>, bool, f32, f64, char,
|
||||
u8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64, AtomicU64, usize, AtomicUsize,
|
||||
i8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64, AtomicI64, isize, AtomicIsize,
|
||||
i128, u128, AtomicBool, AtomicPtr<T>
|
||||
);
|
||||
|
||||
#[cfg(feature = "rc")]
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
#[cfg(feature = "rc")]
|
||||
impl_type!(Rc<T>, Arc<T>);
|
||||
|
||||
#[cfg(feature = "glam")]
|
||||
use glam::*;
|
||||
#[cfg(feature = "glam")]
|
||||
#[rustfmt::skip]
|
||||
impl_type!(
|
||||
IVec2, IVec3, IVec4, UVec2, UVec3, UVec4, BVec2, BVec3, BVec4,
|
||||
Vec2, Vec3, Vec3A, Vec4, DVec2, DVec3, DVec4,
|
||||
Mat2, Mat3, Mat3A, Mat4, DMat2, DMat3, DMat4,
|
||||
Quat, Affine2, Affine3A, DAffine2, DAffine3, DQuat
|
||||
);
|
||||
|
||||
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
||||
|
|
|
|||
|
|
@ -209,9 +209,9 @@ impl Color {
|
|||
} else {
|
||||
(max_channel - min_channel) / (2. - max_channel - min_channel)
|
||||
};
|
||||
let hue = if self.red > self.green && self.red > self.blue {
|
||||
let hue = if self.red >= self.green && self.red >= self.blue {
|
||||
(self.green - self.blue) / (max_channel - min_channel)
|
||||
} else if self.green > self.red && self.green > self.blue {
|
||||
} else if self.green >= self.red && self.green >= self.blue {
|
||||
2. + (self.blue - self.red) / (max_channel - min_channel)
|
||||
} else {
|
||||
4. + (self.red - self.green) / (max_channel - min_channel)
|
||||
|
|
@ -282,6 +282,7 @@ fn hsl_roundtrip() {
|
|||
(95, 79, 88),
|
||||
(13, 34, 4),
|
||||
(82, 84, 84),
|
||||
(255, 255, 178),
|
||||
] {
|
||||
let col = Color::from_rgb8(red, green, blue);
|
||||
let [hue, saturation, luminance, alpha] = col.to_hsla();
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ license = "MIT OR Apache-2.0"
|
|||
[dependencies]
|
||||
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
||||
graphene-std = { path = "../gstd" }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||
num-traits = "0.2"
|
||||
borrow_stack = { path = "../borrow_stack" }
|
||||
dyn-clone = "1.0"
|
||||
rand_chacha = "0.3.1"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||
glam = { version = "0.17" }
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "graphene-std/serde"]
|
||||
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ impl DocumentNode {
|
|||
}
|
||||
|
||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||
assert_ne!(self.inputs.len(), 0, "Resolving document node {:#?} with no inputs", self);
|
||||
let first = self.inputs.remove(0);
|
||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||
let (input, mut args) = match first {
|
||||
|
|
@ -102,6 +103,9 @@ pub enum NodeInput {
|
|||
}
|
||||
|
||||
impl NodeInput {
|
||||
pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self {
|
||||
Self::Value { tagged_value, exposed }
|
||||
}
|
||||
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
|
||||
if let NodeInput::Node(id) = self {
|
||||
*self = NodeInput::Node(f(*id))
|
||||
|
|
|
|||
|
|
@ -1,28 +1,40 @@
|
|||
use dyn_any::StaticType;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
use dyn_any::{DynAny, Upcast};
|
||||
use dyn_clone::DynClone;
|
||||
use glam::DVec2;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TaggedValue {
|
||||
None,
|
||||
String(String),
|
||||
U32(u32),
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
Bool(bool),
|
||||
DVec2(DVec2),
|
||||
Image(graphene_std::raster::Image),
|
||||
Color(graphene_core::raster::color::Color),
|
||||
Subpath(graphene_std::vector::subpath::Subpath),
|
||||
RcSubpath(Arc<graphene_std::vector::subpath::Subpath>),
|
||||
}
|
||||
|
||||
impl TaggedValue {
|
||||
pub fn to_value(self) -> Value {
|
||||
match self {
|
||||
TaggedValue::None => Box::new(()),
|
||||
TaggedValue::String(x) => Box::new(x),
|
||||
TaggedValue::U32(x) => Box::new(x),
|
||||
TaggedValue::F32(x) => Box::new(x),
|
||||
TaggedValue::F64(x) => Box::new(x),
|
||||
TaggedValue::Bool(x) => Box::new(x),
|
||||
TaggedValue::DVec2(x) => Box::new(x),
|
||||
TaggedValue::Image(x) => Box::new(x),
|
||||
TaggedValue::Color(x) => Box::new(x),
|
||||
TaggedValue::Subpath(x) => Box::new(x),
|
||||
TaggedValue::RcSubpath(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use borrow_stack::FixedSizeStack;
|
||||
use glam::DVec2;
|
||||
use graphene_core::generic::FnNode;
|
||||
use graphene_core::ops::AddNode;
|
||||
use graphene_core::raster::color::Color;
|
||||
|
|
@ -9,6 +10,7 @@ use graphene_core::Node;
|
|||
use graphene_std::any::DowncastBothNode;
|
||||
use graphene_std::any::{Any, DowncastNode, DynAnyNode, IntoTypeErasedNode, TypeErasedNode};
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use crate::proto::Type;
|
||||
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNode, ProtoNodeInput, Type::Concrete};
|
||||
|
|
@ -48,8 +50,8 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Add Node constructed with out rhs input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node: DynAnyNode<_, f32, _, _> = DynAnyNode::new(ConsNode::new(input_node).then(graphene_core::ops::AddNode));
|
||||
let input_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(value_node);
|
||||
let node: DynAnyNode<_, f64, _, _> = DynAnyNode::new(ConsNode::new(input_node).then(graphene_core::ops::AddNode));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
|
|
@ -297,14 +299,28 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
}
|
||||
})
|
||||
}),
|
||||
(NodeIdentifier::new("graphene_std::raster::InvertImageColorNode", &[]), |proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let node = DynAnyNode::new(graphene_std::raster::InvertImageColorNode);
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
}),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
NodeIdentifier::new("graphene_std::raster::ShiftImageHSLNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Image Node constructed with out shift input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_std::raster::HueShiftImage::new(input_node));
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ShiftImageHSLNode Node constructed without inputs") };
|
||||
|
||||
let hue: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let saturation: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||
let luminance: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::ShiftImageHSLNode::new(hue, saturation, luminance));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
|
|
@ -316,13 +332,82 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
NodeIdentifier::new("graphene_std::raster::ImageBrightnessAndContrast", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Brighten Image Node constructed with out brighten input node") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_std::raster::BrightenImageNode::new(input_node));
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageBrightnessAndContrast Node constructed without inputs") };
|
||||
|
||||
let brightness: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let contrast: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::ImageBrightnessAndContrast::new(brightness, contrast));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::ImageGammaNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageGammaNode Node constructed without inputs") };
|
||||
let gamma: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::ImageGammaNode::new(gamma));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::ImageOpacityNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageOpacityNode Node constructed without inputs") };
|
||||
let opacity: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::ImageOpacityNode::new(opacity));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::Posterize", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Posterize Node constructed without inputs") };
|
||||
let value: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::Posterize::new(value));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::raster::ExposureNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||
|proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ExposureNode constructed without inputs") };
|
||||
let value: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let node = DynAnyNode::new(graphene_std::raster::ExposureNode::new(value));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
|
|
@ -378,6 +463,45 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
}
|
||||
},
|
||||
),
|
||||
(NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), |_proto_node, stack| {
|
||||
stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitCircleGenerator).into_type_erased())
|
||||
}),
|
||||
(NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), |_proto_node, stack| {
|
||||
stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitSquareGenerator).into_type_erased())
|
||||
}),
|
||||
(NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]), |proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("BlitSubpath without subpath input") };
|
||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||
let input_node: DowncastBothNode<_, (), Subpath> = DowncastBothNode::new(value_node);
|
||||
let node = DynAnyNode::new(graphene_std::vector::generator_nodes::BlitSubpath::new(input_node));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
}),
|
||||
(NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), |proto_node, stack| {
|
||||
stack.push_fn(move |nodes| {
|
||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("TransformSubpathNode without subpath input") };
|
||||
let translate_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||
let rotate_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||
let scale_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap());
|
||||
let shear_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[3] as usize).unwrap());
|
||||
|
||||
let node = DynAnyNode::new(graphene_std::vector::generator_nodes::TransformSubpathNode::new(translate_node, rotate_node, scale_node, shear_node));
|
||||
|
||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||
(pre_node).then(node).into_type_erased()
|
||||
} else {
|
||||
node.into_type_erased()
|
||||
}
|
||||
})
|
||||
}),
|
||||
];
|
||||
|
||||
pub fn push_node(proto_node: ProtoNode, stack: &FixedSizeStack<TypeErasedNode<'static>>) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ quote = {version = "1.0", default-features = false }
|
|||
image = "*"
|
||||
dyn-clone = "1.0"
|
||||
|
||||
log = "0.4"
|
||||
bezier-rs = { path = "../../libraries/bezier-rs" }
|
||||
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
glam = { version = "0.17", features = ["serde"] }
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
//pub mod value;
|
||||
//#![feature(const_type_name)]
|
||||
|
||||
|
|
@ -5,6 +9,7 @@
|
|||
pub mod memo;
|
||||
|
||||
pub mod raster;
|
||||
pub mod vector;
|
||||
|
||||
pub mod any;
|
||||
|
||||
|
|
|
|||
|
|
@ -176,89 +176,286 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<()
|
|||
})
|
||||
}
|
||||
|
||||
fn grayscale_image(mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GrayscaleImageNode;
|
||||
|
||||
impl Node<Image> for GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
grayscale_image(image)
|
||||
}
|
||||
}
|
||||
impl Node<Image> for &GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
grayscale_image(image)
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_image(mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(1. - pixel.r(), 1. - pixel.g(), 1. - pixel.b(), pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BrightenImageNode<N: Node<(), Output = f32>>(N);
|
||||
pub struct InvertImageColorNode;
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for BrightenImageNode<N> {
|
||||
impl Node<Image> for InvertImageColorNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
invert_image(image)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &BrightenImageNode<N> {
|
||||
impl Node<Image> for &InvertImageColorNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
invert_image(image)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> BrightenImageNode<N> {
|
||||
fn shift_image_hsl(mut image: Image, hue_shift: f32, saturation_shift: f32, luminance_shift: f32) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(
|
||||
(hue + hue_shift / 360.) % 1.,
|
||||
(saturation + saturation_shift / 100.).clamp(0., 1.),
|
||||
(luminance + luminance_shift / 100.).clamp(0., 1.),
|
||||
alpha,
|
||||
);
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ShiftImageHSLNode<Hue, Sat, Lum>
|
||||
where
|
||||
Hue: Node<(), Output = f64>,
|
||||
Sat: Node<(), Output = f64>,
|
||||
Lum: Node<(), Output = f64>,
|
||||
{
|
||||
hue: Hue,
|
||||
saturation: Sat,
|
||||
luminance: Lum,
|
||||
}
|
||||
|
||||
impl<Hue, Sat, Lum> Node<Image> for ShiftImageHSLNode<Hue, Sat, Lum>
|
||||
where
|
||||
Hue: Node<(), Output = f64>,
|
||||
Sat: Node<(), Output = f64>,
|
||||
Lum: Node<(), Output = f64>,
|
||||
{
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
shift_image_hsl(image, self.hue.eval(()) as f32, self.saturation.eval(()) as f32, self.luminance.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
impl<Hue, Sat, Lum> Node<Image> for &ShiftImageHSLNode<Hue, Sat, Lum>
|
||||
where
|
||||
Hue: Node<(), Output = f64> + Copy,
|
||||
Sat: Node<(), Output = f64> + Copy,
|
||||
Lum: Node<(), Output = f64> + Copy,
|
||||
{
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
shift_image_hsl(image, self.hue.eval(()) as f32, self.saturation.eval(()) as f32, self.luminance.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Hue, Sat, Lum> ShiftImageHSLNode<Hue, Sat, Lum>
|
||||
where
|
||||
Hue: Node<(), Output = f64>,
|
||||
Sat: Node<(), Output = f64>,
|
||||
Lum: Node<(), Output = f64>,
|
||||
{
|
||||
pub fn new(hue: Hue, saturation: Sat, luminance: Lum) -> Self {
|
||||
Self { hue, saturation, luminance }
|
||||
}
|
||||
}
|
||||
|
||||
// Copy pasta from https://stackoverflow.com/questions/2976274/adjust-bitmap-image-brightness-contrast-using-c
|
||||
fn adjust_image_brightness_and_contrast(mut image: Image, brightness_shift: f32, contrast: f32) -> Image {
|
||||
let factor = (259. * (contrast + 255.)) / (255. * (259. - contrast));
|
||||
let channel = |channel: f32| ((factor * (channel * 255. + brightness_shift - 128.) + 128.) / 255.).clamp(0., 1.);
|
||||
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageBrightnessAndContrast<Brightness, Contrast>
|
||||
where
|
||||
Brightness: Node<(), Output = f64>,
|
||||
Contrast: Node<(), Output = f64>,
|
||||
{
|
||||
brightness: Brightness,
|
||||
contrast: Contrast,
|
||||
}
|
||||
|
||||
impl<Brightness, Contrast> Node<Image> for ImageBrightnessAndContrast<Brightness, Contrast>
|
||||
where
|
||||
Brightness: Node<(), Output = f64>,
|
||||
Contrast: Node<(), Output = f64>,
|
||||
{
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
adjust_image_brightness_and_contrast(image, self.brightness.eval(()) as f32, self.contrast.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Brightness, Contrast> Node<Image> for &ImageBrightnessAndContrast<Brightness, Contrast>
|
||||
where
|
||||
Brightness: Node<(), Output = f64> + Copy,
|
||||
Contrast: Node<(), Output = f64> + Copy,
|
||||
{
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
adjust_image_brightness_and_contrast(image, self.brightness.eval(()) as f32, self.contrast.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Brightness, Contrast> ImageBrightnessAndContrast<Brightness, Contrast>
|
||||
where
|
||||
Brightness: Node<(), Output = f64>,
|
||||
Contrast: Node<(), Output = f64>,
|
||||
{
|
||||
pub fn new(brightness: Brightness, contrast: Contrast) -> Self {
|
||||
Self { brightness, contrast }
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/
|
||||
fn image_gamma(mut image: Image, gamma: f32) -> Image {
|
||||
let inverse_gamma = 1. / gamma;
|
||||
let channel = |channel: f32| channel.powf(inverse_gamma);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageGammaNode<N: Node<(), Output = f64>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f64>> Node<Image> for ImageGammaNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
image_gamma(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ImageGammaNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
image_gamma(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f64> + Copy> ImageGammaNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
fn image_opacity(mut image: Image, opacity_multiplier: f32) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(pixel.r(), pixel.g(), pixel.b(), pixel.a() * opacity_multiplier)
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
// Based on http://www.axiomx.com/posterize.htm
|
||||
fn posterize(mut image: Image, posterize_value: f32) -> Image {
|
||||
let number_of_areas = posterize_value.recip();
|
||||
let size_of_areas = (posterize_value - 1.).recip();
|
||||
let channel = |channel: f32| (channel / number_of_areas).floor() * size_of_areas;
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/questions/12166117/what-is-the-math-behind-exposure-adjustment-on-photoshop
|
||||
fn exposure(mut image: Image, exposure: f32) -> Image {
|
||||
let multiplier = 2f32.powf(exposure);
|
||||
let channel = |channel: f32| channel * multiplier;
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Posterize<N: Node<(), Output = f64>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f64>> Node<Image> for Posterize<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
posterize(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &Posterize<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
posterize(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f64> + Copy> Posterize<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HueShiftImage<N: Node<(), Output = f32>>(N);
|
||||
pub struct ImageOpacityNode<N: Node<(), Output = f64>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for HueShiftImage<N> {
|
||||
impl<N: Node<(), Output = f64>> Node<Image> for ImageOpacityNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
image_opacity(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &HueShiftImage<N> {
|
||||
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ImageOpacityNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
fn eval(self, image: Image) -> Image {
|
||||
image_opacity(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftImage<N> {
|
||||
impl<N: Node<(), Output = f64> + Copy> ImageOpacityNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ExposureNode<N: Node<(), Output = f64>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f64>> Node<Image> for ExposureNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
exposure(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ExposureNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, image: Image) -> Image {
|
||||
exposure(image, self.0.eval(()) as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f64> + Copy> ExposureNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::Node;
|
||||
|
||||
use super::subpath::Subpath;
|
||||
|
||||
type VectorData = Subpath;
|
||||
|
||||
pub struct UnitCircleGenerator;
|
||||
|
||||
impl Node<()> for UnitCircleGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &UnitCircleGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct UnitSquareGenerator;
|
||||
|
||||
impl Node<()> for UnitSquareGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &UnitSquareGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
Subpath::new_rect(DVec2::ZERO, DVec2::ONE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator(Arc<Subpath>);
|
||||
|
||||
impl Node<()> for PathGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &PathGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
(*self.0).clone()
|
||||
}
|
||||
}
|
||||
use crate::raster::Image;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlitSubpath<N: Node<(), Output = Subpath>>(N);
|
||||
|
||||
impl<N: Node<(), Output = Subpath>> Node<Image> for BlitSubpath<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let subpath = self.0.eval(());
|
||||
info!("Blitting subpath {subpath:?}");
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = Subpath> + Copy> Node<Image> for &BlitSubpath<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let subpath = self.0.eval(());
|
||||
info!("Blitting subpath {subpath:?}");
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = Subpath>> BlitSubpath<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
translate_node: Translation,
|
||||
rotate_node: Rotation,
|
||||
scale_node: Scale,
|
||||
shear_node: Shear,
|
||||
}
|
||||
|
||||
impl<Translation, Rotation, Scale, Shear> TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
pub fn new(translate_node: Translation, rotate_node: Rotation, scale_node: Scale, shear_node: Shear) -> Self {
|
||||
Self {
|
||||
translate_node,
|
||||
rotate_node,
|
||||
scale_node,
|
||||
shear_node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
type Output = Subpath;
|
||||
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||
let translate = self.translate_node.eval(());
|
||||
let rotate = self.rotate_node.eval(());
|
||||
let scale = self.scale_node.eval(());
|
||||
let shear = self.shear_node.eval(());
|
||||
|
||||
let (sin, cos) = rotate.sin_cos();
|
||||
|
||||
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||
subpath
|
||||
}
|
||||
}
|
||||
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for &TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2> + Copy,
|
||||
Rotation: Node<(), Output = f64> + Copy,
|
||||
Scale: Node<(), Output = DVec2> + Copy,
|
||||
Shear: Node<(), Output = DVec2> + Copy,
|
||||
{
|
||||
type Output = Subpath;
|
||||
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||
let translate = self.translate_node.eval(());
|
||||
let rotate = self.rotate_node.eval(());
|
||||
let scale = self.scale_node.eval(());
|
||||
let shear = self.shear_node.eval(());
|
||||
|
||||
let (sin, cos) = rotate.sin_cos();
|
||||
|
||||
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||
subpath
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,15 @@ pub struct IdBackedVec<T> {
|
|||
}
|
||||
|
||||
impl<T> IdBackedVec<T> {
|
||||
/// Creates a new empty vector
|
||||
pub const fn new() -> Self {
|
||||
IdBackedVec {
|
||||
elements: vec![],
|
||||
element_ids: vec![],
|
||||
next_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new element to the start of the vector
|
||||
pub fn push_front(&mut self, element: T) -> Option<ElementId> {
|
||||
self.next_id += 1;
|
||||
|
|
@ -135,11 +144,7 @@ impl<T> IdBackedVec<T> {
|
|||
|
||||
impl<T> Default for IdBackedVec<T> {
|
||||
fn default() -> Self {
|
||||
IdBackedVec {
|
||||
elements: vec![],
|
||||
element_ids: vec![],
|
||||
next_id: 0,
|
||||
}
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
pub mod consts;
|
||||
pub mod generator_nodes;
|
||||
pub mod id_vec;
|
||||
pub mod manipulator_group;
|
||||
pub mod manipulator_point;
|
||||
pub mod subpath;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use super::consts::ManipulatorType;
|
||||
use super::id_vec::IdBackedVec;
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
use super::manipulator_point::ManipulatorPoint;
|
||||
use crate::layers::id_vec::IdBackedVec;
|
||||
use crate::layers::layer_info::{Layer, LayerDataType};
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, PathEl, Shape};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -11,15 +11,15 @@ use serde::{Deserialize, Serialize};
|
|||
/// [Subpath] represents a single vector path, containing many [ManipulatorGroups].
|
||||
/// For each closed shape we keep a [Subpath] which contains the [ManipulatorGroup]s (handles and anchors) that define that shape.
|
||||
// TODO Add "closed" bool to subpath
|
||||
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize, DynAny)]
|
||||
pub struct Subpath(IdBackedVec<ManipulatorGroup>);
|
||||
|
||||
impl Subpath {
|
||||
// ** INITIALIZATION **
|
||||
|
||||
/// Create a new [Subpath] with no [ManipulatorGroup]s.
|
||||
pub fn new() -> Self {
|
||||
Subpath { ..Default::default() }
|
||||
pub const fn new() -> Self {
|
||||
Subpath(IdBackedVec::new())
|
||||
}
|
||||
|
||||
/// Construct a [Subpath] from a point iterator
|
||||
|
|
@ -421,34 +421,6 @@ impl Subpath {
|
|||
}
|
||||
}
|
||||
|
||||
// ** CONVERSIONS **
|
||||
|
||||
impl<'a> TryFrom<&'a mut Layer> for &'a mut Subpath {
|
||||
type Error = &'static str;
|
||||
/// Convert a mutable layer into a mutable [Subpath].
|
||||
fn try_from(layer: &'a mut Layer) -> Result<&'a mut Subpath, Self::Error> {
|
||||
match &mut layer.data {
|
||||
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
|
||||
// TODO Resolve converting text into a Subpath at the layer level
|
||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||
_ => Err("Did not find any shape data in the layer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Layer> for &'a Subpath {
|
||||
type Error = &'static str;
|
||||
/// Convert a reference to a layer into a reference of a [Subpath].
|
||||
fn try_from(layer: &'a Layer) -> Result<&'a Subpath, Self::Error> {
|
||||
match &layer.data {
|
||||
LayerDataType::Shape(layer) => Ok(&layer.shape),
|
||||
// TODO Resolve converting text into a Subpath at the layer level
|
||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||
_ => Err("Did not find any shape data in the layer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`bezier_rs::Bezier`] containing also the IDs for the [`ManipulatorGroup`]s where the points are from.
|
||||
pub struct BezierId {
|
||||
/// The internal [`bezier_rs::Bezier`].
|
||||
Loading…
Reference in New Issue