oxihuman_export/
cdl_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct CdlNode {
10 pub id: String,
11 pub slope: [f32; 3],
12 pub offset: [f32; 3],
13 pub power: [f32; 3],
14 pub saturation: f32,
15}
16
17impl Default for CdlNode {
18 fn default() -> Self {
19 Self {
20 id: "default".to_string(),
21 slope: [1.0, 1.0, 1.0],
22 offset: [0.0, 0.0, 0.0],
23 power: [1.0, 1.0, 1.0],
24 saturation: 1.0,
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct CdlExport {
32 pub version: String,
33 pub nodes: Vec<CdlNode>,
34}
35
36pub fn new_cdl_export() -> CdlExport {
38 CdlExport {
39 version: "1.01".to_string(),
40 nodes: Vec::new(),
41 }
42}
43
44pub fn cdl_add_node(export: &mut CdlExport, node: CdlNode) {
46 export.nodes.push(node);
47}
48
49pub fn cdl_add_identity(export: &mut CdlExport, id: &str) {
51 let node = CdlNode {
52 id: id.to_string(),
53 ..CdlNode::default()
54 };
55 export.nodes.push(node);
56}
57
58pub fn cdl_node_count(export: &CdlExport) -> usize {
60 export.nodes.len()
61}
62
63pub fn cdl_find_node<'a>(export: &'a CdlExport, id: &str) -> Option<&'a CdlNode> {
65 export.nodes.iter().find(|n| n.id == id)
66}
67
68pub fn validate_cdl(export: &CdlExport) -> bool {
70 export
71 .nodes
72 .iter()
73 .all(|n| n.saturation >= 0.0 && n.slope[0] >= 0.0 && n.slope[1] >= 0.0 && n.slope[2] >= 0.0)
74}
75
76pub fn cdl_to_xml(export: &CdlExport) -> String {
78 let mut out = format!(
79 "<?xml version=\"1.0\"?>\n<ColorDecisionList xmlns=\"urn:ASC:CDL:v{}\">\n",
80 export.version
81 );
82 for node in &export.nodes {
83 out.push_str(&format!(
84 " <ColorDecision>\n <ColorCorrection id=\"{}\">\n <SOPNode>\n <Slope>{:.6} {:.6} {:.6}</Slope>\n <Offset>{:.6} {:.6} {:.6}</Offset>\n <Power>{:.6} {:.6} {:.6}</Power>\n </SOPNode>\n <SatNode><Saturation>{:.6}</Saturation></SatNode>\n </ColorCorrection>\n </ColorDecision>\n",
85 node.id,
86 node.slope[0], node.slope[1], node.slope[2],
87 node.offset[0], node.offset[1], node.offset[2],
88 node.power[0], node.power[1], node.power[2],
89 node.saturation
90 ));
91 }
92 out.push_str("</ColorDecisionList>\n");
93 out
94}
95
96pub fn cdl_size_bytes(export: &CdlExport) -> usize {
98 cdl_to_xml(export).len()
99}
100
101pub fn apply_cdl(node: &CdlNode, rgb: [f32; 3]) -> [f32; 3] {
103 let sop: [f32; 3] = [
104 (rgb[0] * node.slope[0] + node.offset[0])
105 .max(0.0)
106 .powf(node.power[0]),
107 (rgb[1] * node.slope[1] + node.offset[1])
108 .max(0.0)
109 .powf(node.power[1]),
110 (rgb[2] * node.slope[2] + node.offset[2])
111 .max(0.0)
112 .powf(node.power[2]),
113 ];
114 let lum = 0.2126 * sop[0] + 0.7152 * sop[1] + 0.0722 * sop[2];
115 [
116 lum + node.saturation * (sop[0] - lum),
117 lum + node.saturation * (sop[1] - lum),
118 lum + node.saturation * (sop[2] - lum),
119 ]
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 fn sample() -> CdlExport {
127 let mut exp = new_cdl_export();
128 cdl_add_identity(&mut exp, "shot_010");
129 let node = CdlNode {
130 id: "shot_020".to_string(),
131 slope: [1.1, 1.0, 0.9],
132 saturation: 1.2,
133 ..CdlNode::default()
134 };
135 cdl_add_node(&mut exp, node);
136 exp
137 }
138
139 #[test]
140 fn test_node_count() {
141 assert_eq!(cdl_node_count(&sample()), 2);
142 }
143
144 #[test]
145 fn test_find_node() {
146 let exp = sample();
147 assert!(cdl_find_node(&exp, "shot_010").is_some());
148 assert!(cdl_find_node(&exp, "none").is_none());
149 }
150
151 #[test]
152 fn test_validate_valid() {
153 assert!(validate_cdl(&sample()));
154 }
155
156 #[test]
157 fn test_validate_bad_slope() {
158 let mut exp = new_cdl_export();
159 let mut node = CdlNode::default();
160 node.slope[0] = -1.0;
161 cdl_add_node(&mut exp, node);
162 assert!(!validate_cdl(&exp));
163 }
164
165 #[test]
166 fn test_to_xml() {
167 let s = cdl_to_xml(&sample());
168 assert!(s.contains("shot_010"));
169 }
170
171 #[test]
172 fn test_size_positive() {
173 assert!(cdl_size_bytes(&sample()) > 0);
174 }
175
176 #[test]
177 fn test_apply_cdl_identity() {
178 let node = CdlNode::default();
179 let rgb = [0.5f32, 0.5, 0.5];
180 let out = apply_cdl(&node, rgb);
181 assert!((out[0] - 0.5).abs() < 1e-4);
182 }
183
184 #[test]
185 fn test_apply_cdl_slope() {
186 let node = CdlNode {
187 slope: [2.0, 2.0, 2.0],
188 ..CdlNode::default()
189 };
190 let out = apply_cdl(&node, [0.5, 0.5, 0.5]);
191 assert!(out[0] > 0.5);
192 }
193
194 #[test]
195 fn test_default_node_identity() {
196 let node = CdlNode::default();
197 assert!((node.saturation - 1.0).abs() < 1e-6);
198 }
199}