Instance tables refactor part 6: unwrap VectorData and ImageFrame from single-row to multi-row tables (#2684)

* Start refactoring the boolean operations code

* Switch to iterators in the boolean operations code

* Make boolean operations work on rows of a table, not Vecs of single-row tables

* Remove more .transform()

* Simplify brush code

* Attempt to remove .transform() by using Instance<Image<Color>> in brush code, but a regression is introduced

* Improve blend_image_closure

* Simplify

* Remove leading underscore from type arguments

* Remove .transform() from ImageFrameTable<P> and fix Mask node behavior on stencils not fully overlapping its target image

* Remove more .one_instance_ref()

* Fully remove .one_instance_ref() and improve the 'Combine Channels' node robustness

* Fully remove .once_instance_mut()

* Fix tests

* Remove .one_empty_image()

* Make Instances<T>::default() return an empty table for images, but still not yet vector

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers 2025-06-04 20:40:15 -07:00 committed by GitHub
parent 76ecdc8f1b
commit cb4289169d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 882 additions and 811 deletions

View File

@ -3292,7 +3292,7 @@ mod document_message_handler_tests {
let document = editor.active_document();
let rect_bbox_before = document.metadata().bounding_box_viewport(rect_layer).unwrap();
// Moving rectangle from folder1 --> folder2
// Moving rectangle from folder1 to folder2
editor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder2, insert_index: 0 }).await;
// Rectangle's viewport position after moving
@ -3304,6 +3304,10 @@ mod document_message_handler_tests {
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.;
let distance = before_center.distance(after_center);
assert!(distance < 1., "Rectangle should maintain its viewport position after moving between transformed groups");
assert!(
distance < 1.,
"Rectangle should maintain its viewport position after moving between transformed groups\n\
Before: {before_center:?}, After: {after_center:?}, Distance: {distance} (should be < 1)"
);
}
}

View File

