Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more) (#2610)

* Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more)

* Fix test?

* Fix more tests?

* Fix moar test??

* Clean up instance method naming
This commit is contained in:
Keavon Chambers 2025-04-22 17:55:57 -07:00 committed by GitHub
parent a29802de36
commit ac9fb2b02d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 811 additions and 529 deletions

View file

@ -1,5 +1,5 @@
use crate::application_io::{ImageTexture, TextureFrameTable};
use crate::instances::Instances;
use crate::instances::{Instance, Instances};
use crate::raster::BlendMode;
use crate::raster::image::{Image, ImageFrameTable};
use crate::transform::TransformMut;
@ -67,10 +67,12 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::empty();
for (graphic_element, source_node_id) in old.elements {
let last = graphic_group_table.push(graphic_element);
*last.source_node_id = source_node_id;
*last.transform = old.transform;
*last.alpha_blending = old.alpha_blending;
graphic_group_table.push(Instance {
instance: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
@ -78,12 +80,14 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
let mut graphic_group_table = GraphicGroupTable::empty();
for instance in old_table.instances() {
for instance in old_table.instance_ref_iter() {
for (graphic_element, source_node_id) in &instance.instance.elements {
let new_row = graphic_group_table.push(graphic_element.clone());
*new_row.source_node_id = *source_node_id;
*new_row.transform = *instance.transform;
*new_row.alpha_blending = *instance.alpha_blending;
graphic_group_table.push(Instance {
instance: graphic_element.clone(),
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
@ -276,8 +280,12 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = ArtboardGroupTable::empty();
for (artboard, source_node_id) in artboard_group.artboards {
let pushed = table.push(artboard);
*pushed.source_node_id = source_node_id;
table.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
@ -290,38 +298,18 @@ pub type ArtboardGroupTable = Instances<Artboard>;
#[node_macro::node(category(""))]
async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
// Get the penultimate element of the node path, or None if the path is too short
let pushed = stack.push(element);
*pushed.source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push(Instance {
instance: element,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
stack
}
// // TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
// #[node_macro::node(category("Debug"))]
// async fn concatenate<T: Clone>(
// _: impl Ctx,
// #[implementations(
// GraphicGroupTable,
// VectorDataTable,
// ImageFrameTable<Color>,
// TextureFrameTable,
// )]
// from: Instances<T>,
// #[expose]
// #[implementations(
// GraphicGroupTable,
// VectorDataTable,
// ImageFrameTable<Color>,
// TextureFrameTable,
// )]
// mut to: Instances<T>,
// ) -> Instances<T> {
// for instance in from.instances() {
// to.push_instance(instance);
// }
// to
// }
#[node_macro::node(category("Debug"))]
async fn to_element<Data: Into<GraphicElement> + 'n>(
_: impl Ctx,
@ -354,7 +342,7 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
for current_instance in current_group_table.instances() {
for current_instance in current_group_table.instance_ref_iter() {
let current_element = current_instance.instance.clone();
let reference = *current_instance.source_node_id;
@ -364,7 +352,7 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
// If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.instances_mut() {
for graphic_element in current_element.instance_mut_iter() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
}
@ -372,10 +360,12 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
_ => {
let pushed = output_group_table.push(current_element);
*pushed.source_node_id = reference;
// Apply the parent group's transform to the leaf element
*pushed.transform = *current_instance.transform * *pushed.transform;
output_group_table.push(Instance {
instance: current_element,
transform: *current_instance.transform,
alpha_blending: *current_instance.alpha_blending,
source_node_id: reference,
});
}
}
}
@ -427,8 +417,12 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
let pushed = artboards.push(artboard);
*pushed.source_node_id = encapsulating_node_id;
artboards.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}

View file

@ -299,7 +299,7 @@ pub trait GraphicElementRendered {
impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
render.parent_tag(
"g",
|attributes| {
@ -325,12 +325,12 @@ impl GraphicElementRendered for GraphicGroupTable {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let transform = transform * *instance.transform;
let alpha_blending = *instance.alpha_blending;
let mut layer = false;
if let Some(bounds) = self.instances().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
if let Some(bounds) = self.instance_ref_iter().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
let blend_mode = match render_params.view_mode {
ViewMode::Outline => peniko::Mix::Normal,
_ => alpha_blending.blend_mode.into(),
@ -356,13 +356,13 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.filter_map(|element| element.instance.bounding_box(transform * *element.transform))
.reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
if let Some(element_id) = instance.source_node_id {
let mut footprint = footprint;
footprint.transform *= *instance.transform;
@ -374,7 +374,7 @@ impl GraphicElementRendered for GraphicGroupTable {
if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
@ -390,7 +390,7 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets);
@ -404,11 +404,11 @@ impl GraphicElementRendered for GraphicGroupTable {
}
fn contains_artboard(&self) -> bool {
self.instances().any(|instance| instance.instance.contains_artboard())
self.instance_ref_iter().any(|instance| instance.instance.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.instance.new_ids_from_hash(*instance.source_node_id);
}
}
@ -420,7 +420,7 @@ impl GraphicElementRendered for GraphicGroupTable {
impl GraphicElementRendered for VectorDataTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let multiplied_transform = render.transform * *instance.transform;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
@ -469,7 +469,7 @@ impl GraphicElementRendered for VectorDataTable {
use vello::kurbo::{Cap, Join};
use vello::peniko;
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let multiplied_transform = parent_transform * *instance.transform;
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
@ -614,7 +614,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.flat_map(|instance| {
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
@ -633,7 +633,7 @@ 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.instances().map(|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;
@ -661,7 +661,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
@ -679,7 +679,7 @@ impl GraphicElementRendered for VectorDataTable {
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}
}
@ -796,42 +796,42 @@ impl GraphicElementRendered for Artboard {
impl GraphicElementRendered for ArtboardGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for artboard in self.instances() {
for artboard in self.instance_ref_iter() {
artboard.instance.render_svg(render, render_params);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.render_to_vello(scene, transform, context, render_params);
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
self.instance_ref_iter().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
instance.instance.add_upstream_click_targets(click_targets);
}
}
fn contains_artboard(&self) -> bool {
self.instances().count() > 0
self.instance_ref_iter().count() > 0
}
}
impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let transform = *instance.transform * render.transform;
let image = &instance.instance;
@ -870,7 +870,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
for instance in self.instances() {
for instance in self.instance_ref_iter() {
let image = &instance.instance;
if image.data.is_empty() {
return;
@ -883,7 +883,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances()
self.instance_ref_iter()
.flat_map(|instance| {
let transform = transform * *instance.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
@ -939,7 +939,7 @@ impl GraphicElementRendered for RasterFrame {
match self {
RasterFrame::ImageFrame(image) => {
for instance in image.instances() {
for instance in image.instance_ref_iter() {
let image = &instance.instance;
if image.data.is_empty() {
return;
@ -951,7 +951,7 @@ impl GraphicElementRendered for RasterFrame {
}
}
RasterFrame::TextureFrame(image_texture) => {
for instance in image_texture.instances() {
for instance in image_texture.instance_ref_iter() {
let image =
vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat);

View file

@ -40,39 +40,15 @@ impl<T> Instances<T> {
}
}
pub fn push(&mut self, instance: T) -> InstanceMut<T> {
self.instance.push(instance);
self.transform.push(DAffine2::IDENTITY);
self.alpha_blending.push(AlphaBlending::default());
self.source_node_id.push(None);
InstanceMut {
instance: self.instance.last_mut().expect("Shouldn't be empty"),
transform: self.transform.last_mut().expect("Shouldn't be empty"),
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
}
pub fn push(&mut self, instance: Instance<T>) {
self.instance.push(instance.instance);
self.transform.push(instance.transform);
self.alpha_blending.push(instance.alpha_blending);
self.source_node_id.push(instance.source_node_id);
}
pub fn push_instance(&mut self, instance: Instance<T>) -> InstanceMut<T>
where
T: Clone,
{
self.instance.push(instance.instance.clone());
self.transform.push(*instance.transform);
self.alpha_blending.push(*instance.alpha_blending);
self.source_node_id.push(*instance.source_node_id);
InstanceMut {
instance: self.instance.last_mut().expect("Shouldn't be empty"),
transform: self.transform.last_mut().expect("Shouldn't be empty"),
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
}
}
pub fn one_instance(&self) -> Instance<T> {
Instance {
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())),
@ -91,12 +67,12 @@ impl<T> Instances<T> {
}
}
pub fn instances(&self) -> impl DoubleEndedIterator<Item = Instance<T>> {
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
instance,
transform,
@ -105,7 +81,21 @@ impl<T> Instances<T> {
})
}
pub fn instances_mut(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
self.instance
.iter_mut()
.zip(self.transform.iter_mut())
@ -119,12 +109,12 @@ impl<T> Instances<T> {
})
}
pub fn get(&self, index: usize) -> Option<Instance<T>> {
pub fn get(&self, index: usize) -> Option<InstanceRef<T>> {
if index >= self.instance.len() {
return None;
}
Some(Instance {
Some(InstanceRef {
instance: &self.instance[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
@ -199,12 +189,13 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
}
#[derive(Copy, Clone, Debug)]
pub struct Instance<'a, T> {
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub instance: &'a mut T,
@ -213,10 +204,32 @@ pub struct InstanceMut<'a, T> {
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Debug)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> Instance<T> {
pub fn to_graphic_element<U>(self) -> Instance<U>
where
T: Into<U>,
{
Instance {
instance: self.instance.into(),
transform: self.transform,
alpha_blending: self.alpha_blending,
source_node_id: self.source_node_id,
}
}
}
// VECTOR DATA TABLE
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl TransformMut for VectorDataTable {
@ -228,7 +241,7 @@ impl TransformMut for VectorDataTable {
// TEXTURE FRAME TABLE
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl TransformMut for TextureFrameTable {
@ -243,7 +256,7 @@ where
GraphicElement: From<Image<P>>,
{
fn transform(&self) -> DAffine2 {
*self.one_instance().transform
*self.one_instance_ref().transform
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>

View file

@ -365,6 +365,30 @@ fn not_equals<U: core::cmp::PartialEq<T>, T>(
other_value != value
}
/// The less-than operation (<) compares two values and returns true if the first value is less than the second, or false if it is not.
/// If enabled with "Or Equal", the less-than-or-equal operation (<=) will be used instead.
#[node_macro::node(category("Math: Logic"))]
fn less_than<T: core::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
or_equal: bool,
) -> bool {
if or_equal { value <= other_value } else { value < other_value }
}
/// The greater-than operation (>) compares two values and returns true if the first value is greater than the second, or false if it is not.
/// If enabled with "Or Equal", the greater-than-or-equal operation (>=) will be used instead.
#[node_macro::node(category("Math: Logic"))]
fn greater_than<T: core::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
or_equal: bool,
) -> bool {
if or_equal { value >= other_value } else { value > other_value }
}
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
#[node_macro::node(category("Math: Logic"))]
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {

View file

@ -300,21 +300,21 @@ trait SetBlendMode {
impl SetBlendMode for VectorDataTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for GraphicGroupTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for ImageFrameTable<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.blend_mode = blend_mode;
}
}

View file

@ -598,7 +598,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result = self.clone();
for (over, under) in result.instances_mut().zip(under.instances()) {
for (over, under) in result.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 {
@ -731,7 +731,7 @@ where
GraphicElement: From<Image<P>>,
{
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
for c in instance.instance.data.iter_mut() {
*c = map_fn(c);
}
@ -1360,14 +1360,14 @@ impl MultiplyAlpha for Color {
}
impl MultiplyAlpha for VectorDataTable {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for GraphicGroupTable {
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
@ -1377,7 +1377,7 @@ where
GraphicElement: From<Image<P>>,
{
fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instances_mut() {
for instance in self.instance_mut_iter() {
instance.alpha_blending.opacity *= factor as f32;
}
}
@ -1577,7 +1577,7 @@ mod test {
let opacity = 100_f64;
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
let result = result.instances().next().unwrap().instance;
let result = result.instance_ref_iter().next().unwrap().instance;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View file

@ -31,7 +31,7 @@ struct BrushCacheImpl {
impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation.
if background.one_instance().instance != self.background.one_instance().instance {
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
self.background = background.clone();
return BrushPlan {
strokes: input.to_vec(),

View file

@ -232,7 +232,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().instance.clone(),
image: image.one_instance_ref().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@ -274,7 +274,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance().instance.image.clone()),
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
@ -315,8 +315,8 @@ where
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image_transform = self.one_instance().transform;
let image = self.one_instance().instance;
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);
@ -333,19 +333,19 @@ where
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_instance().instance;
let image = self.one_instance_ref().instance;
image.width()
}
fn height(&self) -> u32 {
let image = self.one_instance().instance;
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().instance;
let image = self.one_instance_ref().instance;
image.get_pixel(x, y)
}

View file

@ -189,7 +189,7 @@ async fn transform<T: 'n + 'static>(
let mut transform_target = transform_target.eval(ctx.into_context()).await;
for data_transform in transform_target.instances_mut() {
for data_transform in transform_target.instance_mut_iter() {
*data_transform.transform = matrix * *data_transform.transform;
}
@ -202,7 +202,7 @@ fn replace_transform<Data, TransformInput: Transform>(
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Instances<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Instances<Data> {
for data_transform in data.instances_mut() {
for data_transform in data.instance_mut_iter() {
*data_transform.transform = transform.transform();
}
data

View file

@ -1,39 +1,74 @@
use crate::instances::Instance;
use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use crate::instances::{InstanceRef, Instances};
use crate::raster::Color;
use crate::raster::image::ImageFrameTable;
use crate::transform::TransformMut;
use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
async fn instance_on_points(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: VectorDataTable,
#[implementations(Context -> VectorDataTable)] instance_node: impl Node<'n, Context<'static>, Output = VectorDataTable>,
) -> VectorDataTable {
let mut result = VectorDataTable::empty();
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
reverse: bool,
) -> GraphicGroupTable {
let mut result_table = GraphicGroupTable::empty();
for Instance { instance: points, transform, .. } in points.instances() {
for (index, &point) in points.point_domain.positions().iter().enumerate() {
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let instanced = instance_node.eval(new_ctx.into_context()).await;
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in instanced.instances() {
let instanced = result.push_instance(instanced);
*instanced.transform *= DAffine2::from_translation(transformed_point);
for mut instanced in generated_instance.instance_iter() {
instanced.transform.translate(transformed_point);
result_table.push(instanced.to_graphic_element());
}
};
let range = points.point_domain.positions().iter().enumerate();
if reverse {
for (index, &point) in range.rev() {
iteration(index, point).await;
}
} else {
for (index, &point) in range {
iteration(index, point).await;
}
}
}
// TODO: Remove once we support empty tables, currently this is here to avoid crashing
if result.is_empty() {
return VectorDataTable::new(VectorData::empty());
}
result
result_table
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
#[default(1)] count: u64,
reverse: bool,
) -> GraphicGroupTable {
let count = count.max(1) as usize;
let mut result_table = GraphicGroupTable::empty();
for index in 0..count {
let index = if reverse { count - index - 1 } else { index };
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() {
result_table.push(instanced.to_graphic_element());
}
}
result_table
}
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
Ok(Some(position)) => return *position,
@ -43,7 +78,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
Default::default()
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() {
Some(index) => return index as f64,
@ -87,10 +122,17 @@ mod test {
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let repeated = super::instance_on_points(owned, points, &rect).await;
let repeated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(repeated.len(), positions.len());
for (position, instanced) in positions.into_iter().zip(repeated.instances()) {
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
let bounds = instanced
.instance
.as_vector_data()
.unwrap()
.one_instance_ref()
.instance
.bounding_box_with_transform(*instanced.transform)
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}

View file

@ -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().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
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!(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().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
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!(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

@ -433,29 +433,29 @@ impl VectorData {
}
}
pub fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64) {
let point_map = other
pub fn concat(&mut self, additional: &Self, transform_of_additional: DAffine2, collision_hash_seed: u64) {
let point_map = additional
.point_domain
.ids()
.iter()
.filter(|id| self.point_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let segment_map = other
let segment_map = additional
.segment_domain
.ids()
.iter()
.filter(|id| self.segment_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let region_map = other
let region_map = additional
.region_domain
.ids()
.iter()
.filter(|id| self.region_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let id_map = IdMap {
@ -465,14 +465,14 @@ impl VectorData {
region_map,
};
self.point_domain.concat(&other.point_domain, transform, &id_map);
self.segment_domain.concat(&other.segment_domain, transform, &id_map);
self.region_domain.concat(&other.region_domain, transform, &id_map);
self.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map);
self.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map);
self.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map);
// TODO: properly deal with fills such as gradients
self.style = other.style.clone();
self.style = additional.style.clone();
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
self.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied());
}
}

View file

@ -181,6 +181,14 @@ impl PointDomain {
}
}
pub fn len(&self) -> usize {
self.id.len()
}
pub fn is_empty(&self) -> bool {
self.id.is_empty()
}
/// Iterate over point IDs and positions
pub fn iter(&self) -> impl Iterator<Item = (PointId, DVec2)> + '_ {
self.ids().iter().copied().zip(self.positions().iter().copied())

View file

@ -424,7 +424,7 @@ 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<VectorModification>) -> VectorDataTable {
let vector_data_transform = *vector_data.one_instance().transform;
let vector_data_transform = *vector_data.one_instance_ref().transform;
let vector_data = vector_data.one_instance_mut().instance;
modification.apply(vector_data);

View file

@ -2,7 +2,8 @@ use super::algorithms::offset_subpath::offset_subpath;
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::instances::{InstanceMut, Instances};
use crate::instances::{Instance, InstanceMut, Instances};
use crate::raster::image::ImageFrameTable;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
@ -11,8 +12,10 @@ use crate::vector::style::{LineCap, LineJoin};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2};
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
/// Implemented for types that can be converted to an iterator of vector data.
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
@ -23,15 +26,15 @@ trait VectorDataTableIterMut {
impl VectorDataTableIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
// Grab only the direct children
self.instances_mut()
self.instance_mut_iter()
.filter_map(|element| element.instance.as_vector_data_mut())
.flat_map(move |vector_data| vector_data.instances_mut())
.flat_map(move |vector_data| vector_data.instance_mut_iter())
}
}
impl VectorDataTableIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
self.instances_mut()
self.instance_mut_iter()
}
}
@ -198,7 +201,7 @@ where
async fn repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: DVec2,
@ -221,13 +224,14 @@ where
for index in 0..instances {
let angle = index as f64 * angle / total;
let translation = index as f64 * direction / total;
let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@ -237,7 +241,7 @@ where
async fn circular_repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[default(5)] instances: IntegerCount,
@ -256,13 +260,14 @@ where
for index in 0..instances {
let rotation = DAffine2::from_angle((std::f64::consts::TAU / instances as f64) * index as f64 + angle_offset.to_radians());
let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@ -274,7 +279,7 @@ async fn copy_to_points<I: 'n + Send>(
points: VectorDataTable,
#[expose]
/// Artwork to be copied and placed at each point.
#[implementations(VectorDataTable, GraphicGroupTable)]
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)]
instance: Instances<I>,
/// Minimum range of randomized sizes given to each instance.
#[default(1)]
@ -301,7 +306,7 @@ where
Instances<I>: GraphicElementRendered,
{
let points_transform = points.transform();
let points_list = points.instances().flat_map(|element| element.instance.point_domain.positions());
let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions());
let random_scale_difference = random_scale_max - random_scale_min;
@ -316,7 +321,7 @@ where
let mut result_table = GraphicGroupTable::default();
for (index, &point) in points_list.into_iter().enumerate() {
for &point in points_list.into_iter() {
let center_transform = DAffine2::from_translation(instance_center);
let translation = points_transform.transform_point2(point);
@ -342,11 +347,14 @@ where
random_scale_min
};
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
let new_instance = result_table.push(new_graphic_element);
*new_instance.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
@ -355,7 +363,7 @@ where
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn mirror<I: 'n + Send>(
_: impl Ctx,
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(0., 0.)] center: DVec2,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
@ -382,20 +390,25 @@ where
);
// Apply reflection around the center point
let modification = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
let transform = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
// Add original instance depending on the keep_original flag
if keep_original {
result_table.push(instance.to_graphic_element());
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform: DAffine2::IDENTITY,
alpha_blending: Default::default(),
source_node_id: None,
});
}
// Create and add mirrored instance
let mut mirrored_element = instance.to_graphic_element();
mirrored_element.new_ids_from_hash(None);
// Apply the transformation to the mirrored instance
let mirrored_instance = result_table.push(mirrored_element);
*mirrored_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
result_table
}
@ -417,7 +430,7 @@ async fn round_corners(
) -> VectorDataTable {
let source_transform = source.transform();
let source_transform_inverse = source_transform.inverse();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let upstream_graphics_group = source.upstream_graphic_group.clone();
// Flip the roundness to help with user intuition
@ -517,7 +530,7 @@ async fn spatial_merge_by_distance(
distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let point_count = vector_data.point_domain.positions().len();
// Find min x and y for grid cell normalization
@ -638,10 +651,10 @@ async fn spatial_merge_by_distance(
#[node_macro::node(category("Debug"), path(graphene_core::vector))]
async fn box_warp(_: impl Ctx, vector_data: VectorDataTable, #[expose] rectangle: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance.clone();
let vector_data = vector_data.one_instance_ref().instance.clone();
let target_transform = rectangle.transform();
let target = rectangle.one_instance().instance;
let target = rectangle.one_instance_ref().instance;
// Get the bounding box of the source vector data
let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
@ -727,7 +740,7 @@ async fn remove_handles(
max_handle_distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
for (_, handles, start, end) in vector_data.segment_domain.handles_mut() {
// Only convert to linear if handles are within the threshold distance
@ -773,7 +786,7 @@ async fn generate_handles(
curvature: f64,
) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorData::empty();
result.style = source.style.clone();
@ -955,7 +968,7 @@ async fn generate_handles(
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut result = vector_data
.bounding_box()
@ -972,7 +985,7 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.bounding_box_with_transform(vector_data_transform)
.map(|[top_left, bottom_right]| bottom_right - top_left)
@ -982,7 +995,7 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@ -1018,7 +1031,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let stroke = vector_data.style.stroke().clone().unwrap_or_default();
let bezpaths = vector_data.stroke_bezpath_iter();
@ -1072,15 +1085,20 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
// a flatten vector elements connected to an if else node, another connection from the cache directly
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut<VectorData>) {
for current_element in graphic_group_table.instances() {
for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() {
match current_element.instance {
GraphicElement::VectorData(vector_data_table) => {
// Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance.
for vector_data_instance in vector_data_table.instances() {
for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() {
let other = vector_data_instance.instance;
let transform = *current_element.transform * *vector_data_instance.transform;
let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
output.instance.concat(other, transform, node_id);
let mut hasher = DefaultHasher::new();
(group_index, vector_index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.instance.concat(other, transform, collision_hash_seed);
// Use the last encountered style as the output style
output.instance.style = vector_data_instance.instance.style.clone();
@ -1088,7 +1106,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
GraphicElement::GraphicGroup(graphic_group) => {
let mut graphic_group = graphic_group.clone();
for instance in graphic_group.instances_mut() {
for instance in graphic_group.instance_mut_iter() {
*instance.transform = *current_element.transform * *instance.transform;
}
@ -1100,7 +1118,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
let mut output_table = VectorDataTable::default();
let Some(mut output) = output_table.instances_mut().next() else { return output_table };
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
flatten_group(&graphic_group_input, &mut output);
@ -1113,7 +1131,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
let spacing = spacing.max(0.01);
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
// Create an iterator over the bezier segments with enumeration and peeking capability.
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@ -1274,7 +1292,7 @@ async fn position_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@ -1306,7 +1324,7 @@ async fn tangent_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@ -1340,7 +1358,7 @@ async fn poisson_disk_points(
seed: SeedValue,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
@ -1391,7 +1409,7 @@ async fn poisson_disk_points(
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.segment_bezier_iter()
@ -1447,7 +1465,7 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
#[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().instance.clone();
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();
@ -1500,14 +1518,14 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> VectorDataTable {
let time = time.clamp(0., 1.);
let source_alpha_blending = source.one_instance().alpha_blending;
let target_alpha_blending = target.one_instance().alpha_blending;
let source_alpha_blending = source.one_instance_ref().alpha_blending;
let target_alpha_blending = target.one_instance_ref().alpha_blending;
let source_transform = source.transform();
let target_transform = target.transform();
let source = source.one_instance().instance;
let target = target.one_instance().instance;
let source = source.one_instance_ref().instance;
let target = target.one_instance_ref().instance;
let mut result = VectorDataTable::default();
@ -1699,7 +1717,7 @@ 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().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
*result.transform_mut() = source_transform;
@ -1709,7 +1727,7 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
#[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().instance.clone();
let mut source = source.one_instance_ref().instance.clone();
source.merge_by_distance(distance);
@ -1725,7 +1743,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut area = 0.;
let scale = vector_data_transform.decompose_scale();
@ -1742,7 +1760,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
if centroid_type == CentroidType::Area {
let mut area = 0.;
@ -1814,7 +1832,7 @@ mod test {
let instances = 3;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1826,7 +1844,7 @@ mod test {
let instances = 8;
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@ -1836,7 +1854,7 @@ mod test {
async fn circular_repeat() {
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
@ -1851,7 +1869,7 @@ mod test {
#[tokio::test]
async fn bounding_box() {
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
@ -1865,7 +1883,7 @@ mod test {
}
.eval(Footprint::default())
.await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let expected_bounding_box = [DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.)];
@ -1882,7 +1900,7 @@ mod test {
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flatten_vector_elements = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
let flattened_copy_to_points = flatten_vector_elements.instances().next().unwrap().instance;
let flattened_copy_to_points = flatten_vector_elements.instance_ref_iter().next().unwrap().instance;
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
@ -1898,7 +1916,7 @@ mod test {
async fn sample_points() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1908,7 +1926,7 @@ mod test {
async fn adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@ -1923,7 +1941,7 @@ mod test {
0,
)
.await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@ -1942,7 +1960,7 @@ mod test {
#[tokio::test]
async fn spline() {
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.instances().next().unwrap().instance;
let spline = spline.instance_ref_iter().next().unwrap().instance;
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
}
@ -1951,7 +1969,7 @@ mod test {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert_eq!(
&sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
@ -1974,7 +1992,7 @@ mod test {
async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8);
@ -1997,7 +2015,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel((), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -2021,7 +2039,7 @@ mod test {
*vector_data_table.one_instance_mut().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.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@ -2039,7 +2057,7 @@ mod test {
async fn bevel_too_high() {
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);
@ -2060,7 +2078,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);

View file

@ -15,7 +15,7 @@ use graphene_core::{Ctx, GraphicElement, Node};
#[node_macro::node(category("Debug"))]
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data.point_domain.positions().to_vec()
}
@ -96,8 +96,8 @@ where
return target;
}
let target_width = target.one_instance().instance.width;
let target_height = target.one_instance().instance.height;
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);
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
@ -122,7 +122,7 @@ where
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
assert!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target.one_instance().instance.data.len());
assert!(target_index(max_x, max_y) < target.one_instance_ref().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 {
@ -143,7 +143,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
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.));
image.one_instance().instance.clone()
image.one_instance_ref().instance.clone()
}
macro_rules! inline_blend_funcs {
@ -220,7 +220,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
let mut background_bounds = bbox.to_transform();
// If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
let bounds_empty = bounds.instances().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
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();
}

View file

@ -9,9 +9,9 @@ use std::cmp::{max, min};
#[node_macro::node(category("Raster"))]
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance().instance;
let image = image_frame.one_instance_ref().instance;
// Prepare the image data for processing
let image_data = bytemuck::cast_vec(image.data.clone());

View file

@ -62,7 +62,7 @@ impl Clone for ComputePass {
#[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().instance;
let image = image.one_instance_ref().instance;
log::debug!("Executing gpu node");
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
@ -113,7 +113,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
};
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().alpha_blending;
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
result
}
@ -133,7 +133,7 @@ where
GraphicElement: From<Image<T>>,
T::Static: Pixel,
{
let image = image.one_instance().instance;
let image = image.one_instance_ref().instance;
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
@ -278,10 +278,10 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let foreground_transform = foreground.transform();
let background_transform = background.transform();
let background_alpha_blending = background.one_instance().alpha_blending;
let background_alpha_blending = background.one_instance_ref().alpha_blending;
let foreground = foreground.one_instance().instance;
let background = background.one_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);

View file

@ -16,7 +16,7 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
let image = image.one_instance().instance;
let image = image.one_instance_ref().instance;
for pixel in image.data.iter() {
let r = pixel.r() * GRID;

View file

@ -29,9 +29,9 @@ impl From<std::io::Error> for Error {
#[node_macro::node(category("Debug: Raster"))]
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance().instance;
let image = image_frame.one_instance_ref().instance;
// Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone());
@ -325,7 +325,7 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
return image;
}
let image_instance = image.one_instance().instance;
let image_instance = image.one_instance_ref().instance;
if image_instance.width == 0 || image_instance.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT);
}
@ -355,7 +355,7 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
let mut result = ImageFrameTable::new(new_img);
*result.transform_mut() = new_texture_to_layer_space;
*result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
*result.one_instance_mut().alpha_blending = *image.one_instance_ref().alpha_blending;
result
}

View file

@ -14,11 +14,11 @@ use std::ops::Mul;
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
.instances()
.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.instances_mut() {
for sub_vector_data in vector_data.instance_mut_iter() {
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
}
@ -28,12 +28,12 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
// Apply the parent group's transform to each element of raster data
match &mut image {
graphene_core::RasterFrame::ImageFrame(image) => {
for instance in image.instances_mut() {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
graphene_core::RasterFrame::TextureFrame(image) => {
for instance in image.instances_mut() {
for instance in image.instance_mut_iter() {
*instance.transform = *element.transform * *instance.transform;
}
}
@ -50,7 +50,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instances_mut() {
for sub_element in graphic_group.instance_mut_iter() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
@ -72,7 +72,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
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().instance, transform_of_lower_into_space_of_upper);
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) };
@ -105,7 +105,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
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().instance, transform_of_lower_into_space_of_upper);
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) };
@ -136,7 +136,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
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().instance, transform_of_lower_into_space_of_upper);
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) };
@ -160,12 +160,12 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
// 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();
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().instance, transform_of_lower_into_space_of_upper);
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) };

View file

@ -187,7 +187,7 @@ where
..Default::default()
};
for instance in data.instances_mut() {
for instance in data.instance_mut_iter() {
*instance.transform = DAffine2::from_translation(-aabb.start) * *instance.transform;
}
data.render_svg(&mut render, &render_params);

View file

@ -17,6 +17,7 @@ use graphene_std::GraphicElement;
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::ImageTexture;
use graphene_std::wasm_application_io::*;
use node_registry_macros::{async_node, into_node};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
@ -24,109 +25,27 @@ use std::sync::Arc;
use wgpu_executor::{ShaderInputFrame, WgpuExecutor};
use wgpu_executor::{WgpuSurface, WindowHandle};
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
async_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {
(
ProtoNodeIdentifier::new(stringify!($path)),
|mut args| {
Box::pin(async move {
args.reverse();
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <$path>::new($(
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + Send>>>::new()
),*);
let params = vec![$(fn_type_fut!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<$input as StaticType>::Static);
node_io
},
)
};
}
macro_rules! into_node {
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|mut args| {
Box::pin(async move {
args.reverse();
let node = graphene_core::ops::IntoNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = graphene_core::ops::IntoNode::<$to>::new();
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static);
node_io
},
)
};
}
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
// (
// ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
// ),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
into_node!(from: f64, to: f64),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: f64,to: f64),
into_node!(from: u32,to: f64),
into_node!(from: u8,to: u32),
into_node!(from: ImageFrameTable<Color>,to: GraphicGroupTable),
into_node!(from: VectorDataTable,to: GraphicGroupTable),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi,to: &WgpuExecutor),
into_node!(from: VectorDataTable,to: GraphicElement),
into_node!(from: ImageFrameTable<Color>,to: GraphicElement),
into_node!(from: GraphicGroupTable,to: GraphicElement),
into_node!(from: VectorDataTable,to: GraphicGroupTable),
into_node!(from: ImageFrameTable<Color>,to: GraphicGroupTable),
into_node!(from: f64, to: f64),
into_node!(from: u32, to: f64),
into_node!(from: u8, to: u32),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: GraphicElement),
into_node!(from: ImageFrameTable<Color>, to: GraphicElement),
into_node!(from: GraphicGroupTable, to: GraphicElement),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
let any: DynAnyNode<Context, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
},
),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::RasterFrame]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::instances::Instances<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
@ -135,6 +54,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => bool]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u64]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]),
@ -149,40 +69,27 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_core::uuid::NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::Color]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrameTable<Color>),
concrete!(ImageFrameTable<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
),
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
// This is how we can generically define composition of two nodes.
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
NodeIOTypes::new(
generic!(T),
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
// Filters
// TODO: Move these filters to the new node macro and put them in `graphene_core::raster::adjustments`, then add them to the document upgrade script which moves many of the adjustment nodes from `graphene_core::raster` to `graphene_core::raster::adjustments`
(
@ -214,118 +121,77 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
},
NodeIOTypes::new(concrete!(ImageFrameTable<Color>), concrete!(ImageFrameTable<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
),
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ClonedNode::new(curve.eval(()).await);
// let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
// ),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32)));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![fn_type!(graphene_core::raster::curve::Curve)],
// ),
// ),
// (
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
// Box::pin(async move {
// use graphene_std::raster::ImaginateNode;
// macro_rules! instantiate_imaginate_node {
// ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) };
// }
// let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
// let any = graphene_std::any::DynAnyNode::new(node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![
// fn_type!(&WasmEditorApi),
// fn_type!(ImaginateController),
// fn_type!(f64),
// fn_type!(Option<DVec2>),
// fn_type!(u32),
// fn_type!(ImaginateSamplingMethod),
// fn_type!(f64),
// fn_type!(String),
// fn_type!(String),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(ImaginateMaskStartingFill),
// fn_type!(bool),
// fn_type!(bool),
// fn_type!(u64),
// ],
// ),
// ),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
// This is how we can generically define composition of two nodes.
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
NodeIOTypes::new(
generic!(T),
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrameTable<Color>),
concrete!(ImageFrameTable<Color>),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
let any: DynAnyNode<Context, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
},
),
];
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
for (constructor, types) in entry.iter() {
map.entry(id.clone().into()).or_default().insert(types.clone(), *constructor);
}
}
for (id, c, types) in node_types.into_iter() {
// TODO: this is a hack to remove the newline from the node new_name
// This occurs for the ChannelMixerNode presumably because of the long name.
@ -340,12 +206,67 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let nid = ProtoNodeIdentifier { name: Cow::Owned(new_name) };
map.entry(nid).or_default().insert(types.clone(), c);
}
map
}
pub static NODE_REGISTRY: Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = Lazy::new(|| node_registry());
#[cfg(test)]
mod protograph_testing {
// TODO: add tests testing the node registry
mod node_registry_macros {
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
async_node!($path, input: $input, fn_params: [ $(() => $type),*])
};
($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {
(
ProtoNodeIdentifier::new(stringify!($path)),
|mut args| {
Box::pin(async move {
args.reverse();
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <$path>::new($(
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + Send>>>::new()
),*);
let params = vec![$(fn_type_fut!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<$input as StaticType>::Static);
node_io
},
)
};
}
macro_rules! into_node {
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|mut args| {
Box::pin(async move {
args.reverse();
let node = graphene_core::ops::IntoNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = graphene_core::ops::IntoNode::<$to>::new();
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static);
node_io
},
)
};
}
pub(crate) use async_node;
pub(crate) use into_node;
}

View file

@ -0,0 +1,80 @@
//! This has all been copied out of node_registry.rs to avoid leaving many lines of commented out code in that file. It's left here instead for future reference.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ClonedNode::new(curve.eval(()).await);
// let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Luma>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
// ),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::curve::Curve;
// use graphene_core::raster::GenerateCurvesNode;
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32)));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![fn_type!(graphene_core::raster::curve::Curve)],
// ),
// ),
// (
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
// Box::pin(async move {
// use graphene_std::raster::ImaginateNode;
// macro_rules! instantiate_imaginate_node {
// ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) };
// }
// let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
// let any = graphene_std::any::DynAnyNode::new(node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![
// fn_type!(&WasmEditorApi),
// fn_type!(ImaginateController),
// fn_type!(f64),
// fn_type!(Option<DVec2>),
// fn_type!(u32),
// fn_type!(ImaginateSamplingMethod),
// fn_type!(f64),
// fn_type!(String),
// fn_type!(String),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(bool),
// fn_type!(f64),
// fn_type!(ImaginateMaskStartingFill),
// fn_type!(bool),
// fn_type!(bool),
// fn_type!(u64),
// ],
// ),
// ),

View file

@ -913,7 +913,7 @@ async fn render_texture<'a: 'n>(
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> ImageTexture {
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
let input = input.one_instance().instance;
let input = input.one_instance_ref().instance;
let new_data: Vec<SRGBA8> = input.data.iter().map(|x| (*x).into()).collect();
let new_image = Image {
width: input.width,