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

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 std::hash::Hash;
use core::fmt::Display;
use core::hash::{Hash, Hasher};
#[derive(Copy, Clone, Debug, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(default))]
pub struct AlphaBlending {
pub blend_mode: BlendMode,
pub opacity: f32,
@ -15,14 +16,14 @@ impl Default 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.fill.to_bits().hash(state);
self.blend_mode.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 {
let round = |x: f32| (x * 1e3).round() / 1e3;
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)]
#[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 {
// Basic group
#[default]
@ -189,18 +190,19 @@ impl BlendMode {
}
/// Renders the blend mode CSS style declaration.
#[cfg(feature = "std")]
pub fn render(&self) -> String {
format!(
r#" mix-blend-mode: {};"#,
self.to_svg_style_name().unwrap_or_else(|| {
warn!("Unsupported blend mode {self:?}");
log::warn!("Unsupported blend mode {self:?}");
"normal"
})
)
}
}
impl std::fmt::Display for BlendMode {
impl Display for BlendMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
// 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::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
use bytemuck::{Pod, Zeroable};
use dyn_any::DynAny;
use core::hash::Hash;
use half::f16;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Euclid;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
use std::hash::Hash;
#[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 {
red: f16,
green: f16,
@ -82,7 +82,8 @@ impl Alpha for RGBA16F {
impl Pixel for RGBA16F {}
#[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 {
red: u8,
green: u8,
@ -162,7 +163,8 @@ impl Alpha for SRGBA8 {
impl Pixel for SRGBA8 {}
#[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);
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 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)]
#[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 {
red: 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::*;
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 {
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 {}
use num_derive::*;
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Num, NumCast, NumOps, One, Zero, ToPrimitive, FromPrimitive)]
pub struct SRGBGammaFloat(f32);
@ -97,14 +95,6 @@ impl<T: Rec709Primaries> RGBPrimaries for T {
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
pub trait Pixel: Clone + Pod + Zeroable + Default {
#[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 = []
[dependencies]
# Local dependencies
graphene-core-shaders = { workspace = true, features = ["std"] }
# Workspace dependencies
bytemuck = { workspace = true }
node-macro = { workspace = true }
num-derive = { workspace = true }
num-traits = { workspace = true }
rand = { workspace = true }
glam = { workspace = true }
@ -30,7 +32,6 @@ rand_chacha = { workspace = true }
bezier-rs = { workspace = true }
specta = { workspace = true }
image = { workspace = true }
half = { workspace = true }
tinyvec = { workspace = true }
parley = { workspace = true }
skrifa = { workspace = true }
@ -46,9 +47,3 @@ wgpu = { workspace = true, optional = true }
# Workspace dependencies
tokio = { 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;
pub use graphene_core_shaders::context::{ArcCtx, Ctx};
use std::any::Any;
use std::borrow::Borrow;
use std::panic::Location;
use std::sync::Arc;
pub trait Ctx: Clone + Send {}
pub trait ExtractFootprint {
#[track_caller]
fn try_footprint(&self) -> Option<&Footprint>;
@ -51,9 +50,6 @@ pub enum VarArgsResult {
IndexOutOfBounds,
NoVarArgs,
}
impl<T: Ctx> Ctx for Option<T> {}
impl<T: Ctx + Sync> Ctx for &T {}
impl Ctx for () {}
impl Ctx for Footprint {}
impl ExtractFootprint for () {
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 Arc<OwnedContextImpl> {}
impl ArcCtx for OwnedContextImpl {}
impl ExtractFootprint for ContextImpl<'_> {
fn try_footprint(&self) -> Option<&Footprint> {

View file

@ -2,10 +2,8 @@
extern crate log;
pub mod animation;
pub mod blending;
pub mod blending_nodes;
pub mod bounds;
pub mod color;
pub mod consts;
pub mod context;
pub mod debug;
@ -33,13 +31,17 @@ pub mod vector;
pub use crate as graphene_core;
pub use blending::*;
pub use color::Color;
pub use context::*;
pub use ctor;
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 memo::MemoHash;
pub use num_traits;
pub use raster::Color;
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
@ -165,12 +167,3 @@ pub trait NodeInputDecleration {
fn identifier() -> ProtoNodeIdentifier;
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 use self::image::{Image, TransformImage};
pub use self::image::Image;
use crate::GraphicGroupTable;
pub use crate::color::*;
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
use std::fmt::Debug;
pub trait Bitmap {

View file

@ -1,36 +1,12 @@
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType};
use std::borrow::Cow;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::{LazyLock, Mutex};
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;
}
pub use graphene_core_shaders::registry::types;
// Translation struct between macro and definition
#[derive(Clone)]
@ -59,33 +35,6 @@ pub struct FieldMetadata {
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)]
pub enum RegistryWidgetOverride {
None,

View file

@ -120,7 +120,6 @@ impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
type Output = T;
#[inline(always)]
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
log::debug!("DebugClonedNode::eval");

View file

@ -7,28 +7,44 @@ authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
[features]
default = ["serde"]
serde = ["dep:serde"]
default = ["std"]
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]
# Local dependencies
dyn-any = { workspace = true }
graphene-core = { workspace = true }
graphene-core-shaders = { workspace = true }
node-macro = { workspace = true }
# Workspace dependencies
glam = { workspace = true }
specta = { workspace = 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 }
# Local std dependencies
dyn-any = { workspace = true, optional = true }
graphene-core = { workspace = true, optional = true }
# Optional workspace dependencies
serde = { workspace = true, optional = true, features = ["derive"] }
# Workspace dependencies
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]
tokio = { workspace = true }

View file

@ -1,6 +1,4 @@
use graphene_core::Color;
use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core_shaders::color::Color;
pub trait Adjust<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) {
for (_pos, c) in self.iter_mut() {
*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() {
#[cfg(feature = "std")]
mod adjust_std {
use super::*;
use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable};
impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for (_pos, c) in self.iter_mut() {
*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::cubic_spline::CubicSplines;
use dyn_any::DynAny;
use graphene_core::color::Color;
use graphene_core::context::Ctx;
use core::fmt::Debug;
#[cfg(feature = "std")]
use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core::registry::types::{Angle, Percentage, SignedPercentage};
use std::fmt::Debug;
use graphene_core_shaders::color::Color;
use graphene_core_shaders::context::Ctx;
use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage};
// TODO: Implement the following:
// 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=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)]
pub enum LuminanceCalculation {
#[default]
@ -37,7 +39,7 @@ pub enum LuminanceCalculation {
MaximumChannels,
}
#[node_macro::node(category("Raster: Adjustment"))]
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
fn luminance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -61,7 +63,7 @@ fn luminance<T: Adjust<Color>>(
input
}
#[node_macro::node(category("Raster"))]
#[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))]
fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -81,7 +83,7 @@ fn gamma_correction<T: Adjust<Color>>(
input
}
#[node_macro::node(category("Raster: Channels"))]
#[node_macro::node(category("Raster: Channels"), shader_node(PerPixelAdjust))]
fn extract_channel<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -104,7 +106,7 @@ fn extract_channel<T: Adjust<Color>>(
input
}
#[node_macro::node(category("Raster: Channels"))]
#[node_macro::node(category("Raster: Channels"), shader_node(PerPixelAdjust))]
fn make_opaque<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -129,7 +131,7 @@ fn make_opaque<T: Adjust<Color>>(
//
// Some further analysis available at:
// 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>>(
_: impl Ctx,
#[implementations(
@ -146,7 +148,7 @@ fn brightness_contrast<T: Adjust<Color>>(
let brightness = brightness as f32 / 255.;
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.;
@ -168,13 +170,13 @@ fn brightness_contrast<T: Adjust<Color>>(
y: [0., 130. + brightness * 51., 233. + brightness * 10., 255.].map(|x| x / 255.),
};
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.);
brightness_curve_points.interpolate(x, &brightness_curve_solutions)
});
// Special handling for when 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;
while x > 1 && brightness_lut[x] > i as f32 / WINDOW_SIZE as f32 {
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.),
};
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.);
contrast_curve_points.interpolate(x, &contrast_curve_solutions)
});
@ -218,7 +220,7 @@ fn brightness_contrast<T: Adjust<Color>>(
//
// Some further analysis available at:
// 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>>(
_: impl Ctx,
#[implementations(
@ -285,7 +287,7 @@ fn levels<T: Adjust<Color>>(
// Algorithm from:
// https://stackoverflow.com/a/55233732/775283
// 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>>(
_: impl Ctx,
#[implementations(
@ -357,7 +359,7 @@ async fn black_and_white<T: Adjust<Color>>(
// 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=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>>(
_: impl Ctx,
#[implementations(
@ -391,7 +393,7 @@ async fn hue_saturation<T: Adjust<Color>>(
// 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
#[node_macro::node(category("Raster: Adjustment"))]
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn invert<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -413,7 +415,7 @@ async fn invert<T: Adjust<Color>>(
// 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
#[node_macro::node(category("Raster: Adjustment"))]
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn threshold<T: Adjust<Color>>(
_: impl Ctx,
#[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.
// 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.)
#[node_macro::node(category("Raster: Adjustment"))]
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
async fn vibrance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -520,7 +522,8 @@ async fn vibrance<T: Adjust<Color>>(
}
/// 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)]
pub enum RedGreenBlue {
#[default]
@ -530,7 +533,8 @@ pub enum RedGreenBlue {
}
/// 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)]
pub enum RedGreenBlueAlpha {
#[default]
@ -541,7 +545,8 @@ pub enum RedGreenBlueAlpha {
}
/// 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)]
pub enum NoiseType {
#[default]
@ -556,7 +561,8 @@ pub enum NoiseType {
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
pub enum FractalType {
#[default]
@ -572,7 +578,8 @@ pub enum FractalType {
}
/// 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 {
#[default]
Euclidean,
@ -582,7 +589,8 @@ pub enum CellularDistanceFunction {
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 {
CellValue,
#[default]
@ -601,7 +609,8 @@ pub enum CellularReturnType {
}
/// 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)]
pub enum DomainWarpType {
#[default]
@ -616,7 +625,7 @@ pub enum DomainWarpType {
// 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=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>>(
_: impl Ctx,
#[implementations(
@ -711,7 +720,8 @@ async fn channel_mixer<T: Adjust<Color>>(
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)]
pub enum RelativeAbsolute {
#[default]
@ -720,7 +730,8 @@ pub enum RelativeAbsolute {
}
#[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 {
#[default]
Reds,
@ -742,7 +753,7 @@ pub enum SelectiveColorChoice {
//
// Algorithm based on:
// 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>>(
_: impl Ctx,
#[implementations(
@ -884,7 +895,7 @@ async fn selective_color<T: Adjust<Color>>(
// Algorithm based on:
// https://www.axiomx.com/posterize.htm
// 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>>(
_: impl Ctx,
#[implementations(
@ -917,7 +928,7 @@ async fn posterize<T: Adjust<Color>>(
//
// Algorithm based on:
// 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>>(
_: impl Ctx,
#[implementations(

View file

@ -1,11 +1,12 @@
use crate::adjust::Adjust;
use graphene_core::color::Pixel;
#[cfg(feature = "std")]
use graphene_core::gradient::GradientStops;
use graphene_core::raster::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::registry::types::Percentage;
use graphene_core::{BlendMode, Color, Ctx};
use std::cmp::Ordering;
#[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable};
use graphene_core_shaders::Ctx;
use graphene_core_shaders::blending::BlendMode;
use graphene_core_shaders::color::{Color, Pixel};
use graphene_core_shaders::registry::types::Percentage;
pub trait Blend<P: Pixel> {
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()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
#[cfg(feature = "std")]
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 {
data,
width: over.instance.width,
height: over.instance.height,
base64_string: None,
});
*over.instance = Raster::new_cpu(Image {
data,
width: over.instance.width,
height: over.instance.height,
base64_string: None,
});
}
result_table
}
result_table
}
}
impl Blend<Color> for GradientStops {
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<_>>();
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));
let stops = combined_stops
.into_iter()
.map(|&position| {
let over_color = self.evaluate(position);
let under_color = under.evaluate(position);
let color = blend_fn(over_color, under_color);
(position, color)
})
.collect::<Vec<_>>();
GradientStops::new(stops)
impl Blend<Color> for GradientStops {
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<_>>();
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));
let stops = combined_stops
.into_iter()
.map(|&position| {
let over_color = self.evaluate(position);
let under_color = under.evaluate(position);
let color = blend_fn(over_color, under_color);
(position, color)
})
.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>(
_: impl Ctx,
#[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.))
}
#[node_macro::node(category("Raster: Adjustment"))]
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
fn color_overlay<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
@ -163,6 +168,7 @@ fn color_overlay<T: Adjust<Color>>(
image
}
#[cfg(feature = "std")]
#[node_macro::node(category(""), skip_impl)]
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
where
@ -174,7 +180,7 @@ where
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
}
#[cfg(test)]
#[cfg(all(feature = "std", test))]
mod test {
use graphene_core::blending::BlendMode;
use graphene_core::color::Color;

View file

@ -47,7 +47,12 @@ impl CubicSplines {
// Gaussian elimination: forward elimination
for row in 0..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();
// 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 adjustments;
pub mod blending_nodes;
pub mod cubic_spline;
#[cfg(feature = "std")]
pub mod curve;
#[cfg(feature = "std")]
pub mod dehaze;
#[cfg(feature = "std")]
pub mod filter;
#[cfg(feature = "std")]
pub mod generate_curves;
#[cfg(feature = "std")]
pub mod gradient_map;
#[cfg(feature = "std")]
pub mod image_color_palette;
#[cfg(feature = "std")]
pub mod std_nodes;

View file

@ -111,13 +111,21 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
})
.collect();
let crate_name = proc_macro_crate::crate_name("graphene-core")
.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 = 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 }
let crate_name = {
let crate_name = proc_macro_crate::crate_name("graphene-core-shaders")
.or_else(|_e| proc_macro_crate::crate_name("graphene-core"))
.map_err(|e| {
syn::Error::new(
Span::call_site(),
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 {
Some(s) => {
let s = s.trim();
quote! { Some(::std::borrow::Cow::Borrowed(#s)) }
quote! { Some(#s) }
}
None => quote! { None },
};
let icon = match &variant.basic_item.icon {
Some(s) => quote! { Some(::std::borrow::Cow::Borrowed(#s)) },
Some(s) => quote! { Some(#s) },
None => quote! { None },
};
quote! {
(
#name::#vname, #crate_name::registry::VariantMetadata {
name: ::std::borrow::Cow::Borrowed(#vname_str),
label: ::std::borrow::Cow::Borrowed(#label),
#name::#vname, #crate_name::choice_type::VariantMetadata {
name: #vname_str,
label: #label,
docstring: #docstring,
icon: #icon,
}
@ -174,10 +182,10 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
}
}
impl #crate_name::registry::ChoiceTypeStatic for #name {
const WIDGET_HINT: #crate_name::registry::ChoiceWidgetHint = #crate_name::registry::ChoiceWidgetHint::#widget_hint;
impl #crate_name::choice_type::ChoiceTypeStatic for #name {
const WIDGET_HINT: #crate_name::choice_type::ChoiceWidgetHint = #crate_name::choice_type::ChoiceWidgetHint::#widget_hint;
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)* ]
}
}