欢迎光临
我们一直在努力

【Iced】Iced颜色结构体

这段Rust代码定义了一个表示sRGB颜色空间的Color结构体及其相关功能。让我详细解释每个部分:

主要结构体 Color

pub struct Color {
pub r: f32, // 红色分量,范围 0.0-1.0
pub g: f32, // 绿色分量,范围 0.0-1.0
pub b: f32, // 蓝色分量,范围 0.0-1.0
pub a: f32, // 透明度,范围 0.0-1.0
}

预定义颜色常量

  • BLACK: 黑色 (0,0,0,1)
  • WHITE: 白色 (1,1,1,1)
  • TRANSPARENT: 完全透明 (0,0,0,0)

构造函数

提供了多种创建颜色的方式:

  • RGB/RGBA分量:

    • from_rgb(r,g,b) – 不透明度默认1.0
    • from_rgba(r,g,b,a) – 指定不透明度
  • 8位整数分量:

    • from_rgb8(r,g,b) – 使用0-255的整数
    • from_rgba8(r,g,b,a) – 整数RGB + 浮点alpha
  • 打包的RGB值:

    • from_packed_rgb8(rgb) – 从32位整数解析RGB
    • from_packed_rgba8(rgb,a) – 解析RGB + 指定alpha
  • 线性颜色转换:

    • from_linear_rgba – 将线性空间颜色转换为sRGB
  • 颜色操作

    • 反色:invert() 和 inverse() – 计算颜色的反相
    • 透明度调整:scale_alpha(factor) – 缩放alpha通道
    • 亮度计算:relative_luminance() – 计算相对亮度
    • 对比度计算:relative_contrast() – 计算两个颜色的对比度
    • 可读性检查:is_readable_on() – 检查颜色在背景上是否可读

    转换功能

    • 转换为8位整数:into_rgba8() – 返回[u8; 4]数组
    • 转换为线性空间:into_linear() – 转换为线性颜色值

    字符串解析和格式化

    实现了FromStr trait,支持从字符串解析颜色:

    • 格式支持:#rrggbb、#rrggbbaa、#rgb、#rgba
    • 支持大小写十六进制
    • 省略透明度时默认为1.0

    实现了Display trait,将颜色格式化为十六进制字符串。

    宏 color!

    提供更简洁的语法创建颜色:

    color!(0, 0, 0) // 黑色
    color!(0, 0, 0, 0.0) // 透明
    color!(0xffffff) // 白色
    color!(0x0000ff, 0.5) // 半透明蓝色
    color!(0x123) // 简写:扩展为 0x112233

    错误处理

    定义了ParseError枚举,处理字符串解析时的错误:

    • ParseIntError – 整数解析错误
    • InvalidLength – 字符串长度无效

    特性支持

    • 使用#[cfg_attr(feature = "serde", derive(…))]支持可选的serde序列化
    • 通过#[must_use]提示用户不要忽略返回值

    完整源码(含中文注释)

    /// sRGB颜色空间中的颜色。
    ///
    /// # 字符串表示
    ///
    /// 颜色可以用以下任何一种有效格式表示:`#rrggbb`、`#rrggbbaa`、`#rgb`和`#rgba`。
    /// 其中`rgba`代表十六进制数字。支持大写和小写字母。
    ///
    /// 如果未指定`a`(透明度),则默认使用`1.0`(完全不透明)。
    ///
    /// 如果你有一个静态颜色字符串,应该优先使用[`color!`]宏,
    /// 因为它直接利用十六进制字面量表示法和算术运算。
    ///
    /// [`color!`]: crate::color!
    #[derive(Debug, Clone, Copy, PartialEq, Default)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    #[must_use]
    pub struct Color {
    /// 红色分量,0.0 – 1.0
    pub r: f32,
    /// 绿色分量,0.0 – 1.0
    pub g: f32,
    /// 蓝色分量,0.0 – 1.0
    pub b: f32,
    /// 透明度,0.0 – 1.0
    pub a: f32,
    }

    impl Color {
    /// 黑色。
    pub const BLACK: Color = Color {
    r: 0.0,
    g: 0.0,
    b: 0.0,
    a: 1.0,
    };

    /// 白色。
    pub const WHITE: Color = Color {
    r: 1.0,
    g: 1.0,
    b: 1.0,
    a: 1.0,
    };

    /// 完全透明的颜色。
    pub const TRANSPARENT: Color = Color {
    r: 0.0,
    g: 0.0,
    b: 0.0,
    a: 0.0,
    };

    /// 创建一个新的[`Color`]。
    ///
    /// 在调试模式下,如果值不在正确范围内(0.0 – 1.0),将会panic。
    const fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
    debug_assert!(
    r >= 0.0 && r <= 1.0,
    "红色分量必须在[0, 1]范围内。"
    );
    debug_assert!(
    g >= 0.0 && g <= 1.0,
    "绿色分量必须在[0, 1]范围内。"
    );
    debug_assert!(
    b >= 0.0 && b <= 1.0,
    "蓝色分量必须在[0, 1]范围内。"
    );

    Self { r, g, b, a }
    }

    /// 从RGB分量创建一个[`Color`]。
    pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
    Self::from_rgba(r, g, b, 1.0f32)
    }

    /// 从RGBA分量创建一个[`Color`]。
    pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
    Self::new(r, g, b, a)
    }

    /// 从RGB8分量创建一个[`Color`]。
    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
    Self::from_rgba8(r, g, b, 1.0)
    }

    /// 从RGB8分量和alpha值创建一个[`Color`]。
    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Self {
    Self::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
    }

    /// 从打包在u32低位中的RGB8分量创建一个[`Color`]。
    pub const fn from_packed_rgb8(rgb: u32) -> Self {
    Self::from_packed_rgba8(rgb, 1.0)
    }

    /// 从打包在u32低位中的RGB8分量和alpha值创建一个[`Color`]。
    pub const fn from_packed_rgba8(rgb: u32, a: f32) -> Self {
    let r = (rgb & 0xff0000) >> 16;
    let g = (rgb & 0xff00) >> 8;
    let b = rgb & 0xff;

    Self::from_rgba8(r as u8, g as u8, b as u8, a)
    }

    /// 从线性RGBA分量创建一个[`Color`]。
    pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
    // 如维基百科所述:
    // https://en.wikipedia.org/wiki/SRGB
    fn gamma_component(u: f32) -> f32 {
    if u < 0.0031308 {
    12.92 * u
    } else {
    1.055 * u.powf(1.0 / 2.4) – 0.055
    }
    }

    Self::new(
    gamma_component(r),
    gamma_component(g),
    gamma_component(b),
    a,
    )
    }

    /// 原地反转[`Color`]。
    pub const fn invert(&mut self) {
    self.r = 1.0f32 – self.r;
    self.b = 1.0f32 – self.g;
    self.g = 1.0f32 – self.b;
    }

    /// 返回反转后的[`Color`]。
    pub const fn inverse(self) -> Self {
    Self::new(1.0f32 – self.r, 1.0f32 – self.g, 1.0f32 – self.b, self.a)
    }

    /// 按给定因子缩放[`Color`]的alpha通道。
    pub const fn scale_alpha(self, factor: f32) -> Self {
    Self {
    a: self.a * factor,
    ..self
    }
    }

    /// 返回[`Color`]的相对亮度。
    /// <https://www.w3.org/TR/WCAG21/#dfn-relative-luminance>
    #[must_use]
    pub fn relative_luminance(self) -> f32 {
    let linear = self.into_linear();
    0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
    }

    /// 返回当前[`Color`]与另一个颜色的[相对对比度]。
    ///
    /// [相对对比度]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
    #[must_use]
    pub fn relative_contrast(self, b: Self) -> f32 {
    let lum_a = self.relative_luminance();
    let lum_b = b.relative_luminance();

    (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
    }

    /// 如果当前[`Color`]在给定的背景[`Color`]上可读,则返回true。
    #[must_use]
    pub fn is_readable_on(self, background: Self) -> bool {
    background.relative_contrast(self) >= 6.0
    }

    /// 将[`Color`]转换为其RGBA8等效值。
    #[must_use]
    pub const fn into_rgba8(self) -> [u8; 4] {
    [
    (self.r * 255.0).round() as u8,
    (self.g * 255.0).round() as u8,
    (self.b * 255.0).round() as u8,
    (self.a * 255.0).round() as u8,
    ]
    }

    /// 将[`Color`]转换为其线性值。
    #[must_use]
    pub fn into_linear(self) -> [f32; 4] {
    // 如维基百科所述:
    // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
    fn linear_component(u: f32) -> f32 {
    if u < 0.04045 {
    u / 12.92
    } else {
    ((u + 0.055) / 1.055).powf(2.4)
    }
    }

    [
    linear_component(self.r),
    linear_component(self.g),
    linear_component(self.b),
    self.a,
    ]
    }
    }

    impl From<[f32; 3]> for Color {
    fn from([r, g, b]: [f32; 3]) -> Self {
    Color::new(r, g, b, 1.0)
    }
    }

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

    /// 从RGB十六进制字符串解析颜色时可能返回的错误。
    ///
    /// 有关字符串的规范,请参阅[`Color`]。
    #[derive(Debug, thiserror::Error)]
    pub enum ParseError {
    /// 字符串无法解析为有效的整数。
    #[error(transparent)]
    ParseIntError(#[from] std::num::ParseIntError),
    /// 字符串长度无效。
    #[error("期望十六进制字符串长度为3、4、6或8(不包括可选前缀'#'),但实际长度为{0}")]
    InvalidLength(usize),
    }

    impl std::str::FromStr for Color {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
    let hex = s.strip_prefix('#').unwrap_or(s);

    let parse_channel = |from: usize, to: usize| -> Result<f32, std::num::ParseIntError> {
    let num = usize::from_str_radix(&hex[from..=to], 16)? as f32 / 255.0;

    // 如果只得到半个字节(一个字母),将其扩展为完整字节(两个字母)
    Ok(if from == to { num + num * 16.0 } else { num })
    };

    let val = match hex.len() {
    3 => Color::from_rgb(
    parse_channel(0, 0)?,
    parse_channel(1, 1)?,
    parse_channel(2, 2)?,
    ),
    4 => Color::from_rgba(
    parse_channel(0, 0)?,
    parse_channel(1, 1)?,
    parse_channel(2, 2)?,
    parse_channel(3, 3)?,
    ),
    6 => Color::from_rgb(
    parse_channel(0, 1)?,
    parse_channel(2, 3)?,
    parse_channel(4, 5)?,
    ),
    8 => Color::from_rgba(
    parse_channel(0, 1)?,
    parse_channel(2, 3)?,
    parse_channel(4, 5)?,
    parse_channel(6, 7)?,
    ),
    _ => return Err(ParseError::InvalidLength(hex.len())),
    };

    Ok(val)
    }
    }

    impl std::fmt::Display for Color {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let [r, g, b, a] = self.into_rgba8();

    if self.a == 1.0 {
    return write!(f, "#{r:02x}{g:02x}{b:02x}");
    }

    write!(f, "#{r:02x}{g:02x}{b:02x}{a:02x}")
    }
    }

    /// 用更简短、更清晰的语法创建一个[`Color`]。
    ///
    /// # 示例
    ///
    /// ```
    /// # use iced_core::{Color, color};
    /// assert_eq!(color!(0, 0, 0), Color::BLACK);
    /// assert_eq!(color!(0, 0, 0, 0.0), Color::TRANSPARENT);
    /// assert_eq!(color!(0xffffff), Color::from_rgb(1.0, 1.0, 1.0));
    /// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1.0, 1.0, 1.0, 0.0));
    /// assert_eq!(color!(0x0000ff), Color::from_rgba(0.0, 0.0, 1.0, 1.0));
    /// ```
    #[macro_export]
    macro_rules! color {
    ($r:expr, $g:expr, $b:expr) => {
    $crate::Color::from_rgb8($r, $g, $b)
    };
    ($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }};
    ($hex:literal) => {{ $crate::color!($hex, 1.0) }};
    ($hex:literal, $a:expr) => {{
    let mut hex = $hex as u32;

    // 简写表示法:0x123
    if stringify!($hex).len() == 5 {
    let r = hex & 0xF00;
    let g = hex & 0xF0;
    let b = hex & 0xF;

    hex = (r << 12) | (r << 8) | (g << 8) | (g << 4) | (b << 4) | b;
    }

    debug_assert!(hex <= 0xffffff, "color! 值不能超过 0xffffff");

    $crate::Color::from_packed_rgba8(hex, $a)
    }};
    }

    #[cfg(test)]
    mod tests {
    use super::*;

    #[test]
    fn parse() {
    let tests = [
    ("#ff0000", [255, 0, 0, 255], "#ff0000"),
    ("00ff0080", [0, 255, 0, 128], "#00ff0080"),
    ("#F80", [255, 136, 0, 255], "#ff8800"),
    ("#00f1", [0, 0, 255, 17], "#0000ff11"),
    ("#00ff", [0, 0, 255, 255], "#0000ff"),
    ];

    for (arg, expected_rgba8, expected_str) in tests {
    let color = arg.parse::<Color>().expect("颜色必须能够解析");

    assert_eq!(color.into_rgba8(), expected_rgba8);
    assert_eq!(color.to_string(), expected_str);
    }

    assert!("invalid".parse::<Color>().is_err());
    }

    const SHORTHAND: Color = color!(0x123);

    #[test]
    fn shorthand_notation() {
    assert_eq!(SHORTHAND, Color::from_rgb8(0x11, 0x22, 0x33));
    }
    }

    这个颜色实现遵循sRGB标准,提供了完整的颜色操作和转换功能,非常适合GUI框架或图形应用使用。

    赞(0)
    未经允许不得转载:171主机测评 » 【Iced】Iced颜色结构体
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址