mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
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:
@@ -13,7 +13,7 @@ impl Adjust<Color> for Color {
|
||||
mod adjust_std {
|
||||
use super::*;
|
||||
use raster_types::{CPU, Raster};
|
||||
use vector_types::GradientStops;
|
||||
use vector_types::Gradient;
|
||||
|
||||
impl Adjust<Color> for Raster<CPU> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
@@ -22,11 +22,9 @@ mod adjust_std {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Adjust<Color> for GradientStops {
|
||||
impl Adjust<Color> for Gradient {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for color in self.color.iter_mut() {
|
||||
*color = map_fn(color);
|
||||
}
|
||||
*self = self.map_colors(map_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -6,7 +6,7 @@ use no_std_types::registry::types::PercentageF32;
|
||||
#[cfg(feature = "std")]
|
||||
use raster_types::{CPU, Raster};
|
||||
#[cfg(feature = "std")]
|
||||
use vector_types::{GradientStop, GradientStops};
|
||||
use vector_types::{Gradient, GradientStop};
|
||||
|
||||
pub trait Blend<P: Pixel> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;
|
||||
@@ -36,73 +36,28 @@ mod blend_std {
|
||||
}
|
||||
}
|
||||
|
||||
impl Blend<Color> for GradientStops {
|
||||
impl Blend<Color> for Gradient {
|
||||
// TODO: This joining is unfaithful in several ways: it samples only at stop positions so midpoint curves flatten away;
|
||||
// TODO: it evaluates both sources with default whole-ramp attributes rather than their own (which this element-level impl cannot read);
|
||||
// TODO: and the output keeps over's attributes despite being sampled with defaults
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut combined_stops = self.position.iter().chain(under.position.iter()).copied().collect::<Vec<_>>();
|
||||
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
||||
let mut combined_stops = self.positions(false).into_iter().chain(under.positions(false)).collect::<Vec<_>>();
|
||||
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
||||
let over_evaluator = self.evaluator(Default::default());
|
||||
let under_evaluator = under.evaluator(Default::default());
|
||||
let stops = combined_stops.into_iter().map(|position| {
|
||||
let over_color = self.evaluate(position);
|
||||
let under_color = under.evaluate(position);
|
||||
let color = blend_fn(over_color, under_color);
|
||||
let color = blend_fn(over_evaluator.evaluate(position), under_evaluator.evaluate(position));
|
||||
GradientStop { position, midpoint: 0.5, color }
|
||||
});
|
||||
GradientStops::new(stops)
|
||||
|
||||
// Positions stay explicit because eliding them needs the cyclic flag this impl can't read, and a wrong guess would relocate the stops
|
||||
Gradient::new(stops)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color {
|
||||
let target_color = match blend_mode {
|
||||
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
|
||||
BlendMode::Erase => return background.alpha_subtract(foreground),
|
||||
BlendMode::Restore => return background.alpha_add(foreground),
|
||||
BlendMode::MultiplyAlpha => return background.alpha_multiply(foreground),
|
||||
blend_mode => apply_blend_mode(foreground, background, blend_mode),
|
||||
};
|
||||
|
||||
background.alpha_blend(target_color.apply_opacity(opacity))
|
||||
}
|
||||
|
||||
pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendMode) -> Color {
|
||||
match blend_mode {
|
||||
// Normal group
|
||||
BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),
|
||||
// Darken group
|
||||
BlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken),
|
||||
BlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply),
|
||||
BlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn),
|
||||
BlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn),
|
||||
BlendMode::DarkerColor => background.blend_darker_color(foreground),
|
||||
// Lighten group
|
||||
BlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten),
|
||||
BlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen),
|
||||
BlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge),
|
||||
BlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge),
|
||||
BlendMode::LighterColor => background.blend_lighter_color(foreground),
|
||||
// Contrast group
|
||||
BlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight),
|
||||
BlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight),
|
||||
BlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight),
|
||||
BlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light),
|
||||
BlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light),
|
||||
BlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light),
|
||||
BlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix),
|
||||
// Inversion group
|
||||
BlendMode::Difference => background.blend_rgb(foreground, Color::blend_difference),
|
||||
BlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion),
|
||||
BlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract),
|
||||
BlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide),
|
||||
// Component group
|
||||
BlendMode::Hue => background.blend_hue(foreground),
|
||||
BlendMode::Saturation => background.blend_saturation(foreground),
|
||||
BlendMode::Color => background.blend_color(foreground),
|
||||
BlendMode::Luminosity => background.blend_luminosity(foreground),
|
||||
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
|
||||
_ => panic!("Used blend mode without alpha blend"),
|
||||
}
|
||||
}
|
||||
pub use no_std_types::blending::{apply_blend_mode, blend_colors};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[node_macro::node(category("Raster"), cfg(feature = "std"))]
|
||||
@@ -111,7 +66,7 @@ fn mix<T: Blend<Color> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
||||
#[implementations(
|
||||
Raster<CPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
Gradient,
|
||||
)]
|
||||
#[gpu_image]
|
||||
over: T,
|
||||
@@ -119,7 +74,7 @@ fn mix<T: Blend<Color> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
||||
#[implementations(
|
||||
Raster<CPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
Gradient,
|
||||
)]
|
||||
#[gpu_image]
|
||||
under: T,
|
||||
@@ -135,7 +90,7 @@ fn color_overlay<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context:
|
||||
#[implementations(
|
||||
Raster<CPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
Gradient,
|
||||
)]
|
||||
#[gpu_image]
|
||||
mut image: T,
|
||||
|
||||
@@ -94,9 +94,9 @@ fn blur(
|
||||
#[range]
|
||||
#[hard(0..)]
|
||||
#[soft(..100)]
|
||||
radius: PixelLength,
|
||||
radius: Item<PixelLength>,
|
||||
/// Use a lower-quality box kernel instead of a circular Gaussian kernel. This is faster but produces boxy artifacts.
|
||||
box_blur: bool,
|
||||
box_blur: Item<bool>,
|
||||
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
|
||||
gamma: bool,
|
||||
) -> Raster<CPU> {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
//! Not immediately shader compatible due to needing [`GradientStops`] as a param, which needs [`Vec`]
|
||||
//! Not immediately shader compatible due to needing [`Gradient`] as a param, which needs [`Vec`]
|
||||
|
||||
use crate::adjust::Adjust;
|
||||
use core_types::{Color, Ctx};
|
||||
use raster_types::{CPU, Raster};
|
||||
use vector_types::GradientStops;
|
||||
use vector_types::markers::{
|
||||
GradientCyclic as GradientCyclicAttr, GradientHueDirection as GradientHueDirectionAttr, GradientInterpolation as GradientInterpolationAttr, GradientSpace as GradientSpaceAttr,
|
||||
GradientSpread as GradientSpreadAttr,
|
||||
};
|
||||
use vector_types::{Gradient, GradientSettings};
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
|
||||
@@ -14,21 +18,30 @@ fn gradient_map<T: Adjust<Color> + Clone + Send + Sync + core_types::CacheHash +
|
||||
#[implementations(
|
||||
Raster<CPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
Gradient,
|
||||
)]
|
||||
mut image: T,
|
||||
gradient: IList<GradientStops>,
|
||||
#[default(Color::BLACK, Color::WHITE)] gradient: IList<Gradient>,
|
||||
reverse: bool,
|
||||
) -> T {
|
||||
if gradient.is_empty() {
|
||||
return image;
|
||||
}
|
||||
let gradient = gradient.element_ref(0);
|
||||
// Master reads the whole-ramp settings off the item; ours ride the gradient's own lane.
|
||||
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>(),
|
||||
};
|
||||
let evaluator = gradient.element_ref(0).evaluator(settings);
|
||||
|
||||
image.adjust(|color| {
|
||||
let intensity = color.luminance_rec_709();
|
||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||
gradient.evaluate(intensity as f64)
|
||||
evaluator.evaluate(intensity as f64)
|
||||
});
|
||||
|
||||
image
|
||||
|
||||
@@ -369,32 +369,32 @@ pub fn image(_: impl Ctx, resource: Resource) -> Raster<CPU> {
|
||||
pub fn noise_pattern(
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
#[default(true)] clip: bool,
|
||||
seed: u32,
|
||||
#[default(true)] clip: Item<bool>,
|
||||
seed: Item<u32>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_scale")]
|
||||
#[default(10.)]
|
||||
scale: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: NoiseType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: DomainWarpType,
|
||||
scale: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: Item<NoiseType>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: Item<DomainWarpType>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_amplitude")]
|
||||
#[default(100.)]
|
||||
domain_warp_amplitude: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: FractalType,
|
||||
domain_warp_amplitude: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: Item<FractalType>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_octaves")]
|
||||
#[default(3)]
|
||||
fractal_octaves: u32,
|
||||
fractal_octaves: Item<u32>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_lacunarity")]
|
||||
#[default(2.)]
|
||||
fractal_lacunarity: f64,
|
||||
fractal_lacunarity: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_gain")]
|
||||
#[default(0.5)]
|
||||
fractal_gain: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: f64,
|
||||
fractal_gain: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_ping_pong_strength")]
|
||||
#[default(2.)]
|
||||
fractal_ping_pong_strength: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: CellularDistanceFunction,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: CellularReturnType,
|
||||
fractal_ping_pong_strength: Item<f64>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: Item<CellularDistanceFunction>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: Item<CellularReturnType>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
|
||||
#[default(1.)]
|
||||
cellular_jitter: f64,
|
||||
|
||||
Reference in New Issue
Block a user