Add color weights to Grayscale node and improve luminance handling (#1015)

* Add weighted grayscale node

* Rename nodes, fix grayscale weighting, add luma calc options

* Fix tests

* Add Tint Option

* Improve (but not full fix) tint

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
isiko
2023-02-10 21:55:01 +01:00
committed by Keavon Chambers
parent d456640bb8
commit a709a772d5
7 changed files with 285 additions and 34 deletions

View File

@@ -6,7 +6,7 @@ use graph_craft::document::*;
use graph_craft::imaginate_input::ImaginateSamplingMethod;
use graph_craft::proto::{NodeIdentifier, Type};
use graph_craft::{concrete, generic};
use graphene_core::raster::Image;
use graphene_core::raster::{Color, Image, LuminanceCalculation};
use std::collections::VecDeque;
@@ -162,10 +162,74 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Grayscale",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::GrayscaleNode", &[concrete!("Image")]),
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
identifier: NodeImplementation::proto(
"graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>",
&[
concrete!("Image"),
concrete!("Color"),
concrete!("f64"),
concrete!("f64"),
concrete!("f64"),
concrete!("f64"),
concrete!("f64"),
concrete!("f64"),
],
),
inputs: &[
DocumentInputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
},
DocumentInputType {
name: "Tint",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::Color(Color::BLACK), false),
},
DocumentInputType {
name: "Reds",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
DocumentInputType {
name: "Yellows",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
DocumentInputType {
name: "Greens",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
DocumentInputType {
name: "Cyans",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
DocumentInputType {
name: "Blues",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
DocumentInputType {
name: "Magentas",
data_type: FrontendGraphDataType::Number,
default: NodeInput::value(TaggedValue::F64(50.), false),
},
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::no_properties,
properties: node_properties::grayscale_properties,
},
DocumentNodeType {
name: "Luminance",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::LuminanceNode<_>", &[concrete!("Image"), concrete!("LuminanceCalculation")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::luminance_properties,
},
#[cfg(feature = "gpu")]
DocumentNodeType {
@@ -255,10 +319,11 @@ static STATIC_NODES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Threshold",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_>", &[concrete!("Image"), concrete!("f64")]),
identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _>", &[concrete!("Image"), concrete!("LuminanceCalculation"), concrete!("f64")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Threshold", TaggedValue::F64(1.), false),
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
DocumentInputType::new("Threshold", TaggedValue::F64(50.), false),
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::adjust_threshold_properties,
@@ -269,7 +334,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>", &[concrete!("Image"), concrete!("f64")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Vibrance", TaggedValue::F64(1.), false),
DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false),
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::adjust_vibrance_properties,
@@ -280,7 +345,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>", &[concrete!("Image"), concrete!("f64")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Factor", TaggedValue::F64(1.), false),
DocumentInputType::new("Factor", TaggedValue::F64(100.), false),
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::multiply_opacity,
@@ -291,7 +356,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>", &[concrete!("Image"), concrete!("f64")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Value", TaggedValue::F64(5.), false),
DocumentInputType::new("Value", TaggedValue::F64(4.), false),
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::posterize_properties,

View File

@@ -8,6 +8,7 @@ use glam::DVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::*;
use graphene_core::raster::{Color, LuminanceCalculation};
use super::document_node_types::NodePropertiesContext;
use super::{FrontendGraphDataType, IMAGINATE_NODE};
@@ -147,6 +148,47 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
widgets
}
// TODO: Generalize this for all dropdowns
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 {
tagged_value: TaggedValue::LuminanceCalculation(calculation),
exposed: false,
} = &document_node.inputs[index]
{
let calculation_modes = LuminanceCalculation::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::LuminanceCalculation(method), node_id, index)));
}
let entries = vec![entries];
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
DropdownInput::new(entries).selected_index(Some(calculation as u32)).widget_holder(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip("Formula used to calculate the luminance of a pixel")
}
fn color_widget(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, color_props: ColorInput, blank_assist: bool) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
if let NodeInput::Value {
tagged_value: TaggedValue::Color(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
color_props
.value(Some(x as Color))
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index))
.widget_holder(),
])
}
LayoutGroup::Row { widgets }
}
/// Properties for the input node, with information describing how frames work and a refresh button
pub fn input_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let information = WidgetHolder::text_widget("The graph's input is the artwork under the frame layer");
@@ -157,6 +199,35 @@ pub fn input_properties(_document_node: &DocumentNode, _node_id: NodeId, _contex
vec![LayoutGroup::Row { widgets: vec![information] }, LayoutGroup::Row { widgets: vec![refresh_button] }]
}
pub fn grayscale_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
const MIN: f64 = -200.;
const MAX: f64 = 300.;
// TODO: Add tint color (blended above using the "Color" blend mode)
let tint = color_widget(document_node, node_id, 1, "Tint", ColorInput::default(), true);
let r_weight = number_widget(document_node, node_id, 2, "Reds", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
let y_weight = number_widget(document_node, node_id, 3, "Yellows", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
let g_weight = number_widget(document_node, node_id, 4, "Greens", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
let c_weight = number_widget(document_node, node_id, 5, "Cyans", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
let b_weight = number_widget(document_node, node_id, 6, "Blues", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
let m_weight = number_widget(document_node, node_id, 7, "Magentas", NumberInput::default().min(MIN).max(MAX).unit("%"), true);
vec![
tint,
LayoutGroup::Row { widgets: r_weight },
LayoutGroup::Row { widgets: y_weight },
LayoutGroup::Row { widgets: g_weight },
LayoutGroup::Row { widgets: c_weight },
LayoutGroup::Row { widgets: b_weight },
LayoutGroup::Row { widgets: m_weight },
]
}
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);
vec![luma_calculation]
}
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true);
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().min(-100.).max(100.).unit("%"), true);
@@ -184,9 +255,10 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
}
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let thereshold = number_widget(document_node, node_id, 1, "Threshold", NumberInput::default().min(0.).max(1.), true);
let luma_calculation = luminance_calculation(document_node, node_id, 1, "Luma Calculation", true);
let thereshold = number_widget(document_node, node_id, 2, "Threshold", NumberInput::default().min(0.).max(100.).unit("%"), true);
vec![LayoutGroup::Row { widgets: thereshold }]
vec![luma_calculation, LayoutGroup::Row { widgets: thereshold }]
}
pub fn adjust_vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
@@ -203,7 +275,7 @@ pub fn gpu_map_properties(document_node: &DocumentNode, node_id: NodeId, _contex
}
pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let gamma = number_widget(document_node, node_id, 1, "Factor", NumberInput::default().min(0.).max(1.), true);
let gamma = number_widget(document_node, node_id, 1, "Factor", NumberInput::default().min(0.).max(100.).unit("%"), true);
vec![LayoutGroup::Row { widgets: gamma }]
}