Skip to main content

concinnity_asset/
color_lut.rs

1// 3D colour-grading lookup-table schema.
2
3use crate::{AssetId, PayloadLocator};
4use alloc::string::String;
5
6/// A 3D colour-grading lookup table applied as a final post-process step. The
7/// build bakes the source into a colour cube; the graded result is blended over
8/// the image by [PostProcessConfig](#postprocessconfig)'s `lut_strength`.
9///
10/// A world declares at most one `ColorLut`; the first wins. When none is
11/// present, colour grading is skipped regardless of `lut_strength`.
12///
13/// Two source formats are accepted, picked by file extension:
14///   - `.cube`  Adobe Cube LUT (plain-text interchange format).
15///   - `.png`   A horizontal slice strip: `(n*n)` wide by `n` tall.
16///
17/// ```rust
18/// # use concinnity_asset::ColorLut;
19/// ColorLut {
20///     source: "luts/cinematic_warm.cube".into(),
21///     ..Default::default()
22/// };
23/// ```
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25#[serde(default)]
26#[derive(Default)]
27pub struct ColorLut {
28    /// Asset identity; injected via `inject_name`. Not part of `args`.
29    #[serde(skip)]
30    pub asset_id: AssetId,
31    /// Path to the source `.cube` or `.png` LUT file.
32    pub source: String,
33    /// Injected at load time from the compiled blob payload.
34    #[serde(skip)]
35    pub locator: Option<PayloadLocator>,
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn a_blank_lut_names_no_source() {
44        let l = ColorLut::default();
45        assert!(l.source.is_empty());
46        assert_eq!(l.asset_id, AssetId::default());
47        assert!(l.locator.is_none());
48    }
49
50    #[test]
51    fn the_source_path_is_the_only_authored_field() {
52        let l: ColorLut = serde_json::from_str(r#"{"source":"grade/warm.cube"}"#).unwrap();
53        assert_eq!(l.source, "grade/warm.cube");
54        assert_eq!(
55            serde_json::to_string(&l).unwrap(),
56            r#"{"source":"grade/warm.cube"}"#
57        );
58
59        let bytes = postcard::to_allocvec(&l).unwrap();
60        let back: ColorLut = postcard::from_bytes(&bytes).unwrap();
61        assert_eq!(back.source, "grade/warm.cube");
62        assert!(back.locator.is_none());
63    }
64}