diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 8a7d1833f2..8e2eeca2dc 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -1057,12 +1057,18 @@ pub async fn flatten_gradient(_: impl Ctx, #[implementations content.into_flattened_list() } -/// Constructs a gradient from a `Color[]`, where the colors are evenly distributed as gradient stops across the range from 0 to 1. +/// Constructs a gradient from a `Color[]`, where each color becomes a gradient stop. A `position` attribute on the colors places their stops along the ramp and a `midpoint` attribute skews each transition, while colors carrying neither are distributed evenly across the 0 to 1 range. #[node_macro::node(category("Gradient"), name("Colors to Gradient"))] fn colors_to_gradient(_: impl Ctx, #[implementations(List, List)] colors: T) -> Item { Item::new_from_element(Gradient::from(colors.into_flattened_list::())) } +/// Unwraps a gradient into a `Color[]` of its stops, keeping any `position` and `midpoint` attributes that place them along the ramp. Attributes belonging to the gradient as a whole (like spread and interpolation), rather than its individual color stops, are not preserved. +#[node_macro::node(category("Gradient"), name("Gradient to Colors"))] +fn gradient_to_colors(_: impl Ctx, gradient: Item) -> List { + gradient.into_element().into_color_list() +} + #[cfg(test)] mod test { use super::*; @@ -1186,4 +1192,30 @@ mod test { let portion = list_slice((), list_of([1., 2., 3., 4., 5.]), Item::new_from_element(3.), Item::new_from_element(3.)); assert!(elements(&portion).is_empty()); } + + #[test] + fn gradient_to_colors_round_trips_the_stops_with_their_placement() { + let mut gradient = Gradient::from(vec![Color::RED, Color::GREEN, Color::BLUE]); + gradient.set_positions(&[0., 0.25, 1.]); + gradient.set_midpoints(&[0.3, 0.5, 0.5]); + + let colors = gradient_to_colors((), Item::new_from_element(gradient.clone())); + assert_eq!(elements(&colors), [Color::RED, Color::GREEN, Color::BLUE], "every stop should come out as its color"); + + let restored = colors_to_gradient((), colors); + assert_eq!( + restored.element(), + &gradient, + "wrapping the stops back up should restore the uneven placement rather than redistributing them" + ); + } + + #[test] + fn gradient_to_colors_leaves_an_untouched_ramp_without_placement_attributes() { + let gradient = Gradient::from(vec![Color::RED, Color::GREEN, Color::BLUE]); + assert!(!gradient.has_position_attribute(), "even spacing is stored as the attribute's absence"); + + let restored = colors_to_gradient((), gradient_to_colors((), Item::new_from_element(gradient.clone()))); + assert_eq!(restored.element(), &gradient, "a default ramp should round trip without gaining attributes it never had"); + } } diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 123d9dfcaf..9a8b330c67 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -1436,9 +1436,9 @@ fn gradient_positions(_: impl Ctx, gradient: Item, positions: List