1#[cfg(feature = "pyo3")]
2use pyo3::prelude::*;
3
4use serde::{
5 Deserialize, Deserializer, Serialize,
6 de::{self, MapAccess, Visitor},
7};
8
9use super::{
10 SolidColor,
11 layers::{Background, Ellipse, Icon, LayerOffset, Polygon, Rectangle, Size, Typography},
12};
13
14#[cfg_attr(
16 feature = "pyo3",
17 pyclass(module = "img_gen", get_all, set_all, from_py_object)
18)]
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct Mask {
21 pub size: Option<Size>,
23
24 #[serde(default)]
26 pub offset: LayerOffset,
27
28 #[serde(default)]
33 pub invert: bool,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub background: Option<Background>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub rectangle: Option<Rectangle>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub ellipse: Option<Ellipse>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub polygon: Option<Polygon>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub icon: Option<Icon>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub typography: Option<Typography>,
53}
54
55#[cfg_attr(
57 feature = "pyo3",
58 pyclass(module = "img_gen", get_all, set_all, from_py_object)
59)]
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct Layer {
62 pub size: Option<Size>,
64
65 #[serde(default)]
67 pub offset: LayerOffset,
68
69 pub background: Option<Background>,
71 pub rectangle: Option<Rectangle>,
73 pub ellipse: Option<Ellipse>,
75 pub polygon: Option<Polygon>,
77 pub icon: Option<Icon>,
79 pub typography: Option<Typography>,
81 pub mask: Option<Mask>,
83}
84
85#[cfg_attr(
87 feature = "pyo3",
88 pyclass(module = "img_gen", set_all, get_all, from_py_object)
89)]
90#[derive(Debug, Clone, Serialize)]
91pub struct Debug {
92 pub enable: bool,
94 pub grid: bool,
96 pub grid_step: u32,
98 pub color: SolidColor,
100}
101
102impl Debug {
103 pub fn get_foreground_color(&self) -> SolidColor {
105 let luminance = {
106 let mut result = 0.0f32;
107 for (index, c) in vec![
108 &self.color.get_r(),
109 &self.color.get_g(),
110 &self.color.get_b(),
111 ]
112 .into_iter()
113 .take(3)
114 .enumerate()
115 {
116 let component = *c as f32 / 255.0;
117 let new_component = if component <= 0.03928 {
118 component / 12.92
119 } else {
120 ((component + 0.055) / 1.055).powf(2.4)
121 };
122 match index {
123 0 => {
124 result += 0.2126 * new_component;
125 }
126 1 => {
127 result += 0.7152 * new_component;
128 }
129 _ => {
130 result += 0.0722 * new_component;
131 }
132 }
133 }
134 result
135 };
136 if luminance > 0.451 {
137 SolidColor::new(0, 0, 0, 255)
138 } else {
139 SolidColor::new(255, 255, 255, 255)
140 }
141 }
142
143 pub(crate) const fn default_grid_step() -> u32 {
144 30
145 }
146
147 pub(crate) fn default_color() -> SolidColor {
148 SolidColor::new(128, 128, 128, 255)
149 }
150
151 const fn default_grid() -> bool {
152 true
153 }
154}
155
156impl<'de> Deserialize<'de> for Debug {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 struct DebugVisitor;
159
160 impl<'de> Visitor<'de> for DebugVisitor {
161 type Value = Debug;
162
163 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
164 formatter.write_str("a boolean or a debug config object")
165 }
166
167 fn visit_bool<E: de::Error>(self, v: bool) -> Result<Debug, E> {
168 Ok(if v {
169 Debug {
170 enable: true,
171 ..Debug::default()
172 }
173 } else {
174 Debug::default()
175 })
176 }
177
178 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Debug, A::Error> {
179 let mut enable = None;
180 let mut grid = None;
181 let mut grid_step = None;
182 let mut color = None;
183
184 while let Some(key) = map.next_key::<std::borrow::Cow<str>>()? {
185 match key.as_ref() {
186 "enable" => enable = Some(map.next_value()?),
187 "grid" => grid = Some(map.next_value()?),
188 "grid_step" => grid_step = Some(map.next_value()?),
189 "color" => color = Some(map.next_value()?),
190 unknown => {
191 return Err(de::Error::unknown_field(
192 unknown,
193 &["enable", "grid", "grid_step", "color"],
194 ));
195 }
196 }
197 }
198
199 Ok(Debug {
200 enable: enable.unwrap_or(false),
201 grid: grid.unwrap_or_else(Debug::default_grid),
202 grid_step: grid_step.unwrap_or_else(Debug::default_grid_step),
203 color: color.unwrap_or_else(Debug::default_color),
204 })
205 }
206 }
207
208 deserializer.deserialize_any(DebugVisitor)
209 }
210}
211
212impl Default for Debug {
213 fn default() -> Self {
214 Self {
215 enable: false,
216 grid: Self::default_grid(),
217 grid_step: Self::default_grid_step(),
218 color: Self::default_color(),
219 }
220 }
221}
222
223#[cfg_attr(
225 feature = "pyo3",
226 pyclass(module = "img_gen", set_all, get_all, from_py_object)
227)]
228#[derive(Debug, Clone, Default, Serialize, Deserialize)]
229pub struct Layout {
230 #[serde(default)]
232 pub size: Size,
233
234 #[serde(default)]
236 pub layers: Vec<Layer>,
237
238 pub debug: Option<Debug>,
240}
241
242#[cfg(test)]
243mod tests {
244 #![allow(clippy::unwrap_used)]
245
246 use super::Debug;
247
248 fn assert_all_defaults(d: &Debug) {
249 assert_eq!(d.grid, Debug::default_grid());
250 assert_eq!(d.grid_step, Debug::default_grid_step());
251 assert_eq!(d.color.to_tuple(), Debug::default_color().to_tuple());
252 }
253
254 #[test]
255 fn debug_bool_true() {
256 let d: Debug = serde_saphyr::from_str("true").unwrap();
257 assert!(d.enable);
258 assert_all_defaults(&d);
259 }
260
261 #[test]
262 fn debug_bool_false() {
263 let d: Debug = serde_saphyr::from_str("false").unwrap();
264 assert!(!d.enable);
265 assert_all_defaults(&d);
266 }
267
268 #[test]
269 fn debug_map_full() {
270 let yaml = "enable: true\ngrid: false\ngrid_step: 50\ncolor: \"blue\"\n";
271 let d: Debug = serde_saphyr::from_str(yaml).unwrap();
272 assert!(d.enable);
273 assert!(!d.grid);
274 assert_eq!(d.grid_step, 50);
275 assert_eq!(d.color.to_tuple(), (0, 0, 255, 255));
276 }
277
278 #[test]
279 fn debug_map_defaults() {
280 let d: Debug = serde_saphyr::from_str("{}").unwrap();
281 assert!(!d.enable);
282 assert_all_defaults(&d);
283 }
284
285 #[test]
286 fn debug_map_unknown_field() {
287 let result: Result<Debug, _> = serde_saphyr::from_str("unknown_key: true\n");
288 assert!(result.is_err());
289 }
290
291 #[test]
292 fn debug_invalid_type() {
293 let result: Result<Debug, _> = serde_saphyr::from_str("42");
295 assert!(result.is_err());
296 }
297
298 #[test]
299 fn debug_foreground_dark_color() {
300 let d: Debug = serde_saphyr::from_str("color: \"black\"\n").unwrap();
303 assert_eq!(d.get_foreground_color().to_tuple(), (255, 255, 255, 255));
304 }
305
306 #[test]
307 fn debug_foreground_bright_color() {
308 let d: Debug = serde_saphyr::from_str("color: \"white\"\n").unwrap();
310 assert_eq!(d.get_foreground_color().to_tuple(), (0, 0, 0, 255));
311 }
312}