Remove the old node macro and fix/clean up several raster nodes (#2650)

* Fix several broken raster nodes and clean up leftover old node system code

* Migrate Brightness/Contrast to the new node macro, and fix it

* Remove last usages of old_node_fn

* Remove old_node_fn
This commit is contained in:
Keavon Chambers
2025-05-17 21:24:32 -07:00
committed by GitHub
parent 77f8bfd9ed
commit a8e209e44c
17 changed files with 511 additions and 1423 deletions

View File

@@ -16,7 +16,7 @@ use graph_craft::document::value::*;
use graph_craft::document::*;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha};
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorDataTable;
@@ -94,22 +94,6 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeDefinition>> =
/// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built.
fn static_nodes() -> Vec<DocumentNodeDefinition> {
let mut custom = vec![
DocumentNodeDefinition {
identifier: "Default Network",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork::default()),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata::default()),
..Default::default()
},
},
description: Cow::Borrowed("A default node network you can use to create your own custom nodes."),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Identity",
@@ -129,6 +113,43 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
properties: Some("identity_properties"),
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Monitor",
category: "Debug",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
manual_composition: Some(generic!(T)),
skip_deduplication: true,
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."),
properties: Some("monitor_properties"),
},
DocumentNodeDefinition {
identifier: "Default Network",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork::default()),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata::default()),
..Default::default()
},
},
description: Cow::Borrowed("A default node network you can use to create your own custom nodes."),
properties: None,
},
DocumentNodeDefinition {
identifier: "Cache",
category: "General",
@@ -210,27 +231,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Monitor",
category: "Debug",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
manual_composition: Some(generic!(T)),
skip_deduplication: true,
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."),
properties: Some("monitor_properties"),
},
DocumentNodeDefinition {
identifier: "Merge",
category: "General",
@@ -837,92 +837,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Generates different noise patterns."),
properties: None,
},
// TODO: This needs to work with resolution-aware data.
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Mask",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
],
manual_composition: Some(generic!(T)),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("Image", "TODO").into(),
PropertiesRow::with_override("Stencil", "TODO", WidgetOverride::Custom("mask_stencil".to_string())),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: This needs to work with resolution-aware data.
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Insert Channel",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("Image", "TODO").into(),
PropertiesRow::with_override("Insertion", "TODO", WidgetOverride::Hidden),
("Into", "TODO").into(),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: This needs to work with resolution-aware data.
DocumentNodeDefinition {
identifier: "Combine Channels",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("None", "TODO").into(),
("Red", "TODO").into(),
("Green", "TODO").into(),
("Blue", "TODO").into(),
("Alpha", "TODO").into(),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Split Channels",
category: "Raster",
@@ -1937,28 +1851,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "GPU Image",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into(), ("Node", "TODO").into()],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Extract",
category: "Debug",
@@ -1977,40 +1869,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27brit%27%20%3D%20Brightness/Contrast
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Padding-,Brightness%20and%20Contrast,-Key%20is%20%27brit
identifier: "Brightness/Contrast",
category: "Raster: Adjustment",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("Image", "TODO").into(),
PropertiesRow::with_override("Brightness", "TODO", WidgetOverride::Custom("brightness".to_string())),
PropertiesRow::with_override("Brightness", "TODO", WidgetOverride::Custom("contrast".to_string())),
("Use Classic", "TODO").into(),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
//
// Some further analysis available at:
// https://geraldbakker.nl/psnumbers/curves.html
// TODO: Fix this, it's currently broken
// DocumentNodeDefinition {
// identifier: "Curves",
@@ -2034,51 +1898,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// properties: None,
// },
// (*IMAGINATE_NODE).clone(),
DocumentNodeDefinition {
identifier: "Line",
category: "Vector: Shape",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., 50.)), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
("None", "TODO").into(),
PropertiesRow::with_override(
"Start",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
..Default::default()
}),
),
PropertiesRow::with_override(
"End",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
..Default::default()
}),
),
],
output_names: vec!["Vector".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Path",
category: "Vector",
@@ -2948,6 +2767,7 @@ pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sy
/// Defines the logic for inputs to display a custom properties panel widget.
fn static_node_properties() -> NodeProperties {
let mut map: NodeProperties = HashMap::new();
map.insert("brightness_contrast_properties".to_string(), Box::new(node_properties::brightness_contrast_properties));
map.insert("channel_mixer_properties".to_string(), Box::new(node_properties::channel_mixer_properties));
map.insert("fill_properties".to_string(), Box::new(node_properties::fill_properties));
map.insert("stroke_properties".to_string(), Box::new(node_properties::stroke_properties));

View File

@@ -164,6 +164,7 @@ pub(crate) fn property_from_type(
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
Some("Resolution") => vector2_widget(default_info, "W", "H", " px", Some(64.)),
Some("PixelSize") => vector2_widget(default_info, "X", "Y", " px", None),
// For all other types, use TypeId-based matching
_ => {
@@ -967,6 +968,56 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
})
}
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in brightness_contrast_properties: {err}");
return Vec::new();
}
};
// Use Classic
let use_classic_index = 3;
let use_classic = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, use_classic_index, true, context), CheckboxInput::default());
let use_classic_value = match document_node.inputs[use_classic_index].as_value() {
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
_ => false,
};
// Brightness
let brightness_index = 1;
let brightness = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, brightness_index, true, context),
NumberInput::default()
.unit("%")
.mode_range()
.display_decimal_places(2)
.range_min(Some(if use_classic_value { -100. } else { -150. }))
.range_max(Some(if use_classic_value { 100. } else { 150. })),
);
// Contrast
let contrast_index = 2;
let contrast = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, contrast_index, true, context),
NumberInput::default()
.unit("%")
.mode_range()
.display_decimal_places(2)
.range_min(Some(if use_classic_value { -100. } else { -50. }))
.range_max(Some(100.)),
);
let layout = vec![
LayoutGroup::Row { widgets: brightness },
LayoutGroup::Row { widgets: contrast },
LayoutGroup::Row { widgets: use_classic },
];
layout
}
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,

View File

@@ -461,7 +461,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
};
const REPLACEMENTS: [(&str, &str); 35] = [
const REPLACEMENTS: [(&str, &str); 36] = [
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
@@ -501,6 +501,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
];
let mut network = document.network_interface.document_network().clone();
network.generate_node_paths(&[]);