Skip to content

Commit 81c73d1

Browse files
authored
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
1 parent cde7d5f commit 81c73d1

File tree

4 files changed

+89
-234
lines changed

4 files changed

+89
-234
lines changed

node-graph/libraries/graphic-types/src/graphic.rs

Lines changed: 65 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -104,201 +104,94 @@ impl From<Table<GradientStops>> for Graphic {
104104
}
105105
}
106106

107-
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)
108-
pub trait IntoGraphicTable {
109-
fn into_graphic_table(self) -> Table<Graphic>;
107+
/// Deeply flattens a graphic table, collecting only elements matching a specific variant (extracted by `extract_variant`)
108+
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-tables composes transforms and opacity.
109+
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
110+
fn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending {
111+
AlphaBlending {
112+
blend_mode: child.blend_mode,
113+
opacity: parent.opacity * child.opacity,
114+
fill: child.fill,
115+
clip: child.clip,
116+
}
117+
}
110118

111-
/// Deeply flattens any vector content within a graphic table, discarding non-vector content, and returning a table of only vector elements.
112-
fn into_flattened_vector_table(self) -> Table<Vector>
113-
where
114-
Self: std::marker::Sized,
115-
{
116-
let content = self.into_graphic_table();
117-
118-
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
119-
fn flatten_table(output_vector_table: &mut Table<Vector>, current_graphic_table: Table<Graphic>) {
120-
for current_graphic_row in current_graphic_table.iter() {
121-
let current_graphic = current_graphic_row.element.clone();
122-
let source_node_id = *current_graphic_row.source_node_id;
123-
124-
match current_graphic {
125-
// If we're allowed to recurse, flatten any tables we encounter
126-
Graphic::Graphic(mut current_graphic_table) => {
127-
// Apply the parent graphic's transform to all child elements
128-
for graphic in current_graphic_table.iter_mut() {
129-
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
130-
}
119+
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
120+
for current_graphic_row in current_graphic_table.into_iter() {
121+
let source_node_id = current_graphic_row.source_node_id;
131122

132-
flatten_table(output_vector_table, current_graphic_table);
123+
match current_graphic_row.element {
124+
// Recurse into nested graphic tables, composing the parent's transform onto each child
125+
Graphic::Graphic(mut sub_table) => {
126+
for graphic in sub_table.iter_mut() {
127+
*graphic.transform = current_graphic_row.transform * *graphic.transform;
128+
*graphic.alpha_blending = compose_alpha_blending(current_graphic_row.alpha_blending, *graphic.alpha_blending);
133129
}
134-
// Push any leaf Vector elements we encounter
135-
Graphic::Vector(vector_table) => {
136-
for current_vector_row in vector_table.iter() {
137-
output_vector_table.push(TableRow {
138-
element: current_vector_row.element.clone(),
139-
transform: *current_graphic_row.transform * *current_vector_row.transform,
140-
alpha_blending: AlphaBlending {
141-
blend_mode: current_vector_row.alpha_blending.blend_mode,
142-
opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
143-
fill: current_vector_row.alpha_blending.fill,
144-
clip: current_vector_row.alpha_blending.clip,
145-
},
130+
131+
flatten_recursive(output, sub_table, extract_variant);
132+
}
133+
// Try to extract the target variant; if it matches, push its rows with composed transform and opacity
134+
other => {
135+
if let Some(typed_table) = extract_variant(other) {
136+
for row in typed_table.into_iter() {
137+
output.push(TableRow {
138+
element: row.element,
139+
transform: current_graphic_row.transform * row.transform,
140+
alpha_blending: compose_alpha_blending(current_graphic_row.alpha_blending, row.alpha_blending),
146141
source_node_id,
147142
});
148143
}
149144
}
150-
_ => {}
151145
}
152146
}
153147
}
154-
155-
let mut output = Table::new();
156-
flatten_table(&mut output, content);
157-
output
158148
}
159149

160-
/// Deeply flattens any raster content within a graphic table, discarding non-raster content, and returning a table of only raster elements.
161-
fn into_flattened_raster_table(self) -> Table<Raster<CPU>>
162-
where
163-
Self: std::marker::Sized,
164-
{
165-
let content = self.into_graphic_table();
166-
167-
fn flatten_table(output_raster_table: &mut Table<Raster<CPU>>, current_graphic_table: Table<Graphic>) {
168-
for current_graphic_row in current_graphic_table.iter() {
169-
let current_graphic = current_graphic_row.element.clone();
170-
let source_node_id = *current_graphic_row.source_node_id;
171-
172-
match current_graphic {
173-
// If we're allowed to recurse, flatten any tables we encounter
174-
Graphic::Graphic(mut current_graphic_table) => {
175-
// Apply the parent graphic's transform to all child elements
176-
for graphic in current_graphic_table.iter_mut() {
177-
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
178-
}
150+
let mut output = Table::new();
151+
flatten_recursive(&mut output, content, extract_variant);
152+
output
153+
}
179154

180-
flatten_table(output_raster_table, current_graphic_table);
181-
}
182-
// Push any leaf RasterCPU elements we encounter
183-
Graphic::RasterCPU(raster_table) => {
184-
for current_raster_row in raster_table.iter() {
185-
output_raster_table.push(TableRow {
186-
element: current_raster_row.element.clone(),
187-
transform: *current_graphic_row.transform * *current_raster_row.transform,
188-
alpha_blending: AlphaBlending {
189-
blend_mode: current_raster_row.alpha_blending.blend_mode,
190-
opacity: current_graphic_row.alpha_blending.opacity * current_raster_row.alpha_blending.opacity,
191-
fill: current_raster_row.alpha_blending.fill,
192-
clip: current_raster_row.alpha_blending.clip,
193-
},
194-
source_node_id,
195-
});
196-
}
197-
}
198-
_ => {}
199-
}
200-
}
201-
}
155+
/// Maps from a concrete element type to its corresponding `Graphic` enum variant,
156+
/// enabling type-directed casting of typed tables from a `Graphic` value.
157+
pub trait TryFromGraphic: Clone + Sized {
158+
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
159+
}
202160

203-
let mut output = Table::new();
204-
flatten_table(&mut output, content);
205-
output
161+
impl TryFromGraphic for Vector {
162+
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
163+
if let Graphic::Vector(t) = graphic { Some(t) } else { None }
206164
}
165+
}
207166

208-
/// Deeply flattens any color content within a graphic table, discarding non-color content, and returning a table of only color elements.
209-
fn into_flattened_color_table(self) -> Table<Color>
210-
where
211-
Self: std::marker::Sized,
212-
{
213-
let content = self.into_graphic_table();
214-
215-
fn flatten_table(output_color_table: &mut Table<Color>, current_graphic_table: Table<Graphic>) {
216-
for current_graphic_row in current_graphic_table.iter() {
217-
let current_graphic = current_graphic_row.element.clone();
218-
let source_node_id = *current_graphic_row.source_node_id;
219-
220-
match current_graphic {
221-
// If we're allowed to recurse, flatten any tables we encounter
222-
Graphic::Graphic(mut current_graphic_table) => {
223-
// Apply the parent graphic's transform to all child elements
224-
for graphic in current_graphic_table.iter_mut() {
225-
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
226-
}
167+
impl TryFromGraphic for Raster<CPU> {
168+
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
169+
if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None }
170+
}
171+
}
227172

228-
flatten_table(output_color_table, current_graphic_table);
229-
}
230-
// Push any leaf Color elements we encounter
231-
Graphic::Color(color_table) => {
232-
for current_color_row in color_table.iter() {
233-
output_color_table.push(TableRow {
234-
element: *current_color_row.element,
235-
transform: *current_graphic_row.transform * *current_color_row.transform,
236-
alpha_blending: AlphaBlending {
237-
blend_mode: current_color_row.alpha_blending.blend_mode,
238-
opacity: current_graphic_row.alpha_blending.opacity * current_color_row.alpha_blending.opacity,
239-
fill: current_color_row.alpha_blending.fill,
240-
clip: current_color_row.alpha_blending.clip,
241-
},
242-
source_node_id,
243-
});
244-
}
245-
}
246-
_ => {}
247-
}
248-
}
249-
}
173+
impl TryFromGraphic for Color {
174+
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
175+
if let Graphic::Color(t) = graphic { Some(t) } else { None }
176+
}
177+
}
250178

251-
let mut output = Table::new();
252-
flatten_table(&mut output, content);
253-
output
179+
impl TryFromGraphic for GradientStops {
180+
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
181+
if let Graphic::Gradient(t) = graphic { Some(t) } else { None }
254182
}
183+
}
255184

256-
/// Deeply flattens any gradient content within a graphic table, discarding non-gradient content, and returning a table of only gradient elements.
257-
fn into_flattened_gradient_table(self) -> Table<GradientStops>
185+
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)
186+
pub trait IntoGraphicTable {
187+
fn into_graphic_table(self) -> Table<Graphic>;
188+
189+
/// Deeply flattens any content of type `T` within a graphic table, discarding all other content, and returning a flat table of only `T` elements.
190+
fn into_flattened_table<T: TryFromGraphic>(self) -> Table<T>
258191
where
259192
Self: std::marker::Sized,
260193
{
261-
let content = self.into_graphic_table();
262-
263-
fn flatten_table(output_gradient_table: &mut Table<GradientStops>, current_graphic_table: Table<Graphic>) {
264-
for current_graphic_row in current_graphic_table.iter() {
265-
let current_graphic = current_graphic_row.element.clone();
266-
let source_node_id = *current_graphic_row.source_node_id;
267-
268-
match current_graphic {
269-
// If we're allowed to recurse, flatten any tables we encounter
270-
Graphic::Graphic(mut current_graphic_table) => {
271-
// Apply the parent graphic's transform to all child elements
272-
for graphic in current_graphic_table.iter_mut() {
273-
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
274-
}
275-
276-
flatten_table(output_gradient_table, current_graphic_table);
277-
}
278-
// Push any leaf GradientStops elements we encounter
279-
Graphic::Gradient(gradient_table) => {
280-
for current_gradient_row in gradient_table.iter() {
281-
output_gradient_table.push(TableRow {
282-
element: current_gradient_row.element.clone(),
283-
transform: *current_graphic_row.transform * *current_gradient_row.transform,
284-
alpha_blending: AlphaBlending {
285-
blend_mode: current_gradient_row.alpha_blending.blend_mode,
286-
opacity: current_graphic_row.alpha_blending.opacity * current_gradient_row.alpha_blending.opacity,
287-
fill: current_gradient_row.alpha_blending.fill,
288-
clip: current_gradient_row.alpha_blending.clip,
289-
},
290-
source_node_id,
291-
});
292-
}
293-
}
294-
_ => {}
295-
}
296-
}
297-
}
298-
299-
let mut output = Table::new();
300-
flatten_table(&mut output, content);
301-
output
194+
flatten_graphic_table(self.into_graphic_table(), T::try_from_graphic)
302195
}
303196
}
304197

node-graph/libraries/graphic-types/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub use vector_types;
88

99
// Re-export commonly used types at the crate root
1010
pub use artboard::Artboard;
11-
pub use graphic::{Graphic, IntoGraphicTable, Vector};
11+
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
1212

1313
pub mod migrations {
1414
use core_types::{

node-graph/nodes/graphic/src/graphic.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,31 +295,31 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
295295
/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content.
296296
#[node_macro::node(category("Vector"))]
297297
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
298-
content.into_flattened_vector_table()
298+
content.into_flattened_table()
299299
}
300300

301301
/// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content.
302302
#[node_macro::node(category("Raster"))]
303303
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {
304-
content.into_flattened_raster_table()
304+
content.into_flattened_table()
305305
}
306306

307307
/// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content.
308308
#[node_macro::node(category("General"))]
309309
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {
310-
content.into_flattened_color_table()
310+
content.into_flattened_table()
311311
}
312312

313313
/// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content.
314314
#[node_macro::node(category("General"))]
315315
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {
316-
content.into_flattened_gradient_table()
316+
content.into_flattened_table()
317317
}
318318

319319
/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
320320
#[node_macro::node(category("Color"))]
321321
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> GradientStops {
322-
let colors = colors.into_flattened_color_table();
322+
let colors = colors.into_flattened_table::<Color>();
323323
let total_colors = colors.len();
324324

325325
if total_colors == 0 {

0 commit comments

Comments
 (0)