Clean up duplicated code used for recursively flattening graphic types (#3836)

* Reduce recusive flattening algorithm duplication

* Generalize further

* Avoid code duplication in the 'Flatten Path' node

* Avoid cloning

* Include intermediate levels of alpha blending composition
This commit is contained in:
Keavon Chambers
2026-02-26 05:36:23 -08:00
committed by GitHub
parent cde7d5f951
commit 81c73d11ff
4 changed files with 105 additions and 250 deletions

View File

@@ -295,31 +295,31 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content.
#[node_macro::node(category("Vector"))]
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
content.into_flattened_vector_table()
content.into_flattened_table()
}
/// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content.
#[node_macro::node(category("Raster"))]
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {
content.into_flattened_raster_table()
content.into_flattened_table()
}
/// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content.
#[node_macro::node(category("General"))]
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {
content.into_flattened_color_table()
content.into_flattened_table()
}
/// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content.
#[node_macro::node(category("General"))]
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {
content.into_flattened_gradient_table()
content.into_flattened_table()
}
/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
#[node_macro::node(category("Color"))]
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> GradientStops {
let colors = colors.into_flattened_color_table();
let colors = colors.into_flattened_table::<Color>();
let total_colors = colors.len();
if total_colors == 0 {

View File

@@ -1212,66 +1212,28 @@ async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table<Ve
content
}
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
pub async fn flatten_path<T: 'n + Send>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
)]
content: Table<T>,
) -> Table<Vector>
where
Graphic: From<Table<T>>,
{
// NOTE(AdamGerhant):
// A node-based solution to support passing through vector data could be a network node with a cache node
// connected to a Flatten Path connected to an if else node, another connection from the cache directly to
// the if else node, and another connection from the cache to a matches type node connected to the if else node.
fn flatten_table(output: &mut TableRowMut<Vector>, graphic_table: &Table<Graphic>) {
for (current_index, current_element) in graphic_table.iter().enumerate() {
match current_element.element {
Graphic::Vector(vector) => {
// Loop through every row of the `Table<Vector>` and concatenate each element's subpath into the output `Vector` element.
for (vector_index, row) in vector.iter().enumerate() {
let other = row.element;
let transform = *current_element.transform * *row.transform;
let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
let mut hasher = DefaultHasher::new();
(current_index, vector_index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.element.concat(other, transform, collision_hash_seed);
// TODO: Make this instead use the first encountered style
// Use the last encountered style as the output style
output.element.style = row.element.style.clone();
}
}
Graphic::Graphic(graphic) => {
let mut graphic = graphic.clone();
for row in graphic.iter_mut() {
*row.transform = *current_element.transform * *row.transform;
}
flatten_table(output, &graphic);
}
_ => {}
}
}
}
pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
// Create a table with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to
let mut output_table = Table::new_from_element(Vector::default());
let Some(mut output) = output_table.iter_mut().next() else { return output_table };
let Some(output) = output_table.iter_mut().next() else { return output_table };
// Flatten the graphic input into the output `Vector` element
let base_graphic_table = Table::new_from_element(Graphic::from(content));
flatten_table(&mut output, &base_graphic_table);
// Concatenate every vector element's subpaths into the single output compound path
for (index, row) in content.into_flattened_table().iter().enumerate() {
let node_id = row.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
let mut hasher = DefaultHasher::new();
(index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.element.concat(row.element, *row.transform, collision_hash_seed);
// TODO: Make this instead use the first encountered style
// Use the last encountered style as the output style
output.element.style = row.element.style.clone();
}
// Return the single-row Table<Vector> containing the flattened Vector subpaths
output_table
}
@@ -1782,7 +1744,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
let graphic_table_content = content.clone().into_graphic_table();
// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content.
let content = content.into_flattened_vector_table();
let content = content.into_flattened_table::<Vector>();
// Determine source and target indices and interpolation time fraction
let progression = progression.max(0.);