Make the node graph use Table<GradientStops> instead of GradientStops (#3837)

* Switch from GradientStops to Table<GradientStops> in all nodes

* Remove TaggedValue::ColorNotInTable

* Fix bug

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Add migrations

* Fix default gradient on empty table

* Update demo artwork

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Keavon Chambers 2026-02-26 14:07:31 -08:00 committed by GitHub
parent 81c73d11ff
commit f1cbc4b396
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 132 additions and 111 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -214,19 +214,19 @@ 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
// ========================== // ==========================
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(), Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(), Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
// ===========
// TABLE TYPES
// ===========
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
// ============ // ============
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info), Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info), Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
Some(x) if x == TypeId::of::<Footprint>() => footprint_widget(default_info, &mut extra_widgets), Some(x) if x == TypeId::of::<Footprint>() => footprint_widget(default_info, &mut extra_widgets),
@ -1145,14 +1145,6 @@ 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_instance(),
),
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() {
@ -1171,7 +1163,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
color_button color_button
.value(match gradient_table.iter().next() { .value(match gradient_table.iter().next() {
Some(row) => FillChoice::Gradient(row.element.clone()), Some(row) => FillChoice::Gradient(row.element.clone()),
None => FillChoice::None, None => FillChoice::Gradient(GradientStops::default()),
}) })
.on_update(update_value( .on_update(update_value(
|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()), |input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),
@ -1181,18 +1173,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
.on_commit(commit_value) .on_commit(commit_value)
.widget_instance(), .widget_instance(),
), ),
TaggedValue::GradientStops(gradient_stops) => widgets.push( x => warn!("Color {x:?}"),
color_button
.value(FillChoice::Gradient(gradient_stops.clone()))
.on_update(update_value(
|input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()),
node_id,
index,
))
.on_commit(commit_value)
.widget_instance(),
),
x => warn!("Colour {x:?}"),
} }
LayoutGroup::Row { widgets } LayoutGroup::Row { widgets }

View File

@ -35,7 +35,7 @@ impl FrontendGraphDataType {
TaggedValue::Raster(_) => Self::Raster, TaggedValue::Raster(_) => Self::Raster,
TaggedValue::Vector(_) => Self::Vector, TaggedValue::Vector(_) => Self::Vector,
TaggedValue::Color(_) => Self::Color, TaggedValue::Color(_) => Self::Color,
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient, TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => Self::Gradient,
TaggedValue::String(_) | TaggedValue::VecString(_) => Self::Typography, TaggedValue::String(_) | TaggedValue::VecString(_) => Self::Typography,
_ => Self::General, _ => Self::General,
} }

View File

@ -67,7 +67,7 @@ impl TypeSource {
TaggedValue::Raster(_) => FrontendGraphDataType::Raster, TaggedValue::Raster(_) => FrontendGraphDataType::Raster,
TaggedValue::Vector(_) => FrontendGraphDataType::Vector, TaggedValue::Vector(_) => FrontendGraphDataType::Vector,
TaggedValue::Color(_) => FrontendGraphDataType::Color, TaggedValue::Color(_) => FrontendGraphDataType::Color,
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient, TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient,
TaggedValue::String(_) => FrontendGraphDataType::Typography, TaggedValue::String(_) => FrontendGraphDataType::Typography,
_ => FrontendGraphDataType::General, _ => FrontendGraphDataType::General,
}, },

View File

