Replace the AlphaBlending struct with separate attributes (#4086)

* Replace the AlphaBlending struct with separate attributes

* Fix bug

* Fix bug
This commit is contained in:
Keavon Chambers
2026-05-01 03:27:42 -07:00
committed by GitHub
parent 86134c26b4
commit 4474de4662
18 changed files with 374 additions and 295 deletions

View File

@@ -1,7 +1,6 @@
use core_types::AlphaBlending;
use core_types::registry::types::Percentage;
use core_types::table::Table;
use core_types::{ATTR_ALPHA_BLENDING, BlendMode, Color, Ctx};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, BlendMode, Color, Ctx};
use graphic_types::Graphic;
use graphic_types::Vector;
use graphic_types::raster_types::{CPU, Raster};
@@ -16,39 +15,42 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
fn multiply_table_attribute<T>(table: &mut Table<T>, key: &str, factor: f64) {
if let Some(values) = table.iter_attribute_values_mut::<f64>(key) {
for v in values {
*v *= factor;
}
} else {
for v in table.iter_attribute_values_mut_or_default::<f64>(key) {
*v = factor;
}
}
}
impl MultiplyAlpha for Table<Vector> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY, factor);
}
}
impl MultiplyAlpha for Table<Graphic> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY, factor);
}
}
impl MultiplyAlpha for Table<Raster<CPU>> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY, factor);
}
}
impl MultiplyAlpha for Table<Color> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY, factor);
}
}
impl MultiplyAlpha for Table<GradientStops> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY, factor);
}
}
@@ -62,37 +64,27 @@ impl MultiplyFill for Color {
}
impl MultiplyFill for Table<Vector> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
}
}
impl MultiplyFill for Table<Graphic> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
}
}
impl MultiplyFill for Table<Raster<CPU>> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
}
}
impl MultiplyFill for Table<Color> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
}
}
impl MultiplyFill for Table<GradientStops> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
}
}
@@ -100,39 +92,35 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
fn set_table_blend_mode<T>(table: &mut Table<T>, blend_mode: BlendMode) {
for v in table.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) {
*v = blend_mode;
}
}
impl SetBlendMode for Table<Vector> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
set_table_blend_mode(self, blend_mode);
}
}
impl SetBlendMode for Table<Graphic> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
set_table_blend_mode(self, blend_mode);
}
}
impl SetBlendMode for Table<Raster<CPU>> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
set_table_blend_mode(self, blend_mode);
}
}
impl SetBlendMode for Table<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
set_table_blend_mode(self, blend_mode);
}
}
impl SetBlendMode for Table<GradientStops> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
set_table_blend_mode(self, blend_mode);
}
}
@@ -140,39 +128,35 @@ trait SetClip {
fn set_clip(&mut self, clip: bool);
}
fn set_table_clip<T>(table: &mut Table<T>, clip: bool) {
for v in table.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) {
*v = clip;
}
}
impl SetClip for Table<Vector> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
set_table_clip(self, clip);
}
}
impl SetClip for Table<Graphic> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
set_table_clip(self, clip);
}
}
impl SetClip for Table<Raster<CPU>> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
set_table_clip(self, clip);
}
}
impl SetClip for Table<Color> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
set_table_clip(self, clip);
}
}
impl SetClip for Table<GradientStops> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
set_table_clip(self, clip);
}
}

View File

