Shaders: add `gcore-shaders` and make `graster-nodes` no-std (#2925)

* gcore-shaders: add crate, move `color` and `blending` from gcore

* gcore-shaders: move `AsU32`

* gcore-shaders: move `ChoiceType`, switch `Cow` for `&str`, adjust node macro

* gcore-shaders: move `registry::types`

* gcore-shaders: move `context::Ctx`

* raster-nodes: make it `no_std` with `std` feature

* gcore-shaders: fix doctest
This commit is contained in:
Firestar99 2025-07-25 19:53:26 +02:00 committed by GitHub
parent 4d5a1a6ff1
commit 4fec24893e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 364 additions and 247 deletions

21
Cargo.lock generated
View File

@ -1527,6 +1527,7 @@ version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
dependencies = [ dependencies = [
"libm",
"serde", "serde",
] ]
@ -1695,12 +1696,11 @@ dependencies = [
"ctor", "ctor",
"dyn-any", "dyn-any",
"glam", "glam",
"half", "graphene-core-shaders",
"image", "image",
"kurbo", "kurbo",
"log", "log",
"node-macro", "node-macro",
"num-derive",
"num-traits", "num-traits",
"parley", "parley",
"petgraph 0.7.1", "petgraph 0.7.1",
@ -1716,6 +1716,22 @@ dependencies = [
"wgpu", "wgpu",
] ]
[[package]]
name = "graphene-core-shaders"
version = "0.1.0"
dependencies = [
"bytemuck",
"dyn-any",
"glam",
"graphene-core",
"half",
"log",
"num-derive",
"num-traits",
"serde",
"specta",
]
[[package]] [[package]]
name = "graphene-math-nodes" name = "graphene-math-nodes"
version = "0.1.0" version = "0.1.0"
@ -1754,6 +1770,7 @@ dependencies = [
"futures", "futures",
"glam", "glam",
"graphene-core", "graphene-core",
"graphene-core-shaders",
"image", "image",
"ndarray", "ndarray",
"node-macro", "node-macro",

View File

@ -7,6 +7,7 @@ members = [
"node-graph/gapplication-io", "node-graph/gapplication-io",
"node-graph/gbrush", "node-graph/gbrush",
"node-graph/gcore", "node-graph/gcore",
"node-graph/gcore-shaders",
"node-graph/gstd", "node-graph/gstd",
"node-graph/gmath-nodes", "node-graph/gmath-nodes",
"node-graph/gpath-bool", "node-graph/gpath-bool",
@ -28,6 +29,7 @@ default-members = [
"frontend/wasm", "frontend/wasm",
"node-graph/gbrush", "node-graph/gbrush",
"node-graph/gcore", "node-graph/gcore",
"node-graph/gcore-shaders",
"node-graph/gstd", "node-graph/gstd",
"node-graph/gmath-nodes", "node-graph/gmath-nodes",
"node-graph/gpath-bool", "node-graph/gpath-bool",
@ -50,6 +52,7 @@ path-bool = { path = "libraries/path-bool" }
graphene-application-io = { path = "node-graph/gapplication-io" } graphene-application-io = { path = "node-graph/gapplication-io" }
graphene-brush = { path = "node-graph/gbrush" } graphene-brush = { path = "node-graph/gbrush" }
graphene-core = { path = "node-graph/gcore" } graphene-core = { path = "node-graph/gcore" }
graphene-core-shaders = { path = "node-graph/gcore-shaders" }
graphene-math-nodes = { path = "node-graph/gmath-nodes" } graphene-math-nodes = { path = "node-graph/gmath-nodes" }
graphene-path-bool = { path = "node-graph/gpath-bool" } graphene-path-bool = { path = "node-graph/gpath-bool" }
graph-craft = { path = "node-graph/graph-craft" } graph-craft = { path = "node-graph/graph-craft" }
@ -166,6 +169,7 @@ opt-level = 1
[profile.dev.package] [profile.dev.package]
graphite-editor = { opt-level = 1 } graphite-editor = { opt-level = 1 }
graphene-core-shaders = { opt-level = 1 }
graphene-core = { opt-level = 1 } graphene-core = { opt-level = 1 }
graphene-std = { opt-level = 1 } graphene-std = { opt-level = 1 }
interpreted-executor = { opt-level = 1 } # This is a mitigation for https://github.com/rustwasm/wasm-pack/issues/981 which is needed because the node_registry function is too large interpreted-executor = { opt-level = 1 } # This is a mitigation for https://github.com/rustwasm/wasm-pack/issues/981 which is needed because the node_registry function is too large

View File

@ -1938,7 +1938,7 @@ pub mod choice {
use super::ParameterWidgetsInfo; use super::ParameterWidgetsInfo;
use crate::messages::tool::tool_messages::tool_prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint}; use graphene_std::choice_type::{ChoiceTypeStatic, ChoiceWidgetHint};
use std::marker::PhantomData; use std::marker::PhantomData;
pub trait WidgetFactory { pub trait WidgetFactory {
@ -1998,10 +1998,7 @@ pub mod choice {
.map(|(item, metadata)| { .map(|(item, metadata)| {
let updater = updater_factory(); let updater = updater_factory();
let committer = committer_factory(); let committer = committer_factory();
MenuListEntry::new(metadata.name.as_ref()) MenuListEntry::new(metadata.name).label(metadata.label).on_update(move |_| updater(item)).on_commit(committer)
.label(metadata.label.as_ref())
.on_update(move |_| updater(item))
.on_commit(committer)
}) })
.collect() .collect()
}) })
@ -2020,11 +2017,11 @@ pub mod choice {
.map(|(item, var_meta)| { .map(|(item, var_meta)| {
let updater = updater_factory(); let updater = updater_factory();
let committer = committer_factory(); let committer = committer_factory();
let entry = RadioEntryData::new(var_meta.name.as_ref()).on_update(move |_| updater(item)).on_commit(committer); let entry = RadioEntryData::new(var_meta.name).on_update(move |_| updater(item)).on_commit(committer);
match (var_meta.icon.as_deref(), var_meta.docstring.as_deref()) { match (var_meta.icon.as_deref(), var_meta.docstring.as_deref()) {
(None, None) => entry.label(var_meta.label.as_ref()), (None, None) => entry.label(var_meta.label),
(None, Some(doc)) => entry.label(var_meta.label.as_ref()).tooltip(doc), (None, Some(doc)) => entry.label(var_meta.label).tooltip(doc),
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label.as_ref()), (Some(icon), None) => entry.icon(icon).tooltip(var_meta.label),
(Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)), (Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)),
} }
}) })

View File

@ -419,7 +419,7 @@ impl LayoutHolder for MenuBarMessageHandler {
action: MenuBarEntry::no_action(), action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers, disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{ children: MenuBarEntryChildren(vec![{
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list(); let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
list.iter() list.iter()
.flat_map(|i| i.iter()) .flat_map(|i| i.iter())
.map(move |(operation, info)| MenuBarEntry { .map(move |(operation, info)| MenuBarEntry {

View File

@ -187,7 +187,7 @@ impl SelectTool {
} }
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> { fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list(); let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| { list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
let mut tooltip = info.label.to_string(); let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring.as_deref() { if let Some(doc) = info.docstring.as_deref() {

View File

@ -0,0 +1,35 @@
[package]
name = "graphene-core-shaders"
version = "0.1.0"
edition = "2024"
description = "no_std API definitions for Graphene"
authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
[features]
std = ["dep:dyn-any", "dep:serde", "dep:specta", "dep:log"]
[dependencies]
# Local std dependencies
dyn-any = { workspace = true, optional = true }
# Workspace dependencies
bytemuck = { workspace = true }
glam = { version = "0.29", default-features = false, features = ["nostd-libm", "scalar-math"] }
half = { workspace = true }
num-derive = { workspace = true }
num-traits = { workspace = true }
# Workspace std dependencies
serde = { workspace = true, optional = true }
specta = { workspace = true, optional = true }
log = { workspace = true, optional = true }
[dev-dependencies]
graphene-core = { workspace = true }
[lints.rust]
# the spirv target is not in the list of common cfgs so must be added manually
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_arch, values("spirv"))',
] }

View File

@ -1,8 +1,9 @@
use dyn_any::DynAny; use core::fmt::Display;
use std::hash::Hash; use core::hash::{Hash, Hasher};
#[derive(Copy, Clone, Debug, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, PartialEq)]
#[serde(default)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(default))]
pub struct AlphaBlending { pub struct AlphaBlending {
pub blend_mode: BlendMode, pub blend_mode: BlendMode,
pub opacity: f32, pub opacity: f32,
@ -15,14 +16,14 @@ impl Default for AlphaBlending {
} }
} }
impl Hash for AlphaBlending { impl Hash for AlphaBlending {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.opacity.to_bits().hash(state); self.opacity.to_bits().hash(state);
self.fill.to_bits().hash(state); self.fill.to_bits().hash(state);
self.blend_mode.hash(state); self.blend_mode.hash(state);
self.clip.hash(state); self.clip.hash(state);
} }
} }
impl std::fmt::Display for AlphaBlending { impl Display for AlphaBlending {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let round = |x: f32| (x * 1e3).round() / 1e3; let round = |x: f32| (x * 1e3).round() / 1e3;
write!( write!(
@ -62,9 +63,9 @@ impl AlphaBlending {
} }
} }
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash, specta::Type)]
#[repr(i32)] #[repr(i32)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub enum BlendMode { pub enum BlendMode {
// Basic group // Basic group
#[default] #[default]
@ -189,18 +190,19 @@ impl BlendMode {
} }
/// Renders the blend mode CSS style declaration. /// Renders the blend mode CSS style declaration.
#[cfg(feature = "std")]
pub fn render(&self) -> String { pub fn render(&self) -> String {
format!( format!(
r#" mix-blend-mode: {};"#, r#" mix-blend-mode: {};"#,
self.to_svg_style_name().unwrap_or_else(|| { self.to_svg_style_name().unwrap_or_else(|| {
warn!("Unsupported blend mode {self:?}"); log::warn!("Unsupported blend mode {self:?}");
"normal" "normal"
}) })
) )
} }
} }
impl std::fmt::Display for BlendMode { impl Display for BlendMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
// Normal group // Normal group

View File

@ -0,0 +1,26 @@
pub trait ChoiceTypeStatic: Sized + Copy + crate::AsU32 + Send + Sync {
const WIDGET_HINT: ChoiceWidgetHint;
const DESCRIPTION: Option<&'static str>;
fn list() -> &'static [&'static [(Self, VariantMetadata)]];
}
pub enum ChoiceWidgetHint {
Dropdown,
RadioButtons,
}
/// Translation struct between macro and definition.
#[derive(Clone, Debug)]
pub struct VariantMetadata {
/// Name as declared in source code.
pub name: &'static str,
/// Name to be displayed in UI.
pub label: &'static str,
/// User-facing documentation text.
pub docstring: Option<&'static str>,
/// Name of icon to display in radio buttons and such.
pub icon: Option<&'static str>,
}

View File

@ -1,16 +1,16 @@
use super::color_traits::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGB, RGBMut, Rec709Primaries, SRGB}; use super::color_traits::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGB, RGBMut, Rec709Primaries, SRGB};
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}; use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use dyn_any::DynAny; use core::hash::Hash;
use half::f16; use half::f16;
#[cfg(target_arch = "spirv")] #[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Euclid; use spirv_std::num_traits::Euclid;
#[cfg(target_arch = "spirv")] #[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float; use spirv_std::num_traits::float::Float;
use std::hash::Hash;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
pub struct RGBA16F { pub struct RGBA16F {
red: f16, red: f16,
green: f16, green: f16,
@ -82,7 +82,8 @@ impl Alpha for RGBA16F {
impl Pixel for RGBA16F {} impl Pixel for RGBA16F {}
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub struct SRGBA8 { pub struct SRGBA8 {
red: u8, red: u8,
green: u8, green: u8,
@ -162,7 +163,8 @@ impl Alpha for SRGBA8 {
impl Pixel for SRGBA8 {} impl Pixel for SRGBA8 {}
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub struct Luma(pub f32); pub struct Luma(pub f32);
impl Luminance for Luma { impl Luminance for Luma {
@ -202,7 +204,8 @@ impl Pixel for Luma {}
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`, /// 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. /// 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)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub struct Color { pub struct Color {
red: f32, red: f32,
green: f32, green: f32,

View File

@ -1,11 +1,10 @@
use bytemuck::{Pod, Zeroable};
use glam::DVec2;
use std::fmt::Debug;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
pub use crate::blending::*; pub use crate::blending::*;
use bytemuck::{Pod, Zeroable};
use core::fmt::Debug;
use glam::DVec2;
use num_derive::*;
#[cfg(target_arch = "spirv")]
use num_traits::float::Float;
pub trait Linear { pub trait Linear {
fn from_f32(x: f32) -> Self; fn from_f32(x: f32) -> Self;
@ -64,7 +63,6 @@ impl<T: Linear + Debug + Copy> Channel for T {
impl<T: Linear + Debug + Copy> LinearChannel for T {} impl<T: Linear + Debug + Copy> LinearChannel for T {}
use num_derive::*;
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Num, NumCast, NumOps, One, Zero, ToPrimitive, FromPrimitive)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Num, NumCast, NumOps, One, Zero, ToPrimitive, FromPrimitive)]
pub struct SRGBGammaFloat(f32); pub struct SRGBGammaFloat(f32);
@ -97,14 +95,6 @@ impl<T: Rec709Primaries> RGBPrimaries for T {
pub trait SRGB: Rec709Primaries {} pub trait SRGB: Rec709Primaries {}
pub trait Serde: serde::Serialize + for<'a> serde::Deserialize<'a> {}
#[cfg(not(feature = "serde"))]
pub trait Serde {}
impl<T: serde::Serialize + for<'a> serde::Deserialize<'a>> Serde for T {}
#[cfg(not(feature = "serde"))]
impl<T> Serde for T {}
// TODO: Come up with a better name for this trait // TODO: Come up with a better name for this trait
pub trait Pixel: Clone + Pod + Zeroable + Default { pub trait Pixel: Clone + Pod + Zeroable + Default {
#[cfg(not(target_arch = "spirv"))] #[cfg(not(target_arch = "spirv"))]

View File

@ -0,0 +1,9 @@
pub trait Ctx: Clone + Send {}
impl<T: Ctx> Ctx for Option<T> {}
impl<T: Ctx + Sync> Ctx for &T {}
impl Ctx for () {}
pub trait ArcCtx: Send + Sync {}
#[cfg(feature = "std")]
impl<T: ArcCtx> Ctx for std::sync::Arc<T> {}

View File

@ -0,0 +1,17 @@
pub mod blending;
pub mod choice_type;
pub mod color;
pub mod context;
pub mod registry;
pub use context::Ctx;
pub use glam;
pub trait AsU32 {
fn as_u32(&self) -> u32;
}
impl AsU32 for u32 {
fn as_u32(&self) -> u32 {
*self
}
}

View File

@ -0,0 +1,24 @@
pub mod types {
/// 0% - 100%
pub type Percentage = f64;
/// -100% - 100%
pub type SignedPercentage = f64;
/// -180° - 180°
pub type Angle = f64;
/// Ends in the unit of x
pub type Multiplier = f64;
/// Non-negative integer with px unit
pub type PixelLength = f64;
/// Non-negative
pub type Length = f64;
/// 0 to 1
pub type Fraction = f64;
/// Unsigned integer
pub type IntegerCount = u32;
/// Unsigned integer to be used for random seeds
pub type SeedValue = u32;
/// DVec2 with px unit
pub type PixelSize = glam::DVec2;
/// String with one or more than one line
pub type TextArea = String;
}

View File

@ -14,10 +14,12 @@ wgpu = ["dep:wgpu"]
dealloc_nodes = [] dealloc_nodes = []
[dependencies] [dependencies]
# Local dependencies
graphene-core-shaders = { workspace = true, features = ["std"] }
# Workspace dependencies # Workspace dependencies
bytemuck = { workspace = true } bytemuck = { workspace = true }
node-macro = { workspace = true } node-macro = { workspace = true }
num-derive = { workspace = true }
num-traits = { workspace = true } num-traits = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
glam = { workspace = true } glam = { workspace = true }
@ -30,7 +32,6 @@ rand_chacha = { workspace = true }
bezier-rs = { workspace = true } bezier-rs = { workspace = true }
specta = { workspace = true } specta = { workspace = true }
image = { workspace = true } image = { workspace = true }
half = { workspace = true }
tinyvec = { workspace = true } tinyvec = { workspace = true }
parley = { workspace = true } parley = { workspace = true }
skrifa = { workspace = true } skrifa = { workspace = true }
@ -46,9 +47,3 @@ wgpu = { workspace = true, optional = true }
# Workspace dependencies # Workspace dependencies
tokio = { workspace = true } tokio = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
[lints.rust]
# the spirv target is not in the list of common cfgs so must be added manually
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_arch, values("spirv"))',
] }

View File

@ -1,11 +1,10 @@
use crate::transform::Footprint; use crate::transform::Footprint;
pub use graphene_core_shaders::context::{ArcCtx, Ctx};
use std::any::Any; use std::any::Any;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::panic::Location; use std::panic::Location;
use std::sync::Arc; use std::sync::Arc;
pub trait Ctx: Clone + Send {}
pub trait ExtractFootprint { pub trait ExtractFootprint {
#[track_caller] #[track_caller]
fn try_footprint(&self) -> Option<&Footprint>; fn try_footprint(&self) -> Option<&Footprint>;
@ -51,9 +50,6 @@ pub enum VarArgsResult {
IndexOutOfBounds, IndexOutOfBounds,
NoVarArgs, NoVarArgs,
} }
impl<T: Ctx> Ctx for Option<T> {}
impl<T: Ctx + Sync> Ctx for &T {}
impl Ctx for () {}
impl Ctx for Footprint {} impl Ctx for Footprint {}
impl ExtractFootprint for () { impl ExtractFootprint for () {
fn try_footprint(&self) -> Option<&Footprint> { fn try_footprint(&self) -> Option<&Footprint> {
@ -157,7 +153,7 @@ impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
} }
impl Ctx for ContextImpl<'_> {} impl Ctx for ContextImpl<'_> {}
impl Ctx for Arc<OwnedContextImpl> {} impl ArcCtx for OwnedContextImpl {}
impl ExtractFootprint for ContextImpl<'_> { impl ExtractFootprint for ContextImpl<'_> {
fn try_footprint(&self) -> Option<&Footprint> { fn try_footprint(&self) -> Option<&Footprint> {

View File

@ -2,10 +2,8 @@
extern crate log; extern crate log;
pub mod animation; pub mod animation;
pub mod blending;
pub mod blending_nodes; pub mod blending_nodes;
pub mod bounds; pub mod bounds;
pub mod color;
pub mod consts; pub mod consts;
pub mod context; pub mod context;
pub mod debug; pub mod debug;
@ -33,13 +31,17 @@ pub mod vector;
pub use crate as graphene_core; pub use crate as graphene_core;
pub use blending::*; pub use blending::*;
pub use color::Color;
pub use context::*; pub use context::*;
pub use ctor; pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_core_shaders::AsU32;
pub use graphene_core_shaders::blending;
pub use graphene_core_shaders::choice_type;
pub use graphene_core_shaders::color;
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::MemoHash; pub use memo::MemoHash;
pub use num_traits; pub use num_traits;
pub use raster::Color;
use std::any::TypeId; use std::any::TypeId;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@ -165,12 +167,3 @@ pub trait NodeInputDecleration {
fn identifier() -> ProtoNodeIdentifier; fn identifier() -> ProtoNodeIdentifier;
type Result; type Result;
} }
pub trait AsU32 {
fn as_u32(&self) -> u32;
}
impl AsU32 for u32 {
fn as_u32(&self) -> u32 {
*self
}
}

View File

@ -5,13 +5,11 @@ pub mod color {
pub mod image; pub mod image;
pub use self::image::{Image, TransformImage}; pub use self::image::Image;
use crate::GraphicGroupTable; use crate::GraphicGroupTable;
pub use crate::color::*; pub use crate::color::*;
use crate::raster_types::{CPU, RasterDataTable}; use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable; use crate::vector::VectorDataTable;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
use std::fmt::Debug; use std::fmt::Debug;
pub trait Bitmap { pub trait Bitmap {

View File

@ -1,36 +1,12 @@
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::pin::Pin; use std::pin::Pin;
use std::sync::{LazyLock, Mutex}; use std::sync::{LazyLock, Mutex};
pub mod types { pub use graphene_core_shaders::registry::types;
/// 0% - 100%
pub type Percentage = f64;
/// -100% - 100%
pub type SignedPercentage = f64;
/// -180° - 180°
pub type Angle = f64;
/// Ends in the unit of x
pub type Multiplier = f64;
/// Non-negative integer with px unit
pub type PixelLength = f64;
/// Non-negative
pub type Length = f64;
/// 0 to 1
pub type Fraction = f64;
/// Unsigned integer
pub type IntegerCount = u32;
/// Unsigned integer to be used for random seeds
pub type SeedValue = u32;
/// DVec2 with px unit
pub type PixelSize = glam::DVec2;
/// String with one or more than one line
pub type TextArea = String;
}
// Translation struct between macro and definition // Translation struct between macro and definition
#[derive(Clone)] #[derive(Clone)]
@ -59,33 +35,6 @@ pub struct FieldMetadata {
pub unit: Option<&'static str>, pub unit: Option<&'static str>,
} }
pub trait ChoiceTypeStatic: Sized + Copy + crate::AsU32 + Send + Sync {
const WIDGET_HINT: ChoiceWidgetHint;
const DESCRIPTION: Option<&'static str>;
fn list() -> &'static [&'static [(Self, VariantMetadata)]];
}
pub enum ChoiceWidgetHint {
Dropdown,
RadioButtons,
}
/// Translation struct between macro and definition.
#[derive(Clone, Debug)]
pub struct VariantMetadata {
/// Name as declared in source code.
pub name: Cow<'static, str>,
/// Name to be displayed in UI.
pub label: Cow<'static, str>,
/// User-facing documentation text.
pub docstring: Option<Cow<'static, str>>,
/// Name of icon to display in radio buttons and such.
pub icon: Option<Cow<'static, str>>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum RegistryWidgetOverride { pub enum RegistryWidgetOverride {
None, None,

View File

@ -120,7 +120,6 @@ impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
type Output = T; type Output = T;
#[inline(always)] #[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output { fn eval(&'i self, _input: ()) -> Self::Output {
#[cfg(not(target_arch = "spirv"))]
// KEEP THIS `debug!()` - It acts as the output for the debug node itself // KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("DebugClonedNode::eval"); log::debug!("DebugClonedNode::eval");

View File

@ -7,28 +7,44 @@ authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[features] [features]
default = ["serde"] default = ["std"]
serde = ["dep:serde"] std = [
"dep:graphene-core",
"dep:dyn-any",
"dep:image",
"dep:ndarray",
"dep:bezier-rs",
"dep:rand",
"dep:rand_chacha",
"dep:fastnoise-lite",
"dep:serde",
"dep:specta",
"dep:glam"
]
[dependencies] [dependencies]
# Local dependencies # Local dependencies
dyn-any = { workspace = true } graphene-core-shaders = { workspace = true }
graphene-core = { workspace = true }
node-macro = { workspace = true } node-macro = { workspace = true }
# Workspace dependencies # Local std dependencies
glam = { workspace = true } dyn-any = { workspace = true, optional = true }
specta = { workspace = true } graphene-core = { workspace = true, optional = true }
image = { workspace = true }
bytemuck = { workspace = true }
ndarray = { workspace = true }
bezier-rs = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
fastnoise-lite = { workspace = true }
# Optional workspace dependencies # Workspace dependencies
serde = { workspace = true, optional = true, features = ["derive"] } bytemuck = { workspace = true }
# glam is reexported from gcore-shaders in no_std mode
glam = { workspace = true, optional = true }
# Workspace std dependencies
specta = { workspace = true, optional = true }
image = { workspace = true, optional = true }
ndarray = { workspace = true, optional = true }
bezier-rs = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
rand_chacha = { workspace = true, optional = true }
fastnoise-lite = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true } tokio = { workspace = true }

View File

@ -1,6 +1,4 @@
use graphene_core::Color; use graphene_core_shaders::color::Color;
use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable};
pub trait Adjust<P> { pub trait Adjust<P> {
fn adjust(&mut self, map_fn: impl Fn(&P) -> P); fn adjust(&mut self, map_fn: impl Fn(&P) -> P);
@ -17,19 +15,26 @@ impl Adjust<Color> for Option<Color> {
} }
} }
} }
impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { #[cfg(feature = "std")]
for (_pos, c) in self.iter_mut() { mod adjust_std {
*c = map_fn(c); use super::*;
} use graphene_core::gradient::GradientStops;
} use graphene_core::raster_types::{CPU, RasterDataTable};
} impl Adjust<Color> for GradientStops {
impl Adjust<Color> for RasterDataTable<CPU> { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { for (_pos, c) in self.iter_mut() {
for instance in self.instance_mut_iter() {
for c in instance.instance.data_mut().data.iter_mut() {
*c = map_fn(c); *c = map_fn(c);
} }
} }
} }
impl Adjust<Color> for RasterDataTable<CPU> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for instance in self.instance_mut_iter() {
for c in instance.instance.data_mut().data.iter_mut() {
*c = map_fn(c);
}
}
}
}
} }

View File

@ -2,13 +2,14 @@
use crate::adjust::Adjust; use crate::adjust::Adjust;
use crate::cubic_spline::CubicSplines; use crate::cubic_spline::CubicSplines;
use dyn_any::DynAny; use core::fmt::Debug;
use graphene_core::color::Color; #[cfg(feature = "std")]
use graphene_core::context::Ctx;
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::registry::types::{Angle, Percentage, SignedPercentage}; use graphene_core_shaders::color::Color;
use std::fmt::Debug; use graphene_core_shaders::context::Ctx;
use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage};
// TODO: Implement the following: // TODO: Implement the following:
// Color Balance // Color Balance
@ -25,7 +26,8 @@ use std::fmt::Debug;
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)] #[widget(Dropdown)]
pub enum LuminanceCalculation { pub enum LuminanceCalculation {
#[default] #[default]
@ -37,7 +39,7 @@ pub enum LuminanceCalculation {
MaximumChannels, MaximumChannels,
} }
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
fn luminance<T: Adjust<Color>>( fn luminance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -61,7 +63,7 @@ fn luminance<T: Adjust<Color>>(
input input
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))]
fn gamma_correction<T: Adjust<Color>>( fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -81,7 +83,7 @@ fn gamma_correction<T: Adjust<Color>>(
input input
} }
#[node_macro::node(category("Raster: Channels"))] #[node_macro::node(category("Raster: Channels"), shader_node(PerPixelAdjust))]
fn extract_channel<T: Adjust<Color>>( fn extract_channel<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -104,7 +106,7 @@ fn extract_channel<T: Adjust<Color>>(
input input
} }
#[node_macro::node(category("Raster: Channels"))] #[node_macro::node(category("Raster: Channels"), shader_node(PerPixelAdjust))]
fn make_opaque<T: Adjust<Color>>( fn make_opaque<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -129,7 +131,7 @@ fn make_opaque<T: Adjust<Color>>(
// //
// Some further analysis available at: // Some further analysis available at:
// https://geraldbakker.nl/psnumbers/brightness-contrast.html // https://geraldbakker.nl/psnumbers/brightness-contrast.html
#[node_macro::node(name("Brightness/Contrast"), category("Raster: Adjustment"), properties("brightness_contrast_properties"))] #[node_macro::node(name("Brightness/Contrast"), category("Raster: Adjustment"), properties("brightness_contrast_properties"), shader_node(PerPixelAdjust))]
fn brightness_contrast<T: Adjust<Color>>( fn brightness_contrast<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -146,7 +148,7 @@ fn brightness_contrast<T: Adjust<Color>>(
let brightness = brightness as f32 / 255.; let brightness = brightness as f32 / 255.;
let contrast = contrast as f32 / 100.; let contrast = contrast as f32 / 100.;
let contrast = if contrast > 0. { (contrast * std::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast }; let contrast = if contrast > 0. { (contrast * core::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast };
let offset = brightness * contrast + brightness - contrast / 2.; let offset = brightness * contrast + brightness - contrast / 2.;
@ -168,13 +170,13 @@ fn brightness_contrast<T: Adjust<Color>>(
y: [0., 130. + brightness * 51., 233. + brightness * 10., 255.].map(|x| x / 255.), y: [0., 130. + brightness * 51., 233. + brightness * 10., 255.].map(|x| x / 255.),
}; };
let brightness_curve_solutions = brightness_curve_points.solve(); let brightness_curve_solutions = brightness_curve_points.solve();
let mut brightness_lut: [f32; WINDOW_SIZE] = std::array::from_fn(|i| { let mut brightness_lut: [f32; WINDOW_SIZE] = core::array::from_fn(|i| {
let x = i as f32 / (WINDOW_SIZE as f32 - 1.); let x = i as f32 / (WINDOW_SIZE as f32 - 1.);
brightness_curve_points.interpolate(x, &brightness_curve_solutions) brightness_curve_points.interpolate(x, &brightness_curve_solutions)
}); });
// Special handling for when brightness is negative // Special handling for when brightness is negative
if brightness_is_negative { if brightness_is_negative {
brightness_lut = std::array::from_fn(|i| { brightness_lut = core::array::from_fn(|i| {
let mut x = i; let mut x = i;
while x > 1 && brightness_lut[x] > i as f32 / WINDOW_SIZE as f32 { while x > 1 && brightness_lut[x] > i as f32 / WINDOW_SIZE as f32 {
x -= 1; x -= 1;
@ -193,7 +195,7 @@ fn brightness_contrast<T: Adjust<Color>>(
y: [0., 64. - contrast * 30., 192. + contrast * 30., 255.].map(|x| x / 255.), y: [0., 64. - contrast * 30., 192. + contrast * 30., 255.].map(|x| x / 255.),
}; };
let contrast_curve_solutions = contrast_curve_points.solve(); let contrast_curve_solutions = contrast_curve_points.solve();
let contrast_lut: [f32; WINDOW_SIZE] = std::array::from_fn(|i| { let contrast_lut: [f32; WINDOW_SIZE] = core::array::from_fn(|i| {
let x = i as f32 / (WINDOW_SIZE as f32 - 1.); let x = i as f32 / (WINDOW_SIZE as f32 - 1.);
contrast_curve_points.interpolate(x, &contrast_curve_solutions) contrast_curve_points.interpolate(x, &contrast_curve_solutions)
}); });
@ -218,7 +220,7 @@ fn brightness_contrast<T: Adjust<Color>>(
// //
// Some further analysis available at: // Some further analysis available at:
// https://geraldbakker.nl/psnumbers/levels.html // https://geraldbakker.nl/psnumbers/levels.html
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
fn levels<T: Adjust<Color>>( fn levels<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -285,7 +287,7 @@ fn levels<T: Adjust<Color>>(
// Algorithm from: // Algorithm from:
// https://stackoverflow.com/a/55233732/775283 // https://stackoverflow.com/a/55233732/775283
// Works the same for gamma and linear color // Works the same for gamma and linear color
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))] #[node_macro::node(name("Black & White"), category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn black_and_white<T: Adjust<Color>>( async fn black_and_white<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -357,7 +359,7 @@ async fn black_and_white<T: Adjust<Color>>(
// Aims for interoperable compatibility with: // Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))] #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn hue_saturation<T: Adjust<Color>>( async fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -391,7 +393,7 @@ async fn hue_saturation<T: Adjust<Color>>(
// Aims for interoperable compatibility with: // Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn invert<T: Adjust<Color>>( async fn invert<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -413,7 +415,7 @@ async fn invert<T: Adjust<Color>>(
// Aims for interoperable compatibility with: // Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn threshold<T: Adjust<Color>>( async fn threshold<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -458,7 +460,7 @@ async fn threshold<T: Adjust<Color>>(
// It's not the same as the saturation component of Hue/Saturation/Value. Vibrance and Saturation are both separable. // It's not the same as the saturation component of Hue/Saturation/Value. Vibrance and Saturation are both separable.
// When both parameters are set, it is equivalent to running this adjustment twice, with only vibrance set and then only saturation set. // When both parameters are set, it is equivalent to running this adjustment twice, with only vibrance set and then only saturation set.
// (Except for some noise probably due to rounding error.) // (Except for some noise probably due to rounding error.)
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn vibrance<T: Adjust<Color>>( async fn vibrance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -520,7 +522,8 @@ async fn vibrance<T: Adjust<Color>>(
} }
/// Color Channel /// Color Channel
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Radio)] #[widget(Radio)]
pub enum RedGreenBlue { pub enum RedGreenBlue {
#[default] #[default]
@ -530,7 +533,8 @@ pub enum RedGreenBlue {
} }
/// Color Channel /// Color Channel
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Radio)] #[widget(Radio)]
pub enum RedGreenBlueAlpha { pub enum RedGreenBlueAlpha {
#[default] #[default]
@ -541,7 +545,8 @@ pub enum RedGreenBlueAlpha {
} }
/// Style of noise pattern /// Style of noise pattern
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)] #[widget(Dropdown)]
pub enum NoiseType { pub enum NoiseType {
#[default] #[default]
@ -556,7 +561,8 @@ pub enum NoiseType {
WhiteNoise, WhiteNoise,
} }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
/// Style of layered levels of the noise pattern /// Style of layered levels of the noise pattern
pub enum FractalType { pub enum FractalType {
#[default] #[default]
@ -572,7 +578,8 @@ pub enum FractalType {
} }
/// Distance function used by the cellular noise /// Distance function used by the cellular noise
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub enum CellularDistanceFunction { pub enum CellularDistanceFunction {
#[default] #[default]
Euclidean, Euclidean,
@ -582,7 +589,8 @@ pub enum CellularDistanceFunction {
Hybrid, Hybrid,
} }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub enum CellularReturnType { pub enum CellularReturnType {
CellValue, CellValue,
#[default] #[default]
@ -601,7 +609,8 @@ pub enum CellularReturnType {
} }
/// Type of domain warp /// Type of domain warp
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)] #[widget(Dropdown)]
pub enum DomainWarpType { pub enum DomainWarpType {
#[default] #[default]
@ -616,7 +625,7 @@ pub enum DomainWarpType {
// Aims for interoperable compatibility with: // Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))] #[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"), shader_node(PerPixelAdjust))]
async fn channel_mixer<T: Adjust<Color>>( async fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -711,7 +720,8 @@ async fn channel_mixer<T: Adjust<Color>>(
image image
} }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Radio)] #[widget(Radio)]
pub enum RelativeAbsolute { pub enum RelativeAbsolute {
#[default] #[default]
@ -720,7 +730,8 @@ pub enum RelativeAbsolute {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub enum SelectiveColorChoice { pub enum SelectiveColorChoice {
#[default] #[default]
Reds, Reds,
@ -742,7 +753,7 @@ pub enum SelectiveColorChoice {
// //
// Algorithm based on: // Algorithm based on:
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html // https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))] #[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"), shader_node(PerPixelAdjust))]
async fn selective_color<T: Adjust<Color>>( async fn selective_color<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -884,7 +895,7 @@ async fn selective_color<T: Adjust<Color>>(
// Algorithm based on: // Algorithm based on:
// https://www.axiomx.com/posterize.htm // https://www.axiomx.com/posterize.htm
// This algorithm produces fully accurate output in relation to the industry standard. // This algorithm produces fully accurate output in relation to the industry standard.
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn posterize<T: Adjust<Color>>( async fn posterize<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -917,7 +928,7 @@ async fn posterize<T: Adjust<Color>>(
// //
// Algorithm based on: // Algorithm based on:
// https://geraldbakker.nl/psnumbers/exposure.html // https://geraldbakker.nl/psnumbers/exposure.html
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))] #[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"), shader_node(PerPixelAdjust))]
async fn exposure<T: Adjust<Color>>( async fn exposure<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(

View File

@ -1,11 +1,12 @@
use crate::adjust::Adjust; use crate::adjust::Adjust;
use graphene_core::color::Pixel; #[cfg(feature = "std")]
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
use graphene_core::raster::Image; #[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::registry::types::Percentage; use graphene_core_shaders::Ctx;
use graphene_core::{BlendMode, Color, Ctx}; use graphene_core_shaders::blending::BlendMode;
use std::cmp::Ordering; use graphene_core_shaders::color::{Color, Pixel};
use graphene_core_shaders::registry::types::Percentage;
pub trait Blend<P: Pixel> { pub trait Blend<P: Pixel> {
fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;
@ -24,41 +25,45 @@ impl Blend<Color> for Option<Color> {
} }
} }
} }
impl Blend<Color> for RasterDataTable<CPU> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) { #[cfg(feature = "std")]
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); mod blend_std {
use super::*;
use core::cmp::Ordering;
use graphene_core::raster::Image;
use graphene_core::raster_types::Raster;
impl Blend<Color> for RasterDataTable<CPU> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.instance = Raster::new_cpu(Image { *over.instance = Raster::new_cpu(Image {
data, data,
width: over.instance.width, width: over.instance.width,
height: over.instance.height, height: over.instance.height,
base64_string: None, base64_string: None,
}); });
}
result_table
} }
result_table
} }
} impl Blend<Color> for GradientStops {
impl Blend<Color> for GradientStops { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { let mut combined_stops = self.iter().map(|(position, _)| position).chain(under.iter().map(|(position, _)| position)).collect::<Vec<_>>();
let mut combined_stops = self.iter().map(|(position, _)| position).chain(under.iter().map(|(position, _)| position)).collect::<Vec<_>>(); combined_stops.dedup_by(|&mut a, &mut b| (a - b).abs() < 1e-6);
combined_stops.dedup_by(|&mut a, &mut b| (a - b).abs() < 1e-6); combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); let stops = combined_stops
.into_iter()
let stops = combined_stops .map(|&position| {
.into_iter() let over_color = self.evaluate(position);
.map(|&position| { let under_color = under.evaluate(position);
let over_color = self.evaluate(position); let color = blend_fn(over_color, under_color);
let under_color = under.evaluate(position); (position, color)
let color = blend_fn(over_color, under_color); })
(position, color) .collect::<Vec<_>>();
}) GradientStops::new(stops)
.collect::<Vec<_>>(); }
GradientStops::new(stops)
} }
} }
@ -114,7 +119,7 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
} }
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))]
async fn blend<T: Blend<Color> + Send>( async fn blend<T: Blend<Color> + Send>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -136,7 +141,7 @@ async fn blend<T: Blend<Color> + Send>(
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
} }
#[node_macro::node(category("Raster: Adjustment"))] #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
fn color_overlay<T: Adjust<Color>>( fn color_overlay<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
@ -163,6 +168,7 @@ fn color_overlay<T: Adjust<Color>>(
image image
} }
#[cfg(feature = "std")]
#[node_macro::node(category(""), skip_impl)] #[node_macro::node(category(""), skip_impl)]
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
where where
@ -174,7 +180,7 @@ where
blend_colors(input.0, input.1, blend_mode, opacity / 100.) blend_colors(input.0, input.1, blend_mode, opacity / 100.)
} }
#[cfg(test)] #[cfg(all(feature = "std", test))]
mod test { mod test {
use graphene_core::blending::BlendMode; use graphene_core::blending::BlendMode;
use graphene_core::color::Color; use graphene_core::color::Color;

View File

@ -47,7 +47,12 @@ impl CubicSplines {
// Gaussian elimination: forward elimination // Gaussian elimination: forward elimination
for row in 0..4 { for row in 0..4 {
let pivot_row_index = (row..4) let pivot_row_index = (row..4)
.max_by(|&a_row, &b_row| augmented_matrix[a_row][row].abs().partial_cmp(&augmented_matrix[b_row][row].abs()).unwrap_or(std::cmp::Ordering::Equal)) .max_by(|&a_row, &b_row| {
augmented_matrix[a_row][row]
.abs()
.partial_cmp(&augmented_matrix[b_row][row].abs())
.unwrap_or(core::cmp::Ordering::Equal)
})
.unwrap(); .unwrap();
// Swap the current row with the row that has the largest pivot element // Swap the current row with the row that has the largest pivot element

View File

@ -1,12 +1,24 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
pub use graphene_core_shaders::glam;
pub mod adjust; pub mod adjust;
pub mod adjustments; pub mod adjustments;
pub mod blending_nodes; pub mod blending_nodes;
pub mod cubic_spline; pub mod cubic_spline;
#[cfg(feature = "std")]
pub mod curve; pub mod curve;
#[cfg(feature = "std")]
pub mod dehaze; pub mod dehaze;
#[cfg(feature = "std")]
pub mod filter; pub mod filter;
#[cfg(feature = "std")]
pub mod generate_curves; pub mod generate_curves;
#[cfg(feature = "std")]
pub mod gradient_map; pub mod gradient_map;
#[cfg(feature = "std")]
pub mod image_color_palette; pub mod image_color_palette;
#[cfg(feature = "std")]
pub mod std_nodes; pub mod std_nodes;

View File

@ -111,13 +111,21 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
}) })
.collect(); .collect();
let crate_name = proc_macro_crate::crate_name("graphene-core") let crate_name = {
.map_err(|e| syn::Error::new(Span::call_site(), format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {}", e)))?; let crate_name = proc_macro_crate::crate_name("graphene-core-shaders")
let crate_name = match crate_name { .or_else(|_e| proc_macro_crate::crate_name("graphene-core"))
proc_macro_crate::FoundCrate::Itself => quote!(crate), .map_err(|e| {
proc_macro_crate::FoundCrate::Name(name) => { syn::Error::new(
let identifier = Ident::new(&name, Span::call_site()); Span::call_site(),
quote! { #identifier } format!("Failed to find location of 'graphene_core' or 'graphene-core-shaders'. Make sure it is imported as a dependency: {}", e),
)
})?;
match crate_name {
proc_macro_crate::FoundCrate::Itself => quote!(crate),
proc_macro_crate::FoundCrate::Name(name) => {
let identifier = Ident::new(&name, Span::call_site());
quote! { #identifier }
}
} }
}; };
@ -140,19 +148,19 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
let docstring = match &variant.basic_item.description { let docstring = match &variant.basic_item.description {
Some(s) => { Some(s) => {
let s = s.trim(); let s = s.trim();
quote! { Some(::std::borrow::Cow::Borrowed(#s)) } quote! { Some(#s) }
} }
None => quote! { None }, None => quote! { None },
}; };
let icon = match &variant.basic_item.icon { let icon = match &variant.basic_item.icon {
Some(s) => quote! { Some(::std::borrow::Cow::Borrowed(#s)) }, Some(s) => quote! { Some(#s) },
None => quote! { None }, None => quote! { None },
}; };
quote! { quote! {
( (
#name::#vname, #crate_name::registry::VariantMetadata { #name::#vname, #crate_name::choice_type::VariantMetadata {
name: ::std::borrow::Cow::Borrowed(#vname_str), name: #vname_str,
label: ::std::borrow::Cow::Borrowed(#label), label: #label,
docstring: #docstring, docstring: #docstring,
icon: #icon, icon: #icon,
} }
@ -174,10 +182,10 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
} }
} }
impl #crate_name::registry::ChoiceTypeStatic for #name { impl #crate_name::choice_type::ChoiceTypeStatic for #name {
const WIDGET_HINT: #crate_name::registry::ChoiceWidgetHint = #crate_name::registry::ChoiceWidgetHint::#widget_hint; const WIDGET_HINT: #crate_name::choice_type::ChoiceWidgetHint = #crate_name::choice_type::ChoiceWidgetHint::#widget_hint;
const DESCRIPTION: Option<&'static str> = #enum_description; const DESCRIPTION: Option<&'static str> = #enum_description;
fn list() -> &'static [&'static [(Self, #crate_name::registry::VariantMetadata)]] { fn list() -> &'static [&'static [(Self, #crate_name::choice_type::VariantMetadata)]] {
&[ #(#group)* ] &[ #(#group)* ]
} }
} }