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

* Instances -> Table

* instances.rs -> table.rs

* Rename occurrances of the word "instances"

* .instance -> .element

* Instance* -> TableRow*

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

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

* Rename occurences of the word "instance"

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

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

* Organize the TaggedValues

* Fix tests

* Fix prior regression with Image Value node not upgrading
This commit is contained in:
Keavon Chambers 2025-08-03 04:12:18 -07:00 committed by GitHub
parent 67123f55dc
commit a0ce56d9b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1906 additions and 2061 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -182,7 +182,7 @@ pub enum DocumentMessage {
UpdateUpstreamTransforms {
upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>,
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
first_element_source_id: HashMap<NodeId, Option<NodeId>>,
},
UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>,

View File

@ -32,7 +32,8 @@ use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::math::quad::Quad;
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{Raster, RasterDataTable};
use graphene_std::raster_types::Raster;
use graphene_std::table::Table;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
@ -837,7 +838,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(DocumentMessage::AddTransaction);
let layer = graph_modification_utils::new_image_layer(RasterDataTable::new(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
let layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
if let Some(name) = name {
responses.add(NodeGraphMessage::SetDisplayName {
@ -1302,10 +1303,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints,
local_transforms,
first_instance_source_id,
first_element_source_id,
} => {
self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id);
self.network_interface.update_first_element_source_id(first_element_source_id);
}
DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets
@ -2920,7 +2921,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), -1, "", &[]);
network_interface.add_export(TaggedValue::ArtboardGroup(Default::default()), -1, "", &[]);
network_interface
}

View File

@ -8,7 +8,8 @@ use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::PointId;
use graphene_std::vector::VectorModificationType;
@ -69,7 +70,7 @@ pub enum GraphOperationMessage {
},
NewBitmapLayer {
id: NodeId,
image_frame: RasterDataTable<CPU>,
image_frame: Table<Raster<CPU>>,
parent: LayerNodeIdentifier,
insert_index: usize,
},

View File

@ -11,12 +11,13 @@ use graph_craft::document::{NodeId, NodeInput};
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::VectorData;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
use graphene_std::{GraphicElement, NodeInputDecleration};
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
@ -130,8 +131,8 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
@ -143,7 +144,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]);
@ -153,7 +154,7 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true));
let vector_data = Table::new_from_element(VectorData::from_subpaths(subpaths, true));
let shape = resolve_document_node_type("Path")
.expect("Path node does not exist")
@ -218,7 +219,7 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
}
pub fn insert_image_data(&mut self, image_frame: RasterDataTable<CPU>, layer: LayerNodeIdentifier) {
pub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image Value")
.expect("ImageValue node does not exist")
@ -302,7 +303,7 @@ impl<'a> ModifyInputsContext<'a> {
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
if layer_input_type == concrete!(GraphicGroupTable) {
if layer_input_type == concrete!(Table<GraphicElement>) {
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
return None;

View File

@ -19,11 +19,12 @@ use graph_craft::document::*;
use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)]
use graphene_std::transform::Footprint;
use graphene_std::vector::VectorDataTable;
use graphene_std::vector::VectorData;
use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque};
@ -268,8 +269,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
],
..Default::default()
},
@ -338,7 +339,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode {
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(artboard::to_artboard::IDENTIFIER),
inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
@ -361,11 +362,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroupTable))), 0),
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(artboard::append_artboard::IDENTIFIER),
..Default::default()
},
]
@ -376,8 +377,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
@ -627,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(
TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
@ -752,7 +753,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@ -761,7 +762,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@ -770,7 +771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@ -779,7 +780,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@ -793,7 +794,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -859,13 +860,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
@ -878,7 +879,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -931,7 +932,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode {
inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
NodeInput::network(concrete!(BrushCache), 2),
],
@ -946,7 +947,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
NodeInput::value(TaggedValue::RasterData(Default::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
],
@ -985,7 +986,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1004,7 +1005,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1098,7 +1099,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
..Default::default()
@ -1116,7 +1117,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1195,7 +1196,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
// inputs: vec![
// NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
// NodeInput::value(TaggedValue::RasterData(Default::default()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ],
// ..Default::default()
@ -1218,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)),
skip_deduplication: true,
@ -1242,7 +1243,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
],
..Default::default()
@ -1373,7 +1374,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}),
),
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector data.").into(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
@ -1495,7 +1496,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
@ -1526,7 +1527,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
],
..Default::default()
@ -1593,14 +1594,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(4), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(u32), 3),
@ -1639,7 +1640,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::U32(100), false),
@ -1760,7 +1761,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2),
],
@ -1794,7 +1795,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::U32(0), false),
],

