pixelflut_rs/
pixel.rs

1use std::fmt;
2use std::fmt::{Debug, Formatter};
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6use custom_error::custom_error;
7
8/// A Pixel!
9///
10/// It consist out of Coordinates which define where on the Grid it
11/// supposed to be and a Color which defines how it should look like.
12/// A Pixel can be send by clients to the server to draw it on the Grid.
13///
14/// # Example
15/// ```
16/// # use pixelflut_rs::pixel::{Pixel, Coordinate, Color};
17/// let pixel: Pixel = "PX 1024 768 ff0f00".parse().unwrap();
18/// assert_eq!(pixel, Pixel::new(Coordinate::new(1024, 768), Color::rgb(0xff, 0x0f, 0x00)));
19/// ```
20#[derive(Copy, Clone, PartialEq, Hash, Debug)]
21pub struct Pixel {
22    coordinate: Coordinate,
23    color: Color,
24}
25
26impl Pixel {
27    /// Creates a new Pixel with the given Coordinate and Color.
28    pub fn new(coordinate: Coordinate, color: Color) -> Pixel {
29        Pixel { coordinate, color }
30    }
31
32    /// Returns the Coordinates of this Pixel.
33    pub fn coordinate(&self) -> &Coordinate {
34        &self.coordinate
35    }
36
37    /// Returns the Color of this Pixel.
38    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        // First part should be 'PX'
56        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/// Coordinates to uniquely determine the position of a Pixel.
85///
86/// # Example
87/// ```
88/// # use pixelflut_rs::pixel::Coordinate;
89/// let coord: Coordinate = "PX 1024 768".parse().unwrap();
90/// assert_eq!(coord, Coordinate::new(1024, 768));
91/// ```
92#[derive(Copy, Clone, PartialEq, Hash, Debug)]
93pub struct Coordinate {
94    x: usize,
95    y: usize,
96}
97
98impl Coordinate {
99    /// Creates a new Coordinate for the given x and y values.
100    pub fn new(x: usize, y: usize) -> Coordinate {
101        Coordinate { x, y }
102    }
103
104    /// Returns the x value of this Coordinate.
105    pub fn x(&self) -> usize {
106        self.x
107    }
108
109    /// Returns the y value of this Coordinate.
110    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        // First part should be 'PX'
128        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/// The Color which is used for a Pixel.
153///
154/// You can create a Color as normal RGB or add an alpha channel to it.
155///
156/// # Example
157/// ## RGB Color
158/// ```
159/// # use pixelflut_rs::pixel::Color;
160/// let color: Color = "ff0f00".parse().unwrap();
161/// assert_eq!(color, Color::rgb(0xff, 0x0f, 0x00));
162/// assert!(color.is_rgb())
163/// ```
164/// ### RGBA Color
165/// ```
166/// # use pixelflut_rs::pixel::Color;
167/// let color: Color = "ff0f00aa".parse().unwrap();
168/// assert_eq!(color, Color::rgba(0xff, 0x0f, 0x00, 0xaa));
169/// assert!(color.is_rgba())
170/// ```
171#[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    /// Creates a new Color without an alpha channel.
181    ///
182    /// ```
183    /// # use pixelflut_rs::pixel::Color;
184    /// let color = Color::rgb(0xff, 0x0f, 0x00);
185    /// assert!(color.is_rgb());
186    /// assert!(!color.is_rgba());
187    /// ```
188    pub fn rgb(r: u8, g: u8, b: u8) -> Color {
189        Color { r, g, b, a: None }
190    }
191
192    /// Creates a new Color with an alpha channel.
193    ///
194    /// ```
195    /// # use pixelflut_rs::pixel::Color;
196    /// let color = Color::rgba(0xff, 0x0f, 0x00, 0xaa);
197    /// assert!(color.is_rgba());
198    /// assert!(!color.is_rgb());
199    /// ```
200    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    /// Returns the rgb values of this Color.
210    ///
211    /// ```
212    /// # use pixelflut_rs::pixel::Color;
213    /// assert_eq!((0xff, 0x0f, 0x00), Color::rgb(0xff, 0x0f, 0x00).rgb_values());
214    /// ```
215    pub fn rgb_values(&self) -> (u8, u8, u8) {
216        (self.r, self.g, self.b)
217    }
218
219    /// Returns the rgb and the alpha value of this Color.
220    ///
221    /// ```
222    /// # use pixelflut_rs::pixel::Color;
223    /// assert_eq!((0xff, 0x0f, 0x00, None), Color::rgb(0xff, 0x0f, 0x00).rgba_values());
224    /// assert_eq!((0xff, 0x0f, 0x00, Some(0xaa)), Color::rgba(0xff, 0x0f, 0x00, 0xaa).rgba_values());
225    /// ```
226    pub fn rgba_values(&self) -> (u8, u8, u8, Option<u8>) {
227        (self.r, self.g, self.b, self.a)
228    }
229
230    /// Returns `true` if this Color doesn't have an alpha channel.
231    ///
232    /// ```
233    /// # use pixelflut_rs::pixel::Color;
234    /// let color = Color::rgb(0xff, 0x0f, 0x00);
235    /// assert!(color.is_rgb());
236    /// let color = Color::rgba(0xff, 0x0f, 0x00, 0xaa);
237    /// assert!(!color.is_rgb())
238    /// ```
239    pub fn is_rgb(&self) -> bool {
240        self.a.is_none()
241    }
242
243    /// Returns `true` if this Color does have an alpha channel.
244    ///
245    /// ```
246    /// # use pixelflut_rs::pixel::Color;
247    /// let color = Color::rgb(0xff, 0x0f, 0x00);
248    /// assert!(!color.is_rgba());
249    /// let color = Color::rgba(0xff, 0x0f, 0x00, 0xaa);
250    /// assert!(color.is_rgba())
251    /// ```
252    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}