1use std::fmt::{Debug, Display};
7
8use thiserror::Error;
9use tiny_skia::Pixmap;
10use tytanic_utils::fmt::Term;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct Size {
15 pub width: u32,
17
18 pub height: u32,
20}
21
22impl Display for Size {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "{}x{}", self.width, self.height)
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum Strategy {
31 Simple {
34 max_delta: u8,
38
39 max_deviation: usize,
42 },
43}
44
45impl Default for Strategy {
46 fn default() -> Self {
47 Self::Simple {
48 max_delta: 0,
49 max_deviation: 0,
50 }
51 }
52}
53
54pub fn page(output: &Pixmap, reference: &Pixmap, strategy: Strategy) -> Result<(), PageError> {
56 match strategy {
57 Strategy::Simple {
58 max_delta,
59 max_deviation,
60 } => page_simple(output, reference, max_delta, max_deviation),
61 }
62}
63
64fn page_simple(
66 output: &Pixmap,
67 reference: &Pixmap,
68 max_delta: u8,
69 max_deviation: usize,
70) -> Result<(), PageError> {
71 if output.width() != reference.width() || output.height() != reference.height() {
72 return Err(PageError::Dimensions {
73 output: Size {
74 width: output.width(),
75 height: output.height(),
76 },
77 reference: Size {
78 width: reference.width(),
79 height: reference.height(),
80 },
81 });
82 }
83
84 let deviations = Iterator::zip(output.pixels().iter(), reference.pixels().iter())
85 .filter(|(a, b)| {
86 u8::abs_diff(a.red(), b.red()) > max_delta
87 || u8::abs_diff(a.green(), b.green()) > max_delta
88 || u8::abs_diff(a.blue(), b.blue()) > max_delta
89 || u8::abs_diff(a.alpha(), b.alpha()) > max_delta
90 })
91 .count();
92
93 if deviations > max_deviation {
94 return Err(PageError::SimpleDeviations { deviations });
95 }
96
97 Ok(())
98}
99
100#[derive(Debug, Clone, Error)]
102pub struct Error {
103 pub output: usize,
105
106 pub reference: usize,
108
109 pub pages: Vec<(usize, PageError)>,
111}
112
113impl Display for Error {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 if self.output != self.reference {
116 write!(
117 f,
118 "page count differed (out {} != ref {})",
119 self.output, self.reference,
120 )?;
121 }
122
123 if self.output != self.reference && self.pages.is_empty() {
124 write!(f, " and ")?;
125 }
126
127 if self.pages.is_empty() {
128 write!(
129 f,
130 "{} {} differed at indices: {:?}",
131 self.pages.len(),
132 Term::simple("page").with(self.pages.len()),
133 self.pages.iter().map(|(n, _)| n).collect::<Vec<_>>()
134 )?;
135 }
136
137 Ok(())
138 }
139}
140
141#[derive(Debug, Clone, Error)]
143pub enum PageError {
144 #[error("dimensions differed: out {output} != ref {reference}")]
146 Dimensions {
147 output: Size,
149
150 reference: Size,
152 },
153
154 #[error(
156 "content differed in at least {} {}",
157 deviations,
158 Term::simple("pixel").with(*deviations)
159 )]
160 SimpleDeviations {
161 deviations: usize,
164 },
165}
166
167#[cfg(test)]
168mod tests {
169 use tiny_skia::PremultipliedColorU8;
170
171 use super::*;
172
173 fn images() -> [Pixmap; 2] {
174 let a = Pixmap::new(10, 1).unwrap();
175 let mut b = Pixmap::new(10, 1).unwrap();
176
177 let red = PremultipliedColorU8::from_rgba(128, 0, 0, 128).unwrap();
178 b.pixels_mut()[0] = red;
179 b.pixels_mut()[1] = red;
180 b.pixels_mut()[2] = red;
181 b.pixels_mut()[3] = red;
182
183 [a, b]
184 }
185
186 #[test]
187 fn test_page_simple_below_max_delta() {
188 let [a, b] = images();
189 assert!(page(
190 &a,
191 &b,
192 Strategy::Simple {
193 max_delta: 128,
194 max_deviation: 0,
195 },
196 )
197 .is_ok())
198 }
199
200 #[test]
201 fn test_page_simple_below_max_devitation() {
202 let [a, b] = images();
203 assert!(page(
204 &a,
205 &b,
206 Strategy::Simple {
207 max_delta: 0,
208 max_deviation: 5,
209 },
210 )
211 .is_ok());
212 }
213
214 #[test]
215 fn test_page_simple_above_max_devitation() {
216 let [a, b] = images();
217 assert!(matches!(
218 page(
219 &a,
220 &b,
221 Strategy::Simple {
222 max_delta: 0,
223 max_deviation: 0,
224 },
225 ),
226 Err(PageError::SimpleDeviations { deviations: 4 })
227 ))
228 }
229}