mutils/
colour.rs

1use std::ops::Add;
2use std::ops::AddAssign;
3use std::ops::Div;
4use std::ops::DivAssign;
5use std::ops::Mul;
6use std::ops::MulAssign;
7use std::ops::Rem;
8use std::ops::Sub;
9use std::ops::SubAssign;
10
11mod colour_parse_error;
12pub use self::colour_parse_error::*;
13
14pub type Color = Colour;
15
16#[derive(Copy, Clone, Debug, PartialEq)]
17pub struct Colour {
18    rgba: u32,
19}
20
21impl Colour {
22    pub const WHITE: Colour = Colour::new_from_rgba(0xffffffff);
23    pub const LIGHT_GREY: Colour = Colour::new_from_rgba(0xC0C0C0ff);
24    pub const GREY: Colour = Colour::new_from_rgba(0x808080ff);
25    pub const DARK_GREY: Colour = Colour::new_from_rgba(0x404040ff);
26    pub const BLACK: Colour = Colour::new_from_rgba(0x000000ff);
27
28    pub const ORANGE_RED: Colour = Colour::new_from_rgba(0xff4500ff);
29    pub const ORANGE: Colour = Colour::new_from_rgba(0xffA500ff);
30
31    pub const DARK_RED: Colour = Colour::new_from_rgba(0x800000ff);
32    pub const RED: Colour = Colour::new_from_rgba(0xff0000ff);
33    pub const LIGHT_RED: Colour = Colour::new_from_rgba(0xff6666ff);
34
35    pub const DARK_GREEN: Colour = Colour::new_from_rgba(0x006400ff);
36    pub const GREEN: Colour = Colour::new_from_rgba(0x00ff00ff);
37    pub const LIGHT_GREEN: Colour = Colour::new_from_rgba(0x90EE90ff);
38
39    pub const DARK_BLUE: Colour = Colour::new_from_rgba(0x00008Bff);
40    pub const BLUE: Colour = Colour::new_from_rgba(0x0000ffff);
41    pub const LIGHT_BLUE: Colour = Colour::new_from_rgba(0xADD8E6ff);
42
43    pub const MAGENTA: Colour = Colour::new_from_rgba(0xff00ffff);
44    pub const CYAN: Colour = Colour::new_from_rgba(0x00ffffff);
45    pub const YELLOW: Colour = Colour::new_from_rgba(0xffff00ff);
46
47    /// Parses a string hex code into a `Colour`.
48    /// The hex code is expected to be 3 or 6 characters long,
49    /// using base 16 encoding.
50    ///
51    /// This supports the following RGB formats ...
52    ///  * 123
53    ///  * #123
54    ///  * 0x123
55    ///  * 112233
56    ///  * #112233
57    ///  * 0x112233
58    ///
59    /// And the following RGBA formats ...
60    ///  * 1234
61    ///  * #1234
62    ///  * 0x1234
63    ///  * 11223344
64    ///  * #11223344
65    ///  * 0x11223344
66    ///
67    pub fn from_hex_str(hex: &str) -> Result<Color, ColourParseError> {
68        if hex.len() <= 6 {
69            return Self::from_short_hex_str(hex);
70        }
71
72        Self::from_long_hex_str(hex)
73    }
74
75    fn from_short_hex_str(hex: &str) -> Result<Color, ColourParseError> {
76        let len = hex.len();
77
78        if len < 3 || 6 < len {
79            return Err(ColourParseError::InvalidFormat);
80        }
81
82        if let Some(first) = hex.get(0..1) {
83            if first == "#" {
84                return Self::from_short_hex_str(&hex[1..]);
85            }
86        }
87
88        if let Some(first) = hex.get(0..2) {
89            if first == "0x" || first == "0X" {
90                return Self::from_short_hex_str(&hex[2..]);
91            }
92        }
93
94        if len == 6 {
95            return Self::from_long_hex_str(hex);
96        }
97
98        let red = lengthen_short_hex_num(&hex.get(0..1).ok_or(ColourParseError::InvalidFormat)?)?;
99        let green = lengthen_short_hex_num(&hex.get(1..2).ok_or(ColourParseError::InvalidFormat)?)?;
100        let blue = lengthen_short_hex_num(&hex.get(2..3).ok_or(ColourParseError::InvalidFormat)?)?;
101        let alpha = match len {
102            3 => 255,
103            4 => lengthen_short_hex_num(&hex.get(3..4).ok_or(ColourParseError::InvalidFormat)?)?,
104            _ => return Err(ColourParseError::InvalidFormat),
105        };
106
107        Ok(Color::new_from_u8s(red, green, blue, alpha))
108    }
109
110    fn from_long_hex_str(hex: &str) -> Result<Color, ColourParseError> {
111        let len = hex.len();
112
113        if let Some(first) = hex.get(0..1) {
114            if first == "#" {
115                return Self::from_long_hex_str(&hex[1..]);
116            }
117        }
118
119        if let Some(first) = hex.get(0..2) {
120            if first == "0x" || first == "0X" {
121                return Self::from_long_hex_str(&hex[2..]);
122            }
123        }
124
125        let red = u8::from_str_radix(&hex.get(0..2).ok_or(ColourParseError::InvalidFormat)?, 16)?;
126        let green = u8::from_str_radix(&hex.get(2..4).ok_or(ColourParseError::InvalidFormat)?, 16)?;
127        let blue = u8::from_str_radix(&hex.get(4..6).ok_or(ColourParseError::InvalidFormat)?, 16)?;
128        let alpha = match len {
129            6 => 255,
130            8 => u8::from_str_radix(&hex.get(6..8).ok_or(ColourParseError::InvalidFormat)?, 16)?,
131            _ => return Err(ColourParseError::InvalidFormat),
132        };
133
134        Ok(Color::new_from_u8s(red, green, blue, alpha))
135    }
136
137    #[inline(always)]
138    fn hex_u32_to_f32(val: u32, shift: u32) -> f32 {
139        ((val >> shift) & 0xff) as f32 / 255.0
140    }
141
142    #[inline(always)]
143    fn hex_u32_to_u32(val: u32, shift: u32) -> u32 {
144        (val >> shift) & 0xff
145    }
146
147    #[inline(always)]
148    fn hex_u32_to_u8(val: u32, shift: u32) -> u8 {
149        ((val >> shift) & 0xff) as u8
150    }
151
152    #[inline(always)]
153    #[allow(dead_code)]
154    fn f32_to_hex_u32(val: f32, shift: u8) -> u32 {
155        ((val * 255.0).round() as u32) << shift
156    }
157
158    #[allow(dead_code)]
159    fn clamp_u8_to_f32(val: u8) -> f32 {
160        if val >= 255 {
161            1.0
162        } else if val == 0 {
163            0.0
164        } else {
165            (val as f32) / 255.0
166        }
167    }
168
169    #[allow(dead_code)]
170    fn f32_to_rgba_u8(val: f32) -> u8 {
171        if val >= 1.0 {
172            255
173        } else if val <= 0.0 {
174            0
175        } else {
176            (val * 255.0) as u8
177        }
178    }
179
180    fn f32_to_rgba_u32(val: f32) -> u32 {
181        if val >= 1.0 {
182            255
183        } else if val <= 0.0 {
184            0
185        } else {
186            (val * 255.0) as u32
187        }
188    }
189
190    const fn rgba_u32s_to_rgba_hex(red: u32, green: u32, blue: u32, alpha: u32) -> u32 {
191        (red << 24) | (green << 16) | (blue << 8) | alpha
192    }
193
194    const fn rgba_u8s_to_rgba_hex(red: u8, green: u8, blue: u8, alpha: u8) -> u32 {
195        ((red as u32) << 24) | ((green as u32) << 16) | ((blue as u32) << 8) | (alpha as u32)
196    }
197
198    pub fn new_from_f32s(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
199        let red = Colour::f32_to_rgba_u32(red);
200        let green = Colour::f32_to_rgba_u32(green);
201        let blue = Colour::f32_to_rgba_u32(blue);
202        let alpha = Colour::f32_to_rgba_u32(alpha);
203
204        Self {
205            rgba: Colour::rgba_u32s_to_rgba_hex(red, green, blue, alpha),
206        }
207    }
208
209    pub const fn new_from_u32s(red: u32, green: u32, blue: u32, alpha: u32) -> Self {
210        Self {
211            rgba: Colour::rgba_u32s_to_rgba_hex(red, green, blue, alpha),
212        }
213    }
214
215    pub const fn new_from_u8s(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
216        Self {
217            rgba: Colour::rgba_u8s_to_rgba_hex(red, green, blue, alpha),
218        }
219    }
220
221    pub const fn new_from_rgba(rgba: u32) -> Self {
222        Self { rgba }
223    }
224
225    pub fn red_f32(&self) -> f32 {
226        Colour::hex_u32_to_f32(self.rgba, 24)
227    }
228
229    pub fn red_u32(&self) -> u32 {
230        Colour::hex_u32_to_u32(self.rgba, 24)
231    }
232
233    pub fn red_u8(&self) -> u8 {
234        Colour::hex_u32_to_u8(self.rgba, 24)
235    }
236
237    pub fn green_f32(&self) -> f32 {
238        Colour::hex_u32_to_f32(self.rgba, 16)
239    }
240
241    pub fn green_u32(&self) -> u32 {
242        Colour::hex_u32_to_u32(self.rgba, 16)
243    }
244
245    pub fn green_u8(&self) -> u8 {
246        Colour::hex_u32_to_u8(self.rgba, 16)
247    }
248
249    pub fn blue_f32(&self) -> f32 {
250        Colour::hex_u32_to_f32(self.rgba, 8)
251    }
252
253    pub fn blue_u32(&self) -> u32 {
254        Colour::hex_u32_to_u32(self.rgba, 8)
255    }
256
257    pub fn blue_u8(&self) -> u8 {
258        Colour::hex_u32_to_u8(self.rgba, 8)
259    }
260
261    pub fn alpha_f32(&self) -> f32 {
262        Colour::hex_u32_to_f32(self.rgba, 0)
263    }
264
265    pub fn alpha_u32(&self) -> u32 {
266        Colour::hex_u32_to_u32(self.rgba, 0)
267    }
268
269    pub fn alpha_u8(&self) -> u8 {
270        Colour::hex_u32_to_u8(self.rgba, 0)
271    }
272
273    pub fn mix(self, other: Self, mut amount: f32) -> Self {
274        amount = amount.max(0.0).min(1.0);
275        let inverse_amount = 1.0 - amount;
276
277        (self * inverse_amount) + (other * amount)
278    }
279
280    pub fn mix_no_alpha(self, other: Self, mut amount: f32) -> Self {
281        amount = amount.max(0.0).min(1.0);
282        let inverse_amount = 1.0 - amount;
283
284        let mut result = (self * inverse_amount) + (other * amount);
285        result.rgba = (result.rgba & 0xffffff00) | self.alpha_u32();
286        result
287    }
288
289    pub fn replace_alpha_f32(self, alpha: f32) -> Self {
290        self.replace_alpha_u32(Colour::f32_to_rgba_u32(alpha))
291    }
292
293    pub fn replace_alpha_u32(mut self, alpha: u32) -> Self {
294        self.set_alpha_u32(alpha);
295        self
296    }
297
298    pub fn set_alpha_f32(&mut self, alpha: f32) {
299        self.set_alpha_u32(Colour::f32_to_rgba_u32(alpha));
300    }
301
302    pub fn set_alpha_u32(&mut self, alpha: u32) {
303        self.rgba = (self.rgba & 0xffffff00) | alpha;
304    }
305
306    #[inline(always)]
307    pub fn to_rgba_u32(self) -> u32 {
308        self.rgba
309    }
310}
311
312impl Add<Self> for Colour {
313    type Output = Self;
314
315    fn add(self, other: Self) -> Self {
316        Self::new_from_f32s(
317            self.red_f32() + other.red_f32(),
318            self.green_f32() + other.green_f32(),
319            self.blue_f32() + other.blue_f32(),
320            self.alpha_f32() + other.alpha_f32(),
321        )
322    }
323}
324
325impl AddAssign<Self> for Colour {
326    fn add_assign(&mut self, other: Self) {
327        *self = *self + other;
328    }
329}
330
331impl Sub<Self> for Colour {
332    type Output = Self;
333
334    fn sub(self, other: Self) -> Self {
335        Self::new_from_f32s(
336            self.red_f32() - other.red_f32(),
337            self.green_f32() - other.green_f32(),
338            self.blue_f32() - other.blue_f32(),
339            self.alpha_f32() - other.alpha_f32(),
340        )
341    }
342}
343
344impl SubAssign<Self> for Colour {
345    fn sub_assign(&mut self, other: Self) {
346        *self = *self - other;
347    }
348}
349
350impl Mul<Self> for Colour {
351    type Output = Self;
352
353    fn mul(self, other: Self) -> Self {
354        Self::new_from_f32s(
355            self.red_f32() * other.red_f32(),
356            self.green_f32() * other.green_f32(),
357            self.blue_f32() * other.blue_f32(),
358            self.alpha_f32() * other.alpha_f32(),
359        )
360    }
361}
362
363impl MulAssign<Self> for Colour {
364    fn mul_assign(&mut self, other: Self) {
365        *self = *self * other;
366    }
367}
368
369impl Div<Self> for Colour {
370    type Output = Self;
371
372    fn div(self, other: Self) -> Self {
373        Self::new_from_f32s(
374            self.red_f32() / other.red_f32(),
375            self.green_f32() / other.green_f32(),
376            self.blue_f32() / other.blue_f32(),
377            self.alpha_f32() / other.alpha_f32(),
378        )
379    }
380}
381
382impl DivAssign<Self> for Colour {
383    fn div_assign(&mut self, other: Self) {
384        *self = *self / other;
385    }
386}
387
388impl Rem<Self> for Colour {
389    type Output = Self;
390
391    fn rem(self, other: Self) -> Self {
392        Self::new_from_f32s(
393            self.red_f32() % other.red_f32(),
394            self.green_f32() % other.green_f32(),
395            self.blue_f32() % other.blue_f32(),
396            self.alpha_f32() % other.alpha_f32(),
397        )
398    }
399}
400
401impl Mul<f32> for Colour {
402    type Output = Self;
403
404    fn mul(self, val: f32) -> Self {
405        Self::new_from_f32s(
406            self.red_f32() * val,
407            self.green_f32() * val,
408            self.blue_f32() * val,
409            self.alpha_f32() * val,
410        )
411    }
412}
413
414impl MulAssign<f32> for Colour {
415    fn mul_assign(&mut self, val: f32) {
416        *self = *self * val;
417    }
418}
419
420impl Div<f32> for Colour {
421    type Output = Self;
422
423    fn div(self, val: f32) -> Self {
424        Self::new_from_f32s(
425            self.red_f32() / val,
426            self.green_f32() / val,
427            self.blue_f32() / val,
428            self.alpha_f32() / val,
429        )
430    }
431}
432
433impl DivAssign<f32> for Colour {
434    fn div_assign(&mut self, val: f32) {
435        *self = *self / val;
436    }
437}
438
439fn lengthen_short_hex_num(hex_num: &str) -> Result<u8, ColourParseError> {
440    let num = u8::from_str_radix(&hex_num, 16)?;
441    let lengthened_num = (num << 4) + num;
442
443    Ok(lengthened_num)
444}
445
446#[cfg(test)]
447mod red_xx {
448    use super::*;
449
450    #[test]
451    fn it_should_return_red_given() {
452        let rgba_hex = 0xffa89321;
453        let colour = Colour::new_from_rgba(rgba_hex);
454
455        assert_eq!(colour.red_f32(), 1.0);
456        assert_eq!(colour.red_u32(), 255);
457        assert_eq!(colour.red_u8(), 255);
458    }
459}
460
461#[cfg(test)]
462mod green_xx {
463    use super::*;
464
465    #[test]
466    fn it_should_return_green_given() {
467        let rgba_hex = 0xffa89321;
468        let colour = Colour::new_from_rgba(rgba_hex);
469
470        assert_eq!(colour.green_f32(), 168.0 / 255.0);
471        assert_eq!(colour.green_u32(), 168);
472        assert_eq!(colour.green_u8(), 168);
473    }
474}
475
476#[cfg(test)]
477mod blue_xx {
478    use super::*;
479
480    #[test]
481    fn it_should_return_blue_given() {
482        let rgba_hex = 0xffa89321;
483        let colour = Colour::new_from_rgba(rgba_hex);
484
485        assert_eq!(colour.blue_f32(), 147.0 / 255.0);
486        assert_eq!(colour.blue_u32(), 147);
487        assert_eq!(colour.blue_u8(), 147);
488    }
489}
490
491#[cfg(test)]
492mod alpha_xx {
493    use super::*;
494
495    #[test]
496    fn it_should_return_alpha_given() {
497        let rgba_hex = 0xffa89321;
498        let colour = Colour::new_from_rgba(rgba_hex);
499
500        assert_eq!(colour.alpha_f32(), 33.0 / 255.0);
501        assert_eq!(colour.alpha_u32(), 33);
502        assert_eq!(colour.alpha_u8(), 33);
503    }
504}
505
506#[cfg(test)]
507mod new_rgba_hex {
508    use super::*;
509
510    #[test]
511    fn it_should_have_components_match_those_given() {
512        let rgba_hex = 0xffa89321;
513        let colour = Colour::new_from_rgba(rgba_hex);
514
515        assert_eq!(colour.red_u32(), 255);
516        assert_eq!(colour.green_u32(), 168);
517        assert_eq!(colour.blue_u32(), 147);
518        assert_eq!(colour.alpha_u32(), 33);
519        assert_eq!(colour.to_rgba_u32(), rgba_hex);
520    }
521}
522
523#[cfg(test)]
524mod replace_alpha_f32 {
525    use super::*;
526
527    #[test]
528    fn it_should_replace_alpha_value() {
529        let rgba_hex = 0xffffff11;
530        let mut colour = Colour::new_from_rgba(rgba_hex);
531
532        colour = colour.replace_alpha_f32(0.0);
533        assert_eq!(colour.alpha_u8(), 0);
534
535        colour = colour.replace_alpha_f32(0.5);
536        assert_eq!(colour.alpha_u8(), 127);
537
538        colour = colour.replace_alpha_f32(1.0);
539        assert_eq!(colour.alpha_u8(), 255);
540    }
541
542    #[test]
543    fn it_should_not_replace_other_values() {
544        let rgba_hex = 0xffffff11;
545        let mut colour = Colour::new_from_rgba(rgba_hex);
546
547        colour = colour.replace_alpha_f32(0.0);
548        assert_eq!(colour.red_u8(), 255);
549        assert_eq!(colour.green_u8(), 255);
550        assert_eq!(colour.blue_u8(), 255);
551    }
552}
553
554#[cfg(test)]
555mod from_hex_str {
556    use super::*;
557    use testcat::*;
558
559    describe!("short hex codes", {
560        it!("should parse with no prefix", short_hex::test_no_prefix);
561        it!("should parse with hash prefix", short_hex::test_hash_prefix);
562        it!("should parse with 0x prefix", short_hex::test_zero_x_prefix);
563        it!(
564            "should parse with no prefix, with alpha",
565            short_hex::test_no_prefix_with_alpha
566        );
567        it!(
568            "should parse with hash prefix, with alpha",
569            short_hex::test_hash_prefix_with_alpha
570        );
571        it!(
572            "should parse with 0x prefix, with alpha",
573            short_hex::test_zero_x_prefix_with_alpha
574        );
575    });
576
577    describe!("long hex codes", {
578        it!("should parse with no prefix", long_hex::test_no_prefix);
579        it!("should parse with hash prefix", long_hex::test_hash_prefix);
580        it!("should parse with 0x prefix", long_hex::test_zero_x_prefix);
581        it!(
582            "should parse with no prefix, with alpha",
583            long_hex::test_no_prefix_with_alpha
584        );
585        it!(
586            "should parse with hash prefix, with alpha",
587            long_hex::test_hash_prefix_with_alpha
588        );
589        it!(
590            "should parse with 0x prefix, with alpha",
591            long_hex::test_zero_x_prefix_with_alpha
592        );
593    });
594
595    #[cfg(test)]
596    mod short_hex {
597        use super::*;
598
599        pub fn test_no_prefix() {
600            let colour = Colour::from_hex_str(&"12c").unwrap();
601
602            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
603        }
604
605        pub fn test_hash_prefix() {
606            let colour = Colour::from_hex_str(&"#12c").unwrap();
607
608            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
609        }
610
611        pub fn test_zero_x_prefix() {
612            let colour = Colour::from_hex_str(&"0x12c").unwrap();
613
614            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
615        }
616
617        pub fn test_no_prefix_with_alpha() {
618            let colour = Colour::from_hex_str(&"12c4").unwrap();
619
620            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
621        }
622
623        pub fn test_hash_prefix_with_alpha() {
624            let colour = Colour::from_hex_str(&"#12c4").unwrap();
625
626            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
627        }
628
629        pub fn test_zero_x_prefix_with_alpha() {
630            let colour = Colour::from_hex_str(&"0x12c4").unwrap();
631
632            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
633        }
634    }
635
636    #[cfg(test)]
637    mod long_hex {
638        use super::*;
639
640        pub fn test_no_prefix() {
641            let colour = Colour::from_hex_str(&"1122cc").unwrap();
642
643            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
644        }
645
646        pub fn test_hash_prefix() {
647            let colour = Colour::from_hex_str(&"#1122cc").unwrap();
648
649            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
650        }
651
652        pub fn test_zero_x_prefix() {
653            let colour = Colour::from_hex_str(&"0x1122cc").unwrap();
654
655            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 255));
656        }
657
658        pub fn test_no_prefix_with_alpha() {
659            let colour = Colour::from_hex_str(&"1122cc44").unwrap();
660
661            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
662        }
663
664        pub fn test_hash_prefix_with_alpha() {
665            let colour = Colour::from_hex_str(&"#1122cc44").unwrap();
666
667            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
668        }
669
670        pub fn test_zero_x_prefix_with_alpha() {
671            let colour = Colour::from_hex_str(&"0x1122cc44").unwrap();
672
673            assert_eq!(colour, Colour::new_from_u8s(17, 34, 204, 68));
674        }
675    }
676}