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:
Dennis Kobert
2023-07-04 17:04:09 +02:00
committed by GitHub
parent 19318a948c
commit f2b911b358
57 changed files with 10169 additions and 845 deletions

View File

@@ -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 = []

View File

@@ -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)]

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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()));

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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))

View File

@@ -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,

View File

@@ -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 }
}

View File

@@ -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))
}

View File

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

View File

@@ -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
}