From a566331f1c484e2f314a24c8620c41fd65b75140 Mon Sep 17 00:00:00 2001 From: isiko Date: Sat, 19 Aug 2023 20:30:03 +0200 Subject: [PATCH] New node: Pixel Noise (#1267) Add Pixel Noise Node Currently only White Noise is implemented, but the Code is written so that other's can be added easily --- Cargo.lock | 2 + .../document_node_types.rs | 16 +++++++- .../node_properties.rs | 41 ++++++++++++++++++- node-graph/gcore/src/raster/adjustments.rs | 21 ++++++++++ node-graph/graph-craft/src/document/value.rs | 5 +++ node-graph/gstd/Cargo.toml | 2 + node-graph/gstd/src/raster.rs | 32 ++++++++++++++- .../interpreted-executor/src/node_registry.rs | 1 + 8 files changed, 117 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa9faf36..76e4d35c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2023,6 +2023,8 @@ dependencies = [ "js-sys", "log", "node-macro", + "rand 0.8.5", + "rand_chacha 0.3.1", "reqwest", "rustc-hash", "serde", 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 fab3f9a5..92e66463 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 @@ -11,7 +11,7 @@ use graph_craft::NodeIdentifier; #[cfg(feature = "gpu")] use graphene_core::application_io::SurfaceHandle; use graphene_core::raster::brush_cache::BrushCache; -use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; +use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::text::Font; use graphene_core::vector::VectorData; use graphene_core::*; @@ -547,6 +547,20 @@ fn static_nodes() -> Vec { properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), ..Default::default() }, + DocumentNodeType { + name: "Pixel Noise", + category: "General", + identifier: NodeImplementation::proto("graphene_std::raster::PixelNoiseNode<_, _, _>"), + inputs: vec![ + DocumentInputType::value("Width", TaggedValue::U32(100), false), + DocumentInputType::value("Height", TaggedValue::U32(100), false), + DocumentInputType::value("Seed", TaggedValue::U32(0), false), + DocumentInputType::value("Noise Type", TaggedValue::NoiseType(NoiseType::WhiteNoise), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::pixel_noise_properties, + ..Default::default() + }, DocumentNodeType { name: "Mask", category: "Image Adjustments", 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 554d6d77..d28c77a7 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 @@ -10,7 +10,7 @@ use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus}; -use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; +use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::text::Font; use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin}; use graphene_core::{Cow, Type, TypeDescriptor}; @@ -316,6 +316,29 @@ fn color_channel(document_node: &DocumentNode, node_id: u64, index: usize, name: LayoutGroup::Row { widgets }.with_tooltip("Color Channel") } +//TODO Use generalized Version of this as soon as it's available +fn noise_type(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { + let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); + if let &NodeInput::Value { + tagged_value: TaggedValue::NoiseType(calculation), + exposed: false, + } = &document_node.inputs[index] + { + let calculation_modes = NoiseType::list(); + let mut entries = Vec::with_capacity(calculation_modes.len()); + for method in calculation_modes { + entries.push(DropdownEntryData::new(method.to_string()).on_update(update_value(move |_| TaggedValue::NoiseType(method), node_id, index))); + } + let entries = vec![entries]; + + widgets.extend_from_slice(&[ + Separator::new(SeparatorType::Unrelated).widget_holder(), + DropdownInput::new(entries).selected_index(Some(calculation as u32)).widget_holder(), + ]); + } + LayoutGroup::Row { widgets }.with_tooltip("Type of Noise") +} + //TODO Use generalized Version of this as soon as it's available fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); @@ -772,6 +795,22 @@ pub fn extract_channel_properties(document_node: &DocumentNode, node_id: NodeId, vec![color_channel] } +// Noise Type is commented out for now as ther is only one type of noise (White Noise). +// As soon as there are more types of noise, this should be uncommented. +pub fn pixel_noise_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let width = number_widget(document_node, node_id, 0, "Width", NumberInput::default().unit("px").min(1.), true); + let height = number_widget(document_node, node_id, 1, "Height", NumberInput::default().unit("px").min(1.), true); + let seed = number_widget(document_node, node_id, 2, "Seed", NumberInput::default().min(0.), true); + let _noise_type = noise_type(document_node, node_id, 3, "Noise Type", true); + + vec![ + LayoutGroup::Row { widgets: width }, + LayoutGroup::Row { widgets: height }, + LayoutGroup::Row { widgets: seed }, + //_noise_type + ] +} + pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true); let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().min(-100.).max(100.).unit("%"), true); diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index cdcba8c7..df294acd 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -532,6 +532,27 @@ impl core::fmt::Display for RedGreenBlue { } } +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", derive(specta::Type))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)] +pub enum NoiseType { + WhiteNoise, +} + +impl core::fmt::Display for NoiseType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + NoiseType::WhiteNoise => write!(f, "White Noise"), + } + } +} + +impl NoiseType { + pub fn list() -> [NoiseType; 1] { + [NoiseType::WhiteNoise] + } +} + #[derive(Debug, Clone, Copy)] pub struct ChannelMixerNode { monochrome: Monochrome, diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index b60f329c..b7951eed 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -43,6 +43,7 @@ pub enum TaggedValue { Stroke(graphene_core::vector::style::Stroke), VecF32(Vec), RedGreenBlue(graphene_core::raster::RedGreenBlue), + NoiseType(graphene_core::raster::NoiseType), RelativeAbsolute(graphene_core::raster::RelativeAbsolute), SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice), LineCap(graphene_core::vector::style::LineCap), @@ -100,6 +101,7 @@ impl Hash for TaggedValue { Self::Stroke(stroke) => stroke.hash(state), Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)), Self::RedGreenBlue(red_green_blue) => red_green_blue.hash(state), + Self::NoiseType(noise_type) => noise_type.hash(state), Self::RelativeAbsolute(relative_absolute) => relative_absolute.hash(state), Self::SelectiveColorChoice(selective_color_choice) => selective_color_choice.hash(state), Self::LineCap(line_cap) => line_cap.hash(state), @@ -164,6 +166,7 @@ impl<'a> TaggedValue { TaggedValue::Stroke(x) => Box::new(x), TaggedValue::VecF32(x) => Box::new(x), TaggedValue::RedGreenBlue(x) => Box::new(x), + TaggedValue::NoiseType(x) => Box::new(x), TaggedValue::RelativeAbsolute(x) => Box::new(x), TaggedValue::SelectiveColorChoice(x) => Box::new(x), TaggedValue::LineCap(x) => Box::new(x), @@ -231,6 +234,7 @@ impl<'a> TaggedValue { TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke), TaggedValue::VecF32(_) => concrete!(Vec), TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue), + TaggedValue::NoiseType(_) => concrete!(graphene_core::raster::NoiseType), TaggedValue::RelativeAbsolute(_) => concrete!(graphene_core::raster::RelativeAbsolute), TaggedValue::SelectiveColorChoice(_) => concrete!(graphene_core::raster::SelectiveColorChoice), TaggedValue::LineCap(_) => concrete!(graphene_core::vector::style::LineCap), @@ -285,6 +289,7 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Ok(TaggedValue::Stroke(*downcast(input).unwrap())), x if x == TypeId::of::>() => Ok(TaggedValue::VecF32(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::RedGreenBlue(*downcast(input).unwrap())), + x if x == TypeId::of::() => Ok(TaggedValue::NoiseType(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::RelativeAbsolute(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::SelectiveColorChoice(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::LineCap(*downcast(input).unwrap())), diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 10ea5758..0c98fb71 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -24,6 +24,8 @@ imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"] wayland = [] [dependencies] +rand = { version = "0.8.5", features = ["alloc", "small_rng"], default-features = false} +rand_chacha = { version = "0.3.1", default-features = false } autoquant = { git = "https://github.com/truedoctor/autoquant", optional = true, features = [ "fitting", ] } diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 8f39a730..176536f9 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -2,7 +2,7 @@ use dyn_any::{DynAny, StaticType}; use glam::{DAffine2, DVec2}; use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::DynFuture; -use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample}; +use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample}; use graphene_core::transform::Transform; use crate::wasm_application_io::WasmEditorApi; @@ -16,6 +16,9 @@ use std::hash::Hash; use std::marker::PhantomData; use std::path::Path; +use rand::prelude::*; +use rand_chacha::ChaCha8Rng; + #[derive(Debug, DynAny)] pub enum Error { IO(std::io::Error), @@ -509,6 +512,33 @@ pub struct ImageFrameNode { fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> { graphene_core::raster::ImageFrame { image, transform } } + +#[derive(Debug, Clone, Copy)] +pub struct PixelNoiseNode { + height: Height, + seed: Seed, + noise_type: NoiseType, +} + +#[node_macro::node_fn(PixelNoiseNode)] +fn pixel_noise(width: u32, height: u32, seed: u32, noise_type: NoiseType) -> graphene_core::raster::ImageFrame { + let mut rng = ChaCha8Rng::seed_from_u64(seed as u64); + let mut image = Image::new(width, height, Color::from_luminance(0.5)); + for y in 0..height { + for x in 0..width { + let pixel = image.get_pixel_mut(x, y).unwrap(); + let luminance = match noise_type { + NoiseType::WhiteNoise => rng.gen_range(0.0..1.0) as f32, + }; + *pixel = Color::from_luminance(luminance); + } + } + ImageFrame:: { + image, + transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)), + } +} + #[cfg(test)] mod test { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 6e37c9db..cb0e0f95 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -612,6 +612,7 @@ fn node_registry() -> HashMap, input: (), output: SurfaceFrame, params: [SurfaceFrame]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), + register_node!(graphene_std::raster::PixelNoiseNode<_, _, _>, input: u32, params: [u32, u32, NoiseType]), #[cfg(feature = "quantization")] register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame, params: [u32, u32]), register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),