Add Table<Color> as a graphical type (#3033)

* Reduce code duplication in bounding box impls on Table

* Working Table<Color> rendering in the graph

* Implement color and fix other rendering with Vello and polish
This commit is contained in:
Keavon Chambers
2025-08-10 01:34:33 -07:00
committed by GitHub
parent 81abfe147a
commit 2f4aef34e5
24 changed files with 462 additions and 198 deletions

View File

@@ -3,6 +3,7 @@ use crate::table::{Table, TableRowRef};
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
use glam::DVec2;
use graphene_core_shaders::color::Color;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
@@ -11,7 +12,8 @@ async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
reverse: bool,
@@ -52,7 +54,8 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
#[default(1)] count: u64,

View File

@@ -4,7 +4,7 @@ use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_f
use super::misc::{CentroidType, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, point_to_dvec2};
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, Vector, VectorExt};
use crate::bounds::BoundingBox;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::raster_types::{CPU, GPU, Raster};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
use crate::table::{Table, TableRow, TableRowMut};
@@ -106,7 +106,7 @@ where
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
async fn fill<F: Into<Fill> + 'n + Send, V>(
async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
_: impl Ctx,
#[implementations(
Table<Vector>,
@@ -116,7 +116,7 @@ async fn fill<F: Into<Fill> + 'n + Send, V>(
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>
Table<Graphic>,
)]
/// The content with vector paths to apply the fill style to.
mut content: V,
@@ -135,10 +135,7 @@ async fn fill<F: Into<Fill> + 'n + Send, V>(
fill: F,
_backup_color: Option<Color>,
_backup_gradient: Gradient,
) -> V
where
V: VectorTableIterMut + 'n + Send,
{
) -> V {
let fill: Fill = fill.into();
for vector in content.vector_iter_mut() {
let mut fill = fill.clone();
@@ -219,7 +216,7 @@ where
async fn repeat<I: 'n + Send + Clone>(
_: impl Ctx,
// TODO: Implement other graphical types.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] instance: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] instance: Table<I>,
#[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: PixelSize,
@@ -255,7 +252,7 @@ async fn repeat<I: 'n + Send + Clone>(
async fn circular_repeat<I: 'n + Send + Clone>(
_: impl Ctx,
// TODO: Implement other graphical types.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] instance: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] instance: Table<I>,
angle_offset: Angle,
#[unit(" px")]
#[default(5)]
@@ -291,7 +288,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
points: Table<Vector>,
#[expose]
/// Artwork to be copied and placed at each point.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)]
instance: Table<I>,
/// Minimum range of randomized sizes given to each instance.
#[default(1)]
@@ -366,7 +363,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn mirror<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] instance: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] content: Table<I>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
#[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle,
@@ -375,14 +372,12 @@ async fn mirror<I: 'n + Send + Clone>(
where
Table<I>: BoundingBox,
{
let mut result_table = Table::new();
// Normalize the direction vector
let normal = DVec2::from_angle(angle.to_radians());
// The mirror reference is based on the bounding box (at least for now, until we have proper local layer origins)
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else {
return result_table;
// The mirror reference may be based on the bounding box if an explicit reference point is chosen
let RenderBoundingBox::Rectangle(bounding_box) = content.bounding_box(DAffine2::IDENTITY, false) else {
return content;
};
let reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into());
@@ -404,15 +399,17 @@ where
reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset))
};
let mut result_table = Table::new();
// Add original instance depending on the keep_original flag
if keep_original {
for instance in instance.clone().into_iter() {
for instance in content.clone().into_iter() {
result_table.push(instance);
}
}
// Create and add mirrored instance
for mut row in instance.into_iter() {
for mut row in content.into_iter() {
row.transform = reflected_transform * row.transform;
result_table.push(row);
}
@@ -1901,7 +1898,7 @@ fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {
}
#[node_macro::node(category("General"), path(graphene_core::vector))]
async fn count_elements<I>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>)] source: Table<I>) -> u64 {
async fn count_elements<I>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)] source: Table<I>) -> u64 {
source.len() as u64
}

View File

@@ -2,7 +2,7 @@ use super::misc::dvec2_to_point;
use super::style::{PathStyle, Stroke};
pub use super::vector_attributes::*;
pub use super::vector_modification::*;
use crate::bounds::BoundingBox;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::math::quad::Quad;
use crate::table::Table;
use crate::transform::Transform;
@@ -437,8 +437,9 @@ impl Vector {
}
impl BoundingBox for Table<Vector> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter()
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
let bounds = self
.iter()
.flat_map(|row| {
if !include_stroke {
return row.element.bounding_box_with_transform(transform * *row.transform);
@@ -455,7 +456,12 @@ impl BoundingBox for Table<Vector> {
row.element.bounding_box_with_transform(transform * *row.transform).map(|[a, b]| [a - offset, b + offset])
})
.reduce(Quad::combine_bounds)
.reduce(Quad::combine_bounds);
match bounds {
Some(bounds) => RenderBoundingBox::Rectangle(bounds),
None => RenderBoundingBox::None,
}
}
}