Rename spread method to gradient spread so it matches the other gradient attribute names (#4404)

* Rename spread method to gradient spread so it matches the other gradient attribute names

* Keep the legacy gradient struct's spread method field name matching its on-disk key
This commit is contained in:
Keavon Chambers
2026-08-04 05:15:21 -07:00
committed by GitHub
parent e907a4d574
commit ca44dbaf55
25 changed files with 251 additions and 246 deletions

View File

@@ -552,7 +552,8 @@ tagged_value! {
StrokeAlign(vector::style::StrokeAlign),
PaintOrder(vector::style::PaintOrder),
GradientType(vector::style::GradientType),
GradientSpreadMethod(vector::style::GradientSpreadMethod),
#[serde(alias = "GradientSpreadMethod")] // TODO: Eventually remove this document upgrade code
GradientSpread(vector::style::GradientSpread),
ReferencePoint(vector::ReferencePoint),
CentroidType(vector::misc::CentroidType),
BooleanOperation(vector::misc::BooleanOperation),
@@ -1021,7 +1022,7 @@ mod paint_default_parsing {
#[cfg(test)]
mod gradient_shape_migration {
use graphic_types::vector_types::GradientSpreadMethod;
use graphic_types::vector_types::GradientSpread;
use super::*;
@@ -1042,7 +1043,7 @@ mod gradient_shape_migration {
let mut gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
gradient.set_positions(&[0.2, 0.9]);
let value = TaggedValue::GradientRamp(GradientRamp {
spread_method: GradientSpreadMethod::Reflect,
gradient_spread: GradientSpread::Reflect,
..GradientRamp::from(gradient)
});

View File

@@ -24,7 +24,7 @@ use graphene_std::transform::{Footprint, ReferencePoint, ScaleType};
use graphene_std::vector::misc::{
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
};
use graphene_std::vector::style::{DashPattern, GradientSpreadMethod, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::style::{DashPattern, GradientSpread, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector, VectorModification};
use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
use node_registry_macros::async_node;
@@ -75,7 +75,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<BlendMode>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<GradientType>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<GradientSpreadMethod>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<GradientSpread>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Item<AttributeValueDyn>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ListDyn]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Item<BrushTrace>]),
@@ -109,7 +109,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BlendMode>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<GradientType>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<GradientSpreadMethod>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<GradientSpread>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Item<Artboard>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Item<Graphic>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Item<Vector>]),
@@ -332,7 +332,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
StrokeCap,
PaintOrder,
GradientType,
GradientSpreadMethod,
GradientSpread,
DashPattern,
BoxCorners,
MergeByDistanceAlgorithm,
@@ -421,7 +421,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
DAffine2,
BlendMode,
GradientType,
GradientSpreadMethod
GradientSpread
));
#[cfg(feature = "gpu")]
node_types.extend(list_dyn_rows!(Raster<GPU>));
@@ -536,7 +536,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
attribute_value_node!(Item<Color>),
attribute_value_node!(Item<BlendMode>),
attribute_value_node!(Item<GradientType>),
attribute_value_node!(Item<GradientSpreadMethod>),
attribute_value_node!(Item<GradientSpread>),
attribute_value_node!(Item<NodeIdPath>),
attribute_value_node!(List<String>),
attribute_value_node!(List<Color>),

View File

@@ -25,8 +25,8 @@ pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL,
ATTR_SPREAD_METHOD, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_SPREAD, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME,
ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;

View File

