1use std::fmt;
2use std::fmt::{Debug, Formatter};
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6use custom_error::custom_error;
7
8#[derive(Copy, Clone, PartialEq, Hash, Debug)]
21pub struct Pixel {
22 coordinate: Coordinate,
23 color: Color,
24}
25
26impl Pixel {
27 pub fn new(coordinate: Coordinate, color: Color) -> Pixel {
29 Pixel { coordinate, color }
30 }
31
32 pub fn coordinate(&self) -> &Coordinate {
34 &self.coordinate
35 }
36
37 pub fn color(&self) -> Color {
39 self.color
40 }
41}
42
43impl fmt::Display for Pixel {
44 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45 write!(f, "{} {}", self.coordinate, self.color)
46 }
47}
48
49impl FromStr for Pixel {
50 type Err = ParsePixelError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 let mut parts = s.split_whitespace();
54
55 match parts.next() {
57 Some("PX") => (),
58 Some(_) => return Err(ParsePixelError::WrongFormat),
59 None => return Err(ParsePixelError::WrongFormat),
60 }
61
62 let pixel = Pixel::new(
63 Coordinate::new(
64 parts.next().ok_or(ParsePixelError::WrongFormat)?.parse()?,
65 parts.next().ok_or(ParsePixelError::WrongFormat)?.parse()?,
66 ),
67 parts.next().ok_or(ParsePixelError::WrongFormat)?.parse()?,
68 );
69
70 if parts.next().is_some() {
71 Err(ParsePixelError::WrongFormat)
72 } else {
73 Ok(pixel)
74 }
75 }
76}
77
78custom_error! {#[derive(PartialEq)] pub ParsePixelError
79 ParseInt{source: ParseIntError} = "no valid 32-bit integer found",
80 ParseColor{source: ParseColorError} = "failed to parse color",
81 WrongFormat = "the string has the wrong format"
82}
83
84#[derive(Copy, Clone, PartialEq, Hash, Debug)]
93pub struct Coordinate {
94 x: usize,
95 y: usize,
96}
97
98impl Coordinate {
99 pub fn new(x: usize, y: usize) -> Coordinate {
101 Coordinate { x, y }
102 }
103
104 pub fn x(&self) -> usize {
106 self.x
107 }
108
109 pub fn y(&self) -> usize {
111 self.y
112 }
113}
114
115impl fmt::Display for Coordinate {
116 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117 write!(f, "PX {} {}", self.x, self.y)
118 }
119}
120
121impl FromStr for Coordinate {
122 type Err = ParseCoordinateError;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 let mut parts = s.split_whitespace();
126
127 match parts.next() {
129 Some("PX") => (),
130 Some(_) => return Err(ParseCoordinateError::WrongFormat),
131 None => return Err(ParseCoordinateError::WrongFormat),
132 }
133
134 let coordinate = Coordinate::new(
135 parts.next().ok_or(ParseCoordinateError::WrongFormat)?.parse()?,
136 parts.next().ok_or(ParseCoordinateError::WrongFormat)?.parse()?,
137 );
138
139 if parts.next().is_some() {
140 Err(ParseCoordinateError::WrongFormat)
141 } else {
142 Ok(coordinate)
143 }
144 }
145}
146
147custom_error! {#[derive(PartialEq)] pub ParseCoordinateError
148 ParseInt{source: ParseIntError} = "no valid integer found",
149 WrongFormat = "the string has the wrong format"
150}
151
152#[derive(Copy, Clone, PartialEq, Hash, Debug)]
172pub struct Color {
173 r: u8,
174 g: u8,
175 b: u8,
176 a: Option<u8>,
177}
178
179impl Color {
180 pub fn rgb(r: u8, g: u8, b: u8) -> Color {
189 Color { r, g, b, a: None }
190 }
191
192 pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
201 Color {
202 r,
203 g,
204 b,
205 a: Some(a),
206 }
207 }
208
209 pub fn rgb_values(&self) -> (u8, u8, u8) {
216 (self.r, self.g, self.b)
217 }
218
219 pub fn rgba_values(&self) -> (u8, u8, u8, Option<u8>) {
227 (self.r, self.g, self.b, self.a)
228 }
229
230 pub fn is_rgb(&self) -> bool {
240 self.a.is_none()
241 }
242
243 pub fn is_rgba(&self) -> bool {
253 self.a.is_some()
254 }
255}
256
257impl fmt::Display for Color {
258 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
259 match self.a {
260 Some(a) => write!(f, "{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, a),
261 None => write!(f, "{:02x}{:02x}{:02x}", self.r, self.g, self.b),
262 }
263 }
264}
265
266impl FromStr for Color {
267 type Err = ParseColorError;
268 fn from_str(s: &str) -> Result<Self, Self::Err> {
269 match s.len() {
270 6 => Ok(Color::rgb(
271 u8::from_str_radix(&s[0..2], 16)?,
272 u8::from_str_radix(&s[2..4], 16)?,
273 u8::from_str_radix(&s[4..6], 16)?,
274 )),
275 8 => Ok(Color::rgba(
276 u8::from_str_radix(&s[0..2], 16)?,
277 u8::from_str_radix(&s[2..4], 16)?,
278 u8::from_str_radix(&s[4..6], 16)?,
279 u8::from_str_radix(&s[6..8], 16)?,
280 )),
281 _ => Err(ParseColorError::WrongSize),
282 }
283 }
284}
285
286custom_error! {#[derive(PartialEq)] pub ParseColorError
287 ParseInt{source: ParseIntError} = "no valid integer found",
288 WrongSize = "the string has the wrong number of digits"
289}
290
291#[cfg(test)]
292mod tests {
293 use crate::pixel::{Color, Coordinate, ParseColorError, ParseCoordinateError, ParsePixelError, Pixel};
294
295 #[test]
296 fn display_pixel() {
297 let px = Pixel::new(Coordinate::new(1024, 768), Color::rgb(0x00, 0xff, 0x00));
298 assert_eq!(px.to_string(), "PX 1024 768 00ff00")
299 }
300
301 #[test]
302 fn fromstr_pixel() {
303 let pixel: Pixel = "PX 1024 768 ff0f00".parse().unwrap();
304 assert_eq!(pixel, Pixel::new(Coordinate::new(1024, 768), Color::rgb(0xff, 0x0f, 0x00)));
305 let pixel: Result<Pixel, ParsePixelError> = "PX 1024 768 ff0f00 hallo".parse();
306 assert_eq!(pixel.unwrap_err(), ParsePixelError::WrongFormat);
307 let pixel: Result<Pixel, ParsePixelError> = "PX 1024 768".parse();
308 assert_eq!(pixel.unwrap_err(), ParsePixelError::WrongFormat);
309 let pixel: Result<Pixel, ParsePixelError> = "nope 1024 768 ff0f00".parse();
310 assert_eq!(pixel.unwrap_err(), ParsePixelError::WrongFormat);
311 }
312
313 #[test]
314 fn display_coordinate() {
315 let coord = Coordinate::new(1024, 768);
316 assert_eq!(coord.to_string(), "PX 1024 768")
317 }
318
319 #[test]
320 fn fromstr_coordinate() {
321 let coord: Coordinate = "PX 1024 768".parse().unwrap();
322 assert_eq!(coord, Coordinate::new(1024, 768));
323 let pixel: Result<Coordinate, ParseCoordinateError> = "PX 1024 768 ff0f00".parse();
324 assert_eq!(pixel.unwrap_err(), ParseCoordinateError::WrongFormat);
325 let pixel: Result<Coordinate, ParseCoordinateError> = "PX 1024".parse();
326 assert_eq!(pixel.unwrap_err(), ParseCoordinateError::WrongFormat);
327 let pixel: Result<Coordinate, ParseCoordinateError> = "nope 1024 768".parse();
328 assert_eq!(pixel.unwrap_err(), ParseCoordinateError::WrongFormat);
329 }
330
331 #[test]
332 fn display_color_rgb() {
333 let rgb = Color::rgb(0x00, 0x00, 0x00);
334 assert_eq!(format!("{}", rgb), "000000");
335 let rgb = Color::rgb(0x0f, 0x0f, 0x0f);
336 assert_eq!(format!("{}", rgb), "0f0f0f");
337 let rgb = Color::rgb(0xff, 0xff, 0xff);
338 assert_eq!(format!("{}", rgb), "ffffff");
339 }
340
341 #[test]
342 fn display_color_rgba() {
343 let rgba = Color::rgba(0x00, 0x00, 0x00, 0x00);
344 assert_eq!(format!("{}", rgba), "00000000");
345 let rgba = Color::rgba(0x0f, 0x0f, 0x0f, 0x0f);
346 assert_eq!(format!("{}", rgba), "0f0f0f0f");
347 let rgba = Color::rgba(0xff, 0xff, 0xff, 0xff);
348 assert_eq!(format!("{}", rgba), "ffffffff");
349 }
350
351 #[test]
352 fn fromstr_color() {
353 let color: Color = "ff0f00".parse().unwrap();
354 assert_eq!(color, Color::rgb(0xff, 0x0f, 0x00));
355 let color: Color = "ff0f00f0".parse().unwrap();
356 assert_eq!(color, Color::rgba(0xff, 0x0f, 0x00, 0xf0));
357 let color: Result<Color, ParseColorError> = "000000f".parse();
358 assert_eq!(color.unwrap_err(), ParseColorError::WrongSize);
359 }
360}