@ -282,13 +282,15 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::math_nodes::footprint_value::IDENTIFIER, node: graphene_std::math_nodes::footprint_value::IDENTIFIER,
aliases: &["graphene_math_nodes::FootprintValueNode", "graphene_core::ops::FootprintValueNode"], aliases: &["graphene_math_nodes::FootprintValueNode", "graphene_core::ops::FootprintValueNode"],
}, },
NodeReplacement {
node: graphene_std::math_nodes::gradient_table_value::IDENTIFIER,
aliases: &["graphene_math_nodes::GradientTableValueNode", "graphene_core::ops::GradientTableValueNode"],
},
NodeReplacement { NodeReplacement {
node: graphene_std::math_nodes::gradient_value::IDENTIFIER, node: graphene_std::math_nodes::gradient_value::IDENTIFIER,
aliases: &["graphene_math_nodes::GradientValueNode", "graphene_core::ops::GradientValueNode"], aliases: &[
"graphene_math_nodes::GradientValueNode",
"graphene_core::ops::GradientValueNode",
"graphene_math_nodes::GradientTableValueNode",
"graphene_core::ops::GradientTableValueNode",
"math_nodes::GradientTableValueNode",
],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::math_nodes::greater_than::IDENTIFIER, node: graphene_std::math_nodes::greater_than::IDENTIFIER,

View File

@ -26,6 +26,7 @@ use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
pub use std::sync::Arc; pub use std::sync::Arc;
use text_nodes::vector_types::GradientStop;
pub struct TaggedValueTypeError; pub struct TaggedValueTypeError;
@ -182,7 +183,6 @@ tagged_value! {
U64(u64), U64(u64),
Bool(bool), Bool(bool),
String(String), String(String),
ColorNotInTable(Color),
// ======================== // ========================
// LISTS OF PRIMITIVE TYPES // LISTS OF PRIMITIVE TYPES
// ======================== // ========================
@ -209,8 +209,10 @@ tagged_value! {
#[serde(alias = "ArtboardGroup")] #[serde(alias = "ArtboardGroup")]
Artboard(Table<Artboard>), Artboard(Table<Artboard>),
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code #[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ColorTable", alias = "OptionalColor")] #[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
Color(Table<Color>), Color(Table<Color>),
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GradientPositions", alias = "GradientStops")]
GradientTable(Table<GradientStops>), GradientTable(Table<GradientStops>),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
@ -222,8 +224,6 @@ tagged_value! {
DAffine2(DAffine2), DAffine2(DAffine2),
Stroke(graphic_types::vector_types::vector::style::Stroke), Stroke(graphic_types::vector_types::vector::style::Stroke),
Gradient(graphic_types::vector_types::vector::style::Gradient), Gradient(graphic_types::vector_types::vector::style::Gradient),
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
GradientStops(GradientStops),
Font(text_nodes::Font), Font(text_nodes::Font),
BrushStrokes(Vec<BrushStroke>), BrushStrokes(Vec<BrushStroke>),
BrushCache(BrushCache), BrushCache(BrushCache),
@ -333,6 +333,35 @@ impl TaggedValue {
None None
} }
fn to_gradient(input: &str) -> Option<GradientStops> {
// String syntax: (e.g. "000000ff, ff0000ff")
let stops = input.split(',').filter_map(|s| to_color(s.trim())).collect::<Vec<_>>();
if stops.len() == 1 {
Some(GradientStops::new(vec![
GradientStop {
position: 0.,
midpoint: 0.5,
color: stops[0],
},
GradientStop {
position: 1.,
midpoint: 0.5,
color: stops[0],
},
]))
} else if stops.len() >= 2 {
let step = 1. / (stops.len() - 1) as f64;
Some(GradientStops::new(stops.into_iter().enumerate().map(|(i, color)| GradientStop {
position: i as f64 * step,
midpoint: 0.5,
color,
})))
} else {
log::error!("Invalid default value gradient string: {input}");
None
}
}
fn to_reference_point(input: &str) -> Option<ReferencePoint> { fn to_reference_point(input: &str) -> Option<ReferencePoint> {
let mut choices = input.split("::"); let mut choices = input.split("::");
let (first, second) = (choices.next()?.trim(), choices.next()?.trim()); let (first, second) = (choices.next()?.trim(), choices.next()?.trim());
@ -375,9 +404,8 @@ impl TaggedValue {
() if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, () if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?, () if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, () if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
() if ty == TypeId::of::<Color>() => to_color(string).map(TaggedValue::ColorNotInTable)?,
() if ty == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, () if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?,
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, () if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, () if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => return None, _ => return None,
@ -537,8 +565,3 @@ mod fake_hash {
} }
} }
} }
#[test]
fn can_construct_color() {
assert_eq!(TaggedValue::from_type(&concrete!(Color)).unwrap(), TaggedValue::ColorNotInTable(Color::default()));
}

View File

@ -94,7 +94,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GradientStops]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_std::uuid::NodeId>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_std::uuid::NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
@ -157,7 +156,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GradientStops]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<NodeId>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<NodeId>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]),
@ -183,7 +181,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Color]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),

