Add "Blend" node (#1024)

* Add Blend node

* Add more implementations

Currently, known buggy implementations:
* Color Burn
* Saturation

Opacity is currently achieved by linear interpolation, this will be changed as soon as all filters are implemented.

* Add more implementations

Currently, known incorrect implementations:
* Color Burn
* Saturation

Not yet Tested:
* Linear Burn
* Linear Dodge
* Vivid Light
* Linear Light
* Pin Light
* Hard Mix
* Subtract
* Divide

Opacity is currently achieved by linear interpolation, this will be changed as soon as all filters are implemented.

* Cleanup

* Removed Unused Code
* Fixed Clamping Issue
* Fixed Inverted Opacity
* Moved Opacity Calculation from individual  Blend Functions into 'blend_node' function

* Fix 'Color Burn' blend mode

Currently, known incorrect implementations:
* Saturation

Not yet Tested:
* Linear Burn
* Darker Color
* Linear Dodge
* Lighter Color
* Vivid Light
* Linear Light
* Pin Light
* Hard Mix
* Subtract
* Divide

Opacity is currently achieved by linear interpolation, this will be changed as soon as all filters are implemented.

* Fix 'Saturation' blend mode

Currently, known incorrect implementations:
* None :D

Not yet Tested:
* Linear Burn
* Darker Color
* Linear Dodge
* Lighter Color
* Vivid Light
* Linear Light
* Pin Light
* Hard Mix
* Subtract
* Divide

Opacity is currently achieved by linear interpolation, this will be changed as soon as all filters are implemented.

* Final Cleanups

* Add proper Inputs

* cargo fmt

* Add test for doubling number

* Display implementation for ProtoNetwork

* Switch top and bottom

* Add input types for blend image node

* Fix test

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
isiko
2023-02-23 12:55:32 +01:00
committed by Keavon Chambers
parent 48dcc2774b
commit 8fe19063c1
10 changed files with 534 additions and 10 deletions

View File

@@ -7,8 +7,9 @@ use graph_craft::document::value::*;
use graph_craft::document::*;
use graph_craft::imaginate_input::ImaginateSamplingMethod;
use graph_craft::concrete;
use graph_craft::NodeIdentifier;
use graphene_core::raster::{Color, Image, ImageFrame, LuminanceCalculation};
use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation};
use graphene_core::*;
use std::collections::VecDeque;
@@ -192,6 +193,19 @@ fn static_nodes() -> Vec<DocumentNodeType> {
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"),
},
DocumentNodeType {
name: "Blend Node",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Second", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("BlendMode", TaggedValue::BlendMode(BlendMode::Normal), false),
DocumentInputType::value("Opacity", TaggedValue::F64(100.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::blend_properties,
},
DocumentNodeType {
name: "Levels",
category: "Image Adjustments",

View File

@@ -8,7 +8,7 @@ use glam::DVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::*;
use graphene_core::raster::{Color, LuminanceCalculation};
use graphene_core::raster::{BlendMode, Color, LuminanceCalculation};
use super::document_node_types::NodePropertiesContext;
use super::{FrontendGraphDataType, IMAGINATE_NODE};
@@ -148,7 +148,27 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
widgets
}
// TODO: Generalize this for all dropdowns
//TODO Use generalized Version of this as soon as it's available
fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let &NodeInput::Value {
tagged_value: TaggedValue::BlendMode(mode),
exposed: false,
} = &document_node.inputs[index]
{
let calculation_modes = BlendMode::list();
let mut entries = Vec::with_capacity(calculation_modes.len());
for method in calculation_modes {
entries.push(DropdownEntryData::new(method.to_string()).on_update(update_value(move |_| TaggedValue::BlendMode(method), node_id, index)));
}
let entries = vec![entries];
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder()]);
}
LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending")
}
// TODO: Generalize this for all dropdowns ( also see blend_mode )
fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let &NodeInput::Value {
@@ -238,6 +258,14 @@ pub fn grayscale_properties(document_node: &DocumentNode, node_id: NodeId, _cont
]
}
pub fn blend_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let backdrop = color_widget(document_node, node_id, 1, "Backdrop", ColorInput::default(), true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().min(0.).max(100.).unit("%"), true);
vec![backdrop, blend_mode, LayoutGroup::Row { widgets: opacity }]
}
pub fn luminance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let luma_calculation = luminance_calculation(document_node, node_id, 1, "Luma Calculation", true);

View File

@@ -33,11 +33,13 @@ impl NodeGraphExecutor {
network.duplicate_outputs(&mut generate_uuid);
network.remove_dead_nodes();
debug!("Execute document network:\n{network:#?}");
// We assume only one output
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = c.compile_single(network, true)?;
debug!("Execute proto network:\n{proto_network}");
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
if let Err(e) = self.executor.update(proto_network) {
error!("Failed to update executor:\n{}", e);