mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 00:58:12 +08:00
Drop the stranded ranked wrappers from the node signatures
This commit is contained in:
@@ -36,20 +36,17 @@ fn real_time(
|
|||||||
/// The time and date component to be produced as a number.
|
/// The time and date component to be produced as a number.
|
||||||
component: RealTimeMode,
|
component: RealTimeMode,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
let component = component.into_element();
|
|
||||||
let real_time = ctx.try_real_time().unwrap_or_default();
|
let real_time = ctx.try_real_time().unwrap_or_default();
|
||||||
|
|
||||||
// TODO: Implement proper conversion using and existing time implementation
|
// TODO: Implement proper conversion using and existing time implementation
|
||||||
let result = match component {
|
match component {
|
||||||
RealTimeMode::Utc => real_time,
|
RealTimeMode::Utc => real_time,
|
||||||
RealTimeMode::Year => (real_time / DAY / 365.25).floor() + 1970., // TODO: Factor in a chosen timezone
|
RealTimeMode::Year => (real_time / DAY / 365.25).floor() + 1970., // TODO: Factor in a chosen timezone
|
||||||
RealTimeMode::Hour => (real_time / 1000. / 3600.).floor() % 24., // TODO: Factor in a chosen timezone
|
RealTimeMode::Hour => (real_time / 1000. / 3600.).floor() % 24., // TODO: Factor in a chosen timezone
|
||||||
RealTimeMode::Minute => (real_time / 1000. / 60.).floor() % 60., // TODO: Factor in a chosen timezone
|
RealTimeMode::Minute => (real_time / 1000. / 60.).floor() % 60., // TODO: Factor in a chosen timezone
|
||||||
RealTimeMode::Second => (real_time / 1000.).floor() % 60.,
|
RealTimeMode::Second => (real_time / 1000.).floor() % 60.,
|
||||||
RealTimeMode::Millisecond => real_time % 1000.,
|
RealTimeMode::Millisecond => real_time % 1000.,
|
||||||
};
|
}
|
||||||
|
|
||||||
Item::new_from_element(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces the time, in seconds on the timeline, since the beginning of animation playback.
|
/// Produces the time, in seconds on the timeline, since the beginning of animation playback.
|
||||||
@@ -61,7 +58,7 @@ fn animation_time(
|
|||||||
#[unit("/sec")]
|
#[unit("/sec")]
|
||||||
rate: f64,
|
rate: f64,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
Item::new_from_element(ctx.try_animation_time().unwrap_or_default() * *rate.element())
|
ctx.try_animation_time().unwrap_or_default() * rate
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
@@ -94,6 +91,7 @@ fn quantize_real_time<T>(
|
|||||||
Context -> List<Color>,
|
Context -> List<Color>,
|
||||||
Context -> List<Gradient>,
|
Context -> List<Gradient>,
|
||||||
Context -> List<Artboard>,
|
Context -> List<Artboard>,
|
||||||
|
Context -> (),
|
||||||
)]
|
)]
|
||||||
value: impl Node<Context<'_>, Output = T>,
|
value: impl Node<Context<'_>, Output = T>,
|
||||||
#[default(1)]
|
#[default(1)]
|
||||||
@@ -102,7 +100,6 @@ fn quantize_real_time<T>(
|
|||||||
) -> GPoll<T> {
|
) -> GPoll<T> {
|
||||||
let time = ctx.try_real_time().unwrap_or_default();
|
let time = ctx.try_real_time().unwrap_or_default();
|
||||||
let time = time / 1000.;
|
let time = time / 1000.;
|
||||||
let quantum = quantum.into_element();
|
|
||||||
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
|
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
|
||||||
if !quantized_time.is_finite() {
|
if !quantized_time.is_finite() {
|
||||||
quantized_time = time;
|
quantized_time = time;
|
||||||
@@ -142,6 +139,7 @@ fn quantize_animation_time<T>(
|
|||||||
Context -> List<Color>,
|
Context -> List<Color>,
|
||||||
Context -> List<Gradient>,
|
Context -> List<Gradient>,
|
||||||
Context -> List<Artboard>,
|
Context -> List<Artboard>,
|
||||||
|
Context -> (),
|
||||||
)]
|
)]
|
||||||
value: impl Node<Context<'_>, Output = T>,
|
value: impl Node<Context<'_>, Output = T>,
|
||||||
#[default(1)]
|
#[default(1)]
|
||||||
@@ -149,7 +147,6 @@ fn quantize_animation_time<T>(
|
|||||||
quantum: f64,
|
quantum: f64,
|
||||||
) -> GPoll<T> {
|
) -> GPoll<T> {
|
||||||
let time = ctx.try_animation_time().unwrap_or_default();
|
let time = ctx.try_animation_time().unwrap_or_default();
|
||||||
let quantum = quantum.into_element();
|
|
||||||
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
|
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
|
||||||
if !quantized_time.is_finite() {
|
if !quantized_time.is_finite() {
|
||||||
quantized_time = time;
|
quantized_time = time;
|
||||||
@@ -161,7 +158,7 @@ fn quantize_animation_time<T>(
|
|||||||
/// Produces the current position of the user's pointer within the document canvas.
|
/// Produces the current position of the user's pointer within the document canvas.
|
||||||
#[node_macro::node(category("Animation"))]
|
#[node_macro::node(category("Animation"))]
|
||||||
fn pointer_position(ctx: impl Ctx + ExtractPointerPosition) -> DVec2 {
|
fn pointer_position(ctx: impl Ctx + ExtractPointerPosition) -> DVec2 {
|
||||||
Item::new_from_element(ctx.try_pointer_position().unwrap_or_default())
|
ctx.try_pointer_position().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These nodes require more sophisticated algorithms for giving the correct result
|
// TODO: These nodes require more sophisticated algorithms for giving the correct result
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use core_types::Ctx;
|
use core_types::Ctx;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use raster_types::{CPU, Raster};
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Logs the input value to the console and passes it through unchanged.
|
/// Meant for debugging purposes, not general use. Logs the input value to the console and passes it through unchanged.
|
||||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||||
|
|||||||
+167
-100
@@ -1,5 +1,6 @@
|
|||||||
use core_types::attribute::Attr;
|
use core_types::attribute::Attr;
|
||||||
use core_types::gpoll::{GraphError, Interrupt};
|
use core_types::gpoll::{GraphError, Interrupt};
|
||||||
|
use core_types::list::List;
|
||||||
use core_types::registry::types::{Fraction, Percentage, PixelSize};
|
use core_types::registry::types::{Fraction, Percentage, PixelSize};
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::{Color, Ctx, ExtractIndex, InjectIndex, num_traits};
|
use core_types::{Color, Ctx, ExtractIndex, InjectIndex, num_traits};
|
||||||
@@ -9,9 +10,12 @@ use math_parser::ast;
|
|||||||
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
||||||
use math_parser::value::{Number, Value};
|
use math_parser::value::{Number, Value};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use std::ops::{Add, Div, Mul, Rem, Sub};
|
use std::ops::{Add, Mul, Rem, Sub};
|
||||||
use vector_types::GradientStops;
|
use vector_types::markers::{
|
||||||
use vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod as SpreadMethodAttr};
|
GradientCyclic as GradientCyclicAttr, GradientForm as GradientFormAttr, GradientHueDirection as GradientHueDirectionAttr, GradientInterpolation as GradientInterpolationAttr,
|
||||||
|
GradientSpace as GradientSpaceAttr, GradientSpread as GradientSpreadAttr,
|
||||||
|
};
|
||||||
|
use vector_types::{Gradient, GradientSettings};
|
||||||
|
|
||||||
/// The struct that stores the context for the maths parser.
|
/// The struct that stores the context for the maths parser.
|
||||||
/// This is currently just limited to supplying `a` and `b` until we add better node graph support and UI for variadic inputs.
|
/// This is currently just limited to supplying `a` and `b` until we add better node graph support and UI for variadic inputs.
|
||||||
@@ -71,12 +75,10 @@ fn math<T: num_traits::float::Float>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Value::Number(num) = value;
|
let Value::Number(num) = value;
|
||||||
let result = match num {
|
match num {
|
||||||
Number::Real(val) => T::from(val).unwrap(),
|
Number::Real(val) => T::from(val).unwrap(),
|
||||||
Number::Complex(c) => T::from(c.re).unwrap(),
|
Number::Complex(c) => T::from(c.re).unwrap(),
|
||||||
};
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The addition operation (`+`) calculates the sum of two scalar numbers or vec2s.
|
/// The addition operation (`+`) calculates the sum of two scalar numbers or vec2s.
|
||||||
@@ -229,10 +231,7 @@ fn modulo<A: Rem<B, Output: Add<B, Output: Rem<B, Output = A::Output>>>, B: Copy
|
|||||||
#[default(true)]
|
#[default(true)]
|
||||||
always_positive: bool,
|
always_positive: bool,
|
||||||
) -> <A as Rem<B>>::Output {
|
) -> <A as Rem<B>>::Output {
|
||||||
let (modulus, always_positive) = (modulus, always_positive);
|
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
|
||||||
|
|
||||||
let result = if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus };
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Exponent<Rhs = Self> {
|
pub trait Exponent<Rhs = Self> {
|
||||||
@@ -441,10 +440,7 @@ fn sine<T: Componentwise>(
|
|||||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||||
radians: bool,
|
radians: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let radians = radians;
|
theta.componentwise(|theta| if radians { theta.sin() } else { theta.to_radians().sin() })
|
||||||
|
|
||||||
let result = theta.componentwise(|theta| if radians { theta.sin() } else { theta.to_radians().sin() });
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cosine trigonometric function (`cos`) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
/// The cosine trigonometric function (`cos`) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||||
@@ -459,10 +455,7 @@ fn cosine<T: Componentwise>(
|
|||||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||||
radians: bool,
|
radians: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let radians = radians;
|
theta.componentwise(|theta| if radians { theta.cos() } else { theta.to_radians().cos() })
|
||||||
|
|
||||||
let result = theta.componentwise(|theta| if radians { theta.cos() } else { theta.to_radians().cos() });
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tangent trigonometric function (`tan`) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
/// The tangent trigonometric function (`tan`) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||||
@@ -477,10 +470,7 @@ fn tangent<T: Componentwise>(
|
|||||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||||
radians: bool,
|
radians: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let radians = radians;
|
theta.componentwise(|theta| if radians { theta.tan() } else { theta.to_radians().tan() })
|
||||||
|
|
||||||
let result = theta.componentwise(|theta| if radians { theta.tan() } else { theta.to_radians().tan() });
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inverse sine trigonometric function (`asin`) calculates the angle whose sine is the input value.
|
/// The inverse sine trigonometric function (`asin`) calculates the angle whose sine is the input value.
|
||||||
@@ -494,8 +484,7 @@ fn sine_inverse<T: num_traits::float::Float>(
|
|||||||
radians: bool,
|
radians: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).asin();
|
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).asin();
|
||||||
let result = if radians { angle } else { angle.to_degrees() };
|
if radians { angle } else { angle.to_degrees() }
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inverse cosine trigonometric function (`acos`) calculates the angle whose cosine is the input value.
|
/// The inverse cosine trigonometric function (`acos`) calculates the angle whose cosine is the input value.
|
||||||
@@ -509,8 +498,7 @@ fn cosine_inverse<T: num_traits::float::Float>(
|
|||||||
radians: bool,
|
radians: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).acos();
|
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).acos();
|
||||||
let result = if radians { angle } else { angle.to_degrees() };
|
if radians { angle } else { angle.to_degrees() }
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inverse tangent trigonometric function (`atan` or `atan2`, depending on input type) calculates:
|
/// The inverse tangent trigonometric function (`atan` or `atan2`, depending on input type) calculates:
|
||||||
@@ -579,8 +567,6 @@ fn remap<U: num_traits::float::Float>(
|
|||||||
/// Whether to constrain the result within the output range instead of extrapolating beyond its bounds.
|
/// Whether to constrain the result within the output range instead of extrapolating beyond its bounds.
|
||||||
clamped: bool,
|
clamped: bool,
|
||||||
) -> U {
|
) -> U {
|
||||||
let (input_min, input_max, output_min, output_max) = (input_min, input_max, output_min, output_max);
|
|
||||||
|
|
||||||
let input_range = input_max - input_min;
|
let input_range = input_max - input_min;
|
||||||
|
|
||||||
// Handle division by zero
|
// Handle division by zero
|
||||||
@@ -593,7 +579,7 @@ fn remap<U: num_traits::float::Float>(
|
|||||||
|
|
||||||
let result = output_min + normalized * output_range;
|
let result = output_min + normalized * output_range;
|
||||||
|
|
||||||
let result = if clamped {
|
if clamped {
|
||||||
// Handle both normal and inverted ranges, since we want to allow the user to use this node to also reverse a range.
|
// Handle both normal and inverted ranges, since we want to allow the user to use this node to also reverse a range.
|
||||||
if output_min <= output_max {
|
if output_min <= output_max {
|
||||||
result.clamp(output_min, output_max)
|
result.clamp(output_min, output_max)
|
||||||
@@ -602,9 +588,7 @@ fn remap<U: num_traits::float::Float>(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result
|
result
|
||||||
};
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Lerp {
|
trait Lerp {
|
||||||
@@ -649,14 +633,13 @@ fn lerp<T: Lerp>(
|
|||||||
let factor = if clamped { factor.clamp(0., 1.) } else { factor };
|
let factor = if clamped { factor.clamp(0., 1.) } else { factor };
|
||||||
|
|
||||||
// Exact endpoint factors pass the endpoint through untouched, since the unused operand would otherwise contaminate the weighted sum (NaN or infinity times 0 is NaN)
|
// Exact endpoint factors pass the endpoint through untouched, since the unused operand would otherwise contaminate the weighted sum (NaN or infinity times 0 is NaN)
|
||||||
let result = if factor == 0. {
|
if factor == 0. {
|
||||||
start
|
start
|
||||||
} else if factor == 1. {
|
} else if factor == 1. {
|
||||||
end
|
end
|
||||||
} else {
|
} else {
|
||||||
start.lerp(end, factor)
|
start.lerp(end, factor)
|
||||||
};
|
}
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The random function (`rand`) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
/// The random function (`rand`) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
||||||
@@ -674,9 +657,8 @@ fn random(
|
|||||||
) -> f64 {
|
) -> f64 {
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
||||||
let result = rng.random::<f64>();
|
let result = rng.random::<f64>();
|
||||||
let (min, max) = (min, max);
|
|
||||||
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
||||||
(result * (max - min) + min)
|
result * (max - min) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.
|
// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.
|
||||||
@@ -791,7 +773,7 @@ fn sign<T: Componentwise>(
|
|||||||
#[implementations(f64, f32, DVec2)]
|
#[implementations(f64, f32, DVec2)]
|
||||||
value: T,
|
value: T,
|
||||||
) -> T {
|
) -> T {
|
||||||
let result = value.componentwise(|value| {
|
value.componentwise(|value| {
|
||||||
if value > 0. {
|
if value > 0. {
|
||||||
1.
|
1.
|
||||||
} else if value < 0. {
|
} else if value < 0. {
|
||||||
@@ -799,8 +781,7 @@ fn sign<T: Componentwise>(
|
|||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MinMax<Rhs = Self> {
|
pub trait MinMax<Rhs = Self> {
|
||||||
@@ -924,8 +905,6 @@ fn clamp<A: MinMax<B>, B: MinMax<Output = B> + Clone>(
|
|||||||
where
|
where
|
||||||
<A as MinMax<B>>::Output: MinMax<B, Output = <A as MinMax<B>>::Output>,
|
<A as MinMax<B>>::Output: MinMax<B, Output = <A as MinMax<B>>::Output>,
|
||||||
{
|
{
|
||||||
let (min, max) = (min, max);
|
|
||||||
|
|
||||||
let (min, max) = (min.clone().minimum(max.clone()), min.maximum(max));
|
let (min, max) = (min.clone().minimum(max.clone()), min.maximum(max));
|
||||||
value.maximum(min).minimum(max)
|
value.maximum(min).minimum(max)
|
||||||
}
|
}
|
||||||
@@ -941,17 +920,13 @@ fn greatest_common_divisor<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32
|
|||||||
#[implementations(u32, u64, i32)]
|
#[implementations(u32, u64, i32)]
|
||||||
other_value: T,
|
other_value: T,
|
||||||
) -> T {
|
) -> T {
|
||||||
let other_value = other_value;
|
if value == T::zero() {
|
||||||
|
|
||||||
let result = if value == T::zero() {
|
|
||||||
other_value
|
other_value
|
||||||
} else if other_value == T::zero() {
|
} else if other_value == T::zero() {
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
binary_gcd(value, other_value)
|
binary_gcd(value, other_value)
|
||||||
};
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The least common multiple (LCM) calculates the smallest positive integer that is a multiple of both of the two input numbers.
|
/// The least common multiple (LCM) calculates the smallest positive integer that is a multiple of both of the two input numbers.
|
||||||
@@ -1011,7 +986,7 @@ fn binary_gcd<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32> + std::ops:
|
|||||||
/// Adds together all the numbers in the input list, producing their total.
|
/// Adds together all the numbers in the input list, producing their total.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn sum(_: impl Ctx, values: List<f64>) -> f64 {
|
fn sum(_: impl Ctx, values: List<f64>) -> f64 {
|
||||||
(values.iter_element_values().sum())
|
values.iter_element_values().sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Averages all the numbers in the input list. An empty list gives 0.
|
/// Averages all the numbers in the input list. An empty list gives 0.
|
||||||
@@ -1020,31 +995,31 @@ fn average(_: impl Ctx, values: List<f64>) -> f64 {
|
|||||||
let count = values.len();
|
let count = values.len();
|
||||||
let average = if count == 0 { 0. } else { values.iter_element_values().sum::<f64>() / count as f64 };
|
let average = if count == 0 { 0. } else { values.iter_element_values().sum::<f64>() / count as f64 };
|
||||||
|
|
||||||
(average)
|
average
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the smallest number in the input list. An empty list gives 0.
|
/// Gives the smallest number in the input list. An empty list gives 0.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn minimum(_: impl Ctx, values: List<f64>) -> f64 {
|
fn minimum(_: impl Ctx, values: List<f64>) -> f64 {
|
||||||
(values.iter_element_values().copied().reduce(f64::min).unwrap_or_default())
|
values.iter_element_values().copied().reduce(f64::min).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the largest number in the input list. An empty list gives 0.
|
/// Gives the largest number in the input list. An empty list gives 0.
|
||||||
#[node_macro::node(category("Math: Numeric"))]
|
#[node_macro::node(category("Math: Numeric"))]
|
||||||
fn maximum(_: impl Ctx, values: List<f64>) -> f64 {
|
fn maximum(_: impl Ctx, values: List<f64>) -> f64 {
|
||||||
(values.iter_element_values().copied().reduce(f64::max).unwrap_or_default())
|
values.iter_element_values().copied().reduce(f64::max).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outputs true if at least one value in the input list is true. An empty list gives false.
|
/// Outputs true if at least one value in the input list is true. An empty list gives false.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn any(_: impl Ctx, values: List<bool>) -> bool {
|
fn any(_: impl Ctx, values: List<bool>) -> bool {
|
||||||
(values.iter_element_values().any(|&value| value))
|
values.iter_element_values().any(|&value| value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outputs true only if every value in the input list is true. An empty list gives true.
|
/// Outputs true only if every value in the input list is true. An empty list gives true.
|
||||||
#[node_macro::node(category("Math: Logic"))]
|
#[node_macro::node(category("Math: Logic"))]
|
||||||
fn all(_: impl Ctx, values: List<bool>) -> bool {
|
fn all(_: impl Ctx, values: List<bool>) -> bool {
|
||||||
(values.iter_element_values().all(|&value| value))
|
values.iter_element_values().all(|&value| value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The less-than operation (`<`) compares two values and returns true if the first value is less than the second, or false if it is not.
|
/// The less-than operation (`<`) compares two values and returns true if the first value is less than the second, or false if it is not.
|
||||||
@@ -1061,10 +1036,7 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
|
|||||||
/// Uses the less-than-or-equal operation (`<=`) instead of the less-than operation (`<`).
|
/// Uses the less-than-or-equal operation (`<=`) instead of the less-than operation (`<`).
|
||||||
or_equal: bool,
|
or_equal: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let other_value = other_value;
|
if or_equal { value <= other_value } else { value < other_value }
|
||||||
|
|
||||||
let result = if or_equal { value <= other_value } else { value < other_value };
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The greater-than operation (`>`) compares two values and returns true if the first value is greater than the second, or false if it is not.
|
/// The greater-than operation (`>`) compares two values and returns true if the first value is greater than the second, or false if it is not.
|
||||||
@@ -1081,10 +1053,7 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
|
|||||||
/// Uses the greater-than-or-equal operation (`>=`) instead of the greater-than operation (`>`).
|
/// Uses the greater-than-or-equal operation (`>=`) instead of the greater-than operation (`>`).
|
||||||
or_equal: bool,
|
or_equal: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let other_value = other_value;
|
if or_equal { value >= other_value } else { value > other_value }
|
||||||
|
|
||||||
let result = if or_equal { value >= other_value } else { value > other_value };
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The equality operation (`==`, `XNOR`) compares two values and returns true if they are equal, or false if they are not.
|
/// The equality operation (`==`, `XNOR`) compares two values and returns true if they are equal, or false if they are not.
|
||||||
@@ -1098,9 +1067,7 @@ fn equals<T: std::cmp::PartialEq<T>>(
|
|||||||
#[implementations(f64, f32, u32, DVec2, bool, String)]
|
#[implementations(f64, f32, u32, DVec2, bool, String)]
|
||||||
other_value: T,
|
other_value: T,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let value = value;
|
other_value == value
|
||||||
|
|
||||||
(other_value == value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inequality operation (`!=`, `XOR`) compares two values and returns true if they are not equal, or false if they are.
|
/// The inequality operation (`!=`, `XOR`) compares two values and returns true if they are not equal, or false if they are.
|
||||||
@@ -1114,9 +1081,7 @@ fn not_equals<T: std::cmp::PartialEq<T>>(
|
|||||||
#[implementations(f64, f32, u32, DVec2, bool, String)]
|
#[implementations(f64, f32, u32, DVec2, bool, String)]
|
||||||
other_value: T,
|
other_value: T,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let value = value;
|
other_value != value
|
||||||
|
|
||||||
(other_value != value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logical OR operation (`||`) returns true if either of the two inputs are true, or false if both are false.
|
/// The logical OR operation (`||`) returns true if either of the two inputs are true, or false if both are false.
|
||||||
@@ -1237,42 +1202,152 @@ fn hex_to_color(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, hex_code: Str
|
|||||||
|
|
||||||
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
|
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops {
|
fn gradient_value(_: impl Ctx, _primary: (), #[default(Color::BLACK, Color::WHITE)] gradient: Gradient) -> Gradient {
|
||||||
gradient
|
gradient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the type (linear or radial) of each gradient in the input list.
|
/// Sets the form (linear or radial) of each gradient in the input list.
|
||||||
#[node_macro::node(category("Color"))]
|
#[node_macro::node(category("Gradient"))]
|
||||||
fn gradient_type(_: impl Ctx, gradient: GradientStops, gradient_type: vector_types::GradientType) -> (GradientStops, Attr<GradientTypeAttr>) {
|
fn gradient_form(_: impl Ctx, gradient: Gradient, gradient_form: vector_types::GradientForm) -> (Gradient, Attr<GradientFormAttr>) {
|
||||||
(gradient, Attr(gradient_type))
|
(gradient, Attr(gradient_form))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets how each gradient in the input list extends past its endpoints: Pad, Reflect, or Repeat.
|
/// Sets how each gradient in the input list extends past its endpoints: Pad, Reflect, Repeat, or Clear.
|
||||||
#[node_macro::node(category("Color"))]
|
#[node_macro::node(category("Gradient"))]
|
||||||
fn spread_method(_: impl Ctx, gradient: GradientStops, spread_method: vector_types::GradientSpreadMethod) -> (GradientStops, Attr<SpreadMethodAttr>) {
|
fn gradient_spread(_: impl Ctx, gradient: Gradient, gradient_spread: vector_types::GradientSpread) -> (Gradient, Attr<GradientSpreadAttr>) {
|
||||||
(gradient, Attr(spread_method))
|
(gradient, Attr(gradient_spread))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
|
/// Sets the color space in which each gradient in the input list interpolates between its stops.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_space(_: impl Ctx, gradient: Gradient, space: vector_types::GradientSpace) -> (Gradient, Attr<GradientSpaceAttr>) {
|
||||||
|
(gradient, Attr(space))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the path each gradient in the input list interpolates along, deciding whether it jumps, turns corners, or flows smoothly through its stops.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_interpolation(_: impl Ctx, gradient: Gradient, interpolation: vector_types::GradientInterpolation) -> (Gradient, Attr<GradientInterpolationAttr>) {
|
||||||
|
(gradient, Attr(interpolation))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether each gradient in the input list treats its stops as a cycle, interpolating from the last stop back around to the first.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_cyclic(_: impl Ctx, gradient: Gradient, cyclic: bool) -> (Gradient, Attr<GradientCyclicAttr>) {
|
||||||
|
(gradient, Attr(cyclic))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets which way around the hue wheel each gradient in the input list interpolates, for polar color spaces.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_hue_direction(_: impl Ctx, gradient: Gradient, hue_direction: vector_types::GradientHueDirection) -> (Gradient, Attr<GradientHueDirectionAttr>) {
|
||||||
|
(gradient, Attr(hue_direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the position of each of a gradient's stops, a factor from 0 to 1 along the gradient.
|
||||||
|
///
|
||||||
|
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each stop to its default evenly spaced position.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_positions(_: impl Ctx, mut gradient: Gradient, positions: List<f64>) -> Gradient {
|
||||||
|
let positions: Vec<f64> = positions.iter_element_values().copied().collect();
|
||||||
|
gradient.set_positions(&positions);
|
||||||
|
gradient
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skews how rapidly the color flows across each interval between color stops, bunching up the transition toward one end instead of progressing uniformly. Each value places the halfway color within its corresponding interval, measured as a fraction of the distance (0 to 1) between the adjacent stops. A 0.5 midpoint keeps a uniform transition rate through the interval.
|
||||||
|
///
|
||||||
|
/// Non-cyclic gradients have no interval following the last stop, meaning the midpoint is ignored in that position.
|
||||||
|
///
|
||||||
|
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each midpoint to its default of 0.5.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_midpoints(_: impl Ctx, mut gradient: Gradient, midpoints: List<f64>) -> Gradient {
|
||||||
|
let midpoints: Vec<f64> = midpoints.iter_element_values().copied().collect();
|
||||||
|
gradient.set_midpoints(&midpoints);
|
||||||
|
gradient
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reverses the order of each gradient's stops, moving the color at the start of the ramp to the end and vice versa.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_reverse(_: impl Ctx, (gradient, cyclic): (Gradient, Attr<GradientCyclicAttr>)) -> (Gradient, Attr<GradientCyclicAttr>) {
|
||||||
|
// Master reads the cyclic flag off the item; ours rides the gradient's own lane.
|
||||||
|
let cyclic = *cyclic;
|
||||||
|
(gradient.reversed(cyclic), Attr(cyclic))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shifts every stop along each gradient's ramp, sliding the colors within the gradient without moving the gradient itself.
|
||||||
|
///
|
||||||
|
/// The fraction is measured against the whole ramp. A cyclic gradient spins, wrapping past the end back around to the start so 1 is a full turn that lands where it began. A gradient that isn't cyclic has no loop to spin around, so its stops slide off the end and keep going, leaving the visible ramp to blend between whichever colors still span it.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_shift(
|
||||||
|
_: impl Ctx,
|
||||||
|
(mut gradient, cyclic): (Gradient, Attr<GradientCyclicAttr>),
|
||||||
|
#[range]
|
||||||
|
#[soft(-1..1)]
|
||||||
|
fraction: f64,
|
||||||
|
) -> (Gradient, Attr<GradientCyclicAttr>) {
|
||||||
|
// Master reads the cyclic flag off the item; ours rides the gradient's own lane.
|
||||||
|
let cyclic = *cyclic;
|
||||||
|
gradient.shift_positions(fraction, cyclic);
|
||||||
|
(gradient, Attr(cyclic))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stretches or squeezes the spacing of each gradient's stops around a pivot, spreading the colors within the gradient without moving the gradient itself.
|
||||||
|
///
|
||||||
|
/// The factor multiplies every stop's distance from the pivot, so 2 spreads the ramp over twice its span while 0.5 packs it into half. A negative factor mirrors the stops across the pivot, reversing the order of the colors.
|
||||||
|
///
|
||||||
|
/// The pivot is the one point that stays put, measured against the whole ramp from 0 at the start to 1 at the end.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_stretch(
|
||||||
|
_: impl Ctx,
|
||||||
|
(mut gradient, cyclic): (Gradient, Attr<GradientCyclicAttr>),
|
||||||
|
#[default(1.)]
|
||||||
|
#[unit("x")]
|
||||||
|
factor: f64,
|
||||||
|
#[default(0.5)]
|
||||||
|
#[range]
|
||||||
|
#[soft(0..1)]
|
||||||
|
pivot: f64,
|
||||||
|
) -> (Gradient, Attr<GradientCyclicAttr>) {
|
||||||
|
// Master reads the cyclic flag off the item; ours rides the gradient's own lane.
|
||||||
|
let cyclic = *cyclic;
|
||||||
|
gradient.stretch_positions(factor, pivot, cyclic);
|
||||||
|
(gradient, Attr(cyclic))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). Positions beyond that range follow the gradient's `gradient_spread` attribute: Pad (default), Reflect, Repeat, or Clear. Colors between stops interpolate in the gradient's `gradient_space` color space.
|
||||||
#[node_macro::node(category("Color"))]
|
#[node_macro::node(category("Color"))]
|
||||||
fn sample_gradient(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, _primary: (), gradient: IList<GradientStops>, position: Fraction) -> Result<IList<Color>, Interrupt> {
|
fn evaluate_gradient(
|
||||||
|
ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
|
_primary: (),
|
||||||
|
#[default(Color::BLACK, Color::WHITE)] gradient: IList<Gradient>,
|
||||||
|
#[range]
|
||||||
|
#[soft(0..1)]
|
||||||
|
position: f64,
|
||||||
|
) -> Result<IList<Color>, Interrupt> {
|
||||||
// An unwired gradient serves an empty level: no color
|
// An unwired gradient serves an empty level: no color
|
||||||
if gradient.is_empty() || ctx.index() != 0 {
|
if gradient.is_empty() || ctx.index() != 0 {
|
||||||
return Err(GraphError::past_end().into());
|
return Err(GraphError::past_end().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = position.clamp(0., 1.);
|
// Master reads the whole-ramp settings off the item; ours ride the gradient's own lane.
|
||||||
Ok(gradient.element_ref(0).evaluate(position))
|
let lane = gradient.lane(0);
|
||||||
|
let settings = GradientSettings {
|
||||||
|
spread: lane.attr::<GradientSpreadAttr>(),
|
||||||
|
cyclic: lane.attr::<GradientCyclicAttr>(),
|
||||||
|
space: lane.attr::<GradientSpaceAttr>(),
|
||||||
|
hue_direction: lane.attr::<GradientHueDirectionAttr>(),
|
||||||
|
interpolation: lane.attr::<GradientInterpolationAttr>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(gradient.element_ref(0).evaluate(position, settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
||||||
#[node_macro::node(category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100., 100.)] resolution: PixelSize) -> Footprint {
|
fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100., 100.)] resolution: PixelSize) -> Footprint {
|
||||||
(Footprint {
|
Footprint {
|
||||||
transform: transform,
|
transform,
|
||||||
resolution: resolution.max(DVec2::ONE).as_uvec2(),
|
resolution: resolution.max(DVec2::ONE).as_uvec2(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Composes a vec2 from its X and Y components.
|
/// Composes a vec2 from its X and Y components.
|
||||||
@@ -1289,7 +1364,7 @@ fn combine_vec2(
|
|||||||
#[expose]
|
#[expose]
|
||||||
y: f64,
|
y: f64,
|
||||||
) -> DVec2 {
|
) -> DVec2 {
|
||||||
(DVec2::new(x, y))
|
DVec2::new(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths.
|
/// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths.
|
||||||
@@ -1308,15 +1383,11 @@ fn dot_product(
|
|||||||
/// Whether to normalize both input vec2s so the calculation ranges in `[-1, 1]` by considering only their degree of directional alignment.
|
/// Whether to normalize both input vec2s so the calculation ranges in `[-1, 1]` by considering only their degree of directional alignment.
|
||||||
normalize: bool,
|
normalize: bool,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
let other_value = other_value;
|
if normalize {
|
||||||
|
|
||||||
let result = if normalize {
|
|
||||||
value.normalize_or_zero().dot(other_value.normalize_or_zero())
|
value.normalize_or_zero().dot(other_value.normalize_or_zero())
|
||||||
} else {
|
} else {
|
||||||
value.dot(other_value)
|
value.dot(other_value)
|
||||||
};
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cross product operation (`×`) calculates the signed area of the parallelogram formed by a vec2 pair.
|
/// The cross product operation (`×`) calculates the signed area of the parallelogram formed by a vec2 pair.
|
||||||
@@ -1348,15 +1419,12 @@ fn angle_between(
|
|||||||
/// Whether the resulting angle should be given in radians instead of degrees.
|
/// Whether the resulting angle should be given in radians instead of degrees.
|
||||||
radians: bool,
|
radians: bool,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
let direction_to = direction_to;
|
|
||||||
|
|
||||||
if direction_from == DVec2::ZERO || direction_to == DVec2::ZERO {
|
if direction_from == DVec2::ZERO || direction_to == DVec2::ZERO {
|
||||||
return 0.;
|
return 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
let angle = direction_from.angle_to(direction_to);
|
let angle = direction_from.angle_to(direction_to);
|
||||||
let result = if radians { angle } else { angle.to_degrees() };
|
if radians { angle } else { angle.to_degrees() }
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToPosition {
|
pub trait ToPosition {
|
||||||
@@ -1391,8 +1459,7 @@ fn angle_to<T: ToPosition, U: ToPosition>(
|
|||||||
let to = position_to.to_position();
|
let to = position_to.to_position();
|
||||||
let delta = to - from;
|
let delta = to - from;
|
||||||
let angle = delta.y.atan2(delta.x);
|
let angle = delta.y.atan2(delta.x);
|
||||||
let result = if radians { angle } else { angle.to_degrees() };
|
if radians { angle } else { angle.to_degrees() }
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow it represents.
|
/// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow it represents.
|
||||||
@@ -1433,9 +1500,9 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn length_function() {
|
pub fn magnitude_function() {
|
||||||
let vector = DVec2::new(3., 4.);
|
let vector = DVec2::new(3., 4.);
|
||||||
assert_eq!(length(&(), vector), 5.);
|
assert_eq!(magnitude(&(), vector), 5.);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ use core::fmt::Debug;
|
|||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
use no_std_types::color::{Color, linear_to_srgb, srgb_to_linear};
|
use no_std_types::color::{Color, linear_to_srgb, srgb_to_linear};
|
||||||
use no_std_types::context::Ctx;
|
use no_std_types::context::Ctx;
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use no_std_types::list::ShaderItem as Item;
|
|
||||||
use no_std_types::registry::types::{AngleF32, PercentageF32, SignedPercentageF32};
|
use no_std_types::registry::types::{AngleF32, PercentageF32, SignedPercentageF32};
|
||||||
use node_macro::BufferStruct;
|
use node_macro::BufferStruct;
|
||||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||||
@@ -55,16 +53,13 @@ fn luminance<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
luminance_calc: Item<LuminanceCalculation>,
|
luminance_calc: LuminanceCalculation,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
let luminance_calc = luminance_calc.into_element();
|
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
let luminance = match luminance_calc {
|
let luminance = match luminance_calc {
|
||||||
LuminanceCalculation::SRGB => color.luminance_rec_709(),
|
LuminanceCalculation::SRGB => color.luminance_rec_709(),
|
||||||
LuminanceCalculation::Perceptual => color.luminance_perceptual(),
|
LuminanceCalculation::Perceptual => color.luminance_perceptual(),
|
||||||
@@ -83,23 +78,19 @@ fn gamma_correction<T: Adjust<Color> + Clone + Send + Sync + no_std_types::conte
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
#[default(2.2)]
|
#[default(2.2)]
|
||||||
#[range]
|
#[range]
|
||||||
#[hard(0.0001..)]
|
#[hard(0.0001..)]
|
||||||
#[soft(0.01..10)]
|
#[soft(0.01..10)]
|
||||||
gamma: Item<f32>,
|
gamma: f32,
|
||||||
inverse: Item<bool>,
|
inverse: bool,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
|
||||||
let gamma = gamma.into_element();
|
|
||||||
let inverse = inverse.into_element();
|
|
||||||
|
|
||||||
let exponent = if inverse { 1. / gamma } else { gamma };
|
let exponent = if inverse { 1. / gamma } else { gamma };
|
||||||
input.element_mut().adjust(|color| color.apply_gamma_exponent(exponent));
|
input.adjust(|color| color.apply_gamma_exponent(exponent));
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,16 +100,13 @@ fn extract_channel<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
channel: Item<RedGreenBlueAlpha>,
|
channel: RedGreenBlueAlpha,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
let channel = channel.into_element();
|
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
let extracted_value = match channel {
|
let extracted_value = match channel {
|
||||||
RedGreenBlueAlpha::Red => color.r(),
|
RedGreenBlueAlpha::Red => color.r(),
|
||||||
RedGreenBlueAlpha::Green => color.g(),
|
RedGreenBlueAlpha::Green => color.g(),
|
||||||
@@ -136,13 +124,12 @@ fn make_opaque<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::C
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
if color.a() == 0. {
|
if color.a() == 0. {
|
||||||
return color.with_alpha(1.);
|
return color.with_alpha(1.);
|
||||||
}
|
}
|
||||||
@@ -159,17 +146,13 @@ fn brightness_contrast_classic<T: Adjust<Color> + Clone + Send + Sync + no_std_t
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
brightness: Item<SignedPercentageF32>,
|
brightness: SignedPercentageF32,
|
||||||
contrast: Item<SignedPercentageF32>,
|
contrast: SignedPercentageF32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
|
||||||
let brightness = brightness.into_element();
|
|
||||||
let contrast = contrast.into_element();
|
|
||||||
|
|
||||||
let brightness = brightness / 255.;
|
let brightness = brightness / 255.;
|
||||||
|
|
||||||
let contrast = contrast / 100.;
|
let contrast = contrast / 100.;
|
||||||
@@ -177,7 +160,7 @@ fn brightness_contrast_classic<T: Adjust<Color> + Clone + Send + Sync + no_std_t
|
|||||||
|
|
||||||
let offset = brightness * contrast + brightness - contrast / 2.;
|
let offset = brightness * contrast + brightness - contrast / 2.;
|
||||||
|
|
||||||
input.element_mut().adjust(|color| color.map_gamma_rgb(|c| (c + c * contrast + offset).clamp(0., 1.)));
|
input.adjust(|color| color.map_gamma_rgb(|c| (c + c * contrast + offset).clamp(0., 1.)));
|
||||||
|
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
@@ -194,23 +177,18 @@ fn brightness_contrast<T: Adjust<Color> + Clone + Send + Sync + no_std_types::co
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
brightness: Item<SignedPercentageF32>,
|
brightness: SignedPercentageF32,
|
||||||
contrast: Item<SignedPercentageF32>,
|
contrast: SignedPercentageF32,
|
||||||
use_classic: Item<bool>,
|
use_classic: bool,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let use_classic = use_classic.into_element();
|
|
||||||
if use_classic {
|
if use_classic {
|
||||||
return brightness_contrast_classic(_ctx, input, brightness, contrast);
|
return brightness_contrast_classic(_ctx, input, brightness, contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut input = input;
|
|
||||||
let brightness = brightness.into_element();
|
|
||||||
let contrast = contrast.into_element();
|
|
||||||
|
|
||||||
const WINDOW_SIZE: usize = 1024;
|
const WINDOW_SIZE: usize = 1024;
|
||||||
|
|
||||||
// Brightness LUT
|
// Brightness LUT
|
||||||
@@ -261,7 +239,7 @@ fn brightness_contrast<T: Adjust<Color> + Clone + Send + Sync + no_std_types::co
|
|||||||
});
|
});
|
||||||
let lut_max = (combined_lut.len() - 1) as f32;
|
let lut_max = (combined_lut.len() - 1) as f32;
|
||||||
|
|
||||||
input.element_mut().adjust(|color| color.map_gamma_rgb(|c| combined_lut[(c * lut_max).round() as usize]));
|
input.adjust(|color| color.map_gamma_rgb(|c| combined_lut[(c * lut_max).round() as usize]));
|
||||||
|
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
@@ -280,24 +258,17 @@ fn levels<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::CacheH
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
#[default(0.)] shadows: Item<PercentageF32>,
|
#[default(0.)] shadows: PercentageF32,
|
||||||
#[default(50.)] midtones: Item<PercentageF32>,
|
#[default(50.)] midtones: PercentageF32,
|
||||||
#[default(100.)] highlights: Item<PercentageF32>,
|
#[default(100.)] highlights: PercentageF32,
|
||||||
#[default(0.)] output_minimums: Item<PercentageF32>,
|
#[default(0.)] output_minimums: PercentageF32,
|
||||||
#[default(100.)] output_maximums: Item<PercentageF32>,
|
#[default(100.)] output_maximums: PercentageF32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let shadows = shadows.into_element();
|
|
||||||
let midtones = midtones.into_element();
|
|
||||||
let highlights = highlights.into_element();
|
|
||||||
let output_minimums = output_minimums.into_element();
|
|
||||||
let output_maximums = output_maximums.into_element();
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
// Levels math operates in gamma space
|
// Levels math operates in gamma space
|
||||||
let [mut r, mut g, mut b, a] = color.to_gamma_srgb_channels();
|
let [mut r, mut g, mut b, a] = color.to_gamma_srgb_channels();
|
||||||
|
|
||||||
@@ -366,46 +337,37 @@ fn black_and_white<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
#[default(Color::BLACK)] tint: Item<Color>,
|
#[default(Color::BLACK)] tint: Color,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
reds: Item<PercentageF32>,
|
reds: PercentageF32,
|
||||||
#[default(60.)]
|
#[default(60.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
yellows: Item<PercentageF32>,
|
yellows: PercentageF32,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
greens: Item<PercentageF32>,
|
greens: PercentageF32,
|
||||||
#[default(60.)]
|
#[default(60.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
cyans: Item<PercentageF32>,
|
cyans: PercentageF32,
|
||||||
#[default(20.)]
|
#[default(20.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
blues: Item<PercentageF32>,
|
blues: PercentageF32,
|
||||||
#[default(80.)]
|
#[default(80.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[soft(-200..300)]
|
#[soft(-200..300)]
|
||||||
magentas: Item<PercentageF32>,
|
magentas: PercentageF32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let tint = tint.into_element();
|
|
||||||
let reds = reds.into_element();
|
|
||||||
let yellows = yellows.into_element();
|
|
||||||
let greens = greens.into_element();
|
|
||||||
let cyans = cyans.into_element();
|
|
||||||
let blues = blues.into_element();
|
|
||||||
let magentas = magentas.into_element();
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
// Black & White channel weights are tuned for gamma-space values
|
// Black & White channel weights are tuned for gamma-space values
|
||||||
let [r, g, b, alpha_part] = color.to_gamma_srgb_channels();
|
let [r, g, b, alpha_part] = color.to_gamma_srgb_channels();
|
||||||
|
|
||||||
@@ -458,20 +420,15 @@ fn hue_saturation<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
hue_shift: Item<AngleF32>,
|
hue_shift: AngleF32,
|
||||||
saturation_shift: Item<SignedPercentageF32>,
|
saturation_shift: SignedPercentageF32,
|
||||||
lightness_shift: Item<SignedPercentageF32>,
|
lightness_shift: SignedPercentageF32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
let hue_shift = hue_shift.into_element();
|
|
||||||
let saturation_shift = saturation_shift.into_element();
|
|
||||||
let lightness_shift = lightness_shift.into_element();
|
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
// HSL operates on gamma-space channels
|
// HSL operates on gamma-space channels
|
||||||
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
||||||
|
|
||||||
@@ -495,13 +452,12 @@ fn invert<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::CacheH
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
// Invert in gamma space relative to alpha
|
// Invert in gamma space relative to alpha
|
||||||
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
||||||
Color::from_gamma_srgb_channels(a - r, a - g, a - b, a)
|
Color::from_gamma_srgb_channels(a - r, a - g, a - b, a)
|
||||||
@@ -517,20 +473,15 @@ fn threshold<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
#[default(50.)] min_luminance: Item<PercentageF32>,
|
#[default(50.)] min_luminance: PercentageF32,
|
||||||
#[default(100.)] max_luminance: Item<PercentageF32>,
|
#[default(100.)] max_luminance: PercentageF32,
|
||||||
luminance_calc: Item<LuminanceCalculation>,
|
luminance_calc: LuminanceCalculation,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let min_luminance = min_luminance.into_element();
|
|
||||||
let max_luminance = max_luminance.into_element();
|
|
||||||
let luminance_calc = luminance_calc.into_element();
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
let min_luminance = srgb_to_linear(min_luminance / 100.);
|
let min_luminance = srgb_to_linear(min_luminance / 100.);
|
||||||
let max_luminance = srgb_to_linear(max_luminance / 100.);
|
let max_luminance = srgb_to_linear(max_luminance / 100.);
|
||||||
|
|
||||||
@@ -568,16 +519,13 @@ fn vibrance<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cach
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
vibrance: Item<SignedPercentageF32>,
|
vibrance: SignedPercentageF32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let vibrance = vibrance.into_element();
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
let r_raw = color.r();
|
let r_raw = color.r();
|
||||||
let g_raw = color.g();
|
let g_raw = color.g();
|
||||||
let b_raw = color.b();
|
let b_raw = color.b();
|
||||||
@@ -773,76 +721,69 @@ fn channel_mixer<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context:
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
|
|
||||||
monochrome: Item<bool>,
|
monochrome: bool,
|
||||||
|
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[name("Red")]
|
#[name("Red")]
|
||||||
monochrome_r: Item<f32>,
|
monochrome_r: f32,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[name("Green")]
|
#[name("Green")]
|
||||||
monochrome_g: Item<f32>,
|
monochrome_g: f32,
|
||||||
#[default(20.)]
|
#[default(20.)]
|
||||||
#[name("Blue")]
|
#[name("Blue")]
|
||||||
monochrome_b: Item<f32>,
|
monochrome_b: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("Constant")]
|
#[name("Constant")]
|
||||||
monochrome_c: Item<f32>,
|
monochrome_c: f32,
|
||||||
|
|
||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
#[name("(Red) Red")]
|
#[name("(Red) Red")]
|
||||||
red_r: Item<f32>,
|
red_r: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Red) Green")]
|
#[name("(Red) Green")]
|
||||||
red_g: Item<f32>,
|
red_g: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Red) Blue")]
|
#[name("(Red) Blue")]
|
||||||
red_b: Item<f32>,
|
red_b: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Red) Constant")]
|
#[name("(Red) Constant")]
|
||||||
red_c: Item<f32>,
|
red_c: f32,
|
||||||
|
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Green) Red")]
|
#[name("(Green) Red")]
|
||||||
green_r: Item<f32>,
|
green_r: f32,
|
||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
#[name("(Green) Green")]
|
#[name("(Green) Green")]
|
||||||
green_g: Item<f32>,
|
green_g: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Green) Blue")]
|
#[name("(Green) Blue")]
|
||||||
green_b: Item<f32>,
|
green_b: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Green) Constant")]
|
#[name("(Green) Constant")]
|
||||||
green_c: Item<f32>,
|
green_c: f32,
|
||||||
|
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Blue) Red")]
|
#[name("(Blue) Red")]
|
||||||
blue_r: Item<f32>,
|
blue_r: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Blue) Green")]
|
#[name("(Blue) Green")]
|
||||||
blue_g: Item<f32>,
|
blue_g: f32,
|
||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
#[name("(Blue) Blue")]
|
#[name("(Blue) Blue")]
|
||||||
blue_b: Item<f32>,
|
blue_b: f32,
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[name("(Blue) Constant")]
|
#[name("(Blue) Constant")]
|
||||||
blue_c: Item<f32>,
|
blue_c: f32,
|
||||||
|
|
||||||
// Display-only properties (not used within the node)
|
// Display-only properties (not used within the node)
|
||||||
_output_channel: Item<RedGreenBlue>,
|
_output_channel: RedGreenBlue,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let monochrome = monochrome.into_element();
|
|
||||||
let (monochrome_r, monochrome_g, monochrome_b, monochrome_c) = (monochrome_r.into_element(), monochrome_g.into_element(), monochrome_b.into_element(), monochrome_c.into_element());
|
|
||||||
let (red_r, red_g, red_b, red_c) = (red_r.into_element(), red_g.into_element(), red_b.into_element(), red_c.into_element());
|
|
||||||
let (green_r, green_g, green_b, green_c) = (green_r.into_element(), green_g.into_element(), green_b.into_element(), green_c.into_element());
|
|
||||||
let (blue_r, blue_g, blue_b, blue_c) = (blue_r.into_element(), blue_g.into_element(), blue_b.into_element(), blue_c.into_element());
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
||||||
|
|
||||||
let (out_r, out_g, out_b) = if monochrome {
|
let (out_r, out_g, out_b) = if monochrome {
|
||||||
@@ -912,73 +853,61 @@ fn selective_color<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
image: Item<T>,
|
mut image: T,
|
||||||
|
|
||||||
mode: Item<RelativeAbsolute>,
|
mode: RelativeAbsolute,
|
||||||
|
|
||||||
#[name("(Reds) Cyan")] r_c: Item<f32>,
|
#[name("(Reds) Cyan")] r_c: f32,
|
||||||
#[name("(Reds) Magenta")] r_m: Item<f32>,
|
#[name("(Reds) Magenta")] r_m: f32,
|
||||||
#[name("(Reds) Yellow")] r_y: Item<f32>,
|
#[name("(Reds) Yellow")] r_y: f32,
|
||||||
#[name("(Reds) Black")] r_k: Item<f32>,
|
#[name("(Reds) Black")] r_k: f32,
|
||||||
|
|
||||||
#[name("(Yellows) Cyan")] y_c: Item<f32>,
|
#[name("(Yellows) Cyan")] y_c: f32,
|
||||||
#[name("(Yellows) Magenta")] y_m: Item<f32>,
|
#[name("(Yellows) Magenta")] y_m: f32,
|
||||||
#[name("(Yellows) Yellow")] y_y: Item<f32>,
|
#[name("(Yellows) Yellow")] y_y: f32,
|
||||||
#[name("(Yellows) Black")] y_k: Item<f32>,
|
#[name("(Yellows) Black")] y_k: f32,
|
||||||
|
|
||||||
#[name("(Greens) Cyan")] g_c: Item<f32>,
|
#[name("(Greens) Cyan")] g_c: f32,
|
||||||
#[name("(Greens) Magenta")] g_m: Item<f32>,
|
#[name("(Greens) Magenta")] g_m: f32,
|
||||||
#[name("(Greens) Yellow")] g_y: Item<f32>,
|
#[name("(Greens) Yellow")] g_y: f32,
|
||||||
#[name("(Greens) Black")] g_k: Item<f32>,
|
#[name("(Greens) Black")] g_k: f32,
|
||||||
|
|
||||||
#[name("(Cyans) Cyan")] c_c: Item<f32>,
|
#[name("(Cyans) Cyan")] c_c: f32,
|
||||||
#[name("(Cyans) Magenta")] c_m: Item<f32>,
|
#[name("(Cyans) Magenta")] c_m: f32,
|
||||||
#[name("(Cyans) Yellow")] c_y: Item<f32>,
|
#[name("(Cyans) Yellow")] c_y: f32,
|
||||||
#[name("(Cyans) Black")] c_k: Item<f32>,
|
#[name("(Cyans) Black")] c_k: f32,
|
||||||
|
|
||||||
#[name("(Blues) Cyan")] b_c: Item<f32>,
|
#[name("(Blues) Cyan")] b_c: f32,
|
||||||
#[name("(Blues) Magenta")] b_m: Item<f32>,
|
#[name("(Blues) Magenta")] b_m: f32,
|
||||||
#[name("(Blues) Yellow")] b_y: Item<f32>,
|
#[name("(Blues) Yellow")] b_y: f32,
|
||||||
#[name("(Blues) Black")] b_k: Item<f32>,
|
#[name("(Blues) Black")] b_k: f32,
|
||||||
|
|
||||||
#[name("(Magentas) Cyan")] m_c: Item<f32>,
|
#[name("(Magentas) Cyan")] m_c: f32,
|
||||||
#[name("(Magentas) Magenta")] m_m: Item<f32>,
|
#[name("(Magentas) Magenta")] m_m: f32,
|
||||||
#[name("(Magentas) Yellow")] m_y: Item<f32>,
|
#[name("(Magentas) Yellow")] m_y: f32,
|
||||||
#[name("(Magentas) Black")] m_k: Item<f32>,
|
#[name("(Magentas) Black")] m_k: f32,
|
||||||
|
|
||||||
#[name("(Whites) Cyan")] w_c: Item<f32>,
|
#[name("(Whites) Cyan")] w_c: f32,
|
||||||
#[name("(Whites) Magenta")] w_m: Item<f32>,
|
#[name("(Whites) Magenta")] w_m: f32,
|
||||||
#[name("(Whites) Yellow")] w_y: Item<f32>,
|
#[name("(Whites) Yellow")] w_y: f32,
|
||||||
#[name("(Whites) Black")] w_k: Item<f32>,
|
#[name("(Whites) Black")] w_k: f32,
|
||||||
|
|
||||||
#[name("(Neutrals) Cyan")] n_c: Item<f32>,
|
#[name("(Neutrals) Cyan")] n_c: f32,
|
||||||
#[name("(Neutrals) Magenta")] n_m: Item<f32>,
|
#[name("(Neutrals) Magenta")] n_m: f32,
|
||||||
#[name("(Neutrals) Yellow")] n_y: Item<f32>,
|
#[name("(Neutrals) Yellow")] n_y: f32,
|
||||||
#[name("(Neutrals) Black")] n_k: Item<f32>,
|
#[name("(Neutrals) Black")] n_k: f32,
|
||||||
|
|
||||||
#[name("(Blacks) Cyan")] k_c: Item<f32>,
|
#[name("(Blacks) Cyan")] k_c: f32,
|
||||||
#[name("(Blacks) Magenta")] k_m: Item<f32>,
|
#[name("(Blacks) Magenta")] k_m: f32,
|
||||||
#[name("(Blacks) Yellow")] k_y: Item<f32>,
|
#[name("(Blacks) Yellow")] k_y: f32,
|
||||||
#[name("(Blacks) Black")] k_k: Item<f32>,
|
#[name("(Blacks) Black")] k_k: f32,
|
||||||
|
|
||||||
_colors: Item<SelectiveColorChoice>,
|
_colors: SelectiveColorChoice,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut image = image;
|
image.adjust(|color| {
|
||||||
let mode = mode.into_element();
|
|
||||||
let (r_c, r_m, r_y, r_k) = (r_c.into_element(), r_m.into_element(), r_y.into_element(), r_k.into_element());
|
|
||||||
let (y_c, y_m, y_y, y_k) = (y_c.into_element(), y_m.into_element(), y_y.into_element(), y_k.into_element());
|
|
||||||
let (g_c, g_m, g_y, g_k) = (g_c.into_element(), g_m.into_element(), g_y.into_element(), g_k.into_element());
|
|
||||||
let (c_c, c_m, c_y, c_k) = (c_c.into_element(), c_m.into_element(), c_y.into_element(), c_k.into_element());
|
|
||||||
let (b_c, b_m, b_y, b_k) = (b_c.into_element(), b_m.into_element(), b_y.into_element(), b_k.into_element());
|
|
||||||
let (m_c, m_m, m_y, m_k) = (m_c.into_element(), m_m.into_element(), m_y.into_element(), m_k.into_element());
|
|
||||||
let (w_c, w_m, w_y, w_k) = (w_c.into_element(), w_m.into_element(), w_y.into_element(), w_k.into_element());
|
|
||||||
let (n_c, n_m, n_y, n_k) = (n_c.into_element(), n_m.into_element(), n_y.into_element(), n_k.into_element());
|
|
||||||
let (k_c, k_m, k_y, k_k) = (k_c.into_element(), k_m.into_element(), k_y.into_element(), k_k.into_element());
|
|
||||||
|
|
||||||
image.element_mut().adjust(|color| {
|
|
||||||
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
||||||
|
|
||||||
let min = |a: f32, b: f32, c: f32| a.min(b).min(c);
|
let min = |a: f32, b: f32, c: f32| a.min(b).min(c);
|
||||||
@@ -1068,18 +997,16 @@ fn posterize<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
#[default(4)]
|
#[default(4)]
|
||||||
#[hard(2..)]
|
#[hard(2..)]
|
||||||
levels: Item<u32>,
|
levels: u32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
let levels = levels as f32;
|
||||||
let levels = levels.into_element() as f32;
|
input.adjust(|color| {
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
let number_of_areas = levels.recip();
|
let number_of_areas = levels.recip();
|
||||||
let size_of_areas = (levels - 1.).recip();
|
let size_of_areas = (levels - 1.).recip();
|
||||||
color.map_gamma_rgb(|c| (c / number_of_areas).floor() * size_of_areas)
|
color.map_gamma_rgb(|c| (c / number_of_areas).floor() * size_of_areas)
|
||||||
@@ -1099,24 +1026,19 @@ fn exposure<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cach
|
|||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Color,
|
Color,
|
||||||
GradientStops,
|
Gradient,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
mut input: T,
|
||||||
exposure: Item<f32>,
|
exposure: f32,
|
||||||
offset: Item<f32>,
|
offset: f32,
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[range]
|
#[range]
|
||||||
#[hard(0.0001..)]
|
#[hard(0.0001..)]
|
||||||
#[soft(0.01..10)]
|
#[soft(0.01..10)]
|
||||||
gamma_correction: Item<f32>,
|
gamma_correction: f32,
|
||||||
) -> Item<T> {
|
) -> T {
|
||||||
let mut input = input;
|
input.adjust(|color| {
|
||||||
let exposure = exposure.into_element();
|
|
||||||
let offset = offset.into_element();
|
|
||||||
let gamma_correction = gamma_correction.into_element();
|
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
|
||||||
let adjusted = color
|
let adjusted = color
|
||||||
// Exposure
|
// Exposure
|
||||||
.map_rgb(|c: f32| c * 2_f32.powf(exposure))
|
.map_rgb(|c: f32| c * 2_f32.powf(exposure))
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ fn blur(
|
|||||||
#[range]
|
#[range]
|
||||||
#[hard(0..)]
|
#[hard(0..)]
|
||||||
#[soft(..100)]
|
#[soft(..100)]
|
||||||
radius: Item<PixelLength>,
|
radius: PixelLength,
|
||||||
/// Use a lower-quality box kernel instead of a circular Gaussian kernel. This is faster but produces boxy artifacts.
|
/// Use a lower-quality box kernel instead of a circular Gaussian kernel. This is faster but produces boxy artifacts.
|
||||||
box_blur: Item<bool>,
|
box_blur: bool,
|
||||||
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
|
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
|
||||||
gamma: bool,
|
gamma: bool,
|
||||||
) -> Raster<CPU> {
|
) -> Raster<CPU> {
|
||||||
|
|||||||
@@ -369,32 +369,32 @@ pub fn image(_: impl Ctx, resource: Resource) -> Raster<CPU> {
|
|||||||
pub fn noise_pattern(
|
pub fn noise_pattern(
|
||||||
ctx: impl ExtractFootprint + Ctx,
|
ctx: impl ExtractFootprint + Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(true)] clip: Item<bool>,
|
#[default(true)] clip: bool,
|
||||||
seed: Item<u32>,
|
seed: u32,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_scale")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_scale")]
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
scale: Item<f64>,
|
scale: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: Item<NoiseType>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: NoiseType,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: Item<DomainWarpType>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: DomainWarpType,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_amplitude")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_amplitude")]
|
||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
domain_warp_amplitude: Item<f64>,
|
domain_warp_amplitude: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: Item<FractalType>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: FractalType,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_octaves")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_octaves")]
|
||||||
#[default(3)]
|
#[default(3)]
|
||||||
fractal_octaves: Item<u32>,
|
fractal_octaves: u32,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_lacunarity")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_lacunarity")]
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
fractal_lacunarity: Item<f64>,
|
fractal_lacunarity: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_gain")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_gain")]
|
||||||
#[default(0.5)]
|
#[default(0.5)]
|
||||||
fractal_gain: Item<f64>,
|
fractal_gain: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: Item<f64>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_ping_pong_strength")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_ping_pong_strength")]
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
fractal_ping_pong_strength: Item<f64>,
|
fractal_ping_pong_strength: f64,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: Item<CellularDistanceFunction>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: CellularDistanceFunction,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: Item<CellularReturnType>,
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: CellularReturnType,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
|
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
cellular_jitter: f64,
|
cellular_jitter: f64,
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ pub fn repeat_array<T>(
|
|||||||
content: impl Node<Context<'_>, Output = (T, Attr<TransformAttr>)>,
|
content: impl Node<Context<'_>, Output = (T, Attr<TransformAttr>)>,
|
||||||
#[default(100., 100.)]
|
#[default(100., 100.)]
|
||||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||||
direction: Item<PixelSize>,
|
direction: PixelSize,
|
||||||
angle: Item<Angle>,
|
angle: Angle,
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
#[hard(1..)]
|
#[hard(1..)]
|
||||||
count: u32,
|
count: u32,
|
||||||
@@ -95,7 +95,7 @@ fn repeat_radial<T>(
|
|||||||
start_angle: Angle,
|
start_angle: Angle,
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
radius: Item<f64>,
|
radius: f64,
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
#[hard(1..)]
|
#[hard(1..)]
|
||||||
count: u32,
|
count: u32,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fn transform_value<T: ApplyTransform + 'static>(
|
|||||||
let transformed = ctx.modify_footprint(|footprint| footprint.apply_transform(&matrix));
|
let transformed = ctx.modify_footprint(|footprint| footprint.apply_transform(&matrix));
|
||||||
let mut transform_target = content.eval(&transformed.ctx())?;
|
let mut transform_target = content.eval(&transformed.ctx())?;
|
||||||
|
|
||||||
item.left_apply_transform(&matrix);
|
transform_target.left_apply_transform(&matrix);
|
||||||
|
|
||||||
Ok(transform_target)
|
Ok(transform_target)
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ fn replace_transform<T>(_: impl Ctx + InjectFootprint, (element, _content_transf
|
|||||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||||
/// Obtains the transform of the first lane of the input, if present.
|
/// Obtains the transform of the first lane of the input, if present.
|
||||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||||
fn extract_transform<T: Clone + Send + Sync + CacheHash + 'static>(_: impl Ctx, #[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, Gradient)] content: IList<T>) -> DAffine2 {
|
fn extract_transform<T: Clone + Send + Sync + CacheHash + 'static>(_: impl Ctx, #[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, Gradient, String, Artboard)] content: IList<T>) -> DAffine2 {
|
||||||
match content.len() {
|
match content.len() {
|
||||||
0 => DAffine2::default(),
|
0 => DAffine2::default(),
|
||||||
_ => content.lane(0).attr::<TransformAttr>(),
|
_ => content.lane(0).attr::<TransformAttr>(),
|
||||||
@@ -106,23 +106,19 @@ fn extract_transform<T: Clone + Send + Sync + CacheHash + 'static>(_: impl Ctx,
|
|||||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn invert_transform(_: impl Ctx, transform: DAffine2) -> DAffine2 {
|
fn invert_transform(_: impl Ctx, transform: DAffine2) -> DAffine2 {
|
||||||
let (transform, attributes) = transform.into_parts();
|
transform.inverse()
|
||||||
|
|
||||||
let result = transform.inverse();
|
|
||||||
|
|
||||||
Item::from_parts(result, attributes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the translation component from the input transform.
|
/// Extracts the translation component from the input transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn decompose_translation(_: impl Ctx, transform: DAffine2) -> DVec2 {
|
fn decompose_translation(_: impl Ctx, transform: DAffine2) -> DVec2 {
|
||||||
Item::new_from_element(transform.into_element().translation)
|
transform.translation
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the rotation component (in degrees) from the input transform.
|
/// Extracts the rotation component (in degrees) from the input transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {
|
fn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {
|
||||||
Item::new_from_element(transform.into_element().decompose_rotation().to_degrees())
|
transform.decompose_rotation().to_degrees()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the scale component from the input transform.
|
/// Extracts the scale component from the input transform.
|
||||||
@@ -130,19 +126,14 @@ fn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {
|
|||||||
/// **Pure** returns the isolated scale factors with rotation and skew stripped away (can be negative for flipped axes).
|
/// **Pure** returns the isolated scale factors with rotation and skew stripped away (can be negative for flipped axes).
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn decompose_scale(_: impl Ctx, transform: DAffine2, scale_type: ScaleType) -> DVec2 {
|
fn decompose_scale(_: impl Ctx, transform: DAffine2, scale_type: ScaleType) -> DVec2 {
|
||||||
let transform = transform.into_element();
|
match scale_type {
|
||||||
let scale_type = scale_type.into_element();
|
|
||||||
|
|
||||||
let result = match scale_type {
|
|
||||||
ScaleType::Magnitude => transform.scale_magnitudes(),
|
ScaleType::Magnitude => transform.scale_magnitudes(),
|
||||||
ScaleType::Pure => transform.decompose_scale(),
|
ScaleType::Pure => transform.decompose_scale(),
|
||||||
};
|
}
|
||||||
|
|
||||||
Item::new_from_element(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the skew angle (in degrees) from the input transform.
|
/// Extracts the skew angle (in degrees) from the input transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn decompose_skew(_: impl Ctx, transform: DAffine2) -> f64 {
|
fn decompose_skew(_: impl Ctx, transform: DAffine2) -> f64 {
|
||||||
Item::new_from_element(transform.into_element().decompose_skew().atan().to_degrees())
|
transform.decompose_skew().atan().to_degrees()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user