Instance tables refactor part 6: unwrap VectorData and ImageFrame from single-row to multi-row tables (#2684)

* Start refactoring the boolean operations code

* Switch to iterators in the boolean operations code

* Make boolean operations work on rows of a table, not Vecs of single-row tables

* Remove more .transform()

* Simplify brush code

* Attempt to remove .transform() by using Instance<Image<Color>> in brush code, but a regression is introduced

* Improve blend_image_closure

* Simplify

* Remove leading underscore from type arguments

* Remove .transform() from ImageFrameTable<P> and fix Mask node behavior on stencils not fully overlapping its target image

* Remove more .one_instance_ref()

* Fully remove .one_instance_ref() and improve the 'Combine Channels' node robustness

* Fully remove .once_instance_mut()

* Fix tests

* Remove .one_empty_image()

* Make Instances<T>::default() return an empty table for images, but still not yet vector

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2025-06-04 20:40:15 -07:00
committed by GitHub
parent 76ecdc8f1b
commit cb4289169d
29 changed files with 882 additions and 811 deletions

View File

@@ -1,7 +1,7 @@
use bezier_rs::{ManipulatorGroup, Subpath};
use glam::{DAffine2, DVec2};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::RasterFrame;
use graphene_core::instances::{Instance, InstanceRef};
use graphene_core::vector::misc::BooleanOperation;
use graphene_core::vector::style::Fill;
pub use graphene_core::vector::*;
@@ -10,207 +10,245 @@ pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation};
use std::ops::Mul;
// TODO: Fix boolean ops to work by removing .transform() and .one_instnace_*() calls,
// TODO: since before we used a Vec of single-row tables and now we use a single table
// TODO: with multiple rows while still assuming a single row for the boolean operations.
#[node_macro::node(category(""))]
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
graphic_group_table
.instance_ref_iter()
.map(|element| match element.instance.clone() {
GraphicElement::VectorData(mut vector_data) => {
// Apply the parent group's transform to each element of vector data
for sub_vector_data in vector_data.instance_mut_iter() {
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
}
vector_data
}
GraphicElement::RasterFrame(mut image) => {
// Apply the parent group's transform to each element of raster data
match &mut image {
graphene_core::RasterFrame::ImageFrame(image) => {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
graphene_core::RasterFrame::TextureFrame(image) => {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
}
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(image.transform());
// Create a vector data table from the rectangular subpath, with a default black fill
let mut vector_data = VectorData::from_subpath(subpath);
vector_data.style.set_fill(Fill::Solid(Color::BLACK));
VectorDataTable::new(vector_data)
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instance_mut_iter() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
// Recursively flatten the inner group into vector data
boolean_operation_on_vector_data(&flatten_vector_data(&graphic_group), BooleanOperation::Union)
}
})
.collect()
}
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
let mut vector_data = vector_data.into_iter();
let mut result = vector_data.next().cloned().unwrap_or_default();
let mut next_vector_data = vector_data.next();
while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
next_vector_data = vector_data.next();
}
result
}
fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable {
match boolean_operation {
BooleanOperation::Union => {
// Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_table = vector_data_table.iter().rev();
let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default();
// Loop over all vector data and union it with the result
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform();
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result_vector_data.point_domain = boolean_operation_result.point_domain;
result_vector_data.segment_domain = boolean_operation_result.segment_domain;
result_vector_data.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data_table.next();
}
result_vector_data_table
}
BooleanOperation::SubtractFront => subtract(vector_data_table.iter()),
BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()),
BooleanOperation::Intersect => {
let mut vector_data = vector_data_table.iter().rev();
let mut result = vector_data.next().cloned().unwrap_or_default();
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(&default));
// For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
}
result
}
BooleanOperation::Difference => {
let mut vector_data_iter = vector_data_table.iter().rev();
let mut any_intersection = VectorDataTable::default();
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default));
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
let all_other_vector_data_instance = all_other_vector_data.one_instance_ref();
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
*boolean_intersection_result.transform_mut() = *all_other_vector_data_instance.transform;
boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
*boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform.inverse() * any_intersection.transform();
let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
*any_intersection.one_instance_mut().instance = union_result;
*any_intersection.transform_mut() = boolean_intersection_result.transform();
any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone();
any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending;
second_vector_data = vector_data_iter.next();
}
// Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union);
boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront)
}
}
}
// The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data(&flatten_vector_data(&group_of_paths), operation);
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
let result_vector_data_table_transform = result_vector_data_table.transform();
*result_vector_data_table.transform_mut() = DAffine2::IDENTITY;
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
VectorData::transform(result_vector_data, result_vector_data_table_transform);
result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.upstream_graphic_group = Some(group_of_paths.clone());
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().next() {
let transform = *result_vector_data.transform;
*result_vector_data.transform = DAffine2::IDENTITY;
VectorData::transform(result_vector_data.instance, transform);
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
}
result_vector_data_table
}
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> VectorDataTable {
match boolean_operation {
BooleanOperation::Union => union(vector_data),
BooleanOperation::SubtractFront => subtract(vector_data),
BooleanOperation::SubtractBack => subtract(vector_data.rev()),
BooleanOperation::Intersect => intersect(vector_data),
BooleanOperation::Difference => difference(vector_data),
}
}
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
// Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_reversed = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::empty();
result_vector_data_table.push(vector_data_reversed.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
// Loop over all vector data and union it with the result
let default = Instance::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.to_instance_ref()));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data_reversed.next();
}
result_vector_data_table
}
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
let mut vector_data = vector_data.into_iter();
let mut result_vector_data_table = VectorDataTable::empty();
result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
let mut next_vector_data = vector_data.next();
while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
next_vector_data = vector_data.next();
}
result_vector_data_table
}
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
let mut vector_data = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::empty();
result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
let default = Instance::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_ref()));
// For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
}
result_vector_data_table
}
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable {
let mut vector_data_iter = vector_data.clone().rev();
let mut any_intersection = Instance::default();
let default = Instance::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.to_instance_ref()));
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
let first_instance = unioned.instance_ref_iter().next().expect("Expected at least one instance after the boolean union");
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let upper_path_string = to_path(first_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut instance = from_path(&boolean_intersection_string);
instance.style = first_instance.instance.style.clone();
let boolean_intersection_result = Instance {
instance,
transform: *first_instance.transform,
alpha_blending: *first_instance.alpha_blending,
source_node_id: *first_instance.source_node_id,
};
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
let upper_path_string = to_path(&boolean_intersection_result.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(&any_intersection.instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
any_intersection.instance = union_result;
any_intersection.transform = boolean_intersection_result.transform;
any_intersection.instance.style = boolean_intersection_result.instance.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
second_vector_data = vector_data_iter.next();
}
// Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
boolean_operation_on_vector_data_table(union.instance_ref_iter().chain(std::iter::once(any_intersection.to_instance_ref())), BooleanOperation::SubtractFront)
}
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable {
let mut result_table = VectorDataTable::empty();
for element in graphic_group_table.instance_ref_iter() {
match element.instance.clone() {
GraphicElement::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data
for mut sub_vector_data in vector_data.instance_iter() {
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
result_table.push(sub_vector_data);
}
}
GraphicElement::RasterFrame(image) => {
let make_instance = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill
let mut instance = VectorData::from_subpath(subpath);
instance.style.set_fill(Fill::Solid(Color::BLACK));
Instance { instance, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
match image {
RasterFrame::ImageFrame(image) => {
for instance in image.instance_ref_iter() {
result_table.push(make_instance(*element.transform * *instance.transform));
}
}
RasterFrame::TextureFrame(image) => {
for instance in image.instance_ref_iter() {
result_table.push(make_instance(*element.transform * *instance.transform));
}
}
}
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instance_mut_iter() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
// Recursively flatten the inner group into vector data
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union);
for element in unioned.instance_iter() {
result_table.push(element);
}
}
}
}
result_table
}
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
let mut path = Vec::new();
for subpath in vector.stroke_bezier_paths() {