View File

@ -12,11 +12,8 @@ use core_types::uuid::{NodeId, generate_uuid};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::Vector; use graphic_types::Vector;
use graphic_types::raster_types::BitmapMut; use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
use graphic_types::raster_types::Image; use graphic_types::vector_types::gradient::{GradientStops, GradientType};
use graphic_types::raster_types::{CPU, GPU, Raster};
use graphic_types::vector_types::gradient::GradientStops;
use graphic_types::vector_types::gradient::GradientType;
use graphic_types::vector_types::subpath::Subpath; use graphic_types::vector_types::subpath::Subpath;
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint}; use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign}; use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};

View File

@ -447,6 +447,24 @@ impl Gradient {
} }
} }
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<core_types::table::Table<GradientStops>, D::Error> {
use core_types::table::Table;
use serde::Deserialize;
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum GradientStopsFormat {
GradientStops(GradientStops),
GradientTable(Table<GradientStops>),
}
Ok(match GradientStopsFormat::deserialize(deserializer)? {
GradientStopsFormat::GradientStops(stops) => Table::new_from_element(stops),
GradientStopsFormat::GradientTable(table) => table,
})
}
impl core_types::bounds::BoundingBox for GradientStops { impl core_types::bounds::BoundingBox for GradientStops {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> core_types::bounds::RenderBoundingBox { fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> core_types::bounds::RenderBoundingBox {
core_types::bounds::RenderBoundingBox::Infinite core_types::bounds::RenderBoundingBox::Infinite

View File

@ -1455,11 +1455,9 @@ mod tests {
() -> Table<Raster<CPU>>, () -> Table<Raster<CPU>>,
() -> Table<Color>, () -> Table<Color>,
() -> Table<GradientStops>, () -> Table<GradientStops>,
() -> GradientStops,
Footprint -> Table<Raster<CPU>>, Footprint -> Table<Raster<CPU>>,
Footprint -> Table<Color>, Footprint -> Table<Color>,
Footprint -> Table<GradientStops>, Footprint -> Table<GradientStops>,
Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
) -> T { ) -> T {

View File

@ -3,7 +3,8 @@ use core_types::transform::Footprint;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl}; use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::{Artboard, Graphic, Vector, vector_types::GradientStops}; use graphic_types::vector_types::GradientStops;
use graphic_types::{Artboard, Graphic, Vector};
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
const DAY: f64 = 1000. * 3600. * 24.; const DAY: f64 = 1000. * 3600. * 24.;
@ -84,7 +85,6 @@ async fn quantize_real_time<T>(
Context -> Table<Color>, Context -> Table<Color>,
Context -> Table<Artboard>, Context -> Table<Artboard>,
Context -> Table<GradientStops>, Context -> Table<GradientStops>,
Context -> GradientStops,
Context -> (), Context -> (),
)] )]
value: impl Node<'n, Context<'static>, Output = T>, value: impl Node<'n, Context<'static>, Output = T>,
@ -128,7 +128,6 @@ async fn quantize_animation_time<T>(
Context -> Table<Color>, Context -> Table<Color>,
Context -> Table<Artboard>, Context -> Table<Artboard>,
Context -> Table<GradientStops>, Context -> Table<GradientStops>,
Context -> GradientStops,
Context -> (), Context -> (),
)] )]
value: impl Node<'n, Context<'static>, Output = T>, value: impl Node<'n, Context<'static>, Output = T>,

