mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-07 04:38:11 +08:00
Add 'Gradient Map' adjustment node
This commit is contained in:
@@ -2282,7 +2282,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
properties: node_properties::adjust_hsl_properties,
|
properties: node_properties::hue_saturation_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Brightness/Contrast",
|
identifier: "Brightness/Contrast",
|
||||||
@@ -2346,7 +2346,28 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
properties: node_properties::adjust_threshold_properties,
|
properties: node_properties::threshold_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Gradient Map",
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::proto("graphene_core::raster::GradientMapNode<_, _>"),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
NodeInput::value(TaggedValue::GradientStops(vector::style::GradientStops::default()), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
input_names: vec!["Image".to_string(), "Gradient".to_string()],
|
||||||
|
output_names: vec!["Image".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
category: "Raster",
|
||||||
|
properties: node_properties::gradient_map_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Vibrance",
|
identifier: "Vibrance",
|
||||||
@@ -2363,7 +2384,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
properties: node_properties::adjust_vibrance_properties,
|
properties: node_properties::vibrance_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Channel Mixer",
|
identifier: "Channel Mixer",
|
||||||
@@ -2426,7 +2447,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
properties: node_properties::adjust_channel_mixer_properties,
|
properties: node_properties::channel_mixer_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Selective Color",
|
identifier: "Selective Color",
|
||||||
@@ -2536,7 +2557,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
properties: node_properties::adjust_selective_color_properties,
|
properties: node_properties::selective_color_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Opacity",
|
identifier: "Opacity",
|
||||||
|
|||||||
@@ -1091,7 +1091,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn hue_saturation_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 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().mode_range().min(-100.).max(100.).unit("%"), true);
|
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
||||||
let lightness_shift = number_widget(document_node, node_id, 3, "Lightness Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
let lightness_shift = number_widget(document_node, node_id, 3, "Lightness Shift", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
||||||
@@ -1128,7 +1128,7 @@ pub fn _blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _co
|
|||||||
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
|
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
|
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
|
||||||
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
|
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().mode_range().min(0.).max(100.).unit("%"), true);
|
||||||
let luminance_calc = luminance_calculation(document_node, node_id, 3, "Luminance Calc", true);
|
let luminance_calc = luminance_calculation(document_node, node_id, 3, "Luminance Calc", true);
|
||||||
@@ -1136,13 +1136,46 @@ pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId
|
|||||||
vec![LayoutGroup::Row { widgets: thereshold_min }, LayoutGroup::Row { widgets: thereshold_max }, luminance_calc]
|
vec![LayoutGroup::Row { widgets: thereshold_min }, LayoutGroup::Row { widgets: thereshold_max }, luminance_calc]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn gradient_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
let gradient_input = 1;
|
||||||
|
let reverse_input = 2;
|
||||||
|
|
||||||
|
let gradient = if let Some(TaggedValue::GradientStops(gradient)) = &document_node.inputs[gradient_input].as_value() {
|
||||||
|
gradient.clone()
|
||||||
|
} else {
|
||||||
|
return vec![LayoutGroup::Row { widgets: vec![] }];
|
||||||
|
};
|
||||||
|
let mut gradient_row = vec![TextLabel::new("Gradient").widget_holder()];
|
||||||
|
add_blank_assist(&mut gradient_row);
|
||||||
|
gradient_row.extend([
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
ColorButton::default()
|
||||||
|
.allow_none(false)
|
||||||
|
.value(FillChoice::Gradient(gradient))
|
||||||
|
.on_update(move |x: &ColorButton| {
|
||||||
|
NodeGraphMessage::SetInputValue {
|
||||||
|
node_id,
|
||||||
|
input_index: gradient_input,
|
||||||
|
value: TaggedValue::GradientStops(x.value.as_gradient().unwrap().clone()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let reverse_row = bool_widget(document_node, node_id, reverse_input, "Reverse", true);
|
||||||
|
|
||||||
|
vec![LayoutGroup::Row { widgets: gradient_row }, LayoutGroup::Row { widgets: reverse_row }]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let vibrance = number_widget(document_node, node_id, 1, "Vibrance", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
let vibrance = number_widget(document_node, node_id, 1, "Vibrance", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: vibrance }]
|
vec![LayoutGroup::Row { widgets: vibrance }]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
// Monochrome
|
// Monochrome
|
||||||
let monochrome_index = 1;
|
let monochrome_index = 1;
|
||||||
let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", true);
|
let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", true);
|
||||||
@@ -1236,7 +1269,7 @@ pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: No
|
|||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
// Colors choice
|
// Colors choice
|
||||||
let colors_index = 38;
|
let colors_index = 38;
|
||||||
let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
|||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
use super::ImageFrame;
|
use super::ImageFrame;
|
||||||
use super::{Channel, Color, Node, RGBMut};
|
use super::{Channel, Color, Node, RGBMut};
|
||||||
|
use crate::vector::style::GradientStops;
|
||||||
use crate::vector::VectorData;
|
use crate::vector::VectorData;
|
||||||
use crate::GraphicGroup;
|
use crate::GraphicGroup;
|
||||||
|
|
||||||
@@ -554,6 +555,21 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
|
|||||||
background.alpha_blend(target_color.to_associated_alpha(opacity))
|
background.alpha_blend(target_color.to_associated_alpha(opacity))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct GradientMapNode<Gradient, Reverse> {
|
||||||
|
gradient: Gradient,
|
||||||
|
reverse: Reverse,
|
||||||
|
// TODO: Add support for dithering to break up gradient color banding
|
||||||
|
// TODO: Add support for controlling the gradient interpolation method (instead of always `luminance_srgb()`)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(GradientMapNode)]
|
||||||
|
fn gradient_map_node(color: Color, gradient: GradientStops, reverse: bool) -> Color {
|
||||||
|
let intensity = color.luminance_srgb();
|
||||||
|
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||||
|
gradient.evalute(intensity as f64)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct VibranceNode<Vibrance> {
|
pub struct VibranceNode<Vibrance> {
|
||||||
vibrance: Vibrance,
|
vibrance: Vibrance,
|
||||||
|
|||||||
@@ -36,6 +36,32 @@ impl Default for GradientStops {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GradientStops {
|
||||||
|
pub fn evalute(&self, t: f64) -> Color {
|
||||||
|
if self.0.is_empty() {
|
||||||
|
return Color::BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if t <= self.0[0].0 {
|
||||||
|
return self.0[0].1;
|
||||||
|
}
|
||||||
|
if t >= self.0[self.0.len() - 1].0 {
|
||||||
|
return self.0[self.0.len() - 1].1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..self.0.len() - 1 {
|
||||||
|
let (t1, c1) = self.0[i];
|
||||||
|
let (t2, c2) = self.0[i + 1];
|
||||||
|
if t >= t1 && t <= t2 {
|
||||||
|
let normalized_t = (t - t1) / (t2 - t1);
|
||||||
|
return c1.lerp(&c2, normalized_t as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::BLACK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A gradient fill.
|
/// A gradient fill.
|
||||||
///
|
///
|
||||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use graphene_core::{Node, NodeIO, NodeIOTypes};
|
|||||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
||||||
use graphene_std::application_io::{RenderConfig, TextureFrame};
|
use graphene_std::application_io::{RenderConfig, TextureFrame};
|
||||||
use graphene_std::raster::*;
|
use graphene_std::raster::*;
|
||||||
|
use graphene_std::vector::style::GradientStops;
|
||||||
use graphene_std::wasm_application_io::*;
|
use graphene_std::wasm_application_io::*;
|
||||||
use graphene_std::GraphicElement;
|
use graphene_std::GraphicElement;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
@@ -484,6 +485,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
|
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
|
||||||
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
|
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
|
||||||
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f64, f64, LuminanceCalculation]),
|
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f64, f64, LuminanceCalculation]),
|
||||||
|
raster_node!(graphene_core::raster::GradientMapNode<_, _>, params: [GradientStops, bool]),
|
||||||
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]),
|
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]),
|
||||||
raster_node!(
|
raster_node!(
|
||||||
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
|
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
|
||||||
|
|||||||
Reference in New Issue
Block a user