Add the Selective Color adjustment node

This commit is contained in:
Keavon Chambers 2023-04-19 03:02:50 -07:00
parent 76c754d38a
commit 161bbc62b4
8 changed files with 411 additions and 32 deletions

View File

@ -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",

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::{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);

View File

@ -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,

View File

@ -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.

View File

@ -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>;

View File

@ -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),

View File

@ -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;

View File

@ -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| {