Instance tables refactor part 2: move the transform and alpha_blending fields up a level (#2249)

* Fix domain data structure field plural naming

* Rename method one_item to one_instance

Rename method one_item to one_instance

* Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T>

Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T>

* Add transform and alpha_blending fields to Instances<T>

* Finish the refactor (Brush tool is broken though)

* Add test for brush node

* Fix brush node

* Fix default empty images being 1x1 instead of 0x0 as they should be

* Fix tests

* Fix path transform

* Add correct upgrading to move the transform/blending up a level

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers 2025-03-02 01:26:36 -08:00
parent 4ff2bdb04f
commit f1160e1ca6
33 changed files with 1099 additions and 984 deletions

View file

@ -28,7 +28,7 @@ use crate::node_graph_executor::NodeGraphExecutor;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_core::raster::image::ImageFrame; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::renderer::{ClickTarget, Quad};
@ -818,12 +818,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
let image_frame = ImageFrame { let layer = graph_modification_utils::new_image_layer(ImageFrameTable::new(ImageFrame { image }), layer_node_id, self.new_layer_parent(true), responses);
image,
transform: DAffine2::IDENTITY,
alpha_blending: Default::default(),
};
let layer = graph_modification_utils::new_image_layer(image_frame, layer_node_id, self.new_layer_parent(true), responses);
if let Some(name) = name { if let Some(name) = name {
responses.add(NodeGraphMessage::SetDisplayName { responses.add(NodeGraphMessage::SetDisplayName {

View file

@ -5,7 +5,7 @@ use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_core::raster::image::ImageFrame; use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
@ -68,7 +68,7 @@ pub enum GraphOperationMessage {
}, },
NewBitmapLayer { NewBitmapLayer {
id: NodeId, id: NodeId,
image_frame: ImageFrame<Color>, image_frame: ImageFrameTable<Color>,
parent: LayerNodeIdentifier, parent: LayerNodeIdentifier,
insert_index: usize, insert_index: usize,
}, },

View file

@ -8,7 +8,7 @@ use bezier_rs::Subpath;
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
@ -212,12 +212,11 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]); self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
} }
pub fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: LayerNodeIdentifier) { pub fn insert_image_data(&mut self, image_frame: ImageFrameTable<Color>, layer: LayerNodeIdentifier) {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template(); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image").expect("Image node does not exist").node_template_input_override([ let image = resolve_document_node_type("Image")
Some(NodeInput::value(TaggedValue::None, false)), .expect("Image node does not exist")
Some(NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::new(image_frame)), false)), .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))]);
]);
let image_id = NodeId::new(); let image_id = NodeId::new();
self.network_interface.insert_node(image_id, image, &[]); self.network_interface.insert_node(image_id, image, &[]);

View file

@ -571,7 +571,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -809,8 +809,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -832,8 +832,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
], ],
..Default::default() ..Default::default()
@ -856,10 +856,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -929,7 +929,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1011,8 +1011,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
], ],
@ -1061,7 +1061,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@ -1080,7 +1080,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@ -1112,7 +1112,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), false)], inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), false)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1825,7 +1825,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1881,7 +1881,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true),
], ],
..Default::default() ..Default::default()
@ -1923,7 +1923,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"), implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false), NodeInput::value(TaggedValue::Bool(false), false),
@ -1955,7 +1955,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// document_node: DocumentNode { // document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), // implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
// inputs: vec![ // inputs: vec![
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), // NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false), // NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ], // ],
// ..Default::default() // ..Default::default()
@ -2799,7 +2799,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true),
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
NodeInput::value(TaggedValue::ImaginateController(Default::default()), false), NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index

View file

@ -457,7 +457,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} }
}; };
const REPLACEMENTS: [(&str, &str); 35] = [ const REPLACEMENTS: [(&str, &str); 34] = [
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
@ -488,7 +488,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"), ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"),
("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"), ("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
( (
"graphene_core::vector::generator_nodes::RegularPolygonGenerator", "graphene_core::vector::generator_nodes::RegularPolygonGenerator",

View file

@ -6,7 +6,7 @@ use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::raster::image::ImageFrame; use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::style::Gradient; use graphene_core::vector::style::Gradient;
@ -207,7 +207,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
} }
/// Create a new bitmap layer. /// Create a new bitmap layer.
pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_image_layer(image_frame: ImageFrameTable<Color>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;
responses.add(GraphOperationMessage::NewBitmapLayer { responses.add(GraphOperationMessage::NewBitmapLayer {
id, id,

View file

@ -298,9 +298,9 @@ impl NodeRuntime {
} }
// Insert the vector modify if we are dealing with vector data // Insert the vector modify if we are dealing with vector data
else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() { else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); self.vector_modify.insert(parent_network_node_id, record.output.one_instance().instance.clone());
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() { } else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); self.vector_modify.insert(parent_network_node_id, record.output.one_instance().instance.clone());
} }
} }
} }

View file

@ -2,7 +2,6 @@ use crate::instances::Instances;
use crate::text::FontCache; use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::ViewMode; use crate::vector::style::ViewMode;
use crate::AlphaBlending;
use dyn_any::{DynAny, StaticType, StaticTypeSized}; use dyn_any::{DynAny, StaticType, StaticTypeSized};
@ -73,36 +72,20 @@ pub struct TextureFrame {
pub texture: Arc<wgpu::Texture>, pub texture: Arc<wgpu::Texture>,
#[cfg(not(feature = "wgpu"))] #[cfg(not(feature = "wgpu"))]
pub texture: (), pub texture: (),
pub transform: DAffine2,
pub alpha_blend: AlphaBlending,
} }
impl Hash for TextureFrame { impl Hash for TextureFrame {
#[cfg(feature = "wgpu")]
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
#[cfg(feature = "wgpu")]
self.texture.hash(state); self.texture.hash(state);
} }
#[cfg(not(feature = "wgpu"))]
fn hash<H: Hasher>(&self, _state: &mut H) {}
} }
impl PartialEq for TextureFrame { impl PartialEq for TextureFrame {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
#[cfg(feature = "wgpu")] self.texture == other.texture
return self.transform.eq(&other.transform) && self.texture == other.texture;
#[cfg(not(feature = "wgpu"))]
self.transform.eq(&other.transform)
}
}
impl Transform for TextureFrame {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for TextureFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
} }
} }

View file

@ -45,15 +45,30 @@ impl AlphaBlending {
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> { pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldGraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum EitherFormat { enum EitherFormat {
GraphicGroup(GraphicGroup), GraphicGroup(GraphicGroup),
OldGraphicGroup(OldGraphicGroup),
GraphicGroupTable(GraphicGroupTable), GraphicGroupTable(GraphicGroupTable),
} }
Ok(match EitherFormat::deserialize(deserializer)? { Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group), EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group),
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::new(GraphicGroup { elements: old.elements });
*graphic_group_table.one_instance_mut().transform = old.transform;
*graphic_group_table.one_instance_mut().alpha_blending = old.alpha_blending;
graphic_group_table
}
EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table, EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table,
}) })
} }
@ -65,15 +80,11 @@ pub type GraphicGroupTable = Instances<GraphicGroup>;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicGroup { pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>, elements: Vec<(GraphicElement, Option<NodeId>)>,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
} }
impl core::hash::Hash for GraphicGroup { impl core::hash::Hash for GraphicGroup {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state));
self.elements.hash(state); self.elements.hash(state);
self.alpha_blending.hash(state);
} }
} }
@ -81,8 +92,6 @@ impl GraphicGroup {
pub fn new(elements: Vec<GraphicElement>) -> Self { pub fn new(elements: Vec<GraphicElement>) -> Self {
Self { Self {
elements: elements.into_iter().map(|element| (element, None)).collect(), elements: elements.into_iter().map(|element| (element, None)).collect(),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
} }
} }
} }
@ -214,29 +223,6 @@ impl serde::Serialize for RasterFrame {
} }
} }
impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 {
match self {
RasterFrame::ImageFrame(frame) => frame.transform(),
RasterFrame::TextureFrame(frame) => frame.transform(),
}
}
fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 {
match self {
RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot),
RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot),
}
}
}
impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
RasterFrame::ImageFrame(frame) => frame.transform_mut(),
RasterFrame::TextureFrame(frame) => frame.transform_mut(),
}
}
}
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported. /// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -281,20 +267,20 @@ impl ArtboardGroup {
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable { async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
let mut stack = stack.one_item().clone(); let mut stack = stack;
if stack.transform.matrix2.determinant() != 0. { if stack.transform().matrix2.determinant() != 0. {
*element.transform_mut() = stack.transform.inverse() * element.transform(); *element.transform_mut() = stack.transform().inverse() * element.transform();
} else { } else {
stack.clear(); stack.one_instance_mut().instance.clear();
stack.transform = DAffine2::IDENTITY; *stack.transform_mut() = DAffine2::IDENTITY;
} }
// Get the penultimate element of the node path, or None if the path is too short // Get the penultimate element of the node path, or None if the path is too short
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push((element, encapsulating_node_id)); stack.one_instance_mut().instance.push((element, encapsulating_node_id));
GraphicGroupTable::new(stack) stack
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
@ -327,40 +313,38 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
let nested_group = group.one_item().clone(); fn flatten_group(result_group: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool) {
let mut flat_group = GraphicGroup::default();
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
let mut collection_group = GraphicGroup::default(); let mut collection_group = GraphicGroup::default();
for (element, reference) in current_group.elements { let current_group_elements = current_group_table.one_instance().instance.elements.clone();
if let GraphicElement::GraphicGroup(nested_group) = element {
let nested_group = nested_group.one_item();
let mut nested_group = nested_group.clone();
*nested_group.transform_mut() = nested_group.transform() * current_group.transform; for (element, reference) in current_group_elements {
if let GraphicElement::GraphicGroup(mut nested_group_table) = element {
*nested_group_table.transform_mut() = nested_group_table.transform() * current_group_table.transform();
let mut sub_group = GraphicGroup::default(); let mut sub_group_table = GraphicGroupTable::default();
if fully_flatten { if fully_flatten {
flatten_group(&mut sub_group, nested_group, fully_flatten); flatten_group(&mut sub_group_table, nested_group_table, fully_flatten);
} else { } else {
for (collection_element, _) in &mut nested_group.elements { let nested_group_table_transform = nested_group_table.transform();
*collection_element.transform_mut() = nested_group.transform * collection_element.transform(); for (collection_element, _) in &mut nested_group_table.one_instance_mut().instance.elements {
*collection_element.transform_mut() = nested_group_table_transform * collection_element.transform();
} }
sub_group = nested_group; sub_group_table = nested_group_table;
} }
collection_group.append(&mut sub_group.elements);
collection_group.append(&mut sub_group_table.one_instance_mut().instance.elements);
} else { } else {
collection_group.push((element, reference)); collection_group.push((element, reference));
} }
} }
result_group.append(&mut collection_group.elements); result_group.one_instance_mut().instance.append(&mut collection_group.elements);
} }
flatten_group(&mut flat_group, nested_group, fully_flatten); let mut flat_group = GraphicGroupTable::default();
flatten_group(&mut flat_group, group, fully_flatten);
GraphicGroupTable::new(flat_group) flat_group
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
@ -487,8 +471,6 @@ where
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self { Self {
elements: (vec![(value.into(), None)]), elements: (vec![(value.into(), None)]),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
} }
} }
} }

View file

