diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 5dce9e8f..e5e695fc 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -7,7 +7,7 @@ use graph_craft::document::value::*; use graph_craft::document::*; use graph_craft::imaginate_input::ImaginateSamplingMethod; use graph_craft::NodeIdentifier; -use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation}; +use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::vector::VectorData; use graphene_core::*; @@ -696,11 +696,72 @@ fn static_nodes() -> Vec { DocumentInputType::value("(Blue) Blue", TaggedValue::F64(100.), false), DocumentInputType::value("(Blue) Constant", TaggedValue::F64(0.), false), // Display-only properties (not used within the node) - DocumentInputType::value("Output Channel", TaggedValue::U32(0), false), + DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::adjust_channel_mixer_properties, }, + DocumentNodeType { + name: "Selective Color", + category: "Image Adjustments", + identifier: NodeImplementation::proto( + "graphene_core::raster::SelectiveColorNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>", + ), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + // Mode + DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), false), + // Reds + DocumentInputType::value("(Reds) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Reds) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Reds) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Reds) Black", TaggedValue::F64(0.), false), + // Yellows + DocumentInputType::value("(Yellows) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Yellows) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Yellows) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Yellows) Black", TaggedValue::F64(0.), false), + // Greens + DocumentInputType::value("(Greens) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Greens) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Greens) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Greens) Black", TaggedValue::F64(0.), false), + // Cyans + DocumentInputType::value("(Cyans) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Cyans) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Cyans) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Cyans) Black", TaggedValue::F64(0.), false), + // Blues + DocumentInputType::value("(Blues) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blues) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blues) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blues) Black", TaggedValue::F64(0.), false), + // Magentas + DocumentInputType::value("(Magentas) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Magentas) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Magentas) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Magentas) Black", TaggedValue::F64(0.), false), + // Whites + DocumentInputType::value("(Whites) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Whites) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Whites) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Whites) Black", TaggedValue::F64(0.), false), + // Neutrals + DocumentInputType::value("(Neutrals) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Neutrals) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Neutrals) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Neutrals) Black", TaggedValue::F64(0.), false), + // Blacks + DocumentInputType::value("(Blacks) Cyan", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blacks) Magenta", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blacks) Yellow", TaggedValue::F64(0.), false), + DocumentInputType::value("(Blacks) Black", TaggedValue::F64(0.), false), + // Display-only properties (not used within the node) + DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::Reds), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::adjust_selective_color_properties, + }, DocumentNodeType { name: "Opacity", category: "Image Adjustments", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index e7aec70d..7c9b208b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -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::{BlendMode, Color, LuminanceCalculation}; +use graphene_core::raster::{BlendMode, Color, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin}; use super::document_node_types::NodePropertiesContext; @@ -546,6 +546,7 @@ pub fn adjust_vibrance_properties(document_node: &DocumentNode, node_id: NodeId, } pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + // Monochrome let monochrome_index = 1; let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", true); let is_monochrome = if let &NodeInput::Value { @@ -558,51 +559,53 @@ pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: No false }; + // Output channel choice let output_channel_index = 18; let mut output_channel = vec![WidgetHolder::text_widget("Output Channel"), WidgetHolder::unrelated_separator()]; add_blank_assist(&mut output_channel); if let &NodeInput::Value { - tagged_value: TaggedValue::U32(red_green_blue_index), + tagged_value: TaggedValue::RedGreenBlue(choice), exposed: false, } = &document_node.inputs[output_channel_index] { - let entries = [("Red", 0), ("Green", 1), ("Blue", 2)] - .into_iter() - .map(|(name, val)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::U32(val), node_id, output_channel_index))) - .collect(); - output_channel.extend([RadioInput::new(entries).selected_index(red_green_blue_index).widget_holder()]); + let entries = vec![ + RadioEntryData::new(RedGreenBlue::Red.to_string()).on_update(update_value(|_| TaggedValue::RedGreenBlue(RedGreenBlue::Red), node_id, output_channel_index)), + RadioEntryData::new(RedGreenBlue::Green.to_string()).on_update(update_value(|_| TaggedValue::RedGreenBlue(RedGreenBlue::Green), node_id, output_channel_index)), + RadioEntryData::new(RedGreenBlue::Blue.to_string()).on_update(update_value(|_| TaggedValue::RedGreenBlue(RedGreenBlue::Blue), node_id, output_channel_index)), + ]; + output_channel.extend([RadioInput::new(entries).selected_index(choice as u32).widget_holder()]); }; - let is_output_channel = if let &NodeInput::Value { - tagged_value: TaggedValue::U32(red_green_blue_index), + tagged_value: TaggedValue::RedGreenBlue(choice), .. } = &document_node.inputs[output_channel_index] { - red_green_blue_index + choice } else { warn!("Channel Mixer node properties panel could not be displayed."); return vec![]; }; + // Channel values let (r, g, b, c) = match (is_monochrome, is_output_channel) { (true, _) => ((2, "Red", 40.), (3, "Green", 40.), (4, "Blue", 20.), (5, "Constant", 0.)), - (false, 0) => ((6, "(Red) Red", 100.), (7, "(Red) Green", 0.), (8, "(Red) Blue", 0.), (9, "(Red) Constant", 0.)), - (false, 1) => ((10, "(Green) Red", 0.), (11, "(Green) Green", 100.), (12, "(Green) Blue", 0.), (13, "(Green) Constant", 0.)), - (false, 2) => ((14, "(Blue) Red", 0.), (15, "(Blue) Green", 0.), (16, "(Blue) Blue", 100.), (17, "(Blue) Constant", 0.)), - _ => unreachable!(), + (false, RedGreenBlue::Red) => ((6, "(Red) Red", 100.), (7, "(Red) Green", 0.), (8, "(Red) Blue", 0.), (9, "(Red) Constant", 0.)), + (false, RedGreenBlue::Green) => ((10, "(Green) Red", 0.), (11, "(Green) Green", 100.), (12, "(Green) Blue", 0.), (13, "(Green) Constant", 0.)), + (false, RedGreenBlue::Blue) => ((14, "(Blue) Red", 0.), (15, "(Blue) Green", 0.), (16, "(Blue) Blue", 100.), (17, "(Blue) Constant", 0.)), }; - let red = number_widget(document_node, node_id, r.0, r.1, NumberInput::default().min(-200.).max(200.).value(Some(r.2)).unit("%"), true); let green = number_widget(document_node, node_id, g.0, g.1, NumberInput::default().min(-200.).max(200.).value(Some(g.2)).unit("%"), true); let blue = number_widget(document_node, node_id, b.0, b.1, NumberInput::default().min(-200.).max(200.).value(Some(b.2)).unit("%"), true); let constant = number_widget(document_node, node_id, c.0, c.1, NumberInput::default().min(-200.).max(200.).value(Some(c.2)).unit("%"), true); + // Monochrome let mut layout = vec![LayoutGroup::Row { widgets: monochrome }]; + // Output channel choice if !is_monochrome { layout.push(LayoutGroup::Row { widgets: output_channel }); }; + // Channel values layout.extend([ - // Gray output LayoutGroup::Row { widgets: red }, LayoutGroup::Row { widgets: green }, LayoutGroup::Row { widgets: blue }, @@ -611,6 +614,85 @@ pub fn adjust_channel_mixer_properties(document_node: &DocumentNode, node_id: No layout } +pub fn adjust_selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + // Colors choice + let colors_index = 38; + let mut colors = vec![WidgetHolder::text_widget("Colors"), WidgetHolder::unrelated_separator()]; + add_blank_assist(&mut colors); + if let &NodeInput::Value { + tagged_value: TaggedValue::SelectiveColorChoice(choice), + exposed: false, + } = &document_node.inputs[colors_index] + { + use SelectiveColorChoice::*; + let entries = [vec![Reds, Yellows, Greens, Cyans, Blues, Magentas], vec![Whites, Neutrals, Blacks]] + .into_iter() + .map(|section| { + section + .into_iter() + .map(|choice| DropdownEntryData::new(choice.to_string()).on_update(update_value(move |_| TaggedValue::SelectiveColorChoice(choice), node_id, colors_index))) + .collect() + }) + .collect(); + colors.extend([DropdownInput::new(entries).selected_index(Some(choice as u32)).widget_holder()]); + }; + let colors_choice_index = if let &NodeInput::Value { + tagged_value: TaggedValue::SelectiveColorChoice(choice), + .. + } = &document_node.inputs[colors_index] + { + choice + } else { + warn!("Selective Color node properties panel could not be displayed."); + return vec![]; + }; + + // CMYK + let (c, m, y, k) = match colors_choice_index { + SelectiveColorChoice::Reds => ((2, "(Reds) Cyan"), (3, "(Reds) Magenta"), (4, "(Reds) Yellow"), (5, "(Reds) Black")), + SelectiveColorChoice::Yellows => ((6, "(Yellows) Cyan"), (7, "(Yellows) Magenta"), (8, "(Yellows) Yellow"), (9, "(Yellows) Black")), + SelectiveColorChoice::Greens => ((10, "(Greens) Cyan"), (11, "(Greens) Magenta"), (12, "(Greens) Yellow"), (13, "(Greens) Black")), + SelectiveColorChoice::Cyans => ((14, "(Cyans) Cyan"), (15, "(Cyans) Magenta"), (16, "(Cyans) Yellow"), (17, "(Cyans) Black")), + SelectiveColorChoice::Blues => ((18, "(Blues) Cyan"), (19, "(Blues) Magenta"), (20, "(Blues) Yellow"), (21, "(Blues) Black")), + SelectiveColorChoice::Magentas => ((22, "(Magentas) Cyan"), (23, "(Magentas) Magenta"), (24, "(Magentas) Yellow"), (25, "(Magentas) Black")), + SelectiveColorChoice::Whites => ((26, "(Whites) Cyan"), (27, "(Whites) Magenta"), (28, "(Whites) Yellow"), (29, "(Whites) Black")), + SelectiveColorChoice::Neutrals => ((30, "(Neutrals) Cyan"), (31, "(Neutrals) Magenta"), (32, "(Neutrals) Yellow"), (33, "(Neutrals) Black")), + SelectiveColorChoice::Blacks => ((34, "(Blacks) Cyan"), (35, "(Blacks) Magenta"), (36, "(Blacks) Yellow"), (37, "(Blacks) Black")), + }; + let cyan = number_widget(document_node, node_id, c.0, c.1, NumberInput::default().min(-100.).max(100.).unit("%"), true); + let magenta = number_widget(document_node, node_id, m.0, m.1, NumberInput::default().min(-100.).max(100.).unit("%"), true); + let yellow = number_widget(document_node, node_id, y.0, y.1, NumberInput::default().min(-100.).max(100.).unit("%"), true); + let black = number_widget(document_node, node_id, k.0, k.1, NumberInput::default().min(-100.).max(100.).unit("%"), true); + + // Mode + let mode_index = 1; + let mut mode = start_widgets(document_node, node_id, mode_index, "Mode", FrontendGraphDataType::General, true); + mode.push(WidgetHolder::unrelated_separator()); + if let &NodeInput::Value { + tagged_value: TaggedValue::RelativeAbsolute(relative_or_absolute), + exposed: false, + } = &document_node.inputs[mode_index] + { + let entries = vec![ + RadioEntryData::new("Relative").on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), node_id, mode_index)), + RadioEntryData::new("Absolute").on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Absolute), node_id, mode_index)), + ]; + mode.push(RadioInput::new(entries).selected_index(relative_or_absolute as u32).widget_holder()); + }; + + vec![ + // Colors choice + LayoutGroup::Row { widgets: colors }, + // CMYK + LayoutGroup::Row { widgets: cyan }, + LayoutGroup::Row { widgets: magenta }, + LayoutGroup::Row { widgets: yellow }, + LayoutGroup::Row { widgets: black }, + // Mode + LayoutGroup::Row { widgets: mode }, + ] +} + #[cfg(feature = "gpu")] pub fn gpu_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let map = text_widget(document_node, node_id, 1, "Map", true); diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index a50b8451..44c5a535 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -3,6 +3,7 @@ use crate::Node; use core::fmt::Debug; use dyn_any::{DynAny, StaticType}; +use serde::{Deserialize, Serialize}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::float::Float; @@ -456,6 +457,24 @@ fn vibrance_node(color: Color, vibrance: f64) -> Color { } } +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)] +pub enum RedGreenBlue { + Red, + Green, + Blue, +} + +impl core::fmt::Display for RedGreenBlue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + RedGreenBlue::Red => write!(f, "Red"), + RedGreenBlue::Green => write!(f, "Green"), + RedGreenBlue::Blue => write!(f, "Blue"), + } + } +} + #[derive(Debug, Clone, Copy)] pub struct ChannelMixerNode { monochrome: Monochrome, @@ -523,6 +542,209 @@ fn channel_mixer_node( color.to_linear_srgb() } +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)] +pub enum RelativeAbsolute { + Relative, + Absolute, +} + +impl core::fmt::Display for RelativeAbsolute { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + RelativeAbsolute::Relative => write!(f, "Relative"), + RelativeAbsolute::Absolute => write!(f, "Absolute"), + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)] +pub enum SelectiveColorChoice { + Reds, + Yellows, + Greens, + Cyans, + Blues, + Magentas, + Whites, + Neutrals, + Blacks, +} + +impl core::fmt::Display for SelectiveColorChoice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SelectiveColorChoice::Reds => write!(f, "Reds"), + SelectiveColorChoice::Yellows => write!(f, "Yellows"), + SelectiveColorChoice::Greens => write!(f, "Greens"), + SelectiveColorChoice::Cyans => write!(f, "Cyans"), + SelectiveColorChoice::Blues => write!(f, "Blues"), + SelectiveColorChoice::Magentas => write!(f, "Magentas"), + SelectiveColorChoice::Whites => write!(f, "Whites"), + SelectiveColorChoice::Neutrals => write!(f, "Neutrals"), + SelectiveColorChoice::Blacks => write!(f, "Blacks"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SelectiveColorNode { + mode: Absolute, + r_c: RC, + r_m: RM, + r_y: RY, + r_k: RK, + y_c: YC, + y_m: YM, + y_y: YY, + y_k: YK, + g_c: GC, + g_m: GM, + g_y: GY, + g_k: GK, + c_c: CC, + c_m: CM, + c_y: CY, + c_k: CK, + b_c: BC, + b_m: BM, + b_y: BY, + b_k: BK, + m_c: MC, + m_m: MM, + m_y: MY, + m_k: MK, + w_c: WC, + w_m: WM, + w_y: WY, + w_k: WK, + n_c: NC, + n_m: NM, + n_y: NY, + n_k: NK, + k_c: KC, + k_m: KM, + k_y: KY, + k_k: KK, +} + +// Based on https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html +#[node_macro::node_fn(SelectiveColorNode)] +fn selective_color_node( + color: Color, + mode: RelativeAbsolute, + r_c: f64, + r_m: f64, + r_y: f64, + r_k: f64, + y_c: f64, + y_m: f64, + y_y: f64, + y_k: f64, + g_c: f64, + g_m: f64, + g_y: f64, + g_k: f64, + c_c: f64, + c_m: f64, + c_y: f64, + c_k: f64, + b_c: f64, + b_m: f64, + b_y: f64, + b_k: f64, + m_c: f64, + m_m: f64, + m_y: f64, + m_k: f64, + w_c: f64, + w_m: f64, + w_y: f64, + w_k: f64, + n_c: f64, + n_m: f64, + n_y: f64, + n_k: f64, + k_c: f64, + k_m: f64, + k_y: f64, + k_k: f64, +) -> Color { + let color = color.to_gamma_srgb(); + + let (r, g, b, a) = color.components(); + + let min = |a: f32, b: f32, c: f32| a.min(b).min(c); + let max = |a: f32, b: f32, c: f32| a.max(b).max(c); + let med = |a: f32, b: f32, c: f32| a + b + c - min(a, b, c) - max(a, b, c); + + let max_channel = max(r, g, b); + let min_channel = min(r, g, b); + + let pixel_color_range = |choice| match choice { + SelectiveColorChoice::Reds => max_channel == r, + SelectiveColorChoice::Yellows => min_channel == b, + SelectiveColorChoice::Greens => max_channel == g, + SelectiveColorChoice::Cyans => min_channel == r, + SelectiveColorChoice::Blues => max_channel == b, + SelectiveColorChoice::Magentas => min_channel == g, + SelectiveColorChoice::Whites => r > 0.5 && g > 0.5 && b > 0.5, + SelectiveColorChoice::Neutrals => r > 0. && g > 0. && b > 0. && r < 1. && g < 1. && b < 1., + SelectiveColorChoice::Blacks => r < 0.5 && g < 0.5 && b < 0.5, + }; + + let color_parameter_group_scale_factor_rgb = max(r, g, b) - med(r, g, b); + let color_parameter_group_scale_factor_cmy = med(r, g, b) - min(r, g, b); + + // Used to apply the r, g, or b channel slope (by multiplying it by 1) in relative mode, or no slope (by multiplying it by 0) in absolute mode + let (slope_r, slope_g, slope_b) = match mode { + RelativeAbsolute::Relative => (r - 1., g - 1., b - 1.), + RelativeAbsolute::Absolute => (-1., -1., -1.), + }; + + let (sum_r, sum_g, sum_b) = [ + (SelectiveColorChoice::Reds, (r_c, r_m, r_y, r_k)), + (SelectiveColorChoice::Yellows, (y_c, y_m, y_y, y_k)), + (SelectiveColorChoice::Greens, (g_c, g_m, g_y, g_k)), + (SelectiveColorChoice::Cyans, (c_c, c_m, c_y, c_k)), + (SelectiveColorChoice::Blues, (b_c, b_m, b_y, b_k)), + (SelectiveColorChoice::Magentas, (m_c, m_m, m_y, m_k)), + (SelectiveColorChoice::Whites, (w_c, w_m, w_y, w_k)), + (SelectiveColorChoice::Neutrals, (n_c, n_m, n_y, n_k)), + (SelectiveColorChoice::Blacks, (k_c, k_m, k_y, k_k)), + ] + .into_iter() + .fold((0., 0., 0.), |acc, (color_parameter_group, (c, m, y, k))| { + // Skip this color parameter group... + // ...if it's unchanged from the default of zero offset on all CMYK paramters, or... + // ...if this pixel's color isn't in the range affected by this color parameter group + if (c < f64::EPSILON && m < f64::EPSILON && y < f64::EPSILON && k < f64::EPSILON) || (!pixel_color_range(color_parameter_group)) { + return acc; + } + + let (c, m, y, k) = (c as f32 / 100., m as f32 / 100., y as f32 / 100., k as f32 / 100.); + + let color_parameter_group_scale_factor = match color_parameter_group { + SelectiveColorChoice::Reds | SelectiveColorChoice::Greens | SelectiveColorChoice::Blues => color_parameter_group_scale_factor_rgb, + SelectiveColorChoice::Cyans | SelectiveColorChoice::Magentas | SelectiveColorChoice::Yellows => color_parameter_group_scale_factor_cmy, + SelectiveColorChoice::Whites => min(r, g, b) * 2. - 1., + SelectiveColorChoice::Neutrals => 1. - ((max(r, g, b) - 0.5).abs() + (min(r, g, b) - 0.5).abs()), + SelectiveColorChoice::Blacks => 1. - max(r, g, b) * 2., + }; + + let offset_r = ((c + k * (c + 1.)) * slope_r).clamp(-r, -r + 1.) * color_parameter_group_scale_factor; + let offset_g = ((m + k * (m + 1.)) * slope_g).clamp(-g, -g + 1.) * color_parameter_group_scale_factor; + let offset_b = ((y + k * (y + 1.)) * slope_b).clamp(-b, -b + 1.) * color_parameter_group_scale_factor; + + (acc.0 + offset_r, acc.1 + offset_g, acc.2 + offset_b) + }); + + let color = Color::from_rgbaf32_unchecked((r + sum_r).clamp(0., 1.), (g + sum_g).clamp(0., 1.), (b + sum_b).clamp(0., 1.), a); + + color.to_linear_srgb() +} + #[derive(Debug, Clone, Copy)] pub struct OpacityNode { opacity_multiplier: O, diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index 129bf16f..751f0134 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -150,9 +150,9 @@ impl Color { /// # Examples /// ``` /// use graphene_core::raster::color::Color; - /// let color = Color::from_rgb8(0x72, 0x67, 0x62); - /// let color2 = Color::from_rgba8(0x72, 0x67, 0x62, 0xFF); - /// assert!(color == color2) + /// let color = Color::from_rgb8_srgb(0x72, 0x67, 0x62); + /// let color2 = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0xFF); + /// assert_eq!(color, color2) /// ``` pub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color { Color::from_rgba8_srgb(red, green, blue, 255) @@ -163,7 +163,7 @@ impl Color { /// # Examples /// ``` /// use graphene_core::raster::color::Color; - /// let color = Color::from_rgba8(0x72, 0x67, 0x62, 0x61); + /// let color = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0x61); /// ``` pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color { let map_range = |int_color| int_color as f32 / 255.0; @@ -503,8 +503,8 @@ impl Color { /// # Examples /// ``` /// use graphene_core::raster::color::Color; - /// let color = Color::from_rgba8(0x7C, 0x67, 0xFA, 0x61); - /// assert!("7C67FA61" == color.rgba_hex()) + /// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61).to_gamma_srgb(); + /// assert_eq!("5267FA61", color.rgba_hex()) /// ``` #[cfg(feature = "std")] pub fn rgba_hex(&self) -> String { @@ -520,12 +520,12 @@ impl Color { /// Return a 6-character RGB hex string (without a # prefix). /// ``` /// use graphene_core::raster::color::Color; - /// let color = Color::from_rgba8(0x7C, 0x67, 0xFA, 0x61); - /// assert!("7C67FA" == color.rgb_hex()) + /// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61).to_gamma_srgb(); + /// assert_eq!("5267FA", color.rgb_hex()) /// ``` #[cfg(feature = "std")] pub fn rgb_hex(&self) -> String { - format!("{:02X?}{:02X?}{:02X?}", (self.r() * 255.) as u8, (self.g() * 255.) as u8, (self.b() * 255.) as u8,) + format!("{:02X?}{:02X?}{:02X?}", (self.r() * 255.) as u8, (self.g() * 255.) as u8, (self.b() * 255.) as u8) } /// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 0b85acdd..2c7689d4 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -1,8 +1,7 @@ -use core::marker::PhantomData; -use dyn_any::{StaticType, StaticTypeSized}; - use crate::Node; +use core::marker::PhantomData; + #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct IntNode; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 6e696983..5b1caaad 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -39,6 +39,9 @@ pub enum TaggedValue { Fill(graphene_core::vector::style::Fill), Stroke(graphene_core::vector::style::Stroke), VecF32(Vec), + RedGreenBlue(graphene_core::raster::RedGreenBlue), + RelativeAbsolute(graphene_core::raster::RelativeAbsolute), + SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice), LineCap(graphene_core::vector::style::LineCap), LineJoin(graphene_core::vector::style::LineJoin), FillType(graphene_core::vector::style::FillType), @@ -92,6 +95,9 @@ impl Hash for TaggedValue { Self::Fill(fill) => fill.hash(state), Self::Stroke(stroke) => stroke.hash(state), Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)), + Self::RedGreenBlue(red_green_blue) => red_green_blue.hash(state), + Self::RelativeAbsolute(relative_absolute) => relative_absolute.hash(state), + Self::SelectiveColorChoice(selective_color_choice) => selective_color_choice.hash(state), Self::LineCap(line_cap) => line_cap.hash(state), Self::LineJoin(line_join) => line_join.hash(state), Self::FillType(fill_type) => fill_type.hash(state), @@ -151,6 +157,9 @@ impl<'a> TaggedValue { TaggedValue::Fill(x) => Box::new(x), TaggedValue::Stroke(x) => Box::new(x), TaggedValue::VecF32(x) => Box::new(x), + TaggedValue::RedGreenBlue(x) => Box::new(x), + TaggedValue::RelativeAbsolute(x) => Box::new(x), + TaggedValue::SelectiveColorChoice(x) => Box::new(x), TaggedValue::LineCap(x) => Box::new(x), TaggedValue::LineJoin(x) => Box::new(x), TaggedValue::FillType(x) => Box::new(x), @@ -193,6 +202,9 @@ impl<'a> TaggedValue { TaggedValue::Fill(_) => concrete!(graphene_core::vector::style::Fill), TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke), TaggedValue::VecF32(_) => concrete!(Vec), + TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue), + TaggedValue::RelativeAbsolute(_) => concrete!(graphene_core::raster::RelativeAbsolute), + TaggedValue::SelectiveColorChoice(_) => concrete!(graphene_core::raster::SelectiveColorChoice), TaggedValue::LineCap(_) => concrete!(graphene_core::vector::style::LineCap), TaggedValue::LineJoin(_) => concrete!(graphene_core::vector::style::LineJoin), TaggedValue::FillType(_) => concrete!(graphene_core::vector::style::FillType), diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 419d595e..678c543c 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,5 +1,4 @@ -use dyn_any::{DynAny, StaticType, StaticTypeSized}; - +use dyn_any::{DynAny, StaticType}; use glam::{DAffine2, DVec2}; use graphene_core::raster::{Alpha, Channel, Image, ImageFrame, Luminance, Pixel, RasterMut, Sample}; use graphene_core::transform::Transform; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 1cf093d2..43f74ff7 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -262,6 +262,10 @@ fn node_registry() -> HashMap, params: [bool, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64] ), + raster_node!( + graphene_core::raster::SelectiveColorNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, + params: [RelativeAbsolute, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64] + ), vec![( NodeIdentifier::new("graphene_core::raster::BrightnessContrastNode<_, _, _>"), |args| {