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::<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::<Color>() => color_widget(default_info, ColorInput::default()),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default()),
// ==========================
// 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<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
// ============
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::<Curve>() => curve_widget(default_info),
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
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(
color_button
.value(match color_table.iter().next() {
@ -1171,7 +1163,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
color_button
.value(match gradient_table.iter().next() {
Some(row) => FillChoice::Gradient(row.element.clone()),
None => FillChoice::None,
None => FillChoice::Gradient(GradientStops::default()),
})
.on_update(update_value(
|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)
.widget_instance(),
),
TaggedValue::GradientStops(gradient_stops) => widgets.push(
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:?}"),
x => warn!("Color {x:?}"),
}
LayoutGroup::Row { widgets }

View File

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

View File

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

View File

@ -282,13 +282,15 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::math_nodes::footprint_value::IDENTIFIER,
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 {
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 {
node: graphene_std::math_nodes::greater_than::IDENTIFIER,

View File

@ -26,6 +26,7 @@ use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
pub use std::sync::Arc;
use text_nodes::vector_types::GradientStop;
pub struct TaggedValueTypeError;
@ -182,7 +183,6 @@ tagged_value! {
U64(u64),
Bool(bool),
String(String),
ColorNotInTable(Color),
// ========================
// LISTS OF PRIMITIVE TYPES
// ========================
@ -209,8 +209,10 @@ tagged_value! {
#[serde(alias = "ArtboardGroup")]
Artboard(Table<Artboard>),
#[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>),
#[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>),
// ============
// STRUCT TYPES
@ -222,8 +224,6 @@ tagged_value! {
DAffine2(DAffine2),
Stroke(graphic_types::vector_types::vector::style::Stroke),
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),
BrushStrokes(Vec<BrushStroke>),
BrushCache(BrushCache),
@ -333,6 +333,35 @@ impl TaggedValue {
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> {
let mut choices = input.split("::");
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::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
() 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<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::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => 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::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 => 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 => Box<graphene_std::vector::VectorModification>]),
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 => 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 => 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<NodeId>]),
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")]
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 => 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 => Graphic]),

View File

@ -12,11 +12,8 @@ use core_types::uuid::{NodeId, generate_uuid};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphic_types::Vector;
use graphic_types::raster_types::BitmapMut;
use graphic_types::raster_types::Image;
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::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
use graphic_types::vector_types::subpath::Subpath;
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
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 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> core_types::bounds::RenderBoundingBox {
core_types::bounds::RenderBoundingBox::Infinite

View File

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

View File

@ -3,7 +3,8 @@ use core_types::transform::Footprint;
use core_types::uuid::NodeId;
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
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};
const DAY: f64 = 1000. * 3600. * 24.;
@ -84,7 +85,6 @@ async fn quantize_real_time<T>(
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> GradientStops,
Context -> (),
)]
value: impl Node<'n, Context<'static>, Output = T>,
@ -128,7 +128,6 @@ async fn quantize_animation_time<T>(
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> GradientStops,
Context -> (),
)]
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::{Color, OwnedContextImpl};
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};
/// 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<Artboard>,
Context -> Table<GradientStops>,
Context -> GradientStops,
)]
value: impl Node<Context<'static>, Output = T>,
/// 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::{Context, Ctx};
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};
/// 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<GPU>>,
Context -> Table<Color>,
Context -> GradientStops,
Context -> Table<GradientStops>,
)]
if_true: impl Node<C, Output = T>,
#[expose]
@ -171,7 +172,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> GradientStops,
Context -> Table<GradientStops>,
)]
if_false: impl Node<C, Output = 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.
#[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 total_colors = colors.len();
if total_colors == 0 {
return GradientStops::new(vec![GradientStop {
position: 0.,
midpoint: 0.5,
color: Color::BLACK,
}]);
return Table::new_from_element(GradientStops::new(vec![
GradientStop {
position: 0.,
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 {
position: index as f64 / (total_colors - 1).max(1) as f64,
position: index as f64 / (total_colors - 1) as f64,
midpoint: 0.5,
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.
#[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
}
/// 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).
#[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 color = gradient.evaluate(position);
let color = row.element.evaluate(position);
Table::new_from_element(color)
}

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ async fn assign_colors<T>(
stroke: bool,
/// The range of colors to select from.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
gradient: GradientStops,
gradient: Table<GradientStops>,
/// Whether to reverse the gradient.
reverse: bool,
/// Whether to randomize the color selection for each element from throughout the gradient.
@ -76,8 +76,10 @@ async fn assign_colors<T>(
where
T: VectorTableIterMut + 'n + Send,
{
let Some(row) = gradient.into_iter().next() else { return content };
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());