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
This commit is contained in:
isiko 2023-08-19 20:30:03 +02:00 committed by GitHub
parent 185106132d
commit a566331f1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 3 deletions

2
Cargo.lock generated
View file

@ -2023,6 +2023,8 @@ dependencies = [
"js-sys",
"log",
"node-macro",
"rand 0.8.5",
"rand_chacha 0.3.1",
"reqwest",
"rustc-hash",
"serde",

View file

@ -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<DocumentNodeType> {
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",

View file

@ -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<LayoutGroup> {
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<LayoutGroup> {
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);

View file

@ -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, MonochromeR, MonochromeG, MonochromeB, MonochromeC, RedR, RedG, RedB, RedC, GreenR, GreenG, GreenB, GreenC, BlueR, BlueG, BlueB, BlueC> {
monochrome: Monochrome,

View file

@ -43,6 +43,7 @@ pub enum TaggedValue {
Stroke(graphene_core::vector::style::Stroke),
VecF32(Vec<f32>),
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<f32>),
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::<graphene_core::vector::style::Stroke>() => Ok(TaggedValue::Stroke(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<f32>>() => Ok(TaggedValue::VecF32(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => Ok(TaggedValue::RedGreenBlue(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => Ok(TaggedValue::NoiseType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => Ok(TaggedValue::RelativeAbsolute(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => Ok(TaggedValue::SelectiveColorChoice(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => Ok(TaggedValue::LineCap(*downcast(input).unwrap())),

View file

@ -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",
] }

View file

@ -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<P, Transform> {
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, Seed, NoiseType> {
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<Color> {
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::<Color> {
image,
transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)),
}
}
#[cfg(test)]
mod test {

View file

@ -612,6 +612,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: SurfaceFrame, params: [SurfaceFrame]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, 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<Color>, params: [u32, u32]),
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),