diff --git a/Cargo.lock b/Cargo.lock index 96f882e2c7..37595cd781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1595,6 +1595,9 @@ dependencies = [ "kurbo", "log", "node-macro", + "num", + "num-derive", + "num-traits", "once_cell", "rand_chacha 0.3.1", "serde", @@ -2657,6 +2660,17 @@ dependencies = [ "serde", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 7b5fdac5c7..37099a479a 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -12,6 +12,7 @@ use document_legacy::LayerId; use document_legacy::Operation as DocumentOperation; use graph_craft::document::NodeId; use graphene_core::raster::Image; +use graphene_core::Color; use serde::{Deserialize, Serialize}; #[remain::sorted] @@ -122,7 +123,7 @@ pub enum DocumentMessage { resize_opposite_corner: Key, }, PasteImage { - image: Image, + image: Image, mouse: Option<(f64, f64)>, }, Redo, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 883f6878e7..1fb16aec30 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -34,7 +34,7 @@ use document_legacy::layers::text_layer::Font; use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; use graph_craft::document::NodeId; use graph_craft::{concrete, Type, TypeDescriptor}; -use graphene_core::raster::{Color, ImageFrame}; +use graphene_core::raster::{Color, ImageFrame, RasterMut}; use graphene_core::Cow; use glam::{DAffine2, DVec2}; @@ -1057,7 +1057,7 @@ impl DocumentMessageHandler { let primary_input_type = node_network.input_types().next().clone(); let response = match primary_input_type { // Only calclate the frame if the primary input is an image - Some(ty) if ty == concrete!(ImageFrame) => { + Some(ty) if ty == concrete!(ImageFrame) => { // Calculate the size of the region to be exported let old_transforms = self.remove_document_transform(); let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap(); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index a52c403887..b3c5dafe5e 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -113,8 +113,8 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Downres".to_string(), - inputs: vec![NodeInput::Network(concrete!(ImageFrame))], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::DownresNode")), + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::DownresNode<_>")), metadata: Default::default(), }, DocumentNode { @@ -160,7 +160,7 @@ fn static_nodes() -> Vec { outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)], nodes: [DocumentNode { name: "Identity".to_string(), - inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), metadata: Default::default(), }] @@ -174,7 +174,7 @@ fn static_nodes() -> Vec { DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, - default: NodeInput::Network(concrete!(ImageFrame)), + default: NodeInput::Network(concrete!(ImageFrame)), }, DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), ], @@ -193,7 +193,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "SetNode".to_string(), - inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")), metadata: Default::default(), }, @@ -277,7 +277,7 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Image Frame", category: "General", - identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_>"), + identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_, _>"), inputs: vec![ DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), @@ -288,7 +288,7 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Mask", category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_std::raster::MaskImageNode<_>"), + identifier: NodeImplementation::proto("graphene_std::raster::MaskImageNode<_, _, _>"), inputs: vec![ DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Stencil", TaggedValue::ImageFrame(ImageFrame::empty()), true), @@ -419,7 +419,7 @@ fn static_nodes() -> Vec { 0, DocumentNode { name: "CacheNode".to_string(), - inputs: vec![NodeInput::Network(concrete!(Image))], + inputs: vec![NodeInput::Network(concrete!(Image))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")), metadata: Default::default(), }, @@ -489,7 +489,7 @@ fn static_nodes() -> Vec { 0, DocumentNode { name: "CacheNode".to_string(), - inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(concrete!(ImageFrame))], + inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(concrete!(ImageFrame))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")), metadata: Default::default(), }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 625d44eb1d..d71ad76879 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -954,7 +954,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte transform: glam::DAffine2::IDENTITY, }); // Compute the transform input to the node graph frame - let image_frame: graphene_core::raster::ImageFrame = context.executor.compute_input(context.network, &imaginate_node, 0, image_frame).unwrap_or_default(); + let image_frame: graphene_core::raster::ImageFrame = context.executor.compute_input(context.network, &imaginate_node, 0, image_frame).unwrap_or_default(); let transform = image_frame.transform; let resolution = { diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index ab973b65fe..79bfa284c8 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -205,7 +205,7 @@ impl MessageHandler> 16) as u8; let g = (random_number >> 8) as u8; let b = random_number as u8; - let random_color = Color::from_rgba8(r, g, b, 255); + let random_color = Color::from_rgba8_srgb(r, g, b, 255); document_data.primary_color = random_color; document_data.update_working_colors(responses); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index a704958f62..acc7c5a192 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -12,6 +12,7 @@ use graph_craft::executor::Compiler; use graph_craft::{concrete, Type, TypeDescriptor}; use graphene_core::raster::{Image, ImageFrame}; use graphene_core::vector::VectorData; +use graphene_core::Color; use interpreted_executor::executor::DynamicExecutor; use glam::{DAffine2, DVec2}; @@ -24,7 +25,7 @@ pub struct NodeGraphExecutor { impl NodeGraphExecutor { /// Execute the network by flattening it and creating a borrow stack. - fn execute_network<'a>(&'a mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result, String> { + fn execute_network<'a>(&'a mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result, String> { let mut scoped_network = wrap_network_in_scope(network); scoped_network.duplicate_outputs(&mut generate_uuid); @@ -44,14 +45,14 @@ impl NodeGraphExecutor { use graph_craft::executor::Executor; match self.executor.input_type() { - Some(t) if t == concrete!(ImageFrame) => self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string()), + Some(t) if t == concrete!(ImageFrame) => self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string()), Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).map_err(|e| e.to_string()), _ => Err("Invalid input type".to_string()), } } /// Computes an input for a node in the graph - pub fn compute_input(&mut self, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image_frame: Cow) -> Result { + pub fn compute_input(&mut self, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image_frame: Cow>) -> Result { let mut network = old_network.clone(); // Adjust the output of the graph so we find the relevant output 'outer: for end in (0..node_path.len()).rev() { @@ -93,7 +94,7 @@ impl NodeGraphExecutor { } /// Encodes an image into a format using the image crate - fn encode_img(image: Image, resize: Option, format: image::ImageOutputFormat) -> Result<(Vec, (u32, u32)), String> { + fn encode_img(image: Image, resize: Option, format: image::ImageOutputFormat) -> Result<(Vec, (u32, u32)), String> { use image::{ImageBuffer, Rgba}; use std::io::Cursor; @@ -118,7 +119,7 @@ impl NodeGraphExecutor { imaginate_node: Vec, (document, document_id): (&mut DocumentMessageHandler, u64), layer_path: Vec, - image_frame: ImageFrame, + image_frame: ImageFrame, (preferences, persistent_data): (&PreferencesMessageHandler, &PersistentData), ) -> Result { use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE; @@ -153,8 +154,8 @@ impl NodeGraphExecutor { }; let use_base_image = self.compute_input::(&network, &imaginate_node, get("Adapt Input Image"), Cow::Borrowed(&image_frame))?; - let input_image_frame: Option = if use_base_image { - Some(self.compute_input::(&network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image_frame))?) + let input_image_frame: Option> = if use_base_image { + Some(self.compute_input::>(&network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image_frame))?) } else { None }; diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index c6c87069bd..86f9a613f8 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -13,7 +13,7 @@ std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta"] default = ["async", "serde", "kurbo", "log", "std"] log = ["dep:log"] serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "base64"] -gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any", "glam/libm"] +gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"] async = ["async-trait", "alloc"] nightly = [] alloc = ["dyn-any", "bezier-rs", "once_cell"] @@ -26,7 +26,7 @@ dyn-any = { path = "../../libraries/dyn-any", features = [ ], optional = true, default-features = false } spirv-std = { version = "0.5", features = ["glam"], optional = true } -bytemuck = { version = "1.8", features = ["derive"], optional = true } +bytemuck = { version = "1.8", features = ["derive"] } async-trait = { version = "0.1", optional = true } serde = { version = "1.0", features = [ "derive", @@ -47,3 +47,6 @@ base64 = { version = "0.13", optional = true } specta.workspace = true specta.optional = true once_cell = { version = "1.17.0", default-features = false, optional = true } +num = "0.4.0" +num-derive = "0.3.3" +num-traits = "0.2.15" diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 5b5c717700..6caac42080 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -2,6 +2,9 @@ use core::{fmt::Debug, marker::PhantomData}; use crate::Node; +use bytemuck::{Pod, Zeroable}; +use glam::DVec2; +use num::Num; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::float::Float; @@ -12,6 +15,183 @@ pub mod brightness_contrast; pub mod color; pub use adjustments::*; +pub trait Channel: Copy + Debug + num::Num + num::NumCast { + fn to_linear(self) -> Out; + fn from_linear(linear: In) -> Self; + fn to_f32(self) -> f32 { + num::cast(self).expect("Failed to convert channel to f32") + } + fn from_f32(value: f32) -> Self { + num::cast(value).expect("Failed to convert f32 to channel") + } + fn to_f64(self) -> f64 { + num::cast(self).expect("Failed to convert channel to f64") + } + fn from_f64(value: f64) -> Self { + num::cast(value).expect("Failed to convert f64 to channel") + } + fn to_channel(self) -> Out { + num::cast(self).expect("Failed to convert channel to channel") + } +} + +pub trait Linear: num::NumCast + Num {} +impl Linear for f32 {} +impl Linear for f64 {} + +impl Channel for T { + #[inline(always)] + fn to_linear(self) -> Out { + num::cast(self).expect("Failed to convert channel to linear") + } + + #[inline(always)] + fn from_linear(linear: In) -> Self { + num::cast(linear).expect("Failed to convert linear to channel") + } +} + +use num_derive::*; +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Num, NumCast, NumOps, One, Zero, ToPrimitive, FromPrimitive)] +struct SRGBGammaFloat(f32); + +impl Channel for SRGBGammaFloat { + #[inline(always)] + fn to_linear(self) -> Out { + let channel = num::cast::<_, f32>(self).expect("Failed to convert srgb to linear"); + let out = if channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) }; + num::cast(out).expect("Failed to convert srgb to linear") + } + + #[inline(always)] + fn from_linear(linear: In) -> Self { + let linear = num::cast::<_, f32>(linear).expect("Failed to convert linear to srgb"); + let out = if linear <= 0.0031308 { linear * 12.92 } else { 1.055 * linear.powf(1. / 2.4) - 0.055 }; + num::cast(out).expect("Failed to convert linear to srgb") + } +} +pub trait RGBPrimaries { + const RED: DVec2; + const GREEN: DVec2; + const BLUE: DVec2; + const WHITE: DVec2; +} +pub trait Rec709Primaries {} +impl RGBPrimaries for T { + const RED: DVec2 = DVec2::new(0.64, 0.33); + const GREEN: DVec2 = DVec2::new(0.3, 0.6); + const BLUE: DVec2 = DVec2::new(0.15, 0.06); + const WHITE: DVec2 = DVec2::new(0.3127, 0.329); +} + +pub trait SRGB: Rec709Primaries {} + +#[cfg(feature = "serde")] +pub trait Serde: serde::Serialize + for<'a> serde::Deserialize<'a> {} +#[cfg(not(feature = "serde"))] +pub trait Serde {} + +#[cfg(feature = "serde")] +impl serde::Deserialize<'a>> Serde for T {} +#[cfg(not(feature = "serde"))] +impl Serde for T {} + +// TODO: Come up with a better name for this trait +pub trait Pixel: Clone + Pod + Zeroable { + fn to_bytes(&self) -> Vec { + bytemuck::bytes_of(self).to_vec() + } + // TODO: use u8 for Color + fn from_bytes(bytes: &[u8]) -> &Self { + bytemuck::try_from_bytes(bytes).expect("Failed to convert bytes to pixel") + } +} +impl Pixel for T {} + +pub trait RGB: Pixel { + type ColorChannel: Channel; + fn red(&self) -> Self::ColorChannel; + fn r(&self) -> Self::ColorChannel { + self.red() + } + fn green(&self) -> Self::ColorChannel; + fn g(&self) -> Self::ColorChannel { + self.green() + } + fn blue(&self) -> Self::ColorChannel; + fn b(&self) -> Self::ColorChannel { + self.blue() + } +} + +pub trait AssociatedAlpha: RGB + Alpha { + fn to_unassociated(&self) -> Out; +} + +pub trait UnassociatedAlpha: RGB + Alpha { + fn to_associated(&self) -> Out; +} + +pub trait Alpha { + type AlphaChannel: Channel; + fn alpha(&self) -> Self::AlphaChannel; + fn a(&self) -> Self::AlphaChannel { + self.alpha() + } + fn multiply_alpha(&self, alpha: Self::AlphaChannel) -> Self; +} + +pub trait Depth { + type DepthChannel: Channel; + fn depth(&self) -> Self::DepthChannel; + fn d(&self) -> Self::DepthChannel { + self.depth() + } +} + +pub trait ExtraChannels { + type ChannelType: Channel; + fn extra_channels(&self) -> [Self::ChannelType; NUM]; +} + +pub trait Luminance { + type LuminanceChannel: Channel; + fn luminance(&self) -> Self::LuminanceChannel; + fn l(&self) -> Self::LuminanceChannel { + self.luminance() + } +} + +// TODO: We might rename this to Raster at some point +pub trait Sample { + type Pixel: Pixel; + // TODO: Add an area parameter + fn sample(&self, pos: DVec2) -> Option; +} + +// TODO: We might rename this to Bitmap at some point +pub trait Raster { + type Pixel: Pixel; + fn width(&self) -> u32; + fn height(&self) -> u32; + fn get_pixel(&self, x: u32, y: u32) -> Option; +} + +pub trait RasterMut: Raster { + fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel>; + fn set_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { + *self.get_pixel_mut(x, y).unwrap() = pixel; + } + fn map_pixels Self::Pixel>(&mut self, map_fn: F) { + for y in 0..self.height() { + for x in 0..self.width() { + let pixel = self.get_pixel(x, y).unwrap(); + self.set_pixel(x, y, map_fn(pixel)); + } + } + } +} + #[derive(Debug, Default)] pub struct MapNode { map_fn: MapFn, @@ -113,25 +293,28 @@ fn distance_node(input: (i32, i32)) -> f32 { } #[derive(Debug, Clone, Copy)] -pub struct ImageIndexIterNode; +pub struct ImageIndexIterNode

{ + _p: core::marker::PhantomData

, +} -#[node_macro::node_fn(ImageIndexIterNode)] -fn image_index_iter_node(input: ImageSlice<'input>) -> core::ops::Range { +#[node_macro::node_fn(ImageIndexIterNode<_P>)] +fn image_index_iter_node<_P>(input: ImageSlice<'input, _P>) -> core::ops::Range { 0..(input.width * input.height) } #[derive(Debug)] -pub struct WindowNode Node<'i, (), Output = u32>, Image: for<'i> Node<'i, (), Output = ImageSlice<'i>>> { +pub struct WindowNode Node<'i, (), Output = u32>, Image: for<'i> Node<'i, (), Output = ImageSlice<'i, P>>> { radius: Radius, image: Image, + _pixel: core::marker::PhantomData

, } -impl<'input, S0: 'input, S1: 'input> Node<'input, u32> for WindowNode +impl<'input, P: 'input, S0: 'input, S1: 'input> Node<'input, u32> for WindowNode where S0: for<'any_input> Node<'any_input, (), Output = u32>, - S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input>>, + S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>, { - type Output = ImageWindowIterator<'input>; + type Output = ImageWindowIterator<'input, P>; #[inline] fn eval<'node: 'input>(&'node self, input: u32) -> Self::Output { let radius = self.radius.eval(()); @@ -142,13 +325,17 @@ where } } } -impl WindowNode +impl WindowNode where S0: for<'any_input> Node<'any_input, (), Output = u32>, - S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input>>, + S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>, { pub const fn new(radius: S0, image: S1) -> Self { - Self { radius, image } + Self { + radius, + image, + _pixel: core::marker::PhantomData, + } } } /* @@ -159,16 +346,16 @@ fn window_node(input: u32, radius: u32, image: ImageSlice<'input>) -> ImageWindo }*/ #[derive(Debug, Clone, Copy)] -pub struct ImageWindowIterator<'a> { - image: ImageSlice<'a>, +pub struct ImageWindowIterator<'a, P> { + image: ImageSlice<'a, P>, radius: u32, index: u32, x: u32, y: u32, } -impl<'a> ImageWindowIterator<'a> { - fn new(image: ImageSlice<'a>, radius: u32, index: u32) -> Self { +impl<'a, P> ImageWindowIterator<'a, P> { + fn new(image: ImageSlice<'a, P>, radius: u32, index: u32) -> Self { let start_x = index as i32 % image.width as i32; let start_y = index as i32 / image.width as i32; let min_x = (start_x - radius as i32).max(0) as u32; @@ -185,8 +372,8 @@ impl<'a> ImageWindowIterator<'a> { } #[cfg(not(target_arch = "spirv"))] -impl<'a> Iterator for ImageWindowIterator<'a> { - type Item = (Color, (i32, i32)); +impl<'a, P: Copy> Iterator for ImageWindowIterator<'a, P> { + type Item = (P, (i32, i32)); #[inline] fn next(&mut self) -> Option { let start_x = self.index as i32 % self.image.width as i32; @@ -255,20 +442,24 @@ where #[cfg(target_arch = "spirv")] const NOTHING: () = (); -use dyn_any::{DynAny, StaticType}; -#[derive(Clone, Debug, PartialEq, DynAny, Copy)] +use dyn_any::{DynAny, StaticType, StaticTypeSized}; +#[derive(Clone, Debug, PartialEq, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub struct ImageSlice<'a> { +pub struct ImageSlice<'a, Pixel> { pub width: u32, pub height: u32, #[cfg(not(target_arch = "spirv"))] - pub data: &'a [Color], + pub data: &'a [Pixel], #[cfg(target_arch = "spirv")] pub data: &'a (), } +impl StaticType for ImageSlice<'_, P> { + type Static = ImageSlice<'static, P::Static>; +} + #[allow(clippy::derivable_impls)] -impl<'a> Default for ImageSlice<'a> { +impl<'a, P> Default for ImageSlice<'a, P> { #[cfg(not(target_arch = "spirv"))] fn default() -> Self { Self { @@ -287,7 +478,25 @@ impl<'a> Default for ImageSlice<'a> { } } -impl ImageSlice<'_> { +impl Raster for ImageSlice<'_, P> { + type Pixel = P; + #[cfg(not(target_arch = "spirv"))] + fn get_pixel(&self, x: u32, y: u32) -> Option

