clf_parser/
clf.rs

1use crate::deserialize::deserialize_space_separated;
2use anyhow::{bail, ensure, Context, Result};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize)]
5#[cfg_attr(
6    feature = "rkyv",
7    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
8    archive_attr(derive(bytecheck::CheckBytes))
9)]
10pub enum BitDepth {
11    #[serde(rename = "8i")]
12    I8,
13    #[serde(rename = "10i")]
14    I10,
15    #[serde(rename = "12i")]
16    I12,
17    #[serde(rename = "16i")]
18    I16,
19    #[serde(rename = "16f")]
20    F16,
21    #[serde(rename = "32f")]
22    F32,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize)]
26#[cfg_attr(
27    feature = "rkyv",
28    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
29    archive_attr(derive(bytecheck::CheckBytes))
30)]
31#[serde(rename_all = "camelCase")]
32pub struct OperatorBitDepth {
33    in_bit_depth: BitDepth,
34    out_bit_depth: BitDepth,
35}
36
37impl OperatorBitDepth {
38    fn validate(&self) -> Result<()> {
39        ensure!(
40            self.in_bit_depth == self.out_bit_depth && self.out_bit_depth == BitDepth::F32,
41            "currently only BitDepth::F32 is supported, found in bit depth: {:?}, out bit depth: {:?}", self.in_bit_depth, self.out_bit_depth
42        );
43        Ok(())
44    }
45}
46
47#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
48#[cfg_attr(
49    feature = "rkyv",
50    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
51    archive_attr(derive(bytecheck::CheckBytes))
52)]
53pub struct Array {
54    #[serde(deserialize_with = "deserialize_space_separated")]
55    pub dim: Vec<u32>,
56    #[serde(rename = "$value", deserialize_with = "deserialize_space_separated")]
57    pub data: Vec<f32>,
58}
59
60/// Range attribute scale + offset
61///
62/// <https://docs.acescentral.com/specifications/clf#range>
63/// <https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/9078753990d7f976a0bfcd55cfa63f2e1de3a53b/src/OpenColorIO/ops/range/RangeOpData.cpp#L474>
64#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
65#[cfg_attr(
66    feature = "rkyv",
67    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
68    archive_attr(derive(bytecheck::CheckBytes))
69)]
70#[serde(rename_all = "camelCase")]
71pub struct Range {
72    #[serde(flatten)]
73    bit_depth: OperatorBitDepth,
74    min_in_value: f32,
75    max_in_value: f32,
76    min_out_value: f32,
77    max_out_value: f32,
78}
79
80impl Range {
81    pub fn scale(&self) -> f32 {
82        (self.max_out_value - self.min_out_value) / (self.max_in_value - self.min_in_value)
83    }
84
85    pub fn offset(&self) -> f32 {
86        self.min_out_value - self.scale() * self.min_in_value
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
91#[cfg_attr(
92    feature = "rkyv",
93    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
94    archive_attr(derive(bytecheck::CheckBytes))
95)]
96#[serde(rename = "camelCase")]
97pub struct Lut1d {
98    #[serde(flatten)]
99    pub bit_depth: OperatorBitDepth,
100    #[serde(rename = "Array")]
101    pub array: Array,
102}
103
104impl Lut1d {
105    pub fn validate(&self) -> Result<()> {
106        self.bit_depth.validate()?;
107
108        if self.array.dim.len() != 2 {
109            bail!(
110                "A dim attribute defined for a Lut1d should have 2 elements; {} were found instead.",
111                self.array.dim.len()
112            )
113        }
114
115        if self.array.dim.iter().product::<u32>() != self.array.data.len() as u32 {
116            bail!(
117                "expected {} elements, got {}",
118                self.array.dim.iter().product::<u32>(),
119                self.array.data.len()
120            )
121        }
122        Ok(())
123    }
124}
125
126#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
127#[cfg_attr(
128    feature = "rkyv",
129    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
130    archive_attr(derive(bytecheck::CheckBytes))
131)]
132#[serde(rename_all = "camelCase")]
133pub struct Lut3d {
134    #[serde(flatten)]
135    pub bit_depth: OperatorBitDepth,
136    #[serde(rename = "Array")]
137    pub array: Array,
138}
139
140impl Lut3d {
141    fn validate(&self) -> Result<()> {
142        self.bit_depth.validate()?;
143
144        if self.array.dim.len() == 4 {
145            if self.array.dim[0] != self.array.dim[1] || self.array.dim[0] != self.array.dim[2] {
146                bail!(
147                    "A Lut3d should have the same dimensions on all three axes. Found {:?} instead.",
148                    self.array.dim
149                )
150            }
151            if self.array.dim[3] != 3 {
152                bail!(
153                    "A Lut3d should have 3 color components; {} were found instead.",
154                    self.array.dim[3]
155                )
156            }
157        } else {
158            bail!("A dim attribute defined for a Lut3D should have 4 elements; {} were found instead.", self.array.dim.len())
159        }
160
161        if self.array.dim.iter().product::<u32>() != self.array.data.len() as u32 {
162            bail!(
163                "expected {} elements, got {}",
164                self.array.dim.iter().product::<u32>(),
165                self.array.data.len()
166            )
167        }
168        Ok(())
169    }
170}
171
172#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
173#[cfg_attr(
174    feature = "rkyv",
175    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
176    archive_attr(derive(bytecheck::CheckBytes))
177)]
178#[serde(rename_all = "camelCase")]
179pub struct ProcessList {
180    #[serde(rename = "compCLFversion")]
181    pub comp_clf_version: u32,
182    pub id: String,
183    #[serde(rename = "$value")]
184    pub operators: Vec<Operator>,
185}
186
187impl ProcessList {
188    pub fn validate(&self) -> Result<()> {
189        if self.comp_clf_version > 3 {
190            bail!(
191                "CLF versions higher than 3 (currently parsing {}) are not yet supported",
192                self.comp_clf_version
193            )
194        }
195
196        for x in &self.operators {
197            x.validate()?
198        }
199
200        Ok(())
201    }
202}
203
204#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
205#[cfg_attr(
206    feature = "rkyv",
207    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
208    archive_attr(derive(bytecheck::CheckBytes))
209)]
210#[serde(deny_unknown_fields)]
211pub enum Operator {
212    Range(Range),
213    #[serde(rename = "LUT1D")]
214    Lut1D(Lut1d),
215    #[serde(rename = "LUT3D")]
216    Lut3D(Lut3d),
217}
218
219impl Operator {
220    fn validate(&self) -> Result<()> {
221        match self {
222            Self::Range(range) => range
223                .bit_depth
224                .validate()
225                .context("Range operator invalid."),
226            Self::Lut1D(lut1d) => lut1d.validate().context("LUT 1D operator invalid."),
227            Self::Lut3D(lut3d) => lut3d.validate().context("LUT 3D operator invalid."),
228        }
229    }
230}