mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
New node: 'Gradient to Colors' for unwrapping a gradient ramp into its colors (#4425)
* New node: 'Gradient to Colors' for unwrapping a gradient ramp into its colors * Update 'Gradient Midpoints' wording
This commit is contained in:
committed by
Dennis Kobert
parent
2baf9a8192
commit
f3aeec3c02
@@ -1,10 +1,11 @@
|
||||
use crate::record::Inherited;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::attribute::{Attr, EditorLayerPath, Name0, Named, Opacity, OpacityFill, Transform as TransformAttr, WireValue};
|
||||
use core_types::attribute::{Attr, Attribute, EditorLayerPath, Midpoint, Name0, Named, Opacity, OpacityFill, Position, Transform as TransformAttr, WireValue};
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::extent::{LevelIn, ListIn, ValueIn};
|
||||
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt};
|
||||
use core_types::list::List;
|
||||
use core_types::list::{ATTR_MIDPOINT, ATTR_POSITION};
|
||||
use core_types::node::Lane;
|
||||
use core_types::registry::types::Angle;
|
||||
use core_types::uuid::NodeId;
|
||||
@@ -15,6 +16,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::{GradientForm as GradientFormValue, GradientHueDirection, GradientSpace, GradientSpread};
|
||||
use vector_types::markers::GradientCyclic;
|
||||
use vector_types::{Gradient, ReferencePoint};
|
||||
|
||||
fn arena_exhausted() -> Interrupt {
|
||||
@@ -559,10 +561,58 @@ pub fn flatten_gradient<'e>(
|
||||
flatten_leaf_lane(content, ctx.index() as usize)
|
||||
}
|
||||
|
||||
/// Constructs a gradient from a `Color[]`, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
|
||||
/// A gradient over a level of colors: a `position` or `midpoint` column on the level places its stops, and a level carrying neither distributes them evenly.
|
||||
fn gradient_of_lanes(colors: core_types::node::List<'_, Color>) -> Gradient {
|
||||
let layout = colors.batch().layout();
|
||||
let has_position = layout.offset_of(Position::NAME, 0).is_some();
|
||||
let has_midpoint = layout.offset_of(Midpoint::NAME, 0).is_some();
|
||||
|
||||
let mut list = List::new();
|
||||
for index in 0..colors.len() {
|
||||
let lane = colors.lane(index);
|
||||
list.push(core_types::list::Item::new_from_element(colors.get(index)));
|
||||
if has_position {
|
||||
list.set_attribute(ATTR_POSITION, index, lane.attr::<Position>());
|
||||
}
|
||||
if has_midpoint {
|
||||
list.set_attribute(ATTR_MIDPOINT, index, lane.attr::<Midpoint>());
|
||||
}
|
||||
}
|
||||
Gradient::from(list)
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
pub fn colors_to_gradient(_: impl Ctx, colors: IList<Color>) -> Gradient {
|
||||
Gradient::from(colors.iter().collect::<Vec<_>>())
|
||||
gradient_of_lanes(colors)
|
||||
}
|
||||
|
||||
/// Unwraps each gradient into the `Color[]` of its stops, each carrying its `position` and `midpoint` 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"), extent(gradient_to_colors_extent))]
|
||||
pub fn gradient_to_colors<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
gradients: IList<Gradient>,
|
||||
) -> Result<IList<(Color, Attr<'e, Position>, Attr<'e, Midpoint>)>, Interrupt> {
|
||||
let mut remaining = ctx.index() as usize;
|
||||
for row in 0..gradients.len() {
|
||||
let gradient = gradients.element_ref(row);
|
||||
if remaining >= gradient.len() {
|
||||
remaining -= gradient.len();
|
||||
continue;
|
||||
}
|
||||
let gradient_cyclic = gradients.lane(row).attr::<GradientCyclic>();
|
||||
let Some(color) = gradient.color(remaining) else { break };
|
||||
return Ok((color, Attr(gradient.position(remaining, gradient_cyclic)), Attr(gradient.midpoint(remaining))));
|
||||
}
|
||||
Err(GraphError::past_end().into())
|
||||
}
|
||||
|
||||
/// The level holds every gradient's stops in order.
|
||||
fn gradient_to_colors_extent(gradients: ListIn<'_, Gradient>, level: LevelIn) -> GPoll<Extent> {
|
||||
match level.top() {
|
||||
true => gradients.get().map(|gradients| Extent::Exactly((0..gradients.len()).map(|row| gradients.element_ref(row).len()).sum())),
|
||||
false => GPoll::Final(Extent::Exactly(1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The gradient over a graphic level's color leaves, as [`colors_to_gradient`].
|
||||
|
||||
@@ -223,7 +223,10 @@ fn flatten_levels_extent(content: ExtentIn<'_>, level: LevelIn) -> GPoll<Extent>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::graphic::{ColorsToGradientNode, FlattenColorNode, FlattenGraphicNode, WrapGraphicNode, flatten_color_layout_meta, flatten_graphic_layout_meta, wrap_graphic_layout_meta};
|
||||
use crate::graphic::{
|
||||
ColorsToGradientNode, FlattenColorNode, FlattenGraphicNode, GradientToColorsNode, WrapGraphicNode, flatten_color_layout_meta, flatten_graphic_layout_meta, gradient_to_colors_layout_meta,
|
||||
wrap_graphic_layout_meta,
|
||||
};
|
||||
use core_types::arena::Arena;
|
||||
use core_types::attribute::Attribute as AttributeMarker;
|
||||
use core_types::context::{ContextImpl, ExtractArena};
|
||||
@@ -736,6 +739,46 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The stops of a gradient level unwrap to color lanes carrying their
|
||||
/// effective placement, which a `Colors to Gradient` reads back.
|
||||
#[test]
|
||||
fn gradient_stops_round_trip_through_color_lanes() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
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 source = core_types::value::LeveledValueSource::new(vec![gradient.clone()]);
|
||||
let layout = Node::<ContextImpl>::layout(&source).clone();
|
||||
let colors = install(GradientToColorsNode::new(source), gradient_to_colors_layout_meta(), &[Some(&layout)]);
|
||||
let colors_layout = Node::<ContextImpl>::layout(&colors).clone();
|
||||
assert_eq!(colors.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, (color, position, midpoint)) in [(Color::RED, 0., 0.3), (Color::GREEN, 0.25, 0.5), (Color::BLUE, 1., 0.5)].into_iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&colors, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(record.element::<Color>(), color, "lane {lane}");
|
||||
assert_eq!(record.attr::<core_types::attribute::Position>(), position, "lane {lane}");
|
||||
assert_eq!(record.attr::<core_types::attribute::Midpoint>(), midpoint, "lane {lane}");
|
||||
}
|
||||
|
||||
let out = Layout::default().with_writes(0, record::element_write_hashed::<Gradient>(), &[]);
|
||||
let restored = install_flip(ColorsToGradientNode::new(colors, &colors_layout), &out);
|
||||
let GPoll::Final(record) = record::capture(&restored, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let restored = record.element::<Gradient>();
|
||||
assert_eq!(restored.positions(false), gradient.positions(false));
|
||||
assert_eq!(restored.midpoints(), gradient.midpoints());
|
||||
assert_eq!(restored.iter().map(|stop| stop.color).collect::<Vec<_>>(), vec![Color::RED, Color::GREEN, Color::BLUE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colors_fold_into_evenly_spaced_stops() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
|
||||
Reference in New Issue
Block a user