Merge origin/master into the async record refactor

Scaffolding merge for the reconcile; the final series to master is
authored fresh. Rank plumbing resolves to our axis-IR model, the node
macro and the LaneSource render walk stay ours, master's vector
restructure and gradient vocabulary are adopted, and the paint and
appearance adoption is deliberately deferred behind our fill and stroke
markers.
This commit is contained in:
Dennis Kobert
2026-09-08 15:03:57 +00:00
385 changed files with 34666 additions and 20075 deletions
+216 -138
View File
@@ -6,6 +6,8 @@ use core::fmt::Debug;
use glam::Vec3;
use no_std_types::color::{Color, linear_to_srgb, srgb_to_linear};
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 node_macro::BufferStruct;
use num_enum::{FromPrimitive, IntoPrimitive};
@@ -14,7 +16,7 @@ use num_traits::float::Float;
#[cfg(feature = "std")]
use raster_types::{CPU, Raster};
#[cfg(feature = "std")]
use vector_types::GradientStops;
use vector_types::Gradient;
// TODO: Implement the following:
// Color Balance
@@ -56,10 +58,13 @@ fn luminance<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
GradientStops,
)]
#[gpu_image]
mut input: T,
luminance_calc: LuminanceCalculation,
) -> T {
input.adjust(|color| {
input: Item<T>,
luminance_calc: Item<LuminanceCalculation>,
) -> Item<T> {
let mut input = input;
let luminance_calc = luminance_calc.into_element();
input.element_mut().adjust(|color| {
let luminance = match luminance_calc {
LuminanceCalculation::SRGB => color.luminance_rec_709(),
LuminanceCalculation::Perceptual => color.luminance_perceptual(),
@@ -81,16 +86,20 @@ fn gamma_correction<T: Adjust<Color> + Clone + Send + Sync + no_std_types::conte
GradientStops,
)]
#[gpu_image]
mut input: T,
input: Item<T>,
#[default(2.2)]
#[range]
#[hard(0.0001..)]
#[soft(0.01..10)]
gamma: f32,
inverse: bool,
) -> T {
gamma: Item<f32>,
inverse: Item<bool>,
) -> Item<T> {
let mut input = input;
let gamma = gamma.into_element();
let inverse = inverse.into_element();
let exponent = if inverse { 1. / gamma } else { gamma };
input.adjust(|color| color.apply_gamma_exponent(exponent));
input.element_mut().adjust(|color| color.apply_gamma_exponent(exponent));
input
}
@@ -103,10 +112,13 @@ fn extract_channel<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
GradientStops,
)]
#[gpu_image]
mut input: T,
channel: RedGreenBlueAlpha,
) -> T {
input.adjust(|color| {
input: Item<T>,
channel: Item<RedGreenBlueAlpha>,
) -> Item<T> {
let mut input = input;
let channel = channel.into_element();
input.element_mut().adjust(|color| {
let extracted_value = match channel {
RedGreenBlueAlpha::Red => color.r(),
RedGreenBlueAlpha::Green => color.g(),
@@ -127,9 +139,10 @@ fn make_opaque<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::C
GradientStops,
)]
#[gpu_image]
mut input: T,
) -> T {
input.adjust(|color| {
input: Item<T>,
) -> Item<T> {
let mut input = input;
input.element_mut().adjust(|color| {
if color.a() == 0. {
return color.with_alpha(1.);
}
@@ -149,10 +162,14 @@ fn brightness_contrast_classic<T: Adjust<Color> + Clone + Send + Sync + no_std_t
GradientStops,
)]
#[gpu_image]
mut input: T,
brightness: SignedPercentageF32,
contrast: SignedPercentageF32,
) -> T {
input: Item<T>,
brightness: Item<SignedPercentageF32>,
contrast: Item<SignedPercentageF32>,
) -> Item<T> {
let mut input = input;
let brightness = brightness.into_element();
let contrast = contrast.into_element();
let brightness = brightness / 255.;
let contrast = contrast / 100.;
@@ -160,7 +177,7 @@ fn brightness_contrast_classic<T: Adjust<Color> + Clone + Send + Sync + no_std_t
let offset = brightness * contrast + brightness - contrast / 2.;
input.adjust(|color| color.map_gamma_rgb(|c| (c + c * contrast + offset).clamp(0., 1.)));
input.element_mut().adjust(|color| color.map_gamma_rgb(|c| (c + c * contrast + offset).clamp(0., 1.)));
input
}
@@ -180,15 +197,20 @@ fn brightness_contrast<T: Adjust<Color> + Clone + Send + Sync + no_std_types::co
GradientStops,
)]
#[gpu_image]
mut input: T,
brightness: SignedPercentageF32,
contrast: SignedPercentageF32,
use_classic: bool,
) -> T {
input: Item<T>,
brightness: Item<SignedPercentageF32>,
contrast: Item<SignedPercentageF32>,
use_classic: Item<bool>,
) -> Item<T> {
let use_classic = use_classic.into_element();
if use_classic {
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;
// Brightness LUT
@@ -239,7 +261,7 @@ fn brightness_contrast<T: Adjust<Color> + Clone + Send + Sync + no_std_types::co
});
let lut_max = (combined_lut.len() - 1) as f32;
input.adjust(|color| color.map_gamma_rgb(|c| combined_lut[(c * lut_max).round() as usize]));
input.element_mut().adjust(|color| color.map_gamma_rgb(|c| combined_lut[(c * lut_max).round() as usize]));
input
}
@@ -261,14 +283,21 @@ fn levels<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::CacheH
GradientStops,
)]
#[gpu_image]
mut image: T,
#[default(0.)] shadows: PercentageF32,
#[default(50.)] midtones: PercentageF32,
#[default(100.)] highlights: PercentageF32,
#[default(0.)] output_minimums: PercentageF32,
#[default(100.)] output_maximums: PercentageF32,
) -> T {
image.adjust(|color| {
image: Item<T>,
#[default(0.)] shadows: Item<PercentageF32>,
#[default(50.)] midtones: Item<PercentageF32>,
#[default(100.)] highlights: Item<PercentageF32>,
#[default(0.)] output_minimums: Item<PercentageF32>,
#[default(100.)] output_maximums: Item<PercentageF32>,
) -> Item<T> {
let mut image = image;
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
let [mut r, mut g, mut b, a] = color.to_gamma_srgb_channels();
@@ -340,34 +369,43 @@ fn black_and_white<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
GradientStops,
)]
#[gpu_image]
mut image: T,
#[default(Color::BLACK)] tint: Color,
image: Item<T>,
#[default(Color::BLACK)] tint: Item<Color>,
#[default(40.)]
#[range]
#[soft(-200..300)]
reds: PercentageF32,
reds: Item<PercentageF32>,
#[default(60.)]
#[range]
#[soft(-200..300)]
yellows: PercentageF32,
yellows: Item<PercentageF32>,
#[default(40.)]
#[range]
#[soft(-200..300)]
greens: PercentageF32,
greens: Item<PercentageF32>,
#[default(60.)]
#[range]
#[soft(-200..300)]
cyans: PercentageF32,
cyans: Item<PercentageF32>,
#[default(20.)]
#[range]
#[soft(-200..300)]
blues: PercentageF32,
blues: Item<PercentageF32>,
#[default(80.)]
#[range]
#[soft(-200..300)]
magentas: PercentageF32,
) -> T {
image.adjust(|color| {
magentas: Item<PercentageF32>,
) -> Item<T> {
let mut image = image;
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
let [r, g, b, alpha_part] = color.to_gamma_srgb_channels();
@@ -423,12 +461,17 @@ fn hue_saturation<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context
GradientStops,
)]
#[gpu_image]
mut input: T,
hue_shift: AngleF32,
saturation_shift: SignedPercentageF32,
lightness_shift: SignedPercentageF32,
) -> T {
input.adjust(|color| {
input: Item<T>,
hue_shift: Item<AngleF32>,
saturation_shift: Item<SignedPercentageF32>,
lightness_shift: Item<SignedPercentageF32>,
) -> Item<T> {
let mut input = input;
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
let [hue, saturation, lightness, alpha] = color.to_hsla();
@@ -455,9 +498,10 @@ fn invert<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::CacheH
GradientStops,
)]
#[gpu_image]
mut input: T,
) -> T {
input.adjust(|color| {
input: Item<T>,
) -> Item<T> {
let mut input = input;
input.element_mut().adjust(|color| {
// Invert in gamma space relative to alpha
let [r, g, b, a] = color.to_gamma_srgb_channels();
Color::from_gamma_srgb_channels(a - r, a - g, a - b, a)
@@ -476,12 +520,17 @@ fn threshold<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
GradientStops,
)]
#[gpu_image]
mut image: T,
#[default(50.)] min_luminance: PercentageF32,
#[default(100.)] max_luminance: PercentageF32,
luminance_calc: LuminanceCalculation,
) -> T {
image.adjust(|color| {
image: Item<T>,
#[default(50.)] min_luminance: Item<PercentageF32>,
#[default(100.)] max_luminance: Item<PercentageF32>,
luminance_calc: Item<LuminanceCalculation>,
) -> Item<T> {
let mut image = image;
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 max_luminance = srgb_to_linear(max_luminance / 100.);
@@ -522,10 +571,13 @@ fn vibrance<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cach
GradientStops,
)]
#[gpu_image]
mut image: T,
vibrance: SignedPercentageF32,
) -> T {
image.adjust(|color| {
image: Item<T>,
vibrance: Item<SignedPercentageF32>,
) -> Item<T> {
let mut image = image;
let vibrance = vibrance.into_element();
image.element_mut().adjust(|color| {
let r_raw = color.r();
let g_raw = color.g();
let b_raw = color.b();
@@ -724,66 +776,73 @@ fn channel_mixer<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context:
GradientStops,
)]
#[gpu_image]
mut image: T,
image: Item<T>,
monochrome: bool,
monochrome: Item<bool>,
#[default(40.)]
#[name("Red")]
monochrome_r: f32,
monochrome_r: Item<f32>,
#[default(40.)]
#[name("Green")]
monochrome_g: f32,
monochrome_g: Item<f32>,
#[default(20.)]
#[name("Blue")]
monochrome_b: f32,
monochrome_b: Item<f32>,
#[default(0.)]
#[name("Constant")]
monochrome_c: f32,
monochrome_c: Item<f32>,
#[default(100.)]
#[name("(Red) Red")]
red_r: f32,
red_r: Item<f32>,
#[default(0.)]
#[name("(Red) Green")]
red_g: f32,
red_g: Item<f32>,
#[default(0.)]
#[name("(Red) Blue")]
red_b: f32,
red_b: Item<f32>,
#[default(0.)]
#[name("(Red) Constant")]
red_c: f32,
red_c: Item<f32>,
#[default(0.)]
#[name("(Green) Red")]
green_r: f32,
green_r: Item<f32>,
#[default(100.)]
#[name("(Green) Green")]
green_g: f32,
green_g: Item<f32>,
#[default(0.)]
#[name("(Green) Blue")]
green_b: f32,
green_b: Item<f32>,
#[default(0.)]
#[name("(Green) Constant")]
green_c: f32,
green_c: Item<f32>,
#[default(0.)]
#[name("(Blue) Red")]
blue_r: f32,
blue_r: Item<f32>,
#[default(0.)]
#[name("(Blue) Green")]
blue_g: f32,
blue_g: Item<f32>,
#[default(100.)]
#[name("(Blue) Blue")]
blue_b: f32,
blue_b: Item<f32>,
#[default(0.)]
#[name("(Blue) Constant")]
blue_c: f32,
blue_c: Item<f32>,
// Display-only properties (not used within the node)
_output_channel: RedGreenBlue,
) -> T {
image.adjust(|color| {
_output_channel: Item<RedGreenBlue>,
) -> Item<T> {
let mut image = image;
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 (out_r, out_g, out_b) = if monochrome {
@@ -856,58 +915,70 @@ fn selective_color<T: Adjust<Color> + Clone + Send + Sync + no_std_types::contex
GradientStops,
)]
#[gpu_image]
mut image: T,
image: Item<T>,
mode: RelativeAbsolute,
mode: Item<RelativeAbsolute>,
#[name("(Reds) Cyan")] r_c: f32,
#[name("(Reds) Magenta")] r_m: f32,
#[name("(Reds) Yellow")] r_y: f32,
#[name("(Reds) Black")] r_k: f32,
#[name("(Reds) Cyan")] r_c: Item<f32>,
#[name("(Reds) Magenta")] r_m: Item<f32>,
#[name("(Reds) Yellow")] r_y: Item<f32>,
#[name("(Reds) Black")] r_k: Item<f32>,
#[name("(Yellows) Cyan")] y_c: f32,
#[name("(Yellows) Magenta")] y_m: f32,
#[name("(Yellows) Yellow")] y_y: f32,
#[name("(Yellows) Black")] y_k: f32,
#[name("(Yellows) Cyan")] y_c: Item<f32>,
#[name("(Yellows) Magenta")] y_m: Item<f32>,
#[name("(Yellows) Yellow")] y_y: Item<f32>,
#[name("(Yellows) Black")] y_k: Item<f32>,
#[name("(Greens) Cyan")] g_c: f32,
#[name("(Greens) Magenta")] g_m: f32,
#[name("(Greens) Yellow")] g_y: f32,
#[name("(Greens) Black")] g_k: f32,
#[name("(Greens) Cyan")] g_c: Item<f32>,
#[name("(Greens) Magenta")] g_m: Item<f32>,
#[name("(Greens) Yellow")] g_y: Item<f32>,
#[name("(Greens) Black")] g_k: Item<f32>,
#[name("(Cyans) Cyan")] c_c: f32,
#[name("(Cyans) Magenta")] c_m: f32,
#[name("(Cyans) Yellow")] c_y: f32,
#[name("(Cyans) Black")] c_k: f32,
#[name("(Cyans) Cyan")] c_c: Item<f32>,
#[name("(Cyans) Magenta")] c_m: Item<f32>,
#[name("(Cyans) Yellow")] c_y: Item<f32>,
#[name("(Cyans) Black")] c_k: Item<f32>,
#[name("(Blues) Cyan")] b_c: f32,
#[name("(Blues) Magenta")] b_m: f32,
#[name("(Blues) Yellow")] b_y: f32,
#[name("(Blues) Black")] b_k: f32,
#[name("(Blues) Cyan")] b_c: Item<f32>,
#[name("(Blues) Magenta")] b_m: Item<f32>,
#[name("(Blues) Yellow")] b_y: Item<f32>,
#[name("(Blues) Black")] b_k: Item<f32>,
#[name("(Magentas) Cyan")] m_c: f32,
#[name("(Magentas) Magenta")] m_m: f32,
#[name("(Magentas) Yellow")] m_y: f32,
#[name("(Magentas) Black")] m_k: f32,
#[name("(Magentas) Cyan")] m_c: Item<f32>,
#[name("(Magentas) Magenta")] m_m: Item<f32>,
#[name("(Magentas) Yellow")] m_y: Item<f32>,
#[name("(Magentas) Black")] m_k: Item<f32>,
#[name("(Whites) Cyan")] w_c: f32,
#[name("(Whites) Magenta")] w_m: f32,
#[name("(Whites) Yellow")] w_y: f32,
#[name("(Whites) Black")] w_k: f32,
#[name("(Whites) Cyan")] w_c: Item<f32>,
#[name("(Whites) Magenta")] w_m: Item<f32>,
#[name("(Whites) Yellow")] w_y: Item<f32>,
#[name("(Whites) Black")] w_k: Item<f32>,
#[name("(Neutrals) Cyan")] n_c: f32,
#[name("(Neutrals) Magenta")] n_m: f32,
#[name("(Neutrals) Yellow")] n_y: f32,
#[name("(Neutrals) Black")] n_k: f32,
#[name("(Neutrals) Cyan")] n_c: Item<f32>,
#[name("(Neutrals) Magenta")] n_m: Item<f32>,
#[name("(Neutrals) Yellow")] n_y: Item<f32>,
#[name("(Neutrals) Black")] n_k: Item<f32>,
#[name("(Blacks) Cyan")] k_c: f32,
#[name("(Blacks) Magenta")] k_m: f32,
#[name("(Blacks) Yellow")] k_y: f32,
#[name("(Blacks) Black")] k_k: f32,
#[name("(Blacks) Cyan")] k_c: Item<f32>,
#[name("(Blacks) Magenta")] k_m: Item<f32>,
#[name("(Blacks) Yellow")] k_y: Item<f32>,
#[name("(Blacks) Black")] k_k: Item<f32>,
_colors: SelectiveColorChoice,
) -> T {
image.adjust(|color| {
_colors: Item<SelectiveColorChoice>,
) -> Item<T> {
let mut image = image;
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 min = |a: f32, b: f32, c: f32| a.min(b).min(c);
@@ -1000,13 +1071,15 @@ fn posterize<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cac
GradientStops,
)]
#[gpu_image]
mut input: T,
input: Item<T>,
#[default(4)]
#[hard(2..)]
levels: u32,
) -> T {
let levels = levels as f32;
input.adjust(|color| {
levels: Item<u32>,
) -> Item<T> {
let mut input = input;
let levels = levels.into_element() as f32;
input.element_mut().adjust(|color| {
let number_of_areas = levels.recip();
let size_of_areas = (levels - 1.).recip();
color.map_gamma_rgb(|c| (c / number_of_areas).floor() * size_of_areas)
@@ -1029,16 +1102,21 @@ fn exposure<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::Cach
GradientStops,
)]
#[gpu_image]
mut input: T,
exposure: f32,
offset: f32,
input: Item<T>,
exposure: Item<f32>,
offset: Item<f32>,
#[default(1.)]
#[range]
#[hard(0.0001..)]
#[soft(0.01..10)]
gamma_correction: f32,
) -> T {
input.adjust(|color| {
gamma_correction: Item<f32>,
) -> Item<T> {
let mut input = input;
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
// Exposure
.map_rgb(|c: f32| c * 2_f32.powf(exposure))