mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
De-table the graphic leaf variants
This commit is contained in:
@@ -300,7 +300,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn text(label: &str) -> Graphic {
|
||||
Graphic::Text(List::new_from_element(label.to_string()))
|
||||
Graphic::Text(label.to_string())
|
||||
}
|
||||
|
||||
fn group(children: Vec<(Graphic, DAffine2)>) -> Graphic {
|
||||
@@ -313,10 +313,10 @@ mod tests {
|
||||
}
|
||||
|
||||
fn text_of(graphic: &Graphic) -> &str {
|
||||
let Graphic::Text(list) = graphic else {
|
||||
let Graphic::Text(text) = graphic else {
|
||||
panic!("expected a text leaf, got {graphic:?}");
|
||||
};
|
||||
list.element(0).expect("a text leaf holds its string")
|
||||
text
|
||||
}
|
||||
|
||||
fn translation(x: f64) -> DAffine2 {
|
||||
@@ -367,7 +367,7 @@ mod tests {
|
||||
let arg = core_types::ExtractVarArgs::vararg(input, 0).ok()?;
|
||||
let list = arg.downcast_ref::<core_types::list::List<Graphic>>()?;
|
||||
let Graphic::Text(text) = list.element(0)? else { return None };
|
||||
Some(text.element(0)?.clone())
|
||||
Some(text.clone())
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for PerRowSource {
|
||||
|
||||
@@ -3,7 +3,7 @@ use core_types::list::{Item, List};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::graphic::{PaintColumns, PaintReach, bake_paint_transforms, set_paint_attribute, set_paint_attribute_at};
|
||||
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, set_paint_attribute, set_paint_attribute_at};
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
use graphic_types::vector_types::GradientStops;
|
||||
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
|
||||
@@ -127,8 +127,7 @@ fn boolean_operation<'e>(
|
||||
> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
||||
let flattened = flatten_vector_run(&run, DAffine2::IDENTITY, PaintReach::NONE);
|
||||
let flattened = flatten_vector_run(GraphicLevel::Run(&item), DAffine2::IDENTITY, PaintReach::NONE);
|
||||
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item)
|
||||
.expect("the run holds the row's element type")
|
||||
.into_graphic_list();
|
||||
@@ -282,85 +281,69 @@ fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: Bo
|
||||
list
|
||||
}
|
||||
|
||||
/// A raster stand-in row: the image's unit rectangle under its transform,
|
||||
/// black-filled, keeping the layer routing and blending attributes.
|
||||
fn raster_stand_in_rows<T>(image: &List<T>, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element)
|
||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||
.with_attribute(ATTR_OPACITY, opacity)
|
||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
item
|
||||
};
|
||||
|
||||
(0..image.len())
|
||||
/// A raster stand-in row per lane: the image's unit rectangle under its
|
||||
/// transform, black-filled, keeping the layer routing and blending
|
||||
/// attributes.
|
||||
fn raster_stand_in_rows<S: core_types::lane::LaneSource>(image: &S, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
||||
(0..image.lane_count())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
||||
let row_transform: DAffine2 = image.attr::<TransformAttr>(i);
|
||||
let layer: Vec<NodeId> = image.attr::<EditorLayerPath>(i).to_vec();
|
||||
let blend_mode: BlendMode = image.attr::<BlendModeAttr>(i);
|
||||
let opacity: f64 = image.attr::<Opacity>(i);
|
||||
let fill: f64 = image.attr::<OpacityFill>(i);
|
||||
let clip: bool = image.attr::<ClippingMask>(i);
|
||||
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(parent_transform * row_transform);
|
||||
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element)
|
||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||
.with_attribute(ATTR_OPACITY, opacity)
|
||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
item
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A color row: an empty vector carrying the color as its fill paint.
|
||||
fn color_paint_rows(color: &List<Color>) -> Vec<Item<Vector>> {
|
||||
color
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (color, mut attributes) = row.into_parts();
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
||||
/// A color row: an empty vector carrying the color as its fill paint over the
|
||||
/// lane's attributes.
|
||||
fn color_paint_row(color: Color, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
Item::from_parts(element, attributes)
|
||||
})
|
||||
.collect()
|
||||
Item::from_parts(element, attributes)
|
||||
}
|
||||
|
||||
/// A gradient row: an empty vector carrying the stops as its fill paint, the
|
||||
/// gradient keys moved onto the paint.
|
||||
fn gradient_paint_rows(gradient: &List<GradientStops>) -> Vec<Item<Vector>> {
|
||||
gradient
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (stops, mut attributes) = row.into_parts();
|
||||
fn gradient_paint_row(stops: GradientStops, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
||||
let mut gradient_paint = List::new_from_element(Graphic::Gradient(stops));
|
||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
}
|
||||
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);
|
||||
}
|
||||
attributes.insert(ATTR_FILL, Some(gradient_paint));
|
||||
|
||||
let mut gradient_paint = List::new_from_element(stops);
|
||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
}
|
||||
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);
|
||||
}
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
Item::from_parts(element, attributes)
|
||||
})
|
||||
.collect()
|
||||
Item::from_parts(element, attributes)
|
||||
}
|
||||
|
||||
/// A text row: the shaped glyph vectors under the composed transform.
|
||||
/// A text lane's rows: the shaped glyph vectors under the composed transform.
|
||||
fn text_rows(text: &List<String>, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
||||
text_nodes::shape_text_list(text, false)
|
||||
.into_iter()
|
||||
@@ -378,9 +361,25 @@ fn push_rows(out: &mut List<Vector>, rows: Vec<Item<Vector>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn push_vector_rows(out: &mut List<Vector>, inner: &List<Vector>, composed: DAffine2, reach: PaintReach<'_>) {
|
||||
for row in 0..inner.len() {
|
||||
let Some(item) = inner.clone_item(row) else { continue };
|
||||
/// A de-tabled vector leaf as one row: the lane's attributes with the reach
|
||||
/// paint and the ancestor transform composed.
|
||||
fn push_leaf_vector_row(out: &mut List<Vector>, level: GraphicLevel<'_>, index: usize, vector: &Vector, ancestors: DAffine2, reach: PaintReach<'_>) {
|
||||
let out_index = out.len();
|
||||
out.push(Item::from_parts(vector.clone(), graphic_types::graphic::lane_attributes(level, index)));
|
||||
if reach.applies() {
|
||||
for (key, slot) in [(ATTR_FILL, reach.paint.fill), (ATTR_STROKE, reach.paint.stroke)] {
|
||||
if let Some(paint) = slot {
|
||||
set_paint_attribute_at(out, out_index, key, paint.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, out_index);
|
||||
out.set_attribute(ATTR_TRANSFORM, out_index, ancestors * current);
|
||||
}
|
||||
|
||||
fn push_vector_rows(out: &mut List<Vector>, rows: &List<Vector>, composed: DAffine2, reach: PaintReach<'_>) {
|
||||
for row in 0..rows.len() {
|
||||
let Some(item) = rows.clone_item(row) else { continue };
|
||||
let index = out.len();
|
||||
out.push(item);
|
||||
if reach.applies() {
|
||||
@@ -401,30 +400,34 @@ fn push_union(out: &mut List<Vector>, flattened: List<Vector>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The native flatten over a graphic level: the legacy flatten's arms over a
|
||||
/// lane source, with lane paint threaded by [`PaintReach`] in place of the
|
||||
/// legacy pre-push, and native group runs walked directly.
|
||||
fn flatten_vector_run<'a, S: core_types::lane::LaneSource<Element = Graphic>>(source: &'a S, transform: DAffine2, inherited: PaintReach<'a>) -> List<Vector> {
|
||||
/// The native flatten over a graphic level: the legacy flatten's arms over
|
||||
/// either level storage, with lane paint threaded by [`PaintReach`], leaf
|
||||
/// attributes read from their lanes, and native group runs walked directly.
|
||||
fn flatten_vector_run(level: GraphicLevel<'_>, transform: DAffine2, inherited: PaintReach<'_>) -> List<Vector> {
|
||||
let mut out = List::new();
|
||||
flatten_vector_run_into(&mut out, source, transform, inherited);
|
||||
flatten_vector_run_into(&mut out, level, transform, inherited);
|
||||
out
|
||||
}
|
||||
|
||||
fn flatten_vector_run_into<'a, S: core_types::lane::LaneSource<Element = Graphic>>(out: &mut List<Vector>, source: &'a S, transform: DAffine2, inherited: PaintReach<'a>) {
|
||||
let columns = PaintColumns::new(source);
|
||||
for index in 0..source.lane_count() {
|
||||
let Some(element) = source.element(index) else { continue };
|
||||
fn flatten_vector_run_into<'a>(out: &mut List<Vector>, level: GraphicLevel<'a>, transform: DAffine2, inherited: PaintReach<'a>) {
|
||||
use core_types::lane::{LaneSource, LeafLane};
|
||||
let columns = PaintColumns::new(&level);
|
||||
for index in 0..level.lane_count() {
|
||||
let Some(element) = level.element(index) else { continue };
|
||||
let reach = inherited.for_lane(&columns, index);
|
||||
let composed = transform * source.attr::<TransformAttr>(index);
|
||||
let composed = transform * level.attr::<TransformAttr>(index);
|
||||
match element {
|
||||
Graphic::Vector(inner) => push_vector_rows(out, inner, composed, reach),
|
||||
Graphic::Graphic(children) => push_union(out, flatten_vector_run(children, composed, reach.nested())),
|
||||
Graphic::Vector(vector) => push_leaf_vector_row(out, level, index, vector, transform, reach),
|
||||
Graphic::Graphic(children) => push_union(out, flatten_vector_run(GraphicLevel::Legacy(children), composed, reach.nested())),
|
||||
Graphic::Group(group) => flatten_group(out, group, composed, reach),
|
||||
Graphic::RasterCPU(image) => push_rows(out, raster_stand_in_rows(image, composed)),
|
||||
Graphic::RasterGPU(image) => push_rows(out, raster_stand_in_rows(image, composed)),
|
||||
Graphic::Color(color) => push_rows(out, color_paint_rows(color)),
|
||||
Graphic::Gradient(gradient) => push_rows(out, gradient_paint_rows(gradient)),
|
||||
Graphic::Text(text) => push_rows(out, text_rows(text, composed)),
|
||||
Graphic::RasterCPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||
Graphic::RasterGPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||
Graphic::Color(color) => push_rows(out, vec![color_paint_row(*color, graphic_types::graphic::lane_attributes(level, index))]),
|
||||
Graphic::Gradient(gradient) => push_rows(out, vec![gradient_paint_row(gradient.clone(), graphic_types::graphic::lane_attributes(level, index))]),
|
||||
Graphic::Text(text) => {
|
||||
let one = List::new_from_item(Item::from_parts(text.clone(), graphic_types::graphic::lane_attributes(level, index)));
|
||||
push_rows(out, text_rows(&one, composed));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,172 +439,26 @@ fn flatten_group(out: &mut List<Vector>, group: &core_types::record::Group, comp
|
||||
let item = &group.content;
|
||||
if let Some(rows) = graphic_types::graphic::run_to_list::<Vector>(item) {
|
||||
push_vector_rows(out, &rows, composed, reach);
|
||||
} else if let Some(run) = core_types::record::RunView::<Graphic>::new(item) {
|
||||
push_union(out, flatten_vector_run(&run, composed, reach.into_group_graphics()));
|
||||
} else if core_types::record::RunView::<Graphic>::new(item).is_some() {
|
||||
push_union(out, flatten_vector_run(GraphicLevel::Run(item), composed, reach.into_group_graphics()));
|
||||
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<CPU>>(item) {
|
||||
push_rows(out, raster_stand_in_rows(&image, composed));
|
||||
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<GPU>>(item) {
|
||||
push_rows(out, raster_stand_in_rows(&image, composed));
|
||||
} else if let Some(color) = graphic_types::graphic::run_to_list::<Color>(item) {
|
||||
push_rows(out, color_paint_rows(&color));
|
||||
push_rows(out, (0..color.len()).filter_map(|i| Some(color_paint_row(*color.element(i)?, color.clone_item_attributes(i)))).collect());
|
||||
} else if let Some(gradient) = graphic_types::graphic::run_to_list::<GradientStops>(item) {
|
||||
push_rows(out, gradient_paint_rows(&gradient));
|
||||
push_rows(
|
||||
out,
|
||||
(0..gradient.len())
|
||||
.filter_map(|i| Some(gradient_paint_row(gradient.element(i)?.clone(), gradient.clone_item_attributes(i))))
|
||||
.collect(),
|
||||
);
|
||||
} else if let Some(text) = graphic_types::graphic::run_to_list::<String>(item) {
|
||||
push_rows(out, text_rows(&text, composed));
|
||||
}
|
||||
}
|
||||
|
||||
/// The legacy baseline the flatten law compares against.
|
||||
#[cfg(test)]
|
||||
fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
(0..graphic_list.len())
|
||||
.flat_map(|index| {
|
||||
let graphic = graphic_list.element(index).unwrap();
|
||||
match graphic.clone() {
|
||||
Graphic::Group(_) => Vec::new(),
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the `List<Vector>`
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
vector
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterCPU(image) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element)
|
||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||
.with_attribute(ATTR_OPACITY, opacity)
|
||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
item
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterGPU(image) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element)
|
||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||
.with_attribute(ATTR_OPACITY, opacity)
|
||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
item
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
// Apply the parent graphic's transform to each element of the inner `List`
|
||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
*transform = parent_transform * *transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner `List` into the output `List<Vector>`
|
||||
let flattened = flatten_vector(&graphic);
|
||||
let unioned = boolean_operation_on_vector_list(&flattened, BooleanOperation::Union);
|
||||
|
||||
unioned.into_iter().collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Color(color) => color
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (color, mut attributes) = row.into_parts();
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
Item::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Gradient(gradient) => gradient
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (stops, mut attributes) = row.into_parts();
|
||||
|
||||
let mut gradient_paint = List::new_from_element(stops);
|
||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
}
|
||||
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);
|
||||
}
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
Item::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Text(text) => {
|
||||
// Shape the glyphs into vectors (each item's own transform is applied), then compose the parent's transform like the other arms
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
text_nodes::shape_text_list(&text, false)
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// This quantization should potentially be removed since it's not conceptually necessary,
|
||||
// but without it, the oak leaf in the Changing Seasons demo artwork is funky because
|
||||
// quantization is needed for the top and bottom points to line up vertically.
|
||||
@@ -699,11 +556,11 @@ mod tests {
|
||||
}
|
||||
|
||||
fn black_paint() -> List<Graphic> {
|
||||
List::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK)))
|
||||
List::new_from_element(Graphic::Color(Color::BLACK))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_native_flatten_matches_the_legacy_flatten() {
|
||||
fn the_native_flatten_reads_lanes_groups_and_reach() {
|
||||
let inner_vector = square(DVec2::ZERO);
|
||||
let inner_layout = core_types::record::Layout::default().with_writes(0, core_types::record::element_write_hashed::<Vector>(), &[]);
|
||||
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()];
|
||||
@@ -713,43 +570,32 @@ mod tests {
|
||||
// SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride.
|
||||
let inner_item = unsafe { core_types::record::GroupItem::from_resident(core_types::node::RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
|
||||
|
||||
let mut painted = List::new();
|
||||
painted.push(Item::new_from_element(square(DVec2::ZERO)));
|
||||
painted.push(Item::new_from_element(square(DVec2::ONE)));
|
||||
painted.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(1., 0.)));
|
||||
set_paint_attribute_at(&mut painted, 1, ATTR_FILL, List::new_from_element(Graphic::Color(List::new_from_element(Color::WHITE))));
|
||||
|
||||
let mut nested_child = List::new();
|
||||
nested_child.push(Item::new_from_element(square(DVec2::new(2., 2.))));
|
||||
let mut nested = List::new_from_element(Graphic::Vector(nested_child));
|
||||
nested.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale(DVec2::splat(2.)));
|
||||
|
||||
let mut colors = List::new_from_element(Color::BLACK);
|
||||
colors.set_attribute(ATTR_OPACITY, 0, 0.5);
|
||||
|
||||
let mut top = List::new();
|
||||
top.push(Item::new_from_element(Graphic::Vector(painted)));
|
||||
top.push(Item::new_from_element(Graphic::Graphic(nested)));
|
||||
top.push(Item::new_from_element(Graphic::Color(colors)));
|
||||
top.push(Item::new_from_element(Graphic::Vector(square(DVec2::ZERO))));
|
||||
top.push(Item::new_from_element(Graphic::Color(Color::BLACK)));
|
||||
top.push(Item::new_from_element(Graphic::Group(Group { row: None, content: inner_item })));
|
||||
top.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||
set_paint_attribute_at(&mut top, 0, ATTR_FILL, black_paint());
|
||||
top.set_attribute(ATTR_TRANSFORM, 3, DAffine2::from_scale(DVec2::splat(3.)));
|
||||
top.set_attribute(ATTR_OPACITY, 1, 0.5);
|
||||
top.set_attribute(ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
||||
|
||||
let legacy = {
|
||||
let mut prepared = top.clone();
|
||||
// The legacy pre-push, written by hand: the painted lane's fill
|
||||
// lands on every interior item.
|
||||
if let Some(Graphic::Vector(inner)) = prepared.element_mut(0) {
|
||||
for index in 0..inner.len() {
|
||||
set_paint_attribute_at(inner, index, ATTR_FILL, black_paint());
|
||||
}
|
||||
}
|
||||
if let Some(element) = prepared.element_mut(3) {
|
||||
*element = graphic_types::graphic::map_groups_to_legacy(element);
|
||||
}
|
||||
flatten_vector(&prepared)
|
||||
};
|
||||
assert_eq!(flatten_vector_run(&top, DAffine2::IDENTITY, PaintReach::NONE), legacy);
|
||||
let rows = flatten_vector_run(GraphicLevel::Legacy(&top), DAffine2::IDENTITY, PaintReach::NONE);
|
||||
assert_eq!(rows.len(), 3);
|
||||
|
||||
// Lane 0: the leaf row keeps its lane attributes, with the lane fill
|
||||
// present and the ancestor composition the identity.
|
||||
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0), DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||
assert!(graphic_types::graphic::paint_graphics::<Fill, _>(&rows, 0).is_some());
|
||||
|
||||
// Lane 1: the color stand-in carries the lane opacity and the color as
|
||||
// its fill.
|
||||
assert_eq!(rows.attribute_cloned_or::<f64>(ATTR_OPACITY, 1, 1.), 0.5);
|
||||
let fill = graphic_types::graphic::paint_graphics::<Fill, _>(&rows, 1).expect("the color row carries its fill");
|
||||
assert!(matches!(fill.element(0), Some(Graphic::Color(color)) if *color == Color::BLACK));
|
||||
|
||||
// Lane 2: the group's vector run serves its row under the lane
|
||||
// transform.
|
||||
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 2), DAffine2::from_scale(DVec2::splat(3.)));
|
||||
assert_eq!(rows.element(2).unwrap(), &inner_vector);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ fn assign_colors_graphic<'e>(
|
||||
core_types::registry::cache_key(&keyed)
|
||||
};
|
||||
let generation = ctx.arena().generation();
|
||||
let (length, mut position) = {
|
||||
let (length, position) = {
|
||||
let mut cached = lane_offsets.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if !matches!(cached.as_ref(), Some(entry) if entry.key == key && entry.generation == generation) {
|
||||
let mut offsets = Vec::with_capacity(content.len() + 1);
|
||||
@@ -218,20 +218,29 @@ fn assign_colors_graphic<'e>(
|
||||
(entry.offsets[content.len()], entry.offsets[lane])
|
||||
};
|
||||
|
||||
if let Some(vector_list) = element.as_vector_mut() {
|
||||
for index in 0..vector_list.len() {
|
||||
let color = assign_color_at(gradient_element, position, length, randomize, seed, repeat_every);
|
||||
// A de-tabled vector leaf carries no attributes, so the paint rides the
|
||||
// containing lane: a bare leaf wraps into a one-lane list, and a lowered
|
||||
// vector run's leaves take one color per lane.
|
||||
if graphic_types::graphic::direct_vector_len(content.element_ref(lane)) > 0 {
|
||||
let mut children = match element {
|
||||
Graphic::Graphic(children) => children,
|
||||
leaf => List::new_from_element(leaf),
|
||||
};
|
||||
let mut consumed = 0;
|
||||
for index in 0..children.len() {
|
||||
let Some(Graphic::Vector(vector)) = children.element(index) else { continue };
|
||||
let has_stroke = vector.stroke.is_some();
|
||||
let color = assign_color_at(gradient_element, position + consumed, length, randomize, seed, repeat_every);
|
||||
let paint = List::new_from_element(color).into_graphic_list();
|
||||
|
||||
if fill {
|
||||
set_paint_attribute_at(vector_list, index, ATTR_FILL, paint.clone());
|
||||
set_paint_attribute_at(&mut children, index, ATTR_FILL, paint.clone());
|
||||
}
|
||||
if stroke && vector_list.element(index).is_some_and(|vector| vector.stroke.is_some()) {
|
||||
set_paint_attribute_at(vector_list, index, ATTR_STROKE, paint.clone());
|
||||
if stroke && has_stroke {
|
||||
set_paint_attribute_at(&mut children, index, ATTR_STROKE, paint.clone());
|
||||
}
|
||||
|
||||
position += 1;
|
||||
consumed += 1;
|
||||
}
|
||||
element = Graphic::Graphic(children);
|
||||
}
|
||||
|
||||
Ok((element, transform, layer_path))
|
||||
@@ -277,21 +286,20 @@ fn park_paint(arena: &core_types::arena::Arena, paint: List<Graphic>) -> Result<
|
||||
/// The gradient defaulting the legacy fill performed, applied to the nested
|
||||
/// stops list the paint table wraps.
|
||||
fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>, gradient_type: GradientType, spread_method: GradientSpreadMethod, transform: Option<DAffine2>) {
|
||||
let has_type = paint.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_some();
|
||||
let has_spread = paint.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_some();
|
||||
let has_transform = paint.iter_attribute_values::<DAffine2>(ATTR_TRANSFORM).is_some();
|
||||
for index in 0..paint.len() {
|
||||
let Some(Graphic::Gradient(gradient)) = paint.element_mut(index) else { continue };
|
||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||
*value = gradient_type;
|
||||
}
|
||||
if !matches!(paint.element(index), Some(Graphic::Gradient(_))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||
*value = spread_method;
|
||||
}
|
||||
if !has_type {
|
||||
paint.set_attribute(ATTR_GRADIENT_TYPE, index, gradient_type);
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<DAffine2>(ATTR_TRANSFORM).is_none() {
|
||||
if !has_spread {
|
||||
paint.set_attribute(ATTR_SPREAD_METHOD, index, spread_method);
|
||||
}
|
||||
if !has_transform {
|
||||
let transform = transform.unwrap_or_else(|| {
|
||||
// Nudge a degenerate axis so the gradient transform stays invertible, matching the editor's `nonzero_bounding_box`
|
||||
let [min, mut max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
@@ -303,10 +311,7 @@ fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>,
|
||||
}
|
||||
initial_gradient_transform_for_bounding_box([min, max])
|
||||
});
|
||||
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
*value = transform;
|
||||
}
|
||||
paint.set_attribute(ATTR_TRANSFORM, index, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,18 +425,13 @@ fn stroke<'e>(
|
||||
/// The vector items of a graphic lane's interior, one wrap level deep, the
|
||||
/// reach of the pre-flip broadcast over a legacy list.
|
||||
fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
||||
let mut walk_list = |list: &mut List<Vector>| {
|
||||
let (elements, transforms) = list.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
||||
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
|
||||
f(vector, *transform);
|
||||
}
|
||||
};
|
||||
match element {
|
||||
Graphic::Vector(list) => walk_list(list),
|
||||
Graphic::Vector(vector) => f(vector, DAffine2::IDENTITY),
|
||||
Graphic::Graphic(children) => {
|
||||
for child in children.iter_element_values_mut() {
|
||||
if let Some(list) = child.as_vector_mut() {
|
||||
walk_list(list);
|
||||
for index in 0..children.len() {
|
||||
let transform: DAffine2 = children.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
if let Some(Graphic::Vector(vector)) = children.element_mut(index) {
|
||||
f(vector, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1449,9 +1449,9 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
||||
/// lane addresses (a fill-bearing row serves two lanes), builds and splits
|
||||
/// only that row, and lane 0 additionally carries the merged-layers snapshot.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn solidify_native_lane<'e, S: core_types::lane::LaneSource<Element = Graphic>>(
|
||||
fn solidify_native_lane<'e>(
|
||||
arena: &'e core_types::arena::Arena,
|
||||
source: &S,
|
||||
level: graphic_types::graphic::GraphicLevel<'_>,
|
||||
snapshot: impl FnOnce() -> List<Graphic>,
|
||||
lane: usize,
|
||||
) -> Result<
|
||||
@@ -1472,7 +1472,7 @@ fn solidify_native_lane<'e, S: core_types::lane::LaneSource<Element = Graphic>>(
|
||||
use graphic_types::graphic::RowStep;
|
||||
let mut remaining = lane;
|
||||
let mut located: Option<List<Vector>> = None;
|
||||
graphic_types::graphic::walk_vector_rows(source, &mut |row| {
|
||||
graphic_types::graphic::walk_vector_rows(level, &mut |row| {
|
||||
let parts = 1 + row.has_fill() as usize;
|
||||
if remaining >= parts {
|
||||
remaining -= parts;
|
||||
@@ -1622,8 +1622,7 @@ fn solidify_stroke<'e>(
|
||||
> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
||||
solidify_native_lane(ctx.arena(), &run, || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||
solidify_native_lane(ctx.arena(), graphic_types::graphic::GraphicLevel::Run(&item), || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||
}
|
||||
|
||||
/// A fill-bearing row splits into a fill lane and a solidified stroke lane,
|
||||
@@ -1663,7 +1662,7 @@ fn solidify_stroke_vector<'e>(
|
||||
Interrupt,
|
||||
> {
|
||||
let wrapper = wrap_vector_level(content);
|
||||
solidify_native_lane(ctx.arena(), &wrapper, || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||
solidify_native_lane(ctx.arena(), graphic_types::graphic::GraphicLevel::Legacy(&wrapper), || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||
}
|
||||
|
||||
fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
|
||||
@@ -1905,8 +1904,7 @@ pub fn flatten_path<'e>(
|
||||
> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(&run);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
||||
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item).expect("the run holds the row's element type");
|
||||
flatten_path_core(ctx.arena(), flattened, snapshot)
|
||||
}
|
||||
@@ -1929,7 +1927,7 @@ pub fn flatten_path_vector<'e>(
|
||||
Interrupt,
|
||||
> {
|
||||
let wrapper = wrap_vector_level(content);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(&wrapper);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));
|
||||
let snapshot = legacy_graphic_list_of(content);
|
||||
flatten_path_core(ctx.arena(), flattened, snapshot)
|
||||
}
|
||||
@@ -2724,9 +2722,9 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic>, progression: f64
|
||||
}
|
||||
}
|
||||
|
||||
fn lerp_gradient_transform(gradient_list_a: &List<GradientStops>, gradient_list_b: &List<GradientStops>, time: f64) -> DAffine2 {
|
||||
let transform_a = gradient_list_a.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
let transform_b = gradient_list_b.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
fn lerp_gradient_transform(paint_a: &List<Graphic>, paint_b: &List<Graphic>, time: f64) -> DAffine2 {
|
||||
let transform_a = paint_a.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
let transform_b = paint_b.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
|
||||
let start_a = transform_a.translation;
|
||||
let end_a = transform_a.translation + transform_a.matrix2.x_axis;
|
||||
@@ -2754,47 +2752,37 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic>, progression: f64
|
||||
(Some(a), Some(b)) => (a, b),
|
||||
};
|
||||
|
||||
// This keeps the gradient metadata attributes
|
||||
let gradient_with_stops = |mut gradient_list: List<GradientStops>, stops: GradientStops| -> Graphic {
|
||||
if let Some(target) = gradient_list.element_mut(0) {
|
||||
*target = stops;
|
||||
} else {
|
||||
gradient_list.push(Item::new_from_element(stops));
|
||||
// This keeps the gradient metadata attributes, which ride the paint lane
|
||||
let gradient_paint = |metadata_source: &List<Graphic>, stops: GradientStops, transform: Option<DAffine2>| -> List<Graphic> {
|
||||
let mut out = List::new_from_item(Item::from_parts(Graphic::Gradient(stops), metadata_source.clone_item_attributes(0)));
|
||||
if let Some(transform) = transform {
|
||||
out.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
}
|
||||
Graphic::Gradient(gradient_list)
|
||||
out
|
||||
};
|
||||
|
||||
let graphic = match (a.element(0), b.element(0)) {
|
||||
(Some(Graphic::Color(color_list_a)), Some(Graphic::Color(color_list_b))) => color_list_a
|
||||
.element(0)
|
||||
.zip(color_list_b.element(0))
|
||||
.map(|(color_a, color_b)| Graphic::from(color_a.lerp(color_b, time as f32))),
|
||||
(Some(Graphic::Color(color_list_a)), Some(Graphic::Gradient(gradient_list_b))) => color_list_a.element(0).zip(gradient_list_b.element(0)).map(|(color_a, stops_b)| {
|
||||
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 stops = solid_to_gradient.lerp(stops_b, time);
|
||||
gradient_with_stops(gradient_list_b.clone(), stops)
|
||||
}),
|
||||
(Some(Graphic::Gradient(gradient_list_a)), Some(Graphic::Color(color_list_b))) => gradient_list_a.element(0).zip(color_list_b.element(0)).map(|(stops_a, color_b)| {
|
||||
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 stops = stops_a.lerp(&gradient_to_solid, time);
|
||||
gradient_with_stops(gradient_list_a.clone(), stops)
|
||||
}),
|
||||
(Some(Graphic::Gradient(gradient_list_a)), Some(Graphic::Gradient(gradient_list_b))) => gradient_list_a.element(0).zip(gradient_list_b.element(0)).map(|(stops_a, stops_b)| {
|
||||
Some(gradient_paint(a, stops, None))
|
||||
}
|
||||
(Some(Graphic::Gradient(stops_a)), Some(Graphic::Gradient(stops_b))) => {
|
||||
let stops = stops_a.lerp(stops_b, time);
|
||||
let metadata_source = if time < 0.5 { gradient_list_a } else { gradient_list_b };
|
||||
|
||||
let mut gradient_list = metadata_source.clone();
|
||||
gradient_list.set_attribute(ATTR_TRANSFORM, 0, lerp_gradient_transform(gradient_list_a, gradient_list_b, time));
|
||||
|
||||
gradient_with_stops(gradient_list, stops)
|
||||
}),
|
||||
let metadata_source = if time < 0.5 { a } else { b };
|
||||
Some(gradient_paint(metadata_source, stops, Some(lerp_gradient_transform(a, b, time))))
|
||||
}
|
||||
// Pairings beyond solid colors and gradients (raster, vector, or mixed) can't be interpolated, so step at the midpoint
|
||||
_ => return Some(if time < 0.5 { a.clone() } else { b.clone() }),
|
||||
};
|
||||
|
||||
graphic.map(List::new_from_element)
|
||||
_ => Some(if time < 0.5 { a.clone() } else { b.clone() }),
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the original legacy snapshot as upstream data so this group layer's nested layers can be edited by the tools.
|
||||
@@ -3323,8 +3311,7 @@ fn morph<'e>(
|
||||
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(&run);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
||||
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
||||
}
|
||||
|
||||
@@ -3357,7 +3344,7 @@ fn morph_vector<'e>(
|
||||
let path_item = unsafe { core_types::record::GroupItem::from_resident(path.batch()) };
|
||||
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
||||
let wrapper = wrap_vector_level(content);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(&wrapper);
|
||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));
|
||||
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
||||
}
|
||||
|
||||
@@ -3961,10 +3948,10 @@ mod test {
|
||||
let fill = paint_graphics::<Fill, _>(&morphed, 0).expect("Morph should keep the fill paint at the midpoint");
|
||||
|
||||
// Interpolated color between red and blue should have >0 value on both R and B
|
||||
let Some(Graphic::Color(colors)) = fill.element(0) else {
|
||||
let Some(Graphic::Color(color)) = fill.element(0) else {
|
||||
panic!("Expected a solid color fill, got {:?}", fill.element(0));
|
||||
};
|
||||
let color = *colors.element(0).expect("Color present");
|
||||
let color = *color;
|
||||
assert!(color.r() > 0. && color.b() > 0., "Fill should be a red-to-blue blend, got {color:?}");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user