Rename Instances<T> to Table<T> and the "instance" terminology to "TableRow" and "element" (#2981)

* Instances -> Table

* instances.rs -> table.rs

* Rename occurrances of the word "instances"

* .instance -> .element

* Instance* -> TableRow*

* Rename Table and TableRow methods to not say "instance"

* Remove presumed unused serde defaults now that tables default to length 0 not 1

* Rename occurences of the word "instance"

* Un-alias the RasterDataTable<Storage>, VectorDataTable, GraphicGroupTable, ArtboardGroupTable typedefs

* Move artboard type and node code out of graphic_element.rs to a new artboard.rs

* Organize the TaggedValues

* Fix tests

* Fix prior regression with Image Value node not upgrading
This commit is contained in:
Keavon Chambers
2025-08-03 04:12:18 -07:00
committed by GitHub
parent 67123f55dc
commit a0ce56d9b6
66 changed files with 1906 additions and 2061 deletions

View File

@@ -5,12 +5,12 @@ use graphene_core::blending::BlendMode;
use graphene_core::bounds::BoundingBox;
use graphene_core::color::{Alpha, Color, Pixel, Sample};
use graphene_core::generic::FnNode;
use graphene_core::instances::Instance;
use graphene_core::math::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::BitmapMut;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::FutureWrapperNode;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::Transform;
use graphene_core::value::ClonedNode;
use graphene_core::{Ctx, Node};
@@ -77,7 +77,7 @@ fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f
}
#[node_macro::node(skip_impl)]
fn blit<BlendFn>(mut target: RasterDataTable<CPU>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> RasterDataTable<CPU>
fn blit<BlendFn>(mut target: Table<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> Table<Raster<CPU>>
where
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
{
@@ -85,14 +85,14 @@ where
return target;
}
for target_instance in target.instance_mut_iter() {
let target_width = target_instance.instance.width;
let target_height = target_instance.instance.height;
for table_row in target.iter_mut() {
let target_width = table_row.element.width;
let target_height = table_row.element.height;
let target_size = DVec2::new(target_width as f64, target_height as f64);
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target_instance.transform.inverse();
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * table_row.transform.inverse();
for position in &positions {
let start = document_to_target.transform_point2(*position).round();
@@ -112,12 +112,12 @@ where
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
assert!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target_instance.instance.data.len());
assert!(target_index(max_x, max_y) < table_row.element.data.len());
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
let src_pixel = texture.data[texture_index(x, y)];
let dst_pixel = &mut target_instance.instance.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
let dst_pixel = &mut table_row.element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
}
}
@@ -130,13 +130,13 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = empty_image((), transform, Color::TRANSPARENT).instance_iter().next().unwrap_or_default();
let blank_texture = empty_image((), transform, Color::TRANSPARENT).iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.instance
image.element
}
pub fn blend_with_mode(background: Instance<Raster<CPU>>, foreground: Instance<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> Instance<Raster<CPU>> {
pub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> TableRow<Raster<CPU>> {
let opacity = opacity / 100.;
match std::hint::black_box(blend_mode) {
// Normal group
@@ -179,14 +179,14 @@ pub fn blend_with_mode(background: Instance<Raster<CPU>>, foreground: Instance<R
}
#[node_macro::node(category("Raster"))]
async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes: Vec<BrushStroke>, cache: BrushCache) -> RasterDataTable<CPU> {
async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes: Vec<BrushStroke>, cache: BrushCache) -> Table<Raster<CPU>> {
if image_frame_table.is_empty() {
image_frame_table.push(Instance::default());
image_frame_table.push(TableRow::default());
}
// TODO: Find a way to handle more than one instance
let image_frame_instance = image_frame_table.instance_ref_iter().next().expect("Expected the one instance we just pushed").to_instance_cloned();
// TODO: Find a way to handle more than one row
let table_row = image_frame_table.iter_ref().next().expect("Expected the one row we just pushed").into_cloned();
let [start, end] = image_frame_instance.clone().to_table().bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let [start, end] = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let image_bbox = AxisAlignedBbox { start, end };
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
@@ -195,11 +195,11 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let mut brush_plan = cache.compute_brush_plan(image_frame_instance, &draw_strokes);
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
// TODO: Find a way to handle more than one instance
let Some(mut actual_image) = extend_image_to_bounds((), brush_plan.background.to_table(), background_bounds).instance_iter().next() else {
return RasterDataTable::default();
// TODO: Find a way to handle more than one row
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).iter().next() else {
return Table::new();
};
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
@@ -238,15 +238,15 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
);
let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), target.to_table(), stroke_to_layer)
extend_image_to_bounds((), Table::new_from_row(target), stroke_to_layer)
} else {
empty_image((), stroke_to_layer, Color::TRANSPARENT)
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
};
let instances = blit_node.eval(blit_target).await;
assert_eq!(instances.len(), 1);
instances.instance_iter().next().unwrap_or_default()
let table = blit_node.eval(blit_target).await;
assert_eq!(table.len(), 1);
table.iter().next().unwrap_or_default()
};
// Cache image before doing final blend, and store final stroke texture.
@@ -261,8 +261,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
if has_erase_strokes {
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = Instance {
instance: Raster::new_cpu(opaque_image),
let mut erase_restore_mask = TableRow {
element: Raster::new_cpu(opaque_image),
transform: background_bounds,
..Default::default()
};
@@ -285,7 +285,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
@@ -295,7 +295,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
}
_ => unreachable!(),
}
@@ -305,8 +305,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
}
let first_row = image_frame_table.instance_mut_iter().next().unwrap();
*first_row.instance = actual_image.instance;
let first_row = image_frame_table.iter_mut().next().unwrap();
*first_row.element = actual_image.element;
*first_row.transform = actual_image.transform;
*first_row.alpha_blending = actual_image.alpha_blending;
*first_row.source_node_id = actual_image.source_node_id;
@@ -314,9 +314,9 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
image_frame_table
}
pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: Instance<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Raster<CPU>> {
let foreground_size = DVec2::new(foreground.instance.width as f64, foreground.instance.height as f64);
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64);
pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
let foreground_size = DVec2::new(foreground.element.width as f64, foreground.element.height as f64);
let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
// Transforms a point from the background image to the foreground image
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
@@ -333,8 +333,8 @@ pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: In
let background_point = DVec2::new(x as f64, y as f64);
let foreground_point = background_to_foreground.transform_point2(background_point);
let source_pixel = foreground.instance.sample(foreground_point);
let Some(destination_pixel) = background.instance.data_mut().get_pixel_mut(x, y) else { continue };
let source_pixel = foreground.element.sample(foreground_point);
let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel);
}
@@ -343,8 +343,8 @@ pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: In
background
}
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: Instance<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Raster<CPU>> {
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64);
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
// Transforms a point from the background image to the foreground image
let background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size);
@@ -363,7 +363,7 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut backgroun
let foreground_point = background_to_foreground.transform_point2(background_point);
let Some(source_pixel) = foreground.sample(foreground_point, area) else { continue };
let Some(destination_pixel) = background.instance.data_mut().get_pixel_mut(x, y) else { continue };
let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel);
}
@@ -391,7 +391,7 @@ mod test {
async fn test_brush_output_size() {
let image = brush(
(),
RasterDataTable::<CPU>::new(Raster::new_cpu(Image::<Color>::default())),
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
vec![BrushStroke {
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle {
@@ -406,6 +406,6 @@ mod test {
BrushCache::default(),
)
.await;
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
assert_eq!(image.iter_ref().next().unwrap().element.width, 20);
}
}

View File

@@ -1,9 +1,9 @@
use crate::brush_stroke::BrushStroke;
use crate::brush_stroke::BrushStyle;
use dyn_any::DynAny;
use graphene_core::instances::Instance;
use graphene_core::raster_types::CPU;
use graphene_core::raster_types::Raster;
use graphene_core::table::TableRow;
use std::collections::HashMap;
use std::hash::Hash;
use std::hash::Hasher;
@@ -20,12 +20,12 @@ struct BrushCacheImpl {
prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background.
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
background: Instance<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
blended_image: Instance<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
last_stroke_texture: Instance<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
background: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
blended_image: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
last_stroke_texture: TableRow<Raster<CPU>>,
// A cache for brush textures.
#[serde(skip)]
@@ -33,7 +33,7 @@ struct BrushCacheImpl {
}
impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
fn compute_brush_plan(&mut self, mut background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation.
if background != self.background {
self.background = background.clone();
@@ -60,8 +60,8 @@ impl BrushCacheImpl {
background = std::mem::take(&mut self.blended_image);
// Check if the first non-blended stroke is an extension of the last one.
let mut first_stroke_texture = Instance {
instance: Raster::<CPU>::default(),
let mut first_stroke_texture = TableRow {
element: Raster::<CPU>::default(),
transform: glam::DAffine2::ZERO,
..Default::default()
};
@@ -88,7 +88,7 @@ impl BrushCacheImpl {
}
}
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {
self.prev_input = input;
self.blended_image = blended_image;
self.last_stroke_texture = last_stroke_texture;
@@ -123,8 +123,8 @@ impl Hash for BrushCacheImpl {
#[derive(Clone, Debug, Default)]
pub struct BrushPlan {
pub strokes: Vec<BrushStroke>,
pub background: Instance<Raster<CPU>>,
pub first_stroke_texture: Instance<Raster<CPU>>,
pub background: TableRow<Raster<CPU>>,
pub first_stroke_texture: TableRow<Raster<CPU>>,
pub first_stroke_point_skip: usize,
}
@@ -160,12 +160,12 @@ impl Hash for BrushCache {
}
impl BrushCache {
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
pub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.0.lock().unwrap();
inner.compute_brush_plan(background, input)
}
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {
let mut inner = self.0.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture)
}

View File

@@ -0,0 +1,149 @@
use crate::blending::AlphaBlending;
use crate::bounds::BoundingBox;
use crate::math::quad::Quad;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::transform::TransformMut;
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
use std::hash::Hash;
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: Table<GraphicElement>,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
pub background: Color,
pub clip: bool,
}
impl Default for Artboard {
fn default() -> Self {
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
}
}
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: Table::new(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background: Color::WHITE,
clip: false,
}
}
}
impl BoundingBox for Artboard {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
}
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(Table<Artboard>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = Table::new();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(TableRow {
element: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
})
}
impl BoundingBox for Table<Artboard> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref().filter_map(|row| row.element.bounding_box(transform, include_stroke)).reduce(Quad::combine_bounds)
}
}
#[node_macro::node(category(""))]
async fn to_artboard<Data: Into<Table<GraphicElement>> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<GraphicElement>,
Context -> Table<VectorData>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> DAffine2,
)]
contents: impl Node<Context<'static>, Output = Data>,
label: String,
location: DVec2,
dimensions: DVec2,
background: Color,
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint {
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
Artboard {
graphic_group: graphic_group.into(),
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background,
clip,
}
}
#[node_macro::node(category(""))]
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: Table<Artboard>, artboard: Artboard, node_path: Vec<NodeId>) -> Table<Artboard> {
// Get the penultimate element of the node path, or None if the path is too short.
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.push(TableRow {
element: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}

View File

@@ -1,8 +1,8 @@
use crate::raster::Image;
use crate::raster_types::{CPU, RasterDataTable};
use crate::raster_types::{CPU, Raster};
use crate::registry::types::Percentage;
use crate::vector::VectorDataTable;
use crate::{BlendMode, Color, Ctx, GraphicElement, GraphicGroupTable};
use crate::table::Table;
use crate::vector::VectorData;
use crate::{BlendMode, Color, Ctx, GraphicElement};
pub(super) trait MultiplyAlpha {
fn multiply_alpha(&mut self, factor: f64);
@@ -13,27 +13,24 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyAlpha for VectorDataTable {
impl MultiplyAlpha for Table<VectorData> {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for GraphicGroupTable {
impl MultiplyAlpha for Table<GraphicElement> {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for RasterDataTable<CPU>
where
GraphicElement: From<Image<Color>>,
{
impl MultiplyAlpha for Table<Raster<CPU>> {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
}
}
}
@@ -46,24 +43,24 @@ impl MultiplyFill for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyFill for VectorDataTable {
impl MultiplyFill for Table<VectorData> {
fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.fill *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
}
}
}
impl MultiplyFill for GraphicGroupTable {
impl MultiplyFill for Table<GraphicElement> {
fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.fill *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
}
}
}
impl MultiplyFill for RasterDataTable<CPU> {
impl MultiplyFill for Table<Raster<CPU>> {
fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.fill *= factor as f32;
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
}
}
}
@@ -72,24 +69,24 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
impl SetBlendMode for VectorDataTable {
impl SetBlendMode for Table<VectorData> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for GraphicGroupTable {
impl SetBlendMode for Table<GraphicElement> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for RasterDataTable<CPU> {
impl SetBlendMode for Table<Raster<CPU>> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
}
}
}
@@ -98,24 +95,24 @@ trait SetClip {
fn set_clip(&mut self, clip: bool);
}
impl SetClip for VectorDataTable {
impl SetClip for Table<VectorData> {
fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.clip = clip;
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
}
}
}
impl SetClip for GraphicGroupTable {
impl SetClip for Table<GraphicElement> {
fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.clip = clip;
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
}
}
}
impl SetClip for RasterDataTable<CPU> {
impl SetClip for Table<Raster<CPU>> {
fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() {
instance.alpha_blending.clip = clip;
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
}
}
}
@@ -124,14 +121,14 @@ impl SetClip for RasterDataTable<CPU> {
fn blend_mode<T: SetBlendMode>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
)]
mut value: T,
blend_mode: BlendMode,
) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.set_blend_mode(blend_mode);
value
}
@@ -140,14 +137,14 @@ fn blend_mode<T: SetBlendMode>(
fn opacity<T: MultiplyAlpha>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
)]
mut value: T,
#[default(100.)] opacity: Percentage,
) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.multiply_alpha(opacity / 100.);
value
}
@@ -156,9 +153,9 @@ fn opacity<T: MultiplyAlpha>(
fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
)]
mut value: T,
blend_mode: BlendMode,
@@ -166,7 +163,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
#[default(100.)] fill: Percentage,
#[default(false)] clip: bool,
) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.set_blend_mode(blend_mode);
value.multiply_alpha(opacity / 100.);
value.multiply_fill(fill / 100.);

View File

@@ -1,10 +1,11 @@
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::raster_types::{CPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::{Color, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<VectorData>, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value
@@ -30,6 +31,6 @@ fn unwrap<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, O
/// Meant for debugging purposes, not general use. Clones the input value.
#[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&RasterDataTable<CPU>)] value: &'i T) -> T {
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&Table<Raster<CPU>>)] value: &'i T) -> T {
value.clone()
}

View File