View File

@ -19,16 +19,14 @@ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
};
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::VectorDataTable;
use graphene_std::vector::misc::GridType;
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
use graphene_std::vector::VectorData;
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{GraphicElement, NodeInputDecleration};
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_holder();
@ -185,9 +183,9 @@ pub(crate) fn property_from_type(
// ====================
// GRAPHICAL DATA TYPES
// ====================
Some(x) if x == TypeId::of::<VectorDataTable>() => vector_data_widget(default_info).into(),
Some(x) if x == TypeId::of::<RasterDataTable<CPU>>() || x == TypeId::of::<RasterDataTable<GPU>>() => raster_widget(default_info).into(),
Some(x) if x == TypeId::of::<GraphicGroupTable>() => group_widget(default_info).into(),
Some(x) if x == TypeId::of::<Table<VectorData>>() => vector_data_widget(default_info).into(),
Some(x) if x == TypeId::of::<Table<Raster<CPU>>>() || x == TypeId::of::<Table<Raster<GPU>>>() => raster_widget(default_info).into(),
Some(x) if x == TypeId::of::<Table<GraphicElement>>() => group_widget(default_info).into(),
// ============
// STRUCT TYPES
// ============

View File

@ -18,18 +18,17 @@ pub enum FrontendGraphDataType {
impl FrontendGraphDataType {
pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::U32(_)
| TaggedValue::U64(_)
| TaggedValue::F64(_)
| TaggedValue::DVec2(_)
| TaggedValue::OptionalDVec2(_)
| TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_)
| TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
TaggedValue::GraphicGroup(_) => Self::Group,
TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General,
}

View File

@ -91,10 +91,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
}
#[cfg(all(not(target_family = "wasm"), test))]
OverlaysMessage::Draw => {
// Removes unused warnings in test builds
drop(responses);
drop(context);
drop(super::utility_types::OverlayContext::new(ipp.viewport_bounds.size(), device_pixel_ratio, visibility_settings));
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
}
OverlaysMessage::AddProvider(message) => {
self.overlay_providers.insert(message);

View File

@ -22,7 +22,7 @@ use std::num::NonZeroU64;
pub struct DocumentMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
pub first_element_source_ids: HashMap<NodeId, Option<NodeId>>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
@ -91,7 +91,7 @@ impl DocumentMetadata {
let mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
if let Some(&source) = self.first_element_source_ids.get(&layer.to_node()) {
if !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
.any(|upstream| Some(upstream) == source)

View File

@ -13,7 +13,9 @@ use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::{Type, concrete};
use graphene_std::Artboard;
use graphene_std::math::quad::Quad;
use graphene_std::table::Table;
use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
@ -507,8 +509,8 @@ impl NodeNetworkInterface {
InputConnector::Node { node_id, input_index } => (node_id, input_index),
InputConnector::Export(export_index) => {
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else {
// The outermost network export defaults to an ArtboardGroupTable.
return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault));
// The outermost network export defaults to a Table<Artboard>.
return Some((concrete!(Table<Artboard>), TypeSource::OuterMostExportDefault));
};
let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path);
@ -3558,8 +3560,8 @@ impl NodeNetworkInterface {
}
/// Update the cached first instance source id of the layers
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_instance_source_ids = new;
pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_element_source_ids = new;
}
/// Update the cached click targets of the layers
@ -6609,11 +6611,22 @@ struct InputTransientMetadata {
fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
use serde::Deserialize;
const REPLACEMENTS: [(&str, &str); 4] = [
("VectorData", "Instances<VectorData>"),
("GraphicGroup", "Instances<GraphicGroup>"),
("ImageFrame", "Instances<Image>"),
("Instances<ImageFrame>", "Instances<Image>"),
const REPLACEMENTS: [(&str, &str); 12] = [
// Single to table data
("VectorData", "Table<VectorData>"),
("GraphicGroup", "Table<GraphicGroup>"),
("ImageFrame", "Table<Image>"),
// `ImageFrame` to `Image` rename
("Instances<ImageFrame>", "Table<Image>"),
// `Instances` to `Table` rename
("Instances<VectorData>", "Table<VectorData>"),
("Instances<GraphicGroup>", "Table<GraphicGroup>"),
("Instances<Image>", "Table<Image>"),
("Instances<GraphicElement>", "Table<GraphicElement>"),
("Future<Instances<VectorData>>", "Future<Table<VectorData>>"),
("Future<Instances<GraphicGroup>>", "Future<Table<GraphicGroup>>"),
("Future<Instances<Image>>", "Future<Table<Image>>"),
("Future<Instances<GraphicElement>>", "Future<Table<GraphicElement>>"),
];
let mut names = Vec::<String>::deserialize(deserializer)?;

View File

@ -10,10 +10,11 @@ use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::ProtoNodeIdentifier;
use graphene_std::table::Table;
use graphene_std::text::{TextAlign, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::VectorData;
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
use graphene_std::vector::{VectorData, VectorDataTable};
use std::collections::HashMap;
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
@ -29,12 +30,12 @@ pub struct NodeReplacement<'a> {
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
// graphic element
NodeReplacement {
node: graphene_std::graphic_element::append_artboard::IDENTIFIER,
aliases: &["graphene_core::AddArtboardNode"],
node: graphene_std::artboard::append_artboard::IDENTIFIER,
aliases: &["graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_artboard::IDENTIFIER,
aliases: &["graphene_core::ConstructArtboardNode"],
node: graphene_std::artboard::to_artboard::IDENTIFIER,
aliases: &["graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_element::IDENTIFIER,
@ -386,7 +387,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
aliases: &["graphene_std::raster::ImageValueNode"],
aliases: &["graphene_std::raster::ImageValueNode", "graphene_std::raster::ImageNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
@ -616,7 +617,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None;
};
let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorData(Table::new_from_element(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]);

View File

@ -54,7 +54,7 @@ pub struct PortfolioMessageHandler {
pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor,
pub selection_mode: SelectionMode,
/// The spreadsheet UI allows for instance data to be previewed.
/// The spreadsheet UI allows for graph data to be previewed.
pub spreadsheet: SpreadsheetMessageHandler,
device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool,

View File

@ -1,7 +1,7 @@
use crate::messages::prelude::*;
use crate::node_graph_executor::InspectResult;
/// The spreadsheet UI allows for instance data to be previewed.
/// The spreadsheet UI allows for graph data to be previewed.
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum SpreadsheetMessage {
@ -12,10 +12,10 @@ pub enum SpreadsheetMessage {
inspect_result: InspectResult,
},
PushToInstancePath {
PushToElementPath {
index: usize,
},
TruncateInstancePath {
TruncateElementPath {
len: usize,
},

View File

@ -5,23 +5,22 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::Context;
use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances;
use graphene_std::memo::IORecord;
use graphene_std::raster::Image;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
use graphene_std::{Artboard, GraphicElement};
use std::any::Any;
use std::sync::Arc;
/// The spreadsheet UI allows for instance data to be previewed.
/// The spreadsheet UI allows for graph data to be previewed.
#[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool,
inspect_node: Option<NodeId>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
instances_path: Vec<usize>,
element_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain,
}
@ -46,12 +45,12 @@ impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
self.update_layout(responses)
}
SpreadsheetMessage::PushToInstancePath { index } => {
self.instances_path.push(index);
SpreadsheetMessage::PushToElementPath { index } => {
self.element_path.push(index);
self.update_layout(responses);
}
SpreadsheetMessage::TruncateInstancePath { len } => {
self.instances_path.truncate(len);
SpreadsheetMessage::TruncateElementPath { len } => {
self.element_path.truncate(len);
self.update_layout(responses);
}
@ -78,7 +77,7 @@ impl SpreadsheetMessageHandler {
}
let mut layout_data = LayoutData {
current_depth: 0,
desired_path: &mut self.instances_path,
desired_path: &mut self.element_path,
breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain,
};
@ -91,7 +90,7 @@ impl SpreadsheetMessageHandler {
if layout_data.breadcrumbs.len() > 1 {
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
.on_update(|&len| SpreadsheetMessage::TruncateInstancePath { len: len as usize }.into())
.on_update(|&len| SpreadsheetMessage::TruncateElementPath { len: len as usize }.into())
.widget_holder();
layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] });
}
@ -113,17 +112,17 @@ struct LayoutData<'a> {
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// We simply try random types. TODO: better strategy.
#[allow(clippy::manual_map)]
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, ArtboardGroupTable>>() {
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), ArtboardGroupTable>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Artboard>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<VectorData>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, GraphicGroupTable>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<GraphicElement>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), GraphicGroupTable>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<GraphicElement>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else {
None
@ -139,7 +138,7 @@ fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row { widgets: error }]
}
trait InstanceLayout {
trait TableRowLayout {
fn type_name() -> &'static str;
fn identifier(&self) -> String;
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
@ -149,14 +148,14 @@ trait InstanceLayout {
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>;
}
impl InstanceLayout for GraphicElement {
impl TableRowLayout for GraphicElement {
fn type_name() -> &'static str {
"GraphicElement"
}
fn identifier(&self) -> String {
match self {
Self::GraphicGroup(instances) => instances.identifier(),
Self::VectorData(instances) => instances.identifier(),
Self::GraphicGroup(table) => table.identifier(),
Self::VectorData(table) => table.identifier(),
Self::RasterDataCPU(_) => "RasterDataCPU".to_string(),
Self::RasterDataGPU(_) => "RasterDataGPU".to_string(),
}
@ -167,15 +166,15 @@ impl InstanceLayout for GraphicElement {
}
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
match self {
Self::GraphicGroup(instances) => instances.layout_with_breadcrumb(data),
Self::VectorData(instances) => instances.layout_with_breadcrumb(data),
Self::GraphicGroup(table) => table.layout_with_breadcrumb(data),
Self::VectorData(table) => table.layout_with_breadcrumb(data),
Self::RasterDataCPU(_) => label("Raster frame not supported"),
Self::RasterDataGPU(_) => label("Raster frame not supported"),
}
}
}
impl InstanceLayout for VectorData {
impl TableRowLayout for VectorData {
fn type_name() -> &'static str {
"VectorData"
}
@ -243,7 +242,7 @@ impl InstanceLayout for VectorData {
}
}
impl InstanceLayout for Image<Color> {
impl TableRowLayout for Image<Color> {
fn type_name() -> &'static str {
"Image"
}
@ -256,7 +255,7 @@ impl InstanceLayout for Image<Color> {
}
}
impl InstanceLayout for Artboard {
impl TableRowLayout for Artboard {
fn type_name() -> &'static str {
"Artboard"
}
@ -268,18 +267,18 @@ impl InstanceLayout for Artboard {
}
}
impl<T: InstanceLayout> InstanceLayout for Instances<T> {
impl<T: TableRowLayout> TableRowLayout for Table<T> {
fn type_name() -> &'static str {
"Instances"
"Table"
}
fn identifier(&self) -> String {
format!("Instances<{}> (length={})", T::type_name(), self.len())
format!("Table<{}> (length={})", T::type_name(), self.len())
}
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
if let Some(instance) = self.get(index) {
if let Some(row) = self.get(index) {
data.current_depth += 1;
let result = instance.instance.layout_with_breadcrumb(data);
let result = row.element.layout_with_breadcrumb(data);
data.current_depth -= 1;
return result;
} else {
@ -289,16 +288,16 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
}
let mut rows = self
.instance_ref_iter()
.iter_ref()
.enumerate()
.map(|(index, instance)| {
let (scale, angle, translation) = instance.transform.to_scale_angle_translation();
.map(|(index, row)| {
let (scale, angle, translation) = row.transform.to_scale_angle_translation();
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
let round = |x: f64| (x * 1e3).round() / 1e3;
vec![
TextLabel::new(format!("{}", index)).widget_holder(),
TextButton::new(instance.instance.identifier())
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
TextButton::new(row.element.identifier())
.on_update(move |_| SpreadsheetMessage::PushToElementPath { index }.into())
.widget_holder(),
TextLabel::new(format!(
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
@ -308,15 +307,15 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
round(scale.y)
))
.widget_holder(),
TextLabel::new(format!("{}", instance.alpha_blending)).widget_holder(),
TextLabel::new(instance.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
]
})
.collect::<Vec<_>>();
rows.insert(0, column_headings(&["", "instance", "transform", "alpha_blending", "source_node_id"]));
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
let instances = vec![TextLabel::new("Instances:").widget_holder()];
vec![LayoutGroup::Row { widgets: instances }, LayoutGroup::Table { rows }]
let table = vec![TextLabel::new("Table:").widget_holder()];
vec![LayoutGroup::Row { widgets: table }, LayoutGroup::Table { rows }]
}
}

View File

@ -11,7 +11,8 @@ use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color;
use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::Gradient;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
@ -211,7 +212,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
}
/// Create a new bitmap layer.
pub fn new_image_layer(image_frame: RasterDataTable<CPU>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
pub fn new_image_layer(image_frame: Table<Raster<CPU>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0;
responses.add(GraphOperationMessage::NewBitmapLayer {
id,
@ -475,6 +476,6 @@ impl<'a> NodeGraphLayer<'a> {
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone();
layer_input_type == concrete!(RasterDataTable<CPU>) || layer_input_type == concrete!(RasterDataTable<GPU>)
layer_input_type == concrete!(Table<Raster<CPU>>) || layer_input_type == concrete!(Table<Raster<GPU>>)
}
}

View File

@ -14,8 +14,9 @@ use glam::{DAffine2, DVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad;
use graphene_std::table::Table;
use graphene_std::text::{FontCache, load_font};
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorDataTable, VectorModification, VectorModificationType};
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModification, VectorModificationType};
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
@ -76,7 +77,7 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
let font_data = font_cache.get(font).map(|data| load_font(data));
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
// TODO: Once the instances refactor is complete and per_glyph_instances can be removed (since it'll be the default),
// TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
let vertical_offset = if per_glyph_instances {
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
@ -599,13 +600,13 @@ pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, m
return None;
}
// Must be a layer of type VectorDataTable
// Must be a layer of type Table<VectorData>
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
.horizontal_layer_flow()
.nth(1)
.map(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
output_type.nested_type() == concrete!(VectorDataTable).nested_type()
output_type.nested_type() == concrete!(Table<VectorData>).nested_type()
})
.unwrap_or_default();
if !compatible_type {

View File

@ -12,6 +12,7 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId;
use graphene_std::renderer::Quad;
use graphene_std::table::Table;
#[derive(Default, ExtractField)]
pub struct ArtboardTool {
@ -337,7 +338,7 @@ impl Fsm for ArtboardToolFsmState {
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_std::Artboard {
graphic_group: graphene_std::GraphicGroupTable::default(),
graphic_group: Table::new(),
label: String::from("Artboard"),
location: start.min(end).round().as_ivec2(),
dimensions: (start.round() - end.round()).abs().as_ivec2(),
@ -568,7 +569,7 @@ mod test_artboard {
Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e),
};
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
instrumented.grab_all_input::<graphene_std::artboard::append_artboard::ArtboardInput>(&editor.runtime).collect()
}
#[tokio::test]

View File

@ -388,7 +388,7 @@ impl NodeGraphExecutor {
let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
first_element_source_id,
click_targets,
clip_targets,
} = render_output_metadata;
@ -397,7 +397,7 @@ impl NodeGraphExecutor {
responses.add(DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
first_element_source_id,
});
responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });

View File

@ -9,13 +9,13 @@ use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Context;
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::instances::Instance;
use graphene_std::memo::IORecord;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache;
use graphene_std::vector::VectorData;
use graphene_std::vector::style::ViewMode;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
use interpreted_executor::util::wrap_network_in_scope;
@ -313,12 +313,10 @@ impl NodeRuntime {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
let default = Instance::default();
self.vector_modify.insert(
parent_network_node_id,
record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(),
);
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
let default = TableRow::default();
self.vector_modify
.insert(parent_network_node_id, record.output.iter_ref().next().unwrap_or_else(|| default.as_ref()).element.clone());
} else {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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