From d2213a7166a464ed674fe7456bd4dd437f2fb08a Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 24 Nov 2024 08:51:56 +0700 Subject: [PATCH] Add `BLACK`, `WHITE`, `TRANSPARENT` constants to color types. These are useful values to have without having to go to the palette. Additionally, this allows for getting the correct value for the colorspace for white without having to go through a conversion. Fixes #63. --- CHANGELOG.md | 8 ++++++ color/src/color.rs | 58 +++++++++++++++++++++++++++++++++++++ color/src/colorspace.rs | 64 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3348f47..da559f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,20 @@ You can find its changes [documented below](#010-2024-11-20). This release has an [MSRV][] of 1.82. +### Added + +* Add `BLACK`, `WHITE`, and `TRANSPARENT` constants to the color types. ([#64][] by [@waywardmonkeys][]) + ## [0.1.0][] (2024-11-20) This release has an [MSRV][] of 1.82. This is the initial release. +[@waywardmonkeys]: https://github.com/waywardmonkeys + +[#64]: https://github.com/linebender/color/pull/64 + [Unreleased]: https://github.com/linebender/color/compare/v0.1.0...HEAD [0.1.0]: https://github.com/linebender/color/releases/tag/v0.1.0 diff --git a/color/src/color.rs b/color/src/color.rs index 31f68b6..22ba77b 100644 --- a/color/src/color.rs +++ b/color/src/color.rs @@ -138,6 +138,20 @@ pub(crate) fn fixup_hues_for_interpolate( } impl OpaqueColor { + /// A black color. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const BLACK: Self = Self::new([0., 0., 0.]); + + /// A white color. + /// + /// This value is specific to the color space. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const WHITE: Self = Self::new(CS::WHITE_COMPONENTS); + /// Create a new color from the given components. pub const fn new(components: [f32; 3]) -> Self { let cs = PhantomData; @@ -292,6 +306,28 @@ pub(crate) const fn add_alpha([x, y, z]: [f32; 3], a: f32) -> [f32; 4] { } impl AlphaColor { + /// A black color. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const BLACK: Self = Self::new([0., 0., 0., 1.]); + + /// A transparent color. + /// + /// This is a black color with full alpha. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const TRANSPARENT: Self = Self::new([0., 0., 0., 0.]); + + /// A white color. + /// + /// This value is specific to the color space. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const WHITE: Self = Self::new(add_alpha(CS::WHITE_COMPONENTS, 1.)); + /// Create a new color from the given components. pub const fn new(components: [f32; 4]) -> Self { let cs = PhantomData; @@ -428,6 +464,28 @@ impl AlphaColor { } impl PremulColor { + /// A black color. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const BLACK: Self = Self::new([0., 0., 0., 1.]); + + /// A transparent color. + /// + /// This is a black color with full alpha. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const TRANSPARENT: Self = Self::new([0., 0., 0., 0.]); + + /// A white color. + /// + /// This value is specific to the color space. + /// + /// More comprehensive pre-defined colors are available + /// in the [`color::palette`](crate::palette) module. + pub const WHITE: Self = Self::new(add_alpha(CS::WHITE_COMPONENTS, 1.)); + /// Create a new color from the given components. pub const fn new(components: [f32; 4]) -> Self { let cs = PhantomData; diff --git a/color/src/colorspace.rs b/color/src/colorspace.rs index aed7d65..41053a7 100644 --- a/color/src/colorspace.rs +++ b/color/src/colorspace.rs @@ -41,6 +41,9 @@ pub trait ColorSpace: Clone + Copy + 'static { /// The tag corresponding to this color space, if a matching tag exists. const TAG: Option = None; + /// The component values for the color white within this color space. + const WHITE_COMPONENTS: [f32; 3]; + /// Convert an opaque color to linear sRGB. /// /// Values are likely to exceed [0, 1] for wide-gamut and HDR colors. @@ -157,6 +160,8 @@ impl ColorSpace for LinearSrgb { const TAG: Option = Some(ColorSpaceTag::LinearSrgb); + const WHITE_COMPONENTS: [f32; 3] = [1., 1., 1.]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { src } @@ -213,6 +218,8 @@ fn lin_to_srgb(x: f32) -> f32 { impl ColorSpace for Srgb { const TAG: Option = Some(ColorSpaceTag::Srgb); + const WHITE_COMPONENTS: [f32; 3] = [1., 1., 1.]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { src.map(srgb_to_lin) } @@ -260,6 +267,8 @@ pub struct DisplayP3; impl ColorSpace for DisplayP3 { const TAG: Option = Some(ColorSpaceTag::DisplayP3); + const WHITE_COMPONENTS: [f32; 3] = [0.99999994, 0.99999994, 0.99999994]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { const LINEAR_DISPLAYP3_TO_SRGB: [[f32; 3]; 3] = [ [1.224_940_2, -0.224_940_18, 0.0], @@ -303,6 +312,8 @@ pub struct A98Rgb; impl ColorSpace for A98Rgb { const TAG: Option = Some(ColorSpaceTag::A98Rgb); + const WHITE_COMPONENTS: [f32; 3] = [1., 1., 1.]; + fn to_linear_srgb([r, g, b]: [f32; 3]) -> [f32; 3] { // XYZ_to_lin_sRGB * lin_A98_to_XYZ #[expect( @@ -378,6 +389,8 @@ pub struct ProphotoRgb; impl ColorSpace for ProphotoRgb { const TAG: Option = Some(ColorSpaceTag::ProphotoRgb); + const WHITE_COMPONENTS: [f32; 3] = [1., 0.99999994, 1.]; + fn to_linear_srgb([r, g, b]: [f32; 3]) -> [f32; 3] { // XYZ_to_lin_sRGB * D50_to_D65 * lin_prophoto_to_XYZ const LINEAR_PROPHOTORGB_TO_SRGB: [[f32; 3]; 3] = [ @@ -449,6 +462,8 @@ impl Rec2020 { impl ColorSpace for Rec2020 { const TAG: Option = Some(ColorSpaceTag::Rec2020); + const WHITE_COMPONENTS: [f32; 3] = [1., 1., 1.]; + fn to_linear_srgb([r, g, b]: [f32; 3]) -> [f32; 3] { // XYZ_to_lin_sRGB * lin_Rec2020_to_XYZ #[expect( @@ -548,6 +563,8 @@ impl ColorSpace for XyzD50 { const TAG: Option = Some(ColorSpaceTag::XyzD50); + const WHITE_COMPONENTS: [f32; 3] = [0.9642956, 1., 0.8251046]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { // XYZ_to_lin_sRGB * D50_to_D65 const XYZ_TO_LINEAR_SRGB: [[f32; 3]; 3] = [ @@ -631,6 +648,8 @@ impl ColorSpace for XyzD65 { const TAG: Option = Some(ColorSpaceTag::XyzD65); + const WHITE_COMPONENTS: [f32; 3] = [0.9504559, 1., 1.0890577]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { const XYZ_TO_LINEAR_SRGB: [[f32; 3]; 3] = [ [3.240_97, -1.537_383_2, -0.498_610_76], @@ -706,6 +725,8 @@ const OKLAB_LMS_TO_LAB: [[f32; 3]; 3] = [ impl ColorSpace for Oklab { const TAG: Option = Some(ColorSpaceTag::Oklab); + const WHITE_COMPONENTS: [f32; 3] = [1., 0., 0.]; + fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { let lms = matmul(&OKLAB_LAB_TO_LMS, src).map(|x| x * x * x); matmul(&OKLAB_LMS_TO_SRGB, lms) @@ -769,6 +790,8 @@ impl ColorSpace for Oklch { const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::HueThird; + const WHITE_COMPONENTS: [f32; 3] = [1., 0., 90.]; + fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] { lab_to_lch(Oklab::from_linear_srgb(src)) } @@ -852,6 +875,8 @@ const KAPPA: f32 = 24389. / 27.; impl ColorSpace for Lab { const TAG: Option = Some(ColorSpaceTag::Lab); + const WHITE_COMPONENTS: [f32; 3] = [100., 0., 0.]; + fn to_linear_srgb([l, a, b]: [f32; 3]) -> [f32; 3] { let f1 = l * (1. / 116.) + (16. / 116.); let f0 = a * (1. / 500.) + f1; @@ -920,6 +945,8 @@ impl ColorSpace for Lch { const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::HueThird; + const WHITE_COMPONENTS: [f32; 3] = [100., 0., 0.]; + fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] { lab_to_lch(Lab::from_linear_srgb(src)) } @@ -1024,6 +1051,8 @@ impl ColorSpace for Hsl { const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::HueFirst; + const WHITE_COMPONENTS: [f32; 3] = [0., 0., 100.]; + fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] { let rgb = Srgb::from_linear_srgb(src); rgb_to_hsl(rgb, true) @@ -1109,6 +1138,8 @@ impl ColorSpace for Hwb { const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::HueFirst; + const WHITE_COMPONENTS: [f32; 3] = [0., 100., 0.]; + fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] { let rgb = Srgb::from_linear_srgb(src); rgb_to_hwb(rgb) @@ -1139,12 +1170,43 @@ impl ColorSpace for Hwb { #[cfg(test)] mod tests { - use crate::{A98Rgb, ColorSpace, OpaqueColor, ProphotoRgb, Rec2020, Srgb}; + use crate::{ + A98Rgb, ColorSpace, DisplayP3, Hsl, Hwb, Lab, Lch, LinearSrgb, Oklab, Oklch, OpaqueColor, + ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65, + }; fn almost_equal(col1: [f32; 3], col2: [f32; 3]) -> bool { OpaqueColor::::new(col1).difference(OpaqueColor::new(col2)) < 1e-4 } + #[test] + fn white_components() { + fn check_white() { + almost_equal::( + Srgb::WHITE_COMPONENTS, + CS::convert::(CS::WHITE_COMPONENTS), + ); + almost_equal::( + CS::WHITE_COMPONENTS, + Srgb::convert::(Srgb::WHITE_COMPONENTS), + ); + } + + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + check_white::(); + } + #[test] fn a98rgb_srgb() { for (srgb, a98) in [