Restore pre-flip color parity: rng replay, typed wrap, boolean marker paints

This commit is contained in:
Dennis Kobert
2026-08-24 00:43:44 +00:00
parent 9ca897963f
commit 8c67ad508f
5 changed files with 68 additions and 41 deletions

View File

@@ -468,9 +468,14 @@ pub fn legacy_layer_extend<T: Send + Clone>(
}
/// Nests the input graphical content in a wrapper graphic. This essentially "groups" the input.
/// The wrapped run keeps the level's element type, so the legacy boundary can
/// lower a wrapped vector level to the bare typed graphic the pre-flip wrap made.
/// The inverse of this node is 'Flatten Graphic'.
#[node_macro::node(category("General"), extent(wrap_graphic_extent))]
pub fn wrap_graphic(_: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Graphic>) -> Result<IList<Graphic>, Interrupt> {
pub fn wrap_graphic<T: Clone + Send + Sync + core_types::CacheHash + 'static>(
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] content: IList<T>,
) -> Result<IList<Graphic>, Interrupt> {
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
Ok(Graphic::Group(core_types::record::Group {
@@ -480,7 +485,7 @@ pub fn wrap_graphic(_: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IL
}
/// The collected group is the level's single lane.
fn wrap_graphic_extent(_content: ListIn<'_, Graphic>, _level: LevelIn) -> GPoll<Extent> {
fn wrap_graphic_extent<T>(_content: ListIn<'_, T>, _level: LevelIn) -> GPoll<Extent> {
GPoll::Final(Extent::Exactly(1))
}

View File

@@ -1,15 +1,15 @@
use core_types::attribute::{Attr, BlendMode as BlendModeAttr, ClippingMask, EditorLayerPath, Opacity, OpacityFill, Transform as TransformAttr};
use core_types::list::{Item, List};
use core_types::uuid::NodeId;
use core_types::attribute::{Attr, BlendMode as BlendModeAttr, ClippingMask, EditorLayerPath, Opacity, OpacityFill, Transform as TransformAttr};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{bake_paint_transforms, set_paint_attribute};
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
use graphic_types::vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
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;
use graphic_types::vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
use graphic_types::{ATTR_FILL, Graphic, IntoGraphicList, Vector};
use linesweeper::topology::Topology;
use linesweeper::{BinaryOp, FillRule, binary_op};
@@ -21,7 +21,11 @@ pub use vector_types::vector::misc::BooleanOperation;
// TODO: since before we used a Vec of single-item `List`s and now we use a single `List`
// TODO: with multiple items while still assuming a single item for the boolean operations.
fn boolean_core<'e>(arena: &'e core_types::arena::Arena, content: List<Graphic>, operation: BooleanOperation) -> Result<
fn boolean_core<'e>(
arena: &'e core_types::arena::Arena,
content: List<Graphic>,
operation: BooleanOperation,
) -> Result<
(
Vector,
Attr<'e, TransformAttr>,
@@ -54,10 +58,12 @@ fn boolean_core<'e>(arena: &'e core_types::arena::Arena, content: List<Graphic>,
result_vector_list.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
}
let exhausted = || core_types::gpoll::Interrupt::from(core_types::gpoll::GraphError {
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
trace: Vec::new(),
});
let exhausted = || {
core_types::gpoll::Interrupt::from(core_types::gpoll::GraphError {
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
trace: Vec::new(),
})
};
let park_paint = |paint: Option<List<Graphic>>| -> Result<Option<&'e List<Graphic>>, core_types::gpoll::Interrupt> {
match paint {
Some(list) => Ok(Some(arena.alloc(list).ok_or_else(exhausted)?.0)),
@@ -66,8 +72,8 @@ fn boolean_core<'e>(arena: &'e core_types::arena::Arena, content: List<Graphic>,
};
let element = result_vector_list.element(0).cloned().unwrap_or_default();
let fill = park_paint(result_vector_list.attribute::<List<Graphic>>(graphic_types::ATTR_FILL, 0).cloned())?;
let stroke = park_paint(result_vector_list.attribute::<List<Graphic>>(graphic_types::ATTR_STROKE, 0).cloned())?;
let fill = park_paint(graphic_types::graphic::graphic_list_at(&result_vector_list, 0, graphic_types::ATTR_FILL).map(|paint| paint.into_owned()))?;
let stroke = park_paint(graphic_types::graphic::graphic_list_at(&result_vector_list, 0, graphic_types::ATTR_STROKE).map(|paint| paint.into_owned()))?;
let layer_path: Vec<NodeId> = result_vector_list
.attribute::<List<NodeId>>(ATTR_EDITOR_LAYER_PATH, 0)
.map(|path| path.iter_element_values().copied().collect())

View File

@@ -58,7 +58,11 @@ fn assign_color_at(gradient: &GradientStops, position: usize, length: usize, ran
let factor = match randomize {
true => {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
(0..=position).map(|_| rng.random::<f64>()).next_back().unwrap_or_default()
let mut draw = 0.;
for _ in 0..=position {
draw = rng.random::<f64>();
}
draw
}
false => match repeat_every {
0 => position as f64 / (length - 1).max(1) as f64,
@@ -171,7 +175,7 @@ fn assign_colors_graphic<'e>(
if lane >= content.len() {
return Err(GraphError::past_end().into());
}
let mut element = content.element_ref(lane).clone();
let mut element = graphic_types::graphic::map_groups_to_legacy(content.element_ref(lane));
let (transform, layer_path) = carried_lane_attrs(ctx.arena(), content.lane(lane))?;
if gradient.len() == 0 {
@@ -187,9 +191,11 @@ fn assign_colors_graphic<'e>(
false => gradient_element,
};
let interior_count = |graphic: &Graphic| graphic.as_vector().map_or(0, |list| list.len());
let length: usize = (0..content.len()).map(|row| interior_count(content.element_ref(row))).sum();
let mut position: usize = (0..lane).map(|row| interior_count(content.element_ref(row))).sum();
// The interiors the pre-flip node reached: only a lane's DIRECT vector
// list, so wrapped groups keep their own styling and consume no position.
let count_lane = |row: usize| graphic_types::graphic::map_groups_to_legacy(content.element_ref(row)).as_vector().map_or(0, |list| list.len());
let length: usize = (0..content.len()).map(count_lane).sum();
let mut position: usize = (0..lane).map(count_lane).sum();
if let Some(vector_list) = element.as_vector_mut() {
for index in 0..vector_list.len() {