@@ -1,171 +1,154 @@
use crate::blending::AlphaBlending;
use crate::bounds::BoundingBox;
use crate::instances::{Instance, Instances};
use crate::math::quad::Quad;
use crate::raster::image::Image;
use crate::raster_types::{CPU, GPU, Raster, RasterDataTable};
use crate::transform::TransformMut;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::uuid::NodeId;
use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use crate::vector::VectorData;
use crate::{Color, Ctx};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
use glam::{DAffine2, DVec2};
use std::hash::Hash;
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct OldGraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
}
pub type OldGraphicGroupTable = Instances<GraphicGroup>;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
OldGraphicGroup(OldGraphicGroup),
InstanceTable(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::default();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(Instance {
instance: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
EitherFormat::InstanceTable(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
let mut graphic_group_table = GraphicGroupTable::default();
for instance in old_table.instance_ref_iter() {
for (graphic_element, source_node_id) in &instance.instance.elements {
graphic_group_table.push(Instance {
instance: graphic_element.clone(),
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
} else if let Ok(new_table) = serde_json::from_value::<GraphicGroupTable>(value) {
new_table
} else {
return Err(serde::de::Error::custom("Failed to deserialize GraphicGroupTable"));
}
}
})
}
// TODO: Rename to GraphicElementTable
pub type GraphicGroupTable = Instances<GraphicElement>;
impl From<VectorData> for GraphicGroupTable {
fn from(vector_data: VectorData) -> Self {
Self::new(GraphicElement::VectorData(VectorDataTable::new(vector_data)))
}
}
impl From<VectorDataTable> for GraphicGroupTable {
fn from(vector_data: VectorDataTable) -> Self {
Self::new(GraphicElement::VectorData(vector_data))
}
}
impl From<Image<Color>> for GraphicGroupTable {
fn from(image: Image<Color>) -> Self {
Self::new(GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(image))))
}
}
impl From<RasterDataTable<CPU>> for GraphicGroupTable {
fn from(raster_data_table: RasterDataTable<CPU>) -> Self {
Self::new(GraphicElement::RasterDataCPU(raster_data_table))
}
}
impl From<RasterDataTable<GPU>> for GraphicGroupTable {
fn from(raster_data_table: RasterDataTable<GPU>) -> Self {
Self::new(GraphicElement::RasterDataGPU(raster_data_table))
}
}
impl From<DAffine2> for GraphicGroupTable {
fn from(_: DAffine2) -> Self {
GraphicGroupTable::default()
}
}
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable),
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable),
RasterDataCPU(RasterDataTable<CPU>),
RasterDataGPU(RasterDataTable<GPU>),
VectorData(Table<VectorData>),
RasterDataCPU(Table<Raster<CPU>>),
RasterDataGPU(Table<Raster<GPU>>),
}
impl Default for GraphicElement {
fn default() -> Self {
Self::GraphicGroup(GraphicGroupTable::default())
Self::GraphicGroup(Default::default())
}
}
// GraphicGroup
impl From<Table<GraphicElement>> for GraphicElement {
fn from(graphic_group: Table<GraphicElement>) -> Self {
GraphicElement::GraphicGroup(graphic_group)
}
}
// VectorData
impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorData(Table::new_from_element(vector_data))
}
}
impl From<Table<VectorData>> for GraphicElement {
fn from(vector_data: Table<VectorData>) -> Self {
GraphicElement::VectorData(vector_data)
}
}
impl From<VectorData> for Table<GraphicElement> {
fn from(vector_data: VectorData) -> Self {
Table::new_from_element(GraphicElement::VectorData(Table::new_from_element(vector_data)))
}
}
impl From<Table<VectorData>> for Table<GraphicElement> {
fn from(vector_data: Table<VectorData>) -> Self {
Table::new_from_element(GraphicElement::VectorData(vector_data))
}
}
// Raster<CPU>
impl From<Raster<CPU>> for GraphicElement {
fn from(raster_data: Raster<CPU>) -> Self {
GraphicElement::RasterDataCPU(Table::new_from_element(raster_data))
}
}
impl From<Table<Raster<CPU>>> for GraphicElement {
fn from(raster_data: Table<Raster<CPU>>) -> Self {
GraphicElement::RasterDataCPU(raster_data)
}
}
impl From<Raster<CPU>> for Table<GraphicElement> {
fn from(raster_data: Raster<CPU>) -> Self {
Table::new_from_element(GraphicElement::RasterDataCPU(Table::new_from_element(raster_data)))
}
}
impl From<Table<Raster<CPU>>> for Table<GraphicElement> {
fn from(raster_data_table: Table<Raster<CPU>>) -> Self {
Table::new_from_element(GraphicElement::RasterDataCPU(raster_data_table))
}
}
// Raster<GPU>
impl From<Raster<GPU>> for GraphicElement {
fn from(raster_data: Raster<GPU>) -> Self {
GraphicElement::RasterDataGPU(Table::new_from_element(raster_data))
}
}
impl From<Table<Raster<GPU>>> for GraphicElement {
fn from(raster_data: Table<Raster<GPU>>) -> Self {
GraphicElement::RasterDataGPU(raster_data)
}
}
impl From<Raster<GPU>> for Table<GraphicElement> {
fn from(raster_data: Raster<GPU>) -> Self {
Table::new_from_element(GraphicElement::RasterDataGPU(Table::new_from_element(raster_data)))
}
}
impl From<Table<Raster<GPU>>> for Table<GraphicElement> {
fn from(raster_data_table: Table<Raster<GPU>>) -> Self {
Table::new_from_element(GraphicElement::RasterDataGPU(raster_data_table))
}
}
// DAffine2
impl From<DAffine2> for GraphicElement {
fn from(_: DAffine2) -> Self {
GraphicElement::default()
}
}
impl From<DAffine2> for Table<GraphicElement> {
fn from(_: DAffine2) -> Self {
Table::new()
}
}
impl GraphicElement {
pub fn as_group(&self) -> Option<&GraphicGroupTable> {
pub fn as_group(&self) -> Option<&Table<GraphicElement>> {
match self {
GraphicElement::GraphicGroup(group) => Some(group),
_ => None,
}
}
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> {
pub fn as_group_mut(&mut self) -> Option<&mut Table<GraphicElement>> {
match self {
GraphicElement::GraphicGroup(group) => Some(group),
_ => None,
}
}
pub fn as_vector_data(&self) -> Option<&VectorDataTable> {
pub fn as_vector_data(&self) -> Option<&Table<VectorData>> {
match self {
GraphicElement::VectorData(data) => Some(data),
_ => None,
}
}
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> {
pub fn as_vector_data_mut(&mut self) -> Option<&mut Table<VectorData>> {
match self {
GraphicElement::VectorData(data) => Some(data),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&RasterDataTable<CPU>> {
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
match self {
GraphicElement::RasterDataCPU(raster) => Some(raster),
_ => None,
}
}
pub fn as_raster_mut(&mut self) -> Option<&mut RasterDataTable<CPU>> {
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
match self {
GraphicElement::RasterDataCPU(raster) => Some(raster),
_ => None,
@@ -174,18 +157,18 @@ impl GraphicElement {
pub fn had_clip_enabled(&self) -> bool {
match self {
GraphicElement::VectorData(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
GraphicElement::GraphicGroup(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
GraphicElement::RasterDataCPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
GraphicElement::RasterDataGPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
GraphicElement::VectorData(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::GraphicGroup(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::RasterDataCPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::RasterDataGPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
}
}
pub fn can_reduce_to_clip_path(&self) -> bool {
match self {
GraphicElement::VectorData(vector_data_table) => vector_data_table.instance_ref_iter().all(|instance_data| {
let style = &instance_data.instance.style;
let alpha_blending = &instance_data.alpha_blending;
GraphicElement::VectorData(vector_data_table) => vector_data_table.iter_ref().all(|row| {
let style = &row.element.style;
let alpha_blending = &row.alpha_blending;
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
}),
_ => false,
@@ -204,97 +187,10 @@ impl BoundingBox for GraphicElement {
}
}
impl BoundingBox for GraphicGroupTable {
impl BoundingBox for Table<GraphicElement> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter()
.filter_map(|element| element.instance.bounding_box(transform * *element.transform, include_stroke))
.reduce(Quad::combine_bounds)
}
}
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: GraphicGroupTable,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
pub background: Color,
pub clip: bool,
}
impl Default for Artboard {
fn default() -> Self {
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
}
}
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: GraphicGroupTable::default(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background: Color::WHITE,
clip: false,
}
}
}
impl BoundingBox for Artboard {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
}
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ArtboardGroupTable, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(ArtboardGroupTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = ArtboardGroupTable::default();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
})
}
pub type ArtboardGroupTable = Instances<Artboard>;
impl BoundingBox for ArtboardGroupTable {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter()
.filter_map(|instance| instance.instance.bounding_box(transform, include_stroke))
self.iter_ref()
.filter_map(|element| element.element.bounding_box(transform * *element.transform, include_stroke))
.reduce(Quad::combine_bounds)
}
}
@@ -302,15 +198,15 @@ impl BoundingBox for ArtboardGroupTable {
#[node_macro::node(category(""))]
async fn layer<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>, RasterDataTable<GPU>)] mut stack: Instances<I>,
#[implementations(Table<GraphicElement>, Table<VectorData>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(GraphicElement, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
node_path: Vec<NodeId>,
) -> Instances<I> {
) -> Table<I> {
// Get the penultimate element of the node path, or None if the path is too short
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push(Instance {
instance: element,
stack.push(TableRow {
element,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
@@ -323,10 +219,10 @@ async fn layer<I: 'n + Send + Clone>(
async fn to_element<Data: Into<GraphicElement> + 'n>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
RasterDataTable<GPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
DAffine2,
)]
data: Data,
@@ -335,26 +231,26 @@ async fn to_element<Data: Into<GraphicElement> + 'n>(
}
#[node_macro::node(category("General"))]
async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
async fn to_group<Data: Into<Table<GraphicElement>> + 'n>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
RasterDataTable<GPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
element: Data,
) -> GraphicGroupTable {
) -> Table<GraphicElement> {
element.into()
}
#[node_macro::node(category("General"))]
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
for current_instance in current_group_table.instance_ref_iter() {
let current_element = current_instance.instance.clone();
let reference = *current_instance.source_node_id;
async fn flatten_group(_: impl Ctx, group: Table<GraphicElement>, fully_flatten: bool) -> Table<GraphicElement> {
// TODO: Avoid mutable reference, instead return a new Table<GraphicElement>?
fn flatten_group(output_group_table: &mut Table<GraphicElement>, current_group_table: Table<GraphicElement>, fully_flatten: bool, recursion_depth: usize) {
for current_row in current_group_table.iter_ref() {
let current_element = current_row.element.clone();
let reference = *current_row.source_node_id;
let recurse = fully_flatten || recursion_depth == 0;
@@ -362,18 +258,18 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
// If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.instance_mut_iter() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_row.transform * *graphic_element.transform;
}
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
_ => {
output_group_table.push(Instance {
instance: current_element,
transform: *current_instance.transform,
alpha_blending: *current_instance.alpha_blending,
output_group_table.push(TableRow {
element: current_element,
transform: *current_row.transform,
alpha_blending: *current_row.alpha_blending,
source_node_id: reference,
});
}
@@ -381,41 +277,41 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
}
}
let mut output = GraphicGroupTable::default();
let mut output = Table::new();
flatten_group(&mut output, group, fully_flatten, 0);
output
}
#[node_macro::node(category("Vector"))]
async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTable {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
fn flatten_group(output_group_table: &mut VectorDataTable, current_group_table: GraphicGroupTable) {
for current_instance in current_group_table.instance_ref_iter() {
let current_element = current_instance.instance.clone();
let reference = *current_instance.source_node_id;
async fn flatten_vector(_: impl Ctx, group: Table<GraphicElement>) -> Table<VectorData> {
// TODO: Avoid mutable reference, instead return a new Table<GraphicElement>?
fn flatten_group(output_group_table: &mut Table<VectorData>, current_group_table: Table<GraphicElement>) {
for current_graphic_element_row in current_group_table.iter_ref() {
let current_element = current_graphic_element_row.element.clone();
let reference = *current_graphic_element_row.source_node_id;
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.instance_mut_iter() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_graphic_element_row.transform * *graphic_element.transform;
}
flatten_group(output_group_table, current_element);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
GraphicElement::VectorData(vector_instance) => {
for current_element in vector_instance.instance_ref_iter() {
output_group_table.push(Instance {
instance: current_element.instance.clone(),
transform: *current_instance.transform * *current_element.transform,
GraphicElement::VectorData(vector_table) => {
for current_vector_row in vector_table.iter_ref() {
output_group_table.push(TableRow {
element: current_vector_row.element.clone(),
transform: *current_graphic_element_row.transform * *current_vector_row.transform,
alpha_blending: AlphaBlending {
blend_mode: current_element.alpha_blending.blend_mode,
opacity: current_instance.alpha_blending.opacity * current_element.alpha_blending.opacity,
fill: current_element.alpha_blending.fill,
clip: current_element.alpha_blending.clip,
blend_mode: current_vector_row.alpha_blending.blend_mode,
opacity: current_graphic_element_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
fill: current_vector_row.alpha_blending.fill,
clip: current_vector_row.alpha_blending.clip,
},
source_node_id: reference,
});
@@ -426,113 +322,12 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl
}
}
let mut output = VectorDataTable::default();
let mut output = Table::new();
flatten_group(&mut output, group);
output
}
#[node_macro::node(category(""))]
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> GraphicGroupTable,
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> DAffine2,
)]
contents: impl Node<Context<'static>, Output = Data>,
label: String,
location: DVec2,
dimensions: DVec2,
background: Color,
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint {
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
Artboard {
graphic_group: graphic_group.into(),
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background,
clip,
}
}
#[node_macro::node(category(""))]
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroupTable {
// Get the penultimate element of the node path, or None if the path is too short.
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}
// TODO: Remove this one
impl From<Image<Color>> for GraphicElement {
fn from(raster_data: Image<Color>) -> Self {
GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(raster_data)))
}
}
impl From<RasterDataTable<CPU>> for GraphicElement {
fn from(raster_data: RasterDataTable<CPU>) -> Self {
GraphicElement::RasterDataCPU(raster_data)
}
}
impl From<RasterDataTable<GPU>> for GraphicElement {
fn from(raster_data: RasterDataTable<GPU>) -> Self {
GraphicElement::RasterDataGPU(raster_data)
}
}
impl From<Raster<CPU>> for GraphicElement {
fn from(raster_data: Raster<CPU>) -> Self {
GraphicElement::RasterDataCPU(RasterDataTable::new(raster_data))
}
}
impl From<Raster<GPU>> for GraphicElement {
fn from(raster_data: Raster<GPU>) -> Self {
GraphicElement::RasterDataGPU(RasterDataTable::new(raster_data))
}
}
// TODO: Remove this one
impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorData(VectorDataTable::new(vector_data))
}
}
impl From<VectorDataTable> for GraphicElement {
fn from(vector_data: VectorDataTable) -> Self {
GraphicElement::VectorData(vector_data)
}
}
impl From<GraphicGroupTable> for GraphicElement {
fn from(graphic_group: GraphicGroupTable) -> Self {
GraphicElement::GraphicGroup(graphic_group)
}
}
pub trait ToGraphicElement {
fn to_graphic_element(&self) -> GraphicElement;
}
/// Returns the value at the specified index in the collection.
/// If that index has no value, the type's default value is returned.
#[node_macro::node(category("General"))]
@@ -544,9 +339,9 @@ fn index<T: AtIndex + Clone + Default>(
Vec<Option<Color>>,
Vec<f64>, Vec<u64>,
Vec<DVec2>,
VectorDataTable,
RasterDataTable<CPU>,
GraphicGroupTable,
Table<VectorData>,
Table<Raster<CPU>>,
Table<GraphicElement>,
)]
collection: T,
/// The index of the item to retrieve, starting from 0 for the first item.
@@ -569,16 +364,75 @@ impl<T: Clone> AtIndex for Vec<T> {
self.get(index).cloned()
}
}
impl<T: Clone> AtIndex for Instances<T> {
type Output = Instances<T>;
impl<T: Clone> AtIndex for Table<T> {
type Output = Table<T>;
fn at_index(&self, index: usize) -> Option<Self::Output> {
let mut result_table = Self::default();
if let Some(row) = self.instance_ref_iter().nth(index) {
result_table.push(row.to_instance_cloned());
if let Some(row) = self.iter_ref().nth(index) {
result_table.push(row.into_cloned());
Some(result_table)
} else {
None
}
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<GraphicElement>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct OldGraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
OldGraphicGroup(OldGraphicGroup),
Table(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = Table::new();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(TableRow {
element: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
EitherFormat::Table(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
let mut graphic_group_table = Table::new();
for row in old_table.iter_ref() {
for (graphic_element, source_node_id) in &row.element.elements {
graphic_group_table.push(TableRow {
element: graphic_element.clone(),
transform: *row.transform,
alpha_blending: *row.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
} else if let Ok(new_table) = serde_json::from_value::<Table<GraphicElement>>(value) {
new_table
} else {
return Err(serde::de::Error::custom("Failed to deserialize Table<GraphicElement>"));
}
}
})
}

View File

@@ -1,281 +0,0 @@
use crate::AlphaBlending;
use crate::transform::ApplyTransform;
use crate::uuid::NodeId;
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Instances<T> {
#[serde(alias = "instances")]
instance: Vec<T>,
#[serde(default = "one_daffine2_default")]
transform: Vec<DAffine2>,
#[serde(default = "one_alpha_blending_default")]
alpha_blending: Vec<AlphaBlending>,
#[serde(default = "one_source_node_id_default")]
source_node_id: Vec<Option<NodeId>>,
}
impl<T> Instances<T> {
pub fn new(instance: T) -> Self {
Self {
instance: vec![instance],
transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()],
source_node_id: vec![None],
}
}
pub fn new_instance(instance: Instance<T>) -> Self {
Self {
instance: vec![instance.instance],
transform: vec![instance.transform],
alpha_blending: vec![instance.alpha_blending],
source_node_id: vec![instance.source_node_id],
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
instance: Vec::with_capacity(capacity),
transform: Vec::with_capacity(capacity),
alpha_blending: Vec::with_capacity(capacity),
source_node_id: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, instance: Instance<T>) {
self.instance.push(instance.instance);
self.transform.push(instance.transform);
self.alpha_blending.push(instance.alpha_blending);
self.source_node_id.push(instance.source_node_id);
}
pub fn extend(&mut self, instances: Instances<T>) {
self.instance.extend(instances.instance);
self.transform.extend(instances.transform);
self.alpha_blending.extend(instances.alpha_blending);
self.source_node_id.extend(instances.source_node_id);
}
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<'_, T>> + Clone {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<'_, T>> {
self.instance
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceMut {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn get(&self, index: usize) -> Option<InstanceRef<'_, T>> {
if index >= self.instance.len() {
return None;
}
Some(InstanceRef {
instance: &self.instance[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
source_node_id: &self.source_node_id[index],
})
}
pub fn get_mut(&mut self, index: usize) -> Option<InstanceMut<'_, T>> {
if index >= self.instance.len() {
return None;
}
Some(InstanceMut {
instance: &mut self.instance[index],
transform: &mut self.transform[index],
alpha_blending: &mut self.alpha_blending[index],
source_node_id: &mut self.source_node_id[index],
})
}
pub fn len(&self) -> usize {
self.instance.len()
}
pub fn is_empty(&self) -> bool {
self.instance.is_empty()
}
}
impl<T> Default for Instances<T> {
fn default() -> Self {
Self {
instance: Vec::new(),
transform: Vec::new(),
alpha_blending: Vec::new(),
source_node_id: Vec::new(),
}
}
}
impl<T: Hash> Hash for Instances<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for instance in &self.instance {
instance.hash(state);
}
}
}
impl<T> ApplyTransform for Instances<T> {
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform *= *modification;
}
}
fn left_apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform = *modification * *transform;
}
}
}
impl<T: PartialEq> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool {
self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: StaticType + 'static> StaticType for Instances<T> {
type Static = Instances<T>;
}
impl<T> FromIterator<Instance<T>> for Instances<T> {
fn from_iter<I: IntoIterator<Item = Instance<T>>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut instances = Self::with_capacity(lower);
for instance in iter {
instances.push(instance);
}
instances
}
}
fn one_daffine2_default() -> Vec<DAffine2> {
vec![DAffine2::IDENTITY]
}
fn one_alpha_blending_default() -> Vec<AlphaBlending> {
vec![AlphaBlending::default()]
}
fn one_source_node_id_default() -> Vec<Option<NodeId>> {
vec![None]
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
impl<T> InstanceRef<'_, T> {
pub fn to_instance_cloned(self) -> Instance<T>
where
T: Clone,
{
Instance {
instance: self.instance.clone(),
transform: *self.transform,
alpha_blending: *self.alpha_blending,
source_node_id: *self.source_node_id,
}
}
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub instance: &'a mut T,
pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending,
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> Instance<T> {
pub fn to_graphic_element<U>(self) -> Instance<U>
where
T: Into<U>,
{
Instance {
instance: self.instance.into(),
transform: self.transform,
alpha_blending: self.alpha_blending,
source_node_id: self.source_node_id,
}
}
pub fn to_instance_ref(&self) -> InstanceRef<'_, T> {
InstanceRef {
instance: &self.instance,
transform: &self.transform,
alpha_blending: &self.alpha_blending,
source_node_id: &self.source_node_id,
}
}
pub fn to_instance_mut(&mut self) -> InstanceMut<'_, T> {
InstanceMut {
instance: &mut self.instance,
transform: &mut self.transform,
alpha_blending: &mut self.alpha_blending,
source_node_id: &mut self.source_node_id,
}
}
pub fn to_table(self) -> Instances<T> {
Instances {
instance: vec![self.instance],
transform: vec![self.transform],
alpha_blending: vec![self.alpha_blending],
source_node_id: vec![self.source_node_id],
}
}
}

View File

@@ -2,6 +2,7 @@
extern crate log;
pub mod animation;
pub mod artboard;
pub mod blending_nodes;
pub mod bounds;
pub mod consts;
@@ -11,7 +12,6 @@ pub mod extract_xy;
pub mod generic;
pub mod gradient;
pub mod graphic_element;
pub mod instances;
pub mod logic;
pub mod math;
pub mod memo;
@@ -22,6 +22,7 @@ pub mod raster_types;
pub mod registry;
pub mod render_complexity;
pub mod structural;
pub mod table;
pub mod text;
pub mod transform;
pub mod transform_nodes;
@@ -30,6 +31,7 @@ pub mod value;
pub mod vector;
pub use crate as graphene_core;
pub use artboard::Artboard;
pub use blending::*;
pub use color::Color;
pub use context::*;
@@ -39,7 +41,7 @@ pub use graphene_core_shaders::AsU32;
pub use graphene_core_shaders::blending;
pub use graphene_core_shaders::choice_type;
pub use graphene_core_shaders::color;
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use graphic_element::GraphicElement;
pub use memo::MemoHash;
pub use num_traits;
use std::any::TypeId;

View File

@@ -1,23 +1,23 @@
use crate::ArtboardGroupTable;
use crate::Artboard;
use crate::Color;
use crate::GraphicElement;
use crate::GraphicGroupTable;
use crate::gradient::GradientStops;
use crate::graphene_core::registry::types::TextArea;
use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::{Context, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, VectorDataTable)] value: T) -> String {
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<VectorData>)] value: T) -> String {
format!("{:?}", value)
}
#[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>(
_: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] value: T,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<GraphicElement>, Table<VectorData>, Table<Raster<CPU>>)] value: T,
) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
}
@@ -59,11 +59,11 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64,
Context -> DVec2,
Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<GraphicElement>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,
@@ -80,11 +80,11 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64,
Context -> DVec2,
Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<GraphicElement>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,

View File

@@ -6,10 +6,8 @@ pub mod color {
pub mod image;
pub use self::image::Image;
use crate::GraphicGroupTable;
pub use crate::color::*;
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::raster_types::CPU;
use std::fmt::Debug;
pub trait Bitmap {

View File

@@ -1,8 +1,9 @@
use super::Color;
use crate::AlphaBlending;
use crate::color::float_to_srgb_u8;
use crate::instances::{Instance, Instances};
use crate::raster_types::Raster;
use crate::table::{Table, TableRow};
use crate::vector::VectorData;
use core::hash::{Hash, Hasher};
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
@@ -212,25 +213,23 @@ impl<P: Pixel> IntoIterator for Image<P> {
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<RasterDataTable<CPU>, D::Error> {
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
use serde::Deserialize;
type ImageFrameTable<P> = Instances<Image<P>>;
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrameTable<Color>),
ImageFrame(Table<Image<Color>>),
}
impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?)))
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
}
}
impl serde::Serialize for RasterFrame {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer),
RasterFrame::ImageFrame(table) => table.serialize(serializer),
}
}
}
@@ -238,9 +237,9 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable),
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable),
VectorData(Table<VectorData>),
RasterFrame(RasterFrame),
}
@@ -250,14 +249,14 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
}
impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
}
}
impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(),
image: image.iter_ref().next().unwrap().element.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@@ -284,54 +283,52 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>),
ImageFrameTable(ImageFrameTable<Color>),
RasterDataTable(RasterDataTable<CPU>),
ImageFrame(Table<ImageFrame<Color>>),
ImageFrameTable(Table<Image<Color>>),
RasterDataTable(Table<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => RasterDataTable::new(Raster::new_cpu(image)),
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
let mut image_frame_table = RasterDataTable::new(Raster::new_cpu(image));
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform;
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending;
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
*image_frame_table.iter_mut().next().unwrap().transform = transform;
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => RasterDataTable::new(Raster::new_cpu(
FormatVersions::ImageFrame(image_frame) => Table::new_from_element(Raster::new_cpu(
image_frame
.instance_ref_iter()
.iter_ref()
.next()
.unwrap_or(Instances::new(ImageFrame::default()).instance_ref_iter().next().unwrap())
.instance
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter_ref().next().unwrap())
.element
.image
.clone(),
)),
FormatVersions::ImageFrameTable(image_frame_table) => RasterDataTable::new(Raster::new_cpu(image_frame_table.instance_ref_iter().next().unwrap().instance.clone())),
FormatVersions::ImageFrameTable(image_frame_table) => Table::new_from_element(Raster::new_cpu(image_frame_table.iter_ref().next().unwrap().element.clone())),
FormatVersions::RasterDataTable(raster_data_table) => raster_data_table,
})
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Raster<CPU>>, D::Error> {
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
use serde::Deserialize;
type ImageFrameTable<P> = Instances<Image<P>>;
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrameTable<Color>),
ImageFrame(Table<Image<Color>>),
}
impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?)))
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
}
}
impl serde::Serialize for RasterFrame {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer),
RasterFrame::ImageFrame(table) => table.serialize(serializer),
}
}
}
@@ -339,9 +336,9 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable),
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable),
VectorData(Table<VectorData>),
RasterFrame(RasterFrame),
}
@@ -351,14 +348,14 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
}
impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
}
}
impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(),
image: image.iter_ref().next().unwrap().element.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@@ -385,33 +382,31 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>),
RasterDataTable(RasterDataTable<CPU>),
ImageInstance(Instance<Raster<CPU>>),
ImageFrame(Table<ImageFrame<Color>>),
RasterDataTable(Table<Raster<CPU>>),
ImageTableRow(TableRow<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Instance {
instance: Raster::new_cpu(image),
FormatVersions::Image(image) => TableRow {
element: Raster::new_cpu(image),
..Default::default()
},
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance {
instance: Raster::new_cpu(image_frame_with_transform_and_blending.image),
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow {
element: Raster::new_cpu(image_frame_with_transform_and_blending.image),
transform: image_frame_with_transform_and_blending.transform,
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
source_node_id: None,
},
FormatVersions::ImageFrame(image_frame) => Instance {
instance: Raster::new_cpu(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()),
FormatVersions::ImageFrame(image_frame) => TableRow {
element: Raster::new_cpu(image_frame.iter_ref().next().unwrap().element.image.clone()),
..Default::default()
},
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(),
FormatVersions::ImageInstance(image_instance) => image_instance,
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
FormatVersions::ImageTableRow(image_table_row) => image_table_row,
})
}
// pub type RasterDataTable<P> = Instances<Image<P>>;
impl<P: Debug + Copy + Pixel> Sample for Image<P> {
type Pixel = P;
@@ -458,23 +453,6 @@ impl From<Image<Color>> for Image<SRGBA8> {
}
}
// impl From<RasterDataTable<CPU>> for RasterDataTable<SRGBA8> {
// fn from(image_frame_table: RasterDataTable<CPU>) -> Self {
// let mut result_table = RasterDataTable::<SRGBA8>::default();
// for image_frame_instance in image_frame_table.instance_iter() {
// result_table.push(Instance {
// instance: image_frame_instance.instance,
// transform: image_frame_instance.transform,
// alpha_blending: image_frame_instance.alpha_blending,
// source_node_id: image_frame_instance.source_node_id,
// });
// }
// result_table
// }
// }
impl From<Image<SRGBA8>> for Image<Color> {
fn from(image: Image<SRGBA8>) -> Self {
let data = image.data.into_iter().map(|x| x.into()).collect();

View File

@@ -1,8 +1,8 @@
use crate::Color;
use crate::bounds::BoundingBox;
use crate::instances::Instances;
use crate::math::quad::Quad;
use crate::raster::Image;
use crate::table::Table;
use core::ops::Deref;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -61,8 +61,6 @@ where
}
}
pub type RasterDataTable<Storage> = Instances<Raster<Storage>>;
pub use cpu::CPU;
mod cpu {
@@ -165,10 +163,13 @@ mod gpu {
#[cfg(not(feature = "wgpu"))]
mod gpu {
use super::*;
use crate::raster_types::__private::Sealed;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct GPU;
impl Sealed for Raster<GPU> {}
impl Storage for Raster<GPU> {
fn is_empty(&self) -> bool {
true
@@ -198,15 +199,15 @@ mod gpu_common {
}
}
impl<T> BoundingBox for RasterDataTable<T>
impl<T> BoundingBox for Table<Raster<T>>
where
Raster<T>: Storage,
{
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter()
.filter(|instance| !instance.instance.is_empty()) // Eliminate empty images
.flat_map(|instance| {
let transform = transform * *instance.transform;
self.iter_ref()
.filter(|row| !row.element.is_empty()) // Eliminate empty images
.flat_map(|row| {
let transform = transform * *row.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
})
.reduce(Quad::combine_bounds)

View File

@@ -1,5 +1,5 @@
use crate::instances::Instances;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::{Artboard, Color, GraphicElement};
use glam::DVec2;
@@ -10,9 +10,9 @@ pub trait RenderComplexity {
}
}
impl<T: RenderComplexity> RenderComplexity for Instances<T> {
impl<T: RenderComplexity> RenderComplexity for Table<T> {
fn render_complexity(&self) -> usize {
self.instance_ref_iter().map(|instance| instance.instance.render_complexity()).fold(0, usize::saturating_add)
self.iter_ref().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
}
}
@@ -25,10 +25,10 @@ impl RenderComplexity for Artboard {
impl RenderComplexity for GraphicElement {
fn render_complexity(&self) -> usize {
match self {
Self::GraphicGroup(instances) => instances.render_complexity(),
Self::VectorData(instances) => instances.render_complexity(),
Self::RasterDataCPU(instances) => instances.render_complexity(),
Self::RasterDataGPU(instances) => instances.render_complexity(),
Self::GraphicGroup(table) => table.render_complexity(),
Self::VectorData(table) => table.render_complexity(),
Self::RasterDataCPU(table) => table.render_complexity(),
Self::RasterDataGPU(table) => table.render_complexity(),
}
}
}

View File

@@ -0,0 +1,252 @@
use crate::AlphaBlending;
use crate::transform::ApplyTransform;
use crate::uuid::NodeId;
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Table<T> {
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
source_node_id: Vec<Option<NodeId>>,
}
impl<T> Table<T> {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
element: Vec::with_capacity(capacity),
transform: Vec::with_capacity(capacity),
alpha_blending: Vec::with_capacity(capacity),
source_node_id: Vec::with_capacity(capacity),
}
}
pub fn new_from_element(element: T) -> Self {
Self {
element: vec![element],
transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()],
source_node_id: vec![None],
}
}
pub fn new_from_row(row: TableRow<T>) -> Self {
Self {
element: vec![row.element],
transform: vec![row.transform],
alpha_blending: vec![row.alpha_blending],
source_node_id: vec![row.source_node_id],
}
}
pub fn push(&mut self, row: TableRow<T>) {
self.element.push(row.element);
self.transform.push(row.transform);
self.alpha_blending.push(row.alpha_blending);
self.source_node_id.push(row.source_node_id);
}
pub fn extend(&mut self, table: Table<T>) {
self.element.extend(table.element);
self.transform.extend(table.transform);
self.alpha_blending.extend(table.alpha_blending);
self.source_node_id.extend(table.source_node_id);
}
pub fn iter(self) -> impl DoubleEndedIterator<Item = TableRow<T>> {
self.element
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((element, transform), alpha_blending), source_node_id)| TableRow {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_ref(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {
self.element
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {
self.element
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn get(&self, index: usize) -> Option<TableRowRef<'_, T>> {
if index >= self.element.len() {
return None;
}
Some(TableRowRef {
element: &self.element[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
source_node_id: &self.source_node_id[index],
})
}
pub fn get_mut(&mut self, index: usize) -> Option<TableRowMut<'_, T>> {
if index >= self.element.len() {
return None;
}
Some(TableRowMut {
element: &mut self.element[index],
transform: &mut self.transform[index],
alpha_blending: &mut self.alpha_blending[index],
source_node_id: &mut self.source_node_id[index],
})
}
pub fn len(&self) -> usize {
self.element.len()
}
pub fn is_empty(&self) -> bool {
self.element.is_empty()
}
}
impl<T> Default for Table<T> {
fn default() -> Self {
Self {
element: Vec::new(),
transform: Vec::new(),
alpha_blending: Vec::new(),
source_node_id: Vec::new(),
}
}
}
impl<T: Hash> Hash for Table<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for element in &self.element {
element.hash(state);
}
}
}
impl<T> ApplyTransform for Table<T> {
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform *= *modification;
}
}
fn left_apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform = *modification * *transform;
}
}
}
impl<T: PartialEq> PartialEq for Table<T> {
fn eq(&self, other: &Self) -> bool {
self.element.len() == other.element.len() && { self.element.iter().zip(other.element.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: StaticType + 'static> StaticType for Table<T> {
type Static = Table<T>;
}
impl<T> FromIterator<TableRow<T>> for Table<T> {
fn from_iter<I: IntoIterator<Item = TableRow<T>>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut table = Self::with_capacity(lower);
for row in iter {
table.push(row);
}
table
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TableRow<T> {
#[serde(alias = "instance")]
pub element: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> TableRow<T> {
pub fn as_ref(&self) -> TableRowRef<'_, T> {
TableRowRef {
element: &self.element,
transform: &self.transform,
alpha_blending: &self.alpha_blending,
source_node_id: &self.source_node_id,
}
}
pub fn as_mut(&mut self) -> TableRowMut<'_, T> {
TableRowMut {
element: &mut self.element,
transform: &mut self.transform,
alpha_blending: &mut self.alpha_blending,
source_node_id: &mut self.source_node_id,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TableRowRef<'a, T> {
pub element: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
impl<T> TableRowRef<'_, T> {
pub fn into_cloned(self) -> TableRow<T>
where
T: Clone,
{
TableRow {
element: self.element.clone(),
transform: *self.transform,
alpha_blending: *self.alpha_blending,
source_node_id: *self.source_node_id,
}
}
}
#[derive(Debug)]
pub struct TableRowMut<'a, T> {
pub element: &'a mut T,
pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending,
pub source_node_id: &'a mut Option<NodeId>,
}

View File

@@ -1,6 +1,6 @@
use super::TextAlign;
use crate::instances::Instance;
use crate::vector::{PointId, VectorData, VectorDataTable};
use crate::table::{Table, TableRow};
use crate::vector::{PointId, VectorData};
use bezier_rs::{ManipulatorGroup, Subpath};
use core::cell::RefCell;
use glam::{DAffine2, DVec2};
@@ -24,7 +24,7 @@ struct PathBuilder {
current_subpath: Subpath<PointId>,
origin: DVec2,
glyph_subpaths: Vec<Subpath<PointId>>,
vector_table: VectorDataTable,
vector_table: Table<VectorData>,
scale: f64,
id: PointId,
}
@@ -51,15 +51,15 @@ impl PathBuilder {
}
if per_glyph_instances {
self.vector_table.push(Instance {
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
self.vector_table.push(TableRow {
element: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
transform: DAffine2::from_translation(glyph_offset),
..Default::default()
});
} else {
for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false);
self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
}
}
}
@@ -205,19 +205,15 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
Some(layout)
}
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> VectorDataTable {
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<VectorData> {
let Some(layout) = layout_text(str, font_data, typesetting) else {
return VectorDataTable::new(VectorData::default());
return Table::new_from_element(VectorData::default());
};
let mut path_builder = PathBuilder {
current_subpath: Subpath::new(Vec::new(), false),
glyph_subpaths: Vec::new(),
vector_table: if per_glyph_instances {
VectorDataTable::default()
} else {
VectorDataTable::new(VectorData::default())
},
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(VectorData::default()) },
scale: layout.scale() as f64,
id: PointId::ZERO,
origin: DVec2::default(),
@@ -232,7 +228,7 @@ pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
}
if path_builder.vector_table.is_empty() {
path_builder.vector_table = VectorDataTable::new(VectorData::default());
path_builder.vector_table = Table::new_from_element(VectorData::default());
}
path_builder.vector_table

View File

@@ -1,8 +1,8 @@
use crate::instances::Instances;
use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl};
use crate::vector::VectorData;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicElement, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
@@ -12,10 +12,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations(
Context -> DAffine2,
Context -> DVec2,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> Table<VectorData>,
Context -> Table<GraphicElement>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
)]
value: impl Node<Context<'static>, Output = T>,
translate: DVec2,
@@ -43,10 +43,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx,
#[implementations(VectorDataTable, RasterDataTable<CPU>, GraphicGroupTable)] mut data: Instances<Data>,
#[implementations(Table<VectorData>, Table<Raster<CPU>>, Table<GraphicElement>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Instances<Data> {
for data_transform in data.instance_mut_iter() {
) -> Table<Data> {
for data_transform in data.iter_mut() {
*data_transform.transform = transform.transform();
}
data
@@ -56,14 +56,14 @@ fn replace_transform<Data, TransformInput: Transform>(
async fn extract_transform<T>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
RasterDataTable<CPU>,
RasterDataTable<GPU>,
Table<GraphicElement>,
Table<VectorData>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
vector_data: Instances<T>,
vector_data: Table<T>,
) -> DAffine2 {
vector_data.instance_ref_iter().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
vector_data.iter_ref().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
}
#[node_macro::node(category("Math: Transform"))]
@@ -90,10 +90,10 @@ fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> Table<VectorData>,
Context -> Table<GraphicElement>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> String,
Context -> f64,
)]
@@ -108,10 +108,10 @@ async fn boundless_footprint<T: 'n + 'static>(
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> Table<VectorData>,
Context -> Table<GraphicElement>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> String,
Context -> f64,
)]

View File

@@ -167,14 +167,17 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
let name = match name.as_str() {
"f32" => "f64".to_string(),
"graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::instances::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>"
| "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>" => {
"graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>"
| "graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>" => {
"graphene_core::table::Table<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
}
"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>" => "graphene_core::table::Table<graphene_core::artboard::Artboard>".to_string(),
_ => name,
};

View File

@@ -1,33 +1,33 @@
use crate::instances::{InstanceRef, Instances};
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use crate::raster_types::{CPU, Raster};
use crate::table::{Table, TableRowRef};
use crate::vector::VectorData;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: VectorDataTable,
points: Table<VectorData>,
#[implementations(
Context -> GraphicGroupTable,
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>
Context -> Table<GraphicElement>,
Context -> Table<VectorData>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
reverse: bool,
) -> Instances<T> {
let mut result_table = Instances::<T>::default();
) -> Table<T> {
let mut result_table = Table::new();
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
for TableRowRef { element: points, transform, .. } in points.iter_ref() {
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut instanced in generated_instance.instance_iter() {
instanced.transform.translation = transformed_point;
result_table.push(instanced);
for mut generated_row in generated_instance.iter() {
generated_row.transform.translation = transformed_point;
result_table.push(generated_row);
}
};
@@ -50,17 +50,17 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> GraphicGroupTable,
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>
Context -> Table<GraphicElement>,
Context -> Table<VectorData>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
#[default(1)] count: u64,
reverse: bool,
) -> Instances<T> {
) -> Table<T> {
let count = count.max(1) as usize;
let mut result_table = Instances::<T>::default();
let mut result_table = Table::new();
for index in 0..count {
let index = if reverse { count - index - 1 } else { index };
@@ -68,8 +68,8 @@ async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'sta
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() {
result_table.push(instanced);
for generated_row in generated_instance.iter() {
result_table.push(generated_row);
}
}
@@ -128,11 +128,11 @@ mod test {
);
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let repeated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(repeated.len(), positions.len());
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
let points = Table::new_from_element(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let generated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(generated.len(), positions.len());
for (position, generated_row) in positions.into_iter().zip(generated.iter_ref()) {
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}

View File

@@ -2,21 +2,22 @@ use super::misc::{ArcType, AsU64, GridType};
use super::{PointId, SegmentId, StrokeId};
use crate::Ctx;
use crate::registry::types::{Angle, PixelSize};
use crate::vector::{HandleId, VectorData, VectorDataTable};
use crate::table::Table;
use crate::vector::{HandleId, VectorData};
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData>;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@@ -32,7 +33,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
@@ -43,9 +44,9 @@ fn circle(
#[unit(" px")]
#[default(50.)]
radius: f64,
) -> VectorDataTable {
) -> Table<VectorData> {
let radius = radius.abs();
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
Table::new_from_element(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -60,8 +61,8 @@ fn arc(
#[range((0., 360.))]
sweep_angle: Angle,
arc_type: ArcType,
) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_arc(
) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_arc(
radius,
start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU,
@@ -83,7 +84,7 @@ fn ellipse(
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> VectorDataTable {
) -> Table<VectorData> {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
@@ -97,7 +98,7 @@ fn ellipse(
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
VectorDataTable::new(ellipse)
Table::new_from_element(ellipse)
}
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
@@ -113,7 +114,7 @@ fn rectangle<T: CornerRadius>(
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
) -> VectorDataTable {
) -> Table<VectorData> {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@@ -128,10 +129,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")]
#[default(50)]
radius: f64,
) -> VectorDataTable {
) -> Table<VectorData> {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
Table::new_from_element(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -148,17 +149,17 @@ fn star<T: AsU64>(
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> VectorDataTable {
) -> Table<VectorData> {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.;
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
Table::new_from_element(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_line(start, end)))
}
trait GridSpacing {
@@ -188,7 +189,7 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> VectorDataTable {
) -> Table<VectorData> {
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into();
@@ -270,7 +271,7 @@ fn grid<T: GridSpacing>(
}
}
VectorDataTable::new(vector_data)
Table::new_from_element(vector_data)
}
#[cfg(test)]
@@ -284,9 +285,9 @@ mod tests {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@@ -299,9 +300,9 @@ mod tests {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;

View File

@@ -5,11 +5,11 @@ mod modification;
use super::misc::{dvec2_to_point, point_to_dvec2};
use super::style::{PathStyle, Stroke};
use crate::bounds::BoundingBox;
use crate::instances::Instances;
use crate::math::quad::Quad;
use crate::table::Table;
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::{AlphaBlending, Color, GraphicGroupTable};
use crate::{AlphaBlending, Color, GraphicElement};
pub use attributes::*;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use core::borrow::Borrow;
@@ -22,7 +22,7 @@ pub use modification::*;
use std::collections::HashMap;
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<VectorData>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
@@ -41,7 +41,7 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<GraphicGroupTable>,
pub upstream_graphic_group: Option<Table<GraphicElement>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -50,13 +50,13 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum EitherFormat {
VectorData(VectorData),
OldVectorData(OldVectorData),
VectorDataTable(VectorDataTable),
VectorDataTable(Table<VectorData>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
EitherFormat::VectorData(vector_data) => Table::new_from_element(vector_data),
EitherFormat::OldVectorData(old) => {
let mut vector_data_table = VectorDataTable::new(VectorData {
let mut vector_data_table = Table::new_from_element(VectorData {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
@@ -64,16 +64,14 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group,
});
*vector_data_table.instance_mut_iter().next().unwrap().transform = old.transform;
*vector_data_table.instance_mut_iter().next().unwrap().alpha_blending = old.alpha_blending;
*vector_data_table.iter_mut().next().unwrap().transform = old.transform;
*vector_data_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_data_table
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
pub type VectorDataTable = Instances<VectorData>;
/// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
///
@@ -91,7 +89,7 @@ pub struct VectorData {
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<GraphicGroupTable>,
pub upstream_graphic_group: Option<Table<GraphicElement>>,
}
impl Default for VectorData {
@@ -495,24 +493,24 @@ impl VectorData {
}
}
impl BoundingBox for VectorDataTable {
impl BoundingBox for Table<VectorData> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter()
.flat_map(|instance| {
self.iter_ref()
.flat_map(|row| {
if !include_stroke {
return instance.instance.bounding_box_with_transform(transform * *instance.transform);
return row.element.bounding_box_with_transform(transform * *row.transform);
}
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
let stroke_width = row.element.style.stroke().map(|s| s.weight()).unwrap_or_default();
let miter_limit = instance.instance.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);
let miter_limit = row.element.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);
let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of stroke caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
instance.instance.bounding_box_with_transform(transform * *instance.transform).map(|[a, b]| [a - offset, b + offset])
row.element.bounding_box_with_transform(transform * *row.transform).map(|[a, b]| [a - offset, b + offset])
})
.reduce(Quad::combine_bounds)
}

View File

@@ -48,7 +48,7 @@ macro_rules! create_ids {
};
}
create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId }
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
/// A no-op hasher that allows writing u64s (the id type).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]

View File

@@ -1,6 +1,6 @@
use super::*;
use crate::Ctx;
use crate::instances::Instance;
use crate::table::TableRow;
use crate::uuid::{NodeId, generate_uuid};
use bezier_rs::BezierHandles;
use dyn_any::DynAny;
@@ -420,35 +420,35 @@ impl Hash for VectorModification {
/// Applies a diff modification to a vector path.
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
async fn path_modify(_ctx: impl Ctx, mut vector_data: Table<VectorData>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<VectorData> {
if vector_data.is_empty() {
vector_data.push(Instance::default());
vector_data.push(TableRow::default());
}
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item");
modification.apply(vector_data_instance.instance);
let row = vector_data.get_mut(0).expect("push should give one item");
modification.apply(row.element);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path);
*row.source_node_id = row.source_node_id.or(this_node_path);
if vector_data.len() > 1 {
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len());
warn!("The path modify ran on {} rows of vector data. Only the first can be modified.", vector_data.len());
}
vector_data
}
/// Applies the vector path's local transformation to its geometry and resets it to the identity.
#[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
for vector_data_instance in vector_data.instance_mut_iter() {
let vector_data = vector_data_instance.instance;
let transform = *vector_data_instance.transform;
async fn apply_transform(_ctx: impl Ctx, mut vector_data: Table<VectorData>) -> Table<VectorData> {
for row in vector_data.iter_mut() {
let vector_data = row.element;
let transform = *row.transform;
for (_, point) in vector_data.point_domain.positions_mut() {
*point = transform.transform_point2(*point);
}
*vector_data_instance.transform = DAffine2::IDENTITY;
*row.transform = DAffine2::IDENTITY;
}
vector_data

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
use bezier_rs::{ManipulatorGroup, Subpath};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphene_core::instances::{Instance, InstanceRef};
use graphene_core::table::{Table, TableRow, TableRowRef};
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphene_core::vector::style::Fill;
use graphene_core::vector::{PointId, VectorData, VectorDataTable};
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
use graphene_core::vector::{PointId, VectorData};
use graphene_core::{Color, Ctx, GraphicElement};
pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation};
use std::ops::Mul;
@@ -32,10 +32,10 @@ pub enum BooleanOperation {
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
#[node_macro::node(category(""))]
async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
async fn boolean_operation<I: Into<Table<GraphicElement>> + 'n + Send + Clone>(
_: impl Ctx,
/// The group of paths to perform the boolean operation on. Nested groups are automatically flattened.
#[implementations(GraphicGroupTable, VectorDataTable)]
#[implementations(Table<GraphicElement>, Table<VectorData>)]
group_of_paths: I,
/// Which boolean operation to perform on the paths.
///
@@ -44,29 +44,29 @@ async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
/// Intersection cuts away all but the overlapping areas shared by every path.
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
operation: BooleanOperation,
) -> VectorDataTable {
) -> Table<VectorData> {
let group_of_paths = group_of_paths.into();
// The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation);
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).iter_ref(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().next() {
if let Some(result_vector_data) = result_vector_data_table.iter_mut().next() {
let transform = *result_vector_data.transform;
*result_vector_data.transform = DAffine2::IDENTITY;
VectorData::transform(result_vector_data.instance, transform);
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
VectorData::transform(result_vector_data.element, transform);
result_vector_data.element.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.element.upstream_graphic_group = Some(group_of_paths.clone());
// Clean up the boolean operation result by merging duplicated points
result_vector_data.instance.merge_by_distance_spatial(*result_vector_data.transform, 0.0001);
result_vector_data.element.merge_by_distance_spatial(*result_vector_data.transform, 0.0001);
}
result_vector_data_table
}
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> VectorDataTable {
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> Table<VectorData> {
match boolean_operation {
BooleanOperation::Union => union(vector_data),
BooleanOperation::SubtractFront => subtract(vector_data),
@@ -76,23 +76,23 @@ fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedItera
}
}
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
// Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_reversed = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data_reversed.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
let mut result_vector_data_table = Table::new_from_row(vector_data_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
// Loop over all vector data and union it with the result
let default = Instance::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.to_instance_ref()));
let default = TableRow::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.as_ref()));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
@@ -109,21 +109,21 @@ fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Vector
result_vector_data_table
}
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
fn subtract<'a>(vector_data: impl Iterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.into_iter();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut next_vector_data = vector_data.next();
while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
@@ -140,23 +140,23 @@ fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>)
result_vector_data_table
}
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let default = Instance::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_ref()));
let default = TableRow::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.as_ref()));
// For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@@ -172,45 +172,45 @@ fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Ve
result_vector_data_table
}
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable {
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone) -> Table<VectorData> {
let mut vector_data_iter = vector_data.clone().rev();
let mut any_intersection = Instance::default();
let default = Instance::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.to_instance_ref()));
let mut any_intersection = TableRow::default();
let default = TableRow::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.as_ref()));
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
let first_instance = unioned.instance_ref_iter().next().expect("Expected at least one instance after the boolean union");
let first_row = unioned.iter_ref().next().expect("Expected at least one row after the boolean union");
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let upper_path_string = to_path(first_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
let upper_path_string = to_path(first_row.element, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut instance = from_path(&boolean_intersection_string);
instance.style = first_instance.instance.style.clone();
let boolean_intersection_result = Instance {
instance,
transform: *first_instance.transform,
alpha_blending: *first_instance.alpha_blending,
source_node_id: *first_instance.source_node_id,
let mut element = from_path(&boolean_intersection_string);
element.style = first_row.element.style.clone();
let boolean_intersection_result = TableRow {
element,
transform: *first_row.transform,
alpha_blending: *first_row.alpha_blending,
source_node_id: *first_row.source_node_id,
};
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
let upper_path_string = to_path(&boolean_intersection_result.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(&any_intersection.instance, transform_of_lower_into_space_of_upper);
let upper_path_string = to_path(&boolean_intersection_result.element, DAffine2::IDENTITY);
let lower_path_string = to_path(&any_intersection.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
any_intersection.instance = union_result;
any_intersection.element = union_result;
any_intersection.transform = boolean_intersection_result.transform;
any_intersection.instance.style = boolean_intersection_result.instance.style.clone();
any_intersection.element.style = boolean_intersection_result.element.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
second_vector_data = vector_data_iter.next();
@@ -218,18 +218,18 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, V
// Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
boolean_operation_on_vector_data_table(union.instance_ref_iter().chain(std::iter::once(any_intersection.to_instance_ref())), BooleanOperation::SubtractFront)
boolean_operation_on_vector_data_table(union.iter_ref().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
}
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable {
fn flatten_vector_data(graphic_group_table: &Table<GraphicElement>) -> Table<VectorData> {
graphic_group_table
.instance_ref_iter()
.iter_ref()
.flat_map(|element| {
match element.instance.clone() {
match element.element.clone() {
GraphicElement::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data
vector_data
.instance_iter()
.iter()
.map(|mut sub_vector_data| {
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
@@ -238,47 +238,47 @@ fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTab
.collect::<Vec<_>>()
}
GraphicElement::RasterDataCPU(image) => {
let make_instance = |transform| {
let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill
let mut instance = VectorData::from_subpath(subpath);
instance.style.set_fill(Fill::Solid(Color::BLACK));
let mut element = VectorData::from_subpath(subpath);
element.style.set_fill(Fill::Solid(Color::BLACK));
Instance { instance, ..Default::default() }
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>()
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
GraphicElement::RasterDataGPU(image) => {
let make_instance = |transform| {
let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill
let mut instance = VectorData::from_subpath(subpath);
instance.style.set_fill(Fill::Solid(Color::BLACK));
let mut element = VectorData::from_subpath(subpath);
element.style.set_fill(Fill::Solid(Color::BLACK));
Instance { instance, ..Default::default() }
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>()
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instance_mut_iter() {
for sub_element in graphic_group.iter_mut() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
// Recursively flatten the inner group into vector data
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union);
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).iter_ref(), BooleanOperation::Union);
unioned.instance_iter().collect::<Vec<_>>()
unioned.iter().collect::<Vec<_>>()
}
}
})

View File

@@ -8,11 +8,13 @@ use graphene_application_io::{ImageTexture, SurfaceFrame};
use graphene_brush::brush_cache::BrushCache;
use graphene_brush::brush_stroke::BrushStroke;
use graphene_core::raster::Image;
use graphene_core::raster_types::CPU;
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId;
use graphene_core::vector::VectorData;
use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type};
use graphene_core::{Artboard, Color, GraphicElement, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata;
use std::fmt::Display;
use std::hash::Hash;
@@ -166,44 +168,36 @@ tagged_value! {
U64(u64),
Bool(bool),
String(String),
#[serde(alias = "IVec2", alias = "UVec2")]
DVec2(DVec2),
DAffine2(DAffine2),
OptionalF64(Option<f64>),
OptionalDVec2(Option<DVec2>),
// ==========================
// PRIMITIVE COLLECTION TYPES
// ==========================
// ========================
// LISTS OF PRIMITIVE TYPES
// ========================
#[serde(alias = "VecF32")] // TODO: Eventually remove this alias document upgrade code
VecF64(Vec<f64>),
VecU64(Vec<u64>),
VecDVec2(Vec<DVec2>),
F64Array4([f64; 4]),
NodePath(Vec<NodeId>),
#[serde(alias = "ManipulatorGroupIds")] // TODO: Eventually remove this alias document upgrade code
PointIds(Vec<graphene_core::vector::PointId>),
// ====================
// GRAPHICAL DATA TYPES
// ====================
GraphicElement(graphene_core::GraphicElement),
// ===========
// TABLE TYPES
// ===========
GraphicElement(GraphicElement),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code
VectorData(graphene_core::vector::VectorDataTable),
VectorData(Table<VectorData>),
#[cfg_attr(target_family = "wasm", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
RasterData(graphene_core::raster_types::RasterDataTable<CPU>),
RasterData(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(graphene_core::ArtboardGroupTable),
GraphicGroup(Table<GraphicElement>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::artboard::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(Table<Artboard>),
// ============
// STRUCT TYPES
// ============
Artboard(graphene_core::Artboard),
Image(graphene_core::raster::Image<Color>),
Color(graphene_core::raster::color::Color),
OptionalColor(Option<graphene_core::raster::color::Color>),
#[serde(alias = "IVec2", alias = "UVec2")]
DVec2(DVec2),
DAffine2(DAffine2),
Color(Color),
OptionalColor(Option<Color>),
Palette(Vec<Color>),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke),
Gradient(graphene_core::vector::style::Gradient),
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
@@ -215,10 +209,10 @@ tagged_value! {
Curve(graphene_raster_nodes::curve::Curve),
Footprint(graphene_core::transform::Footprint),
VectorModification(Box<graphene_core::vector::VectorModification>),
FontCache(Arc<graphene_core::text::FontCache>),
// ==========
// ENUM TYPES
// ==========
Fill(graphene_core::vector::style::Fill),
BlendMode(graphene_core::blending::BlendMode),
LuminanceCalculation(graphene_raster_nodes::adjustments::LuminanceCalculation),
XY(graphene_core::extract_xy::XY),
@@ -243,7 +237,6 @@ tagged_value! {
StrokeAlign(graphene_core::vector::style::StrokeAlign),
PaintOrder(graphene_core::vector::style::PaintOrder),
FillType(graphene_core::vector::style::FillType),
FillChoice(graphene_core::vector::style::FillChoice),
GradientType(graphene_core::vector::style::GradientType),
ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType),

View File

@@ -10,8 +10,8 @@ impl Adjust<Color> for Color {
}
impl Adjust<Color> for Option<Color> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
if let Some(v) = self {
*v = map_fn(v)
if let Some(color) = self {
*color = map_fn(color)
}
}
}
@@ -20,19 +20,21 @@ impl Adjust<Color> for Option<Color> {
mod adjust_std {
use super::*;
use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for (_pos, c) in self.iter_mut() {
*c = map_fn(c);
for (_, color) in self.iter_mut() {
*color = map_fn(color);
}
}
}
impl Adjust<Color> for RasterDataTable<CPU> {
impl Adjust<Color> for Table<Raster<CPU>> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for instance in self.instance_mut_iter() {
for c in instance.instance.data_mut().data.iter_mut() {
*c = map_fn(c);
for row in self.iter_mut() {
for color in row.element.data_mut().data.iter_mut() {
*color = map_fn(color);
}
}
}

View File

@@ -6,7 +6,8 @@ use core::fmt::Debug;
#[cfg(feature = "std")]
use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core_shaders::color::Color;
use graphene_core_shaders::context::Ctx;
use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage};
@@ -44,7 +45,7 @@ fn luminance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -68,7 +69,7 @@ fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -88,7 +89,7 @@ fn extract_channel<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -111,7 +112,7 @@ fn make_opaque<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -136,7 +137,7 @@ fn brightness_contrast<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -225,7 +226,7 @@ fn levels<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -292,7 +293,7 @@ async fn black_and_white<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -364,7 +365,7 @@ async fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -398,7 +399,7 @@ async fn invert<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -420,7 +421,7 @@ async fn threshold<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -465,7 +466,7 @@ async fn vibrance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -630,7 +631,7 @@ async fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -758,7 +759,7 @@ async fn selective_color<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -900,7 +901,7 @@ async fn posterize<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,
@@ -933,7 +934,7 @@ async fn exposure<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut input: T,

View File

@@ -2,7 +2,8 @@ use crate::adjust::Adjust;
#[cfg(feature = "std")]
use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core_shaders::Ctx;
use graphene_core_shaders::blending::BlendMode;
use graphene_core_shaders::color::{Color, Pixel};
@@ -32,16 +33,18 @@ mod blend_std {
use core::cmp::Ordering;
use graphene_core::raster::Image;
use graphene_core::raster_types::Raster;
impl Blend<Color> for RasterDataTable<CPU> {
use graphene_core::table::Table;
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.instance_mut_iter().zip(under.instance_ref_iter()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
for (over, under) in result_table.iter_mut().zip(under.iter_ref()) {
let data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.instance = Raster::new_cpu(Image {
*over.element = Raster::new_cpu(Image {
data,
width: over.instance.width,
height: over.instance.height,
width: over.element.width,
height: over.element.height,
base64_string: None,
});
}
@@ -124,14 +127,14 @@ async fn blend<T: Blend<Color> + Send>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
over: T,
#[expose]
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
under: T,
@@ -146,7 +149,7 @@ fn color_overlay<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,
@@ -185,7 +188,8 @@ mod test {
use graphene_core::blending::BlendMode;
use graphene_core::color::Color;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{Raster, RasterDataTable};
use graphene_core::raster_types::Raster;
use graphene_core::table::Table;
#[tokio::test]
async fn color_overlay_multiply() {
@@ -198,8 +202,8 @@ mod test {
// 100% of the output should come from the multiplied value
let opacity = 100_f64;
let result = super::color_overlay((), RasterDataTable::new(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.instance_ref_iter().next().unwrap().instance;
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.iter_ref().next().unwrap().element;
// 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()));

View File

@@ -1,17 +1,18 @@
use graphene_core::context::Ctx;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::types::Percentage;
use graphene_core::table::Table;
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
use std::cmp::{max, min};
#[node_macro::node(category("Raster: Filter"))]
async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percentage) -> RasterDataTable<CPU> {
async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percentage) -> Table<Raster<CPU>> {
image_frame
.instance_iter()
.map(|mut image_frame_instance| {
let image = image_frame_instance.instance;
.iter()
.map(|mut row| {
let image = row.element;
// Prepare the image data for processing
let image_data = bytemuck::cast_vec(image.data.clone());
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
@@ -30,8 +31,8 @@ async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percen
base64_string: None,
};
image_frame_instance.instance = Raster::new_cpu(dehazed_image);
image_frame_instance
row.element = Raster::new_cpu(dehazed_image);
row
})
.collect()
}

View File

@@ -2,15 +2,16 @@ use graphene_core::color::Color;
use graphene_core::context::Ctx;
use graphene_core::raster::image::Image;
use graphene_core::raster::{Bitmap, BitmapMut};
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::types::PixelLength;
use graphene_core::table::Table;
/// Blurs the image with a Gaussian or blur kernel filter.
#[node_macro::node(category("Raster: Filter"))]
async fn blur(
_: impl Ctx,
/// The image to be blurred.
image_frame: RasterDataTable<CPU>,
image_frame: Table<Raster<CPU>>,
/// The radius of the blur kernel.
#[range((0., 100.))]
#[hard_min(0.)]
@@ -19,11 +20,11 @@ async fn blur(
box_blur: bool,
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
gamma: bool,
) -> RasterDataTable<CPU> {
) -> Table<Raster<CPU>> {
image_frame
.instance_iter()
.map(|mut image_instance| {
let image = image_instance.instance.clone();
.iter()
.map(|mut row| {
let image = row.element.clone();
// Run blur algorithm
let blurred_image = if radius < 0.1 {
@@ -35,8 +36,8 @@ async fn blur(
Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma))
};
image_instance.instance = blurred_image;
image_instance
row.element = blurred_image;
row
})
.collect()
}

View File

@@ -2,7 +2,8 @@
use crate::adjust::Adjust;
use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::{Color, Ctx};
// Aims for interoperable compatibility with:
@@ -13,7 +14,7 @@ async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
RasterDataTable<CPU>,
Table<Raster<CPU>>,
GradientStops,
)]
mut image: T,

View File

@@ -1,11 +1,12 @@
use graphene_core::color::Color;
use graphene_core::context::Ctx;
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
#[node_macro::node(category("Color"))]
async fn image_color_palette(
_: impl Ctx,
image: RasterDataTable<CPU>,
image: Table<Raster<CPU>>,
#[hard_min(1.)]
#[soft_max(28.)]
max_size: u32,
@@ -17,8 +18,8 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
for image_instance in image.instance_ref_iter() {
for pixel in image_instance.instance.data.iter() {
for row in image.iter_ref() {
for pixel in row.element.data.iter() {
let r = pixel.r() * GRID;
let g = pixel.g() * GRID;
let b = pixel.b() * GRID;
@@ -66,13 +67,13 @@ async fn image_color_palette(
mod test {
use super::*;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{Raster, RasterDataTable};
use graphene_core::raster_types::Raster;
#[test]
fn test_image_color_palette() {
let result = image_color_palette(
(),
RasterDataTable::new(Raster::new_cpu(Image {
Table::new_from_element(Raster::new_cpu(Image {
width: 100,
height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],

View File

@@ -6,11 +6,11 @@ use graphene_core::blending::AlphaBlending;
use graphene_core::color::Color;
use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
use graphene_core::context::{Ctx, ExtractFootprint};
use graphene_core::instances::Instance;
use graphene_core::math::bbox::Bbox;
use graphene_core::raster::image::Image;
use graphene_core::raster::{Bitmap, BitmapMut};
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::Transform;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
@@ -30,12 +30,12 @@ impl From<std::io::Error> for Error {
}
#[node_macro::node(category("Debug: Raster"))]
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image_frame
.instance_iter()
.filter_map(|mut image_frame_instance| {
let image_frame_transform = image_frame_instance.transform;
let image = image_frame_instance.instance;
.iter()
.filter_map(|mut row| {
let image_frame_transform = row.transform;
let image = row.element;
// Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone());
@@ -87,9 +87,9 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
image_frame_instance.transform = new_transform;
image_frame_instance.instance = Raster::new_cpu(image);
Some(image_frame_instance)
row.transform = new_transform;
row.element = Raster::new_cpu(image);
Some(row)
})
.collect()
}
@@ -98,28 +98,28 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast
pub fn combine_channels(
_: impl Ctx,
_primary: (),
#[expose] red: RasterDataTable<CPU>,
#[expose] green: RasterDataTable<CPU>,
#[expose] blue: RasterDataTable<CPU>,
#[expose] alpha: RasterDataTable<CPU>,
) -> RasterDataTable<CPU> {
#[expose] red: Table<Raster<CPU>>,
#[expose] green: Table<Raster<CPU>>,
#[expose] blue: Table<Raster<CPU>>,
#[expose] alpha: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());
let red = red.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let blue = blue.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let alpha = alpha.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let red = red.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let blue = blue.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let alpha = alpha.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
red.zip(green)
.zip(blue)
.zip(alpha)
.filter_map(|(((red, green), blue), alpha)| {
// Turn any default zero-sized image instances into None
let red = red.filter(|i| i.instance.width > 0 && i.instance.height > 0);
let green = green.filter(|i| i.instance.width > 0 && i.instance.height > 0);
let blue = blue.filter(|i| i.instance.width > 0 && i.instance.height > 0);
let alpha = alpha.filter(|i| i.instance.width > 0 && i.instance.height > 0);
// Turn any default zero-sized image rows into None
let red = red.filter(|i| i.element.width > 0 && i.element.height > 0);
let green = green.filter(|i| i.element.width > 0 && i.element.height > 0);
let blue = blue.filter(|i| i.element.width > 0 && i.element.height > 0);
let alpha = alpha.filter(|i| i.element.width > 0 && i.element.height > 0);
// Get this instance's transform and alpha blending mode from the first non-empty channel
// Get this row's transform and alpha blending mode from the first non-empty channel
let (transform, alpha_blending, source_node_id) = [&red, &green, &blue, &alpha]
.iter()
.find_map(|i| i.as_ref())
@@ -127,10 +127,10 @@ pub fn combine_channels(
// Get the common width and height of the channels, which must have equal dimensions
let channel_dimensions = [
red.as_ref().map(|r| (r.instance.width, r.instance.height)),
green.as_ref().map(|g| (g.instance.width, g.instance.height)),
blue.as_ref().map(|b| (b.instance.width, b.instance.height)),
alpha.as_ref().map(|a| (a.instance.width, a.instance.height)),
red.as_ref().map(|r| (r.element.width, r.element.height)),
green.as_ref().map(|g| (g.element.width, g.element.height)),
blue.as_ref().map(|b| (b.element.width, b.element.height)),
alpha.as_ref().map(|a| (a.element.width, a.element.height)),
];
if channel_dimensions.iter().all(Option::is_none)
|| channel_dimensions
@@ -142,7 +142,7 @@ pub fn combine_channels(
}
let &(width, height) = channel_dimensions.iter().flatten().next()?;
// Create a new image for this instance output
// Create a new image for the output element
let mut image = Image::new(width, height, Color::TRANSPARENT);
// Iterate over all pixels in the image and set the color channels
@@ -150,22 +150,22 @@ pub fn combine_channels(
for x in 0..image.width() {
let image_pixel = image.get_pixel_mut(x, y).unwrap();
if let Some(r) = red.as_ref().and_then(|r| r.instance.get_pixel(x, y)) {
if let Some(r) = red.as_ref().and_then(|r| r.element.get_pixel(x, y)) {
image_pixel.set_red(r.l().cast_linear_channel());
} else {
image_pixel.set_red(Channel::from_linear(0.));
}
if let Some(g) = green.as_ref().and_then(|g| g.instance.get_pixel(x, y)) {
if let Some(g) = green.as_ref().and_then(|g| g.element.get_pixel(x, y)) {
image_pixel.set_green(g.l().cast_linear_channel());
} else {
image_pixel.set_green(Channel::from_linear(0.));
}
if let Some(b) = blue.as_ref().and_then(|b| b.instance.get_pixel(x, y)) {
if let Some(b) = blue.as_ref().and_then(|b| b.element.get_pixel(x, y)) {
image_pixel.set_blue(b.l().cast_linear_channel());
} else {
image_pixel.set_blue(Channel::from_linear(0.));
}
if let Some(a) = alpha.as_ref().and_then(|a| a.instance.get_pixel(x, y)) {
if let Some(a) = alpha.as_ref().and_then(|a| a.element.get_pixel(x, y)) {
image_pixel.set_alpha(a.l().cast_linear_channel());
} else {
image_pixel.set_alpha(Channel::from_linear(1.));
@@ -173,8 +173,8 @@ pub fn combine_channels(
}
}
Some(Instance {
instance: Raster::new_cpu(image),
Some(TableRow {
element: Raster::new_cpu(image),
transform,
alpha_blending,
source_node_id,
@@ -187,70 +187,70 @@ pub fn combine_channels(
pub fn mask(
_: impl Ctx,
/// The image to be masked.
image: RasterDataTable<CPU>,
image: Table<Raster<CPU>>,
/// The stencil to be used for masking.
#[expose]
stencil: RasterDataTable<CPU>,
) -> RasterDataTable<CPU> {
// TODO: Support multiple stencil instances
let Some(stencil_instance) = stencil.instance_iter().next() else {
stencil: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
// TODO: Figure out what it means to support multiple stencil rows?
let Some(stencil) = stencil.iter().next() else {
// No stencil provided so we return the original image
return image;
};
let stencil_size = DVec2::new(stencil_instance.instance.width as f64, stencil_instance.instance.height as f64);
let stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64);
image
.instance_iter()
.filter_map(|mut image_instance| {
let image_size = DVec2::new(image_instance.instance.width as f64, image_instance.instance.height as f64);
let mask_size = stencil_instance.transform.decompose_scale();
.iter()
.filter_map(|mut row| {
let image_size = DVec2::new(row.element.width as f64, row.element.height as f64);
let mask_size = stencil.transform.decompose_scale();
if mask_size == DVec2::ZERO {
return None;
}
// Transforms a point from the background image to the foreground image
let bg_to_fg = image_instance.transform * DAffine2::from_scale(1. / image_size);
let stencil_transform_inverse = stencil_instance.transform.inverse();
let bg_to_fg = row.transform * DAffine2::from_scale(1. / image_size);
let stencil_transform_inverse = stencil.transform.inverse();
for y in 0..image_instance.instance.height {
for x in 0..image_instance.instance.width {
for y in 0..row.element.height {
for x in 0..row.element.width {
let image_point = DVec2::new(x as f64, y as f64);
let mask_point = bg_to_fg.transform_point2(image_point);
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
let mask_point = stencil_instance.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
let mask_point = (DAffine2::from_scale(stencil_size) * stencil_instance.transform.inverse()).transform_point2(mask_point);
let mask_point = stencil.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
let mask_point = (DAffine2::from_scale(stencil_size) * stencil.transform.inverse()).transform_point2(mask_point);
let image_pixel = image_instance.instance.data_mut().get_pixel_mut(x, y).unwrap();
let mask_pixel = stencil_instance.instance.sample(mask_point);
let image_pixel = row.element.data_mut().get_pixel_mut(x, y).unwrap();
let mask_pixel = stencil.element.sample(mask_point);
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());
}
}
Some(image_instance)
Some(row)
})
.collect()
}
#[node_macro::node(category(""))]
pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DAffine2) -> Table<Raster<CPU>> {
image
.instance_iter()
.map(|mut image_instance| {
let image_aabb = Bbox::unit().affine_transform(image_instance.transform).to_axis_aligned_bbox();
.iter()
.map(|mut row| {
let image_aabb = Bbox::unit().affine_transform(row.transform).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
return image_instance;
return row;
}
let image_data = &image_instance.instance.data;
let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height);
let image_data = &row.element.data;
let (image_width, image_height) = (row.element.width, row.element.height);
if image_width == 0 || image_height == 0 {
return empty_image((), bounds, Color::TRANSPARENT).instance_iter().next().unwrap();
return empty_image((), bounds, Color::TRANSPARENT).iter().next().unwrap();
}
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.transform.inverse();
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * row.transform.inverse();
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
@@ -270,26 +270,26 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds:
// Compute new transform.
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
let new_texture_to_layer_space = row.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
image_instance.instance = Raster::new_cpu(new_image);
image_instance.transform = new_texture_to_layer_space;
image_instance
row.element = Raster::new_cpu(new_image);
row.transform = new_texture_to_layer_space;
row
})
.collect()
}
#[node_macro::node(category("Debug: Raster"))]
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> Table<Raster<CPU>> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let image = Image::new(width, height, color);
let mut result_table = RasterDataTable::new(Raster::new_cpu(image));
let image_instance = result_table.get_mut(0).unwrap();
*image_instance.transform = transform;
*image_instance.alpha_blending = AlphaBlending::default();
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
let row = result_table.get_mut(0).unwrap();
*row.transform = transform;
*row.alpha_blending = AlphaBlending::default();
// Callers of empty_image can safely unwrap on returned table
result_table
@@ -297,7 +297,7 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterData
/// Constructs a raster image.
#[node_macro::node(category(""))]
pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
pub fn image_value(_: impl Ctx, _primary: (), image: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image
}
@@ -321,7 +321,7 @@ pub fn noise_pattern(
cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType,
cellular_jitter: f64,
) -> RasterDataTable<CPU> {
) -> Table<Raster<CPU>> {
let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -339,7 +339,7 @@ pub fn noise_pattern(
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return RasterDataTable::default();
return Table::new();
}
let footprint_scale = footprint.scale();
@@ -383,8 +383,8 @@ pub fn noise_pattern(
}
}
return RasterDataTable::new_instance(Instance {
instance: Raster::new_cpu(image),
return Table::new_from_row(TableRow {
element: Raster::new_cpu(image),
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
});
@@ -445,15 +445,15 @@ pub fn noise_pattern(
}
}
RasterDataTable::new_instance(Instance {
instance: Raster::new_cpu(image),
Table::new_from_row(TableRow {
element: Raster::new_cpu(image),
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
})
}
#[node_macro::node(category("Raster: Pattern"))]
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -465,7 +465,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return RasterDataTable::default();
return Table::new();
}
let scale = footprint.scale();
@@ -487,8 +487,8 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
}
}
RasterDataTable::new_instance(Instance {
instance: Raster::new_cpu(Image {
Table::new_from_row(TableRow {
element: Raster::new_cpu(Image {
width,
height,
data,

View File

@@ -1,7 +1,6 @@
use crate::vector::VectorDataTable;
use graph_craft::wasm_application_io::WasmEditorApi;
use graphene_core::Ctx;
pub use graphene_core::text::*;
use graphene_core::{Ctx, table::Table, vector::VectorData};
#[node_macro::node(category(""))]
fn text<'i: 'n>(
@@ -29,10 +28,10 @@ fn text<'i: 'n>(
#[default(0.)]
tilt: f64,
align: TextAlign,
/// Splits each text glyph into its own instance, i.e. row in the table of vector data.
/// Splits each text glyph into its own row in the table of vector data.
#[default(false)]
per_glyph_instances: bool,
) -> VectorDataTable {
) -> Table<VectorData> {
let typesetting = TypesettingConfig {
font_size,
line_height_ratio,

View File

@@ -2,15 +2,15 @@ use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType;
pub use graph_craft::wasm_application_io::*;
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_family = "wasm")]
use graphene_core::instances::Instances;
use graphene_core::Artboard;
#[cfg(target_family = "wasm")]
use graphene_core::math::bbox::Bbox;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorDataTable;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
use graphene_core::vector::VectorData;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicElement, OwnedContextImpl, WasmNotSend};
use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
@@ -100,9 +100,9 @@ fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
}
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
fn image_to_bytes(_: impl Ctx, image: RasterDataTable<CPU>) -> Vec<u8> {
let Some(image) = image.instance_ref_iter().next() else { return vec![] };
image.instance.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
let Some(image) = image.iter_ref().next() else { return vec![] };
image.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
}
#[node_macro::node(category("Web Request"))]
@@ -121,9 +121,9 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
}
#[node_macro::node(category("Web Request"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> {
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return RasterDataTable::default();
return Table::new();
};
let image = image.to_rgba32f();
let image = Image {
@@ -136,7 +136,7 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> {
..Default::default()
};
RasterDataTable::new(Raster::new_cpu(image))
Table::new_from_element(Raster::new_cpu(image))
}
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType {
@@ -221,22 +221,22 @@ async fn render_canvas(
async fn rasterize<T: WasmNotSend + 'n>(
_: impl Ctx,
#[implementations(
VectorDataTable,
RasterDataTable<CPU>,
GraphicGroupTable,
Table<VectorData>,
Table<Raster<CPU>>,
Table<GraphicElement>,
)]
mut data: Instances<T>,
mut data: Table<T>,
footprint: Footprint,
surface_handle: Arc<graphene_application_io::SurfaceHandle<HtmlCanvasElement>>,
) -> RasterDataTable<CPU>
) -> Table<Raster<CPU>>
where
Instances<T>: GraphicElementRendered,
Table<T>: GraphicElementRendered,
{
use graphene_core::instances::Instance;
use graphene_core::table::TableRow;
if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization");
return RasterDataTable::default();
return Table::new();
}
let mut render = SvgRender::new();
@@ -249,8 +249,8 @@ where
..Default::default()
};
for instance in data.instance_mut_iter() {
*instance.transform = DAffine2::from_translation(-aabb.start) * *instance.transform;
for row in data.iter_mut() {
*row.transform = DAffine2::from_translation(-aabb.start) * *row.transform;
}
data.render_svg(&mut render, &render_params);
render.format_svg(glam::DVec2::ZERO, size);
@@ -277,8 +277,8 @@ where
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
RasterDataTable::new_instance(Instance {
instance: Raster::new_cpu(image),
Table::new_from_row(TableRow {
element: Raster::new_cpu(image),
transform: footprint.transform,
..Default::default()
})
@@ -289,11 +289,11 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
render_config: RenderConfig,
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
#[implementations(
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>,
Context -> GraphicGroupTable,
Context -> graphene_core::Artboard,
Context -> graphene_core::ArtboardGroupTable,
Context -> Table<VectorData>,
Context -> Table<Raster<CPU>>,
Context -> Table<GraphicElement>,
Context -> Table<Artboard>,
Context -> Artboard,
Context -> Option<Color>,
Context -> Vec<Color>,
Context -> bool,

View File

@@ -6,17 +6,17 @@ use glam::{DAffine2, DVec2};
use graphene_core::blending::BlendMode;
use graphene_core::bounds::BoundingBox;
use graphene_core::color::Color;
use graphene_core::instances::Instance;
use graphene_core::math::quad::Quad;
use graphene_core::raster::Image;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::render_complexity::RenderComplexity;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::VectorDataTable;
use graphene_core::vector::VectorData;
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
use graphene_core::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
use graphene_core::{Artboard, GraphicElement};
use num_traits::Zero;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
@@ -206,7 +206,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
pub struct RenderMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
pub first_element_source_id: HashMap<NodeId, Option<NodeId>>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
}
@@ -234,37 +234,37 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {}
}
impl GraphicElementRendered for GraphicGroupTable {
impl GraphicElementRendered for Table<GraphicElement> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let mut iter = self.instance_ref_iter().peekable();
let mut iter = self.iter_ref().peekable();
let mut mask_state = None;
while let Some(instance) = iter.next() {
while let Some(row) = iter.next() {
render.parent_tag(
"g",
|attributes| {
let matrix = format_transform_matrix(*instance.transform);
let matrix = format_transform_matrix(*row.transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. {
attributes.push("opacity", opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", row.alpha_blending.blend_mode.render());
}
let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled());
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
if next_clips && mask_state.is_none() {
let uuid = generate_uuid();
let mask_type = if instance.instance.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
let mask_type = if row.element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
mask_state = Some((uuid, mask_type));
let mut svg = SvgRender::new();
instance.instance.render_svg(&mut svg, &render_params.for_clipper());
row.element.render_svg(&mut svg, &render_params.for_clipper());
write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap();
mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string());
@@ -280,7 +280,7 @@ impl GraphicElementRendered for GraphicGroupTable {
}
},
|render| {
instance.instance.render_svg(render, render_params);
row.element.render_svg(render, render_params);
},
);
}
@@ -288,12 +288,12 @@ impl GraphicElementRendered for GraphicGroupTable {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
let mut iter = self.instance_ref_iter().peekable();
let mut mask_instance_state = None;
let mut iter = self.iter_ref().peekable();
let mut mask_element_and_transform = None;
while let Some(instance) = iter.next() {
let transform = transform * *instance.transform;
let alpha_blending = *instance.alpha_blending;
while let Some(row) = iter.next() {
let transform = transform * *row.transform;
let alpha_blending = *row.alpha_blending;
let mut layer = false;
@@ -303,9 +303,9 @@ impl GraphicElementRendered for GraphicGroupTable {
};
let mut bounds = None;
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. || (render_params.view_mode != ViewMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
bounds = instance.instance.bounding_box(transform, true);
bounds = row.element.bounding_box(transform, true);
if let Some(bounds) = bounds {
scene.push_layer(
@@ -318,35 +318,35 @@ impl GraphicElementRendered for GraphicGroupTable {
}
}
let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled());
if next_clips && mask_instance_state.is_none() {
mask_instance_state = Some((instance.instance, transform));
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
if next_clips && mask_element_and_transform.is_none() {
mask_element_and_transform = Some((row.element, transform));
instance.instance.render_to_vello(scene, transform, context, render_params);
} else if let Some((instance_mask, transform_mask)) = mask_instance_state {
row.element.render_to_vello(scene, transform, context, render_params);
} else if let Some((mask_element, transform_mask)) = mask_element_and_transform {
if !next_clips {
mask_instance_state = None;
mask_element_and_transform = None;
}
if !layer {
bounds = instance.instance.bounding_box(transform, true);
bounds = row.element.bounding_box(transform, true);
}
if let Some(bounds) = bounds {
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
instance_mask.render_to_vello(scene, transform_mask, context, &render_params.for_clipper());
mask_element.render_to_vello(scene, transform_mask, context, &render_params.for_clipper());
scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect);
}
instance.instance.render_to_vello(scene, transform, context, render_params);
row.element.render_to_vello(scene, transform, context, render_params);
if bounds.is_some() {
scene.pop_layer();
scene.pop_layer();
}
} else {
instance.instance.render_to_vello(scene, transform, context, render_params);
row.element.render_to_vello(scene, transform, context, render_params);
}
if layer {
@@ -356,24 +356,24 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() {
if let Some(element_id) = instance.source_node_id {
for row in self.iter_ref() {
if let Some(element_id) = row.source_node_id {
let mut footprint = footprint;
footprint.transform *= *instance.transform;
footprint.transform *= *row.transform;
instance.instance.collect_metadata(metadata, footprint, Some(*element_id));
row.element.collect_metadata(metadata, footprint, Some(*element_id));
}
}
if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
for instance in self.instance_ref_iter() {
for row in self.iter_ref() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
row.element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(*instance.transform)
click_target.apply_transform(*row.transform)
}
all_upstream_click_targets.extend(new_click_targets);
@@ -384,13 +384,13 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() {
for row in self.iter_ref() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
row.element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(*instance.transform)
click_target.apply_transform(*row.transform)
}
click_targets.extend(new_click_targets);
@@ -398,25 +398,25 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn contains_artboard(&self) -> bool {
self.instance_ref_iter().any(|instance| instance.instance.contains_artboard())
self.iter_ref().any(|row| row.element.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instance_mut_iter() {
instance.instance.new_ids_from_hash(*instance.source_node_id);
for row in self.iter_mut() {
row.element.new_ids_from_hash(*row.source_node_id);
}
}
}
impl GraphicElementRendered for VectorDataTable {
impl GraphicElementRendered for Table<VectorData> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instance_ref_iter() {
let multiplied_transform = *instance.transform;
let vector_data = &instance.instance;
for row in self.iter_ref() {
let multiplied_transform = *row.transform;
let vector_data = &row.element;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = vector_data.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(*instance.transform);
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
@@ -425,7 +425,7 @@ impl GraphicElementRendered for VectorDataTable {
let mut path = String::new();
for subpath in instance.instance.stroke_bezier_paths() {
for subpath in row.element.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
}
@@ -440,18 +440,18 @@ impl GraphicElementRendered for VectorDataTable {
MaskType::Mask
};
let can_use_order = !instance.instance.style.fill().is_none() && mask_type == MaskType::Mask;
let can_use_order = !row.element.style.fill().is_none() && mask_type == MaskType::Mask;
if !can_use_order {
let id = format!("alignment-{}", generate_uuid());
let mut fill_instance = instance.instance.clone();
fill_instance.style.clear_stroke();
fill_instance.style.set_fill(Fill::solid(Color::BLACK));
let mut element = row.element.clone();
element.style.clear_stroke();
element.style.set_fill(Fill::solid(Color::BLACK));
let vector_row = VectorDataTable::new_instance(Instance {
instance: fill_instance,
alpha_blending: *instance.alpha_blending,
transform: *instance.transform,
let vector_row = Table::new_from_row(TableRow {
element,
alpha_blending: *row.alpha_blending,
transform: *row.transform,
source_node_id: None,
});
@@ -471,7 +471,7 @@ impl GraphicElementRendered for VectorDataTable {
let mut svg = SvgRender::new();
vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));
let weight = instance.instance.style.stroke().unwrap().weight * instance.transform.matrix2.determinant();
let weight = row.element.style.stroke().unwrap().weight * row.transform.matrix2.determinant();
let quad = Quad::from_box(transformed_bounds).inflate(weight);
let (x, y) = quad.top_left().into();
let (width, height) = (quad.bottom_right() - quad.top_left()).into();
@@ -483,7 +483,7 @@ impl GraphicElementRendered for VectorDataTable {
}
}
let fill_and_stroke = instance.instance.style.render(
let fill_and_stroke = row.element.style.render(
defs,
element_transform,
applied_stroke_transform,
@@ -500,13 +500,13 @@ impl GraphicElementRendered for VectorDataTable {
}
attributes.push_val(fill_and_stroke);
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. {
attributes.push("opacity", opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", row.alpha_blending.blend_mode.render());
}
});
}
@@ -519,33 +519,33 @@ impl GraphicElementRendered for VectorDataTable {
use vello::kurbo::{Cap, Join};
use vello::peniko;
for instance in self.instance_ref_iter() {
let multiplied_transform = parent_transform * *instance.transform;
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
for row in self.iter_ref() {
let multiplied_transform = parent_transform * *row.transform;
let has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.instance.bounding_box().unwrap_or_default();
let layer_bounds = row.element.bounding_box().unwrap_or_default();
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new();
for subpath in instance.instance.stroke_bezier_paths() {
for subpath in row.element.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path);
}
// If we're using opacity or a blend mode, we need to push a layer
let blend_mode = match render_params.view_mode {
ViewMode::Outline => peniko::Mix::Normal,
_ => instance.alpha_blending.blend_mode.to_peniko(),
_ => row.alpha_blending.blend_mode.to_peniko(),
};
let mut layer = false;
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. || row.alpha_blending.blend_mode != BlendMode::default() {
layer = true;
let weight = instance.instance.style.stroke().unwrap().weight;
let weight = row.element.style.stroke().unwrap().weight;
let quad = Quad::from_box(layer_bounds).inflate(weight * element_transform.matrix2.determinant());
let layer_bounds = quad.bounding_box();
scene.push_layer(
@@ -556,25 +556,25 @@ impl GraphicElementRendered for VectorDataTable {
);
}
let can_draw_aligned_stroke = instance.instance.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered())
&& instance.instance.stroke_bezier_paths().all(|path| path.closed());
let can_draw_aligned_stroke =
row.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed());
let reorder_for_outside = instance.instance.style.stroke().is_some_and(|stroke| stroke.align == StrokeAlign::Outside) && !instance.instance.style.fill().is_none();
let reorder_for_outside = row.element.style.stroke().is_some_and(|stroke| stroke.align == StrokeAlign::Outside) && !row.element.style.fill().is_none();
let use_layer = can_draw_aligned_stroke && !reorder_for_outside;
if use_layer {
let mut fill_instance = instance.instance.clone();
fill_instance.style.clear_stroke();
fill_instance.style.set_fill(Fill::solid(Color::BLACK));
let mut element = row.element.clone();
element.style.clear_stroke();
element.style.set_fill(Fill::solid(Color::BLACK));
let vector_data = VectorDataTable::new_instance(Instance {
instance: fill_instance,
alpha_blending: *instance.alpha_blending,
transform: *instance.transform,
let vector_data = Table::new_from_row(TableRow {
element,
alpha_blending: *row.alpha_blending,
transform: *row.transform,
source_node_id: None,
});
let bounds = instance.instance.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
let weight = instance.instance.style.stroke().unwrap().weight;
let bounds = row.element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
let weight = row.element.style.stroke().unwrap().weight;
let quad = Quad::from_box(bounds).inflate(weight * element_transform.matrix2.determinant());
let bounds = quad.bounding_box();
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
@@ -611,7 +611,7 @@ impl GraphicElementRendered for VectorDataTable {
Stroke,
}
let order = match instance.instance.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) || reorder_for_outside {
let order = match row.element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) || reorder_for_outside {
true => [Op::Stroke, Op::Fill],
false => [Op::Fill, Op::Stroke], // Default
};
@@ -619,7 +619,7 @@ impl GraphicElementRendered for VectorDataTable {
for operation in order {
match operation {
Op::Fill => {
match instance.instance.style.fill() {
match row.element.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
@@ -633,7 +633,7 @@ impl GraphicElementRendered for VectorDataTable {
});
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.instance.nonzero_bounding_box();
let bounds = row.element.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. {
@@ -679,7 +679,7 @@ impl GraphicElementRendered for VectorDataTable {
};
}
Op::Stroke => {
if let Some(stroke) = instance.instance.style.stroke() {
if let Some(stroke) = row.element.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT,
@@ -728,13 +728,13 @@ impl GraphicElementRendered for VectorDataTable {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() {
let instance_transform = *instance.transform;
let instance = instance.instance;
for row in self.iter_ref() {
let transform = *row.transform;
let vector_data = row.element;
if let Some(element_id) = element_id {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None;
let stroke_width = vector_data.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = vector_data.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| {
if filled {
subpath.set_closed(true);
@@ -743,9 +743,9 @@ impl GraphicElementRendered for VectorDataTable {
};
// For free-floating anchors, we need to add a click target for each
let single_anchors_targets = instance.point_domain.ids().iter().filter_map(|&point_id| {
if instance.connected_count(point_id) == 0 {
let anchor = instance.point_domain.position_from_id(point_id).unwrap_or_default();
let single_anchors_targets = vector_data.point_domain.ids().iter().filter_map(|&point_id| {
if vector_data.connected_count(point_id) == 0 {
let anchor = vector_data.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor);
Some(ClickTarget::new_with_free_point(point))
@@ -754,7 +754,7 @@ impl GraphicElementRendered for VectorDataTable {
}
});
let click_targets = instance
let click_targets = vector_data
.stroke_bezier_paths()
.map(fill)
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width))
@@ -764,40 +764,40 @@ impl GraphicElementRendered for VectorDataTable {
metadata.click_targets.entry(element_id).or_insert(click_targets);
}
if let Some(upstream_graphic_group) = &instance.upstream_graphic_group {
footprint.transform *= instance_transform;
if let Some(upstream_graphic_group) = &vector_data.upstream_graphic_group {
footprint.transform *= transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None);
}
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() {
let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.instance.style.fill() != &Fill::None;
for row in self.iter_ref() {
let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = row.element.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| {
if filled {
subpath.set_closed(true);
}
subpath
};
click_targets.extend(instance.instance.stroke_bezier_paths().map(fill).map(|subpath| {
click_targets.extend(row.element.stroke_bezier_paths().map(fill).map(|subpath| {
let mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width);
click_target.apply_transform(*instance.transform);
click_target.apply_transform(*row.transform);
click_target
}));
// For free-floating anchors, we need to add a click target for each
let single_anchors_targets = instance.instance.point_domain.ids().iter().filter_map(|&point_id| {
if instance.instance.connected_count(point_id) > 0 {
let single_anchors_targets = row.element.point_domain.ids().iter().filter_map(|&point_id| {
if row.element.connected_count(point_id) > 0 {
return None;
}
let anchor = instance.instance.point_domain.position_from_id(point_id).unwrap_or_default();
let anchor = row.element.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor);
let mut click_target = ClickTarget::new_with_free_point(point);
click_target.apply_transform(*instance.transform);
click_target.apply_transform(*row.transform);
Some(click_target)
});
click_targets.extend(single_anchors_targets);
@@ -805,8 +805,8 @@ impl GraphicElementRendered for VectorDataTable {
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
for instance in self.instance_mut_iter() {
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
for row in self.iter_mut() {
row.element.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}
}
}
@@ -907,49 +907,49 @@ impl GraphicElementRendered for Artboard {
}
}
impl GraphicElementRendered for ArtboardGroupTable {
impl GraphicElementRendered for Table<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for artboard in self.instance_ref_iter() {
artboard.instance.render_svg(render, render_params);
for artboard in self.iter_ref() {
artboard.element.render_svg(render, render_params);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instance_ref_iter() {
instance.instance.render_to_vello(scene, transform, context, render_params);
for row in self.iter_ref() {
row.element.render_to_vello(scene, transform, context, render_params);
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() {
instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id);
for row in self.iter_ref() {
row.element.collect_metadata(metadata, footprint, *row.source_node_id);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() {
instance.instance.add_upstream_click_targets(click_targets);
for row in self.iter_ref() {
row.element.add_upstream_click_targets(click_targets);
}
}
fn contains_artboard(&self) -> bool {
self.instance_ref_iter().count() > 0
self.iter_ref().count() > 0
}
}
impl GraphicElementRendered for RasterDataTable<CPU> {
impl GraphicElementRendered for Table<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instance_ref_iter() {
let transform = *instance.transform;
let image = &instance.instance;
for row in self.iter_ref() {
let image = row.element;
let transform = *row.transform;
if image.data.is_empty() {
continue;
}
if render_params.to_canvas() {
let id = instance.source_node_id.map(|x| x.0).unwrap_or_else(|| {
let id = row.source_node_id.map(|x| x.0).unwrap_or_else(|| {
let mut state = DefaultHasher::new();
image.data().hash(&mut state);
state.finish()
@@ -973,13 +973,13 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
attributes.push("width", size.x.to_string());
attributes.push("height", size.y.to_string());
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. {
attributes.push("opacity", opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", row.alpha_blending.blend_mode.render());
}
},
|render| {
@@ -1013,12 +1013,12 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
attributes.push("transform", matrix);
}
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. {
attributes.push("opacity", opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", row.alpha_blending.blend_mode.render());
}
});
}
@@ -1029,13 +1029,13 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;
for instance in self.instance_ref_iter() {
let image = &instance.instance;
for row in self.iter_ref() {
let image = &row.element;
if image.data.is_empty() {
continue;
}
let alpha_blending = *instance.alpha_blending;
let alpha_blending = *row.alpha_blending;
let blend_mode = alpha_blending.blend_mode.to_peniko();
let opacity = alpha_blending.opacity(render_params.for_mask);
@@ -1051,7 +1051,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
}
let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
@@ -1068,7 +1068,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() {
if let Some(image) = self.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@@ -1081,7 +1081,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
impl GraphicElementRendered for RasterDataTable<GPU> {
impl GraphicElementRendered for Table<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
log::warn!("tried to render texture as an svg");
}
@@ -1090,8 +1090,8 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
for instance in self.instance_ref_iter() {
let blend_mode = *instance.alpha_blending;
for row in self.iter_ref() {
let blend_mode = *row.alpha_blending;
let mut layer = false;
if blend_mode != Default::default() {
if let Some(bounds) = self.bounding_box(transform, true) {
@@ -1105,13 +1105,13 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
let image = peniko::Image::new(
peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
peniko::ImageFormat::Rgba8,
instance.instance.data().width(),
instance.instance.data().height(),
row.element.data().width(),
row.element.data().height(),
)
.with_extend(peniko::Extend::Repeat);
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
context.resource_overrides.push((image, instance.instance.data().clone()));
context.resource_overrides.push((image, row.element.data().clone()));
if layer {
scene.pop_layer()
@@ -1126,7 +1126,7 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() {
if let Some(image) = self.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@@ -1166,8 +1166,8 @@ impl GraphicElementRendered for GraphicElement {
GraphicElement::VectorData(vector_data) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() {
metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id);
if let Some(vector_data) = vector_data.iter_ref().next() {
metadata.first_element_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform);
}
}
@@ -1175,7 +1175,7 @@ impl GraphicElementRendered for GraphicElement {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.instance_ref_iter().next() {
if let Some(image) = raster_frame.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@@ -1183,7 +1183,7 @@ impl GraphicElementRendered for GraphicElement {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.instance_ref_iter().next() {
if let Some(image) = raster_frame.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}

View File

@@ -4,9 +4,8 @@ use graph_craft::document::value::RenderOutput;
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::raster::color::Color;
use graphene_core::raster::*;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
use graphene_core::vector::VectorDataTable;
use graphene_core::{Artboard, GraphicGroupTable, concrete, generic};
use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::{Artboard, concrete, generic};
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
use graphene_core::{NodeIO, NodeIOTypes};
use graphene_core::{fn_type_fut, future};
@@ -16,6 +15,8 @@ use graphene_std::GraphicElement;
use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node};
@@ -30,25 +31,25 @@ use wgpu_executor::{WgpuSurface, WindowHandle};
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: VectorDataTable, to: VectorDataTable),
into_node!(from: VectorDataTable, to: GraphicElement),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: GraphicGroupTable, to: GraphicGroupTable),
into_node!(from: GraphicGroupTable, to: GraphicElement),
into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<CPU>),
// into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<SRGBA8>),
into_node!(from: RasterDataTable<CPU>, to: GraphicElement),
into_node!(from: RasterDataTable<GPU>, to: GraphicElement),
into_node!(from: RasterDataTable<CPU>, to: GraphicGroupTable),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
into_node!(from: Table<VectorData>, to: Table<VectorData>),
into_node!(from: Table<VectorData>, to: GraphicElement),
into_node!(from: Table<VectorData>, to: Table<GraphicElement>),
into_node!(from: Table<GraphicElement>, to: Table<GraphicElement>),
into_node!(from: Table<GraphicElement>, to: GraphicElement),
into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),
// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),
into_node!(from: Table<Raster<CPU>>, to: GraphicElement),
into_node!(from: Table<Raster<GPU>>, to: GraphicElement),
into_node!(from: Table<Raster<CPU>>, to: Table<GraphicElement>),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<GraphicElement>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::instances::Instances<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::table::Table<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),
@@ -77,9 +78,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GraphicElement>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
@@ -92,9 +93,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<GraphicElement>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<GraphicElement>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
@@ -117,9 +118,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
#[cfg(feature = "gpu")]

View File

@@ -969,7 +969,7 @@ mod tests {
fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment"));
let input = quote!(
fn levels<P: Pixel>(image: RasterDataTable<P>, #[implementations(f32, f64)] shadows: f64) -> RasterDataTable<P> {
fn levels<P: Pixel>(image: Table<Raster<P>>, #[implementations(f32, f64)] shadows: f64) -> Table<Raster<P>> {
// Implementation details...
}
);
@@ -993,10 +993,10 @@ mod tests {
where_clause: None,
input: Input {
pat_ident: pat_ident("image"),
ty: parse_quote!(RasterDataTable<P>),
ty: parse_quote!(Table<Raster<P>>),
implementations: Punctuated::new(),
},
output_type: parse_quote!(RasterDataTable<P>),
output_type: parse_quote!(Table<Raster<P>>),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("shadows"),
@@ -1099,7 +1099,7 @@ mod tests {
fn test_async_node() {
let attr = quote!(category("IO"));
let input = quote!(
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> RasterDataTable<CPU> {
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> Table<Raster<CPU>> {
// Implementation details...
}
);
@@ -1126,7 +1126,7 @@ mod tests {
ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(),
},
output_type: parse_quote!(RasterDataTable<CPU>),
output_type: parse_quote!(Table<Raster<CPU>>),
is_async: true,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("path"),
@@ -1248,7 +1248,7 @@ mod tests {
fn test_invalid_implementation_syntax() {
let attr = quote!(category("Test"));
let input = quote!(
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, RasterDataTable<CPU>))] input: impl Node<Footprint, Output = T>) -> T {
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, Table<Raster<CPU>>))] input: impl Node<Footprint, Output = T>) -> T {
// Implementation details...
}
);
@@ -1274,10 +1274,10 @@ mod tests {
#[implementations((), #tuples, Footprint)] footprint: F,
#[implementations(
() -> Color,
() -> RasterDataTable<CPU>,
() -> Table<Raster<CPU>>,
() -> GradientStops,
Footprint -> Color,
Footprint -> RasterDataTable<CPU>,
Footprint -> Table<Raster<CPU>>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,

View File

@@ -1,19 +1,19 @@
use crate::WgpuExecutor;
use graphene_core::color::SRGBA8;
use graphene_core::instances::Instance;
use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::table::{Table, TableRow};
use graphene_core::{Ctx, ExtractFootprint};
use wgpu::util::{DeviceExt, TextureDataOrder};
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
#[node_macro::node(category(""))]
pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: RasterDataTable<CPU>, executor: &'a WgpuExecutor) -> RasterDataTable<GPU> {
pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: Table<Raster<CPU>>, executor: &'a WgpuExecutor) -> Table<Raster<GPU>> {
let device = &executor.context.device;
let queue = &executor.context.queue;
let instances = input
.instance_ref_iter()
.map(|instance| {
let image = instance.instance;
let table = input
.iter_ref()
.map(|row| {
let image = row.element;
let rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
let texture = device.create_texture_with_data(
@@ -37,15 +37,16 @@ pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: Raste
bytemuck::cast_slice(rgba8_data.as_slice()),
);
Instance {
instance: Raster::new_gpu(texture.into()),
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *instance.source_node_id,
TableRow {
element: Raster::new_gpu(texture),
transform: *row.transform,
alpha_blending: *row.alpha_blending,
source_node_id: *row.source_node_id,
}
})
.collect();
queue.submit([]);
instances
table
}