xuko-misc 0.6.1

Miscellaneous utilities for xuko
Documentation
//! Color types

/// An error that occur when creating a [`RGBAColor`]
#[derive(Debug, thiserror::Error)]
pub enum ColorCreationError {
    /// Parse error
    #[error("parse error: {0}")]
    ParseError(String),
}

/// RGBA Color
///
/// # Examples
///
/// ```
/// use xuko_misc::color::RGBAColor;
/// let color = RGBAColor::from(0xFF33AA);
///
/// assert_eq!(color.r, 255);
/// assert_eq!(color.g, 51);
/// assert_eq!(color.b, 170);
/// assert_eq!(color.a, 255);
/// ```
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct RGBAColor {
    /// The red component of the [RGBAColor]
    pub r: u8,
    /// The green component of the [RGBAColor]
    pub g: u8,
    /// The blue component of the [RGBAColor]
    pub b: u8,
    /// The alpha component of the [RGBAColor]
    pub a: u8,
}

impl RGBAColor {
    /// Create a new [`RGBAColor`] from anything that implements [`Into`]<[`RGBAColor`]>
    pub fn new(c: impl Into<RGBAColor>) -> Self {
        c.into()
    }

    /// Create a new [`RGBAColor`] from anything that implements
    /// [`TryInto`]<[`RGBAColor`], Error = [`ColorCreationError`]>
    pub fn try_new(
        c: impl TryInto<RGBAColor, Error = ColorCreationError>,
    ) -> Result<Self, ColorCreationError> {
        c.try_into()
    }

    /// Add a color with another.
    pub const fn add(&self, other: &Self) -> Self {
        Self {
            r: self.r.saturating_add(other.r),
            g: self.g.saturating_add(other.g),
            b: self.b.saturating_add(other.b),
            a: self.a.saturating_add(other.a),
        }
    }

    /// Subtract a color from another.
    pub const fn sub(&self, other: &Self) -> Self {
        Self {
            r: self.r.saturating_sub(other.r),
            g: self.g.saturating_sub(other.g),
            b: self.b.saturating_sub(other.b),
            a: self.a.saturating_sub(other.a),
        }
    }

    /// Multiply a color with another.
    pub const fn mul(&self, other: &Self) -> Self {
        Self {
            r: self.r.saturating_mul(other.r),
            g: self.g.saturating_mul(other.g),
            b: self.b.saturating_mul(other.b),
            a: self.a.saturating_mul(other.a),
        }
    }

    /// Colorize color to another color.
    pub const fn colorize(&self, other: &Self) -> Self {
        let red = 255u8.wrapping_sub(other.r);
        let green = 255u8.wrapping_sub(other.g);
        let blue = 255u8.wrapping_sub(other.b);

        Self {
            r: self.r.saturating_sub(red),
            g: self.r.saturating_sub(green),
            b: self.r.saturating_sub(blue),
            a: self.a,
        }
    }
}

impl Default for RGBAColor {
    fn default() -> Self {
        Self {
            r: 0,
            g: 0,
            b: 0,
            a: 255,
        }
    }
}

impl From<u32> for RGBAColor {
    fn from(value: u32) -> Self {
        // color must be atleast triple hex
        // assert!(value >= 0x000000_u32);

        if value > 0xFFFFFF {
            Self {
                r: ((value >> 24) & 0xFF) as u8,
                g: ((value >> 16) & 0xFF) as u8,
                b: ((value >> 8) & 0xFF) as u8,
                a: (value & 0xFF) as u8,
            }
        } else {
            Self {
                r: ((value >> 16) & 0xFF) as u8,
                g: ((value >> 8) & 0xFF) as u8,
                b: (value & 0xFF) as u8,
                a: 255,
            }
        }
    }
}

impl TryFrom<&str> for RGBAColor {
    type Error = ColorCreationError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let s = value.strip_prefix('#').unwrap_or(value);

        u32::from_str_radix(s, 16)
            .map_err(|e| ColorCreationError::ParseError(e.to_string()))
            .map(|v| v.into())
    }
}

impl TryFrom<String> for RGBAColor {
    type Error = ColorCreationError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::try_new(&*value)
    }
}

impl From<[u8; 3]> for RGBAColor {
    fn from(value: [u8; 3]) -> Self {
        Self {
            r: value[0],
            g: value[1],
            b: value[2],
            a: 255,
        }
    }
}

impl From<[u8; 4]> for RGBAColor {
    fn from(value: [u8; 4]) -> Self {
        Self {
            r: value[0],
            g: value[1],
            b: value[2],
            a: value[3],
        }
    }
}

impl From<(u8, u8, u8)> for RGBAColor {
    fn from(value: (u8, u8, u8)) -> Self {
        Self {
            r: value.0,
            g: value.1,
            b: value.2,
            a: 255,
        }
    }
}

impl From<(u8, u8, u8, u8)> for RGBAColor {
    fn from(value: (u8, u8, u8, u8)) -> Self {
        Self {
            r: value.0,
            g: value.1,
            b: value.2,
            a: value.3,
        }
    }
}