diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 72e2aafe48..7dbfaef8b7 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -609,7 +609,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrameTable>")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrameTable>")), ..Default::default() }, DocumentNode { @@ -647,7 +647,7 @@ fn static_nodes() -> Vec { node_metadata: [ DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Convert Image Frame".to_string(), + display_name: "Into".to_string(), node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), ..Default::default() }, diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 0590717f55..31abef10e4 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -36,6 +36,15 @@ impl AlphaBlending { blend_mode: BlendMode::Normal, } } + + pub fn lerp(&self, other: &Self, t: f32) -> Self { + let lerp = |a: f32, b: f32, t: f32| a + (b - a) * t; + + AlphaBlending { + opacity: lerp(self.opacity, other.opacity, t), + blend_mode: if t < 0.5 { self.blend_mode } else { other.blend_mode }, + } + } } // TODO: Eventually remove this migration document upgrade code diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index bc3627402e..003ea098fe 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -204,7 +204,7 @@ pub struct InstanceMut<'a, T> { pub source_node_id: &'a mut Option, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Default, Debug)] pub struct Instance { pub instance: T, pub transform: DAffine2, diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index cd84272eae..5f4681c29a 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -2,7 +2,7 @@ use super::Color; use super::discrete_srgb::float_to_srgb_u8; use crate::AlphaBlending; use crate::GraphicElement; -use crate::instances::Instances; +use crate::instances::{Instance, Instances}; use crate::transform::TransformMut; use alloc::vec::Vec; use core::hash::{Hash, Hasher}; @@ -393,6 +393,23 @@ impl From> for Image { } } +impl From> for ImageFrameTable { + fn from(image_frame_table: ImageFrameTable) -> Self { + let mut result_table = ImageFrameTable::::empty(); + + for image_frame_instance in image_frame_table.instance_iter() { + result_table.push(Instance { + instance: image_frame_instance.instance.into(), + transform: image_frame_instance.transform, + alpha_blending: image_frame_instance.alpha_blending, + source_node_id: image_frame_instance.source_node_id, + }); + } + + result_table + } +} + impl From> for Image { fn from(image: Image) -> Self { let data = image.data.into_iter().map(|x| x.into()).collect(); diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index aae7ed6544..e6f091b69d 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,6 +1,5 @@ use super::*; use crate::Ctx; -use crate::transform::TransformMut; use crate::uuid::generate_uuid; use bezier_rs::BezierHandles; use core::hash::BuildHasher; @@ -425,14 +424,10 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { - let vector_data_transform = *vector_data.one_instance_ref().transform; - let vector_data = vector_data.one_instance_mut().instance; - - modification.apply(vector_data); - - let mut result = VectorDataTable::new(vector_data.clone()); - *result.transform_mut() = vector_data_transform; - result + for mut vector_data_instance in vector_data.instance_mut_iter() { + modification.apply(&mut vector_data_instance.instance); + } + vector_data } #[test] diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 0295141d30..df7da779ea 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -7,7 +7,7 @@ use crate::instances::{Instance, InstanceMut, Instances}; use crate::raster::image::ImageFrameTable; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue}; use crate::renderer::GraphicElementRendered; -use crate::transform::{Footprint, ReferencePoint, Transform, TransformMut}; +use crate::transform::{Footprint, ReferencePoint, Transform}; use crate::vector::PointDomain; use crate::vector::misc::dvec2_to_point; use crate::vector::style::{LineCap, LineJoin}; @@ -312,56 +312,56 @@ async fn copy_to_points( where Instances: GraphicElementRendered, { - let points_transform = points.transform(); - let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions()); + let mut result_table = GraphicGroupTable::default(); let random_scale_difference = random_scale_max - random_scale_min; let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY, false).unwrap_or_default(); let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]); - let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); - let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); - - let do_scale = random_scale_difference.abs() > 1e-6; - let do_rotation = random_rotation.abs() > 1e-6; + for point_instance in points.instance_iter() { + let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); + let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); - let mut result_table = GraphicGroupTable::default(); + let do_scale = random_scale_difference.abs() > 1e-6; + let do_rotation = random_rotation.abs() > 1e-6; - for &point in points_list.into_iter() { - let center_transform = DAffine2::from_translation(instance_center); + let points_transform = point_instance.transform; + for &point in point_instance.instance.point_domain.positions() { + let center_transform = DAffine2::from_translation(instance_center); - let translation = points_transform.transform_point2(point); + let translation = points_transform.transform_point2(point); - let rotation = if do_rotation { - let degrees = (rotation_rng.random::() - 0.5) * random_rotation; - degrees / 360. * std::f64::consts::TAU - } else { - 0. - }; + let rotation = if do_rotation { + let degrees = (rotation_rng.random::() - 0.5) * random_rotation; + degrees / 360. * std::f64::consts::TAU + } else { + 0. + }; - let scale = if do_scale { - if random_scale_bias.abs() < 1e-6 { - // Linear - random_scale_min + scale_rng.random::() * random_scale_difference + let scale = if do_scale { + if random_scale_bias.abs() < 1e-6 { + // Linear + random_scale_min + scale_rng.random::() * random_scale_difference + } else { + // Weighted (see ) + let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); + let scale_factor = (1. - scale_rng.random::() * horizontal_scale_factor).log2() / random_scale_bias; + random_scale_min + scale_factor * random_scale_difference + } } else { - // Weighted (see ) - let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); - let scale_factor = (1. - scale_rng.random::() * horizontal_scale_factor).log2() / random_scale_bias; - random_scale_min + scale_factor * random_scale_difference - } - } else { - random_scale_min - }; + random_scale_min + }; - let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform; + let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform; - result_table.push(Instance { - instance: instance.to_graphic_element().clone(), - transform, - alpha_blending: Default::default(), - source_node_id: None, - }); + result_table.push(Instance { + instance: instance.to_graphic_element().clone(), + transform, + alpha_blending: Default::default(), + source_node_id: None, + }); + } } result_table @@ -445,12 +445,11 @@ async fn round_corners( #[default(5.)] min_angle_threshold: Angle, ) -> VectorDataTable { - let source_transform = source.transform(); - let source_transform_inverse = source_transform.inverse(); - let mut result_table = VectorDataTable::empty(); for source in source.instance_ref_iter() { + let source_transform = *source.transform; + let source_transform_inverse = source_transform.inverse(); let source = source.instance; let upstream_graphic_group = source.upstream_graphic_group.clone(); @@ -1418,181 +1417,193 @@ async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> V } #[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))] -async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { - let original_transform = vector_data.transform(); - let vector_data = vector_data.one_instance_mut().instance; +async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { + let mut result_table = VectorDataTable::empty(); - // Exit early if there are no points to generate splines from. - if vector_data.point_domain.positions().is_empty() { - return VectorDataTable::new(VectorData::empty()); - } + for mut vector_data_instance in vector_data.instance_iter() { + // Exit early if there are no points to generate splines from. + if vector_data_instance.instance.point_domain.positions().is_empty() { + continue; + } - let mut segment_domain = SegmentDomain::default(); - for subpath in vector_data.stroke_bezier_paths() { - let positions = subpath.manipulator_groups().iter().map(|group| group.anchor).collect::>(); - let closed = subpath.closed() && positions.len() > 2; + let mut segment_domain = SegmentDomain::default(); + for subpath in vector_data_instance.instance.stroke_bezier_paths() { + let positions = subpath.manipulator_groups().iter().map(|group| group.anchor).collect::>(); + let closed = subpath.closed() && positions.len() > 2; - // Compute control point handles for Bezier spline. - let first_handles = if closed { - bezier_rs::solve_spline_first_handle_closed(&positions) - } else { - bezier_rs::solve_spline_first_handle_open(&positions) - }; + // Compute control point handles for Bezier spline. + let first_handles = if closed { + bezier_rs::solve_spline_first_handle_closed(&positions) + } else { + bezier_rs::solve_spline_first_handle_open(&positions) + }; - let stroke_id = StrokeId::ZERO; + let stroke_id = StrokeId::ZERO; - // Create segments with computed Bezier handles and add them to vector data. - for i in 0..(positions.len() - if closed { 0 } else { 1 }) { - let next_index = (i + 1) % positions.len(); + // Create segments with computed Bezier handles and add them to vector data. + for i in 0..(positions.len() - if closed { 0 } else { 1 }) { + let next_index = (i + 1) % positions.len(); - let start_index = vector_data.point_domain.resolve_id(subpath.manipulator_groups()[i].id).unwrap(); - let end_index = vector_data.point_domain.resolve_id(subpath.manipulator_groups()[next_index].id).unwrap(); + let start_index = vector_data_instance.instance.point_domain.resolve_id(subpath.manipulator_groups()[i].id).unwrap(); + let end_index = vector_data_instance.instance.point_domain.resolve_id(subpath.manipulator_groups()[next_index].id).unwrap(); - let handle_start = first_handles[i]; - let handle_end = positions[next_index] * 2. - first_handles[next_index]; - let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end }; + let handle_start = first_handles[i]; + let handle_end = positions[next_index] * 2. - first_handles[next_index]; + let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end }; - segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id); + segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id); + } } + + vector_data_instance.instance.segment_domain = segment_domain; + result_table.push(vector_data_instance); } - vector_data.segment_domain = segment_domain; - let mut result = VectorDataTable::new(vector_data.clone()); - *result.transform_mut() = original_transform; - result + // TODO: remove after pt6 of instance table refactor + if result_table.is_empty() { + return VectorDataTable::new(VectorData::empty()); + } + + result_table } #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { - let vector_data_transform = vector_data.transform(); - let mut vector_data = vector_data.one_instance_ref().instance.clone(); - - let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); + let mut result_table = VectorDataTable::empty(); - let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); + for mut vector_data_instance in vector_data.instance_iter() { + let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - let deltas = (0..vector_data.point_domain.positions().len()) - .map(|_| { - let angle = rng.random::() * std::f64::consts::TAU; + let vector_data_transform = vector_data_instance.transform; + let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); - inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::() * amount) - }) - .collect::>(); - let mut already_applied = vec![false; vector_data.point_domain.positions().len()]; + let deltas = (0..vector_data_instance.instance.point_domain.positions().len()) + .map(|_| { + let angle = rng.random::() * std::f64::consts::TAU; - for (handles, start, end) in vector_data.segment_domain.handles_and_points_mut() { - let start_delta = deltas[*start]; - let end_delta = deltas[*end]; + inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::() * amount) + }) + .collect::>(); + let mut already_applied = vec![false; vector_data_instance.instance.point_domain.positions().len()]; - if !already_applied[*start] { - let start_position = vector_data.point_domain.positions()[*start]; - vector_data.point_domain.set_position(*start, start_position + start_delta); - already_applied[*start] = true; - } - if !already_applied[*end] { - let end_position = vector_data.point_domain.positions()[*end]; - vector_data.point_domain.set_position(*end, end_position + end_delta); - already_applied[*end] = true; - } + for (handles, start, end) in vector_data_instance.instance.segment_domain.handles_and_points_mut() { + let start_delta = deltas[*start]; + let end_delta = deltas[*end]; - match handles { - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - *handle_start += start_delta; - *handle_end += end_delta; + if !already_applied[*start] { + let start_position = vector_data_instance.instance.point_domain.positions()[*start]; + vector_data_instance.instance.point_domain.set_position(*start, start_position + start_delta); + already_applied[*start] = true; } - bezier_rs::BezierHandles::Quadratic { handle } => { - *handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; + if !already_applied[*end] { + let end_position = vector_data_instance.instance.point_domain.positions()[*end]; + vector_data_instance.instance.point_domain.set_position(*end, end_position + end_delta); + already_applied[*end] = true; + } + + match handles { + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + *handle_start += start_delta; + *handle_end += end_delta; + } + bezier_rs::BezierHandles::Quadratic { handle } => { + *handle = vector_data_instance.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; + } + bezier_rs::BezierHandles::Linear => {} } - bezier_rs::BezierHandles::Linear => {} } - } - vector_data.style.set_stroke_transform(DAffine2::IDENTITY); + vector_data_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY); + result_table.push(vector_data_instance); + } - let mut result = VectorDataTable::new(vector_data.clone()); - *result.transform_mut() = vector_data_transform; - result + result_table } #[node_macro::node(category("Vector"), path(graphene_core::vector))] async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction) -> VectorDataTable { - let mut source = source; - let mut target = target; - let time = time.clamp(0., 1.); - let mut result_table = VectorDataTable::default(); - - // Lerp styles - let source_alpha_blending = source.one_instance_ref().alpha_blending; - let target_alpha_blending = target.one_instance_ref().alpha_blending; - *result_table.one_instance_mut().alpha_blending = if time < 0.5 { *source_alpha_blending } else { *target_alpha_blending }; - result_table.one_instance_mut().instance.style = source.one_instance_ref().instance.style.lerp(&target.one_instance_ref().instance.style, time); - - // Before and after transforms - let source_transform = *source.one_instance_ref().transform; - let target_transform = *target.one_instance_ref().transform; - - // Before and after paths - let source_paths = source.one_instance_mut().instance.stroke_bezier_paths(); - let target_paths = target.one_instance_mut().instance.stroke_bezier_paths(); - for (mut source_path, mut target_path) in source_paths.zip(target_paths) { - source_path.apply_transform(source_transform); - target_path.apply_transform(target_transform); - - // Align point counts by inserting mid‐segment points until their counts match - while source_path.manipulator_groups().len() < target_path.manipulator_groups().len() { - let last = source_path.len() - 1; - source_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 }); - } - while target_path.manipulator_groups().len() < source_path.manipulator_groups().len() { - let last = target_path.len() - 1; - target_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 }); - } + let mut result_table = VectorDataTable::empty(); - // Interpolate anchors and handles - for (source_manipulators, target_manipulators) in source_path.manipulator_groups_mut().iter_mut().zip(target_path.manipulator_groups()) { - let source_anchor = source_manipulators.anchor; - let target_anchor = target_manipulators.anchor; - source_manipulators.anchor = source_anchor.lerp(target_anchor, time); + for (source_instance, target_instance) in source.instance_iter().zip(target.instance_iter()) { + let mut vector_data_instance = VectorData::default(); - let source_in_handle = source_manipulators.in_handle.unwrap_or(source_anchor); - let target_in_handle = target_manipulators.in_handle.unwrap_or(target_anchor); - source_manipulators.in_handle = Some(source_in_handle.lerp(target_in_handle, time)); + // Lerp styles + let vector_data_alpha_blending = source_instance.alpha_blending.lerp(&target_instance.alpha_blending, time as f32); + vector_data_instance.style = source_instance.instance.style.lerp(&target_instance.instance.style, time); - let source_out_handle = source_manipulators.out_handle.unwrap_or(source_anchor); - let target_out_handle = target_manipulators.out_handle.unwrap_or(target_anchor); - source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time)); - } + // Before and after transforms + let source_transform = source_instance.transform; + let target_transform = target_instance.transform; - result_table.one_instance_mut().instance.append_subpath(source_path.clone(), true); - } + // Before and after paths + let source_paths = source_instance.instance.stroke_bezier_paths(); + let target_paths = target_instance.instance.stroke_bezier_paths(); + for (mut source_path, mut target_path) in source_paths.zip(target_paths) { + source_path.apply_transform(source_transform); + target_path.apply_transform(target_transform); + + // Align point counts by inserting mid‐segment points until their counts match + while source_path.manipulator_groups().len() < target_path.manipulator_groups().len() { + let last = source_path.len() - 1; + source_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 }); + } + while target_path.manipulator_groups().len() < source_path.manipulator_groups().len() { + let last = target_path.len() - 1; + target_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 }); + } + + // Interpolate anchors and handles + for (source_manipulators, target_manipulators) in source_path.manipulator_groups_mut().iter_mut().zip(target_path.manipulator_groups()) { + let source_anchor = source_manipulators.anchor; + let target_anchor = target_manipulators.anchor; + source_manipulators.anchor = source_anchor.lerp(target_anchor, time); - // Deal with unmatched extra paths by collapsing them - let source_paths_count = source.one_instance_ref().instance.stroke_bezier_paths().count(); - let target_paths_count = target.one_instance_ref().instance.stroke_bezier_paths().count(); - let source_paths = source.one_instance_mut().instance.stroke_bezier_paths().skip(target_paths_count); - let target_paths = target.one_instance_mut().instance.stroke_bezier_paths().skip(source_paths_count); - - for mut source_path in source_paths { - source_path.apply_transform(source_transform); - let end = source_path.manipulator_groups().last().map(|group| group.anchor).unwrap_or_default(); - for group in source_path.manipulator_groups_mut() { - group.anchor = group.anchor.lerp(end, time); - group.in_handle = group.in_handle.map(|handle| handle.lerp(end, time)); - group.out_handle = group.out_handle.map(|handle| handle.lerp(end, time)); + let source_in_handle = source_manipulators.in_handle.unwrap_or(source_anchor); + let target_in_handle = target_manipulators.in_handle.unwrap_or(target_anchor); + source_manipulators.in_handle = Some(source_in_handle.lerp(target_in_handle, time)); + + let source_out_handle = source_manipulators.out_handle.unwrap_or(source_anchor); + let target_out_handle = target_manipulators.out_handle.unwrap_or(target_anchor); + source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time)); + } + + vector_data_instance.append_subpath(source_path.clone(), true); } - result_table.one_instance_mut().instance.append_subpath(source_path, true); - } - for mut target_path in target_paths { - target_path.apply_transform(target_transform); - let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); - for group in target_path.manipulator_groups_mut() { - group.anchor = start.lerp(group.anchor, time); - group.in_handle = group.in_handle.map(|handle| start.lerp(handle, time)); - group.out_handle = group.out_handle.map(|handle| start.lerp(handle, time)); + + // Deal with unmatched extra paths by collapsing them + let source_paths_count = source_instance.instance.stroke_bezier_paths().count(); + let target_paths_count = target_instance.instance.stroke_bezier_paths().count(); + let source_paths = source_instance.instance.stroke_bezier_paths().skip(target_paths_count); + let target_paths = target_instance.instance.stroke_bezier_paths().skip(source_paths_count); + + for mut source_path in source_paths { + source_path.apply_transform(source_transform); + let end = source_path.manipulator_groups().last().map(|group| group.anchor).unwrap_or_default(); + for group in source_path.manipulator_groups_mut() { + group.anchor = group.anchor.lerp(end, time); + group.in_handle = group.in_handle.map(|handle| handle.lerp(end, time)); + group.out_handle = group.out_handle.map(|handle| handle.lerp(end, time)); + } + vector_data_instance.append_subpath(source_path, true); } - result_table.one_instance_mut().instance.append_subpath(target_path, true); + for mut target_path in target_paths { + target_path.apply_transform(target_transform); + let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default(); + for group in target_path.manipulator_groups_mut() { + group.anchor = start.lerp(group.anchor, time); + group.in_handle = group.in_handle.map(|handle| start.lerp(handle, time)); + group.out_handle = group.out_handle.map(|handle| start.lerp(handle, time)); + } + vector_data_instance.append_subpath(target_path, true); + } + + result_table.push(Instance { + instance: vector_data_instance, + alpha_blending: vector_data_alpha_blending, + ..Default::default() + }); } result_table @@ -1638,7 +1649,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, point_domain.push(next_id.next_id(), pos); // Add a new segment to be created later - new_segments.push([new_index, original_index]) + new_segments.push([new_index, original_index]); } } @@ -1707,12 +1718,16 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, #[node_macro::node(category("Vector"), path(graphene_core::vector))] fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { - let source_transform = source.transform(); - let source = source.one_instance_ref().instance; + let mut result_table = VectorDataTable::empty(); + + for source_instance in source.instance_iter() { + result_table.push(Instance { + instance: bevel_algorithm(source_instance.instance, source_instance.transform, distance), + ..Default::default() + }); + } - let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance)); - *result.transform_mut() = source_transform; - result + result_table } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -1734,15 +1749,14 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool { #[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))] fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { - let source_transform = source.transform(); - let mut source = source.one_instance_ref().instance.clone(); - - source.merge_by_distance(distance); + let mut result_table = VectorDataTable::empty(); - let mut result = VectorDataTable::new(source); - *result.transform_mut() = source_transform; + for mut source_instance in source.instance_iter() { + source_instance.instance.merge_by_distance(distance); + result_table.push(source_instance); + } - result + result_table } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -1750,16 +1764,13 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node< let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let vector_data = vector_data.eval(new_ctx).await; - let vector_data_transform = vector_data.transform(); - let vector_data = vector_data.one_instance_ref().instance; - - let mut area = 0.; - let scale = vector_data_transform.decompose_scale(); - for subpath in vector_data.stroke_bezier_paths() { - area += subpath.area(Some(1e-3), Some(1e-3)); - } - - area * scale[0] * scale[1] + vector_data + .instance_ref_iter() + .map(|vector_data_instance| { + let scale = vector_data_instance.transform.decompose_scale(); + vector_data_instance.instance.stroke_bezier_paths().map(|subpath| subpath.area(Some(1e-3), Some(1e-3))).sum::() * scale.x * scale.y + }) + .sum() } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -1767,49 +1778,52 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let vector_data = vector_data.eval(new_ctx).await; - let vector_data_transform = vector_data.transform(); - let vector_data = vector_data.one_instance_ref().instance; - - if centroid_type == CentroidType::Area { - let mut area = 0.; - let mut centroid = DVec2::ZERO; - for subpath in vector_data.stroke_bezier_paths() { - if let Some((subpath_centroid, subpath_area)) = subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)) { - if subpath_area == 0. { - continue; - } - area += subpath_area; - centroid += subpath_area * subpath_centroid; - } - } - - if area != 0. { - centroid /= area; - return vector_data_transform.transform_point2(centroid); - } + if vector_data.is_empty() { + return DVec2::ZERO; } - let mut length = 0.; + // All subpath centroid positions added together as if they were vectors from the origin. let mut centroid = DVec2::ZERO; - for subpath in vector_data.stroke_bezier_paths() { - if let Some((subpath_centroid, subpath_length)) = subpath.length_centroid_and_length(None, true) { - length += subpath_length; - centroid += subpath_length * subpath_centroid; + // Cumulative area or length of all subpaths + let mut sum = 0.; + + for vector_data_instance in vector_data.instance_ref_iter() { + for subpath in vector_data_instance.instance.stroke_bezier_paths() { + let partial = match centroid_type { + CentroidType::Area => subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.), + CentroidType::Length => subpath.length_centroid_and_length(None, true), + }; + if let Some((subpath_centroid, area_or_length)) = partial { + let subpath_centroid = vector_data_instance.transform.transform_point2(subpath_centroid); + + sum += area_or_length; + centroid += area_or_length * subpath_centroid; + } } } - if length != 0. { - centroid /= length; - return vector_data_transform.transform_point2(centroid); + if sum > 0. { + centroid / sum } + // Without a summed denominator, return the average of all positions instead + else { + let mut count: usize = 0; + + let summed_positions = vector_data + .instance_ref_iter() + .flat_map(|vector_data_instance| { + vector_data_instance + .instance + .point_domain + .positions() + .iter() + .map(|&p| vector_data_instance.transform.transform_point2(p)) + }) + .inspect(|_| count += 1) + .sum::(); - let positions = vector_data.point_domain.positions(); - if !positions.is_empty() { - let centroid = positions.iter().sum::() / (positions.len() as f64); - return vector_data_transform.transform_point2(centroid); + if count != 0 { summed_positions / (count as f64) } else { DVec2::ZERO } } - - DVec2::ZERO } #[cfg(test)] @@ -1885,7 +1899,7 @@ mod test { // Test a VectorData with non-zero rotation let square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)); let mut square = VectorDataTable::new(square); - *square.one_instance_mut().transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); + *square.get_mut(0).unwrap().transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4); let bounding_box = BoundingBoxNode { vector_data: FutureWrapperNode(square), } @@ -2044,7 +2058,7 @@ mod test { let vector_data = VectorData::from_subpath(source); let mut vector_data_table = VectorDataTable::new(vector_data.clone()); - *vector_data_table.one_instance_mut().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); + *vector_data_table.get_mut(0).unwrap().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.); let beveled = beveled.instance_ref_iter().next().unwrap().instance; diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index e9b5fe10d0..5e0d731b03 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -15,9 +15,7 @@ use graphene_core::{Ctx, GraphicElement, Node}; #[node_macro::node(category("Debug"))] fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec { - let vector_data = vector_data.one_instance_ref().instance; - - vector_data.point_domain.positions().to_vec() + vector_data.instance_iter().flat_map(|element| element.instance.point_domain.positions().to_vec()).collect() } #[derive(Clone, Copy, Debug, PartialEq)] @@ -96,39 +94,41 @@ where return target; } - let target_width = target.one_instance_ref().instance.width; - let target_height = target.one_instance_ref().instance.height; - let target_size = DVec2::new(target_width as f64, target_height as f64); + for target_instance in target.instance_mut_iter() { + let target_width = target_instance.instance.width; + let target_height = target_instance.instance.height; + let target_size = DVec2::new(target_width as f64, target_height as f64); - let texture_size = DVec2::new(texture.width as f64, texture.height as f64); + let texture_size = DVec2::new(texture.width as f64, texture.height as f64); - let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform().inverse(); + let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target_instance.transform.inverse(); - for position in positions { - let start = document_to_target.transform_point2(position).round(); - let stop = start + texture_size; + for position in &positions { + let start = document_to_target.transform_point2(*position).round(); + let stop = start + texture_size; - // Half-open integer ranges [start, stop). - let clamp_start = start.clamp(DVec2::ZERO, target_size).as_uvec2(); - let clamp_stop = stop.clamp(DVec2::ZERO, target_size).as_uvec2(); + // Half-open integer ranges [start, stop). + let clamp_start = start.clamp(DVec2::ZERO, target_size).as_uvec2(); + let clamp_stop = stop.clamp(DVec2::ZERO, target_size).as_uvec2(); - let blit_area_offset = (clamp_start.as_dvec2() - start).as_uvec2().min(texture_size.as_uvec2()); - let blit_area_dimensions = (clamp_stop - clamp_start).min(texture_size.as_uvec2() - blit_area_offset); + let blit_area_offset = (clamp_start.as_dvec2() - start).as_uvec2().min(texture_size.as_uvec2()); + let blit_area_dimensions = (clamp_stop - clamp_start).min(texture_size.as_uvec2() - blit_area_offset); - // Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop. - let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) }; - let target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) }; + // Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop. + let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) }; + let target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) }; - let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1); - let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1); - assert!(texture_index(max_x, max_y) < texture.data.len()); - assert!(target_index(max_x, max_y) < target.one_instance_ref().instance.data.len()); + let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1); + let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1); + assert!(texture_index(max_x, max_y) < texture.data.len()); + assert!(target_index(max_x, max_y) < target_instance.instance.data.len()); - for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y { - for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x { - let src_pixel = texture.data[texture_index(x, y)]; - let dst_pixel = &mut target.one_instance_mut().instance.data[target_index(x + clamp_start.x, y + clamp_start.y)]; - *dst_pixel = blend_mode.eval((src_pixel, *dst_pixel)); + for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y { + for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x { + let src_pixel = texture.data[texture_index(x, y)]; + let dst_pixel = &mut target_instance.instance.data[target_index(x + clamp_start.x, y + clamp_start.y)]; + *dst_pixel = blend_mode.eval((src_pixel, *dst_pixel)); + } } } } diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index 1157ea2777..8fbc963376 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -1,6 +1,5 @@ use graph_craft::proto::types::Percentage; use graphene_core::raster::image::{Image, ImageFrameTable}; -use graphene_core::transform::{Transform, TransformMut}; use graphene_core::{Color, Ctx}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; @@ -8,34 +7,34 @@ use std::cmp::{max, min}; #[node_macro::node(category("Raster"))] async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Percentage) -> ImageFrameTable { - let image_frame_transform = image_frame.transform(); - let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending; - - let image = image_frame.one_instance_ref().instance; - - // Prepare the image data for processing - let image_data = bytemuck::cast_vec(image.data.clone()); - let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); - let dynamic_image: image::DynamicImage = image_buffer.into(); - - // Run the dehaze algorithm - let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.); - - // Prepare the image data for returning - let buffer = dehazed_dynamic_image.to_rgba32f().into_raw(); - let color_vec = bytemuck::cast_vec(buffer); - let dehazed_image = Image { - width: image.width, - height: image.height, - data: color_vec, - base64_string: None, - }; - - let mut result = ImageFrameTable::new(dehazed_image); - *result.transform_mut() = image_frame_transform; - *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending; + let mut result_table = ImageFrameTable::empty(); + + for mut image_frame_instance in image_frame.instance_iter() { + let image = image_frame_instance.instance; + // Prepare the image data for processing + let image_data = bytemuck::cast_vec(image.data.clone()); + let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); + let dynamic_image: image::DynamicImage = image_buffer.into(); + + // Run the dehaze algorithm + let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.); + + // Prepare the image data for returning + let buffer = dehazed_dynamic_image.to_rgba32f().into_raw(); + let color_vec = bytemuck::cast_vec(buffer); + let dehazed_image = Image { + width: image.width, + height: image.height, + data: color_vec, + base64_string: None, + }; + + image_frame_instance.instance = dehazed_image; + image_frame_instance.source_node_id = None; + result_table.push(image_frame_instance); + } - result + result_table } // There is no real point in modifying these values because they do not change the final result all that much. diff --git a/node-graph/gstd/src/filter.rs b/node-graph/gstd/src/filter.rs index 685a30c610..f528ab067a 100644 --- a/node-graph/gstd/src/filter.rs +++ b/node-graph/gstd/src/filter.rs @@ -1,7 +1,6 @@ use graph_craft::proto::types::PixelLength; use graphene_core::raster::image::{Image, ImageFrameTable}; use graphene_core::raster::{Bitmap, BitmapMut}; -use graphene_core::transform::{Transform, TransformMut}; use graphene_core::{Color, Ctx}; /// Blurs the image with a Gaussian or blur kernel filter. @@ -19,26 +18,25 @@ async fn blur( /// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software. gamma: bool, ) -> ImageFrameTable { - let image_frame_transform = image_frame.transform(); - let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending; - - let image = image_frame.one_instance_ref().instance.clone(); - - // Run blur algorithm - let blurred_image = if radius < 0.1 { - // Minimum blur radius - image.clone() - } else if box_blur { - box_blur_algorithm(image, radius, gamma) - } else { - gaussian_blur_algorithm(image, radius, gamma) - }; - - let mut result = ImageFrameTable::new(blurred_image); - *result.transform_mut() = image_frame_transform; - *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending; + let mut result_table = ImageFrameTable::empty(); + for mut image_instance in image_frame.instance_iter() { + let image = image_instance.instance.clone(); + + // Run blur algorithm + let blurred_image = if radius < 0.1 { + // Minimum blur radius + image.clone() + } else if box_blur { + box_blur_algorithm(image, radius, gamma) + } else { + gaussian_blur_algorithm(image, radius, gamma) + }; - result + image_instance.instance = blurred_image; + image_instance.source_node_id = None; + result_table.push(image_instance); + } + result_table } // 1D gaussian kernel diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index 0f896e3fc8..5d42d0554e 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -5,8 +5,6 @@ use graph_craft::document::*; use graph_craft::proto::*; use graphene_core::raster::BlendMode; use graphene_core::raster::image::{Image, ImageFrameTable}; -use graphene_core::transform::Transform; -use graphene_core::transform::TransformMut; use graphene_core::*; use std::sync::Arc; use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor}; @@ -37,32 +35,32 @@ 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, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { - let foreground_transform = foreground.transform(); - let background_transform = background.transform(); + 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; - let background_alpha_blending = background.one_instance_ref().alpha_blending; + let foreground = foreground_instance.instance; + let background = background_instance.instance; - let foreground = foreground.one_instance_ref().instance; - let background = background.one_instance_ref().instance; + let foreground_size = DVec2::new(foreground.width as f64, foreground.height as f64); + let background_size = DVec2::new(background.width as f64, background.height as f64); - let foreground_size = DVec2::new(foreground.width as f64, foreground.height as f64); - 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 = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size); - // Transforms a point from the background image to the foreground image - let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size); + let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2(); + let translation: Vec2 = bg_to_fg.translation.as_vec2(); - let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2(); - let translation: Vec2 = bg_to_fg.translation.as_vec2(); + log::debug!("Executing gpu blend node!"); + let compiler = graph_craft::graphene_compiler::Compiler {}; - log::debug!("Executing gpu blend node!"); - let compiler = graph_craft::graphene_compiler::Compiler {}; - - let network = NodeNetwork { - exports: vec![NodeInput::node(NodeId(0), 0)], - nodes: [DocumentNode { - inputs: vec![NodeInput::Inline(InlineRust::new( - format!( - r#"graphene_core::raster::adjustments::BlendNode::new( + let network = NodeNetwork { + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: [DocumentNode { + inputs: vec![NodeInput::Inline(InlineRust::new( + format!( + r#"graphene_core::raster::adjustments::BlendNode::new( graphene_core::value::CopiedNode::new({}), graphene_core::value::CopiedNode::new({}), ).eval(( @@ -78,146 +76,146 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, backgr }}, i1[(_global_index.y * i0 + _global_index.x) as usize], ))"#, - TaggedValue::BlendMode(blend_mode).to_primitive_string(), - TaggedValue::F64(opacity).to_primitive_string(), - ), - concrete![Color], - ))], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), + TaggedValue::BlendMode(blend_mode).to_primitive_string(), + TaggedValue::F64(opacity).to_primitive_string(), + ), + concrete![Color], + ))], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), + ..Default::default() + }] + .into_iter() + .enumerate() + .map(|(id, node)| (NodeId(id as u64), node)) + .collect(), ..Default::default() - }] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }; - log::debug!("compiling network"); - let proto_networks: Result, _> = 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(); - }; - let proto_networks = proto_networks_result; - log::debug!("compiling shader"); - - let shader = compilation_client::compile( - proto_networks, - vec![ - concrete!(u32), - concrete!(Color), - concrete!(Color), - concrete!(u32), - concrete_with_name!(Mat2, "Mat2"), - concrete_with_name!(Vec2, "Vec2"), - ], - vec![concrete!(Color)], - ShaderIO { - inputs: vec![ - ShaderInput::UniformBuffer((), concrete!(u32)), // width of the output image - ShaderInput::StorageBuffer((), concrete!(Color)), // background image - ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image - ShaderInput::UniformBuffer((), concrete!(u32)), // width of the foreground image - ShaderInput::UniformBuffer((), concrete_with_name!(Mat2, "Mat2")), // bg_to_fg.matrix2 - ShaderInput::UniformBuffer((), concrete_with_name!(Vec2, "Vec2")), // bg_to_fg.translation - ShaderInput::OutputBuffer((), concrete!(Color)), + }; + log::debug!("compiling network"); + let proto_networks: Result, _> = 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(); + }; + let proto_networks = proto_networks_result; + log::debug!("compiling shader"); + + let shader = compilation_client::compile( + proto_networks, + vec![ + concrete!(u32), + concrete!(Color), + concrete!(Color), + concrete!(u32), + concrete_with_name!(Mat2, "Mat2"), + concrete_with_name!(Vec2, "Vec2"), ], - output: ShaderInput::OutputBuffer((), concrete!(Color)), - }, - ) - .await - .unwrap(); - let len = background.data.len(); - - let executor = WgpuExecutor::new() - .await - .expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser."); - log::debug!("creating buffer"); - let width_uniform = executor.create_uniform_buffer(background.width).unwrap(); - let bg_storage_buffer = executor - .create_storage_buffer( - background.data.clone(), - StorageBufferOptions { - cpu_writable: false, - gpu_writable: true, - cpu_readable: false, - storage: true, - }, - ) - .unwrap(); - let fg_storage_buffer = executor - .create_storage_buffer( - foreground.data.clone(), - StorageBufferOptions { - cpu_writable: false, - gpu_writable: true, - cpu_readable: false, - storage: true, + vec![concrete!(Color)], + ShaderIO { + inputs: vec![ + ShaderInput::UniformBuffer((), concrete!(u32)), // width of the output image + ShaderInput::StorageBuffer((), concrete!(Color)), // background image + ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image + ShaderInput::UniformBuffer((), concrete!(u32)), // width of the foreground image + ShaderInput::UniformBuffer((), concrete_with_name!(Mat2, "Mat2")), // bg_to_fg.matrix2 + ShaderInput::UniformBuffer((), concrete_with_name!(Vec2, "Vec2")), // bg_to_fg.translation + ShaderInput::OutputBuffer((), concrete!(Color)), + ], + output: ShaderInput::OutputBuffer((), concrete!(Color)), }, ) + .await .unwrap(); - let fg_width_uniform = executor.create_uniform_buffer(foreground.width).unwrap(); - let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap(); - let translation_uniform = executor.create_uniform_buffer(translation).unwrap(); - let width_uniform = Arc::new(width_uniform); - let bg_storage_buffer = Arc::new(bg_storage_buffer); - let fg_storage_buffer = Arc::new(fg_storage_buffer); - let fg_width_uniform = Arc::new(fg_width_uniform); - let transform_uniform = Arc::new(transform_uniform); - let translation_uniform = Arc::new(translation_uniform); - let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap(); - let output_buffer = Arc::new(output_buffer); - let readback_buffer = executor.create_output_buffer(len, concrete!(Color), true).unwrap(); - let readback_buffer = Arc::new(readback_buffer); - log::debug!("created buffer"); - let bind_group = Bindgroup { - buffers: vec![ - width_uniform.clone(), - bg_storage_buffer.clone(), - fg_storage_buffer.clone(), - fg_width_uniform.clone(), - transform_uniform.clone(), - translation_uniform.clone(), - ], - }; - - let shader = Shader { - source: shader.spirv_binary.into(), - name: "gpu::eval", - io: shader.io, - }; - log::debug!("loading shader"); - log::debug!("shader: {:?}", shader.source); - let shader = executor.load_shader(shader).unwrap(); - log::debug!("loaded shader"); - let pipeline = PipelineLayout { - shader: shader.into(), - entry_point: "eval".to_string(), - bind_group: bind_group.into(), - output_buffer: output_buffer.clone(), - }; - log::debug!("created pipeline"); - let compute_pass = executor - .create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.width, background.height)) - .unwrap(); - executor.execute_compute_pipeline(compute_pass).unwrap(); - log::debug!("executed pipeline"); - log::debug!("reading buffer"); - let result = executor.read_output_buffer(readback_buffer).await.unwrap(); - let colors = bytemuck::pod_collect_to_vec::(result.as_slice()); - - let created_image = Image { - data: colors, - width: background.width, - height: background.height, - ..Default::default() - }; - - let mut result = ImageFrameTable::new(created_image); - *result.transform_mut() = background_transform; - *result.one_instance_mut().alpha_blending = *background_alpha_blending; - - result + let len = background.data.len(); + + let executor = WgpuExecutor::new() + .await + .expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser."); + log::debug!("creating buffer"); + let width_uniform = executor.create_uniform_buffer(background.width).unwrap(); + let bg_storage_buffer = executor + .create_storage_buffer( + background.data.clone(), + StorageBufferOptions { + cpu_writable: false, + gpu_writable: true, + cpu_readable: false, + storage: true, + }, + ) + .unwrap(); + let fg_storage_buffer = executor + .create_storage_buffer( + foreground.data.clone(), + StorageBufferOptions { + cpu_writable: false, + gpu_writable: true, + cpu_readable: false, + storage: true, + }, + ) + .unwrap(); + let fg_width_uniform = executor.create_uniform_buffer(foreground.width).unwrap(); + let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap(); + let translation_uniform = executor.create_uniform_buffer(translation).unwrap(); + let width_uniform = Arc::new(width_uniform); + let bg_storage_buffer = Arc::new(bg_storage_buffer); + let fg_storage_buffer = Arc::new(fg_storage_buffer); + let fg_width_uniform = Arc::new(fg_width_uniform); + let transform_uniform = Arc::new(transform_uniform); + let translation_uniform = Arc::new(translation_uniform); + let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap(); + let output_buffer = Arc::new(output_buffer); + let readback_buffer = executor.create_output_buffer(len, concrete!(Color), true).unwrap(); + let readback_buffer = Arc::new(readback_buffer); + log::debug!("created buffer"); + let bind_group = Bindgroup { + buffers: vec![ + width_uniform.clone(), + bg_storage_buffer.clone(), + fg_storage_buffer.clone(), + fg_width_uniform.clone(), + transform_uniform.clone(), + translation_uniform.clone(), + ], + }; + + let shader = Shader { + source: shader.spirv_binary.into(), + name: "gpu::eval", + io: shader.io, + }; + log::debug!("loading shader"); + log::debug!("shader: {:?}", shader.source); + let shader = executor.load_shader(shader).unwrap(); + log::debug!("loaded shader"); + let pipeline = PipelineLayout { + shader: shader.into(), + entry_point: "eval".to_string(), + bind_group: bind_group.into(), + output_buffer: output_buffer.clone(), + }; + log::debug!("created pipeline"); + let compute_pass = executor + .create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.width, background.height)) + .unwrap(); + executor.execute_compute_pipeline(compute_pass).unwrap(); + log::debug!("executed pipeline"); + log::debug!("reading buffer"); + let result = executor.read_output_buffer(readback_buffer).await.unwrap(); + let colors = bytemuck::pod_collect_to_vec::(result.as_slice()); + + let created_image = Image { + data: colors, + width: background.width, + height: background.height, + ..Default::default() + }; + + background_instance.instance = created_image; + background_instance.source_node_id = None; + result_table.push(background_instance); + } + result_table } // struct ComputePass { diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 164db26208..a9b24853f7 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -16,17 +16,17 @@ async fn image_color_palette( let mut histogram: Vec = vec![0; (bins + 1.) as usize]; let mut colors: Vec> = vec![vec![]; (bins + 1.) as usize]; - let image = image.one_instance_ref().instance; + for image_instance in image.instance_ref_iter() { + for pixel in image_instance.instance.data.iter() { + let r = pixel.r() * GRID; + let g = pixel.g() * GRID; + let b = pixel.b() * GRID; - for pixel in image.data.iter() { - let r = pixel.r() * GRID; - let g = pixel.g() * GRID; - let b = pixel.b() * GRID; + let bin = (r * GRID + g * GRID + b * GRID) as usize; - let bin = (r * GRID + g * GRID + b * GRID) as usize; - - histogram[bin] += 1; - colors[bin].push(pixel.to_gamma_srgb()); + histogram[bin] += 1; + colors[bin].push(pixel.to_gamma_srgb()); + } } let shorted = histogram.iter().enumerate().filter(|&(_, &count)| count > 0).map(|(i, _)| i).collect::>(); diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 8879085034..e0ace4f399 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -27,66 +27,74 @@ impl From for Error { #[node_macro::node(category("Debug: Raster"))] fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable) -> ImageFrameTable { - let image_frame_transform = image_frame.transform(); - let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending; + let mut result_table = ImageFrameTable::empty(); - let image = image_frame.one_instance_ref().instance; + for mut image_frame_instance in image_frame.instance_iter() { + let image_frame_transform = image_frame_instance.transform; + let image = image_frame_instance.instance; - // Resize the image using the image crate - let data = bytemuck::cast_vec(image.data.clone()); + // Resize the image using the image crate + let data = bytemuck::cast_vec(image.data.clone()); - let footprint = ctx.footprint(); - let viewport_bounds = footprint.viewport_bounds_in_local_space(); - let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); - let intersection = viewport_bounds.intersect(&image_bounds); - let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); - let size = intersection.size(); - let size_px = image_size.transform_vector2(size).as_uvec2(); - - // If the image would not be visible, return an empty image - if size.x <= 0. || size.y <= 0. { - return ImageFrameTable::one_empty_image(); - } - - let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); + let footprint = ctx.footprint(); + let viewport_bounds = footprint.viewport_bounds_in_local_space(); + let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); + let intersection = viewport_bounds.intersect(&image_bounds); + let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); + let size = intersection.size(); + let size_px = image_size.transform_vector2(size).as_uvec2(); - let dynamic_image: ::image::DynamicImage = image_buffer.into(); - let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); - let offset_px = image_size.transform_vector2(offset).as_uvec2(); - let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y); - - let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length(); - let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length(); - let mut new_width = size_px.x; - let mut new_height = size_px.y; - - // Only downscale the image for now - let resized = if new_width < image.width || new_height < image.height { - new_width = viewport_resolution_x as u32; - new_height = viewport_resolution_y as u32; - // TODO: choose filter based on quality requirements - cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle) - } else { - cropped - }; - let buffer = resized.to_rgba32f(); - let buffer = buffer.into_raw(); - let vec = bytemuck::cast_vec(buffer); - let image = Image { - width: new_width, - height: new_height, - data: vec, - base64_string: None, - }; - // we need to adjust the offset if we truncate the offset calculation + // If the image would not be visible, add nothing. + if size.x <= 0. || size.y <= 0. { + continue; + } - let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); + let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); + + let dynamic_image: ::image::DynamicImage = image_buffer.into(); + let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); + let offset_px = image_size.transform_vector2(offset).as_uvec2(); + let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y); + + let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length(); + let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length(); + let mut new_width = size_px.x; + let mut new_height = size_px.y; + + // Only downscale the image for now + let resized = if new_width < image.width || new_height < image.height { + new_width = viewport_resolution_x as u32; + new_height = viewport_resolution_y as u32; + // TODO: choose filter based on quality requirements + cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle) + } else { + cropped + }; + let buffer = resized.to_rgba32f(); + let buffer = buffer.into_raw(); + let vec = bytemuck::cast_vec(buffer); + let image = Image { + width: new_width, + height: new_height, + data: vec, + base64_string: None, + }; + // we need to adjust the offset if we truncate the offset calculation + + let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); + + image_frame_instance.transform = new_transform; + image_frame_instance.source_node_id = None; + image_frame_instance.instance = image; + result_table.push(image_frame_instance) + } - let mut result = ImageFrameTable::new(image); - *result.transform_mut() = new_transform; - *result.one_instance_mut().alpha_blending = *image_frame_alpha_blending; + // TODO: Remove when we've completed part 6 of the instance tables refactor + if result_table.is_empty() { + return ImageFrameTable::one_empty_image(); + } - result + result_table } #[node_macro::node(category("Raster"))] @@ -251,45 +259,55 @@ where #[node_macro::node(category(""))] fn extend_image_to_bounds(_: impl Ctx, image: ImageFrameTable, bounds: DAffine2) -> ImageFrameTable { - let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox(); - let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); - if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { - return image; - } + let mut result_table = ImageFrameTable::empty(); + + for mut image_instance in image.instance_iter() { + let image_aabb = Bbox::unit().affine_transform(image_instance.transform).to_axis_aligned_bbox(); + let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); + if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { + result_table.push(image_instance); + continue; + } - let image_instance = image.one_instance_ref().instance; - if image_instance.width == 0 || image_instance.height == 0 { - return empty_image((), bounds, Color::TRANSPARENT); - } + let image_data = image_instance.instance.data; + let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height); + if image_width == 0 || image_height == 0 { + for image_instance in empty_image((), bounds, Color::TRANSPARENT).instance_iter() { + result_table.push(image_instance); + } + continue; + } - let orig_image_scale = DVec2::new(image_instance.width as f64, image_instance.height as f64); - let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse(); - let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); - - let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); - let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale); - let new_scale = new_end - new_start; - - // Copy over original image into enlarged image. - let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); - let offset_in_new_image = (-new_start).as_uvec2(); - for y in 0..image_instance.height { - let old_start = y * image_instance.width; - let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x; - let old_row = &image_instance.data[old_start as usize..(old_start + image_instance.width) as usize]; - let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.width) as usize]; - new_row.copy_from_slice(old_row); - } + let orig_image_scale = DVec2::new(image_width as f64, image_height as f64); + let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.transform.inverse(); + let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); + + let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); + let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale); + let new_scale = new_end - new_start; + + // Copy over original image into enlarged image. + let mut new_image = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); + let offset_in_new_image = (-new_start).as_uvec2(); + for y in 0..image_height { + let old_start = y * image_width; + let new_start = (y + offset_in_new_image.y) * new_image.width + offset_in_new_image.x; + let old_row = &image_data[old_start as usize..(old_start + image_width) as usize]; + let new_row = &mut new_image.data[new_start as usize..(new_start + image_width) as usize]; + new_row.copy_from_slice(old_row); + } - // Compute new transform. - // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); - let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); + // Compute new transform. + // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); + let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); - let mut result = ImageFrameTable::new(new_img); - *result.transform_mut() = new_texture_to_layer_space; - *result.one_instance_mut().alpha_blending = *image.one_instance_ref().alpha_blending; + image_instance.instance = new_image; + image_instance.transform = new_texture_to_layer_space; + image_instance.source_node_id = None; + result_table.push(image_instance); + } - result + result_table } #[node_macro::node(category("Debug: Raster"))] @@ -299,11 +317,13 @@ fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTabl let image = Image::new(width, height, color); - let mut result = ImageFrameTable::new(image); - *result.transform_mut() = transform; - *result.one_instance_mut().alpha_blending = AlphaBlending::default(); + let mut result_table = ImageFrameTable::new(image); + let image_instance = result_table.get_mut(0).unwrap(); + *image_instance.transform = transform; + *image_instance.alpha_blending = AlphaBlending::default(); - result + // Callers of empty_image can safely unwrap on returned table + result_table } /// Constructs a raster image. diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 4401d1d70c..9f6e623a98 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -30,9 +30,10 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; #[node_macro::node(category("Debug: GPU"))] async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc { - return Arc::new(editor.application_io.as_ref().unwrap().create_window()); + Arc::new(editor.application_io.as_ref().unwrap().create_window()) } +// TODO: Fix and reenable in order to get the 'Draw Canvas' node working again. // #[cfg(target_arch = "wasm32")] // use wasm_bindgen::Clamped; // diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 0fb3197f2f..3592382fc3 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -27,16 +27,17 @@ use wgpu_executor::{WgpuSurface, WindowHandle}; fn node_registry() -> HashMap> { let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ into_node!(from: f64, to: f64), - into_node!(from: ImageFrameTable, to: GraphicGroupTable), into_node!(from: f64, to: f64), into_node!(from: u32, to: f64), into_node!(from: u8, to: u32), - into_node!(from: ImageFrameTable, to: GraphicGroupTable), - into_node!(from: VectorDataTable, to: GraphicGroupTable), + into_node!(from: VectorDataTable, to: VectorDataTable), into_node!(from: VectorDataTable, to: GraphicElement), - into_node!(from: ImageFrameTable, to: GraphicElement), - into_node!(from: GraphicGroupTable, to: GraphicElement), into_node!(from: VectorDataTable, to: GraphicGroupTable), + into_node!(from: GraphicGroupTable, to: GraphicGroupTable), + into_node!(from: GraphicGroupTable, to: GraphicElement), + into_node!(from: ImageFrameTable, to: ImageFrameTable), + into_node!(from: ImageFrameTable, to: ImageFrameTable), + into_node!(from: ImageFrameTable, to: GraphicElement), into_node!(from: ImageFrameTable, to: GraphicGroupTable), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),