@ -9,7 +9,7 @@ use crate::transform::{Footprint, Transform};
use crate::uuid::{generate_uuid, NodeId}; use crate::uuid::{generate_uuid, NodeId};
use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::{PointId, VectorDataTable}; use crate::vector::{PointId, VectorDataTable};
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame}; use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroupTable, RasterFrame};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use dyn_any::DynAny; use dyn_any::DynAny;
@ -291,14 +291,7 @@ pub trait GraphicElementRendered {
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {} fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene { fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_context: &mut RenderContext) {}
let mut scene = vello::Scene::new();
self.render_to_vello(&mut scene, transform, context);
scene
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {}
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
false false
@ -311,40 +304,54 @@ pub trait GraphicElementRendered {
} }
} }
impl GraphicElementRendered for GraphicGroup { impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
render.parent_tag( for instance in self.instances() {
"g", render.parent_tag(
|attributes| { "g",
let matrix = format_transform_matrix(self.transform); |attributes| {
if !matrix.is_empty() { let matrix = format_transform_matrix(instance.transform());
attributes.push("transform", matrix); if !matrix.is_empty() {
} attributes.push("transform", matrix);
}
if self.alpha_blending.opacity < 1. { if instance.alpha_blending.opacity < 1. {
attributes.push("opacity", self.alpha_blending.opacity.to_string()); attributes.push("opacity", instance.alpha_blending.opacity.to_string());
} }
if self.alpha_blending.blend_mode != BlendMode::default() { if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.alpha_blending.blend_mode.render()); attributes.push("style", instance.alpha_blending.blend_mode.render());
} }
}, },
|render| { |render| {
for (element, _) in self.iter() { for (element, _) in instance.instance.iter() {
element.render_svg(render, render_params); element.render_svg(render, render_params);
} }
}, },
); );
}
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.iter().filter_map(|(element, _)| element.bounding_box(transform * self.transform)).reduce(Quad::combine_bounds) self.instances()
.flat_map(|instance| {
instance
.instance
.iter()
.filter_map(|(element, _)| element.bounding_box(transform * instance.transform()))
.reduce(Quad::combine_bounds)
})
.reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
footprint.transform *= self.transform; let instance_transform = self.transform();
let instance = self.one_instance().instance;
for (element, element_id) in self.elements.iter() { let mut footprint = footprint;
footprint.transform *= instance_transform;
for (element, element_id) in instance.elements.iter() {
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
element.collect_metadata(metadata, footprint, Some(*element_id)); element.collect_metadata(metadata, footprint, Some(*element_id));
} }
@ -352,104 +359,79 @@ impl GraphicElementRendered for GraphicGroup {
if let Some(graphic_group_id) = element_id { if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new(); let mut all_upstream_click_targets = Vec::new();
self.add_upstream_click_targets(&mut all_upstream_click_targets);
for (element, _) in instance.elements.iter() {
let mut new_click_targets = Vec::new();
element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(element.transform())
}
all_upstream_click_targets.extend(new_click_targets);
}
metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets); metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets);
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for (element, _) in self.elements.iter() { for instance in self.instances() {
let mut new_click_targets = Vec::new(); for (element, _) in instance.instance.elements.iter() {
let mut new_click_targets = Vec::new();
element.add_upstream_click_targets(&mut new_click_targets); element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() { for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(element.transform()) click_target.apply_transform(element.transform())
}
click_targets.extend(new_click_targets);
}
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
for instance in self.instances() {
let transform = transform * instance.transform();
let alpha_blending = *instance.alpha_blending;
let blending = vello::peniko::BlendMode::new(alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
let mut layer = false;
if alpha_blending.opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
if let Some(bounds) = instance.instance.iter().filter_map(|(element, _)| element.bounding_box(transform)).reduce(Quad::combine_bounds) {
scene.push_layer(
blending,
alpha_blending.opacity,
kurbo::Affine::IDENTITY,
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
);
layer = true;
}
} }
click_targets.extend(new_click_targets); for (element, _) in instance.instance.iter() {
} element.render_to_vello(scene, transform, context);
} }
#[cfg(feature = "vello")] if layer {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { scene.pop_layer();
let child_transform = transform * self.transform; }
let Some(bounds) = self.bounding_box(transform) else { return };
let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
let mut layer = false;
if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() {
layer = true;
scene.push_layer(
blending,
self.alpha_blending.opacity,
kurbo::Affine::IDENTITY,
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
);
}
for (element, _) in self.iter() {
element.render_to_vello(scene, child_transform, context);
}
if layer {
scene.pop_layer();
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
self.iter().any(|(element, _)| element.contains_artboard()) self.instances().any(|instance| instance.instance.iter().any(|(element, _)| element.contains_artboard()))
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for (element, node_id) in self.elements.iter_mut() {
element.new_ids_from_hash(*node_id);
}
}
fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone()))
}
}
impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
instance.render_svg(render, render_params);
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
instance.collect_metadata(metadata, footprint, element_id);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
instance.add_upstream_click_targets(click_targets);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
for instance in self.instances() {
instance.render_to_vello(scene, transform, context);
}
}
fn contains_artboard(&self) -> bool {
self.instances().any(|instance| instance.contains_artboard())
} }
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instances_mut() { for instance in self.instances_mut() {
instance.new_ids_from_hash(None); for (element, node_id) in instance.instance.elements.iter_mut() {
element.new_ids_from_hash(*node_id);
}
} }
} }
@ -461,16 +443,21 @@ impl GraphicElementRendered for GraphicGroupTable {
impl GraphicElementRendered for VectorDataTable { impl GraphicElementRendered for VectorDataTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() { for instance in self.instances() {
let multiplied_transform = render.transform * instance.transform; let multiplied_transform = render.transform * instance.transform();
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let set_stroke_transform = instance
let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform); .instance
.style
.stroke()
.map(|stroke| stroke.transform)
.filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform());
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.bounding_box().unwrap_or_default(); let layer_bounds = instance.instance.bounding_box().unwrap_or_default();
let transformed_bounds = instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default(); let transformed_bounds = instance.instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let mut path = String::new(); let mut path = String::new();
for subpath in instance.stroke_bezier_paths() { for subpath in instance.instance.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
} }
@ -481,6 +468,7 @@ impl GraphicElementRendered for VectorDataTable {
let defs = &mut attributes.0.svg_defs; let defs = &mut attributes.0.svg_defs;
let fill_and_stroke = instance let fill_and_stroke = instance
.instance
.style .style
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds); .render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke); attributes.push_val(fill_and_stroke);
@ -499,22 +487,23 @@ impl GraphicElementRendered for VectorDataTable {
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances() self.instances()
.flat_map(|instance| { .flat_map(|instance| {
let stroke_width = instance.style.stroke().map(|s| s.weight()).unwrap_or_default(); let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
let miter_limit = instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.); let miter_limit = instance.instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
let scale = transform.decompose_scale(); let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of line caps // We use the full line width here to account for different styles of line caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit); let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
instance.bounding_box_with_transform(transform * instance.transform).map(|[a, b]| [a - offset, b + offset]) instance.instance.bounding_box_with_transform(transform * instance.transform()).map(|[a, b]| [a - offset, b + offset])
}) })
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item(); let instance_transform = self.transform();
let instance = self.one_instance().instance;
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
@ -536,15 +525,15 @@ impl GraphicElementRendered for VectorDataTable {
} }
if let Some(upstream_graphic_group) = &instance.upstream_graphic_group { if let Some(upstream_graphic_group) = &instance.upstream_graphic_group {
footprint.transform *= instance.transform; footprint.transform *= instance_transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None); upstream_graphic_group.collect_metadata(metadata, footprint, None);
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() { for instance in self.instances() {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None; let filled = instance.instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| { let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled { if filled {
subpath.set_closed(true); subpath.set_closed(true);
@ -552,7 +541,7 @@ impl GraphicElementRendered for VectorDataTable {
subpath subpath
}; };
click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width))); click_targets.extend(instance.instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
} }
} }
@ -564,12 +553,17 @@ impl GraphicElementRendered for VectorDataTable {
for instance in self.instances() { for instance in self.instances() {
let mut layer = false; let mut layer = false;
let multiplied_transform = parent_transform * instance.transform; let multiplied_transform = parent_transform * instance.transform();
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let set_stroke_transform = instance
.instance
.style
.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 = set_stroke_transform.unwrap_or(multiplied_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.bounding_box().unwrap_or_default(); let layer_bounds = instance.instance.bounding_box().unwrap_or_default();
if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() { if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
layer = true; layer = true;
@ -583,11 +577,11 @@ impl GraphicElementRendered for VectorDataTable {
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new(); let mut path = kurbo::BezPath::new();
for subpath in instance.stroke_bezier_paths() { for subpath in instance.instance.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path); subpath.to_vello_path(applied_stroke_transform, &mut path);
} }
match instance.style.fill() { match instance.instance.style.fill() {
Fill::Solid(color) => { Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])); 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); scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
@ -601,7 +595,7 @@ impl GraphicElementRendered for VectorDataTable {
}); });
} }
// Compute bounding box of the shape to determine the gradient start and end points // Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.nonzero_bounding_box(); let bounds = instance.instance.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default(); let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
@ -638,7 +632,7 @@ impl GraphicElementRendered for VectorDataTable {
Fill::None => (), Fill::None => (),
}; };
if let Some(stroke) = instance.style.stroke() { if let Some(stroke) = instance.instance.style.stroke() {
let color = match stroke.color { let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]), Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT, None => peniko::Color::TRANSPARENT,
@ -676,12 +670,12 @@ impl GraphicElementRendered for VectorDataTable {
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
for instance in self.instances_mut() { for instance in self.instances_mut() {
instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
} }
} }
fn to_graphic_element(&self) -> GraphicElement { fn to_graphic_element(&self) -> GraphicElement {
let instance = self.one_item(); let instance = self.one_instance().instance;
GraphicElement::VectorData(VectorDataTable::new(instance.clone())) GraphicElement::VectorData(VectorDataTable::new(instance.clone()))
} }
@ -833,11 +827,11 @@ impl GraphicElementRendered for ArtboardGroup {
impl GraphicElementRendered for ImageFrameTable<Color> { impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() { for instance in self.instances() {
let transform = instance.transform * render.transform; let transform = instance.transform() * render.transform;
match render_params.image_render_mode { match render_params.image_render_mode {
ImageRenderMode::Base64 => { ImageRenderMode::Base64 => {
let image = &instance.image; let image = &instance.instance.image;
if image.data.is_empty() { if image.data.is_empty() {
return; return;
} }
@ -874,20 +868,20 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances() self.instances()
.flat_map(|instance| { .flat_map(|instance| {
let transform = transform * instance.transform; let transform = transform * instance.transform();
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) (transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
}) })
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item(); let instance_transform = self.transform();
let Some(element_id) = element_id else { return }; let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]); metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, instance.transform)); metadata.footprints.insert(element_id, (footprint, instance_transform));
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@ -900,12 +894,12 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
use vello::peniko; use vello::peniko;
for instance in self.instances() { for instance in self.instances() {
let image = &instance.image; let image = &instance.instance.image;
if image.data.is_empty() { if image.data.is_empty() {
return; return;
} }
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let transform = transform * instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); let transform = transform * instance.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
} }
@ -923,8 +917,8 @@ impl GraphicElementRendered for RasterFrame {
RasterFrame::TextureFrame(_) => return, RasterFrame::TextureFrame(_) => return,
}; };
for image in image.instances() { for instance in image.instances() {
let (image, blending) = (&image.image, image.alpha_blending); let (image, blending) = (&instance.instance.image, instance.alpha_blending);
if image.data.is_empty() { if image.data.is_empty() {
return; return;
} }
@ -999,25 +993,26 @@ impl GraphicElementRendered for RasterFrame {
match self { match self {
RasterFrame::ImageFrame(image_frame) => { RasterFrame::ImageFrame(image_frame) => {
for image_frame in image_frame.instances() { for instance in image_frame.instances() {
let image = &image_frame.image; let image = &instance.instance.image;
if image.data.is_empty() { if image.data.is_empty() {
return; return;
} }
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
render_stuff(image, image_frame.alpha_blending); render_stuff(image, *instance.alpha_blending);
} }
} }
RasterFrame::TextureFrame(texture) => { RasterFrame::TextureFrame(texture) => {
for texture in texture.instances() { for instance in texture.instances() {
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat); let image =
vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat);
let id = image.data.id(); let id = image.data.id();
context.resource_overrides.insert(id, texture.texture.clone()); context.resource_overrides.insert(id, instance.instance.texture.clone());
render_stuff(image, texture.alpha_blend); render_stuff(image, *instance.alpha_blending);
} }
} }
} }

View file

@ -1,8 +1,12 @@
use crate::vector::InstanceId; use crate::application_io::{TextureFrame, TextureFrameTable};
use crate::GraphicElement; use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::Pixel;
use crate::transform::{Transform, TransformMut};
use crate::vector::{InstanceId, VectorData, VectorDataTable};
use crate::{AlphaBlending, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
use dyn_any::StaticType; use dyn_any::StaticType;
use glam::{DAffine2, DVec2};
use std::hash::Hash; use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@ -11,54 +15,73 @@ where
T: Into<GraphicElement> + StaticType + 'static, T: Into<GraphicElement> + StaticType + 'static,
{ {
id: Vec<InstanceId>, id: Vec<InstanceId>,
instances: Vec<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>,
} }
impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> { impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
pub fn new(instance: T) -> Self { pub fn new(instance: T) -> Self {
Self { Self {
id: vec![InstanceId::generate()], id: vec![InstanceId::generate()],
instances: vec![instance], instance: vec![instance],
transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()],
} }
} }
pub fn one_item(&self) -> &T { pub fn one_instance(&self) -> Instance<T> {
self.instances.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item)", self.instances.len())) Instance {
id: self.id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
}
} }
pub fn one_item_mut(&mut self) -> &mut T { pub fn one_instance_mut(&mut self) -> InstanceMut<T> {
let length = self.instances.len(); let length = self.instance.len();
self.instances.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item_mut)", length))
InstanceMut {
id: self.id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
}
} }
pub fn instances(&self) -> impl Iterator<Item = &T> { pub fn instances(&self) -> impl Iterator<Item = Instance<T>> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len()); assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instance.len());
self.instances.iter() self.id
.iter()
.zip(self.instance.iter())
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.map(|(((id, instance), transform), alpha_blending)| Instance {
id,
instance,
transform,
alpha_blending,
})
} }
pub fn instances_mut(&mut self) -> impl Iterator<Item = &mut T> { pub fn instances_mut(&mut self) -> impl Iterator<Item = InstanceMut<T>> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instances.len()); assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instance.len());
self.instances.iter_mut() self.id
.iter_mut()
.zip(self.instance.iter_mut())
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.map(|(((id, instance), transform), alpha_blending)| InstanceMut {
id,
instance,
transform,
alpha_blending,
})
} }
// pub fn id(&self) -> impl Iterator<Item = InstanceId> + '_ {
// self.id.iter().copied()
// }
// pub fn push(&mut self, id: InstanceId, instance: T) {
// self.id.push(id);
// self.instances.push(instance);
// }
// pub fn replace_all(&mut self, id: InstanceId, instance: T) {
// let mut instance = instance;
// for (old_id, old_instance) in self.id.iter_mut().zip(self.instances.iter_mut()) {
// let mut new_id = id;
// std::mem::swap(old_id, &mut new_id);
// std::mem::swap(&mut instance, old_instance);
// }
// }
} }
impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> { impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> {
@ -70,7 +93,7 @@ impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default fo
impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> { impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state); self.id.hash(state);
for instance in &self.instances { for instance in &self.instance {
instance.hash(state); instance.hash(state);
} }
} }
@ -78,10 +101,208 @@ impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for
impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> { impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.instances.len() == other.instances.len() && { self.instances.iter().zip(other.instances.iter()).all(|(a, b)| a == b) } self.id == other.id && self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
} }
} }
unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> { unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> {
type Static = Instances<T>; type Static = Instances<T>;
} }
fn one_daffine2_default() -> Vec<DAffine2> {
vec![DAffine2::IDENTITY]
}
fn one_alpha_blending_default() -> Vec<AlphaBlending> {
vec![AlphaBlending::default()]
}
#[derive(Copy, Clone, Debug)]
pub struct Instance<'a, T> {
pub id: &'a InstanceId,
pub instance: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub id: &'a mut InstanceId,
pub instance: &'a mut T,
pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending,
}
// GRAPHIC ELEMENT
impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 {
match self {
GraphicElement::GraphicGroup(group) => group.transform(),
GraphicElement::VectorData(vector_data) => vector_data.transform(),
GraphicElement::RasterFrame(frame) => frame.transform(),
}
}
}
impl TransformMut for GraphicElement {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
GraphicElement::GraphicGroup(group) => group.transform_mut(),
GraphicElement::VectorData(vector_data) => vector_data.transform_mut(),
GraphicElement::RasterFrame(frame) => frame.transform_mut(),
}
}
}
// GRAPHIC GROUP
impl Transform for Instance<'_, GraphicGroup> {
fn transform(&self) -> DAffine2 {
*self.transform
}
}
impl Transform for InstanceMut<'_, GraphicGroup> {
fn transform(&self) -> DAffine2 {
*self.transform
}
}
impl TransformMut for InstanceMut<'_, GraphicGroup> {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform
}
}
// GRAPHIC GROUP TABLE
impl Transform for GraphicGroupTable {
fn transform(&self) -> DAffine2 {
self.one_instance().transform()
}
}
impl TransformMut for GraphicGroupTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
}
}
// TEXTURE FRAME
impl Transform for Instance<'_, TextureFrame> {
fn transform(&self) -> DAffine2 {
*self.transform
}
}
impl Transform for InstanceMut<'_, TextureFrame> {
fn transform(&self) -> DAffine2 {
*self.transform
}
}
impl TransformMut for InstanceMut<'_, TextureFrame> {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform
}
}
// TEXTURE FRAME TABLE
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
self.one_instance().transform()
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
}
}
// IMAGE FRAME
impl<P: Pixel> Transform for Instance<'_, ImageFrame<P>> {
fn transform(&self) -> DAffine2 {
*self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.transform.transform_point2(pivot)
}
}
impl<P: Pixel> Transform for InstanceMut<'_, ImageFrame<P>> {
fn transform(&self) -> DAffine2 {
*self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.transform.transform_point2(pivot)
}
}
impl<P: Pixel> TransformMut for InstanceMut<'_, ImageFrame<P>> {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform
}
}
// IMAGE FRAME TABLE
impl<P: Pixel> Transform for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform(&self) -> DAffine2 {
self.one_instance().transform()
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
}
}
// VECTOR DATA
impl Transform for Instance<'_, VectorData> {
fn transform(&self) -> DAffine2 {
*self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.transform.transform_point2(self.instance.layerspace_pivot(pivot))
}
}
impl Transform for InstanceMut<'_, VectorData> {
fn transform(&self) -> DAffine2 {
*self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.transform.transform_point2(self.instance.layerspace_pivot(pivot))
}
}
impl TransformMut for InstanceMut<'_, VectorData> {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform
}
}
// VECTOR DATA TABLE
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
self.one_instance().transform()
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
}
}
// RASTER FRAME
impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform(),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(),
}
}
}
impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")),
}
}
}

View file