View File

@ -5,7 +5,8 @@ use core_types::transform::Footprint;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{Color, OwnedContextImpl}; use core_types::{Color, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::{Artboard, Graphic, Vector, vector_types::GradientStops}; use graphic_types::vector_types::GradientStops;
use graphic_types::{Artboard, Graphic, Vector};
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
/// Filters out what should be unused components of the context based on the specified requirements. /// Filters out what should be unused components of the context based on the specified requirements.
@ -37,7 +38,6 @@ async fn context_modification<T>(
Context -> Table<Color>, Context -> Table<Color>,
Context -> Table<Artboard>, Context -> Table<Artboard>,
Context -> Table<GradientStops>, Context -> Table<GradientStops>,
Context -> GradientStops,
)] )]
value: impl Node<Context<'static>, Output = T>, value: impl Node<Context<'static>, Output = T>,
/// The parts of the context to keep when evaluating the input value. All other parts are nullified. /// The parts of the context to keep when evaluating the input value. All other parts are nullified.

View File

@ -3,7 +3,8 @@ use core_types::registry::types::TextArea;
use core_types::table::Table; use core_types::table::Table;
use core_types::{Context, Ctx}; use core_types::{Context, Ctx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::{Artboard, Graphic, Vector, vector_types::GradientStops}; use graphic_types::vector_types::GradientStops;
use graphic_types::{Artboard, Graphic, Vector};
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
/// Type-asserts a value to be a string. /// Type-asserts a value to be a string.
@ -152,7 +153,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> Table<Raster<CPU>>, Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> Table<Raster<GPU>>,
Context -> Table<Color>, Context -> Table<Color>,
Context -> GradientStops, Context -> Table<GradientStops>,
)] )]
if_true: impl Node<C, Output = T>, if_true: impl Node<C, Output = T>,
#[expose] #[expose]
@ -171,7 +172,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> Table<Raster<CPU>>, Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> Table<Raster<GPU>>,
Context -> Table<Color>, Context -> Table<Color>,
Context -> GradientStops, Context -> Table<GradientStops>,
)] )]
if_false: impl Node<C, Output = T>, if_false: impl Node<C, Output = T>,
) -> T { ) -> T {

View File

@ -318,22 +318,47 @@ pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl C
/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1. /// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> GradientStops { fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> {
let colors = colors.into_flattened_table::<Color>(); let colors = colors.into_flattened_table::<Color>();
let total_colors = colors.len(); let total_colors = colors.len();
if total_colors == 0 { if total_colors == 0 {
return GradientStops::new(vec![GradientStop { return Table::new_from_element(GradientStops::new(vec![
position: 0., GradientStop {
midpoint: 0.5, position: 0.,
color: Color::BLACK, midpoint: 0.5,
}]); color: Color::BLACK,
},
GradientStop {
position: 1.,
midpoint: 0.5,
color: Color::BLACK,
},
]));
}
if let (Some(color), None) = {
let mut colors_iter = colors.iter();
(colors_iter.next(), colors_iter.next())
} {
return Table::new_from_element(GradientStops::new(vec![
GradientStop {
position: 0.,
midpoint: 0.5,
color: *color.element,
},
GradientStop {
position: 1.,
midpoint: 0.5,
color: *color.element,
},
]));
} }
let colors = colors.into_iter().enumerate().map(|(index, row)| GradientStop { let colors = colors.into_iter().enumerate().map(|(index, row)| GradientStop {
position: index as f64 / (total_colors - 1).max(1) as f64, position: index as f64 / (total_colors - 1) as f64,
midpoint: 0.5, midpoint: 0.5,
color: row.element, color: row.element,
}); });
GradientStops::new(colors) Table::new_from_element(GradientStops::new(colors))
} }

