Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)

* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates

* Refactor Table to move its hard-coded fields into an attributes field

* Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods

* Remove TaggedValue::GraphicUnused

* Refactor Table<T> to use dynamic attributes instead fixed names

* Fix code review soundness concerns

* Add todo work

* Replace row-oriented Table<T> API with column-oriented access

* Fix attribute propagation bugs

---------
This commit is contained in:
Keavon Chambers
2026-04-28 02:10:24 -07:00
parent 324b9e664c
commit 76938eb69a
75 changed files with 2330 additions and 1349 deletions

View File

@@ -30,13 +30,17 @@ mod blend_std {
impl Blend<Color> for Table<Raster<CPU>> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.iter_mut().zip(under.iter()) {
let data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
let pair_count = result_table.len().min(under.len());
for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break };
let Some(under_element) = under.element(index) else { break };
let data = over.data.iter().zip(under_element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
let (width, height) = (over.width, over.height);
*over.element = Raster::new_cpu(Image {
*result_table.element_mut(index).unwrap() = Raster::new_cpu(Image {
data,
width: over.element.width,
height: over.element.height,
width,
height,
base64_string: None,
});
}
@@ -46,8 +50,12 @@ mod blend_std {
impl Blend<Color> for Table<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.iter_mut().zip(under.iter()) {
*over.element = blend_fn(*over.element, *under.element);
let pair_count = result_table.len().min(under.len());
for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break };
let Some(under_element) = under.element(index) else { break };
let new_val = blend_fn(*over, *under_element);
*result_table.element_mut(index).unwrap() = new_val;
}
result_table
}
@@ -55,8 +63,12 @@ mod blend_std {
impl Blend<Color> for Table<GradientStops> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.iter_mut().zip(under.iter()) {
*over.element = over.element.blend(under.element, &blend_fn);
let pair_count = result_table.len().min(under.len());
for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break };
let Some(under_element) = under.element(index) else { break };
let new_val = over.blend(under_element, &blend_fn);
*result_table.element_mut(index).unwrap() = new_val;
}
result_table
}
@@ -201,7 +213,7 @@ mod test {
let opacity = 100.;
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.iter().next().unwrap().element;
let result = result.element(0).unwrap().clone();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));