Skip to main content

lumen_engine/node/processing/
hue_saturation.rs

1use crate::node::{NodeId, NodeProperty, PortRef};
2
3use crate::gpu::{
4    BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5    RasterHandle, compiler,
6};
7
8pub(crate) const SHADER: &str = include_str!("hue_saturation.wgsl");
9
10/// Adjusts raster hue, saturation, and lightness.
11#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(
13    kind = "hue_saturation",
14    name = "Hue Saturation",
15    category = "processing"
16)]
17pub struct HueSaturation {
18    pub id: NodeId,
19    /// Hue rotation in degrees.
20    #[property(kind = "float", name = "Hue", step = 1)]
21    pub hue_degrees: NodeProperty,
22    /// Saturation multiplier.
23    #[property(kind = "float", step = 0.01)]
24    pub saturation: NodeProperty,
25    /// Lightness offset.
26    #[property(kind = "float", step = 0.01)]
27    pub lightness: NodeProperty,
28    #[input()]
29    pub source: PortRef,
30}
31
32impl Default for HueSaturation {
33    fn default() -> Self {
34        Self {
35            id: NodeId::new(0),
36            hue_degrees: NodeProperty::Float(0.0),
37            saturation: NodeProperty::Float(1.0),
38            lightness: NodeProperty::Float(0.0),
39            source: PortRef::empty(),
40        }
41    }
42}
43
44impl GpuCompileNode for HueSaturation {
45    fn compile_gpu(
46        &self,
47        ctx: &mut crate::gpu::CompileContext<'_>,
48        port: &PortRef,
49    ) -> crate::Result<CompiledOutput> {
50        let (source, texture, params) = ctx.compile_unary_filter(
51            self.id,
52            &self.source,
53            port,
54            "hue-saturation",
55            SHADER,
56            std::mem::size_of::<compiler::HueSaturationParams>() as u64,
57        )?;
58        ctx.push_frame_binding(FrameBinding::HueSaturation {
59            node_id: self.id,
60            hue_degrees: self.hue_degrees.clone(),
61            saturation: self.saturation.clone(),
62            lightness: self.lightness.clone(),
63            buffer: params,
64        });
65        Ok(CompiledOutput::Raster(RasterHandle {
66            texture,
67            domain: source.domain,
68            metadata: source.metadata,
69        }))
70    }
71}
72
73impl GpuFrameBindNode for HueSaturation {
74    fn bind_gpu_frame(
75        &self,
76        ctx: &FrameBindContext<'_>,
77        binding: &FrameBinding,
78        bound: &mut BoundFrame,
79    ) -> crate::Result<()> {
80        let FrameBinding::HueSaturation {
81            node_id,
82            hue_degrees,
83            saturation,
84            lightness,
85            buffer,
86        } = binding
87        else {
88            return Ok(());
89        };
90        let params = compiler::HueSaturationParams {
91            hue_offset: (hue_degrees.resolve_float(
92                *node_id,
93                "hue_degrees",
94                &ctx.expr_context(*node_id, "hue_degrees"),
95            )? / 360.0) as f32,
96            saturation: saturation.resolve_float(
97                *node_id,
98                "saturation",
99                &ctx.expr_context(*node_id, "saturation"),
100            )? as f32,
101            lightness: lightness.resolve_float(
102                *node_id,
103                "lightness",
104                &ctx.expr_context(*node_id, "lightness"),
105            )? as f32,
106            _pad: 0.0,
107        };
108        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
109        Ok(())
110    }
111}