@@ -10,8 +10,8 @@ use core_types::table::{Table, TableRow};
use core_types::transform::Transform;
use core_types::uuid::NodeId;
use core_types::value::ClonedNode;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM};
use core_types::{AlphaBlending, Ctx, Node};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM};
use core_types::{Ctx, Node};
use glam::{DAffine2, DVec2};
use raster_nodes::blending_nodes::blend_colors;
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
@@ -314,12 +314,18 @@ async fn brush(
}
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
let blend_mode: BlendMode = actual_image.attribute_cloned_or_default(ATTR_BLEND_MODE);
let opacity: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY, 1.);
let fill: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK);
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
*image.element_mut(0).unwrap() = actual_image.into_element();
image.set_attribute(ATTR_TRANSFORM, 0, transform);
image.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending);
image.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
image.set_attribute(ATTR_OPACITY, 0, opacity);
image.set_attribute(ATTR_OPACITY_FILL, 0, fill);
image.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
image
@@ -410,7 +416,7 @@ mod test {
let image = brush(
(),
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
vec![BrushStroke {
Table::new_from_element(BrushStroke {
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle {
color: Color::BLACK,
@@ -420,7 +426,7 @@ mod test {
spacing: 20.,
blend_mode: BlendMode::Normal,
},
}],
}),
BrushCache::default(),
)
.await;

View File

@@ -1,6 +1,6 @@
use core_types::table::{Table, TableRow};
use core_types::uuid::NodeId;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AlphaBlending, Color, Ctx};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
use glam::{DAffine2, DVec2};
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
use graphic_types::vector_types::vector::PointId;
@@ -179,7 +179,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
}
Graphic::RasterCPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_row = |transform, layer, alpha_blending| {
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@@ -187,7 +187,10 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow::new_from_element(element)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
.with_attribute(ATTR_BLEND_MODE, blend_mode)
.with_attribute(ATTR_OPACITY, opacity)
.with_attribute(ATTR_OPACITY_FILL, fill)
.with_attribute(ATTR_CLIPPING_MASK, clip)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
};
@@ -198,14 +201,17 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
make_row(parent_transform * row_transform, layer, alpha_blending)
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
})
.collect::<Vec<_>>()
}
Graphic::RasterGPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_row = |transform, layer, alpha_blending| {
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@@ -213,7 +219,10 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow::new_from_element(element)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
.with_attribute(ATTR_BLEND_MODE, blend_mode)
.with_attribute(ATTR_OPACITY, opacity)
.with_attribute(ATTR_OPACITY_FILL, fill)
.with_attribute(ATTR_CLIPPING_MASK, clip)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
};
@@ -224,8 +233,11 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
make_row(parent_transform * row_transform, layer, alpha_blending)
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
})
.collect::<Vec<_>>()
}

View File

@@ -1,12 +1,11 @@
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
use core_types::blending::AlphaBlending;
use core_types::ATTR_TRANSFORM;
use core_types::color::Color;
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
use core_types::context::{Ctx, ExtractFootprint};
use core_types::math::bbox::Bbox;
use core_types::table::{Table, TableRow};
use core_types::transform::Transform;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM};
use dyn_any::DynAny;
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
@@ -284,7 +283,6 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
result_table.set_attribute(ATTR_TRANSFORM, 0, transform);
result_table.set_attribute(ATTR_ALPHA_BLENDING, 0, AlphaBlending::default());
// Callers of empty_image can safely unwrap on returned `Table`
result_table

View File

@@ -1,13 +1,16 @@
use core::cmp::Ordering;
use core::f64::consts::{PI, TAU};
use core::hash::{Hash, Hasher};
use core_types::AlphaBlending;
use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
use core_types::table::{Table, TableRow};
use core_types::transform::{Footprint, Transform};
use core_types::uuid::NodeId;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use core_types::{
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll,
OwnedContextImpl,
};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
use graphic_types::raster_types::{CPU, GPU, Raster};
@@ -2310,10 +2313,20 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
return content;
};
// Lerp styles
let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, source_index);
let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, target_index);
let vector_alpha_blending = source_alpha_blending.lerp(&target_alpha_blending, time as f32);
// Lerp blending attributes: opacity/fill interpolate, blend_mode/clip step at the midpoint
let source_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, source_index);
let target_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, target_index);
let source_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, source_index, 1.);
let target_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, target_index, 1.);
let source_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, source_index, 1.);
let target_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, target_index, 1.);
let source_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, source_index);
let target_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, target_index);
let lerped_blend_mode = if time < 0.5 { source_blend_mode } else { target_blend_mode };
let lerped_opacity = source_opacity + (target_opacity - source_opacity) * time;
let lerped_fill = source_fill + (target_fill - source_fill) * time;
let lerped_clip = if time < 0.5 { source_clip } else { target_clip };
// Evaluate the spatial position on the control path for the translation component.
// When the segment has zero arc length (e.g., two objects at the same position), inv_arclen
@@ -2534,7 +2547,10 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
Table::new_from_row(
TableRow::new_from_element(vector)
.with_attribute(ATTR_TRANSFORM, lerped_transform)
.with_attribute(ATTR_ALPHA_BLENDING, vector_alpha_blending)
.with_attribute(ATTR_BLEND_MODE, lerped_blend_mode)
.with_attribute(ATTR_OPACITY, lerped_opacity)
.with_attribute(ATTR_OPACITY_FILL, lerped_fill)
.with_attribute(ATTR_CLIPPING_MASK, lerped_clip)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content),
)