@ -413,13 +413,10 @@ impl<'a> ModifyInputsContext<'a> {
pub fn transform_set_direct(&mut self, transform: DAffine2, skip_rerender: bool, transform_node_id: Option<NodeId>) {
// If the Transform node didn't exist yet, create it now
let Some(transform_node_id) = transform_node_id.or_else(|| {
// Check if the transform is the identity transform and if so, don't create a new Transform node
if let Some((scale, angle, translation)) = (transform.matrix2.determinant() != 0.).then(|| transform.to_scale_angle_translation()) {
// Check if the transform is the identity transform within an epsilon
if scale.x.abs() < 1e-6 && scale.y.abs() < 1e-6 && angle.abs() < 1e-6 && translation.x.abs() < 1e-6 && translation.y.abs() < 1e-6 {
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
return None;
}
// Check if the transform is the identity transform (within an epsilon) and if so, don't create a new Transform node
if transform.abs_diff_eq(DAffine2::IDENTITY, 1e-6) {
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
return None;
}
// Create the Transform node
@ -453,7 +450,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
let Some(brush_node_id) = self.existing_node_id("Brush", true) else { return };
self.set_input_with_refresh(InputConnector::node(brush_node_id, 2), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);
self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);
}
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {

View File

@ -636,7 +636,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -893,7 +893,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -978,7 +978,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1019,6 +1019,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Remove this and just use the proto node definition directly
DocumentNodeDefinition {
identifier: "Brush",
category: "Raster",
@ -1029,9 +1030,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![DocumentNode {
inputs: vec![
NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
NodeInput::network(concrete!(ImageFrameTable<Color>), 1),
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
NodeInput::network(concrete!(BrushCache), 3),
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 1),
NodeInput::network(concrete!(BrushCache), 2),
],
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
@ -1044,15 +1044,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Background", "TODO").into(), ("Bounds", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1084,7 +1083,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1103,7 +1102,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1803,7 +1802,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -2685,7 +2684,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// ..Default::default()
// }),
// inputs: vec![
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
// NodeInput::scope("editor-api"),
// NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
// NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index

View File

@ -635,17 +635,17 @@ impl<'a> Selected<'a> {
}
pub fn apply_transformation(&mut self, transformation: DAffine2, transform_operation: Option<TransformOperation>) {
if !self.selected.is_empty() {
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for layer in self.network_interface.shallowest_unique_layers(&[]) {
match &mut self.original_transforms {
OriginalTransforms::Layer(layer_transforms) => {
Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses)
}
OriginalTransforms::Path(path_transforms) => {
if let Some(initial_points) = path_transforms.get_mut(&layer) {
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses, transform_operation)
}
if self.selected.is_empty() {
return;
}
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for layer in self.network_interface.shallowest_unique_layers(&[]) {
match &mut self.original_transforms {
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses),
OriginalTransforms::Path(path_transforms) => {
if let Some(initial_points) = path_transforms.get_mut(&layer) {
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses, transform_operation)
}
}
}

View File

@ -930,6 +930,23 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
// We have removed the last input, so we don't add index 3
}
if reference == "Brush" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
}
}
// TODO: Eventually remove this document upgrade code

View File

@ -276,15 +276,16 @@ impl BrushToolData {
let Some(reference) = document.network_interface.reference(&node_id, &[]) else {
continue;
};
if *reference == Some("Brush".to_string()) && node_id != layer.to_node() {
let points_input = node.inputs.get(2)?;
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
continue;
};
let points_input = node.inputs.get(1)?;
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else { continue };
self.strokes.clone_from(strokes);
return Some(layer);
} else if *reference == Some("Transform".to_string()) {
}
if *reference == Some("Transform".to_string()) {
let upstream = document.metadata().upstream_transform(node_id);
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;

View File

@ -397,31 +397,28 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
return;
}
let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else {
selected.original_transforms.clear();
return;
};
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
if let [point] = selected_points.as_slice() {
if matches!(point, ManipulatorPointId::Anchor(_)) {
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
let handle1_length = handle1.length(&vector_data);
let handle2_length = handle2.length(&vector_data);
if let [point] = selected_points.as_slice() {
if matches!(point, ManipulatorPointId::Anchor(_)) {
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
let handle1_length = handle1.length(&vector_data);
let handle2_length = handle2.length(&vector_data);
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
// G should work for this point but not R and S
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
selected.original_transforms.clear();
return;
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
// G should work for this point but not R and S
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
selected.original_transforms.clear();
return;
}
}
}
}
} else {
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
} else {
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
if handle_length == Some(0.) {
selected.original_transforms.clear();
return;
if handle_length == Some(0.) {
selected.original_transforms.clear();
return;
}
}
}
}

View File

@ -14,6 +14,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_core::text::FontCache;
use graphene_core::vector::style::ViewMode;
use graphene_std::Context;
use graphene_std::instances::Instance;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
@ -293,9 +294,16 @@ impl NodeRuntime {
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>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_instance_ref().instance.clone());
let default = Instance {
instance: VectorData::empty(),
..Default::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 {
log::warn!("failed to downcast monitor node output {parent_network_node_id:?}");
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
}
}
}

View File

@ -1,6 +1,6 @@
use crate::instances::Instances;
use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::transform::Footprint;
use crate::vector::style::ViewMode;
use alloc::sync::Arc;
use core::fmt::Debug;
@ -37,17 +37,6 @@ impl Hash for SurfaceFrame {
}
}
impl Transform for SurfaceFrame {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for SurfaceFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
#[cfg(feature = "dyn-any")]
unsafe impl StaticType for SurfaceFrame {
type Static = SurfaceFrame;
@ -152,18 +141,6 @@ unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {
type Static = SurfaceHandleFrame<T>;
}
impl<T> Transform for SurfaceHandleFrame<T> {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl<T> TransformMut for SurfaceHandleFrame<T> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// TODO: think about how to automatically clean up memory
/*
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {

View File

@ -277,13 +277,13 @@ pub trait GraphicElementRendered {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]>;
// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
// TODO: Store all click targets in a vec which contains the AABB, click target, and path
// fn add_click_targets(&self, click_targets: &mut Vec<([DVec2; 2], ClickTarget, Vec<NodeId>)>, current_path: Option<NodeId>) {}
// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify
/// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify.
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
fn contains_artboard(&self) -> bool {
@ -639,9 +639,10 @@ impl GraphicElementRendered for VectorDataTable {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance_transform = self.transform();
for instance in self.instance_ref_iter() {
let instance_transform = *instance.transform;
let instance = instance.instance;
for instance in self.instance_ref_iter().map(|instance| instance.instance) {
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;
@ -905,14 +906,15 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance_transform = self.transform();
let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, instance_transform);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@ -933,8 +935,8 @@ impl GraphicElementRendered for RasterFrame {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let mut render_stuff = |image: vello::peniko::Image, instance_transform: DAffine2, blend_mode: crate::AlphaBlending| {
let image_transform = transform * instance_transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let layer = blend_mode != Default::default();
let Some(bounds) = self.bounding_box(transform, true) else { return };
@ -960,7 +962,7 @@ impl GraphicElementRendered for RasterFrame {
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
render_stuff(image, *instance.alpha_blending);
render_stuff(image, *instance.transform, *instance.alpha_blending);
}
}
RasterFrame::TextureFrame(image_texture) => {
@ -971,15 +973,22 @@ impl GraphicElementRendered for RasterFrame {
let id = image.data.id();
context.resource_overrides.insert(id, instance.instance.texture.clone());
render_stuff(image, *instance.alpha_blending);
render_stuff(image, *instance.transform, *instance.alpha_blending);
}
}
}
}
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let transform = transform * self.transform();
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
let calculate_transform = |instance_transform| {
let transform: DAffine2 = transform * instance_transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
};
match self {
RasterFrame::ImageFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
RasterFrame::TextureFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
@ -988,7 +997,21 @@ impl GraphicElementRendered for RasterFrame {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, self.transform());
match self {
RasterFrame::ImageFrame(instances) => {
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
RasterFrame::TextureFrame(instances) => {
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image_texture) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image_texture.transform);
}
}
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@ -1031,11 +1054,27 @@ impl GraphicElementRendered for GraphicElement {
}
GraphicElement::VectorData(vector_data) => {
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, vector_data.transform());
// 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.local_transforms.insert(element_id, *vector_data.transform);
}
}
GraphicElement::RasterFrame(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, raster_frame.transform());
match raster_frame {
RasterFrame::ImageFrame(instances) => {
// TODO: Find a way to handle more than one row of images
if let Some(image) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
RasterFrame::TextureFrame(instances) => {
// TODO: Find a way to handle more than one row of image textures
if let Some(image_texture) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image_texture.transform);
}
}
}
}
}
}

View File

@ -1,10 +1,5 @@
use crate::application_io::TextureFrameTable;
use crate::raster::Pixel;
use crate::raster::image::{Image, ImageFrameTable};
use crate::transform::{Transform, TransformMut};
use crate::AlphaBlending;
use crate::uuid::NodeId;
use crate::vector::VectorDataTable;
use crate::{AlphaBlending, GraphicElement, RasterFrame};
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
@ -47,26 +42,6 @@ impl<T> Instances<T> {
self.source_node_id.push(instance.source_node_id);
}
pub fn one_instance_ref(&self) -> InstanceRef<T> {
InstanceRef {
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
source_node_id: self.source_node_id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
}
}
pub fn one_instance_mut(&mut self) -> InstanceMut<T> {
let length = self.instance.len();
InstanceMut {
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
source_node_id: self.source_node_id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
}
}
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.into_iter()
@ -81,7 +56,7 @@ impl<T> Instances<T> {
})
}
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> + Clone {
self.instance
.iter()
.zip(self.transform.iter())
@ -146,11 +121,8 @@ impl<T> Instances<T> {
impl<T: Default + Hash + 'static> Default for Instances<T> {
fn default() -> Self {
// TODO: Remove once all types have been converted to tables
let converted_to_tables = [TypeId::of::<crate::Artboard>(), TypeId::of::<crate::GraphicElement>()];
use core::any::TypeId;
if converted_to_tables.contains(&TypeId::of::<T>()) {
if TypeId::of::<T>() != TypeId::of::<crate::vector::VectorData>() {
// TODO: Remove the 'static trait bound when this special casing is removed by making all types return empty
Self::empty()
} else {
@ -188,7 +160,7 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
vec![None]
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
@ -196,6 +168,20 @@ pub struct InstanceRef<'a, T> {
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,
@ -204,7 +190,7 @@ pub struct InstanceMut<'a, T> {
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Default, Debug)]
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
@ -224,64 +210,31 @@ impl<T> Instance<T> {
source_node_id: self.source_node_id,
}
}
}
// VECTOR DATA TABLE
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
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,
}
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// TEXTURE FRAME TABLE
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
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,
}
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// IMAGE FRAME TABLE
impl<P: Pixel> Transform for ImageFrameTable<P>
where
GraphicElement: From<Image<P>>,
{
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
GraphicElement: From<Image<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// RASTER FRAME
impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform(),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(),
}
}
}
impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform_mut(),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform_mut(),
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

@ -6,7 +6,7 @@ use glam::{DAffine2, DVec2};
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
#[cfg(not(target_arch = "spirv"))]
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
debug!("{:#?}", value);
log::debug!("{:#?}", value);
value
}

View File

@ -586,22 +586,22 @@ impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Nod
// Into
pub struct IntoNode<O>(PhantomData<O>);
impl<_O> IntoNode<_O> {
impl<O> IntoNode<O> {
#[cfg(feature = "alloc")]
pub const fn new() -> Self {
Self(core::marker::PhantomData)
}
}
impl<_O> Default for IntoNode<_O> {
impl<O> Default for IntoNode<O> {
fn default() -> Self {
Self::new()
}
}
impl<'input, I: 'input, _O: 'input> Node<'input, I> for IntoNode<_O>
impl<'input, I: 'input, O: 'input> Node<'input, I> for IntoNode<O>
where
I: Into<_O> + Sync + Send,
I: Into<O> + Sync + Send,
{
type Output = ::dyn_any::DynFuture<'input, _O>;
type Output = ::dyn_any::DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {

View File

@ -665,9 +665,9 @@ impl Blend<Color> for Option<Color> {
}
impl Blend<Color> for ImageFrameTable<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result = self.clone();
let mut result_table = self.clone();
for (over, under) in result.instance_mut_iter().zip(under.instance_ref_iter()) {
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();
*over.instance = Image {
@ -678,7 +678,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
};
}
result
result_table
}
}
impl Blend<Color> for GradientStops {

View File

@ -1,5 +1,5 @@
use crate::Color;
use crate::graphene_core::raster::image::ImageFrameTable;
use crate::instances::Instance;
use crate::raster::Image;
use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle;
@ -16,12 +16,12 @@ struct BrushCacheImpl {
prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background.
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
background: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
blended_image: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
last_stroke_texture: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
background: Instance<Image<Color>>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
blended_image: Instance<Image<Color>>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
last_stroke_texture: Instance<Image<Color>>,
// A cache for brush textures.
#[cfg_attr(feature = "serde", serde(skip))]
@ -29,9 +29,9 @@ struct BrushCacheImpl {
}
impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
fn compute_brush_plan(&mut self, mut background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation.
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
if background != self.background {
self.background = background.clone();
return BrushPlan {
strokes: input.to_vec(),
@ -56,7 +56,11 @@ impl BrushCacheImpl {
background = core::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 = ImageFrameTable::one_empty_image();
let mut first_stroke_texture = Instance {
instance: Image::default(),
transform: glam::DAffine2::ZERO,
..Default::default()
};
let mut first_stroke_point_skip = 0;
let strokes = input[num_blended_strokes..].to_vec();
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
@ -80,7 +84,7 @@ impl BrushCacheImpl {
}
}
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
self.prev_input = input;
self.blended_image = blended_image;
self.last_stroke_texture = last_stroke_texture;
@ -95,8 +99,8 @@ impl Hash for BrushCacheImpl {
#[derive(Clone, Debug, Default)]
pub struct BrushPlan {
pub strokes: Vec<BrushStroke>,
pub background: ImageFrameTable<Color>,
pub first_stroke_texture: ImageFrameTable<Color>,
pub background: Instance<Image<Color>>,
pub first_stroke_texture: Instance<Image<Color>>,
pub first_stroke_point_skip: usize,
}
@ -160,12 +164,12 @@ impl BrushCache {
}
}
pub fn compute_brush_plan(&self, background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
pub fn compute_brush_plan(&self, background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.inner.lock().unwrap();
inner.compute_brush_plan(background, input)
}
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
let mut inner = self.inner.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture)
}

View File

@ -3,7 +3,6 @@ use super::discrete_srgb::float_to_srgb_u8;
use crate::AlphaBlending;
use crate::GraphicElement;
use crate::instances::{Instance, Instances};
use crate::transform::TransformMut;
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use dyn_any::StaticType;
@ -232,7 +231,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
image: image.one_instance_ref().instance.clone(),
image: image.instance_ref_iter().next().unwrap().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@ -270,27 +269,90 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
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 = ImageFrameTable::new(image);
*image_frame_table.one_instance_mut().transform = transform;
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform;
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()),
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Image<Color>>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> {
pub image: Image<P>,
}
impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
}
}
impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
}
}
#[cfg(feature = "dyn-any")]
unsafe impl<P> StaticType for ImageFrame<P>
where
P: dyn_any::StaticTypeSized + Pixel,
P::Static: Pixel,
{
type Static = ImageFrame<P::Static>;
}
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldImageFrame<P: Pixel> {
image: Image<P>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>),
ImageFrameTable(ImageFrameTable<Color>),
ImageInstance(Instance<Image<Color>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Instance {
instance: image,
..Default::default()
},
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance {
instance: 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: image_frame.instance_ref_iter().next().unwrap().instance.image.clone(),
..Default::default()
},
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(),
FormatVersions::ImageInstance(image_instance) => image_instance,
})
}
// TODO: Rename to ImageTable
pub type ImageFrameTable<P> = Instances<Image<P>>;
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
impl ImageFrameTable<Color> {
pub fn one_empty_image() -> Self {
let mut result = Self::new(Image::default());
*result.transform_mut() = DAffine2::ZERO;
result
}
}
impl<P: Debug + Copy + Pixel> Sample for Image<P> {
type Pixel = P;
@ -305,62 +367,6 @@ impl<P: Debug + Copy + Pixel> Sample for Image<P> {
}
}
impl<P> Sample for ImageFrameTable<P>
where
P: Debug + Copy + Pixel,
GraphicElement: From<Image<P>>,
{
type Pixel = P;
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image_transform = self.one_instance_ref().transform;
let image = self.one_instance_ref().instance;
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
Sample::sample(image, pos, area)
}
}
impl<P> Bitmap for ImageFrameTable<P>
where
P: Copy + Pixel,
GraphicElement: From<Image<P>>,
{
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_instance_ref().instance;
image.width()
}
fn height(&self) -> u32 {
let image = self.one_instance_ref().instance;
image.height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_instance_ref().instance;
image.get_pixel(x, y)
}
}
impl<P> BitmapMut for ImageFrameTable<P>
where
P: Copy + Pixel,
GraphicElement: From<Image<P>>,
{
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
self.one_instance_mut().instance.get_pixel_mut(x, y)
}
}
impl<P: Copy + Pixel> Image<P> {
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {
&mut self.data[y * (self.width as usize) + x]

View File

@ -266,11 +266,11 @@ pub struct DynAnyNode<I, O, Node> {
_o: PhantomData<O>,
}
impl<'input, _I, _O, N> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
impl<'input, I, O, N> Node<'input, Any<'input>> for DynAnyNode<I, O, N>
where
_I: 'input + dyn_any::StaticType + WasmNotSend,
_O: 'input + dyn_any::StaticType + WasmNotSend,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
I: 'input + dyn_any::StaticType + WasmNotSend,
O: 'input + dyn_any::StaticType + WasmNotSend,
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
{
type Output = FutureAny<'input>;
#[inline]
@ -294,11 +294,11 @@ where
self.node.serialize()
}
}
impl<'input, _I, _O, N> DynAnyNode<_I, _O, N>
impl<'input, I, O, N> DynAnyNode<I, O, N>
where
_I: 'input + dyn_any::StaticType,
_O: 'input + dyn_any::StaticType,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
I: 'input + dyn_any::StaticType,
O: 'input + dyn_any::StaticType,
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
{
pub const fn new(node: N) -> Self {
Self {

View File

@ -129,7 +129,9 @@ mod test {
.instance
.as_vector_data()
.unwrap()
.one_instance_ref()
.instance_ref_iter()
.next()
.unwrap()
.instance
.bounding_box_with_transform(*instanced.transform)
.unwrap();

View File

@ -253,9 +253,9 @@ fn isometric_grid_test() {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
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!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@ -268,9 +268,9 @@ fn isometric_grid_test() {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
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!(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

@ -60,8 +60,8 @@ 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.one_instance_mut().transform = old.transform;
*vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending;
*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
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,

View File

@ -1193,11 +1193,16 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
}
let mut output_table = VectorDataTable::default();
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
// Create a table with one instance of an empty VectorData, then get a mutable reference to it which we append flattened subpaths to
let mut output_table = VectorDataTable::new(VectorData::default());
let Some(mut output) = output_table.instance_mut_iter().next() else {
return output_table;
};
// Flatten the graphic group input into the output VectorData instance
flatten_group(&graphic_group_input, &mut output);
// Return the single-row VectorDataTable containing the flattened VectorData subpaths
output_table
}
@ -1217,6 +1222,8 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
style: std::mem::take(&mut vector_data_instance.instance.style),
upstream_graphic_group: std::mem::take(&mut vector_data_instance.instance.upstream_graphic_group),
};
// Transfer the stroke transform from the input vector data to the result.
result.style.set_stroke_transform(vector_data_instance.transform);
// Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath.
// So we can index into `subpath_segment_lengths` to get the length of the segments.
@ -1249,10 +1256,6 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
result.append_bezpath(sample_bezpath);
}
// Transfer the style from the input vector data to the result.
result.style = vector_data_instance.instance.style;
result.style.set_stroke_transform(vector_data_instance.transform);
vector_data_instance.instance = result;
result_table.push(vector_data_instance);
}
@ -1459,11 +1462,6 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
result_table.push(vector_data_instance);
}
// TODO: remove after pt6 of instance table refactor
if result_table.is_empty() {
return VectorDataTable::new(VectorData::empty());
}
result_table
}
@ -1732,14 +1730,14 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn close_path(_: impl Ctx, source: VectorDataTable) -> VectorDataTable {
let mut new_table = VectorDataTable::empty();
let mut result_table = VectorDataTable::empty();
for mut source_instance in source.instance_iter() {
source_instance.instance.close_subpaths();
new_table.push(source_instance);
result_table.push(source_instance);
}
new_table
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]

View File

@ -109,9 +109,7 @@ macro_rules! tagged_value {
}
pub fn from_type(input: &Type) -> Option<Self> {
match input {
Type::Generic(_) => {
None
}
Type::Generic(_) => None,
Type::Concrete(concrete_type) => {
let internal_id = concrete_type.id?;
use std::any::TypeId;

View File

@ -1,14 +1,16 @@
use crate::raster::{BlendImageTupleNode, blend_image_closure, extend_image_to_bounds};
use crate::raster::{empty_image, extend_image_to_bounds};
use glam::{DAffine2, DVec2};
use graph_craft::generic::FnNode;
use graph_craft::proto::FutureWrapperNode;
use graphene_core::instances::Instance;
use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Pixel, Sample};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{ClonedNode, ValueNode};
use graphene_core::raster::{Alpha, BitmapMut, BlendMode, Color, Pixel, Sample};
use graphene_core::renderer::GraphicElementRendered;
use graphene_core::transform::Transform;
use graphene_core::value::ClonedNode;
use graphene_core::vector::VectorDataTable;
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::{Ctx, GraphicElement, Node};
@ -31,12 +33,6 @@ impl<P: Pixel + Alpha> Transform for BrushStampGenerator<P> {
}
}
impl<P: Pixel + Alpha> TransformMut for BrushStampGenerator<P> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
type Pixel = P;
@ -139,93 +135,78 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
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.));
use crate::raster::empty_image;
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
let blank_texture = empty_image((), transform, Color::TRANSPARENT).instance_iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.one_instance_ref().instance.clone()
image.instance
}
macro_rules! inline_blend_funcs {
($bg:ident, $fg:ident, $blend_mode:ident, $opacity:ident, [$($mode:path,)*]) => {
match std::hint::black_box($blend_mode) {
$(
$mode => {
blend_image_closure($fg, $bg, |a, b| blend_colors(a, b, $mode, $opacity))
}
)*
}
};
}
pub fn blend_with_mode(background: ImageFrameTable<Color>, foreground: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
pub fn blend_with_mode(background: Instance<Image<Color>>, foreground: Instance<Image<Color>>, blend_mode: BlendMode, opacity: f64) -> Instance<Image<Color>> {
let opacity = opacity / 100.;
inline_blend_funcs!(
background,
foreground,
blend_mode,
opacity,
[
// Normal group
BlendMode::Normal,
// Darken group
BlendMode::Darken,
BlendMode::Multiply,
BlendMode::ColorBurn,
BlendMode::LinearBurn,
BlendMode::DarkerColor,
// Lighten group
BlendMode::Lighten,
BlendMode::Screen,
BlendMode::ColorDodge,
BlendMode::LinearDodge,
BlendMode::LighterColor,
// Contrast group
BlendMode::Overlay,
BlendMode::SoftLight,
BlendMode::HardLight,
BlendMode::VividLight,
BlendMode::LinearLight,
BlendMode::PinLight,
BlendMode::HardMix,
// Inversion group
BlendMode::Difference,
BlendMode::Exclusion,
BlendMode::Subtract,
BlendMode::Divide,
// Component group
BlendMode::Hue,
BlendMode::Saturation,
BlendMode::Color,
BlendMode::Luminosity,
// Other utility blend modes (hidden from the normal list)
BlendMode::Erase,
BlendMode::Restore,
BlendMode::MultiplyAlpha,
]
)
match std::hint::black_box(blend_mode) {
// Normal group
BlendMode::Normal => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Normal, opacity)),
// Darken group
BlendMode::Darken => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Darken, opacity)),
BlendMode::Multiply => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Multiply, opacity)),
BlendMode::ColorBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorBurn, opacity)),
BlendMode::LinearBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearBurn, opacity)),
BlendMode::DarkerColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::DarkerColor, opacity)),
// Lighten group
BlendMode::Lighten => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Lighten, opacity)),
BlendMode::Screen => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Screen, opacity)),
BlendMode::ColorDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorDodge, opacity)),
BlendMode::LinearDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearDodge, opacity)),
BlendMode::LighterColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LighterColor, opacity)),
// Contrast group
BlendMode::Overlay => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Overlay, opacity)),
BlendMode::SoftLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::SoftLight, opacity)),
BlendMode::HardLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardLight, opacity)),
BlendMode::VividLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::VividLight, opacity)),
BlendMode::LinearLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearLight, opacity)),
BlendMode::PinLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::PinLight, opacity)),
BlendMode::HardMix => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardMix, opacity)),
// Inversion group
BlendMode::Difference => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Difference, opacity)),
BlendMode::Exclusion => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Exclusion, opacity)),
BlendMode::Subtract => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Subtract, opacity)),
BlendMode::Divide => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Divide, opacity)),
// Component group
BlendMode::Hue => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Hue, opacity)),
BlendMode::Saturation => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Saturation, opacity)),
BlendMode::Color => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Color, opacity)),
BlendMode::Luminosity => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Luminosity, opacity)),
// Other utility blend modes (hidden from the normal list)
BlendMode::Erase => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Erase, opacity)),
BlendMode::Restore => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Restore, opacity)),
BlendMode::MultiplyAlpha => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::MultiplyAlpha, opacity)),
}
}
#[node_macro::node(category(""))]
async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
#[node_macro::node(category("Raster"))]
async fn brush(_: impl Ctx, mut image_frame_table: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
// TODO: Find a way to handle more than one instance
let Some(image_frame_instance) = image_frame_table.instance_ref_iter().next() else {
return ImageFrameTable::default();
};
let image_frame_instance = image_frame_instance.to_instance_cloned();
let [start, end] = image_frame_instance.clone().to_table().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 image_bbox = Bbox::from_transform(image_frame_table.transform()).to_axis_aligned_bbox();
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
let background_bounds = bbox.to_transform();
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_table, &draw_strokes);
let mut brush_plan = cache.compute_brush_plan(image_frame_instance, &draw_strokes);
let mut background_bounds = bbox.to_transform();
// 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 ImageFrameTable::default();
};
// If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
let bounds_empty = bounds.instance_ref_iter().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
if bounds.transform().matrix2.determinant() != 0. && !bounds_empty {
background_bounds = bounds.transform();
}
let mut actual_image = extend_image_to_bounds((), brush_plan.background, background_bounds);
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
// Create brush texture.
@ -262,14 +243,16 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
);
let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), target, stroke_to_layer)
extend_image_to_bounds((), target.to_table(), stroke_to_layer)
} else {
use crate::raster::empty_image;
empty_image((), stroke_to_layer, Color::TRANSPARENT)
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
};
blit_node.eval(blit_target).await
let instances = blit_node.eval(blit_target).await;
assert_eq!(instances.len(), 1);
instances.instance_iter().next().unwrap_or_default()
};
// Cache image before doing final blend, and store final stroke texture.
@ -284,9 +267,11 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
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 = ImageFrameTable::new(opaque_image);
*erase_restore_mask.transform_mut() = background_bounds;
*erase_restore_mask.one_instance_mut().alpha_blending = Default::default();
let mut erase_restore_mask = Instance {
instance: opaque_image,
transform: background_bounds,
..Default::default()
};
for stroke in erase_restore_strokes {
let mut brush_texture = cache.get_cached_brush(&stroke.style);
@ -306,7 +291,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
@ -316,25 +301,87 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
}
_ => unreachable!(),
}
}
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
}
actual_image
let first_row = image_frame_table.instance_mut_iter().next().unwrap();
*first_row.instance = actual_image.instance;
*first_row.transform = actual_image.transform;
*first_row.alpha_blending = actual_image.alpha_blending;
*first_row.source_node_id = actual_image.source_node_id;
image_frame_table
}
pub fn blend_image_closure(foreground: Instance<Image<Color>>, mut background: Instance<Image<Color>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Image<Color>> {
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);
// 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);
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
// Clamp the foreground image to the background image
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
let end = (background_aabb.end * background_size).min(background_size).as_uvec2();
for y in start.y..end.y {
for x in start.x..end.x {
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.get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel);
}
}
background
}
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: Instance<Image<Color>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Image<Color>> {
let background_size = DVec2::new(background.instance.width as f64, background.instance.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);
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
// Clamp the foreground image to the background image
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
let end = (background_aabb.end * background_size).min(background_size).as_uvec2();
let area = background_to_foreground.transform_point2(DVec2::new(1., 1.)) - background_to_foreground.transform_point2(DVec2::ZERO);
for y in start.y..end.y {
for x in start.x..end.x {
let background_point = DVec2::new(x as f64, y as f64);
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.get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel);
}
}
background
}
#[cfg(test)]
mod test {
use super::*;
use glam::DAffine2;
use graphene_core::raster::Bitmap;
use graphene_core::transform::Transform;
#[test]
@ -350,8 +397,7 @@ mod test {
async fn test_brush_output_size() {
let image = brush(
(),
ImageFrameTable::<Color>::default(),
ImageFrameTable::<Color>::default(),
ImageFrameTable::<Color>::new(Image::<Color>::default()),
vec![BrushStroke {
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle {
@ -366,6 +412,6 @@ mod test {
BrushCache::new_proto(),
)
.await;
assert_eq!(image.width(), 20);
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
}
}

View File

@ -19,6 +19,7 @@ async fn blur(
gamma: bool,
) -> ImageFrameTable<Color> {
let mut result_table = ImageFrameTable::empty();
for mut image_instance in image_frame.instance_iter() {
let image = image_instance.instance.clone();
@ -36,6 +37,7 @@ async fn blur(
image_instance.source_node_id = None;
result_table.push(image_instance);
}
result_table
}

View File

@ -36,6 +36,7 @@ async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context
#[node_macro::node(category("Debug: GPU"))]
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
let mut result_table = ImageFrameTable::empty();
for (foreground_instance, mut background_instance) in foreground.instance_iter().zip(background.instance_iter()) {
let foreground_transform = foreground_instance.transform;
let background_transform = background_instance.transform;
@ -94,7 +95,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
let Ok(proto_networks_result) = proto_networks else {
log::error!("Error compiling network in 'blend_gpu_image()");
return ImageFrameTable::one_empty_image();
return ImageFrameTable::default();
};
let proto_networks = proto_networks_result;
log::debug!("compiling shader");
@ -215,6 +216,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
background_instance.source_node_id = None;
result_table.push(background_instance);
}
result_table
}
@ -241,7 +243,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
// #[node_macro::old_node_impl(MapGpuNode)]
// async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
// let image_frame_table = &image;
// let image = image.one_instance_ref().instance;
// let image = image.instance_ref_iter().next().unwrap().instance;
// log::debug!("Executing gpu node");
// let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
@ -256,7 +258,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
// let name = "placeholder".to_string();
// let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
// log::error!("Error creating compute pass descriptor in 'map_gpu()");
// return ImageFrameTable::one_empty_image();
// return ImageFrameTable::default();
// };
// self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
// log::error!("created compute pass");
@ -292,7 +294,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
// };
// let mut result = ImageFrameTable::new(new_image);
// *result.transform_mut() = image_frame_table.transform();
// *result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
// *result.instance_mut_iter().next().unwrap().alpha_blending = *image_frame_table.instance_ref_iter().next().unwrap().alpha_blending;
// result
// }
@ -312,7 +314,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
// GraphicElement: From<Image<T>>,
// T::Static: Pixel,
// {
// let image = image.one_instance_ref().instance;
// let image = image.instance_ref_iter().next().unwrap().instance;
// let compiler = graph_craft::graphene_compiler::Compiler {};
// let inner_network = NodeNetwork::value_network(node);

View File

@ -1,13 +1,12 @@
use dyn_any::DynAny;
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
use graphene_core::instances::Instance;
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Sample,
};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
use graphene_core::raster::{Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, Channel, DomainWarpType, FractalType, LinearChannel, Luminance, NoiseType, RGBMut};
use graphene_core::transform::Transform;
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint};
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use std::fmt::Debug;
@ -89,172 +88,146 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
result_table.push(image_frame_instance)
}
// TODO: Remove when we've completed part 6 of the instance tables refactor
if result_table.is_empty() {
return ImageFrameTable::one_empty_image();
result_table
}
#[node_macro::node(category("Raster"))]
fn combine_channels(
_: impl Ctx,
_primary: (),
#[expose] red: ImageFrameTable<Color>,
#[expose] green: ImageFrameTable<Color>,
#[expose] blue: ImageFrameTable<Color>,
#[expose] alpha: ImageFrameTable<Color>,
) -> ImageFrameTable<Color> {
let mut result_table = ImageFrameTable::empty();
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);
for (((red, green), blue), alpha) in red.zip(green).zip(blue).zip(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);
// Get this instance's transform and alpha blending mode from the first non-empty channel
let Some((transform, alpha_blending)) = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| (i.transform, i.alpha_blending)) else {
continue;
};
// 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)),
];
if channel_dimensions.iter().all(Option::is_none)
|| channel_dimensions
.iter()
.flatten()
.any(|&(x, y)| channel_dimensions.iter().flatten().any(|&(other_x, other_y)| x != other_x || y != other_y))
{
continue;
}
let Some(&(width, height)) = channel_dimensions.iter().flatten().next() else { continue };
// Create a new image for this instance output
let mut image = Image::new(width, height, Color::TRANSPARENT);
// Iterate over all pixels in the image and set the color channels
for y in 0..image.height() {
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)) {
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)) {
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)) {
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)) {
image_pixel.set_alpha(a.l().cast_linear_channel());
} else {
image_pixel.set_alpha(Channel::from_linear(1.));
}
}
}
// Add this instance to the result table
result_table.push(Instance {
instance: image,
transform,
alpha_blending,
source_node_id: None,
});
}
result_table
}
#[node_macro::node(category("Raster"))]
fn combine_channels<_I, Red, Green, Blue, Alpha>(
_: impl Ctx,
_primary: (),
#[implementations(ImageFrameTable<Color>)] red: Red,
#[implementations(ImageFrameTable<Color>)] green: Green,
#[implementations(ImageFrameTable<Color>)] blue: Blue,
#[implementations(ImageFrameTable<Color>)] alpha: Alpha,
) -> ImageFrameTable<Color>
where
_I: Pixel + Luminance,
Red: Bitmap<Pixel = _I>,
Green: Bitmap<Pixel = _I>,
Blue: Bitmap<Pixel = _I>,
Alpha: Bitmap<Pixel = _I>,
{
let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()];
if dimensions.iter().any(|&(x, y)| x == 0 || y == 0) || dimensions.iter().any(|&(x, y)| dimensions.iter().any(|&(other_x, other_y)| x != other_x || y != other_y)) {
return ImageFrameTable::one_empty_image();
}
let mut image = Image::new(red.width(), red.height(), Color::TRANSPARENT);
for y in 0..image.height() {
for x in 0..image.width() {
let image_pixel = image.get_pixel_mut(x, y).unwrap();
if let Some(r) = red.get_pixel(x, y) {
image_pixel.set_red(r.l().cast_linear_channel());
}
if let Some(g) = green.get_pixel(x, y) {
image_pixel.set_green(g.l().cast_linear_channel());
}
if let Some(b) = blue.get_pixel(x, y) {
image_pixel.set_blue(b.l().cast_linear_channel());
}
if let Some(a) = alpha.get_pixel(x, y) {
image_pixel.set_alpha(a.l().cast_linear_channel());
}
}
}
ImageFrameTable::new(image)
}
#[node_macro::node(category("Raster"))]
fn mask<_P, _S, Input, Stencil>(
fn mask(
_: impl Ctx,
/// The image to be masked.
#[implementations(ImageFrameTable<Color>)]
mut image: Input,
image: ImageFrameTable<Color>,
/// The stencil to be used for masking.
#[implementations(ImageFrameTable<Color>)]
#[expose]
stencil: Stencil,
) -> Input
where
// _P is the color of the input image. It must have an alpha channel because that is going to be modified by the mask.
_P: Alpha,
// _S is the color of the stencil. It must have a luminance channel because that is used to mask the input image.
_S: Luminance,
// Input image
Input: Transform + BitmapMut<Pixel = _P>,
// Stencil
Stencil: Transform + Sample<Pixel = _S>,
{
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
let mask_size = stencil.transform().decompose_scale();
if mask_size == DVec2::ZERO {
stencil: ImageFrameTable<Color>,
) -> ImageFrameTable<Color> {
// TODO: Support multiple stencil instances
let Some(stencil_instance) = stencil.instance_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);
// Transforms a point from the background image to the foreground image
let bg_to_fg = image.transform() * DAffine2::from_scale(1. / image_size);
let stencil_transform_inverse = stencil.transform().inverse();
let mut result_table = ImageFrameTable::empty();
let area = bg_to_fg.transform_vector2(DVec2::ONE);
for y in 0..image.height() {
for x in 0..image.width() {
let image_point = DVec2::new(x as f64, y as f64);
let mut mask_point = bg_to_fg.transform_point2(image_point);
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
mask_point = stencil.transform().transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
for mut image_instance in image.instance_iter() {
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();
let image_pixel = image.get_pixel_mut(x, y).unwrap();
if let Some(mask_pixel) = stencil.sample(mask_point, area) {
if mask_size == DVec2::ZERO {
continue;
}
// 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();
for y in 0..image_instance.instance.height {
for x in 0..image_instance.instance.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 image_pixel = image_instance.instance.get_pixel_mut(x, y).unwrap();
let mask_pixel = stencil_instance.instance.sample(mask_point);
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());
}
}
result_table.push(image_instance);
}
image
}
// #[derive(Debug, Clone, Copy)]
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
// map_fn: MapFn,
// _p: PhantomData<P>,
// _fg: PhantomData<Fg>,
// }
#[node_macro::node(skip_impl)]
async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
where
_P: Alpha + Pixel + Debug + Send,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
GraphicElement: From<Image<_P>>,
{
let (background, foreground) = images;
blend_image(foreground, background, map_fn)
}
fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background
where
MapFn: Node<'input, (_P, _P), Output = _P>,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{
blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b)))
}
pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background
where
MapFn: Fn(_P, _P) -> _P,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
// Transforms a point from the background image to the foreground image
let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size);
// Footprint of the foreground image (0,0) (1, 1) in the background image space
let bg_aabb = Bbox::unit().affine_transform(background.transform().inverse() * foreground.transform()).to_axis_aligned_bbox();
// Clamp the foreground image to the background image
let start = (bg_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
let end = (bg_aabb.end * background_size).min(background_size).as_uvec2();
let area = bg_to_fg.transform_point2(DVec2::new(1., 1.)) - bg_to_fg.transform_point2(DVec2::ZERO);
for y in start.y..end.y {
for x in start.x..end.x {
let bg_point = DVec2::new(x as f64, y as f64);
let fg_point = bg_to_fg.transform_point2(bg_point);
if let Some(src_pixel) = foreground.sample(fg_point, area) {
if let Some(dst_pixel) = background.get_pixel_mut(x, y) {
*dst_pixel = map_fn(src_pixel, *dst_pixel);
}
}
}
}
background
result_table
}
#[node_macro::node(category(""))]
@ -469,7 +442,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::one_empty_image();
return ImageFrameTable::default();
}
let footprint_scale = footprint.scale();
@ -513,9 +486,12 @@ fn noise_pattern(
}
}
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
let mut result = ImageFrameTable::empty();
result.push(Instance {
instance: image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
});
return result;
}
@ -575,9 +551,12 @@ fn noise_pattern(
}
}
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
let mut result = ImageFrameTable::empty();
result.push(Instance {
instance: image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
});
result
}
@ -595,7 +574,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::one_empty_image();
return ImageFrameTable::default();
}
let scale = footprint.scale();
@ -623,9 +602,12 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
data,
..Default::default()
};
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = Default::default();
let mut result = ImageFrameTable::empty();
result.push(Instance {
instance: image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
});
result
}

View File

@ -1,7 +1,7 @@
use bezier_rs::{ManipulatorGroup, Subpath};
use glam::{DAffine2, DVec2};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::RasterFrame;
use graphene_core::instances::{Instance, InstanceRef};
use graphene_core::vector::misc::BooleanOperation;
use graphene_core::vector::style::Fill;
pub use graphene_core::vector::*;
@ -10,207 +10,245 @@ pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation};
use std::ops::Mul;
// TODO: Fix boolean ops to work by removing .transform() and .one_instnace_*() calls,
// TODO: since before we used a Vec of single-row tables and now we use a single table
// TODO: with multiple rows while still assuming a single row for the boolean operations.
#[node_macro::node(category(""))]
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
graphic_group_table
.instance_ref_iter()
.map(|element| match element.instance.clone() {
GraphicElement::VectorData(mut vector_data) => {
// Apply the parent group's transform to each element of vector data
for sub_vector_data in vector_data.instance_mut_iter() {
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
}
vector_data
}
GraphicElement::RasterFrame(mut image) => {
// Apply the parent group's transform to each element of raster data
match &mut image {
graphene_core::RasterFrame::ImageFrame(image) => {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
graphene_core::RasterFrame::TextureFrame(image) => {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.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(image.transform());
// Create a vector data table from the rectangular subpath, with a default black fill
let mut vector_data = VectorData::from_subpath(subpath);
vector_data.style.set_fill(Fill::Solid(Color::BLACK));
VectorDataTable::new(vector_data)
}
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() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
// Recursively flatten the inner group into vector data
boolean_operation_on_vector_data(&flatten_vector_data(&graphic_group), BooleanOperation::Union)
}
})
.collect()
}
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
let mut vector_data = vector_data.into_iter();
let mut result = vector_data.next().cloned().unwrap_or_default();
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 = result.transform().inverse() * lower_vector_data.transform();
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
next_vector_data = vector_data.next();
}
result
}
fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable {
match boolean_operation {
BooleanOperation::Union => {
// Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_table = vector_data_table.iter().rev();
let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default();
// Loop over all vector data and union it with the result
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform();
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result_vector_data.point_domain = boolean_operation_result.point_domain;
result_vector_data.segment_domain = boolean_operation_result.segment_domain;
result_vector_data.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data_table.next();
}
result_vector_data_table
}
BooleanOperation::SubtractFront => subtract(vector_data_table.iter()),
BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()),
BooleanOperation::Intersect => {
let mut vector_data = vector_data_table.iter().rev();
let mut result = vector_data.next().cloned().unwrap_or_default();
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(&default));
// For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
let result = result.one_instance_mut().instance;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
}
result
}
BooleanOperation::Difference => {
let mut vector_data_iter = vector_data_table.iter().rev();
let mut any_intersection = VectorDataTable::default();
let default = VectorDataTable::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default));
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
let all_other_vector_data_instance = all_other_vector_data.one_instance_ref();
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, 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 boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
*boolean_intersection_result.transform_mut() = *all_other_vector_data_instance.transform;
boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
*boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform.inverse() * any_intersection.transform();
let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
let lower_path_string = to_path(any_intersection.one_instance_mut().instance, 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.one_instance_mut().instance = union_result;
*any_intersection.transform_mut() = boolean_intersection_result.transform();
any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone();
any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending;
second_vector_data = vector_data_iter.next();
}
// Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union);
boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront)
}
}
}
// The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data(&flatten_vector_data(&group_of_paths), operation);
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
let result_vector_data_table_transform = result_vector_data_table.transform();
*result_vector_data_table.transform_mut() = DAffine2::IDENTITY;
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
VectorData::transform(result_vector_data, result_vector_data_table_transform);
result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.upstream_graphic_group = Some(group_of_paths.clone());
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().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());
}
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 {
match boolean_operation {
BooleanOperation::Union => union(vector_data),
BooleanOperation::SubtractFront => subtract(vector_data),
BooleanOperation::SubtractBack => subtract(vector_data.rev()),
BooleanOperation::Intersect => intersect(vector_data),
BooleanOperation::Difference => difference(vector_data),
}
}
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
// 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::empty();
result_vector_data_table.push(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");
// 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()));
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 result = &mut first_instance.instance;
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);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data_reversed.next();
}
result_vector_data_table
}
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
let mut vector_data = vector_data.into_iter();
let mut result_vector_data_table = VectorDataTable::empty();
result_vector_data_table.push(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 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 result = &mut first_instance.instance;
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);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
next_vector_data = vector_data.next();
}
result_vector_data_table
}
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
let mut vector_data = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::empty();
result_vector_data_table.push(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 default = Instance::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_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 result = &mut first_instance.instance;
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);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let boolean_operation_result = from_path(&boolean_operation_string);
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
}
result_vector_data_table
}
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable {
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()));
// 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 transform_of_lower_into_space_of_upper = first_instance.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);
#[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 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);
#[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
any_intersection.instance = union_result;
any_intersection.transform = boolean_intersection_result.transform;
any_intersection.instance.style = boolean_intersection_result.instance.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
second_vector_data = vector_data_iter.next();
}
// 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)
}
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable {
let mut result_table = VectorDataTable::empty();
for element in graphic_group_table.instance_ref_iter() {
match element.instance.clone() {
GraphicElement::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data
for mut sub_vector_data in vector_data.instance_iter() {
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
result_table.push(sub_vector_data);
}
}
GraphicElement::RasterFrame(image) => {
let make_instance = |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));
Instance { instance, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
match image {
RasterFrame::ImageFrame(image) => {
for instance in image.instance_ref_iter() {
result_table.push(make_instance(*element.transform * *instance.transform));
}
}
RasterFrame::TextureFrame(image) => {
for instance in image.instance_ref_iter() {
result_table.push(make_instance(*element.transform * *instance.transform));
}
}
}
}
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() {
*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);
for element in unioned.instance_iter() {
result_table.push(element);
}
}
}
}
result_table
}
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
let mut path = Vec::new();
for subpath in vector.stroke_bezier_paths() {

View File

@ -12,8 +12,6 @@ use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
use graphene_core::transform::Footprint;
#[cfg(target_arch = "wasm32")]
use graphene_core::transform::TransformMut;
use graphene_core::vector::VectorDataTable;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
@ -44,7 +42,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
// surface_handle: Arc<WasmSurfaceHandle>,
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
// let image = image.one_instance_ref().instance;
// let image = image.instance_ref_iter().next().unwrap().instance;
// let image_data = image.image.data;
// let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
// if image.image.width > 0 && image.image.height > 0 {
@ -80,7 +78,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
#[node_macro::node(category("Network"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return ImageFrameTable::one_empty_image();
return ImageFrameTable::default();
};
let image = image.to_rgba32f();
let image = Image {
@ -177,6 +175,8 @@ async fn rasterize<T: WasmNotSend + 'n>(
where
Instances<T>: GraphicElementRendered,
{
use graphene_core::instances::Instance;
if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization");
return ImageFrameTable::empty();
@ -218,8 +218,12 @@ where
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let mut result = ImageFrameTable::new(Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32));
*result.transform_mut() = footprint.transform;
let mut result = ImageFrameTable::empty();
result.push(Instance {
instance: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
transform: footprint.transform,
..Default::default()
});
result
}

View File

@ -9,6 +9,7 @@ use futures::Future;
use glam::{DAffine2, UVec2};
use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
use graphene_core::application_io::{ApplicationIo, EditorApi, ImageTexture, SurfaceHandle, TextureFrameTable};
use graphene_core::instances::Instance;
use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{Image, SRGBA8};
use graphene_core::transform::{Footprint, Transform};
@ -911,36 +912,32 @@ async fn render_texture<'a: 'n>(
#[node_macro::node(category(""))]
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrameTable {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let image = input.one_instance_ref().instance;
let new_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
let new_image = Image {
width: image.width,
height: image.height,
data: new_data,
base64_string: None,
};
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
let texture = match shader_input {
ShaderInput::TextureBuffer(buffer, _) => buffer,
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
_ => unreachable!("Unsupported ShaderInput type"),
};
let texture = ImageTexture {
texture: texture.into(),
// TODO: Find an alternate way to encode the transform and alpha_blend now that these fields have been moved up out of ImageTexture
// transform: input.transform,
// alpha_blend: Default::default(),
};
let mut result_table = TextureFrameTable::empty();
result_table.push(graphene_core::instances::Instance {
instance: texture,
transform: input.transform(),
alpha_blending: *input.one_instance_ref().alpha_blending,
source_node_id: *input.one_instance_ref().source_node_id,
});
for instance in input.instance_ref_iter() {
let image = instance.instance;
let new_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
let new_image = Image {
width: image.width,
height: image.height,
data: new_data,
base64_string: None,
};
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
let texture = match shader_input {
ShaderInput::TextureBuffer(buffer, _) => buffer,
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
_ => unreachable!("Unsupported ShaderInput type"),
};
result_table.push(Instance {
instance: ImageTexture { texture: texture.into() },
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *instance.source_node_id,
});
}
result_table
}