1use std::collections::HashMap;
2
3use error_stack::{Report, report, Result, ResultExt};
4use libxml::readonly::RoNode;
5
6use crate::error::{ParseNodeError, ParseNodeErrorKind::{self, *}};
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct STF {
11 midtones_balance: f64,
12 shadows_clip: f64,
13 highlights_clip: f64,
14 shadows_expansion: f64,
15 highlights_expansion: f64,
16
17 clip_delta: f64,
18 expand_delta: f64,
19}
20impl Default for STF {
22 fn default() -> Self {
23 Self {
24 midtones_balance: 0.5,
25 shadows_clip: 0.0,
26 highlights_clip: 1.0,
27 shadows_expansion: 0.0,
28 highlights_expansion: 1.0,
29 clip_delta: 1.0,
30 expand_delta: 1.0,
31 }
32 }
33}
34impl STF {
35 pub fn new(m: f64, mut s: f64, mut h: f64, l: f64, r: f64) -> Self {
44 if s > h {
45 std::mem::swap(&mut s, &mut h);
46 }
47 let shadows_clip = s.clamp(0.0, 1.0);
48 let highlights_clip = h.clamp(0.0, 1.0);
49 let shadows_expansion = l.min(0.0);
50 let highlights_expansion = r.max(1.0);
51 Self {
52 midtones_balance: m.clamp(0.0, 1.0),
53 shadows_clip,
54 highlights_clip,
55 shadows_expansion,
56 highlights_expansion,
57 clip_delta: highlights_clip - shadows_clip,
58 expand_delta: highlights_expansion - shadows_expansion,
59 }
60 }
61
62 #[inline]
64 pub fn mb(&self) -> f64 {
65 self.midtones_balance
66 }
67
68 #[inline]
70 pub fn sc(&self) -> f64 {
71 self.shadows_clip
72 }
73
74 #[inline]
76 pub fn hc(&self) -> f64 {
77 self.highlights_clip
78 }
79
80 #[inline]
82 pub fn se(&self) -> f64 {
83 self.shadows_expansion
84 }
85
86 #[inline]
88 pub fn he(&self) -> f64 {
89 self.highlights_expansion
90 }
91
92 #[inline]
99 fn mtf(&self, x: f64) -> f64 {
100 if x > 0.0 {
101 if x < 1.0 {
102 let m1 = self.mb() - 1.0;
103 m1 * x / ((self.mb() + m1) * x - self.mb())
104 } else {
105 1.0
106 }
107 } else {
108 0.0
109 }
110 }
111
112 #[inline]
114 fn clip(&self, x: f64) -> f64 {
115 if x < self.sc() {
116 0.0
117 } else if x > self.hc() {
118 1.0
119 } else if self.clip_delta == 0.0 {
120 self.sc()
121 } else {
122 (x - self.sc()) / self.clip_delta
123 }
124 }
125
126 #[inline]
128 fn expand(&self, x: f64) -> f64 {
129 (x - self.se()) / self.expand_delta
130 }
131
132 #[inline]
134 pub fn apply(&self, x: f64) -> f64 {
135 self.expand(self.mtf(self.clip(x)))
136 }
137}
138
139fn report(kind: ParseNodeErrorKind) -> Report<ParseNodeError> {
140 report!(context(kind))
141}
142const fn context(kind: ParseNodeErrorKind) -> ParseNodeError {
143 ParseNodeError::new("DisplayFunction", kind)
144}
145
146#[derive(Clone, Debug)]
148pub struct DisplayFunction {
149 name: Option<String>,
150 rgbl: [STF; 4],
151}
152impl Default for DisplayFunction {
154 fn default() -> Self {
155 Self {
156 name: Default::default(),
157 rgbl: Default::default(),
158 }
159 }
160}
161impl PartialEq for DisplayFunction {
163 fn eq(&self, other: &Self) -> bool {
164 self.rgbl == other.rgbl
165 }
166 fn ne(&self, other: &Self) -> bool {
167 self.rgbl != other.rgbl
168 }
169}
170impl DisplayFunction {
171 pub(crate) fn parse_node(node: RoNode) -> Result<Self, ParseNodeError> {
172 let _span_guard = tracing::debug_span!("DisplayFunction");
173 let mut attrs = node.get_attributes();
174 let children = node.get_child_nodes();
175
176 let name = attrs.remove("name");
177
178 fn parse_rkgbl(attr: &str, attrs: &mut HashMap<String, String>) -> Result<[f64; 4], ParseNodeError> {
179 if let Some(val) = attrs.remove(attr) {
180 match val.split(":").collect::<Vec<_>>().as_slice() {
181 &[rk, g, b, l] => Ok([
182 rk.trim().parse::<f64>()
183 .change_context(context(InvalidAttr))
184 .attach_printable_lazy(|| format!("Invalid {attr} attribute: failed to parse red/grayscale value"))?,
185 g.trim().parse::<f64>()
186 .change_context(context(InvalidAttr))
187 .attach_printable_lazy(|| format!("Invalid {attr} attribute: failed to parse green value"))?,
188 b.trim().parse::<f64>()
189 .change_context(context(InvalidAttr))
190 .attach_printable_lazy(|| format!("Invalid {attr} attribute: failed to parse blue value"))?,
191 l.trim().parse::<f64>()
192 .change_context(context(InvalidAttr))
193 .attach_printable_lazy(|| format!("Invalid {attr} attribute: failed to parse luminance value"))?,
194 ]),
195 _bad => Err(report(InvalidAttr)).attach_printable(format!("Invalid {attr} attribute: expected pattern rk:g:b:l, found {val}")),
196 }
197 } else {
198 Err(report(MissingAttr)).attach_printable(format!("Missing {attr} attribute"))
199 }
200 }
201
202 let m = parse_rkgbl("m", &mut attrs)?;
203 let s = parse_rkgbl("s", &mut attrs)?;
204 let h = parse_rkgbl("h", &mut attrs)?;
205 let l = parse_rkgbl("l", &mut attrs)?;
206 let r = parse_rkgbl("r", &mut attrs)?;
207
208 let rk = STF::new(m[0], s[0], h[0], l[0], r[0]);
209 let g = STF::new(m[1], s[1], h[1], l[1], r[1]);
210 let b = STF::new(m[2], s[2], h[2], l[2], r[2]);
211 let l = STF::new(m[3], s[3], h[3], l[3], r[3]);
212
213 for remaining in attrs.into_iter() {
214 tracing::warn!("Ignoring unrecognized attribute {}=\"{}\"", remaining.0, remaining.1);
215 }
216 for child in children {
217 tracing::warn!("Ignoring unrecognized child node <{}>", child.get_name());
218 }
219
220 Ok(Self {
221 name,
222 rgbl: [rk, g, b, l],
223 })
224 }
225
226 #[inline]
229 pub fn name(&self) -> Option<&str> {
230 self.name.as_deref()
231 }
232
233 #[inline]
235 pub fn r(&self) -> &STF {
236 &self.rgbl[0]
237 }
238 #[inline]
240 pub fn k(&self) -> &STF {
241 &self.rgbl[0]
242 }
243 #[inline]
245 pub fn g(&self) -> &STF {
246 &self.rgbl[1]
247 }
248 #[inline]
250 pub fn b(&self) -> &STF {
251 &self.rgbl[2]
252 }
253 #[inline]
262 pub fn l(&self) -> &STF {
263 &self.rgbl[3]
264 }
265}
266
267impl std::ops::Index<usize> for DisplayFunction {
269 type Output = STF;
270
271 fn index(&self, index: usize) -> &Self::Output {
272 &self.rgbl[index]
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use libxml::parser::Parser;
280
281 #[test]
282 fn parse_node() {
283 let auto_stretch = r#"<DisplayFunction m="0.000735:0.000735:0.000735:0.5"
285 s="0.003758:0.003758:0.003758:0"
286 h="1:1:1:1"
287 l="0:0:0:0"
288 r="1:1:1:1"
289 name="AutoStretch" />"#;
290
291 let xml = Parser::default().parse_string(auto_stretch.as_bytes()).unwrap();
292 let df = DisplayFunction::parse_node(xml.get_root_readonly().unwrap());
293 let df = df.unwrap();
294 assert_eq!(df.name(), Some("AutoStretch"));
295 let rgb = STF::new(0.000735, 0.003758, 1.0, 0.0, 1.0);
296 assert_eq!(df.r(), &rgb);
297 assert_eq!(df.g(), &rgb);
298 assert_eq!(df.b(), &rgb);
299 assert_eq!(df.l(), &Default::default());
300
301 let malformed = r#"<DisplayFunction m="1.000735:1.000735:1.000735:0.5"
304 s="1:1:1:0"
305 h="0.003758:0.003758:0.003758:1"
306 l="1:1:1:0"
307 r="0:0:0:1" />"#;
308 let xml = Parser::default().parse_string(malformed.as_bytes()).unwrap();
309 let df = DisplayFunction::parse_node(xml.get_root_readonly().unwrap());
310 let df = df.unwrap();
311 assert_eq!(df.name(), None);
312 let rgb = STF::new(1.0, 0.003758, 1.0, 0.0, 1.0);
313 assert_eq!(df.r(), &rgb);
314 assert_eq!(df.g(), &rgb);
315 assert_eq!(df.b(), &rgb);
316 assert_eq!(df.l(), &Default::default());
317 }
318}