@ -129,7 +129,7 @@ impl<T: serde::Serialize + for<'a> serde::Deserialize<'a>> Serde for T {}
impl<T> Serde for T {} impl<T> Serde for T {}
// TODO: Come up with a better name for this trait // TODO: Come up with a better name for this trait
pub trait Pixel: Clone + Pod + Zeroable { pub trait Pixel: Clone + Pod + Zeroable + Default {
#[cfg(not(target_arch = "spirv"))] #[cfg(not(target_arch = "spirv"))]
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
bytemuck::bytes_of(self).to_vec() bytemuck::bytes_of(self).to_vec()

View file

@ -605,17 +605,15 @@ impl Blend<Color> for ImageFrameTable<Color> {
let mut result = self.clone(); let mut result = self.clone();
for (over, under) in result.instances_mut().zip(under.instances()) { for (over, under) in result.instances_mut().zip(under.instances()) {
let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); let data = over.instance.image.data.iter().zip(under.instance.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over = ImageFrame { *over.instance = ImageFrame {
image: super::Image { image: super::Image {
data, data,
width: over.image.width, width: over.instance.image.width,
height: over.image.height, height: over.instance.image.height,
base64_string: None, base64_string: None,
}, },
transform: over.transform,
alpha_blending: over.alpha_blending,
}; };
} }
@ -744,7 +742,7 @@ where
{ {
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) { fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
for instance in self.instances_mut() { for instance in self.instances_mut() {
for c in instance.image.data.iter_mut() { for c in instance.instance.image.data.iter_mut() {
*c = map_fn(c); *c = map_fn(c);
} }
} }
@ -1582,10 +1580,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn color_overlay_multiply() { async fn color_overlay_multiply() {
let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4); let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);
let image = ImageFrame { let image = ImageFrame { image: Image::new(1, 1, image_color) };
image: Image::new(1, 1, image_color),
..Default::default()
};
// Color { red: 0., green: 1., blue: 0., alpha: 1. } // Color { red: 0., green: 1., blue: 0., alpha: 1. }
let overlay_color = Color::GREEN; let overlay_color = Color::GREEN;
@ -1594,7 +1589,7 @@ mod test {
let opacity = 100_f64; let opacity = 100_f64;
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity); let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
let result = result.one_item(); let result = result.one_instance().instance;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) // 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.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View file

@ -1,16 +1,15 @@
use core::hash::Hash; use crate::graphene_core::raster::image::ImageFrameTable;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use dyn_any::DynAny;
use crate::raster::image::ImageFrame;
use crate::raster::Image; use crate::raster::Image;
use crate::vector::brush_stroke::BrushStroke; use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle; use crate::vector::brush_stroke::BrushStyle;
use crate::Color; use crate::Color;
use core::hash::Hash;
use dyn_any::DynAny;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
#[derive(Clone, Debug, PartialEq, DynAny, Default)] #[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct BrushCacheImpl { struct BrushCacheImpl {
@ -18,9 +17,12 @@ struct BrushCacheImpl {
prev_input: Vec<BrushStroke>, prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background. // The strokes that have been fully processed and blended into the background.
background: ImageFrame<Color>, #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
blended_image: ImageFrame<Color>, background: ImageFrameTable<Color>,
last_stroke_texture: ImageFrame<Color>, #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
blended_image: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
last_stroke_texture: ImageFrameTable<Color>,
// A cache for brush textures. // A cache for brush textures.
#[cfg_attr(feature = "serde", serde(skip))] #[cfg_attr(feature = "serde", serde(skip))]
@ -28,9 +30,9 @@ struct BrushCacheImpl {
} }
impl BrushCacheImpl { impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan { fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation. // Do background invalidation.
if background.transform != self.background.transform || background.image != self.background.image { if background.one_instance().instance.image != self.background.one_instance().instance.image {
self.background = background.clone(); self.background = background.clone();
return BrushPlan { return BrushPlan {
strokes: input.to_vec(), strokes: input.to_vec(),
@ -55,7 +57,7 @@ impl BrushCacheImpl {
background = core::mem::take(&mut self.blended_image); background = core::mem::take(&mut self.blended_image);
// Check if the first non-blended stroke is an extension of the last one. // Check if the first non-blended stroke is an extension of the last one.
let mut first_stroke_texture = ImageFrame::default(); let mut first_stroke_texture = ImageFrameTable::empty();
let mut first_stroke_point_skip = 0; let mut first_stroke_point_skip = 0;
let strokes = input[num_blended_strokes..].to_vec(); let strokes = input[num_blended_strokes..].to_vec();
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes { if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
@ -79,7 +81,7 @@ impl BrushCacheImpl {
} }
} }
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) { pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
self.prev_input = input; self.prev_input = input;
self.blended_image = blended_image; self.blended_image = blended_image;
self.last_stroke_texture = last_stroke_texture; self.last_stroke_texture = last_stroke_texture;
@ -94,8 +96,8 @@ impl Hash for BrushCacheImpl {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct BrushPlan { pub struct BrushPlan {
pub strokes: Vec<BrushStroke>, pub strokes: Vec<BrushStroke>,
pub background: ImageFrame<Color>, pub background: ImageFrameTable<Color>,
pub first_stroke_texture: ImageFrame<Color>, pub first_stroke_texture: ImageFrameTable<Color>,
pub first_stroke_point_skip: usize, pub first_stroke_point_skip: usize,
} }
@ -159,12 +161,12 @@ impl BrushCache {
} }
} }
pub fn compute_brush_plan(&self, background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan { pub fn compute_brush_plan(&self, background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
inner.compute_brush_plan(background, input) inner.compute_brush_plan(background, input)
} }
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) { pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture) inner.cache_results(input, blended_image, last_stroke_texture)
} }

View file

@ -1,6 +1,6 @@
use super::discrete_srgb::float_to_srgb_u8; use super::discrete_srgb::float_to_srgb_u8;
use super::Color; use super::Color;
use crate::instances::Instances; use crate::{instances::Instances, transform::TransformMut};
use crate::{AlphaBlending, GraphicElement}; use crate::{AlphaBlending, GraphicElement};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
@ -110,15 +110,6 @@ impl<P: Hash + Pixel> Hash for Image<P> {
} }
impl<P: Pixel> Image<P> { impl<P: Pixel> Image<P> {
pub const fn empty() -> Self {
Self {
width: 0,
height: 0,
data: Vec::new(),
base64_string: None,
}
}
pub fn new(width: u32, height: u32, color: P) -> Self { pub fn new(width: u32, height: u32, color: P) -> Self {
Self { Self {
width, width,
@ -221,47 +212,50 @@ impl<P: Pixel> IntoIterator for Image<P> {
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> { pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldImageFrame<P: Pixel> {
image: Image<P>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum EitherFormat { enum EitherFormat {
ImageFrame(ImageFrame<Color>), ImageFrame(ImageFrame<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrameTable(ImageFrameTable<Color>), ImageFrameTable(ImageFrameTable<Color>),
} }
Ok(match EitherFormat::deserialize(deserializer)? { Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame), EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame),
EitherFormat::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
let mut image_frame_table = ImageFrameTable::new(ImageFrame { image });
*image_frame_table.one_instance_mut().transform = transform;
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
image_frame_table
}
EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table, EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table,
}) })
} }
pub type ImageFrameTable<P> = Instances<ImageFrame<P>>; pub type ImageFrameTable<P> = Instances<ImageFrame<P>>;
#[derive(Clone, Debug, PartialEq, specta::Type)] /// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
impl ImageFrameTable<Color> {
pub fn empty() -> Self {
let mut result = Self::new(ImageFrame::default());
*result.transform_mut() = DAffine2::ZERO;
result
}
}
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> { pub struct ImageFrame<P: Pixel> {
pub image: Image<P>, pub image: Image<P>,
// The transform that maps image space to layer space.
//
// Image space is unitless [0, 1] for both axes, with x axis positive
// going right and y axis positive going down, with the origin lying at
// the topleft of the image and (1, 1) lying at the bottom right of the image.
//
// Layer space has pixels as its units for both axes, with the x axis
// positive going right and y axis positive going down, with the origin
// being an unspecified quantity.
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
}
impl<P: Pixel> Default for ImageFrame<P> {
fn default() -> Self {
Self {
image: Image::empty(),
alpha_blending: AlphaBlending::new(),
// Different from DAffine2::default() which is IDENTITY
transform: DAffine2::ZERO,
}
}
} }
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> { impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
@ -271,7 +265,6 @@ impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
#[inline(always)] #[inline(always)]
fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> { fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> {
let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64); let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64);
let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos);
if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y { if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y {
return None; return None;
} }
@ -289,7 +282,11 @@ where
// TODO: Improve sampling logic // TODO: Improve sampling logic
#[inline(always)] #[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> { fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image = self.one_item(); let image_transform = self.one_instance().transform;
let image = self.one_instance().instance;
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
Sample::sample(image, pos, area) Sample::sample(image, pos, area)
} }
@ -319,19 +316,19 @@ where
type Pixel = P; type Pixel = P;
fn width(&self) -> u32 { fn width(&self) -> u32 {
let image = self.one_item(); let image = self.one_instance().instance;
image.width() image.width()
} }
fn height(&self) -> u32 { fn height(&self) -> u32 {
let image = self.one_item(); let image = self.one_instance().instance;
image.height() image.height()
} }
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> { fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_item(); let image = self.one_instance().instance;
image.get_pixel(x, y) image.get_pixel(x, y)
} }
@ -349,7 +346,7 @@ where
P::Static: Pixel, P::Static: Pixel,
{ {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
let image = self.one_item_mut(); let image = self.one_instance_mut().instance;
BitmapMut::get_pixel_mut(image, x, y) BitmapMut::get_pixel_mut(image, x, y)
} }
@ -384,19 +381,11 @@ impl<P: Pixel> AsRef<ImageFrame<P>> for ImageFrame<P> {
impl<P: Hash + Pixel> Hash for ImageFrame<P> { impl<P: Hash + Pixel> Hash for ImageFrame<P> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
0.hash(state); 0.hash(state);
self.image.hash(state); self.image.hash(state);
} }
} }
impl<P: Pixel> ImageFrame<P> {
/// Compute the pivot in local space with the current transform applied
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
self.transform.transform_point2(normalized_pivot)
}
}
/* This does not work because of missing specialization /* This does not work because of missing specialization
* so we have to manually implement this for now * so we have to manually implement this for now
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> { impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {
@ -420,8 +409,6 @@ impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
height: image.image.height, height: image.image.height,
base64_string: None, base64_string: None,
}, },
transform: image.transform,
alpha_blending: image.alpha_blending,
} }
} }
} }
@ -436,8 +423,6 @@ impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
height: image.image.height, height: image.image.height,
base64_string: None, base64_string: None,
}, },
transform: image.transform,
alpha_blending: image.alpha_blending,
} }
} }
} }

View file

@ -1,9 +1,8 @@
use crate::application_io::TextureFrameTable; use crate::application_io::TextureFrameTable;
use crate::raster::bbox::AxisAlignedBbox; use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::image::ImageFrameTable;
use crate::raster::Pixel; use crate::vector::VectorDataTable;
use crate::vector::{VectorData, VectorDataTable}; use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl};
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@ -34,153 +33,6 @@ impl<T: Transform> Transform for &T {
} }
} }
// Implementations for ImageFrame<P>
impl<P: Pixel> Transform for ImageFrame<P> {
fn transform(&self) -> DAffine2 {
self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrame<P> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// Implementations for ImageFrameTable<P>
impl<P: Pixel> Transform for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for TextureTable
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for GraphicGroup
impl Transform for GraphicGroup {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for GraphicGroup {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// Implementations for GraphicGroupTable
impl Transform for GraphicGroupTable {
fn transform(&self) -> DAffine2 {
let graphic_group = self.one_item();
graphic_group.transform
}
}
impl TransformMut for GraphicGroupTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let graphic_group = self.one_item_mut();
&mut graphic_group.transform
}
}
// Implementations for GraphicElement
impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElement::RasterFrame(raster) => raster.transform(),
}
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot),
}
}
}
impl TransformMut for GraphicElement {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElement::RasterFrame(raster) => raster.transform_mut(),
}
}
}
// Implementations for VectorData
impl Transform for VectorData {
fn transform(&self) -> DAffine2 {
self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.local_pivot(pivot)
}
}
impl TransformMut for VectorData {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// Implementations for VectorDataTable
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
let vector_data = self.one_item();
vector_data.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let vector_data = self.one_item();
vector_data.local_pivot(pivot)
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let vector_data = self.one_item_mut();
&mut vector_data.transform
}
}
// Implementations for Artboard // Implementations for Artboard
impl Transform for Artboard { impl Transform for Artboard {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {

View file

@ -1,4 +1,4 @@
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable}; use crate::vector::{HandleId, VectorData, VectorDataTable};
use crate::Ctx; use crate::Ctx;
use bezier_rs::Subpath; use bezier_rs::Subpath;
@ -105,15 +105,3 @@ fn star(
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
} }
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[node_macro::node(category(""))]
fn path(_: impl Ctx, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
VectorDataTable::new(vector_data)
}

View file

@ -17,16 +17,50 @@ use glam::{DAffine2, DVec2};
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<VectorDataTable, D::Error> {
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldVectorData {
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
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>,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)] #[serde(untagged)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
enum EitherFormat { enum EitherFormat {
VectorData(VectorData), VectorData(VectorData),
OldVectorData(OldVectorData),
VectorDataTable(VectorDataTable), VectorDataTable(VectorDataTable),
} }
Ok(match EitherFormat::deserialize(deserializer)? { Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data), EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
EitherFormat::OldVectorData(old) => {
let mut vector_data_table = VectorDataTable::new(VectorData {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
segment_domain: old.segment_domain,
region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group,
});
*vector_data_table.one_instance_mut().transform = old.transform;
*vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending;
vector_data_table
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table, EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
}) })
} }
@ -38,9 +72,8 @@ pub type VectorDataTable = Instances<VectorData>;
#[derive(Clone, Debug, PartialEq, DynAny)] #[derive(Clone, Debug, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VectorData { pub struct VectorData {
pub transform: DAffine2,
pub style: PathStyle, pub style: PathStyle,
pub alpha_blending: AlphaBlending,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another). /// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it). /// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>, pub colinear_manipulators: Vec<[HandleId; 2]>,
@ -58,20 +91,17 @@ impl core::hash::Hash for VectorData {
self.point_domain.hash(state); self.point_domain.hash(state);
self.segment_domain.hash(state); self.segment_domain.hash(state);
self.region_domain.hash(state); self.region_domain.hash(state);
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
self.style.hash(state); self.style.hash(state);
self.alpha_blending.hash(state);
self.colinear_manipulators.hash(state); self.colinear_manipulators.hash(state);
} }
} }
impl VectorData { impl VectorData {
/// An empty subpath with no data, an identity transform, and a black fill. /// An empty subpath with no data, an identity transform, and a black fill.
// TODO: Replace with just `Default`
pub const fn empty() -> Self { pub const fn empty() -> Self {
Self { Self {
transform: DAffine2::IDENTITY,
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None), style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
alpha_blending: AlphaBlending::new(),
colinear_manipulators: Vec::new(), colinear_manipulators: Vec::new(),
point_domain: PointDomain::new(), point_domain: PointDomain::new(),
segment_domain: SegmentDomain::new(), segment_domain: SegmentDomain::new(),
@ -190,11 +220,6 @@ impl VectorData {
bounds_min + bounds_size * normalized_pivot bounds_min + bounds_size * normalized_pivot
} }
/// Compute the pivot in local space with the current transform applied
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
}
pub fn start_point(&self) -> impl Iterator<Item = PointId> + '_ { pub fn start_point(&self) -> impl Iterator<Item = PointId> + '_ {
self.segment_domain.start_point().iter().map(|&index| self.point_domain.ids()[index]) self.segment_domain.start_point().iter().map(|&index| self.point_domain.ids()[index])
} }

View file

@ -1,3 +1,4 @@
use crate::transform::Transform;
use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable}; use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable};
use crate::vector::ConcatElement; use crate::vector::ConcatElement;
@ -82,32 +83,30 @@ impl core::hash::BuildHasher for NoHashBuilder {
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes. /// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
pub struct PointDomain { pub struct PointDomain {
id: Vec<PointId>, id: Vec<PointId>,
positions: Vec<DVec2>, #[serde(alias = "positions")]
position: Vec<DVec2>,
} }
impl core::hash::Hash for PointDomain { impl core::hash::Hash for PointDomain {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state); self.id.hash(state);
self.positions.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state)); self.position.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state));
} }
} }
impl PointDomain { impl PointDomain {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self { id: Vec::new(), position: Vec::new() }
id: Vec::new(),
positions: Vec::new(),
}
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.id.clear(); self.id.clear();
self.positions.clear(); self.position.clear();
} }
pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) { pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) {
let mut keep = self.id.iter().map(&f); let mut keep = self.id.iter().map(&f);
self.positions.retain(|_| keep.next().unwrap_or_default()); self.position.retain(|_| keep.next().unwrap_or_default());
// TODO(TrueDoctor): Consider using a prefix sum to avoid this Vec allocation (https://github.com/GraphiteEditor/Graphite/pull/1949#discussion_r1741711562) // TODO(TrueDoctor): Consider using a prefix sum to avoid this Vec allocation (https://github.com/GraphiteEditor/Graphite/pull/1949#discussion_r1741711562)
let mut id_map = Vec::with_capacity(self.ids().len()); let mut id_map = Vec::with_capacity(self.ids().len());
@ -131,19 +130,19 @@ impl PointDomain {
pub fn push(&mut self, id: PointId, position: DVec2) { pub fn push(&mut self, id: PointId, position: DVec2) {
debug_assert!(!self.id.contains(&id)); debug_assert!(!self.id.contains(&id));
self.id.push(id); self.id.push(id);
self.positions.push(position); self.position.push(position);
} }
pub fn positions(&self) -> &[DVec2] { pub fn positions(&self) -> &[DVec2] {
&self.positions &self.position
} }
pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> { pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {
self.id.iter().copied().zip(self.positions.iter_mut()) self.id.iter().copied().zip(self.position.iter_mut())
} }
pub fn set_position(&mut self, index: usize, position: DVec2) { pub fn set_position(&mut self, index: usize, position: DVec2) {
self.positions[index] = position; self.position[index] = position;
} }
pub fn ids(&self) -> &[PointId] { pub fn ids(&self) -> &[PointId] {
@ -156,7 +155,7 @@ impl PointDomain {
#[track_caller] #[track_caller]
pub fn position_from_id(&self, id: PointId) -> Option<DVec2> { pub fn position_from_id(&self, id: PointId) -> Option<DVec2> {
let pos = self.resolve_id(id).map(|index| self.positions[index]); let pos = self.resolve_id(id).map(|index| self.position[index]);
if pos.is_none() { if pos.is_none() {
warn!("Resolving pos of invalid id"); warn!("Resolving pos of invalid id");
} }
@ -169,7 +168,7 @@ impl PointDomain {
fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) { fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id))); self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id)));
self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos))); self.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos)));
} }
fn map_ids(&mut self, id_map: &IdMap) { fn map_ids(&mut self, id_map: &IdMap) {
@ -177,7 +176,7 @@ impl PointDomain {
} }
fn transform(&mut self, transform: DAffine2) { fn transform(&mut self, transform: DAffine2) {
for pos in &mut self.positions { for pos in &mut self.position {
*pos = transform.transform_point2(*pos); *pos = transform.transform_point2(*pos);
} }
} }
@ -187,7 +186,8 @@ impl PointDomain {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes. /// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
pub struct SegmentDomain { pub struct SegmentDomain {
ids: Vec<SegmentId>, #[serde(alias = "ids")]
id: Vec<SegmentId>,
start_point: Vec<usize>, start_point: Vec<usize>,
end_point: Vec<usize>, end_point: Vec<usize>,
handles: Vec<bezier_rs::BezierHandles>, handles: Vec<bezier_rs::BezierHandles>,
@ -197,7 +197,7 @@ pub struct SegmentDomain {
impl SegmentDomain { impl SegmentDomain {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
ids: Vec::new(), id: Vec::new(),
start_point: Vec::new(), start_point: Vec::new(),
end_point: Vec::new(), end_point: Vec::new(),
handles: Vec::new(), handles: Vec::new(),
@ -206,7 +206,7 @@ impl SegmentDomain {
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.ids.clear(); self.id.clear();
self.start_point.clear(); self.start_point.clear();
self.end_point.clear(); self.end_point.clear();
self.handles.clear(); self.handles.clear();
@ -215,7 +215,7 @@ impl SegmentDomain {
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) { pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) {
let additional_delete_ids = self let additional_delete_ids = self
.ids .id
.iter() .iter()
.zip(&self.start_point) .zip(&self.start_point)
.zip(&self.end_point) .zip(&self.end_point)
@ -236,17 +236,17 @@ impl SegmentDomain {
} }
}; };
let mut keep = self.ids.iter().map(can_delete()); let mut keep = self.id.iter().map(can_delete());
self.start_point.retain(|_| keep.next().unwrap_or_default()); self.start_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(can_delete()); let mut keep = self.id.iter().map(can_delete());
self.end_point.retain(|_| keep.next().unwrap_or_default()); self.end_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(can_delete()); let mut keep = self.id.iter().map(can_delete());
self.handles.retain(|_| keep.next().unwrap_or_default()); self.handles.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(can_delete()); let mut keep = self.id.iter().map(can_delete());
self.stroke.retain(|_| keep.next().unwrap_or_default()); self.stroke.retain(|_| keep.next().unwrap_or_default());
let mut delete_iter = additional_delete_ids.iter().peekable(); let mut delete_iter = additional_delete_ids.iter().peekable();
self.ids.retain(move |id| { self.id.retain(move |id| {
if delete_iter.peek() == Some(&id) { if delete_iter.peek() == Some(&id) {
delete_iter.next(); delete_iter.next();
false false
@ -257,7 +257,7 @@ impl SegmentDomain {
} }
pub fn ids(&self) -> &[SegmentId] { pub fn ids(&self) -> &[SegmentId] {
&self.ids &self.id
} }
pub fn next_id(&self) -> SegmentId { pub fn next_id(&self) -> SegmentId {
@ -289,9 +289,9 @@ impl SegmentDomain {
} }
pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: bezier_rs::BezierHandles, stroke: StrokeId) { pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
debug_assert!(!self.ids.contains(&id), "Tried to push an existing point to a point domain"); debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain");
self.ids.push(id); self.id.push(id);
self.start_point.push(start); self.start_point.push(start);
self.end_point.push(end); self.end_point.push(end);
self.handles.push(handles); self.handles.push(handles);
@ -299,15 +299,15 @@ impl SegmentDomain {
} }
pub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> { pub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
self.ids.iter().copied().zip(self.start_point.iter_mut()) self.id.iter().copied().zip(self.start_point.iter_mut())
} }
pub(crate) fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> { pub(crate) fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
self.ids.iter().copied().zip(self.end_point.iter_mut()) self.id.iter().copied().zip(self.end_point.iter_mut())
} }
pub(crate) fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, usize, usize)> { pub(crate) fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, usize, usize)> {
let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point); let nested = self.id.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
nested.map(|(((&a, b), &c), &d)| (a, b, c, d)) nested.map(|(((&a, b), &c), &d)| (a, b, c, d))
} }
@ -317,7 +317,7 @@ impl SegmentDomain {
} }
pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> { pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {
self.ids.iter().copied().zip(self.stroke.iter_mut()) self.id.iter().copied().zip(self.stroke.iter_mut())
} }
pub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option<usize> { pub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option<usize> {
@ -348,15 +348,15 @@ impl SegmentDomain {
} }
fn id_to_index(&self, id: SegmentId) -> Option<usize> { fn id_to_index(&self, id: SegmentId) -> Option<usize> {
debug_assert_eq!(self.ids.len(), self.handles.len()); debug_assert_eq!(self.id.len(), self.handles.len());
debug_assert_eq!(self.ids.len(), self.start_point.len()); debug_assert_eq!(self.id.len(), self.start_point.len());
debug_assert_eq!(self.ids.len(), self.end_point.len()); debug_assert_eq!(self.id.len(), self.end_point.len());
self.ids.iter().position(|&check_id| check_id == id) self.id.iter().position(|&check_id| check_id == id)
} }
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> { fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) { match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end), (Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.id.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
_ => { _ => {
warn!("Resolving range with invalid id"); warn!("Resolving range with invalid id");
None None
@ -365,7 +365,7 @@ impl SegmentDomain {
} }
fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) { fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
self.ids.extend(other.ids.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id))); self.id.extend(other.id.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id)));
self.start_point.extend(other.start_point.iter().map(|&index| id_map.point_offset + index)); self.start_point.extend(other.start_point.iter().map(|&index| id_map.point_offset + index));
self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index)); self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index));
self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p)))); self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p))));
@ -373,7 +373,7 @@ impl SegmentDomain {
} }
fn map_ids(&mut self, id_map: &IdMap) { fn map_ids(&mut self, id_map: &IdMap) {
self.ids.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id)); self.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
} }
fn transform(&mut self, transform: DAffine2) { fn transform(&mut self, transform: DAffine2) {
@ -384,12 +384,12 @@ impl SegmentDomain {
/// Enumerate all segments that start at the point. /// Enumerate all segments that start at the point.
pub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ { pub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) self.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
} }
/// Enumerate all segments that end at the point. /// Enumerate all segments that end at the point.
pub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ { pub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) self.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
} }
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment. /// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
@ -407,7 +407,8 @@ impl SegmentDomain {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes. /// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.
pub struct RegionDomain { pub struct RegionDomain {
ids: Vec<RegionId>, #[serde(alias = "ids")]
id: Vec<RegionId>,
segment_range: Vec<core::ops::RangeInclusive<SegmentId>>, segment_range: Vec<core::ops::RangeInclusive<SegmentId>>,
fill: Vec<FillId>, fill: Vec<FillId>,
} }
@ -415,54 +416,54 @@ pub struct RegionDomain {
impl RegionDomain { impl RegionDomain {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
ids: Vec::new(), id: Vec::new(),
segment_range: Vec::new(), segment_range: Vec::new(),
fill: Vec::new(), fill: Vec::new(),
} }
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.ids.clear(); self.id.clear();
self.segment_range.clear(); self.segment_range.clear();
self.fill.clear(); self.fill.clear();
} }
pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) { pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {
let mut keep = self.ids.iter().map(&f); let mut keep = self.id.iter().map(&f);
self.segment_range.retain(|_| keep.next().unwrap_or_default()); self.segment_range.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f); let mut keep = self.id.iter().map(&f);
self.fill.retain(|_| keep.next().unwrap_or_default()); self.fill.retain(|_| keep.next().unwrap_or_default());
self.ids.retain(&f); self.id.retain(&f);
} }
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) { pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
if self.ids.contains(&id) { if self.id.contains(&id) {
warn!("Duplicate region"); warn!("Duplicate region");
return; return;
} }
self.ids.push(id); self.id.push(id);
self.segment_range.push(segment_range); self.segment_range.push(segment_range);
self.fill.push(fill); self.fill.push(fill);
} }
fn _resolve_id(&self, id: RegionId) -> Option<usize> { fn _resolve_id(&self, id: RegionId) -> Option<usize> {
self.ids.iter().position(|&check_id| check_id == id) self.id.iter().position(|&check_id| check_id == id)
} }
pub fn next_id(&self) -> RegionId { pub fn next_id(&self) -> RegionId {
self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO) self.id.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
} }
pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> { pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> {
self.ids.iter().copied().zip(self.segment_range.iter_mut()) self.id.iter().copied().zip(self.segment_range.iter_mut())
} }
pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> { pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {
self.ids.iter().copied().zip(self.fill.iter_mut()) self.id.iter().copied().zip(self.fill.iter_mut())
} }
pub fn ids(&self) -> &[RegionId] { pub fn ids(&self) -> &[RegionId] {
&self.ids &self.id
} }
pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] { pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] {
@ -474,7 +475,7 @@ impl RegionDomain {
} }
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) { fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id))); self.id.extend(other.id.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
self.segment_range.extend( self.segment_range.extend(
other other
.segment_range .segment_range
@ -485,7 +486,7 @@ impl RegionDomain {
} }
fn map_ids(&mut self, id_map: &IdMap) { fn map_ids(&mut self, id_map: &IdMap) {
self.ids.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id)); self.id.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));
self.segment_range self.segment_range
.iter_mut() .iter_mut()
.for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end())); .for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end()));
@ -525,7 +526,7 @@ impl VectorData {
self.segment_domain self.segment_domain
.handles .handles
.iter() .iter()
.zip(&self.segment_domain.ids) .zip(&self.segment_domain.id)
.zip(self.segment_domain.start_point()) .zip(self.segment_domain.start_point())
.zip(self.segment_domain.end_point()) .zip(self.segment_domain.end_point())
.map(to_bezier) .map(to_bezier)
@ -574,7 +575,7 @@ impl VectorData {
/// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions. /// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions.
pub fn region_bezier_paths(&self) -> impl Iterator<Item = (RegionId, bezier_rs::Subpath<PointId>)> + '_ { pub fn region_bezier_paths(&self) -> impl Iterator<Item = (RegionId, bezier_rs::Subpath<PointId>)> + '_ {
self.region_domain self.region_domain
.ids .id
.iter() .iter()
.zip(&self.region_domain.segment_range) .zip(&self.region_domain.segment_range)
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range))) .filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
@ -774,16 +775,16 @@ impl ConcatElement for VectorData {
let point_map = new_ids.collect::<HashMap<_, _>>(); let point_map = new_ids.collect::<HashMap<_, _>>();
let new_ids = other let new_ids = other
.segment_domain .segment_domain
.ids .id
.iter() .iter()
.filter(|id| self.segment_domain.ids.contains(id)) .filter(|id| self.segment_domain.id.contains(id))
.map(|&old| (old, old.generate_from_hash(node_id))); .map(|&old| (old, old.generate_from_hash(node_id)));
let segment_map = new_ids.collect::<HashMap<_, _>>(); let segment_map = new_ids.collect::<HashMap<_, _>>();
let new_ids = other let new_ids = other
.region_domain .region_domain
.ids .id
.iter() .iter()
.filter(|id| self.region_domain.ids.contains(id)) .filter(|id| self.region_domain.id.contains(id))
.map(|&old| (old, old.generate_from_hash(node_id))); .map(|&old| (old, old.generate_from_hash(node_id)));
let region_map = new_ids.collect::<HashMap<_, _>>(); let region_map = new_ids.collect::<HashMap<_, _>>();
let id_map = IdMap { let id_map = IdMap {
@ -792,20 +793,20 @@ impl ConcatElement for VectorData {
segment_map, segment_map,
region_map, region_map,
}; };
self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); self.point_domain.concat(&other.point_domain, transform, &id_map);
self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); self.segment_domain.concat(&other.segment_domain, transform, &id_map);
self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); self.region_domain.concat(&other.region_domain, transform, &id_map);
// TODO: properly deal with fills such as gradients // TODO: properly deal with fills such as gradients
self.style = other.style.clone(); self.style = other.style.clone();
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
self.alpha_blending = other.alpha_blending;
} }
} }
impl ConcatElement for VectorDataTable { impl ConcatElement for VectorDataTable {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
for (instance, other_instance) in self.instances_mut().zip(other.instances()) { for (instance, other_instance) in self.instances_mut().zip(other.instances()) {
instance.concat(other_instance, transform, node_id); *instance.alpha_blending = *other_instance.alpha_blending;
instance.instance.concat(other_instance.instance, transform * other_instance.transform(), node_id);
} }
} }
} }

View file

@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::transform::TransformMut;
use crate::uuid::generate_uuid; use crate::uuid::generate_uuid;
use crate::Ctx; use crate::Ctx;
@ -425,11 +426,14 @@ impl core::hash::Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`]. /// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable { async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
let vector_data = vector_data.one_item_mut(); let vector_data_transform = *vector_data.one_instance().transform;
let vector_data = vector_data.one_instance_mut().instance;
modification.apply(vector_data); modification.apply(vector_data);
VectorDataTable::new(vector_data.clone()) let mut result = VectorDataTable::new(vector_data.clone());
*result.transform_mut() = vector_data_transform;
result
} }
#[test] #[test]

View file

@ -1,12 +1,13 @@
use super::misc::CentroidType; use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke}; use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable}; use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::instances::InstanceMut;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue}; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered; use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::LineJoin; use crate::vector::style::LineJoin;
use crate::vector::PointDomain; use crate::vector::PointDomain;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@ -20,15 +21,16 @@ trait VectorIterMut {
impl VectorIterMut for GraphicGroupTable { impl VectorIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> { fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let instance = self.one_item_mut(); let parent_transform = self.transform();
let instance = self.one_instance_mut().instance;
let parent_transform = instance.transform;
// Grab only the direct children // Grab only the direct children
instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| { instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| {
let vector_data = vector_data.one_item_mut(); let transform = parent_transform * vector_data.transform();
let transform = parent_transform * vector_data.transform;
(vector_data, transform) let vector_data_instance = vector_data.one_instance_mut().instance;
(vector_data_instance, transform)
}) })
} }
} }
@ -36,8 +38,8 @@ impl VectorIterMut for GraphicGroupTable {
impl VectorIterMut for VectorDataTable { impl VectorIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> { fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
self.instances_mut().map(|instance| { self.instances_mut().map(|instance| {
let transform = instance.transform; let transform = instance.transform();
(instance, transform) (instance.instance, transform)
}) })
} }
} }
@ -176,11 +178,10 @@ async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send
let instances = instances.max(1); let instances = instances.max(1);
let total = (instances - 1) as f64; let total = (instances - 1) as f64;
let mut result = GraphicGroup::default(); let mut result_table = GraphicGroupTable::default();
let result = result_table.one_instance_mut().instance;
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { return result_table };
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.; let center = (bounding_box[0] + bounding_box[1]) / 2.;
@ -196,7 +197,7 @@ async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send
result.push((new_instance, None)); result.push((new_instance, None));
} }
GraphicGroupTable::new(result) result_table
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -211,11 +212,10 @@ async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformM
let first_vector_transform = instance.transform(); let first_vector_transform = instance.transform();
let instances = instances.max(1); let instances = instances.max(1);
let mut result = GraphicGroup::default(); let mut result_table = GraphicGroupTable::default();
let result = result_table.one_instance_mut().instance;
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { return result_table };
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.; let center = (bounding_box[0] + bounding_box[1]) / 2.;
let base_transform = DVec2::new(0., radius) - center; let base_transform = DVec2::new(0., radius) - center;
@ -232,7 +232,7 @@ async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformM
result.push((new_instance, None)); result.push((new_instance, None));
} }
GraphicGroupTable::new(result) result_table
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -249,7 +249,8 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
random_rotation: Angle, random_rotation: Angle,
random_rotation_seed: SeedValue, random_rotation_seed: SeedValue,
) -> GraphicGroupTable { ) -> GraphicGroupTable {
let points = points.one_item(); let points_transform = points.transform();
let points = points.one_instance().instance;
let instance_transform = instance.transform(); let instance_transform = instance.transform();
@ -266,11 +267,13 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
let do_scale = random_scale_difference.abs() > 1e-6; let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6; let do_rotation = random_rotation.abs() > 1e-6;
let mut result = GraphicGroup::default(); let mut result_table = GraphicGroupTable::default();
let result = result_table.one_instance_mut().instance;
for &point in points_list { for &point in points_list {
let center_transform = DAffine2::from_translation(instance_center); let center_transform = DAffine2::from_translation(instance_center);
let translation = points.transform.transform_point2(point); let translation = points_transform.transform_point2(point);
let rotation = if do_rotation { let rotation = if do_rotation {
let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation; let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;
@ -301,14 +304,15 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
result.push((new_instance, None)); result.push((new_instance, None));
} }
GraphicGroupTable::new(result) result_table
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data = vector_data.one_item(); let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); let bounding_box = vector_data.bounding_box_with_transform(vector_data_transform).unwrap();
let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])); let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]));
result.style = vector_data.style.clone(); result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
@ -318,7 +322,8 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] #[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable { async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
let vector_data = vector_data.one_item(); let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let subpaths = vector_data.stroke_bezier_paths(); let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty(); let mut result = VectorData::empty();
@ -327,7 +332,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
// Perform operation on all subpaths in this shape. // Perform operation on all subpaths in this shape.
for mut subpath in subpaths { for mut subpath in subpaths {
subpath.apply_transform(vector_data.transform); subpath.apply_transform(vector_data_transform);
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths. // Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
let subpath_out = subpath.offset( let subpath_out = subpath.offset(
@ -348,9 +353,9 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data = vector_data.one_item(); let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let transform = &vector_data.transform;
let style = &vector_data.style; let style = &vector_data.style;
let subpaths = vector_data.stroke_bezier_paths(); let subpaths = vector_data.stroke_bezier_paths();
@ -359,7 +364,7 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat
// Perform operation on all subpaths in this shape. // Perform operation on all subpaths in this shape.
for mut subpath in subpaths { for mut subpath in subpaths {
let stroke = style.stroke().unwrap(); let stroke = style.stroke().unwrap();
subpath.apply_transform(*transform); subpath.apply_transform(vector_data_transform);
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths. // Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
let subpath_out = subpath.outline( let subpath_out = subpath.outline(
@ -398,34 +403,35 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable { async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
let graphic_group_input = graphic_group_input.one_item();
// A node based solution to support passing through vector data could be a network node with a cache node connected to // A node based solution to support passing through vector data could be a network node with a cache node connected to
// a flatten vector elements connected to an if else node, another connection from the cache directly // a flatten vector elements connected to an if else node, another connection from the cache directly
// To the if else node, and another connection from the cache to a matches type node connected to the if else node. // To the if else node, and another connection from the cache to a matches type node connected to the if else node.
fn concat_group(graphic_group: &GraphicGroup, current_transform: DAffine2, result: &mut VectorData) { fn concat_group(graphic_group_table: &GraphicGroupTable, current_transform: DAffine2, result: &mut InstanceMut<VectorData>) {
for (element, reference) in graphic_group.iter() { for (element, reference) in graphic_group_table.one_instance().instance.iter() {
match element { match element {
GraphicElement::VectorData(vector_data) => { GraphicElement::VectorData(vector_data) => {
for instance in vector_data.instances() { for instance in vector_data.instances() {
result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default()); *result.alpha_blending = *instance.alpha_blending;
result
.instance
.concat(instance.instance, current_transform * instance.transform(), reference.map(|node_id| node_id.0).unwrap_or_default());
} }
} }
GraphicElement::GraphicGroup(graphic_group) => { GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item(); concat_group(graphic_group, current_transform * graphic_group.transform(), result);
concat_group(graphic_group, current_transform * graphic_group.transform, result);
} }
_ => {} _ => {}
} }
} }
} }
let mut result = VectorData::empty(); let mut result_table = VectorDataTable::default();
concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result); let mut result_instance = result_table.one_instance_mut();
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms. // TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
result.style.set_stroke_transform(DAffine2::IDENTITY); result_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY);
concat_group(&graphic_group_input, DAffine2::IDENTITY, &mut result_instance);
VectorDataTable::new(result) result_table
} }
pub trait ConcatElement { pub trait ConcatElement {
@ -434,16 +440,18 @@ pub trait ConcatElement {
impl ConcatElement for GraphicGroupTable { impl ConcatElement for GraphicGroupTable {
fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) { fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) {
let own = self.one_item_mut(); let other_transform = other.transform();
let other = other.one_item();
let self_group = self.one_instance_mut().instance;
let other_group = other.one_instance().instance;
// TODO: Decide if we want to keep this behavior whereby the layers are flattened // TODO: Decide if we want to keep this behavior whereby the layers are flattened
for (mut element, footprint_mapping) in other.iter().cloned() { for (mut element, footprint_mapping) in other_group.iter().cloned() {
*element.transform_mut() = transform * element.transform() * other.transform(); *element.transform_mut() = transform * element.transform() * other_transform;
own.push((element, footprint_mapping)); self_group.push((element, footprint_mapping));
} }
own.alpha_blending = other.alpha_blending; *self.one_instance_mut().alpha_blending = *other.one_instance().alpha_blending;
} }
} }
@ -451,14 +459,16 @@ impl ConcatElement for GraphicGroupTable {
async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable { async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable {
// Limit the smallest spacing to something sensible to avoid freezing the application. // Limit the smallest spacing to something sensible to avoid freezing the application.
let spacing = spacing.max(0.01); let spacing = spacing.max(0.01);
let vector_data = vector_data.one_item();
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
// Create an iterator over the bezier segments with enumeration and peeking capability. // Create an iterator over the bezier segments with enumeration and peeking capability.
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable(); let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
// Initialize the result VectorData with the same transformation as the input. // Initialize the result VectorData with the same transformation as the input.
let mut result = VectorData::empty(); let mut result = VectorDataTable::default();
result.transform = vector_data.transform; *result.transform_mut() = vector_data_transform;
// Iterate over each segment in the bezier iterator. // Iterate over each segment in the bezier iterator.
while let Some((index, (segment_id, _, start_point_index, mut last_end))) = bezier.next() { while let Some((index, (segment_id, _, start_point_index, mut last_end))) = bezier.next() {
@ -537,7 +547,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
// Retrieve the segment and apply transformation. // Retrieve the segment and apply transformation.
let Some(segment) = vector_data.segment_from_id(current_segment_id) else { continue }; let Some(segment) = vector_data.segment_from_id(current_segment_id) else { continue };
let segment = segment.apply_transformation(|point| vector_data.transform.transform_point2(point)); let segment = segment.apply_transformation(|point| vector_data_transform.transform_point2(point));
// Calculate the position on the segment. // Calculate the position on the segment.
let parametric_t = segment.euclidean_to_parametric_with_total_length((total_distance - total_length_before) / length, 0.001, length); let parametric_t = segment.euclidean_to_parametric_with_total_length((total_distance - total_length_before) / length, 0.001, length);
@ -545,10 +555,10 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
// Generate a new PointId and add the point to result.point_domain. // Generate a new PointId and add the point to result.point_domain.
let point_id = PointId::generate(); let point_id = PointId::generate();
result.point_domain.push(point_id, vector_data.transform.inverse().transform_point2(point)); result.one_instance_mut().instance.point_domain.push(point_id, vector_data_transform.inverse().transform_point2(point));
// Store the index of the point. // Store the index of the point.
let point_index = result.point_domain.ids().len() - 1; let point_index = result.one_instance_mut().instance.point_domain.ids().len() - 1;
point_indices.push(point_index); point_indices.push(point_index);
} }
@ -565,7 +575,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
let stroke_id = StrokeId::generate(); let stroke_id = StrokeId::generate();
// Add the segment to result.segment_domain. // Add the segment to result.segment_domain.
result.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id); result.one_instance_mut().instance.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id);
} }
} }
@ -582,17 +592,17 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
let stroke_id = StrokeId::generate(); let stroke_id = StrokeId::generate();
// Add the closing segment to result.segment_domain. // Add the closing segment to result.segment_domain.
result.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id); result.one_instance_mut().instance.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id);
} }
} }
} }
// Transfer the style from the input vector data to the result. // Transfer the style from the input vector data to the result.
result.style = vector_data.style.clone(); result.one_instance_mut().instance.style = vector_data.style.clone();
result.style.set_stroke_transform(vector_data.transform); result.one_instance_mut().instance.style.set_stroke_transform(vector_data_transform);
// Return the resulting vector data with newly generated points and segments. // Return the resulting vector data with newly generated points and segments.
VectorDataTable::new(result) result
} }
#[node_macro::node(category(""), path(graphene_core::vector))] #[node_macro::node(category(""), path(graphene_core::vector))]
@ -604,7 +614,8 @@ async fn poisson_disk_points(
separation_disk_diameter: f64, separation_disk_diameter: f64,
seed: SeedValue, seed: SeedValue,
) -> VectorDataTable { ) -> VectorDataTable {
let vector_data = vector_data.one_item(); let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty(); let mut result = VectorData::empty();
@ -618,7 +629,7 @@ async fn poisson_disk_points(
continue; continue;
} }
subpath.apply_transform(vector_data.transform); subpath.apply_transform(vector_data_transform);
let mut previous_point_index: Option<usize> = None; let mut previous_point_index: Option<usize> = None;
@ -648,17 +659,18 @@ async fn poisson_disk_points(
#[node_macro::node(category(""), path(graphene_core::vector))] #[node_macro::node(category(""), path(graphene_core::vector))]
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> { async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
let vector_data = vector_data.one_item(); let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
vector_data vector_data
.segment_bezier_iter() .segment_bezier_iter()
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None)) .map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data_transform.transform_point2(point)).length(None))
.collect() .collect()
} }
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))] #[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
let vector_data = vector_data.one_item_mut(); let vector_data = vector_data.one_instance_mut().instance;
// Exit early if there are no points to generate splines from. // Exit early if there are no points to generate splines from.
if vector_data.point_domain.positions().is_empty() { if vector_data.point_domain.positions().is_empty() {
@ -700,7 +712,8 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
let mut vector_data = vector_data.one_item().clone(); let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
@ -718,30 +731,29 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
if !already_applied[*start] { if !already_applied[*start] {
let start_position = vector_data.point_domain.positions()[*start]; let start_position = vector_data.point_domain.positions()[*start];
let start_position = vector_data.transform.transform_point2(start_position); let start_position = vector_data_transform.transform_point2(start_position);
vector_data.point_domain.set_position(*start, start_position + start_delta); vector_data.point_domain.set_position(*start, start_position + start_delta);
already_applied[*start] = true; already_applied[*start] = true;
} }
if !already_applied[*end] { if !already_applied[*end] {
let end_position = vector_data.point_domain.positions()[*end]; let end_position = vector_data.point_domain.positions()[*end];
let end_position = vector_data.transform.transform_point2(end_position); let end_position = vector_data_transform.transform_point2(end_position);
vector_data.point_domain.set_position(*end, end_position + end_delta); vector_data.point_domain.set_position(*end, end_position + end_delta);
already_applied[*end] = true; already_applied[*end] = true;
} }
match handles { match handles {
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
*handle_start = vector_data.transform.transform_point2(*handle_start) + start_delta; *handle_start = vector_data_transform.transform_point2(*handle_start) + start_delta;
*handle_end = vector_data.transform.transform_point2(*handle_end) + end_delta; *handle_end = vector_data_transform.transform_point2(*handle_end) + end_delta;
} }
bezier_rs::BezierHandles::Quadratic { handle } => { bezier_rs::BezierHandles::Quadratic { handle } => {
*handle = vector_data.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; *handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
} }
bezier_rs::BezierHandles::Linear => {} bezier_rs::BezierHandles::Linear => {}
} }
} }
vector_data.transform = DAffine2::IDENTITY;
vector_data.style.set_stroke_transform(DAffine2::IDENTITY); vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
VectorDataTable::new(vector_data) VectorDataTable::new(vector_data)
@ -757,23 +769,29 @@ async fn morph(
time: Fraction, time: Fraction,
#[min(0.)] start_index: IntegerCount, #[min(0.)] start_index: IntegerCount,
) -> VectorDataTable { ) -> VectorDataTable {
let source = source.one_item();
let target = target.one_item();
let mut result = VectorData::empty();
let time = time.clamp(0., 1.); let time = time.clamp(0., 1.);
let source_alpha_blending = source.one_instance().alpha_blending;
let target_alpha_blending = target.one_instance().alpha_blending;
let source_transform = source.transform();
let target_transform = target.transform();
let source = source.one_instance().instance;
let target = target.one_instance().instance;
let mut result = VectorDataTable::default();
// Lerp styles // Lerp styles
result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending }; *result.one_instance_mut().alpha_blending = if time < 0.5 { *source_alpha_blending } else { *target_alpha_blending };
result.style = source.style.lerp(&target.style, time); result.one_instance_mut().instance.style = source.style.lerp(&target.style, time);
let mut source_paths = source.stroke_bezier_paths(); let mut source_paths = source.stroke_bezier_paths();
let mut target_paths = target.stroke_bezier_paths(); let mut target_paths = target.stroke_bezier_paths();
for (mut source_path, mut target_path) in (&mut source_paths).zip(&mut target_paths) { for (mut source_path, mut target_path) in (&mut source_paths).zip(&mut target_paths) {
// Deal with mismatched transforms // Deal with mismatched transforms
source_path.apply_transform(source.transform); source_path.apply_transform(source_transform);
target_path.apply_transform(target.transform); target_path.apply_transform(target_transform);
// Deal with mismatched start index // Deal with mismatched start index
for _ in 0..start_index { for _ in 0..start_index {
@ -816,11 +834,12 @@ async fn morph(
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time); manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
} }
result.append_subpath(source_path, true); result.one_instance_mut().instance.append_subpath(source_path, true);
} }
// Mismatched subpath count // Mismatched subpath count
for mut source_path in source_paths { for mut source_path in source_paths {
source_path.apply_transform(source.transform); source_path.apply_transform(source_transform);
let end = source_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); let end = source_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
for group in source_path.manipulator_groups_mut() { for group in source_path.manipulator_groups_mut() {
group.anchor = group.anchor.lerp(end, time); group.anchor = group.anchor.lerp(end, time);
@ -829,7 +848,7 @@ async fn morph(
} }
} }
for mut target_path in target_paths { for mut target_path in target_paths {
target_path.apply_transform(target.transform); target_path.apply_transform(target_transform);
let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
for group in target_path.manipulator_groups_mut() { for group in target_path.manipulator_groups_mut() {
group.anchor = start.lerp(group.anchor, time); group.anchor = start.lerp(group.anchor, time);
@ -838,10 +857,10 @@ async fn morph(
} }
} }
VectorDataTable::new(result) result
} }
fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData {
// Splits a bézier curve based on a distance measurement // Splits a bézier curve based on a distance measurement
fn split_distance(bezier: bezier_rs::Bezier, distance: f64, length: f64) -> bezier_rs::Bezier { fn split_distance(bezier: bezier_rs::Bezier, distance: f64, length: f64) -> bezier_rs::Bezier {
const EUCLIDEAN_ERROR: f64 = 0.001; const EUCLIDEAN_ERROR: f64 = 0.001;
@ -885,7 +904,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
} }
} }
fn update_existing_segments(vector_data: &mut VectorData, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> { fn update_existing_segments(vector_data: &mut VectorData, vector_data_transform: DAffine2, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> {
let mut next_id = vector_data.point_domain.next_id(); let mut next_id = vector_data.point_domain.next_id();
let mut new_segments = Vec::new(); let mut new_segments = Vec::new();
@ -900,8 +919,8 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
if bezier.is_linear() { if bezier.is_linear() {
bezier.handles = bezier_rs::BezierHandles::Linear; bezier.handles = bezier_rs::BezierHandles::Linear;
} }
bezier = bezier.apply_transformation(|p| vector_data.transform.transform_point2(p)); bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p));
let inverse_transform = (vector_data.transform.matrix2.determinant() != 0.).then(|| vector_data.transform.inverse()).unwrap_or_default(); let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
let original_length = bezier.length(None); let original_length = bezier.length(None);
let mut length = original_length; let mut length = original_length;
@ -942,7 +961,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
} }
let mut segments_connected = segments_connected_count(&vector_data); let mut segments_connected = segments_connected_count(&vector_data);
let new_segments = update_existing_segments(&mut vector_data, distance, &mut segments_connected); let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected);
insert_new_segments(&mut vector_data, &new_segments); insert_new_segments(&mut vector_data, &new_segments);
vector_data vector_data
@ -950,22 +969,28 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source = source.one_item(); let source_transform = source.transform();
let source = source.one_instance().instance;
VectorDataTable::new(bevel_algorithm(source.clone(), distance)) let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
*result.transform_mut() = source_transform;
result
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 { async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await; let vector_data = vector_data.eval(new_ctx).await;
let vector_data = vector_data.one_item();
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let mut area = 0.; let mut area = 0.;
let scale = vector_data.transform.decompose_scale(); let scale = vector_data_transform.decompose_scale();
for subpath in vector_data.stroke_bezier_paths() { for subpath in vector_data.stroke_bezier_paths() {
area += subpath.area(Some(1e-3), Some(1e-3)); area += subpath.area(Some(1e-3), Some(1e-3));
} }
area * scale[0] * scale[1] area * scale[0] * scale[1]
} }
@ -973,7 +998,9 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 { async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await; let vector_data = vector_data.eval(new_ctx).await;
let vector_data = vector_data.one_item();
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
if centroid_type == CentroidType::Area { if centroid_type == CentroidType::Area {
let mut area = 0.; let mut area = 0.;
@ -990,7 +1017,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
if area != 0. { if area != 0. {
centroid /= area; centroid /= area;
return vector_data.transform().transform_point2(centroid); return vector_data_transform.transform_point2(centroid);
} }
} }
@ -1005,13 +1032,13 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
if length != 0. { if length != 0. {
centroid /= length; centroid /= length;
return vector_data.transform().transform_point2(centroid); return vector_data_transform.transform_point2(centroid);
} }
let positions = vector_data.point_domain.positions(); let positions = vector_data.point_domain.positions();
if !positions.is_empty() { if !positions.is_empty() {
let centroid = positions.iter().sum::<DVec2>() / (positions.len() as f64); let centroid = positions.iter().sum::<DVec2>() / (positions.len() as f64);
return vector_data.transform().transform_point2(centroid); return vector_data_transform.transform_point2(centroid);
} }
DVec2::ZERO DVec2::ZERO
@ -1047,7 +1074,7 @@ mod test {
let instances = 3; let instances = 3;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.one_item(); let vector_data = vector_data.one_instance().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 3); assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1059,7 +1086,7 @@ mod test {
let instances = 8; let instances = 8;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.one_item(); let vector_data = vector_data.one_instance().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8); assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1069,7 +1096,7 @@ mod test {
async fn circle_repeat() { async fn circle_repeat() {
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.one_item(); let vector_data = vector_data.one_instance().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8); assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.; let expected_angle = (index as f64 + 1.) * 45.;
@ -1081,20 +1108,21 @@ mod test {
#[tokio::test] #[tokio::test]
async fn bounding_box() { async fn bounding_box() {
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await; let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
let bounding_box = bounding_box.one_item(); let bounding_box = bounding_box.one_instance().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1); assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1; let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]); assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
// Test a VectorData with non-zero rotation // Test a VectorData with non-zero rotation
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)); let square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); let mut square = VectorDataTable::new(square);
*square.one_instance_mut().transform_mut() *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode { let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(VectorDataTable::new(square)), vector_data: FutureWrapperNode(square),
} }
.eval(Footprint::default()) .eval(Footprint::default())
.await; .await;
let bounding_box = bounding_box.one_item(); let bounding_box = bounding_box.one_instance().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1); assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1; let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let sqrt2 = core::f64::consts::SQRT_2; let sqrt2 = core::f64::consts::SQRT_2;
@ -1108,7 +1136,7 @@ mod test {
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await; let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
let flattened_copy_to_points = flattened_copy_to_points.one_item(); let flattened_copy_to_points = flattened_copy_to_points.one_instance().instance;
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
let offset = expected_points[index]; let offset = expected_points[index];
@ -1122,7 +1150,7 @@ mod test {
async fn sample_points() { async fn sample_points() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await; let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
let sample_points = sample_points.one_item(); let sample_points = sample_points.one_instance().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4); assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1132,7 +1160,7 @@ mod test {
async fn adaptive_spacing() { async fn adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await; let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
let sample_points = sample_points.one_item(); let sample_points = sample_points.one_instance().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4); assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1147,7 +1175,7 @@ mod test {
0, 0,
) )
.await; .await;
let sample_points = sample_points.one_item(); let sample_points = sample_points.one_instance().instance;
assert!( assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()), (20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}", "actual len {}",
@ -1166,7 +1194,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn spline() { async fn spline() {
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.one_item(); let spline = spline.one_instance().instance;
assert_eq!(spline.stroke_bezier_paths().count(), 1); assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
} }
@ -1175,7 +1203,7 @@ mod test {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO); let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await; let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
let sample_points = sample_points.one_item(); let sample_points = sample_points.one_instance().instance;
assert_eq!( assert_eq!(
&sample_points.point_domain.positions()[..4], &sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)] vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
@ -1186,14 +1214,19 @@ mod test {
fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) { fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) {
let segments = vector.segment_bezier_iter().map(|x| x.1); let segments = vector.segment_bezier_iter().map(|x| x.1);
let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count(); let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count();
assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::<Vec<_>>()); assert_eq!(
count,
1,
"Expected exactly one matching segment for {target:?}, but found {count}. The given segments are: {:#?}",
vector.segment_bezier_iter().collect::<Vec<_>>()
);
} }
#[tokio::test] #[tokio::test]
async fn bevel_rect() { async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.one_item(); let beveled = beveled.one_instance().instance;
assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8);
@ -1216,7 +1249,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel((), vector_node(source), 5.); let beveled = super::bevel((), vector_node(source), 5.);
let beveled = beveled.one_item(); let beveled = beveled.one_instance().instance;
assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3); assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -1232,32 +1265,34 @@ mod test {
#[tokio::test] #[tokio::test]
async fn bevel_with_transform() { async fn bevel_with_transform() {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::X * 10.); let curve = Bezier::from_cubic_dvec2(DVec2::new(0., 0.), DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::new(10., 0.));
let source = Subpath::<PointId>::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -10., DVec2::ZERO), curve], false); let source = Subpath::<PointId>::from_beziers(&[Bezier::from_linear_dvec2(DVec2::new(-10., 0.), DVec2::ZERO), curve], false);
let mut vector_data = VectorData::from_subpath(source); let vector_data = VectorData::from_subpath(source);
let mut vector_data_table = VectorDataTable::new(vector_data.clone());
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
vector_data.transform = transform; *vector_data_table.one_instance_mut().transform_mut() = transform;
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.); let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
let beveled = beveled.one_item(); let beveled = beveled.one_instance().instance;
assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3); assert_eq!(beveled.segment_domain.ids().len(), 3);
assert_eq!(beveled.transform, transform);
// Segments // Segments
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-10., 0.)));
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
contains_segment(beveled.clone(), trimmed); contains_segment(beveled.clone(), trimmed);
// Join // Join
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
} }
#[tokio::test] #[tokio::test]
async fn bevel_too_high() { async fn bevel_too_high() {
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false); let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.); let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
let beveled = beveled.one_item(); let beveled = beveled.one_instance().instance;
assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5); assert_eq!(beveled.segment_domain.ids().len(), 5);
@ -1278,7 +1313,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO); let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.one_item(); let beveled = beveled.one_instance().instance;
assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5); assert_eq!(beveled.segment_domain.ids().len(), 5);

View file

@ -118,7 +118,8 @@ tagged_value! {
String(String), String(String),
U32(u32), U32(u32),
U64(u64), U64(u64),
#[cfg_attr(feature = "serde", serde(alias = "F32"))] // TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "F32"))]
F64(f64), F64(f64),
OptionalF64(Option<f64>), OptionalF64(Option<f64>),
Bool(bool), Bool(bool),
@ -129,7 +130,8 @@ tagged_value! {
DAffine2(DAffine2), DAffine2(DAffine2),
Image(graphene_core::raster::Image<Color>), Image(graphene_core::raster::Image<Color>),
ImaginateCache(ImaginateCache), ImaginateCache(ImaginateCache),
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))]
ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>), ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>),
Color(graphene_core::raster::color::Color), Color(graphene_core::raster::color::Color),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>), Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
@ -138,12 +140,14 @@ tagged_value! {
ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateSamplingMethod(ImaginateSamplingMethod),
ImaginateMaskStartingFill(ImaginateMaskStartingFill), ImaginateMaskStartingFill(ImaginateMaskStartingFill),
ImaginateController(ImaginateController), ImaginateController(ImaginateController),
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))]
VectorData(graphene_core::vector::VectorDataTable), VectorData(graphene_core::vector::VectorDataTable),
Fill(graphene_core::vector::style::Fill), Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke), Stroke(graphene_core::vector::style::Stroke),
F64Array4([f64; 4]), F64Array4([f64; 4]),
#[cfg_attr(feature = "serde", serde(alias = "VecF32"))] // TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "VecF32"))]
VecF64(Vec<f64>), VecF64(Vec<f64>),
VecU64(Vec<u64>), VecU64(Vec<u64>),
NodePath(Vec<NodeId>), NodePath(Vec<NodeId>),
@ -163,16 +167,19 @@ tagged_value! {
FillChoice(graphene_core::vector::style::FillChoice), FillChoice(graphene_core::vector::style::FillChoice),
Gradient(graphene_core::vector::style::Gradient), Gradient(graphene_core::vector::style::Gradient),
GradientType(graphene_core::vector::style::GradientType), GradientType(graphene_core::vector::style::GradientType),
#[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))] // TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))]
GradientStops(graphene_core::vector::style::GradientStops), GradientStops(graphene_core::vector::style::GradientStops),
OptionalColor(Option<graphene_core::raster::color::Color>), OptionalColor(Option<graphene_core::raster::color::Color>),
#[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))] // TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))]
PointIds(Vec<graphene_core::vector::PointId>), PointIds(Vec<graphene_core::vector::PointId>),
Font(graphene_core::text::Font), Font(graphene_core::text::Font),
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>), BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
BrushCache(BrushCache), BrushCache(BrushCache),
DocumentNode(DocumentNode), DocumentNode(DocumentNode),
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))]
GraphicGroup(graphene_core::GraphicGroupTable), GraphicGroup(graphene_core::GraphicGroupTable),
GraphicElement(graphene_core::GraphicElement), GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup), ArtboardGroup(graphene_core::ArtboardGroup),

View file

@ -89,3 +89,6 @@ web-sys = { workspace = true, optional = true, features = [
# Optional dependencies # Optional dependencies
image-compare = { version = "0.4.1", optional = true } image-compare = { version = "0.4.1", optional = true }
ndarray = "0.16.1" ndarray = "0.16.1"
[dev-dependencies]
tokio = { workspace = true, features = ["macros"] }

View file

@ -6,19 +6,18 @@ use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::BlendMode; use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Image, Pixel, Sample};
use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample};
use graphene_core::transform::{Transform, TransformMut}; use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::vector::VectorDataTable; use graphene_core::vector::VectorDataTable;
use graphene_core::{Ctx, Node}; use graphene_core::{Ctx, GraphicElement, Node};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> { fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
let vector_data = vector_data.one_item(); let vector_data = vector_data.one_instance().instance;
vector_data.point_domain.positions().to_vec() vector_data.point_domain.positions().to_vec()
} }
@ -89,17 +88,24 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64)
} }
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
fn blit<P: Alpha + Pixel + std::fmt::Debug, BlendFn>(mut target: ImageFrame<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrame<P> fn blit<P, BlendFn>(mut target: ImageFrameTable<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrameTable<P>
where where
P: Pixel + Alpha + std::fmt::Debug + dyn_any::StaticType,
P::Static: Pixel,
BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>, BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>,
GraphicElement: From<ImageFrame<P>>,
{ {
if positions.is_empty() { if positions.is_empty() {
return target; return target;
} }
let target_size = DVec2::new(target.image.width as f64, target.image.height as f64); let target_width = target.one_instance().instance.image.width;
let target_height = target.one_instance().instance.image.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 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.transform.inverse();
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform().inverse();
for position in positions { for position in positions {
let start = document_to_target.transform_point2(position).round(); let start = document_to_target.transform_point2(position).round();
@ -114,17 +120,17 @@ where
// Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop. // Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop.
let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) }; let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) };
let target_index = |x: u32, y: u32| -> usize { (y as usize * target.image.width as usize) + (x as usize) }; let target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) };
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1); 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); 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!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target.image.data.len()); assert!(target_index(max_x, max_y) < target.one_instance().instance.image.data.len());
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y { 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 { 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 src_pixel = texture.data[texture_index(x, y)];
let dst_pixel = &mut target.image.data[target_index(x + clamp_start.x, y + clamp_start.y)]; let dst_pixel = &mut target.one_instance_mut().instance.image.data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel)); *dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
} }
} }
@ -138,16 +144,9 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
use crate::raster::empty_image; use crate::raster::empty_image;
let blank_texture = empty_image((), transform, Color::TRANSPARENT); let blank_texture = empty_image((), transform, Color::TRANSPARENT);
// let normal_blend = BlendColorPairNode::new( let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))),
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), image.one_instance().instance.image.clone()
// );
// normal_blend.eval((Color::default(), Color::default()));
// use crate::raster::blend_image_tuple;
// blend_image_tuple((blank_texture, stamp), &normal_blend).await.image;
crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image
// let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend)));
// blend_executor.eval((blank_texture, stamp)).image
} }
macro_rules! inline_blend_funcs { macro_rules! inline_blend_funcs {
@ -162,7 +161,7 @@ macro_rules! inline_blend_funcs {
}; };
} }
pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrame<Color> { pub fn blend_with_mode(background: ImageFrameTable<Color>, foreground: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
let opacity = opacity / 100.; let opacity = opacity / 100.;
inline_blend_funcs!( inline_blend_funcs!(
background, background,
@ -211,21 +210,21 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> { async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
let image = image.one_item().clone();
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox(); let image_bbox = Bbox::from_transform(image_frame_table.transform()).to_axis_aligned_bbox();
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); 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 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, &draw_strokes); let mut brush_plan = cache.compute_brush_plan(image_frame_table, &draw_strokes);
let mut background_bounds = bbox.to_transform(); let mut background_bounds = bbox.to_transform();
if bounds.transform() != DAffine2::ZERO { // If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
let bounds_empty = bounds.instances().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
if bounds.transform().matrix2.determinant() != 0. && !bounds_empty {
background_bounds = bounds.transform(); background_bounds = bounds.transform();
} }
@ -289,10 +288,10 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
if has_erase_strokes { if has_erase_strokes {
let opaque_image = ImageFrame { let opaque_image = ImageFrame {
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE), image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
transform: background_bounds,
alpha_blending: Default::default(),
}; };
let mut erase_restore_mask = opaque_image; let mut erase_restore_mask = ImageFrameTable::new(opaque_image);
*erase_restore_mask.transform_mut() = background_bounds;
*erase_restore_mask.one_instance_mut().alpha_blending = Default::default();
for stroke in erase_restore_strokes { for stroke in erase_restore_strokes {
let mut brush_texture = cache.get_cached_brush(&stroke.style); let mut brush_texture = cache.get_cached_brush(&stroke.style);
@ -314,7 +313,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
); );
erase_restore_mask = blit_node.eval(erase_restore_mask).await; erase_restore_mask = blit_node.eval(erase_restore_mask).await;
} }
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => { BlendMode::Restore => {
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.)); let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.));
@ -325,7 +323,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
); );
erase_restore_mask = blit_node.eval(erase_restore_mask).await; erase_restore_mask = blit_node.eval(erase_restore_mask).await;
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -335,13 +332,14 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await; actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
} }
ImageFrameTable::new(actual_image) actual_image
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use graphene_core::raster::Bitmap;
use graphene_core::transform::Transform; use graphene_core::transform::Transform;
use glam::DAffine2; use glam::DAffine2;
@ -354,4 +352,27 @@ mod test {
// center pixel should be BLACK // center pixel should be BLACK
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK)); assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
} }
#[tokio::test]
async fn test_brush_output_size() {
let image = brush(
(),
ImageFrameTable::<Color>::default(),
ImageFrameTable::<Color>::default(),
vec![BrushStroke {
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle {
color: Color::BLACK,
diameter: 20.,
hardness: 20.,
flow: 20.,
spacing: 20.,
blend_mode: BlendMode::Normal,
},
}],
BrushCache::new_proto(),
)
.await;
assert_eq!(image.width(), 20);
}
} }

View file

@ -1,6 +1,7 @@
use graph_craft::proto::types::Percentage; use graph_craft::proto::types::Percentage;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{Color, Ctx}; use graphene_core::{Color, Ctx};
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
@ -9,7 +10,10 @@ use std::cmp::{max, min};
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> { async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
let image_frame = image_frame.one_item(); let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame = image_frame.one_instance().instance;
// Prepare the image data for processing // Prepare the image data for processing
let image = &image_frame.image; let image = &image_frame.image;
@ -30,13 +34,11 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Perc
base64_string: None, base64_string: None,
}; };
let result = ImageFrame { let mut result = ImageFrameTable::new(ImageFrame { image: dehazed_image });
image: dehazed_image, *result.transform_mut() = image_frame_transform;
transform: image_frame.transform, *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
alpha_blending: image_frame.alpha_blending,
};
ImageFrameTable::new(result) result
} }
// There is no real point in modifying these values because they do not change the final result all that much. // There is no real point in modifying these values because they do not change the final result all that much.

View file

@ -6,6 +6,8 @@ use graph_craft::proto::*;
use graphene_core::application_io::ApplicationIo; use graphene_core::application_io::ApplicationIo;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::{BlendMode, Image, Pixel}; use graphene_core::raster::{BlendMode, Image, Pixel};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::*; use graphene_core::*;
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput}; use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
@ -64,7 +66,7 @@ impl Clone for ComputePass {
#[node_macro::old_node_impl(MapGpuNode)] #[node_macro::old_node_impl(MapGpuNode)]
async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> { async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
let image_frame_table = &image; let image_frame_table = &image;
let image = image.one_item(); let image = image.one_instance().instance;
log::debug!("Executing gpu node"); log::debug!("Executing gpu node");
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap(); let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
@ -81,7 +83,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
let name = "placeholder".to_string(); let name = "placeholder".to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else { let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()"); log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrameTable::default(); return ImageFrameTable::empty();
}; };
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
log::error!("created compute pass"); log::error!("created compute pass");
@ -109,18 +111,17 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
#[cfg(feature = "image-compare")] #[cfg(feature = "image-compare")]
log::debug!("score: {:?}", score.score); log::debug!("score: {:?}", score.score);
let result = ImageFrame { let new_image = Image {
image: Image { data: colors,
data: colors, width: image.image.width,
width: image.image.width, height: image.image.height,
height: image.image.height, ..Default::default()
..Default::default()
},
transform: image.transform,
alpha_blending: image.alpha_blending,
}; };
let mut result = ImageFrameTable::new(ImageFrame { image: new_image });
*result.transform_mut() = image_frame_table.transform();
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance().alpha_blending;
ImageFrameTable::new(result) result
} }
impl<Node, EditorApi> MapGpuNode<Node, EditorApi> { impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
@ -138,7 +139,7 @@ where
GraphicElement: From<ImageFrame<T>>, GraphicElement: From<ImageFrame<T>>,
T::Static: Pixel, T::Static: Pixel,
{ {
let image = image.one_item(); let image = image.one_instance().instance;
let compiler = graph_craft::graphene_compiler::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node); let inner_network = NodeNetwork::value_network(node);
@ -280,14 +281,19 @@ where
#[node_macro::node(category("Debug: GPU"))] #[node_macro::node(category("Debug: GPU"))]
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> { async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
let foreground = foreground.one_item(); let foreground_transform = foreground.transform();
let background = background.one_item(); let background_transform = background.transform();
let background_alpha_blending = background.one_instance().alpha_blending;
let foreground = foreground.one_instance().instance;
let background = background.one_instance().instance;
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64); let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64); let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2(); let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2();
let translation: Vec2 = bg_to_fg.translation.as_vec2(); let translation: Vec2 = bg_to_fg.translation.as_vec2();
@ -334,7 +340,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect(); let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
let Ok(proto_networks_result) = proto_networks else { let Ok(proto_networks_result) = proto_networks else {
log::error!("Error compiling network in 'blend_gpu_image()"); log::error!("Error compiling network in 'blend_gpu_image()");
return ImageFrameTable::default(); return ImageFrameTable::empty();
}; };
let proto_networks = proto_networks_result; let proto_networks = proto_networks_result;
log::debug!("compiling shader"); log::debug!("compiling shader");
@ -444,16 +450,16 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let result = executor.read_output_buffer(readback_buffer).await.unwrap(); let result = executor.read_output_buffer(readback_buffer).await.unwrap();
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice()); let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
let result = ImageFrame { let created_image = Image {
image: Image { data: colors,
data: colors, width: background.image.width,
width: background.image.width, height: background.image.height,
height: background.image.height, ..Default::default()
..Default::default()
},
transform: background.transform,
alpha_blending: background.alpha_blending,
}; };
ImageFrameTable::new(result) let mut result = ImageFrameTable::new(ImageFrame { image: created_image });
*result.transform_mut() = background_transform;
*result.one_instance_mut().alpha_blending = *background_alpha_blending;
result
} }

View file

@ -16,7 +16,7 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize]; let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize]; let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
let image = image.one_item(); let image = image.one_instance().instance;
for pixel in image.image.data.iter() { for pixel in image.image.data.iter() {
let r = pixel.r() * GRID; let r = pixel.r() * GRID;
@ -79,7 +79,6 @@ mod test {
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
base64_string: None, base64_string: None,
}, },
..Default::default()
}), }),
1, 1,
); );

View file

@ -327,7 +327,7 @@ pub async fn imaginate<'a, P: Pixel>(
set_progress(ImaginateStatus::Failed(err.to_string())); set_progress(ImaginateStatus::Failed(err.to_string()));
} }
}; };
Image::empty() Image::default()
}) })
} }

View file

@ -5,8 +5,8 @@ use graphene_core::raster::{
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
Sample, Sample,
}; };
use graphene_core::transform::Transform; use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
use fastnoise_lite; use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2}; use glam::{DAffine2, DVec2, Vec2};
@ -30,7 +30,10 @@ impl From<std::io::Error> for Error {
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> { fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame = image_frame.one_item(); let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame = image_frame.one_instance().instance;
// Resize the image using the image crate // Resize the image using the image crate
let image = &image_frame.image; let image = &image_frame.image;
@ -38,7 +41,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
let footprint = ctx.footprint(); let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox();
let intersection = viewport_bounds.intersect(&image_bounds); let intersection = viewport_bounds.intersect(&image_bounds);
let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64));
let size = intersection.size(); let size = intersection.size();
@ -46,7 +49,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default(); return ImageFrameTable::empty();
} }
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
@ -81,15 +84,13 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
}; };
// we need to adjust the offset if we truncate the offset calculation // we need to adjust the offset if we truncate the offset calculation
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
let result = ImageFrame { let mut result = ImageFrameTable::new(ImageFrame { image });
image, *result.transform_mut() = new_transform;
transform: new_transform, *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
alpha_blending: image_frame.alpha_blending,
};
ImageFrameTable::new(result) result
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -256,33 +257,35 @@ fn mask_image<
// } // }
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P> async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
where where
_P: Alpha + Pixel + Debug + Send + dyn_any::StaticType,
_P::Static: Pixel,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
GraphicElement: From<ImageFrame<_P>>,
{ {
let (background, foreground) = images; let (background, foreground) = images;
blend_image(foreground, background, map_fn) blend_image(foreground, background, map_fn)
} }
fn blend_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>( fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background
foreground: Frame,
background: Background,
map_fn: &'input MapFn,
) -> Background
where where
MapFn: Node<'input, (_P, _P), Output = _P>, MapFn: Node<'input, (_P, _P), Output = _P>,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{ {
blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b))) blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b)))
} }
pub fn blend_image_closure<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>( pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background
foreground: Frame,
mut background: Background,
map_fn: MapFn,
) -> Background
where where
MapFn: Fn(_P, _P) -> _P, MapFn: Fn(_P, _P) -> _P,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{ {
let background_size = DVec2::new(background.width() as f64, background.height() as f64); let background_size = DVec2::new(background.width() as f64, background.height() as f64);
@ -319,19 +322,20 @@ pub struct ExtendImageToBoundsNode<Bounds> {
} }
#[node_macro::old_node_fn(ExtendImageToBoundsNode)] #[node_macro::old_node_fn(ExtendImageToBoundsNode)]
fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFrame<Color> { fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox(); let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.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) { if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
return image; return image;
} }
if image.image.width == 0 || image.image.height == 0 { let image_instance = image.one_instance().instance;
if image_instance.image.width == 0 || image_instance.image.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT); return empty_image((), bounds, Color::TRANSPARENT);
} }
let orig_image_scale = DVec2::new(image.image.width as f64, image.image.height as f64); let orig_image_scale = DVec2::new(image_instance.image.width as f64, image_instance.image.height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform.inverse(); let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse();
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); 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); let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
@ -341,36 +345,37 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
// Copy over original image into enlarged image. // Copy over original image into enlarged image.
let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT);
let offset_in_new_image = (-new_start).as_uvec2(); let offset_in_new_image = (-new_start).as_uvec2();
for y in 0..image.image.height { for y in 0..image_instance.image.height {
let old_start = y * image.image.width; let old_start = y * image_instance.image.width;
let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x; let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x;
let old_row = &image.image.data[old_start as usize..(old_start + image.image.width) as usize]; let old_row = &image_instance.image.data[old_start as usize..(old_start + image_instance.image.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image.image.width) as usize]; let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.image.width) as usize];
new_row.copy_from_slice(old_row); new_row.copy_from_slice(old_row);
} }
// Compute new transform. // 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 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.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
ImageFrame {
image: new_img, let mut result = ImageFrameTable::new(ImageFrame { image: new_img });
transform: new_texture_to_layer_space, *result.transform_mut() = new_texture_to_layer_space;
alpha_blending: image.alpha_blending, *result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
}
result
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
fn empty_image<P: Pixel>(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> { fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTable<Color> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; 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 height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let image = Image::new(width, height, color); let image = Image::new(width, height, color);
ImageFrame { let mut result = ImageFrameTable::new(ImageFrame { image });
image, *result.transform_mut() = transform;
transform, *result.one_instance_mut().alpha_blending = AlphaBlending::default();
alpha_blending: AlphaBlending::default(),
} result
} }
// #[cfg(feature = "serde")] // #[cfg(feature = "serde")]
@ -510,7 +515,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default(); return ImageFrameTable::empty();
} }
let footprint_scale = footprint.scale(); let footprint_scale = footprint.scale();
@ -554,13 +559,11 @@ fn noise_pattern(
} }
} }
let result = ImageFrame { let mut result = ImageFrameTable::new(ImageFrame { image });
image, *result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), *result.one_instance_mut().alpha_blending = AlphaBlending::default();
alpha_blending: AlphaBlending::default(),
};
return ImageFrameTable::new(result); return result;
} }
}; };
noise.set_noise_type(Some(noise_type)); noise.set_noise_type(Some(noise_type));
@ -618,13 +621,11 @@ fn noise_pattern(
} }
} }
let result = ImageFrame { let mut result = ImageFrameTable::new(ImageFrame { image });
image, *result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), *result.one_instance_mut().alpha_blending = AlphaBlending::default();
alpha_blending: AlphaBlending::default(),
};
ImageFrameTable::new(result) result
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
@ -640,7 +641,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default(); return ImageFrameTable::empty();
} }
let scale = footprint.scale(); let scale = footprint.scale();
@ -662,18 +663,17 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
} }
} }
let result = ImageFrame { let image = Image {
image: Image { width,
width, height,
height, data,
data, ..Default::default()
..Default::default()
},
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: Default::default(),
}; };
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = Default::default();
ImageFrameTable::new(result) result
} }
#[inline(always)] #[inline(always)]

View file

@ -1,8 +1,9 @@
use bezier_rs::{ManipulatorGroup, Subpath}; use bezier_rs::{ManipulatorGroup, Subpath};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::misc::BooleanOperation;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::Fill;
pub use graphene_core::vector::*; pub use graphene_core::vector::*;
use graphene_core::{transform::Transform, GraphicGroup};
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
pub use path_bool as path_bool_lib; pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation}; use path_bool::{FillRule, PathBooleanOperation};
@ -12,7 +13,7 @@ use std::ops::Mul;
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable { async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData { fn vector_from_image<T: Transform>(image_frame: T) -> VectorDataTable {
let corner1 = DVec2::ZERO; let corner1 = DVec2::ZERO;
let corner2 = DVec2::new(1., 1.); let corner2 = DVec2::new(1., 1.);
@ -22,18 +23,14 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
let mut vector_data = VectorData::from_subpath(subpath); let mut vector_data = VectorData::from_subpath(subpath);
vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb())); vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb()));
vector_data VectorDataTable::new(vector_data)
} }
fn union_vector_data(graphic_element: &GraphicElement) -> VectorData { fn union_vector_data(graphic_element: &GraphicElement) -> VectorDataTable {
match graphic_element { match graphic_element {
GraphicElement::VectorData(vector_data) => { GraphicElement::VectorData(vector_data) => vector_data.clone(),
let vector_data = vector_data.one_item();
vector_data.clone()
}
// Union all vector data in the graphic group into a single vector // Union all vector data in the graphic group into a single vector
GraphicElement::GraphicGroup(graphic_group) => { GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item();
let vector_data = collect_vector_data(graphic_group); let vector_data = collect_vector_data(graphic_group);
boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union) boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union)
@ -42,28 +39,32 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
} }
} }
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> { fn collect_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
let graphic_group = graphic_group_table.one_instance();
// Ensure all non vector data in the graphic group is converted to vector data // Ensure all non vector data in the graphic group is converted to vector data
let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element)); let vector_data_tables = graphic_group.instance.iter().map(|(element, _)| union_vector_data(element));
// Apply the transform from the parent graphic group // Apply the transform from the parent graphic group
let transformed_vector_data = vector_data.map(|mut vector_data| { let transformed_vector_data = vector_data_tables.map(|mut vector_data_table| {
vector_data.transform = graphic_group.transform * vector_data.transform; *vector_data_table.transform_mut() = graphic_group.transform() * vector_data_table.transform();
vector_data vector_data_table
}); });
transformed_vector_data.collect::<Vec<_>>() transformed_vector_data.collect::<Vec<_>>()
} }
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorData>) -> VectorData { fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
let mut vector_data = vector_data.into_iter(); let mut vector_data = vector_data.into_iter();
let mut result = vector_data.next().cloned().unwrap_or_default(); let mut result = vector_data.next().cloned().unwrap_or_default();
let mut next_vector_data = vector_data.next(); let mut next_vector_data = vector_data.next();
while let Some(lower_vector_data) = next_vector_data { while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(&result, DAffine2::IDENTITY); let result = result.one_instance_mut().instance;
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
@ -76,49 +77,58 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
next_vector_data = vector_data.next(); next_vector_data = vector_data.next();
} }
result result
} }
fn boolean_operation_on_vector_data(vector_data: &[VectorData], boolean_operation: BooleanOperation) -> VectorData { fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable {
match boolean_operation { match boolean_operation {
BooleanOperation::Union => { BooleanOperation::Union => {
// Reverse vector data so that the result style is the style of the first vector data // Reverse vector data so that the result style is the style of the first vector data
let mut vector_data = vector_data.iter().rev(); let mut vector_data_table = vector_data_table.iter().rev();
let mut result = vector_data.next().cloned().unwrap_or_default(); let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() }));
// Loop over all vector data and union it with the result // Loop over all vector data and union it with the result
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default));
while let Some(lower_vector_data) = second_vector_data { while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(&result, DAffine2::IDENTITY); let result_vector_data = result_vector_data_table.one_instance_mut().instance;
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string); let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators; result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain; result_vector_data.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain; result_vector_data.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain; result_vector_data.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
second_vector_data = vector_data_table.next();
} }
result
result_vector_data_table
} }
BooleanOperation::SubtractFront => subtract(vector_data.iter()), BooleanOperation::SubtractFront => subtract(vector_data_table.iter()),
BooleanOperation::SubtractBack => subtract(vector_data.iter().rev()), BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()),
BooleanOperation::Intersect => { BooleanOperation::Intersect => {
let mut vector_data = vector_data.iter().rev(); let mut vector_data = vector_data_table.iter().rev();
let mut result = vector_data.next().cloned().unwrap_or_default(); let mut result = vector_data.next().cloned().unwrap_or_default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() })); let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(&default));
// For each vector data, set the result to the intersection of that data and the result // 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 { while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(&result, DAffine2::IDENTITY); let result = result.one_instance_mut().instance;
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@ -130,63 +140,67 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
result.region_domain = boolean_operation_result.region_domain; result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next(); second_vector_data = vector_data.next();
} }
result result
} }
BooleanOperation::Difference => { BooleanOperation::Difference => {
let mut vector_data_iter = vector_data.iter().rev(); let mut vector_data_iter = vector_data_table.iter().rev();
let mut any_intersection = VectorData::empty(); let mut any_intersection = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(const { &VectorData::empty() })); let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default));
// Find where all vector data intersect at least once // Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data { while let Some(lower_vector_data) = second_vector_data {
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union); let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
let all_other_vector_data_instance = all_other_vector_data.one_instance();
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform.inverse() * lower_vector_data.transform; let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(&all_other_vector_data, DAffine2::IDENTITY); let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut boolean_intersection_result = from_path(&boolean_intersection_string); let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
*boolean_intersection_result.transform_mut() = all_other_vector_data_instance.transform();
boolean_intersection_result.transform = all_other_vector_data.transform; boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
boolean_intersection_result.style = all_other_vector_data.style.clone(); *boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
boolean_intersection_result.alpha_blending = all_other_vector_data.alpha_blending;
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform; let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform().inverse() * any_intersection.transform();
let upper_path_string = to_path(&boolean_intersection_result, DAffine2::IDENTITY); let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
let lower_path_string = to_path(&any_intersection, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) }); let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
any_intersection = union_result; *any_intersection.one_instance_mut().instance = union_result;
any_intersection.transform = boolean_intersection_result.transform; *any_intersection.transform_mut() = boolean_intersection_result.transform();
any_intersection.style = boolean_intersection_result.style.clone(); any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending; any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending;
second_vector_data = vector_data_iter.next(); second_vector_data = vector_data_iter.next();
} }
// Subtract the area where they intersect at least once from the union of all vector data // Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data(vector_data, BooleanOperation::Union); let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union);
boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront) boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront)
} }
} }
} }
let group_of_paths = group_of_paths.one_item();
// The first index is the bottom of the stack // The first index is the bottom of the stack
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation); let mut result_vector_data_table = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation);
let transform = boolean_operation_result.transform; // Replace the transformation matrix with a mutation of the vector points themselves
VectorData::transform(&mut boolean_operation_result, transform); let result_vector_data_table_transform = result_vector_data_table.transform();
boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY); *result_vector_data_table.transform_mut() = DAffine2::IDENTITY;
boolean_operation_result.transform = DAffine2::IDENTITY; let result_vector_data = result_vector_data_table.one_instance_mut().instance;
boolean_operation_result.upstream_graphic_group = Some(GraphicGroupTable::new(group_of_paths.clone())); VectorData::transform(result_vector_data, result_vector_data_table_transform);
result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.upstream_graphic_group = Some(group_of_paths.clone());
VectorDataTable::new(boolean_operation_result) result_vector_data_table
} }
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> { fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {

View file

@ -11,6 +11,8 @@ use graphene_core::raster::Image;
use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
#[cfg(target_arch = "wasm32")]
use graphene_core::transform::TransformMut;
use graphene_core::vector::VectorDataTable; use graphene_core::vector::VectorDataTable;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
@ -40,7 +42,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
// image: ImageFrameTable<graphene_core::raster::SRGBA8>, // image: ImageFrameTable<graphene_core::raster::SRGBA8>,
// surface_handle: Arc<WasmSurfaceHandle>, // surface_handle: Arc<WasmSurfaceHandle>,
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> { // ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
// let image = image.one_item(); // let image = image.one_instance().instance;
// let image_data = image.image.data; // let image_data = image.image.data;
// let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice())); // let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
// if image.image.width > 0 && image.image.height > 0 { // if image.image.width > 0 && image.image.height > 0 {
@ -76,7 +78,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
#[node_macro::node(category("Network"))] #[node_macro::node(category("Network"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> { fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return ImageFrameTable::default(); return ImageFrameTable::empty();
}; };
let image = image.to_rgba32f(); let image = image.to_rgba32f();
let image = ImageFrame { let image = ImageFrame {
@ -86,8 +88,6 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
height: image.height(), height: image.height(),
..Default::default() ..Default::default()
}, },
transform: glam::DAffine2::IDENTITY,
alpha_blending: Default::default(),
}; };
ImageFrameTable::new(image) ImageFrameTable::new(image)
@ -130,7 +130,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
let mut child = Scene::new(); let mut child = Scene::new();
let mut context = wgpu_executor::RenderContext::default(); let mut context = wgpu_executor::RenderContext::default();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context); data.render_to_vello(&mut child, Default::default(), &mut context);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost // TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array()))); scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
@ -167,7 +167,7 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
) -> ImageFrameTable<Color> { ) -> ImageFrameTable<Color> {
if footprint.transform.matrix2.determinant() == 0. { if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization"); log::trace!("Invalid footprint received for rasterization");
return ImageFrameTable::default(); return ImageFrameTable::empty();
} }
let mut render = SvgRender::new(); let mut render = SvgRender::new();
@ -204,13 +204,12 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap(); let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let result = ImageFrame { let mut result = ImageFrameTable::new(ImageFrame {
image: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32), image: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
transform: footprint.transform, });
..Default::default() *result.transform_mut() = footprint.transform;
};
ImageFrameTable::new(result) result
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
@ -242,8 +241,10 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
let data = data.eval(ctx.clone()).await; let data = data.eval(ctx.clone()).await;
let editor_api = editor_api.eval(ctx.clone()).await; let editor_api = editor_api.eval(ctx.clone()).await;
#[cfg(all(feature = "vello", target_arch = "wasm32"))] #[cfg(all(feature = "vello", target_arch = "wasm32"))]
let surface_handle = _surface_handle.eval(ctx.clone()).await; let surface_handle = _surface_handle.eval(ctx.clone()).await;
let use_vello = editor_api.editor_preferences.use_vello(); let use_vello = editor_api.editor_preferences.use_vello();
#[cfg(all(feature = "vello", target_arch = "wasm32"))] #[cfg(all(feature = "vello", target_arch = "wasm32"))]
let use_vello = use_vello && surface_handle.is_some(); let use_vello = use_vello && surface_handle.is_some();

View file

@ -15,7 +15,7 @@ use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureW
use graphene_std::application_io::TextureFrame; use graphene_std::application_io::TextureFrame;
use graphene_std::wasm_application_io::*; use graphene_std::wasm_application_io::*;
use graphene_std::Context; use graphene_std::Context;
use graphene_std::{GraphicElement, GraphicGroup}; use graphene_std::GraphicElement;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
use wgpu_executor::{WgpuSurface, WindowHandle}; use wgpu_executor::{WgpuSurface, WindowHandle};
@ -261,7 +261,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), 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 => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]), 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 => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]

View file

@ -916,7 +916,7 @@ async fn render_texture<'a: 'n>(
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrame { async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrame {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect(); // let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let input = input.one_item(); let input = input.one_instance().instance;
let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect(); let new_data: Vec<SRGBA8> = input.image.data.iter().map(|x| (*x).into()).collect();
let new_image = Image { let new_image = Image {
width: input.image.width, width: input.image.width,
@ -934,7 +934,8 @@ async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFram
TextureFrame { TextureFrame {
texture: texture.into(), texture: texture.into(),
transform: input.transform, // TODO: Find an alternate way to encode the transform and alpha_blend now that these fields have been moved up out of TextureFrame
alpha_blend: Default::default(), // transform: input.transform,
// alpha_blend: Default::default(),
} }
} }