View File

@ -767,21 +767,17 @@ fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Table<
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
#[node_macro::node(category("Value"))] #[node_macro::node(category("Value"))]
fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { fn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) -> Table<GradientStops> {
gradient gradient
} }
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
#[node_macro::node(category("Value"))]
fn gradient_table_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> Table<GradientStops> {
Table::new_from_element(gradient)
}
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). /// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn sample_gradient(_: impl Ctx, _primary: (), gradient: GradientStops, position: Fraction) -> Table<Color> { fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> {
let Some(row) = gradient.get(0) else { return Table::new() };
let position = position.clamp(0., 1.); let position = position.clamp(0., 1.);
let color = gradient.evaluate(position); let color = row.element.evaluate(position);
Table::new_from_element(color) Table::new_from_element(color)
} }

View File

@ -54,7 +54,6 @@ fn luminance<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -80,7 +79,6 @@ fn gamma_correction<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -102,7 +100,6 @@ fn extract_channel<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -127,7 +124,6 @@ fn make_opaque<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -154,7 +150,6 @@ fn brightness_contrast_classic<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -186,7 +181,6 @@ fn brightness_contrast<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -268,7 +262,6 @@ fn levels<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -337,7 +330,6 @@ fn black_and_white<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -411,7 +403,6 @@ fn hue_saturation<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -447,7 +438,6 @@ fn invert<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -471,7 +461,6 @@ fn threshold<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -518,7 +507,6 @@ fn vibrance<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -684,7 +672,6 @@ fn channel_mixer<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -815,7 +802,6 @@ fn selective_color<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@ -962,7 +948,6 @@ fn posterize<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@ -997,7 +982,6 @@ fn exposure<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,

View File

@ -136,7 +136,6 @@ fn blend<T: Blend<Color> + Send>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
over: T, over: T,
@ -145,7 +144,6 @@ fn blend<T: Blend<Color> + Send>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
under: T, under: T,
@ -162,7 +160,6 @@ fn color_overlay<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,

View File

@ -16,16 +16,17 @@ async fn gradient_map<T: Adjust<Color>>(
Table<Raster<CPU>>, Table<Raster<CPU>>,
Table<Color>, Table<Color>,
Table<GradientStops>, Table<GradientStops>,
GradientStops,
)] )]
mut image: T, mut image: T,
gradient: GradientStops, gradient: Table<GradientStops>,
reverse: bool, reverse: bool,
) -> T { ) -> T {
let Some(row) = gradient.get(0) else { return image };
image.adjust(|color| { image.adjust(|color| {
let intensity = color.luminance_srgb(); let intensity = color.luminance_srgb();
let intensity = if reverse { 1. - intensity } else { intensity }; let intensity = if reverse { 1. - intensity } else { intensity };
gradient.evaluate(intensity as f64).to_linear_srgb() row.element.evaluate(intensity as f64).to_linear_srgb()
}); });
image image

View File

@ -60,7 +60,7 @@ async fn assign_colors<T>(
stroke: bool, stroke: bool,
/// The range of colors to select from. /// The range of colors to select from.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
gradient: GradientStops, gradient: Table<GradientStops>,
/// Whether to reverse the gradient. /// Whether to reverse the gradient.
reverse: bool, reverse: bool,
/// Whether to randomize the color selection for each element from throughout the gradient. /// Whether to randomize the color selection for each element from throughout the gradient.
@ -76,8 +76,10 @@ async fn assign_colors<T>(
where where
T: VectorTableIterMut + 'n + Send, T: VectorTableIterMut + 'n + Send,
{ {
let Some(row) = gradient.into_iter().next() else { return content };
let length = content.vector_iter_mut().count(); let length = content.vector_iter_mut().count();
let gradient = if reverse { gradient.reversed() } else { gradient }; let gradient = if reverse { row.element.reversed() } else { row.element };
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());