Add the Selective Color adjustment node
This commit is contained in:
parent
76c754d38a
commit
161bbc62b4
|
|
@ -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<DocumentNodeType> {
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -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<LayoutGroup> {
|
||||
// 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<LayoutGroup> {
|
||||
// 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<LayoutGroup> {
|
||||
let map = text_widget(document_node, node_id, 1, "Map", true);
|
||||
|
|
|
|||
|
|
@ -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, MonochromeR, MonochromeG, MonochromeB, MonochromeC, RedR, RedG, RedB, RedC, GreenR, GreenG, GreenB, GreenC, BlueR, BlueG, BlueB, BlueC> {
|
||||
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<Absolute, RC, RM, RY, RK, YC, YM, YY, YK, GC, GM, GY, GK, CC, CM, CY, CK, BC, BM, BY, BK, MC, MM, MY, MK, WC, WM, WY, WK, NC, NM, NY, NK, KC, KM, KY, KK> {
|
||||
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<O> {
|
||||
opacity_multiplier: O,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<const N: u32>;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ pub enum TaggedValue {
|
|||
Fill(graphene_core::vector::style::Fill),
|
||||
Stroke(graphene_core::vector::style::Stroke),
|
||||
VecF32(Vec<f32>),
|
||||
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<f32>),
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -262,6 +262,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
|
||||
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| {
|
||||
|
|
|
|||
Loading…
Reference in New Issue