mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-02 12:32:17 +00:00
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:
parent
185106132d
commit
a566331f1c
8 changed files with 117 additions and 3 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2023,6 +2023,8 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"node-macro",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())),
|
||||
|
|
|
@ -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",
|
||||
] }
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue