Change `Table<Color>` node inputs to `Color` where only one value is used so GPU nodes work (#3096)

* graster-nodes: change `Table<Color>` params to `Color` where only one value is used

* Re-add support for Color and Option<Color>

* Add warning when a default value isn't parsed

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Firestar99 2025-08-28 15:16:56 +02:00 committed by GitHub
parent 9987112cc9
commit 95ef8a5343
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 46 additions and 23 deletions

1
Cargo.lock generated
View File

@ -4129,6 +4129,7 @@ dependencies = [
"graph-craft", "graph-craft",
"graphene-std", "graphene-std",
"interpreted-executor", "interpreted-executor",
"log",
] ]
[[package]] [[package]]

View File

@ -173,6 +173,8 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(), Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false), Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false),
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets), Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default()),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default()),
// ========================== // ==========================
// PRIMITIVE COLLECTION TYPES // PRIMITIVE COLLECTION TYPES
// ========================== // ==========================
@ -926,6 +928,22 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
// Add the color input // Add the color input
match &**tagged_value { match &**tagged_value {
TaggedValue::ColorNotInTable(color) => widgets.push(
color_button
.value(FillChoice::Solid(*color))
.allow_none(false)
.on_update(update_value(|input: &ColorInput| TaggedValue::ColorNotInTable(input.value.as_solid().unwrap()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::OptionalColorNotInTable(color) => widgets.push(
color_button
.value(color.map_or(FillChoice::None, FillChoice::Solid))
.allow_none(true)
.on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColorNotInTable(input.value.as_solid()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::Color(color_table) => widgets.push( TaggedValue::Color(color_table) => widgets.push(
color_button color_button
.value(match color_table.iter().next() { .value(match color_table.iter().next() {
@ -965,7 +983,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
.on_commit(commit_value) .on_commit(commit_value)
.widget_holder(), .widget_holder(),
), ),
_ => {} x => warn!("Colour {x:?}"),
} }
LayoutGroup::Row { widgets } LayoutGroup::Row { widgets }

View File

@ -171,6 +171,8 @@ tagged_value! {
Bool(bool), Bool(bool),
String(String), String(String),
OptionalF64(Option<f64>), OptionalF64(Option<f64>),
ColorNotInTable(Color),
OptionalColorNotInTable(Option<Color>),
// ======================== // ========================
// LISTS OF PRIMITIVE TYPES // LISTS OF PRIMITIVE TYPES
// ======================== // ========================
@ -358,6 +360,8 @@ impl TaggedValue {
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?, x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
x if x == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::ColorNotInTable(color))?,
x if x == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => return None, _ => return None,
@ -512,3 +516,8 @@ mod fake_hash {
} }
} }
} }
#[test]
fn can_construct_color() {
assert_eq!(TaggedValue::from_type(&concrete!(Color)).unwrap(), TaggedValue::ColorNotInTable(Color::default()));
}

View File

@ -307,7 +307,7 @@ fn black_and_white<T: Adjust<Color>>(
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
#[default(Color::BLACK)] tint: Table<Color>, #[default(Color::BLACK)] tint: Color,
#[default(40.)] #[default(40.)]
#[range((-200., 300.))] #[range((-200., 300.))]
reds: PercentageF32, reds: PercentageF32,
@ -327,9 +327,6 @@ fn black_and_white<T: Adjust<Color>>(
#[range((-200., 300.))] #[range((-200., 300.))]
magentas: PercentageF32, magentas: PercentageF32,
) -> T { ) -> T {
let tint: Option<Color> = tint.into();
let tint = tint.unwrap_or(Color::BLACK);
image.adjust(|color| { image.adjust(|color| {
let color = color.to_gamma_srgb(); let color = color.to_gamma_srgb();

View File

@ -166,15 +166,12 @@ fn color_overlay<T: Adjust<Color>>(
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
#[default(Color::BLACK)] color: Table<Color>, #[default(Color::BLACK)] color: Color,
blend_mode: BlendMode, blend_mode: BlendMode,
#[default(100.)] opacity: PercentageF32, #[default(100.)] opacity: PercentageF32,
) -> T { ) -> T {
let opacity = (opacity as f32 / 100.).clamp(0., 1.); let opacity = (opacity as f32 / 100.).clamp(0., 1.);
let color: Option<Color> = color.into();
let color = color.unwrap_or(Color::BLACK);
image.adjust(|pixel| { image.adjust(|pixel| {
let image = pixel.map_rgb(|channel| channel * (1. - opacity)); let image = pixel.map_rgb(|channel| channel * (1. - opacity));
@ -206,13 +203,7 @@ mod test {
// 100% of the output should come from the multiplied value // 100% of the output should come from the multiplied value
let opacity = 100.; let opacity = 100.;
let result = super::color_overlay( let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
(),
Table::new_from_element(Raster::new_cpu(image.clone())),
Table::new_from_element(overlay_color),
BlendMode::Multiply,
opacity,
);
let result = result.iter().next().unwrap().element; let result = result.iter().next().unwrap().element;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)

View File

@ -17,12 +17,8 @@ fn text<'i: 'n>(
#[unit(" px")] #[unit(" px")]
#[default(0.)] #[default(0.)]
character_spacing: f64, character_spacing: f64,
#[unit(" px")] #[unit(" px")] max_width: Option<f64>,
#[default(None)] #[unit(" px")] max_height: Option<f64>,
max_width: Option<f64>,
#[unit(" px")]
#[default(None)]
max_height: Option<f64>,
/// Faux italic. /// Faux italic.
#[unit("°")] #[unit("°")]
#[default(0.)] #[default(0.)]

View File

@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0"
[features] [features]
[dependencies] [dependencies]
log = { workspace = true }
# Workspace dependencies # Workspace dependencies
graphene-std = { workspace = true, features = ["gpu"] } graphene-std = { workspace = true, features = ["gpu"] }

View File

@ -1,3 +1,6 @@
#[macro_use]
extern crate log;
use graph_craft::document::value::*; use graph_craft::document::value::*;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::proto::RegistryValueSource; use graph_craft::proto::RegistryValueSource;
@ -150,7 +153,14 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp
match field.value_source { match field.value_source {
RegistryValueSource::None => {} RegistryValueSource::None => {}
RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), RegistryValueSource::Default(data) => {
if let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) {
return NodeInput::value(custom_default, exposed);
} else {
// It is incredibly useful to get a warning when the default type cannot be parsed rather than defaulting to `()`.
warn!("Failed to parse default value for type {ty:?} with data {data}");
}
}
RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)),
}; };