vsvg 0.5.0

Core library for pen-plotter graphics.
Documentation
use std::fmt;

#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
    pub a: u8,
}

impl Color {
    pub const BLACK: Self = Self::rgb(0, 0, 0);
    pub const DARK_GRAY: Self = Self::rgb(96, 96, 96);
    pub const GRAY: Self = Self::rgb(160, 160, 160);
    pub const LIGHT_GRAY: Self = Self::rgb(220, 220, 220);
    pub const WHITE: Self = Self::rgb(255, 255, 255);
    pub const BROWN: Self = Self::rgb(165, 42, 42);
    pub const DARK_RED: Self = Self::rgb(0x8B, 0, 0);
    pub const RED: Self = Self::rgb(255, 0, 0);
    pub const LIGHT_RED: Self = Self::rgb(255, 128, 128);
    pub const YELLOW: Self = Self::rgb(255, 255, 0);
    pub const LIGHT_YELLOW: Self = Self::rgb(255, 255, 0xE0);
    pub const KHAKI: Self = Self::rgb(240, 230, 140);
    pub const DARK_GREEN: Self = Self::rgb(0, 0x64, 0);
    pub const GREEN: Self = Self::rgb(0, 255, 0);
    pub const LIGHT_GREEN: Self = Self::rgb(0x90, 0xEE, 0x90);
    pub const DARK_BLUE: Self = Self::rgb(0, 0, 0x8B);
    pub const BLUE: Self = Self::rgb(0, 0, 255);
    pub const LIGHT_BLUE: Self = Self::rgb(0xAD, 0xD8, 0xE6);
    pub const GOLD: Self = Self::rgb(255, 215, 0);

    #[must_use]
    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self { r, g, b, a }
    }

    #[must_use]
    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b, a: 0xFF }
    }

    #[must_use]
    pub const fn gray(v: u8) -> Self {
        Self {
            r: v,
            g: v,
            b: v,
            a: 0xFF,
        }
    }

    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_sign_loss)]
    #[must_use]
    pub fn with_opacity(&self, opacity: f32) -> Self {
        Self {
            r: self.r,
            g: self.g,
            b: self.b,
            a: (opacity * 255.0) as u8,
        }
    }

    #[must_use]
    pub const fn to_rgba(&self) -> u32 {
        self.r as u32 | (self.g as u32) << 8 | (self.b as u32) << 16 | (self.a as u32) << 24
    }

    #[must_use]
    pub fn to_rgb_string(&self) -> String {
        format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
    }

    #[must_use]
    pub fn opacity(&self) -> f32 {
        f32::from(self.a) / 255.0
    }
}

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

impl fmt::Display for Color {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "#{:02x}{:02x}{:02x}{:02x}",
            self.r, self.g, self.b, self.a
        )
    }
}

impl From<[f32; 4]> for Color {
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    fn from(value: [f32; 4]) -> Self {
        Self {
            r: (value[0].clamp(0., 1.) * 255.0) as u8,
            g: (value[1].clamp(0., 1.) * 255.0) as u8,
            b: (value[2].clamp(0., 1.) * 255.0) as u8,
            a: (value[3].clamp(0., 1.) * 255.0) as u8,
        }
    }
}

impl From<Color> for [f32; 4] {
    fn from(value: Color) -> Self {
        [
            f32::from(value.r) / 255.0,
            f32::from(value.g) / 255.0,
            f32::from(value.b) / 255.0,
            f32::from(value.a) / 255.0,
        ]
    }
}

pub const COLORS: [Color; 19] = [
    Color::BLACK,
    Color::DARK_GRAY,
    Color::GRAY,
    Color::LIGHT_GRAY,
    Color::WHITE,
    Color::BROWN,
    Color::DARK_RED,
    Color::RED,
    Color::LIGHT_RED,
    Color::YELLOW,
    Color::LIGHT_YELLOW,
    Color::KHAKI,
    Color::DARK_GREEN,
    Color::GREEN,
    Color::LIGHT_GREEN,
    Color::DARK_BLUE,
    Color::BLUE,
    Color::LIGHT_BLUE,
    Color::GOLD,
];