primase_core/atmosphere/
status.rs1use serde::Serialize;
3
4use crate::{JsonError, Resource, World};
5
6use super::ToneMap;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
10#[serde(rename_all = "snake_case")]
11pub enum PostStage {
12 Curve,
14 ColourMatrix,
16 Vignette,
18 ColourLookup,
20 InputPower,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
26#[serde(rename_all = "snake_case")]
27pub enum PostStageStatusKind {
28 Unresolved,
30 Approximate,
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
36pub struct PostStageStatus {
37 pub stage: PostStage,
39 pub kind: PostStageStatusKind,
41 pub key: String,
43 pub reason: String,
45}
46
47impl PostStageStatus {
48 pub fn is_relevant(&self, tone: Option<&ToneMap>) -> bool {
51 if self.kind == PostStageStatusKind::Approximate {
52 return true;
53 }
54 match self.stage {
55 PostStage::Curve => tone.is_none(),
56 PostStage::ColourMatrix => tone.is_none_or(|tone| tone.colour_matrix.is_none()),
57 PostStage::Vignette => tone.is_none_or(|tone| tone.vignette.is_none()),
58 PostStage::ColourLookup | PostStage::InputPower => tone.is_none_or(|tone| tone.lut.is_none()),
59 }
60 }
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
66pub struct PostProcessingStatus(pub Vec<PostStageStatus>);
67
68impl Resource for PostProcessingStatus {
69 const NAME: &'static str = "core.post_processing_status";
70
71 fn to_json(&self) -> Result<Option<serde_json::Value>, JsonError> {
72 crate::json::to_value(self).map(Some)
73 }
74
75 fn verify(&self, _world: &World) -> Result<(), String> {
76 let mut keys = std::collections::BTreeSet::new();
77 for status in &self.0 {
78 if status.key.trim().is_empty() || status.reason.trim().is_empty() {
79 return Err("post stage status needs a nonempty key and explanation".into());
80 }
81 if !keys.insert(&status.key) {
82 return Err(format!("post stage status repeats report key {}", status.key));
83 }
84 }
85 Ok(())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::{ColourLut, ColourMatrix, ReinhardCurve};
93
94 fn status(stage: PostStage, kind: PostStageStatusKind) -> PostStageStatus {
95 PostStageStatus {
96 stage,
97 kind,
98 key: "post.stage_unresolved".into(),
99 reason: "source operation unresolved".into(),
100 }
101 }
102
103 #[test]
104 fn provided_stages_resolve_only_their_own_omissions_and_keep_approximation_limits() {
105 let mut tone = ToneMap {
106 curve: ReinhardCurve {
107 contrast: 1.,
108 white_balance: 1.,
109 gamma: 1.,
110 },
111 colour_matrix: None,
112 vignette: None,
113 lut: None,
114 };
115 let curve = status(PostStage::Curve, PostStageStatusKind::Unresolved);
116 let matrix = status(PostStage::ColourMatrix, PostStageStatusKind::Unresolved);
117 let vignette = status(PostStage::Vignette, PostStageStatusKind::Unresolved);
118 let lookup = status(PostStage::ColourLookup, PostStageStatusKind::Unresolved);
119 let power = status(PostStage::InputPower, PostStageStatusKind::Approximate);
120 assert!(curve.is_relevant(None));
121 assert!(!curve.is_relevant(Some(&tone)));
122 assert!(
123 matrix.is_relevant(Some(&tone))
124 && vignette.is_relevant(Some(&tone))
125 && lookup.is_relevant(Some(&tone))
126 );
127 tone.colour_matrix = Some(ColourMatrix::AfterCurve([
128 [1., 0., 0., 0.],
129 [0., 1., 0., 0.],
130 [0., 0., 1., 0.],
131 ]));
132 assert!(!matrix.is_relevant(Some(&tone)));
133 assert!(vignette.is_relevant(Some(&tone)) && lookup.is_relevant(Some(&tone)));
134 tone.vignette = Some([0.; 4]);
135 tone.lut = Some(ColourLut {
136 side: 2,
137 values: vec![[0.; 3]; 8],
138 input_power: 1.,
139 });
140 assert!(!vignette.is_relevant(Some(&tone)) && !lookup.is_relevant(Some(&tone)));
141 assert!(power.is_relevant(Some(&tone)));
142 }
143
144 #[test]
145 fn status_is_portable_data_with_unique_nonempty_report_keys() {
146 let world = World::new();
147 let issue = status(PostStage::ColourMatrix, PostStageStatusKind::Unresolved);
148 let mut statuses = PostProcessingStatus(vec![issue.clone()]);
149 assert!(statuses.verify(&world).is_ok());
150 let json = statuses.to_json().unwrap().unwrap();
151 assert_eq!(json[0]["stage"], "colour_matrix");
152 assert_eq!(json[0]["kind"], "unresolved");
153 assert_eq!(json[0]["reason"], "source operation unresolved");
154 statuses.0.push(issue);
155 assert!(
156 statuses
157 .verify(&world)
158 .unwrap_err()
159 .contains("repeats report key")
160 );
161 statuses.0.pop();
162 statuses.0[0].reason.clear();
163 assert!(statuses.verify(&world).unwrap_err().contains("nonempty"));
164 }
165}