mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Rework Gradient into a newtype of List<Color> with optional position and midpoint attributes (#4397)
* Rework Gradient into a newtype of List<Color> with optional position and midpoint attributes * Fix Vello stopless-gradient fallback coverage, empty legacy gradient tables, the node docs gradient swatch, NaN position elision, and wired setter input overwrites
This commit is contained in:
committed by
Dennis Kobert
parent
8504564f5d
commit
f6b4ce71e6
@@ -5,7 +5,7 @@ pub mod brush_stroke;
|
||||
pub mod migrations {
|
||||
use crate::brush_stroke::BrushStroke;
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
pub fn migrate_to_brush_strokes<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<BrushStroke>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
||||
use graphic_types::{ATTR_FILL, ATTR_STROKE, Vector};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
use vector_types::gradient::{GradientSpreadMethod, GradientType as GradientTypeValue};
|
||||
use vector_types::{Gradient, GradientStop, ReferencePoint};
|
||||
use vector_types::{Gradient, ReferencePoint};
|
||||
|
||||
fn arena_exhausted() -> Interrupt {
|
||||
GraphError {
|
||||
@@ -555,21 +555,10 @@ pub fn flatten_gradient<'e>(
|
||||
flatten_leaf_lane(content, ctx.index() as usize)
|
||||
}
|
||||
|
||||
/// A gradient with `colors` as evenly spaced stops from 0 to 1; none makes a
|
||||
/// black gradient and one repeats at both ends.
|
||||
fn evenly_spaced_gradient(colors: &[Color]) -> Gradient {
|
||||
let stop = |position: f64, color: Color| GradientStop { position, midpoint: 0.5, color };
|
||||
match colors {
|
||||
[] => Gradient::new(vec![stop(0., Color::BLACK), stop(1., Color::BLACK)]),
|
||||
[color] => Gradient::new(vec![stop(0., *color), stop(1., *color)]),
|
||||
colors => Gradient::new(colors.iter().enumerate().map(|(index, color)| stop(index as f64 / (colors.len() - 1) as f64, *color))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a gradient from a `Color[]`, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
|
||||
#[node_macro::node(category("Color"), name("Colors to Gradient"))]
|
||||
pub fn colors_to_gradient(_: impl Ctx, colors: IList<Color>) -> Gradient {
|
||||
evenly_spaced_gradient(&colors.iter().collect::<Vec<_>>())
|
||||
Gradient::from(colors.iter().collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// The gradient over a graphic level's color leaves, as [`colors_to_gradient`].
|
||||
@@ -583,7 +572,7 @@ pub fn colors_to_gradient_graphic(_: impl Ctx, colors: IList<Graphic<'static>>)
|
||||
RowStep::Continue
|
||||
});
|
||||
}
|
||||
evenly_spaced_gradient(&leaves)
|
||||
Gradient::from(leaves)
|
||||
}
|
||||
|
||||
pub use _colors_to_gradient_graphic_mod::colors_to_gradient_graphic_entries;
|
||||
|
||||
@@ -796,11 +796,12 @@ mod tests {
|
||||
assert_eq!(three.iter().map(|stop| stop.position).collect::<Vec<_>>(), vec![0., 0.5, 1.]);
|
||||
assert_eq!(three.iter().map(|stop| stop.color).collect::<Vec<_>>(), vec![Color::BLACK, Color::WHITE, Color::BLACK]);
|
||||
|
||||
// A lone color is a one-stop gradient and no colors a stopless one; neither is padded
|
||||
let single = stops_of(vec![Color::WHITE]);
|
||||
assert_eq!(single.iter().map(|stop| (stop.position, stop.color)).collect::<Vec<_>>(), vec![(0., Color::WHITE), (1., Color::WHITE)]);
|
||||
assert_eq!(single.iter().map(|stop| (stop.position, stop.color)).collect::<Vec<_>>(), vec![(0., Color::WHITE)]);
|
||||
|
||||
let empty = stops_of(Vec::new());
|
||||
assert_eq!(empty.iter().map(|stop| (stop.position, stop.color)).collect::<Vec<_>>(), vec![(0., Color::BLACK), (1., Color::BLACK)]);
|
||||
assert!(empty.iter().next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1198,7 +1198,7 @@ fn hex_to_color(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, hex_code: Str
|
||||
|
||||
/// 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: Gradient) -> Gradient {
|
||||
fn gradient_value(_: impl Ctx, _primary: (), #[default(Color::BLACK, Color::WHITE)] gradient: Gradient) -> Gradient {
|
||||
gradient
|
||||
}
|
||||
|
||||
@@ -1214,16 +1214,43 @@ fn spread_method(_: impl Ctx, gradient: Gradient, spread_method: vector_types::G
|
||||
(gradient, Attr(spread_method))
|
||||
}
|
||||
|
||||
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
|
||||
/// Sets the position of each of a gradient's stops, a factor from 0 to 1 along the gradient.
|
||||
///
|
||||
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each stop to its default evenly spaced position.
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn sample_gradient(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, _primary: (), gradient: IList<Gradient>, position: Fraction) -> Result<IList<Color>, Interrupt> {
|
||||
fn gradient_positions(_: impl Ctx, mut gradient: Gradient, positions: IList<f64>) -> Gradient {
|
||||
let positions: Vec<f64> = positions.iter().collect();
|
||||
gradient.set_positions(&positions);
|
||||
gradient
|
||||
}
|
||||
|
||||
/// Sets the interpolation midpoint for each interval between gradient stops, a factor from 0 to 1 where the 0.5 default means linear interpolation and another value skews the transition speed toward one stop or the other.
|
||||
///
|
||||
/// The final stop belongs to no interval so its midpoint is ignored.
|
||||
///
|
||||
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each midpoint to its default of 0.5.
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn gradient_midpoints(_: impl Ctx, mut gradient: Gradient, midpoints: IList<f64>) -> Gradient {
|
||||
let midpoints: Vec<f64> = midpoints.iter().collect();
|
||||
gradient.set_midpoints(&midpoints);
|
||||
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.
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn sample_gradient(
|
||||
ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
_primary: (),
|
||||
#[default(Color::BLACK, Color::WHITE)] gradient: IList<Gradient>,
|
||||
position: Fraction,
|
||||
) -> Result<IList<Color>, Interrupt> {
|
||||
// An unwired gradient serves an empty level: no color
|
||||
if gradient.is_empty() || ctx.index() != 0 {
|
||||
return Err(GraphError::past_end().into());
|
||||
}
|
||||
|
||||
let position = position.clamp(0., 1.);
|
||||
Ok(gradient.element_ref(0).evaluate(position))
|
||||
let spread_method = gradient.lane(0).attr::<SpreadMethodAttr>();
|
||||
Ok(gradient.element_ref(0).evaluate(position, spread_method))
|
||||
}
|
||||
|
||||
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
||||
|
||||
@@ -24,9 +24,7 @@ mod adjust_std {
|
||||
}
|
||||
impl Adjust<Color> for Gradient {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for color in self.color.iter_mut() {
|
||||
*color = map_fn(color);
|
||||
}
|
||||
*self = self.map_colors(map_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,19 @@ mod blend_std {
|
||||
|
||||
impl Blend<Color> for Gradient {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut combined_stops = self.position.iter().chain(under.position.iter()).copied().collect::<Vec<_>>();
|
||||
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
||||
let mut combined_stops = self.positions().into_iter().chain(under.positions()).collect::<Vec<_>>();
|
||||
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
||||
let stops = combined_stops.into_iter().map(|position| {
|
||||
let over_color = self.evaluate(position);
|
||||
let under_color = under.evaluate(position);
|
||||
let over_color = self.evaluate(position, Default::default());
|
||||
let under_color = under.evaluate(position, Default::default());
|
||||
let color = blend_fn(over_color, under_color);
|
||||
GradientStop { position, midpoint: 0.5, color }
|
||||
});
|
||||
Gradient::new(stops)
|
||||
|
||||
let mut gradient = Gradient::new(stops);
|
||||
gradient.elide_default_attributes();
|
||||
gradient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,19 @@ fn gradient_map<T: Adjust<Color> + Clone + Send + Sync + core_types::CacheHash +
|
||||
Gradient,
|
||||
)]
|
||||
mut image: T,
|
||||
gradient: IList<Gradient>,
|
||||
#[default(Color::BLACK, Color::WHITE)] gradient: IList<Gradient>,
|
||||
reverse: bool,
|
||||
) -> T {
|
||||
if gradient.is_empty() {
|
||||
return image;
|
||||
}
|
||||
let spread_method = gradient.lane(0).attr::<vector_types::markers::SpreadMethod>();
|
||||
let gradient = gradient.element_ref(0);
|
||||
|
||||
image.adjust(|color| {
|
||||
let intensity = color.luminance_rec_709();
|
||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||
gradient.evaluate(intensity as f64)
|
||||
gradient.evaluate(intensity as f64, spread_method)
|
||||
});
|
||||
|
||||
image
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Default for Font {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
fn migrate_font_style<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
|
||||
use serde::Deserialize;
|
||||
String::deserialize(deserializer).map(|name| if name == "Normal (400)" { "Regular (400)".to_string() } else { name })
|
||||
|
||||
@@ -61,7 +61,7 @@ fn assign_color_at(gradient: &Gradient, position: usize, length: usize, randomiz
|
||||
_ => position as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
|
||||
},
|
||||
};
|
||||
gradient.evaluate(factor)
|
||||
gradient.evaluate(factor, Default::default())
|
||||
}
|
||||
|
||||
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
|
||||
@@ -77,6 +77,7 @@ fn assign_colors<'e>(
|
||||
/// Whether to style the stroke.
|
||||
stroke: bool,
|
||||
/// The range of colors to select from.
|
||||
#[default(Color::BLACK, Color::WHITE)]
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
|
||||
gradient: IList<Gradient>,
|
||||
/// Whether to reverse the gradient.
|
||||
@@ -323,7 +324,7 @@ fn fill<'e>(
|
||||
#[default(Color::BLACK)]
|
||||
fill: IList<Graphic<'static>>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<Gradient>,
|
||||
#[default(Color::BLACK, Color::WHITE)] _backup_gradient: IList<Gradient>,
|
||||
_gradient_type: GradientType,
|
||||
_spread_method: GradientSpreadMethod,
|
||||
_has_transform: bool,
|
||||
@@ -344,7 +345,7 @@ fn fill_graphic_leveled<'e>(
|
||||
(element, _content_fill): (Graphic<'static>, Attr<Fill>),
|
||||
#[default(Color::BLACK)] fill: IList<Graphic<'static>>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<Gradient>,
|
||||
#[default(Color::BLACK, Color::WHITE)] _backup_gradient: IList<Gradient>,
|
||||
_gradient_type: GradientType,
|
||||
_spread_method: GradientSpreadMethod,
|
||||
_has_transform: bool,
|
||||
@@ -2950,14 +2951,12 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
||||
match (a.element(0), b.element(0)) {
|
||||
(Some(Graphic::Color(color_a)), Some(Graphic::Color(color_b))) => Some(List::new_from_element(Graphic::from(color_a.lerp(color_b, time as f32)))),
|
||||
(Some(Graphic::Color(color_a)), Some(Graphic::Gradient(stops_b))) => {
|
||||
let mut solid_to_gradient = stops_b.clone();
|
||||
solid_to_gradient.color.iter_mut().for_each(|color| *color = *color_a);
|
||||
let solid_to_gradient = stops_b.map_colors(|_| *color_a);
|
||||
let stops = solid_to_gradient.lerp(stops_b, time);
|
||||
Some(gradient_paint(b, stops, None))
|
||||
}
|
||||
(Some(Graphic::Gradient(stops_a)), Some(Graphic::Color(color_b))) => {
|
||||
let mut gradient_to_solid = stops_a.clone();
|
||||
gradient_to_solid.color.iter_mut().for_each(|color| *color = *color_b);
|
||||
let gradient_to_solid = stops_a.map_colors(|_| *color_b);
|
||||
let stops = stops_a.lerp(&gradient_to_solid, time);
|
||||
Some(gradient_paint(a, stops, None))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user