@@ -55,9 +55,9 @@ pub const ATTR_DIMENSIONS: &str = "dimensions";
pub const ATTR_BACKGROUND: &str = "background";
/// `bool` for whether an artboard clips content to its bounds.
pub const ATTR_CLIP: &str = "clip";
// TODO: Consider adding "spread_method_left" and "spread_method_right" override attributes to allow setting different spread methods on each side of a gradient
/// Gradient's `GradientSpreadMethod` (`Pad`, `Reflect`, or `Repeat`).
pub const ATTR_SPREAD_METHOD: &str = "spread_method";
// TODO: Consider adding "gradient_spread_left" and "gradient_spread_right" override attributes to allow setting different gradient spreads on each side of a gradient
/// Gradient's `GradientSpread` (`Pad`, `Reflect`, or `Repeat`).
pub const ATTR_GRADIENT_SPREAD: &str = "gradient_spread";
/// Gradient's `GradientType` (`Linear` or `Radial`).
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
/// Gradient stop's `f64` position from 0 to 1 along the gradient, on the `List<Color>` inside a `Gradient`.
@@ -1229,7 +1229,7 @@ impl<T: CacheHash> CacheHash for List<T> {
self.element.cache_hash(state);
// Hash every attribute attribute (key + values) rather than just the well-known ones, so changes to user-defined keys
// (e.g., gradient_type, spread_method) invalidate downstream graph caches as expected
// (e.g., gradient_type, gradient_spread) invalidate downstream graph caches as expected
for (key, attribute) in &self.attributes.attributes {
std::hash::Hash::hash(key.as_str(), state);
attribute.cache_hash_dyn(state);

View File

@@ -666,8 +666,8 @@ mod tests {
#[cfg(test)]
mod graphic_is_opaque_tests {
use core_types::ATTR_SPREAD_METHOD;
use vector_types::{GradientSpreadMethod, GradientStop};
use core_types::ATTR_GRADIENT_SPREAD;
use vector_types::{GradientSpread, GradientStop};
use super::*;
@@ -678,7 +678,7 @@ mod graphic_is_opaque_tests {
fn gradient_graphic(gradient: Gradient) -> Graphic {
let mut gradient_list = List::new_from_element(gradient);
gradient_list.set_attribute(ATTR_SPREAD_METHOD, 0, GradientSpreadMethod::Pad);
gradient_list.set_attribute(ATTR_GRADIENT_SPREAD, 0, GradientSpread::Pad);
Graphic::Gradient(gradient_list)
}

View File

@@ -33,7 +33,7 @@ pub mod migrations {
pub start: DVec2,
pub end: DVec2,
#[serde(default)]
pub spread_method: vector::style::GradientSpreadMethod,
pub spread_method: vector::style::GradientSpread,
#[serde(default)]
pub absolute: bool,
#[serde(default)]

View File

@@ -3,14 +3,14 @@ use crate::{Render, RenderSvgSegmentList, SvgRender};
use core_types::color::SRGBA8;
use core_types::list::List;
use core_types::uuid::generate_uuid;
use core_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
use core_types::{ATTR_GRADIENT_SPREAD, ATTR_GRADIENT_TYPE, ATTR_TRANSFORM, Color};
use glam::{DAffine2, DVec2};
use graphic_types::Graphic;
use graphic_types::vector_types::gradient::GradientType;
use graphic_types::vector_types::vector::style::{PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use std::fmt::Write;
use vector_types::Gradient;
use vector_types::gradient::GradientSpreadMethod;
use vector_types::gradient::GradientSpread;
#[derive(Copy, Clone, PartialEq)]
pub enum PaintTarget {
@@ -95,7 +95,7 @@ impl RenderExt for List<Gradient> {
let Some(stops) = self.element(0) else { return 0 };
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
let local_gradient_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
let gradient_spread: GradientSpread = self.attribute_cloned_or_default(ATTR_GRADIENT_SPREAD, 0);
for (position, color, original_midpoint) in stops.interpolated_samples() {
stop.push_str("<stop");
@@ -134,10 +134,10 @@ impl RenderExt for List<Gradient> {
format!(r#" gradientTransform="{gradient_transform}""#)
};
let spread_method = if spread_method == GradientSpreadMethod::Pad {
let gradient_spread = if gradient_spread == GradientSpread::Pad {
String::new()
} else {
format!(r#" spreadMethod="{}""#, spread_method.svg_name())
format!(r#" spreadMethod="{}""#, gradient_spread.svg_name())
};
let gradient_id = generate_uuid();
@@ -146,14 +146,14 @@ impl RenderExt for List<Gradient> {
GradientType::Linear => {
let _ = write!(
svg_defs,
r#"<linearGradient id="{}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{spread_method}{gradient_transform}>{}</linearGradient>"#,
r#"<linearGradient id="{}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{gradient_spread}{gradient_transform}>{}</linearGradient>"#,
gradient_id, stop
);
}
GradientType::Radial => {
let _ = write!(
svg_defs,
r#"<radialGradient id="{}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{spread_method}{gradient_transform}>{}</radialGradient>"#,
r#"<radialGradient id="{}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{gradient_spread}{gradient_transform}>{}</radialGradient>"#,
gradient_id, stop
);
}

View File

@@ -14,7 +14,7 @@ use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid};
use core_types::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_FONT,
ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD,
ATTR_FONT_SIZE, ATTR_GRADIENT_SPREAD, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL,
ATTR_TEXT_ALIGN, ATTR_TRANSFORM,
};
use dyn_any::DynAny;
@@ -39,7 +39,7 @@ use std::fmt::Write;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
use vector_types::gradient::GradientSpreadMethod;
use vector_types::gradient::GradientSpread;
use vello::*;
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -424,7 +424,7 @@ fn create_peniko_gradient_brush(gradient_list: &List<Gradient>, multiplied_trans
let gradient_type: GradientType = gradient_list.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
let gradient_transform: DAffine2 = gradient_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
let spread_method: GradientSpreadMethod = gradient_list.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
let gradient_spread: GradientSpread = gradient_list.attribute_cloned_or_default(ATTR_GRADIENT_SPREAD, 0);
let peniko_stops = peniko_color_stops(stops);
@@ -446,10 +446,10 @@ fn create_peniko_gradient_brush(gradient_list: &List<Gradient>, multiplied_trans
}
.into(),
},
extend: match spread_method {
GradientSpreadMethod::Pad => peniko::Extend::Pad,
GradientSpreadMethod::Reflect => peniko::Extend::Reflect,
GradientSpreadMethod::Repeat => peniko::Extend::Repeat,
extend: match gradient_spread {
GradientSpread::Pad => peniko::Extend::Pad,
GradientSpread::Reflect => peniko::Extend::Reflect,
GradientSpread::Repeat => peniko::Extend::Repeat,
},
stops: peniko_stops,
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
@@ -2094,7 +2094,7 @@ impl Render for List<Gradient> {
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, index);
let gradient_spread: GradientSpread = self.attribute_cloned_or_default(ATTR_GRADIENT_SPREAD, index);
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
render.leaf_tag(tag, |attributes| {
@@ -2133,10 +2133,10 @@ impl Render for List<Gradient> {
};
let gradient_id = generate_uuid();
let spread_method_attribute = if spread_method == GradientSpreadMethod::Pad {
let gradient_spread_attribute = if gradient_spread == GradientSpread::Pad {
String::new()
} else {
format!(r#" spreadMethod="{}""#, spread_method.svg_name())
format!(r#" spreadMethod="{}""#, gradient_spread.svg_name())
};
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied
@@ -2144,13 +2144,13 @@ impl Render for List<Gradient> {
GradientType::Linear => {
let _ = write!(
&mut attributes.0.svg_defs,
r#"<linearGradient id="{gradient_id}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{spread_method_attribute}{gradient_transform_attribute}>{stop_string}</linearGradient>"#
r#"<linearGradient id="{gradient_id}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{gradient_spread_attribute}{gradient_transform_attribute}>{stop_string}</linearGradient>"#
);
}
GradientType::Radial => {
let _ = write!(
&mut attributes.0.svg_defs,
r#"<radialGradient id="{gradient_id}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{spread_method_attribute}{gradient_transform_attribute}>{stop_string}</radialGradient>"#
r#"<radialGradient id="{gradient_id}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{gradient_spread_attribute}{gradient_transform_attribute}>{stop_string}</radialGradient>"#
);
}
}
@@ -2176,10 +2176,10 @@ impl Render for List<Gradient> {
return;
}
for (((index, gradient), spread_method), gradient_type) in self
for (((index, gradient), gradient_spread), gradient_type) in self
.iter_element_values()
.enumerate()
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
.zip(self.iter_attribute_values_or_default::<GradientSpread>(ATTR_GRADIENT_SPREAD))
.zip(self.iter_attribute_values_or_default::<GradientType>(ATTR_GRADIENT_TYPE))
{
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
@@ -2193,10 +2193,10 @@ impl Render for List<Gradient> {
let stops = peniko_color_stops(gradient);
let extend = match spread_method {
GradientSpreadMethod::Pad => peniko::Extend::Pad,
GradientSpreadMethod::Reflect => peniko::Extend::Reflect,
GradientSpreadMethod::Repeat => peniko::Extend::Repeat,
let extend = match gradient_spread {
GradientSpread::Pad => peniko::Extend::Pad,
GradientSpread::Reflect => peniko::Extend::Reflect,
GradientSpread::Repeat => peniko::Extend::Repeat,
};
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied.

View File

@@ -1,6 +1,6 @@
use core_types::Color;
use core_types::color::SRGBA8;
use core_types::list::{ATTR_MIDPOINT, ATTR_POSITION, ATTR_SPREAD_METHOD, Item, List};
use core_types::list::{ATTR_GRADIENT_SPREAD, ATTR_MIDPOINT, ATTR_POSITION, Item, List};
use core_types::render_complexity::RenderComplexity;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -102,9 +102,9 @@ impl GradientStops<SRGBA8> {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GradientRamp<C = Color> {
pub stops: GradientStops<C>,
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "GradientSpreadMethod::is_default"))]
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "GradientSpread::is_default"))]
#[cfg_attr(feature = "wasm", tsify(optional))]
pub spread_method: GradientSpreadMethod,
pub gradient_spread: GradientSpread,
}
unsafe impl<C: dyn_any::StaticTypeSized> dyn_any::StaticType for GradientRamp<C> {
@@ -115,7 +115,7 @@ impl<C> From<GradientStops<C>> for GradientRamp<C> {
fn from(stops: GradientStops<C>) -> Self {
Self {
stops,
spread_method: Default::default(),
gradient_spread: Default::default(),
}
}
}
@@ -124,7 +124,7 @@ impl From<&Gradient> for GradientRamp {
fn from(gradient: &Gradient) -> Self {
Self {
stops: gradient.into(),
spread_method: Default::default(),
gradient_spread: Default::default(),
}
}
}
@@ -152,8 +152,8 @@ impl From<&GradientRamp> for Gradient {
impl From<GradientRamp> for Item<Gradient> {
fn from(ramp: GradientRamp) -> Self {
let mut item = Item::new_from_element(Gradient::from(ramp.stops));
if !ramp.spread_method.is_default() {
item.set_attribute(ATTR_SPREAD_METHOD, ramp.spread_method);
if !ramp.gradient_spread.is_default() {
item.set_attribute(ATTR_GRADIENT_SPREAD, ramp.gradient_spread);
}
item
}
@@ -163,7 +163,7 @@ impl From<&Item<Gradient>> for GradientRamp {
fn from(item: &Item<Gradient>) -> Self {
Self {
stops: item.element().into(),
spread_method: item.attribute_cloned_or_default(ATTR_SPREAD_METHOD),
gradient_spread: item.attribute_cloned_or_default(ATTR_GRADIENT_SPREAD),
}
}
}
@@ -189,7 +189,7 @@ impl From<&GradientRamp> for GradientRamp<SRGBA8> {
fn from(ramp: &GradientRamp) -> Self {
Self {
stops: ramp.into(),
spread_method: ramp.spread_method,
gradient_spread: ramp.gradient_spread,
}
}
}
@@ -198,7 +198,7 @@ impl From<&Gradient> for GradientRamp<SRGBA8> {
fn from(gradient: &Gradient) -> Self {
Self {
stops: gradient.into(),
spread_method: Default::default(),
gradient_spread: Default::default(),
}
}
}
@@ -206,7 +206,7 @@ impl From<&Gradient> for GradientRamp<SRGBA8> {
impl From<&GradientRamp<SRGBA8>> for GradientRamp {
fn from(ramp: &GradientRamp<SRGBA8>) -> Self {
Self {
spread_method: ramp.spread_method,
gradient_spread: ramp.gradient_spread,
..Self::from(&ramp.stops)
}
}
@@ -600,12 +600,12 @@ impl Gradient {
stops
}
/// Samples the gradient's color at `t`. Given a `t` outside the 0 to 1 range, the `spread_method` determines how the gradient extends.
pub fn evaluate(&self, t: f64, spread_method: GradientSpreadMethod) -> Color {
let t = match spread_method {
GradientSpreadMethod::Pad => t.clamp(0., 1.),
GradientSpreadMethod::Repeat => t.rem_euclid(1.),
GradientSpreadMethod::Reflect => {
/// Samples the gradient's color at `t`. Given a `t` outside the 0 to 1 range, the `gradient_spread` determines how the gradient extends.
pub fn evaluate(&self, t: f64, gradient_spread: GradientSpread) -> Color {
let t = match gradient_spread {
GradientSpread::Pad => t.clamp(0., 1.),
GradientSpread::Repeat => t.rem_euclid(1.),
GradientSpread::Reflect => {
let cycle = t.rem_euclid(2.);
if cycle > 1. { 2. - cycle } else { cycle }
}
@@ -784,7 +784,7 @@ impl Gradient {
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum GradientSpreadMethod {
pub enum GradientSpread {
/// Extends the end colors outward.
#[default]
Pad,
@@ -795,12 +795,12 @@ pub enum GradientSpreadMethod {
// TODO: Add a "Clear" variant that returns transparent black outside the gradient's range
}
impl GradientSpreadMethod {
impl GradientSpread {
pub fn svg_name(&self) -> &'static str {
match self {
GradientSpreadMethod::Pad => "pad",
GradientSpreadMethod::Reflect => "reflect",
GradientSpreadMethod::Repeat => "repeat",
GradientSpread::Pad => "pad",
GradientSpread::Reflect => "reflect",
GradientSpread::Repeat => "repeat",
}
}
@@ -905,39 +905,39 @@ mod tests {
}
#[test]
fn spread_method_serializes_only_when_not_default() {
fn gradient_spread_serializes_only_when_not_default() {
let default_spread = GradientRamp::from(Gradient::from(vec![Color::BLACK, Color::WHITE]));
let json = serde_json::to_string(&default_spread).unwrap();
assert!(!json.contains("spread_method"), "the default Pad spread method must not serialize: {json}");
assert!(!json.contains("gradient_spread"), "the default Pad gradient spread must not serialize: {json}");
assert_eq!(serde_json::from_str::<GradientRamp>(&json).unwrap(), default_spread);
let repeating = GradientRamp {
spread_method: GradientSpreadMethod::Repeat,
gradient_spread: GradientSpread::Repeat,
..default_spread.clone()
};
let json = serde_json::to_string(&repeating).unwrap();
assert!(json.contains(r#""spread_method":"Repeat""#), "a non-default spread method must serialize: {json}");
assert!(json.contains(r#""gradient_spread":"Repeat""#), "a non-default gradient spread must serialize: {json}");
assert_eq!(serde_json::from_str::<GradientRamp>(&json).unwrap(), repeating);
}
#[test]
fn spread_method_round_trips_through_the_item_attribute() {
fn gradient_spread_round_trips_through_the_item_attribute() {
let ramp = GradientRamp {
spread_method: GradientSpreadMethod::Repeat,
gradient_spread: GradientSpread::Repeat,
..GradientRamp::from(Gradient::from(vec![Color::BLACK, Color::WHITE]))
};
let item = Item::<Gradient>::from(ramp.clone());
assert_eq!(
item.attribute_cloned_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD),
GradientSpreadMethod::Repeat,
"the runtime item should carry the spread method as its attribute"
item.attribute_cloned_or_default::<GradientSpread>(ATTR_GRADIENT_SPREAD),
GradientSpread::Repeat,
"the runtime item should carry the gradient spread as its attribute"
);
assert_eq!(GradientRamp::from(&item), ramp);
let padded = Item::<Gradient>::from(GradientRamp::from(Gradient::from(vec![Color::BLACK, Color::WHITE])));
assert!(
padded.attribute::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_none(),
padded.attribute::<GradientSpread>(ATTR_GRADIENT_SPREAD).is_none(),
"the default Pad must stay absent rather than materialize"
);
}

View File

@@ -8,7 +8,7 @@ pub mod vector;
// Re-export commonly used types at the crate root
pub use core_types as gcore;
pub use gradient::{Gradient, GradientRamp, GradientSpreadMethod, GradientStop, GradientType};
pub use gradient::{Gradient, GradientRamp, GradientSpread, GradientStop, GradientType};
pub use math::{QuadExt, RectExt};
pub use subpath::Subpath;
pub use vector::Vector;

View File

@@ -9,7 +9,7 @@ use rand::SeedableRng;
use rand::seq::SliceRandom;
use raster_types::{CPU, GPU, Raster};
use std::cmp::Ordering;
use vector_types::gradient::{GradientSpreadMethod, GradientType};
use vector_types::gradient::{GradientSpread, GradientType};
use vector_types::{Gradient, ReferencePoint};
/// Returns the list with the item at the specified index removed.
@@ -568,7 +568,7 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
List<Artboard>,
List<BlendMode>,
List<GradientType>,
List<GradientSpreadMethod>,
List<GradientSpread>,
)]
content: List<T>,
/// The attribute name (key) to write or replace.
@@ -730,18 +730,18 @@ fn read_attribute_gradient_type(
result
}
/// Reads a named `GradientSpreadMethod` attribute from the input list, outputting each value as an element of a new `GradientSpreadMethod[]`.
/// Reads a named `GradientSpread` attribute from the input list, outputting each value as an element of a new `GradientSpread[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_spread_method(
fn read_attribute_gradient_spread(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: Item<String>,
) -> List<GradientSpreadMethod> {
) -> List<GradientSpread> {
let name = name.into_element();
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue };
let Some(value) = content.attribute::<GradientSpread>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
}
result

View File

@@ -1387,9 +1387,9 @@ fn gradient_type(_: impl Ctx, gradient: Item<Gradient>, gradient_type: Item<vect
/// Sets how each gradient in the input list extends past its endpoints: Pad, Reflect, or Repeat.
#[node_macro::node(category("Gradient"))]
fn spread_method(_: impl Ctx, gradient: Item<Gradient>, spread_method: Item<vector_types::GradientSpreadMethod>) -> Item<Gradient> {
fn gradient_spread(_: impl Ctx, gradient: Item<Gradient>, gradient_spread: Item<vector_types::GradientSpread>) -> Item<Gradient> {
let mut gradient = gradient;
gradient.set_attribute(core_types::ATTR_SPREAD_METHOD, *spread_method.element());
gradient.set_attribute(core_types::ATTR_GRADIENT_SPREAD, *gradient_spread.element());
gradient
}
@@ -1417,11 +1417,11 @@ fn gradient_midpoints(_: impl Ctx, gradient: Item<Gradient>, midpoints: List<f64
gradient
}
/// Evaluates the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). Positions beyond that range follow the gradient's `spread_method` attribute: Pad (default), Reflect, or Repeat.
/// Evaluates the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). Positions beyond that range follow the gradient's `gradient_spread` attribute: Pad (default), Reflect, or Repeat.
#[node_macro::node(category("Color"))]
fn sample_gradient(_: impl Ctx, _primary: (), #[default(Color::BLACK, Color::WHITE)] gradient: Item<Gradient>, position: Item<Fraction>) -> Item<Color> {
let spread_method = gradient.attribute_cloned_or_default::<vector_types::GradientSpreadMethod>(core_types::ATTR_SPREAD_METHOD);
let color = gradient.element().evaluate(*position.element(), spread_method);
let gradient_spread = gradient.attribute_cloned_or_default::<vector_types::GradientSpread>(core_types::ATTR_GRADIENT_SPREAD);
let color = gradient.element().evaluate(*position.element(), gradient_spread);
Item::new_from_element(color)
}

View File

@@ -1,10 +1,10 @@
use core_types::list::{ATTR_FILL, Item, ItemAttributeValues, List};
use core_types::{
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color, Ctx,
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_SPREAD, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx,
};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{bake_paint_transforms, set_paint_attribute};
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
use graphic_types::vector_types::gradient::{GradientSpread, GradientType};
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
use graphic_types::vector_types::vector::PointId;
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
@@ -286,8 +286,8 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
if let Some(gradient_type) = attributes.remove::<GradientType>(ATTR_GRADIENT_TYPE) {
gradient_paint.set_attribute(ATTR_GRADIENT_TYPE, 0, gradient_type);
}
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
if let Some(gradient_spread) = attributes.remove::<GradientSpread>(ATTR_GRADIENT_SPREAD) {
gradient_paint.set_attribute(ATTR_GRADIENT_SPREAD, 0, gradient_spread);
}
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);

View File

@@ -22,14 +22,14 @@ async fn gradient_map<T: Adjust<Color> + Send>(
reverse: Item<bool>,
) -> Item<T> {
let mut image = image;
let spread_method = gradient.attribute_cloned_or_default::<vector_types::GradientSpreadMethod>(core_types::ATTR_SPREAD_METHOD);
let gradient_spread = gradient.attribute_cloned_or_default::<vector_types::GradientSpread>(core_types::ATTR_GRADIENT_SPREAD);
let gradient = gradient.into_element();
let reverse = reverse.into_element();
image.element_mut().adjust(|color| {
let intensity = color.luminance_rec_709();
let intensity = if reverse { 1. - intensity } else { intensity };
gradient.evaluate(intensity as f64, spread_method)
gradient.evaluate(intensity as f64, gradient_spread)
});
image