{ + self.data.get((x + y * self.width) as usize).copied() + } + #[cfg(target_arch = "spirv")] + fn get_pixel(&self, _x: u32, _y: u32) -> P { + Color::default() + } + fn width(&self) -> u32 { + self.width + } + fn height(&self) -> u32 { + self.height + } +} + +impl

ImageSlice<'_, P> { #[cfg(not(target_arch = "spirv"))] pub const fn empty() -> Self { Self { width: 0, height: 0, data: &[] } @@ -295,28 +504,30 @@ impl ImageSlice<'_> { } #[cfg(not(target_arch = "spirv"))] -impl<'a> IntoIterator for ImageSlice<'a> { - type Item = &'a Color; - type IntoIter = core::slice::Iter<'a, Color>; +impl<'a, P: 'a> IntoIterator for ImageSlice<'a, P> { + type Item = &'a P; + type IntoIter = core::slice::Iter<'a, P>; fn into_iter(self) -> Self::IntoIter { self.data.iter() } } #[cfg(not(target_arch = "spirv"))] -impl<'a> IntoIterator for &'a ImageSlice<'a> { - type Item = &'a Color; - type IntoIter = core::slice::Iter<'a, Color>; +impl<'a, P: 'a> IntoIterator for &'a ImageSlice<'a, P> { + type Item = &'a P; + type IntoIter = core::slice::Iter<'a, P>; fn into_iter(self) -> Self::IntoIter { self.data.iter() } } #[derive(Debug)] -pub struct ImageDimensionsNode; +pub struct ImageDimensionsNode

