mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Graphene CLI + quantization research (#1320)
* Implement skeleton for graphene-cli * Configure gpu surface on non wasm32 targets * Create window with full hd size * Create window using the graphen-cli * Use window size for surface creation * Reuse surface configuration * Reduce window size for native applications to 800x600 * Add compute pipeline test * Poll wgpu execution externally * Remove cache node after texture upload * Add profiling instructions * Add more debug markers * Evaluate extract node before flattening the network * Reenable hue saturation node for compilation * Make hue saturation node work on the gpu + make f32 default for user inputs * Add version of test files without caching * Only dispatch each workgroup not pixel * ICE * Add quantization to gpu code * Fix quantization * Load images at graph runtime * Fix quantization calculation * Feature gate quantization * Use git version of autoquant * Add license to `graphene-cli` * Fix graphene-cli test case * Ignore tests on non unix platforms * Fix flattening test
This commit is contained in:
@@ -9,24 +9,10 @@ license = "MIT OR Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"dyn-any",
|
||||
"dyn-any/std",
|
||||
"alloc",
|
||||
"glam/std",
|
||||
"specta",
|
||||
"num-traits/std",
|
||||
"rustybuzz",
|
||||
]
|
||||
std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta", "num-traits/std", "rustybuzz"]
|
||||
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"]
|
||||
log = ["dep:log"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"glam/serde",
|
||||
"bezier-rs/serde",
|
||||
"bezier-rs/serde",
|
||||
"base64",
|
||||
]
|
||||
serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "bezier-rs/serde", "base64"]
|
||||
gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"]
|
||||
async = ["async-trait", "alloc"]
|
||||
nightly = []
|
||||
|
||||
@@ -8,7 +8,10 @@ use dyn_any::StaticType;
|
||||
use dyn_any::StaticTypeSized;
|
||||
use glam::DAffine2;
|
||||
|
||||
use core::any::Any;
|
||||
use core::future::Future;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::pin::Pin;
|
||||
|
||||
use crate::text::FontCache;
|
||||
|
||||
@@ -93,6 +96,7 @@ pub trait ApplicationIo {
|
||||
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||
None
|
||||
}
|
||||
fn load_resource<'a>(&self, url: impl AsRef<str>) -> Result<Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>, ApplicationError>;
|
||||
}
|
||||
|
||||
impl<T: ApplicationIo> ApplicationIo for &T {
|
||||
@@ -110,6 +114,16 @@ impl<T: ApplicationIo> ApplicationIo for &T {
|
||||
fn gpu_executor(&self) -> Option<&T::Executor> {
|
||||
(**self).gpu_executor()
|
||||
}
|
||||
|
||||
fn load_resource<'a>(&self, url: impl AsRef<str>) -> Result<Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>, ApplicationError> {
|
||||
(**self).load_resource(url)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ApplicationError {
|
||||
NotFound,
|
||||
InvalidUrl,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, NodeMut};
|
||||
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
|
||||
@@ -16,6 +16,21 @@ impl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnMutNode<T: FnMut(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, O: 'i, I: 'i> NodeMut<'i, I> for FnMutNode<T, I, O> {
|
||||
type MutOutput = O;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
self.0(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, I: 'i, O: 'i> FnMutNode<T, I, O> {
|
||||
pub fn new(f: T) -> Self {
|
||||
FnMutNode(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnNodeWithState<'i, T: Fn(I, &'i State) -> O, I, O, State: 'i>(T, State, PhantomData<(&'i O, I)>);
|
||||
impl<'i, I: 'i, O: 'i, State, T: Fn(I, &'i State) -> O + 'i> Node<'i, I> for FnNodeWithState<'i, T, I, O, State> {
|
||||
type Output = O;
|
||||
|
||||
@@ -55,6 +55,32 @@ pub trait Node<'i, Input: 'i>: 'i {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeMut<'i, Input: 'i>: 'i {
|
||||
type MutOutput: 'i;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput;
|
||||
}
|
||||
|
||||
pub trait NodeOnce<'i, Input>
|
||||
where
|
||||
Input: 'i,
|
||||
{
|
||||
type OnceOutput: 'i;
|
||||
fn eval_once(self, input: Input) -> Self::OnceOutput;
|
||||
}
|
||||
|
||||
impl<'i, T: Node<'i, I>, I: 'i> NodeOnce<'i, I> for &'i T {
|
||||
type OnceOutput = T::Output;
|
||||
fn eval_once(self, input: I) -> Self::OnceOutput {
|
||||
(self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, T: Node<'i, I> + ?Sized, I: 'i> NodeMut<'i, I> for &'i T {
|
||||
type MutOutput = T::Output;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
(*self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod types;
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -98,52 +124,40 @@ where
|
||||
{
|
||||
}
|
||||
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N {
|
||||
impl<'i, 's: 'i, I: 'i, N: Node<'i, I> + ?Sized> Node<'i, I> for &'i N {
|
||||
type Output = N::Output;
|
||||
fn eval(&'i self, input: I) -> N::Output {
|
||||
(*self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for Box<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for Box<N> {
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for alloc::sync::Arc<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for alloc::sync::Arc<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn Node<'i, I, Output = O> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
use core::pin::Pin;
|
||||
|
||||
use dyn_any::StaticTypeSized;
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
// TODO: This should return a reference to the cached cached_value
|
||||
// but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty acurate xD
|
||||
type Output = Pin<Box<dyn Future<Output = T> + 'i>>;
|
||||
fn eval(&'i self, input: ()) -> Self::Output {
|
||||
fn eval(&'i self, input: ()) -> Pin<Box<dyn Future<Output = T> + 'i>> {
|
||||
Box::pin(async move {
|
||||
if let Some(cached_value) = self.cache.take() {
|
||||
self.cache.set(Some(cached_value.clone()));
|
||||
|
||||
@@ -210,6 +210,7 @@ pub struct IntoNode<I, O> {
|
||||
_i: PhantomData<I>,
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
#[node_macro::node_fn(IntoNode<_I, _O>)]
|
||||
async fn into<_I, _O>(input: _I) -> _O
|
||||
where
|
||||
|
||||
@@ -1,58 +1,121 @@
|
||||
use crate::raster::Color;
|
||||
use crate::raster::{Color, Pixel};
|
||||
use crate::Node;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
use num_traits::CheckedShr;
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::Float;
|
||||
|
||||
#[derive(Clone, Debug, DynAny, PartialEq)]
|
||||
#[derive(Clone, Copy, DynAny, PartialEq, Pod, Zeroable)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C, align(16))]
|
||||
pub struct Quantization {
|
||||
pub fn_index: usize,
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
pub d: f32,
|
||||
pub bits: u32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Quantization {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Quantization").field("a", &self.a).field("b", &self.b()).field("bits", &self.bits()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Quantization {
|
||||
pub fn new(a: f32, b: f32, bits: u32) -> Self {
|
||||
Self { a, b, bits, _padding: 0 }
|
||||
}
|
||||
|
||||
pub fn a(&self) -> f32 {
|
||||
self.a
|
||||
}
|
||||
|
||||
pub fn b(&self) -> f32 {
|
||||
self.b
|
||||
}
|
||||
|
||||
pub fn bits(&self) -> u32 {
|
||||
self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Quantization {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.fn_index.hash(state);
|
||||
self.a.to_bits().hash(state);
|
||||
self.b.to_bits().hash(state);
|
||||
self.c.to_bits().hash(state);
|
||||
self.d.to_bits().hash(state);
|
||||
self.bits().hash(state);
|
||||
self.a().to_bits().hash(state);
|
||||
self.b().to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quantization {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fn_index: Default::default(),
|
||||
a: 1.,
|
||||
b: Default::default(),
|
||||
c: Default::default(),
|
||||
d: Default::default(),
|
||||
}
|
||||
Self::new(1., 0., 8)
|
||||
}
|
||||
}
|
||||
|
||||
pub type QuantizationChannels = [Quantization; 4];
|
||||
#[repr(transparent)]
|
||||
#[derive(DynAny, Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
|
||||
pub struct PackedPixel(pub u32);
|
||||
|
||||
fn quantize(value: f32, quantization: &Quantization) -> f32 {
|
||||
let Quantization { fn_index, a, b, c, d } = quantization;
|
||||
match fn_index {
|
||||
1 => ((value + a) * d).abs().ln() * b + c,
|
||||
_ => a * value + b,
|
||||
}
|
||||
impl Pixel for PackedPixel {}
|
||||
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (((a * value) * ((1 << bits) - 1) as f32) as i32 + b) as u32;
|
||||
value.checked_shl(32 - bits - offset).unwrap_or(0)
|
||||
}*/
|
||||
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let b = quantization.b();
|
||||
let bits = quantization.bits();
|
||||
|
||||
// Calculate the quantized value
|
||||
// Scale the value by 'a' and the maximum quantization range
|
||||
let scaled_value = ((a * value) + b) * ((1 << bits) - 1) as f32;
|
||||
// Round the scaled value to the nearest integer
|
||||
let rounded_value = scaled_value.clamp(0., (1 << bits) as f32 - 1.) as u32;
|
||||
|
||||
// Shift the quantized value to the appropriate position based on the offset
|
||||
let shifted_value = rounded_value.checked_shl(32 - bits - offset).unwrap();
|
||||
|
||||
shifted_value as u32
|
||||
}
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (value << offset) >> (31 - bits);
|
||||
let value = value as i32 - b;
|
||||
(value as f32 / ((1 << bits) - 1) as f32) / a
|
||||
}*/
|
||||
|
||||
fn decode(value: f32, quantization: &Quantization) -> f32 {
|
||||
let Quantization { fn_index, a, b, c, d } = quantization;
|
||||
match fn_index {
|
||||
1 => -(-c / b).exp() * (a * d * (c / b).exp() - (value / b).exp()) / d,
|
||||
_ => (value - b) / a,
|
||||
}
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
|
||||
// Shift the value to the appropriate position based on the offset
|
||||
let shifted_value = value.checked_shr(32 - bits - offset).unwrap();
|
||||
|
||||
// Unpack the quantized value
|
||||
let unpacked_value = shifted_value & ((1 << bits) - 1); // Mask out the unnecessary bits
|
||||
let normalized_value = unpacked_value as f32 / ((1 << bits) - 1) as f32; // Normalize the value based on the quantization range
|
||||
let decoded_value = normalized_value - b;
|
||||
let original_value = decoded_value / a;
|
||||
|
||||
original_value
|
||||
}
|
||||
|
||||
pub struct QuantizeNode<Quantization> {
|
||||
@@ -60,14 +123,22 @@ pub struct QuantizeNode<Quantization> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(QuantizeNode)]
|
||||
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization.as_slice();
|
||||
let r = quantize(color.r(), &quant[0]);
|
||||
let g = quantize(color.g(), &quant[1]);
|
||||
let b = quantize(color.b(), &quant[2]);
|
||||
let a = quantize(color.a(), &quant[3]);
|
||||
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> PackedPixel {
|
||||
let quant = quantization;
|
||||
quantize_color(color, quant)
|
||||
}
|
||||
|
||||
Color::from_rgbaf32_unchecked(r, g, b, a)
|
||||
pub fn quantize_color(color: Color, quant: [Quantization; 4]) -> PackedPixel {
|
||||
let mut offset = 0;
|
||||
let r = quantize(color.r(), offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let g = quantize(color.g(), offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let b = quantize(color.b(), offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let a = quantize(color.a(), offset, quant[3]);
|
||||
|
||||
PackedPixel(r | g | b | a)
|
||||
}
|
||||
|
||||
pub struct DeQuantizeNode<Quantization> {
|
||||
@@ -75,12 +146,53 @@ pub struct DeQuantizeNode<Quantization> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(DeQuantizeNode)]
|
||||
fn dequantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization.as_slice();
|
||||
let r = decode(color.r(), &quant[0]);
|
||||
let g = decode(color.g(), &quant[1]);
|
||||
let b = decode(color.b(), &quant[2]);
|
||||
let a = decode(color.a(), &quant[3]);
|
||||
fn dequantize_fn<'a>(color: PackedPixel, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization;
|
||||
dequantize_color(color, quant)
|
||||
}
|
||||
|
||||
pub fn dequantize_color(color: PackedPixel, quant: [Quantization; 4]) -> Color {
|
||||
let mut offset = 0;
|
||||
let r = decode(color.0, offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let g = decode(color.0, offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let b = decode(color.0, offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let a = decode(color.0, offset, quant[3]);
|
||||
|
||||
Color::from_rgbaf32_unchecked(r, g, b, a)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quantize() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0.5, 0.5, 0.5, 0.5);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0x7f7f7f7f);
|
||||
let dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
//assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quantize_black() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0., 0., 0., 1.);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0xff);
|
||||
let dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_getters() {
|
||||
let quant = Quantization::new(1., 3., 8);
|
||||
assert_eq!(quant.a(), 1.);
|
||||
assert_eq!(quant.b(), 3.);
|
||||
assert_eq!(quant.bits(), 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod adjustments;
|
||||
pub mod bbox;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub mod brightness_contrast;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub mod brush_cache;
|
||||
pub mod color;
|
||||
pub mod discrete_srgb;
|
||||
|
||||
@@ -229,7 +229,7 @@ pub struct LevelsNode<InputStart, InputMid, InputEnd, OutputStart, OutputEnd> {
|
||||
|
||||
// From https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
||||
#[node_macro::node_fn(LevelsNode)]
|
||||
fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, output_start: f64, output_end: f64) -> Color {
|
||||
fn levels_node(color: Color, input_start: f32, input_mid: f32, input_end: f32, output_start: f32, output_end: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
// Input Range (Range: 0-1)
|
||||
@@ -238,8 +238,8 @@ fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, o
|
||||
let input_highlights = (input_end / 100.) as f32;
|
||||
|
||||
// Output Range (Range: 0-1)
|
||||
let output_minimums = (output_start / 100.) as f32;
|
||||
let output_maximums = (output_end / 100.) as f32;
|
||||
let output_minimums = output_start / 100.;
|
||||
let output_maximums = output_end / 100.;
|
||||
|
||||
// Midtones interpolation factor between minimums and maximums (Range: 0-1)
|
||||
let midtones = output_minimums + (output_maximums - output_minimums) * input_midtones;
|
||||
@@ -286,7 +286,7 @@ pub struct GrayscaleNode<Tint, Reds, Yellows, Greens, Cyans, Blues, Magentas> {
|
||||
// From <https://stackoverflow.com/a/55233732/775283>
|
||||
// Works the same for gamma and linear color
|
||||
#[node_macro::node_fn(GrayscaleNode)]
|
||||
fn grayscale_color_node(color: Color, tint: Color, reds: f64, yellows: f64, greens: f64, cyans: f64, blues: f64, magentas: f64) -> Color {
|
||||
fn grayscale_color_node(color: Color, tint: Color, reds: f32, yellows: f32, greens: f32, cyans: f32, blues: f32, magentas: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let reds = reds as f32 / 100.;
|
||||
@@ -321,38 +321,29 @@ fn grayscale_color_node(color: Color, tint: Color, reds: f64, yellows: f64, gree
|
||||
color.to_linear_srgb()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub use hue_shift::HueSaturationNode;
|
||||
#[derive(Debug)]
|
||||
pub struct HueSaturationNode<Hue, Saturation, Lightness> {
|
||||
hue_shift: Hue,
|
||||
saturation_shift: Saturation,
|
||||
lightness_shift: Lightness,
|
||||
}
|
||||
|
||||
// TODO: Make this work on GPU so it can be removed from the wrapper module that excludes GPU (it doesn't work because of the modulo)
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
mod hue_shift {
|
||||
use super::*;
|
||||
#[node_macro::node_fn(HueSaturationNode)]
|
||||
fn hue_shift_color_node(color: Color, hue_shift: f32, saturation_shift: f32, lightness_shift: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HueSaturationNode<Hue, Saturation, Lightness> {
|
||||
hue_shift: Hue,
|
||||
saturation_shift: Saturation,
|
||||
lightness_shift: Lightness,
|
||||
}
|
||||
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
||||
|
||||
#[node_macro::node_fn(HueSaturationNode)]
|
||||
fn hue_shift_color_node(color: Color, hue_shift: f64, saturation_shift: f64, lightness_shift: f64) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
let color = Color::from_hsla(
|
||||
(hue + hue_shift / 360.) % 1.,
|
||||
// TODO: Improve the way saturation works (it's slightly off)
|
||||
(saturation + saturation_shift / 100.).clamp(0., 1.),
|
||||
// TODO: Fix the way lightness works (it's very off)
|
||||
(lightness + lightness_shift / 100.).clamp(0., 1.),
|
||||
alpha,
|
||||
);
|
||||
|
||||
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
||||
|
||||
let color = Color::from_hsla(
|
||||
(hue + hue_shift as f32 / 360.) % 1.,
|
||||
// TODO: Improve the way saturation works (it's slightly off)
|
||||
(saturation + saturation_shift as f32 / 100.).clamp(0., 1.),
|
||||
// TODO: Fix the way lightness works (it's very off)
|
||||
(lightness + lightness_shift as f32 / 100.).clamp(0., 1.),
|
||||
alpha,
|
||||
);
|
||||
|
||||
color.to_linear_srgb()
|
||||
}
|
||||
color.to_linear_srgb()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -388,9 +379,9 @@ pub struct ThresholdNode<MinLuminance, MaxLuminance, LuminanceCalc> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ThresholdNode)]
|
||||
fn threshold_node(color: Color, min_luminance: f64, max_luminance: f64, luminance_calc: LuminanceCalculation) -> Color {
|
||||
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
|
||||
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
|
||||
fn threshold_node(color: Color, min_luminance: f32, max_luminance: f32, luminance_calc: LuminanceCalculation) -> Color {
|
||||
let min_luminance = Color::srgb_to_linear(min_luminance / 100.);
|
||||
let max_luminance = Color::srgb_to_linear(max_luminance / 100.);
|
||||
|
||||
let luminance = match luminance_calc {
|
||||
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
||||
@@ -414,7 +405,7 @@ pub struct BlendNode<BlendMode, Opacity> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(BlendNode)]
|
||||
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
|
||||
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f32) -> Color {
|
||||
blend_colors(input.0, input.1, blend_mode, opacity as f32 / 100.)
|
||||
}
|
||||
|
||||
@@ -470,8 +461,8 @@ pub struct VibranceNode<Vibrance> {
|
||||
// Modified from https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
|
||||
// The results of this implementation are very close to correct, but not quite perfect
|
||||
#[node_macro::node_fn(VibranceNode)]
|
||||
fn vibrance_node(color: Color, vibrance: f64) -> Color {
|
||||
let vibrance = vibrance as f32 / 100.;
|
||||
fn vibrance_node(color: Color, vibrance: f32) -> Color {
|
||||
let vibrance = vibrance / 100.;
|
||||
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
|
||||
// So this scales the 0% to -50% range to 0% to -100%.
|
||||
let slowed_vibrance = if vibrance >= 0. { vibrance } else { vibrance * 0.5 };
|
||||
@@ -562,22 +553,22 @@ pub struct ChannelMixerNode<Monochrome, MonochromeR, MonochromeG, MonochromeB, M
|
||||
fn channel_mixer_node(
|
||||
color: Color,
|
||||
monochrome: bool,
|
||||
monochrome_r: f64,
|
||||
monochrome_g: f64,
|
||||
monochrome_b: f64,
|
||||
monochrome_c: f64,
|
||||
red_r: f64,
|
||||
red_g: f64,
|
||||
red_b: f64,
|
||||
red_c: f64,
|
||||
green_r: f64,
|
||||
green_g: f64,
|
||||
green_b: f64,
|
||||
green_c: f64,
|
||||
blue_r: f64,
|
||||
blue_g: f64,
|
||||
blue_b: f64,
|
||||
blue_c: f64,
|
||||
monochrome_r: f32,
|
||||
monochrome_g: f32,
|
||||
monochrome_b: f32,
|
||||
monochrome_c: f32,
|
||||
red_r: f32,
|
||||
red_g: f32,
|
||||
red_b: f32,
|
||||
red_c: f32,
|
||||
green_r: f32,
|
||||
green_g: f32,
|
||||
green_b: f32,
|
||||
green_c: f32,
|
||||
blue_r: f32,
|
||||
blue_g: f32,
|
||||
blue_b: f32,
|
||||
blue_c: f32,
|
||||
) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -699,42 +690,42 @@ pub struct SelectiveColorNode<Absolute, RC, RM, RY, RK, YC, YM, YY, YK, GC, GM,
|
||||
fn selective_color_node(
|
||||
color: Color,
|
||||
mode: RelativeAbsolute,
|
||||
r_c: f64,
|
||||
r_m: f64,
|
||||
r_y: f64,
|
||||
r_k: f64,
|
||||
y_c: f64,
|
||||
y_m: f64,
|
||||
y_y: f64,
|
||||
y_k: f64,
|
||||
g_c: f64,
|
||||
g_m: f64,
|
||||
g_y: f64,
|
||||
g_k: f64,
|
||||
c_c: f64,
|
||||
c_m: f64,
|
||||
c_y: f64,
|
||||
c_k: f64,
|
||||
b_c: f64,
|
||||
b_m: f64,
|
||||
b_y: f64,
|
||||
b_k: f64,
|
||||
m_c: f64,
|
||||
m_m: f64,
|
||||
m_y: f64,
|
||||
m_k: f64,
|
||||
w_c: f64,
|
||||
w_m: f64,
|
||||
w_y: f64,
|
||||
w_k: f64,
|
||||
n_c: f64,
|
||||
n_m: f64,
|
||||
n_y: f64,
|
||||
n_k: f64,
|
||||
k_c: f64,
|
||||
k_m: f64,
|
||||
k_y: f64,
|
||||
k_k: f64,
|
||||
r_c: f32,
|
||||
r_m: f32,
|
||||
r_y: f32,
|
||||
r_k: f32,
|
||||
y_c: f32,
|
||||
y_m: f32,
|
||||
y_y: f32,
|
||||
y_k: f32,
|
||||
g_c: f32,
|
||||
g_m: f32,
|
||||
g_y: f32,
|
||||
g_k: f32,
|
||||
c_c: f32,
|
||||
c_m: f32,
|
||||
c_y: f32,
|
||||
c_k: f32,
|
||||
b_c: f32,
|
||||
b_m: f32,
|
||||
b_y: f32,
|
||||
b_k: f32,
|
||||
m_c: f32,
|
||||
m_m: f32,
|
||||
m_y: f32,
|
||||
m_k: f32,
|
||||
w_c: f32,
|
||||
w_m: f32,
|
||||
w_y: f32,
|
||||
w_k: f32,
|
||||
n_c: f32,
|
||||
n_m: f32,
|
||||
n_y: f32,
|
||||
n_k: f32,
|
||||
k_c: f32,
|
||||
k_m: f32,
|
||||
k_y: f32,
|
||||
k_k: f32,
|
||||
) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -784,7 +775,7 @@ fn selective_color_node(
|
||||
// Skip this color parameter group...
|
||||
// ...if it's unchanged from the default of zero offset on all CMYK paramters, or...
|
||||
// ...if this pixel's color isn't in the range affected by this color parameter group
|
||||
if (c < f64::EPSILON && m < f64::EPSILON && y < f64::EPSILON && k < f64::EPSILON) || (!pixel_color_range(color_parameter_group)) {
|
||||
if (c < f32::EPSILON && m < f32::EPSILON && y < f32::EPSILON && k < f32::EPSILON) || (!pixel_color_range(color_parameter_group)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
@@ -816,7 +807,7 @@ pub struct OpacityNode<O> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(OpacityNode)]
|
||||
fn image_opacity(color: Color, opacity_multiplier: f64) -> Color {
|
||||
fn image_opacity(color: Color, opacity_multiplier: f32) -> Color {
|
||||
let opacity_multiplier = opacity_multiplier as f32 / 100.;
|
||||
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
|
||||
}
|
||||
@@ -829,7 +820,7 @@ pub struct PosterizeNode<P> {
|
||||
// Based on http://www.axiomx.com/posterize.htm
|
||||
// This algorithm is perfectly accurate.
|
||||
#[node_macro::node_fn(PosterizeNode)]
|
||||
fn posterize(color: Color, posterize_value: f64) -> Color {
|
||||
fn posterize(color: Color, posterize_value: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let posterize_value = posterize_value as f32;
|
||||
@@ -850,7 +841,7 @@ pub struct ExposureNode<Exposure, Offset, GammaCorrection> {
|
||||
|
||||
// Based on https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[node_macro::node_fn(ExposureNode)]
|
||||
fn exposure(color: Color, exposure: f64, offset: f64, gamma_correction: f64) -> Color {
|
||||
fn exposure(color: Color, exposure: f32, offset: f32, gamma_correction: f32) -> Color {
|
||||
let adjusted = color
|
||||
// Exposure
|
||||
.map_rgb(|c: f32| c * 2_f32.powf(exposure as f32))
|
||||
|
||||
@@ -100,7 +100,6 @@ pub struct BrushPlan {
|
||||
}
|
||||
|
||||
#[derive(Debug, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushCache {
|
||||
inner: Arc<Mutex<BrushCacheImpl>>,
|
||||
proto: bool,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, NodeMut};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComposeNode<First, Second, I> {
|
||||
first: First,
|
||||
second: Second,
|
||||
@@ -21,12 +21,20 @@ where
|
||||
second.eval(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input>
|
||||
impl<'i, 'f: 'i, 's: 'i, Input: 'i, First, Second> NodeMut<'i, Input> for ComposeNode<First, Second, Input>
|
||||
where
|
||||
First: Node<'i, Input>,
|
||||
Second: Node<'i, <First as Node<'i, Input>>::Output>,
|
||||
Second: NodeMut<'i, <First as Node<'i, Input>>::Output> + 'i,
|
||||
{
|
||||
type MutOutput = <Second as NodeMut<'i, <First as Node<'i, Input>>::Output>>::MutOutput;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput {
|
||||
let arg = self.first.eval(input);
|
||||
let second = &mut self.second;
|
||||
second.eval_mut(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input> {
|
||||
pub const fn new(first: First, second: Second) -> Self {
|
||||
ComposeNode::<First, Second, Input> { first, second, phantom: PhantomData }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct TextGenerator<Text, FontName, Size> {
|
||||
}
|
||||
|
||||
#[node_fn(TextGenerator)]
|
||||
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
||||
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f32) -> crate::vector::VectorData {
|
||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
|
||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size as f64, None))
|
||||
}
|
||||
|
||||
@@ -78,10 +78,10 @@ pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f32, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||
let pivot = DAffine2::from_translation(data.local_pivot(pivot));
|
||||
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
let data_transform = data.transform_mut();
|
||||
*data_transform = modification * (*data_transform);
|
||||
|
||||
|
||||
@@ -53,21 +53,21 @@ pub struct SetStrokeNode<Color, Weight, DashLengths, DashOffset, LineCap, LineJo
|
||||
fn set_vector_data_stroke(
|
||||
mut vector_data: VectorData,
|
||||
color: Option<Color>,
|
||||
weight: f64,
|
||||
weight: f32,
|
||||
dash_lengths: Vec<f32>,
|
||||
dash_offset: f64,
|
||||
dash_offset: f32,
|
||||
line_cap: super::style::LineCap,
|
||||
line_join: super::style::LineJoin,
|
||||
miter_limit: f64,
|
||||
miter_limit: f32,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_stroke(Stroke {
|
||||
color,
|
||||
weight,
|
||||
weight: weight as f64,
|
||||
dash_lengths,
|
||||
dash_offset,
|
||||
dash_offset: dash_offset as f64,
|
||||
line_cap,
|
||||
line_join,
|
||||
line_join_miter_limit: miter_limit,
|
||||
line_join_miter_limit: miter_limit as f64,
|
||||
});
|
||||
vector_data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user