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}