Files
Graphite/node-graph/nodes/raster/src/curve.rs
Keavon Chambers 76938eb69a Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)
* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates

* Refactor Table to move its hard-coded fields into an attributes field

* Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods

* Remove TaggedValue::GraphicUnused

* Refactor Table<T> to use dynamic attributes instead fixed names

* Fix code review soundness concerns

* Add todo work

* Replace row-oriented Table<T> API with column-oriented access

* Fix attribute propagation bugs

---------
2026-04-28 03:25:16 -07:00

70 lines
2.1 KiB
Rust

use core_types::Node;
use core_types::color::{Channel, Linear, LuminanceMut};
use dyn_any::{DynAny, StaticType, StaticTypeSized};
use std::ops::{Add, Mul, Sub};
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, PartialEq, core_types::CacheHash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Curve {
#[cfg_attr(feature = "serde", serde(rename = "manipulatorGroups"))]
pub manipulator_groups: Vec<CurveManipulatorGroup>,
#[cfg_attr(feature = "serde", serde(rename = "firstHandle"))]
pub first_handle: [f32; 2],
#[cfg_attr(feature = "serde", serde(rename = "lastHandle"))]
pub last_handle: [f32; 2],
}
impl Default for Curve {
fn default() -> Self {
Self {
manipulator_groups: vec![],
first_handle: [0.2; 2],
last_handle: [0.8; 2],
}
}
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, core_types::CacheHash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CurveManipulatorGroup {
pub anchor: [f32; 2],
pub handles: [[f32; 2]; 2],
}
pub struct ValueMapperNode<C> {
lut: Vec<C>,
}
unsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> {
type Static = ValueMapperNode<C::Static>;
}
impl<C> ValueMapperNode<C> {
pub const fn new(lut: Vec<C>) -> Self {
Self { lut }
}
}
impl<'i, L: LuminanceMut + 'i> Node<'i, L> for ValueMapperNode<L::LuminanceChannel>
where
L::LuminanceChannel: Linear + Copy,
L::LuminanceChannel: Add<Output = L::LuminanceChannel>,
L::LuminanceChannel: Sub<Output = L::LuminanceChannel>,
L::LuminanceChannel: Mul<Output = L::LuminanceChannel>,
{
type Output = L;
fn eval(&'i self, mut val: L) -> L {
let luminance: f32 = val.luminance().to_linear();
let floating_sample_index = luminance * (self.lut.len() - 1) as f32;
let index_in_lut = floating_sample_index.floor() as usize;
let a = self.lut[index_in_lut];
let b = self.lut[(index_in_lut + 1).clamp(0, self.lut.len() - 1)];
let result = a.lerp(b, L::LuminanceChannel::from_linear(floating_sample_index.fract()));
val.set_luminance(result);
val
}
}