{ + _p: PhantomData

, +} -#[node_macro::node_fn(ImageDimensionsNode)] -fn dimensions_node(input: ImageSlice<'input>) -> (u32, u32) { +#[node_macro::node_fn(ImageDimensionsNode<_P>)] +fn dimensions_node<_P>(input: ImageSlice<'input, _P>) -> (u32, u32) { (input.width, input.height) } @@ -335,27 +546,24 @@ mod image { mod base64_serde { //! Basic wrapper for [`serde`] for [`base64`] encoding - use crate::Color; + use super::super::Pixel; use serde::{Deserialize, Deserializer, Serializer}; - pub fn as_base64(key: &[Color], serializer: S) -> Result + pub fn as_base64(key: &Vec

, serializer: S) -> Result where S: Serializer, { - let u8_data = key - .iter() - .flat_map(|color| [color.r(), color.g(), color.b(), color.a()].into_iter().map(|channel| (channel * 255.).clamp(0., 255.) as u8)) - .collect::>(); + let u8_data = key.iter().flat_map(|color| color.to_bytes()).collect::>(); serializer.serialize_str(&base64::encode(u8_data)) } - pub fn from_base64<'a, D>(deserializer: D) -> Result, D::Error> + pub fn from_base64<'a, D, P: Pixel>(deserializer: D) -> Result, D::Error> where D: Deserializer<'a>, { use serde::de::Error; - let color_from_chunk = |chunk: &[u8]| Color::from_rgba8(chunk[0], chunk[1], chunk[2], chunk[3]); + let color_from_chunk = |chunk: &[u8]| P::from_bytes(chunk.try_into().unwrap()).clone(); let colors_from_bytes = |bytes: Vec| bytes.chunks_exact(4).map(color_from_chunk).collect(); @@ -366,16 +574,42 @@ mod image { } } - #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type)] + #[derive(Clone, Debug, PartialEq, Default, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - pub struct Image { + pub struct Image { pub width: u32, pub height: u32, #[cfg_attr(feature = "serde", serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64"))] - pub data: Vec, + pub data: Vec

