Skip to main content

lumen_engine/node/processing/
levels.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!("levels.wgsl");
9
10/// Remaps raster black, white, gamma, and output range.
11#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(kind = "levels", name = "Levels", category = "processing")]
13pub struct Levels {
14    pub id: NodeId,
15    /// Input black point.
16    #[property(kind = "float", min = 0, max = 1, step = 0.01)]
17    pub black_point: NodeProperty,
18    /// Input white point.
19    #[property(kind = "float", min = 0, max = 1, step = 0.01)]
20    pub white_point: NodeProperty,
21    /// Midtone gamma adjustment.
22    #[property(kind = "float", min = 0.01, step = 0.01)]
23    pub gamma: NodeProperty,
24    /// Output black level.
25    #[property(kind = "float", min = 0, max = 1, step = 0.01)]
26    pub output_black: NodeProperty,
27    /// Output white level.
28    #[property(kind = "float", min = 0, max = 1, step = 0.01)]
29    pub output_white: NodeProperty,
30    #[input()]
31    pub source: PortRef,
32}
33
34impl Default for Levels {
35    fn default() -> Self {
36        Self {
37            id: NodeId::new(0),
38            black_point: NodeProperty::Float(0.0),
39            white_point: NodeProperty::Float(1.0),
40            gamma: NodeProperty::Float(1.0),
41            output_black: NodeProperty::Float(0.0),
42            output_white: NodeProperty::Float(1.0),
43            source: PortRef::empty(),
44        }
45    }
46}
47
48impl GpuCompileNode for Levels {
49    fn compile_gpu(
50        &self,
51        ctx: &mut crate::gpu::CompileContext<'_>,
52        port: &PortRef,
53    ) -> crate::Result<CompiledOutput> {
54        let (source, texture, params) = ctx.compile_unary_filter(
55            self.id,
56            &self.source,
57            port,
58            "levels",
59            SHADER,
60            std::mem::size_of::<compiler::LevelsParams>() as u64,
61        )?;
62        ctx.push_frame_binding(FrameBinding::Levels {
63            node_id: self.id,
64            black_point: self.black_point.clone(),
65            white_point: self.white_point.clone(),
66            gamma: self.gamma.clone(),
67            output_black: self.output_black.clone(),
68            output_white: self.output_white.clone(),
69            buffer: params,
70        });
71        Ok(CompiledOutput::Raster(RasterHandle {
72            texture,
73            domain: source.domain,
74            metadata: source.metadata,
75        }))
76    }
77}
78
79impl GpuFrameBindNode for Levels {
80    fn bind_gpu_frame(
81        &self,
82        ctx: &FrameBindContext<'_>,
83        binding: &FrameBinding,
84        bound: &mut BoundFrame,
85    ) -> crate::Result<()> {
86        let FrameBinding::Levels {
87            node_id,
88            black_point,
89            white_point,
90            gamma,
91            output_black,
92            output_white,
93            buffer,
94        } = binding
95        else {
96            return Ok(());
97        };
98        let params = compiler::LevelsParams {
99            black_point: black_point.resolve_float(
100                *node_id,
101                "black_point",
102                &ctx.expr_context(*node_id, "black_point"),
103            )? as f32,
104            white_point: white_point.resolve_float(
105                *node_id,
106                "white_point",
107                &ctx.expr_context(*node_id, "white_point"),
108            )? as f32,
109            gamma: gamma.resolve_float(*node_id, "gamma", &ctx.expr_context(*node_id, "gamma"))?
110                as f32,
111            output_black: output_black.resolve_float(
112                *node_id,
113                "output_black",
114                &ctx.expr_context(*node_id, "output_black"),
115            )? as f32,
116            output_white: output_white.resolve_float(
117                *node_id,
118                "output_white",
119                &ctx.expr_context(*node_id, "output_white"),
120            )? as f32,
121            _pad: [0.0; 3],
122        };
123        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
124        Ok(())
125    }
126}