, } - impl Hash for Image { + impl StaticType for Image

+ where + P::Static: Pixel, + { + type Static = Image; + } + + impl Raster for Image

{ + type Pixel = P; + fn get_pixel(&self, x: u32, y: u32) -> Option

{ + self.data.get((x + y * self.width) as usize).copied() + } + fn width(&self) -> u32 { + self.width + } + fn height(&self) -> u32 { + self.height + } + } + + impl RasterMut for Image

{ + fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut P> { + self.data.get_mut((x + y * self.width) as usize) + } + } + + impl Hash for Image

{ fn hash(&self, state: &mut H) { const HASH_SAMPLES: u64 = 1000; let data_length = self.data.len() as u64; @@ -387,7 +621,7 @@ mod image { } } - impl Image { + impl Image

{ pub const fn empty() -> Self { Self { width: 0, @@ -396,7 +630,7 @@ mod image { } } - pub fn new(width: u32, height: u32, color: Color) -> Self { + pub fn new(width: u32, height: u32, color: P) -> Self { Self { width, height, @@ -404,51 +638,66 @@ mod image { } } - pub fn as_slice(&self) -> ImageSlice { + pub fn as_slice(&self) -> ImageSlice

{ ImageSlice { width: self.width, height: self.height, data: self.data.as_slice(), } } + } - pub fn get_mut(&mut self, x: u32, y: u32) -> Option<&mut Color> { - self.data.get_mut((y * self.width + x) as usize) - } - - pub fn get(&self, x: u32, y: u32) -> Option<&Color> { - self.data.get((y * self.width + x) as usize) - } - + impl Image { /// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array) pub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self { - let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect(); + let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8_srgb(v[0], v[1], v[2], v[3])).collect(); Image { width, height, data } } + } + use super::*; + impl Image

+ where + P::ColorChannel: Linear, + { /// Flattens each channel cast to a u8 pub fn into_flat_u8(self) -> (Vec, u32, u32) { let Image { width, height, data } = self; - let result_bytes = data.into_iter().flat_map(|color| color.to_rgba8()).collect(); + let to_gamma = |x| SRGBGammaFloat::from_linear(x); + let to_u8 = |x| (num::cast::<_, f32>(x).unwrap() * 255.) as u8; + + let result_bytes = data + .into_iter() + .flat_map(|color| { + [ + to_u8(to_gamma(color.r())), + to_u8(to_gamma(color.g())), + to_u8(to_gamma(color.b())), + (num::cast::<_, f32>(color.a()).unwrap() * 255.) as u8, + ] + }) + .collect(); (result_bytes, width, height) } } - impl IntoIterator for Image { - type Item = Color; - type IntoIter = alloc::vec::IntoIter; + impl IntoIterator for Image

{ + type Item = P; + type IntoIter = alloc::vec::IntoIter

; fn into_iter(self) -> Self::IntoIter { self.data.into_iter() } } #[derive(Debug, Clone, Copy, Default)] - pub struct ImageRefNode; + pub struct ImageRefNode

{ + _p: PhantomData

, + } - #[node_macro::node_fn(ImageRefNode)] - fn image_ref_node(image: &'input Image) -> ImageSlice<'input> { + #[node_macro::node_fn(ImageRefNode<_P>)] + fn image_ref_node<_P: Pixel>(image: &'input Image<_P>) -> ImageSlice<'input, _P> { image.as_slice() } @@ -469,7 +718,7 @@ mod image { } #[node_macro::node_fn(MapImageSliceNode)] - fn map_node(input: (u32, u32), data: Vec) -> Image { + fn map_node(input: (u32, u32), data: Vec

) -> Image

{ Image { width: input.0, height: input.1, @@ -477,14 +726,56 @@ mod image { } } - #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type)] + #[derive(Clone, Debug, PartialEq, Default, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - pub struct ImageFrame { - pub image: Image, + pub struct ImageFrame { + pub image: Image

, pub transform: DAffine2, } - impl ImageFrame { + impl Sample for ImageFrame

{ + type Pixel = P; + + fn sample(&self, pos: DVec2) -> Option { + let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64); + let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos); + if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y { + return None; + } + self.image.get_pixel(pos.x as u32, pos.y as u32) + } + } + + impl Raster for ImageFrame

{ + type Pixel = P; + + fn width(&self) -> u32 { + self.image.width() + } + + fn height(&self) -> u32 { + self.image.height() + } + + fn get_pixel(&self, x: u32, y: u32) -> Option { + self.image.get_pixel(x, y) + } + } + + impl RasterMut for ImageFrame

{ + fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { + self.image.get_pixel_mut(x, y) + } + } + + impl StaticType for ImageFrame

+ where + P::Static: Pixel, + { + type Static = ImageFrame; + } + + impl ImageFrame

{ pub const fn empty() -> Self { Self { image: Image::empty(), @@ -492,12 +783,12 @@ mod image { } } - pub fn get_mut(&mut self, x: usize, y: usize) -> &mut Color { + pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P { &mut self.image.data[y * (self.image.width as usize) + x] } /// Clamps the provided point to ((0, 0), (ImageSize.x, ImageSize.y)) and returns the closest pixel - pub fn sample(&self, position: DVec2) -> Color { + pub fn sample(&self, position: DVec2) -> P { let x = position.x.clamp(0., self.image.width as f64 - 1.) as usize; let y = position.y.clamp(0., self.image.height as f64 - 1.) as usize; @@ -505,13 +796,13 @@ mod image { } } - impl AsRef for ImageFrame { - fn as_ref(&self) -> &ImageFrame { + impl AsRef> for ImageFrame

{ + fn as_ref(&self) -> &ImageFrame

{ self } } - impl Hash for ImageFrame { + impl Hash for ImageFrame

{ fn hash(&self, state: &mut H) { self.image.hash(state); self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index a649d0f589..f9f8ebc030 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -10,18 +10,18 @@ use spirv_std::num_traits::float::Float; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Euclid; -#[cfg(feature = "gpu")] use bytemuck::{Pod, Zeroable}; +use super::{Alpha, AssociatedAlpha, Luminance, Rec709Primaries, RGB, SRGB}; + /// Structure that represents a color. /// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque). /// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`, /// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color. #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "gpu", derive(Pod, Zeroable))] #[cfg_attr(feature = "std", derive(specta::Type))] -#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)] +#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable)] pub struct Color { red: f32, green: f32, @@ -39,6 +39,50 @@ impl Hash for Color { } } +impl RGB for Color { + type ColorChannel = f32; + fn red(&self) -> f32 { + self.red + } + fn green(&self) -> f32 { + self.green + } + fn blue(&self) -> f32 { + self.blue + } +} + +impl Alpha for Color { + type AlphaChannel = f32; + fn alpha(&self) -> f32 { + self.alpha + } + fn multiply_alpha(&self, alpha: Self::AlphaChannel) -> Self { + Self { + red: self.red * alpha, + green: self.green * alpha, + blue: self.blue * alpha, + alpha: self.alpha * alpha, + } + } +} + +impl AssociatedAlpha for Color { + fn to_unassociated(&self) -> Out { + todo!() + } +} + +impl Luminance for Color { + type LuminanceChannel = f32; + fn luminance(&self) -> f32 { + 0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue + } +} + +impl Rec709Primaries for Color {} +impl SRGB for Color {} + impl Color { pub const BLACK: Color = Color::from_rgbf32_unchecked(0., 0., 0.); pub const WHITE: Color = Color::from_rgbf32_unchecked(1., 1., 1.); @@ -97,8 +141,8 @@ impl Color { /// let color2 = Color::from_rgba8(0x72, 0x67, 0x62, 0xFF); /// assert!(color == color2) /// ``` - pub fn from_rgb8(red: u8, green: u8, blue: u8) -> Color { - Color::from_rgba8(red, green, blue, 255) + pub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color { + Color::from_rgba8_srgb(red, green, blue, 255) } /// Return an SDR `Color` given RGBA channels from `0` to `255`. @@ -108,7 +152,7 @@ impl Color { /// use graphene_core::raster::color::Color; /// let color = Color::from_rgba8(0x72, 0x67, 0x62, 0x61); /// ``` - pub fn from_rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Color { + pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color { let map_range = |int_color| int_color as f32 / 255.0; Color { red: map_range(red), @@ -116,6 +160,7 @@ impl Color { blue: map_range(blue), alpha: map_range(alpha), } + .to_linear_srgb() } /// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1) @@ -527,7 +572,7 @@ impl Color { let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; let a = u8::from_str_radix(&color_str[6..8], 16).ok()?; - Some(Color::from_rgba8(r, g, b, a)) + Some(Color::from_rgba8_srgb(r, g, b, a)) } /// Creates a color from a 6-character RGB hex string (without a # prefix). @@ -544,7 +589,7 @@ impl Color { let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; - Some(Color::from_rgb8(r, g, b)) + Some(Color::from_rgb8_srgb(r, g, b)) } /// Linearly interpolates between two colors based on t. @@ -676,7 +721,7 @@ fn hsl_roundtrip() { (82, 84, 84), (255, 255, 178), ] { - let col = Color::from_rgb8(red, green, blue); + let col = Color::from_rgb8_srgb(red, green, blue); let [hue, saturation, lightness, alpha] = col.to_hsla(); let result = Color::from_hsla(hue, saturation, lightness, alpha); assert!((col.r() - result.r()) < f32::EPSILON * 100.); diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 5ca5bdcb61..8d58cd0881 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -3,6 +3,7 @@ use glam::DAffine2; use glam::DVec2; use crate::raster::ImageFrame; +use crate::raster::Pixel; use crate::vector::VectorData; use crate::Node; @@ -11,6 +12,12 @@ pub trait Transform { fn local_pivot(&self, pivot: DVec2) -> DVec2 { pivot } + fn decompose_scale(&self) -> DVec2 { + DVec2::new( + self.transform().transform_vector2((1., 0.).into()).length(), + self.transform().transform_vector2((0., 1.).into()).length(), + ) + } } pub trait TransformMut: Transform { @@ -20,17 +27,17 @@ pub trait TransformMut: Transform { } } -impl Transform for ImageFrame { +impl Transform for ImageFrame

{ fn transform(&self) -> DAffine2 { self.transform } } -impl Transform for &ImageFrame { +impl Transform for &ImageFrame

{ fn transform(&self) -> DAffine2 { self.transform } } -impl TransformMut for ImageFrame { +impl TransformMut for ImageFrame

{ fn transform_mut(&mut self) -> &mut DAffine2 { &mut self.transform } diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gcore/src/vector/style.rs index 4d1c67986f..0183e38600 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gcore/src/vector/style.rs @@ -396,7 +396,7 @@ impl Default for Stroke { fn default() -> Self { Self { weight: 0., - color: Some(Color::from_rgba8(0, 0, 0, 255)), + color: Some(Color::from_rgba8_srgb(0, 0, 0, 255)), dash_lengths: Vec::new(), dash_offset: 0., line_cap: LineCap::Butt, diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 62d6543bc2..74b1848cd6 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -3,7 +3,7 @@ use dyn_any::{DynAny, Upcast}; use dyn_clone::DynClone; pub use glam::{DAffine2, DVec2}; use graphene_core::raster::{BlendMode, LuminanceCalculation}; -use graphene_core::{Node, Type}; +use graphene_core::{Color, Node, Type}; use std::hash::Hash; pub use std::sync::Arc; @@ -23,9 +23,9 @@ pub enum TaggedValue { DVec2(DVec2), OptionalDVec2(Option), DAffine2(DAffine2), - Image(graphene_core::raster::Image), - RcImage(Option>), - ImageFrame(graphene_core::raster::ImageFrame), + Image(graphene_core::raster::Image), + RcImage(Option>>), + ImageFrame(graphene_core::raster::ImageFrame), Color(graphene_core::raster::color::Color), Subpaths(Vec>), RcSubpath(Arc>), @@ -168,9 +168,9 @@ impl<'a> TaggedValue { TaggedValue::Bool(_) => concrete!(bool), TaggedValue::DVec2(_) => concrete!(DVec2), TaggedValue::OptionalDVec2(_) => concrete!(Option), - TaggedValue::Image(_) => concrete!(graphene_core::raster::Image), - TaggedValue::RcImage(_) => concrete!(Option>), - TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame), + TaggedValue::Image(_) => concrete!(graphene_core::raster::Image), + TaggedValue::RcImage(_) => concrete!(Option>>), + TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame), TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), TaggedValue::Subpaths(_) => concrete!(Vec>), TaggedValue::RcSubpath(_) => concrete!(Arc>), diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index be6fb1a3a9..613c7a8fe5 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use glam::{DAffine2, DVec2}; -use graphene_core::raster::{Color, Image, ImageFrame}; +use graphene_core::raster::{Color, Image, ImageFrame, RasterMut}; use graphene_core::transform::TransformMut; use graphene_core::vector::VectorData; use graphene_core::Node; @@ -93,7 +93,7 @@ fn erase(input: (Color, Color), flow: f64) -> Color { } #[node_fn(BrushTextureNode)] -fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> ImageFrame { +fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> ImageFrame { // Diameter let radius = diameter / 2.; // TODO: Remove the 4px padding after figuring out why the brush stamp gets randomly offset by 1px up/down/left/right when clicking with the Brush tool @@ -128,7 +128,7 @@ fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> Image let pixel_fill = summation / MULTISAMPLE_GRID.len() as f64; - let pixel = image.get_mut(x, y).unwrap(); + let pixel = image.get_pixel_mut(x, y).unwrap(); *pixel = color.apply_opacity(pixel_fill as f32); } } @@ -187,7 +187,7 @@ mod test { assert_eq!(image.image.height, size.ceil() as u32 + 4); assert_eq!(image.transform, DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil() + 4.), 0., -DVec2::splat(size / 2.))); // center pixel should be BLACK - assert_eq!(image.image.get(11, 11), Some(&Color::BLACK)); + assert_eq!(image.image.get_pixel(11, 11), Some(Color::BLACK)); } #[test] diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index dfded595a3..b28ebc8617 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,11 +1,12 @@ use dyn_any::{DynAny, StaticType, StaticTypeSized}; use glam::{BVec2, DAffine2, DVec2}; -use graphene_core::raster::{Color, Image, ImageFrame}; +use graphene_core::raster::{Alpha, Channel, Color, Image, ImageFrame, Luminance, Pixel, RasterMut, Sample}; use graphene_core::transform::Transform; use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::Node; +use std::fmt::Debug; use std::marker::PhantomData; use std::path::Path; @@ -93,10 +94,12 @@ pub fn export_image_node<'i, 's: 'i>() -> impl Node<'i, 's, (Image, &'i str), Ou } */ -pub struct DownresNode; +pub struct DownresNode

{ + _p: PhantomData

, +} -#[node_macro::node_fn(DownresNode)] -fn downres(image_frame: ImageFrame) -> ImageFrame { +#[node_macro::node_fn(DownresNode<_P>)] +fn downres<_P: Pixel>(image_frame: ImageFrame<_P>) -> ImageFrame<_P> { let target_width = (image_frame.transform.transform_vector2((1., 0.).into()).length() as usize).min(image_frame.image.width as usize); let target_height = (image_frame.transform.transform_vector2((0., 1.).into()).length() as usize).min(image_frame.image.height as usize); @@ -121,43 +124,22 @@ fn downres(image_frame: ImageFrame) -> ImageFrame { } #[derive(Debug, Clone, Copy)] -pub struct MapImageNode { +pub struct MapImageNode { map_fn: MapFn, + _p: PhantomData

, } -#[node_macro::node_fn(MapImageNode)] -fn map_image(image: Image, map_fn: &'any_input MapFn) -> Image +#[node_macro::node_fn(MapImageNode<_P>)] +fn map_image>(image: Img, map_fn: &'any_input MapFn) -> Img where - MapFn: for<'any_input> Node<'any_input, Color, Output = Color> + 'input, + MapFn: for<'any_input> Node<'any_input, _P, Output = _P> + 'input, { let mut image = image; - for pixel in &mut image.data { - *pixel = map_fn.eval(*pixel); - } + + image.map_pixels(|c| map_fn.eval(c)); image } -#[derive(Debug, Clone, Copy)] -pub struct MapImageFrameNode { - map_fn: MapFn, -} - -impl StaticType for MapImageFrameNode { - type Static = MapImageFrameNode; -} - -#[node_macro::node_fn(MapImageFrameNode)] -fn map_image(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame -where - MapFn: for<'any_input> Node<'any_input, Color, Output = Color> + 'input, -{ - for pixel in &mut image_frame.image.data { - *pixel = map_fn.eval(*pixel); - } - - image_frame -} - #[derive(Debug, Clone, DynAny)] pub struct AxisAlignedBbox { start: DVec2, @@ -227,33 +209,48 @@ fn compute_transformed_bounding_box(transform: DAffine2) -> Bbox { } #[derive(Debug, Clone, Copy)] -pub struct MaskImageNode { - mask: Mask, +pub struct MaskImageNode { + stencil: Stencil, + _p: PhantomData

, + _s: PhantomData, } -#[node_macro::node_fn(MaskImageNode)] -fn mask_image(mut image: ImageFrame, mask: ImageFrame) -> ImageFrame { - let image_size = DVec2::new(image.image.width as f64, image.image.height as f64); - let mask_size = DVec2::new(mask.image.width as f64, mask.image.height as f64); +#[node_macro::node_fn(MaskImageNode<_P, _S>)] +fn mask_image< + // _P is the color of the input image. It must have an alpha channel because that is going to + // be modified by the mask + _P: Copy + Alpha, + // _S is the color of the stencil. It must have a luminance channel because that is used to + // mask the input image + _S: Luminance, + // Input image + Input: Transform + RasterMut, + // Stencil + Stencil: Sample + Transform, +>( + mut image: Input, + stencil: Stencil, +) -> Input { + let image_size = DVec2::new(image.width() as f64, image.height() as f64); + let mask_size = stencil.transform().decompose_scale(); if mask_size == DVec2::ZERO { return image; } // Transforms a point from the background image to the forground image - let bg_to_fg = DAffine2::from_scale(mask_size) * mask.transform.inverse() * image.transform * DAffine2::from_scale(1. / image_size); + let bg_to_fg = DAffine2::from_scale(mask_size) * stencil.transform().inverse() * image.transform() * DAffine2::from_scale(1. / image_size); - for y in 0..image.image.height { - for x in 0..image.image.width { + for y in 0..image.height() { + for x in 0..image.width() { let image_point = DVec2::new(x as f64, y as f64); let mut mask_point = bg_to_fg.transform_point2(image_point); mask_point = mask_point.clamp(DVec2::ZERO, mask_size); - let image_pixel = image.get_mut(x as usize, y as usize); - let mask_pixel = mask.sample(mask_point); - let alpha = image_pixel.a() * mask_pixel.r(); - - *image_pixel = Color::from_rgbaf32(image_pixel.r(), image_pixel.g(), image_pixel.b(), alpha).unwrap(); + let image_pixel = image.get_pixel_mut(x as u32, y as u32).unwrap(); + if let Some(mask_pixel) = stencil.sample(mask_point) { + image_pixel.multiply_alpha(mask_pixel.l().to_channel()); + } } } @@ -261,18 +258,19 @@ fn mask_image(mut image: ImageFrame, mask: ImageFrame) -> ImageFrame { } #[derive(Debug, Clone, Copy)] -pub struct BlendImageTupleNode { +pub struct BlendImageTupleNode { map_fn: MapFn, + _p: PhantomData

, } -impl StaticType for BlendImageTupleNode { - type Static = BlendImageTupleNode; +impl StaticType for BlendImageTupleNode { + type Static = BlendImageTupleNode; } -#[node_macro::node_fn(BlendImageTupleNode)] -fn blend_image_tuple(images: (ImageFrame, ImageFrame), map_fn: &'any_input MapFn) -> ImageFrame +#[node_macro::node_fn(BlendImageTupleNode<_P>)] +fn blend_image_tuple<_P: Pixel + Debug, MapFn>(images: (ImageFrame<_P>, ImageFrame<_P>), map_fn: &'any_input MapFn) -> ImageFrame<_P> where - MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input + Clone, + MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone, { let (background, foreground) = images; @@ -281,30 +279,34 @@ where } #[derive(Debug, Clone, Copy)] -pub struct BlendImageNode { +pub struct BlendImageNode { background: Background, map_fn: MapFn, + _p: PhantomData

, } -impl StaticType for BlendImageNode { - type Static = BlendImageNode; +impl StaticType for BlendImageNode { + type Static = BlendImageNode; } // TODO: Implement proper blending -#[node_macro::node_fn(BlendImageNode)] -fn blend_image>(foreground: Frame, mut background: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame +#[node_macro::node_fn(BlendImageNode<_P>)] +fn blend_image<_P: Clone, MapFn, Frame: Sample + Transform, Background: RasterMut + Transform>( + foreground: Frame, + mut background: Background, + map_fn: &'any_input MapFn, +) -> Background where - MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input, + MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input, { - let foreground = foreground.as_ref(); - let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64); - let background_size = DVec2::new(background.image.width as f64, background.image.height as f64); + let foreground_size = foreground.transform().decompose_scale(); + let background_size = DVec2::new(background.width() as f64, background.height() as f64); // Transforms a point from the background image to the forground image - let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); + let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size); // Footprint of the foreground image (0,0) (1, 1) in the background image space - let bg_aabb = compute_transformed_bounding_box(background.transform.inverse() * foreground.transform).axis_aligned_bbox(); + let bg_aabb = compute_transformed_bounding_box(background.transform().inverse() * foreground.transform()).axis_aligned_bbox(); // Clamp the foreground image to the background image let start = (bg_aabb.start * background_size).max(DVec2::ZERO).as_uvec2(); @@ -314,14 +316,12 @@ where for x in start.x..end.x { let bg_point = DVec2::new(x as f64, y as f64); let fg_point = bg_to_fg.transform_point2(bg_point); - if !((fg_point.cmpge(DVec2::ZERO) & fg_point.cmple(foreground_size)) == BVec2::new(true, true)) { - continue; + + if let Some(src_pixel) = foreground.sample(fg_point) { + if let Some(dst_pixel) = background.get_pixel_mut(x, y) { + *dst_pixel = map_fn.eval((src_pixel, dst_pixel.clone())); + } } - - let dst_pixel = background.get_mut(x as usize, y as usize); - let src_pixel = foreground.sample(fg_point); - - *dst_pixel = map_fn.eval((src_pixel, *dst_pixel)); } } @@ -347,12 +347,13 @@ fn merge_bounding_box_node<_Data: Transform>(input: (Option, _D } #[derive(Clone, Debug, PartialEq)] -pub struct EmptyImageNode { +pub struct EmptyImageNode { pub color: FillColor, + _p: PhantomData

, } -#[node_macro::node_fn(EmptyImageNode)] -fn empty_image(transform: DAffine2, color: Color) -> ImageFrame { +#[node_macro::node_fn(EmptyImageNode<_P>)] +fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> { let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; @@ -361,12 +362,13 @@ fn empty_image(transform: DAffine2, color: Color) -> ImageFrame { } #[derive(Debug, Clone, Copy)] -pub struct ImaginateNode { +pub struct ImaginateNode { cached: E, + _p: PhantomData

, } -#[node_macro::node_fn(ImaginateNode)] -fn imaginate(image_frame: ImageFrame, cached: Option>) -> ImageFrame { +#[node_macro::node_fn(ImaginateNode<_P>)] +fn imaginate<_P: Pixel>(image_frame: ImageFrame<_P>, cached: Option>>) -> ImageFrame<_P> { let cached_image = cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image_frame.image); ImageFrame { image: cached_image, @@ -375,11 +377,12 @@ fn imaginate(image_frame: ImageFrame, cached: Option { +pub struct ImageFrameNode { transform: Transform, + _p: PhantomData

, } -#[node_macro::node_fn(ImageFrameNode)] -fn image_frame(image: Image, transform: DAffine2) -> graphene_core::raster::ImageFrame { +#[node_macro::node_fn(ImageFrameNode<_P>)] +fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> { graphene_core::raster::ImageFrame { image, transform } } #[cfg(test)] diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index a48813d1cd..96fb070dc7 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -87,25 +87,25 @@ macro_rules! raster_node { |args| { let node = construct_node!(args, $path, [$($type),*]); let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); Box::pin(any) }, { let params = vec![$(value_fn!($type)),*]; - NodeIOTypes::new(concrete!(Image), concrete!(Image), params) + NodeIOTypes::new(concrete!(Image), concrete!(Image), params) }, ), ( NodeIdentifier::new(stringify!($path)), |args| { let node = construct_node!(args, $path, [$($type),*]); - let map_node = graphene_std::raster::MapImageFrameNode::new(graphene_core::value::ValueNode::new(node)); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); + let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); Box::pin(any) }, { let params = vec![$(value_fn!($type)),*]; - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), params) + NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), params) }, ) ] @@ -128,7 +128,7 @@ fn node_registry() -> HashMap, input: &u32, params: [&u32]), register_node!(graphene_core::ops::AddNode, input: (u32, u32), params: []), register_node!(graphene_core::ops::AddNode, input: (u32, &u32), params: []), - register_node!(graphene_core::ops::CloneNode<_>, input: &ImageFrame, params: []), + register_node!(graphene_core::ops::CloneNode<_>, input: &ImageFrame, params: []), register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [u32]), register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [u32]), register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [&u32]), @@ -137,12 +137,12 @@ fn node_registry() -> HashMap, input: &f64, params: [f64]), register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]), register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]), - register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []), - register_node!(graphene_std::raster::DownresNode, input: ImageFrame, params: []), - register_node!(graphene_std::raster::MaskImageNode<_>, input: ImageFrame, params: [ImageFrame]), - register_node!(graphene_std::raster::EmptyImageNode<_>, input: DAffine2, params: [Color]), + register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []), + register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame, params: []), + register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame, params: [ImageFrame]), + register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]), #[cfg(feature = "gpu")] - register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]), + register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]), vec![( NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), |args| { @@ -155,8 +155,8 @@ fn node_registry() -> HashMap, input: core::slice::Iter, params: [ImageFrame, &ValueNode, ClonedNode>>>>]), - //register_node!(graphene_std::brush::ReduceNode<_, _>, input: core::slice::Iter, params: [AxisAlignedBbox, &MergeBoundingBoxNode]), + //register_node!(graphene_std::brush::ReduceNode<_, _>, input: core::slice::Iter>, params: [ImageFrame, &ValueNode, ClonedNode>>>>]), + //register_node!(graphene_std::brush::ReduceNode<_, _>, input: core::slice::Iter>, params: [AxisAlignedBbox, &MergeBoundingBoxNode]), register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec, params: []), vec![( NodeIdentifier::new("graphene_std::brush::BrushNode"), @@ -193,23 +193,27 @@ fn node_registry() -> HashMap), vec![value_fn!(Vec), value_fn!(f64), value_fn!(f64), value_fn!(f64), value_fn!(Color)], ), )], vec![( NodeIdentifier::new("graphene_std::brush::ReduceNode<_, _>"), |args| { - let acc: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let acc: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); let image = acc.eval(()); let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(1.0)); let _ = &blend_node as &dyn for<'i> Node<'i, (Color, Color), Output = Color>; let node = ReduceNode::new(ClonedNode::new(image), ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node)))); - //let _ = &node as &dyn for<'i> Node<'i, core::slice::Iter, Output = ImageFrame>; - let any: DynAnyNode + Sync + Send>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(node)); + //let _ = &node as &dyn for<'i> Node<'i, core::slice::Iter>, Output = ImageFrame>; + let any: DynAnyNode> + Sync + Send>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(node)); Box::pin(any) }, - NodeIOTypes::new(concrete!(Box + Sync + Send>), concrete!(ImageFrame), vec![value_fn!(ImageFrame)]), + NodeIOTypes::new( + concrete!(Box> + Sync + Send>), + concrete!(ImageFrame), + vec![value_fn!(ImageFrame)], + ), )], // Filters raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), @@ -218,29 +222,33 @@ fn node_registry() -> HashMap"), |args| { - let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(()))); let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node)); - let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>; - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>; + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); any.into_type_erased() }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f64)]), + NodeIOTypes::new( + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f64)], + ), ), ( NodeIdentifier::new("graphene_core::raster::EraseNode<_, _>"), |args| { - let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]); let blend_node = graphene_std::brush::EraseNode::new(ClonedNode::new(opacity.eval(()))); let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node)); - let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>; - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>; + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); any.into_type_erased() }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(ImageFrame), value_fn!(f64)]), + NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(ImageFrame), value_fn!(f64)]), ), ], raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), @@ -261,17 +269,17 @@ fn node_registry() -> HashMap = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); + let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(()))); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); Box::pin(any) } else { let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast); - let map_image_frame_node = graphene_std::raster::MapImageFrameNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); + let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); Box::pin(any) } }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(f64), value_fn!(f64), value_fn!(bool)]), + NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![value_fn!(f64), value_fn!(f64), value_fn!(bool)]), )], raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]), raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]), @@ -280,63 +288,63 @@ fn node_registry() -> HashMap"), |_| { - let node: LetNode = graphene_std::memo::LetNode::new(); + let node: LetNode> = graphene_std::memo::LetNode::new(); let any = graphene_std::any::DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(Option), concrete!(&ImageFrame), vec![]), + NodeIOTypes::new(concrete!(Option>), concrete!(&ImageFrame), vec![]), ), ( NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"), |args| { - let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); let node = graphene_std::memo::EndLetNode::new(input); - let any: DynAnyInRefNode = graphene_std::any::DynAnyInRefNode::new(node); + let any: DynAnyInRefNode, _, _> = graphene_std::any::DynAnyInRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(ImageFrame)]), + NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(ImageFrame)]), ), ( NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"), |args| { let input: DowncastBothNode<(), VectorData> = DowncastBothNode::new(args[0]); let node = graphene_std::memo::EndLetNode::new(input); - let any: DynAnyInRefNode = graphene_std::any::DynAnyInRefNode::new(node); + let any: DynAnyInRefNode, _, _> = graphene_std::any::DynAnyInRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(VectorData)]), + NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![value_fn!(VectorData)]), ), ( NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"), |args| { - let map_fn: DowncastBothRefNode, ImageFrame> = DowncastBothRefNode::new(args[0]); + let map_fn: DowncastBothRefNode>, ImageFrame> = DowncastBothRefNode::new(args[0]); let node = graphene_std::memo::RefNode::new(map_fn); let any = graphene_std::any::DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![]), + NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![]), ), ( NodeIdentifier::new("graphene_core::structural::MapImageNode"), |args| { let map_fn: DowncastBothNode = DowncastBothNode::new(args[0]); let node = graphene_std::raster::MapImageNode::new(ValueNode::new(map_fn)); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); any.into_type_erased() }, - NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![]), + NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![]), ), ( NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>"), |args| { - let cached = graphene_std::any::input_node::>>(args[15]); + let cached = graphene_std::any::input_node::>>>(args[15]); let node = graphene_std::raster::ImaginateNode::new(cached); let any = DynAnyNode::new(ValueNode::new(node)); any.into_type_erased() }, NodeIOTypes::new( - concrete!(ImageFrame), - concrete!(ImageFrame), + concrete!(ImageFrame), + concrete!(ImageFrame), vec![ value_fn!(f64), value_fn!(Option), @@ -353,7 +361,7 @@ fn node_registry() -> HashMap>), + value_fn!(Option>>), value_fn!(f64), value_fn!(ImaginateStatus), ], @@ -364,9 +372,9 @@ fn node_registry() -> HashMap::new(args[0]); let sigma = DowncastBothNode::<(), f64>::new(args[1]); - let image = DowncastBothRefNode::::new(args[2]); - let empty_image: ValueNode = ValueNode::new(Image::empty()); - let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); + let image = DowncastBothRefNode::, Image>::new(args[2]); + let empty_image: ValueNode> = ValueNode::new(Image::empty()); + let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); use graphene_core::Node; let radius = ClonedNode::new(radius.eval(())); let sigma = ClonedNode::new(sigma.eval(())); @@ -391,41 +399,41 @@ fn node_registry() -> HashMap = TypeNode::new(dimensions); let new_image = dimensions.then(new_image); let new_image = ForgetNode::new().then(new_image); - let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); + let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); node.into_type_erased() }, - NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![value_fn!(u32), value_fn!(f64)]), + NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![value_fn!(u32), value_fn!(f64)]), ), - //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), + //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), |args| { - let input: DowncastBothNode<(), Image> = DowncastBothNode::new(args[0]); - let node: CacheNode = graphene_std::memo::CacheNode::new(input); + let input: DowncastBothNode<(), Image> = DowncastBothNode::new(args[0]); + let node: CacheNode, _> = graphene_std::memo::CacheNode::new(input); let any = DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(()), concrete!(&Image), vec![value_fn!(Image)]), + NodeIOTypes::new(concrete!(()), concrete!(&Image), vec![value_fn!(Image)]), ), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), |args| { - let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); - let node: CacheNode = graphene_std::memo::CacheNode::new(input); + let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let node: CacheNode, _> = graphene_std::memo::CacheNode::new(input); let any = DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![value_fn!(ImageFrame)]), + NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![value_fn!(ImageFrame)]), ), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), |args| { - let input: DowncastBothNode = DowncastBothNode::new(args[0]); - let node: CacheNode = graphene_std::memo::CacheNode::new(input); + let input: DowncastBothNode, ImageFrame> = DowncastBothNode::new(args[0]); + let node: CacheNode, _> = graphene_std::memo::CacheNode::new(input); let any = DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(ImageFrame), concrete!(&ImageFrame), vec![fn_type!(ImageFrame, ImageFrame)]), + NodeIOTypes::new(concrete!(ImageFrame), concrete!(&ImageFrame), vec![fn_type!(ImageFrame, ImageFrame)]), ), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), @@ -448,19 +456,19 @@ fn node_registry() -> HashMap), vec![value_fn!(Vec)]), ), ], - register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), - register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]), + register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), + register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), #[cfg(feature = "quantization")] - register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame, params: [u32, u32]), + register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame, params: [u32, u32]), raster_node!(graphene_core::quantization::QuantizeNode<_>, params: [QuantizationChannels]), raster_node!(graphene_core::quantization::DeQuantizeNode<_>, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]), - register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]), + register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]), register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]), - register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame, params: [ImageFrame]), + register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame, params: [ImageFrame]), register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]), - register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame, params: [DAffine2]), + register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame, params: [DAffine2]), register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::vector::style::FillType, Option, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option)>]), register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option, f64, Vec, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]), register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []),