From d626898a5ceab97eaad04af202f18d7d1ebf0270 Mon Sep 17 00:00:00 2001 From: Erik Hedvall Date: Sat, 16 Nov 2024 20:42:13 +0100 Subject: [PATCH] Add `Rgb::from_hex` and implement parsing for `u16`, `u32`, `f32`, `f64` --- palette/src/color_difference.rs | 8 +- palette/src/okhsl.rs | 2 +- palette/src/okhsv.rs | 6 +- palette/src/oklab.rs | 4 +- palette/src/relative_contrast.rs | 8 +- palette/src/rgb.rs | 1 + palette/src/rgb/hex.rs | 69 ++++++ palette/src/rgb/rgb.rs | 407 +++++++++++++++++++++++++++---- palette/src/stimulus.rs | 31 ++- 9 files changed, 462 insertions(+), 74 deletions(-) create mode 100644 palette/src/rgb/hex.rs diff --git a/palette/src/color_difference.rs b/palette/src/color_difference.rs index e343a2221..7e80133b1 100644 --- a/palette/src/color_difference.rs +++ b/palette/src/color_difference.rs @@ -310,7 +310,7 @@ pub trait EuclideanDistance: Sized { /// /// // the rustdoc "DARK" theme background and text colors /// let background: Srgb = Srgb::from(0x353535).into_format(); -/// let foreground = Srgb::from_str("#ddd")?.into_format(); +/// let foreground = Srgb::from_str("#ddd")?; /// /// assert!(background.has_enhanced_contrast_text(foreground)); /// # Ok(()) @@ -515,7 +515,7 @@ mod test { black.relative_contrast(white) ); - let c1 = Srgb::from_str("#600").unwrap().into_format(); + let c1 = Srgb::from_str("#600").unwrap(); assert_relative_eq!(c1.relative_contrast(white), 13.41, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 1.56, epsilon = 0.01); @@ -532,12 +532,12 @@ mod test { assert!(!c1.has_enhanced_contrast_large_text(black)); assert!(!c1.has_min_contrast_graphics(black)); - let c1 = Srgb::from_str("#066").unwrap().into_format(); + let c1 = Srgb::from_str("#066").unwrap(); assert_relative_eq!(c1.relative_contrast(white), 6.79, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 3.09, epsilon = 0.01); - let c1 = Srgb::from_str("#9f9").unwrap().into_format(); + let c1 = Srgb::from_str("#9f9").unwrap(); assert_relative_eq!(c1.relative_contrast(white), 1.22, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 17.11, epsilon = 0.01); diff --git a/palette/src/okhsl.rs b/palette/src/okhsl.rs index f40e57311..ae66424c1 100644 --- a/palette/src/okhsl.rs +++ b/palette/src/okhsl.rs @@ -368,7 +368,7 @@ mod tests { #[test] fn test_srgb_to_okhsl() { let red_hex = "#834941"; - let rgb: Srgb = Srgb::from_str(red_hex).unwrap().into_format(); + let rgb: Srgb = Srgb::from_str(red_hex).unwrap(); let lin_rgb = LinSrgb::::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); println!( diff --git a/palette/src/okhsv.rs b/palette/src/okhsv.rs index d271e08b7..6081b77d5 100644 --- a/palette/src/okhsv.rs +++ b/palette/src/okhsv.rs @@ -292,8 +292,6 @@ mod tests { #[cfg(feature = "approx")] mod conversion { - use core::str::FromStr; - use crate::{ convert::FromColorUnclamped, encoding, rgb::Rgb, visual::VisuallyEqual, LinSrgb, Okhsv, Oklab, OklabHue, Srgb, @@ -403,9 +401,7 @@ mod tests { #[test] fn test_srgb_to_okhsv() { let red_hex = "#ff0004"; - let rgb: Srgb = Rgb::::from_str(red_hex) - .unwrap() - .into_format(); + let rgb: Srgb = red_hex.parse().unwrap(); let okhsv = Okhsv::from_color_unclamped(rgb); assert_relative_eq!(okhsv.saturation, 1.0, epsilon = 1e-3); assert_relative_eq!(okhsv.value, 1.0, epsilon = 1e-3); diff --git a/palette/src/oklab.rs b/palette/src/oklab.rs index 6501c250f..3e8b5f9d7 100644 --- a/palette/src/oklab.rs +++ b/palette/src/oklab.rs @@ -545,7 +545,7 @@ mod test { /// Asserts that, for any color space, the lightness of pure white is converted to `l == 1.0` #[test] fn lightness_of_white_is_one() { - let rgb: Srgb = Rgb::from_str("#ffffff").unwrap().into_format(); + let rgb: Srgb = Rgb::from_str("#ffffff").unwrap(); let lin_rgb = LinSrgb::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); println!("white {rgb:?} == {oklab:?}"); @@ -565,7 +565,7 @@ mod test { #[test] fn blue_srgb() { // use f64 to be comparable to javascript - let rgb: Srgb = Rgb::from_str("#0000ff").unwrap().into_format(); + let rgb: Srgb = Rgb::from_str("#0000ff").unwrap(); let lin_rgb = LinSrgb::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); diff --git a/palette/src/relative_contrast.rs b/palette/src/relative_contrast.rs index 727fbed98..58b13feda 100644 --- a/palette/src/relative_contrast.rs +++ b/palette/src/relative_contrast.rs @@ -26,7 +26,7 @@ use crate::{ /// /// // the rustdoc "DARK" theme background and text colors /// let background: Srgb = Srgb::from(0x353535).into_format(); -/// let foreground = Srgb::from_str("#ddd")?.into_format(); +/// let foreground = Srgb::from_str("#ddd")?; /// /// assert!(background.has_enhanced_contrast_text(foreground)); /// # Ok(()) @@ -145,7 +145,7 @@ mod test { black.get_contrast_ratio(white) ); - let c1 = Srgb::from_str("#600").unwrap().into_format(); + let c1 = Srgb::from_str("#600").unwrap(); assert_relative_eq!(c1.get_contrast_ratio(white), 13.41, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 1.56, epsilon = 0.01); @@ -162,12 +162,12 @@ mod test { assert!(!c1.has_enhanced_contrast_large_text(black)); assert!(!c1.has_min_contrast_graphics(black)); - let c1 = Srgb::from_str("#066").unwrap().into_format(); + let c1 = Srgb::from_str("#066").unwrap(); assert_relative_eq!(c1.get_contrast_ratio(white), 6.79, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 3.09, epsilon = 0.01); - let c1 = Srgb::from_str("#9f9").unwrap().into_format(); + let c1 = Srgb::from_str("#9f9").unwrap(); assert_relative_eq!(c1.get_contrast_ratio(white), 1.22, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 17.11, epsilon = 0.01); diff --git a/palette/src/rgb.rs b/palette/src/rgb.rs index d34d00b28..94b37f6a5 100644 --- a/palette/src/rgb.rs +++ b/palette/src/rgb.rs @@ -70,6 +70,7 @@ use crate::{ pub use self::rgb::{FromHexError, Iter, Rgb, Rgba}; pub mod channels; +mod hex; #[allow(clippy::module_inception)] mod rgb; diff --git a/palette/src/rgb/hex.rs b/palette/src/rgb/hex.rs new file mode 100644 index 000000000..589a53582 --- /dev/null +++ b/palette/src/rgb/hex.rs @@ -0,0 +1,69 @@ +use core::num::ParseIntError; + +#[inline] +pub(crate) fn rgb_from_hex_4bit(hex: &str) -> Result<(u8, u8, u8), ParseIntError> { + let red = u8::from_str_radix(&hex[..1], 16)?; + let green = u8::from_str_radix(&hex[1..2], 16)?; + let blue = u8::from_str_radix(&hex[2..3], 16)?; + + Ok((red * 17, green * 17, blue * 17)) +} + +#[inline] +pub(crate) fn rgba_from_hex_4bit(hex: &str) -> Result<(u8, u8, u8, u8), ParseIntError> { + let (red, green, blue) = rgb_from_hex_4bit(hex)?; + let alpha = u8::from_str_radix(&hex[3..4], 16)?; + + Ok((red, green, blue, alpha * 17)) +} + +#[inline] +pub(crate) fn rgb_from_hex_8bit(hex: &str) -> Result<(u8, u8, u8), ParseIntError> { + let red = u8::from_str_radix(&hex[..2], 16)?; + let green = u8::from_str_radix(&hex[2..4], 16)?; + let blue = u8::from_str_radix(&hex[4..6], 16)?; + + Ok((red, green, blue)) +} + +#[inline] +pub(crate) fn rgba_from_hex_8bit(hex: &str) -> Result<(u8, u8, u8, u8), ParseIntError> { + let (red, green, blue) = rgb_from_hex_8bit(hex)?; + let alpha = u8::from_str_radix(&hex[6..8], 16)?; + + Ok((red, green, blue, alpha)) +} + +#[inline] +pub(crate) fn rgb_from_hex_16bit(hex: &str) -> Result<(u16, u16, u16), ParseIntError> { + let red = u16::from_str_radix(&hex[..4], 16)?; + let green = u16::from_str_radix(&hex[4..8], 16)?; + let blue = u16::from_str_radix(&hex[8..12], 16)?; + + Ok((red, green, blue)) +} + +#[inline] +pub(crate) fn rgba_from_hex_16bit(hex: &str) -> Result<(u16, u16, u16, u16), ParseIntError> { + let (red, green, blue) = rgb_from_hex_16bit(hex)?; + let alpha = u16::from_str_radix(&hex[12..16], 16)?; + + Ok((red, green, blue, alpha)) +} + +#[inline] +pub(crate) fn rgb_from_hex_32bit(hex: &str) -> Result<(u32, u32, u32), ParseIntError> { + let red = u32::from_str_radix(&hex[..8], 16)?; + let green = u32::from_str_radix(&hex[8..16], 16)?; + let blue = u32::from_str_radix(&hex[16..24], 16)?; + + Ok((red, green, blue)) +} + +#[inline] +pub(crate) fn rgba_from_hex_32bit(hex: &str) -> Result<(u32, u32, u32, u32), ParseIntError> { + let (red, green, blue) = rgb_from_hex_32bit(hex)?; + let alpha = u32::from_str_radix(&hex[24..32], 16)?; + + Ok((red, green, blue, alpha)) +} diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index f91fb5046..342b82308 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -29,7 +29,13 @@ use crate::{ FromColor, GetHue, Hsl, Hsv, IntoColor, Luma, Oklab, RgbHue, Xyz, Yxy, }; -use super::Primaries; +use super::{ + hex::{ + rgb_from_hex_16bit, rgb_from_hex_32bit, rgb_from_hex_4bit, rgb_from_hex_8bit, + rgba_from_hex_16bit, rgba_from_hex_32bit, rgba_from_hex_4bit, rgba_from_hex_8bit, + }, + Primaries, +}; /// Generic RGB with an alpha component. See the [`Rgba` implementation in /// `Alpha`](crate::Alpha#Rgba). @@ -65,7 +71,12 @@ pub type Rgba = Alpha, T>; /// // ...or more explicitly like this: /// let rgb_u8_from_f32_2 = Srgb::new(0.3f32, 0.8, 0.1).into_format::(); /// -/// // Hexadecimal is also supported, with or without the #: +/// // Hexadecimal is also supported: +/// let rgb_from_hex_parse: Srgb = "#f034e6".parse().unwrap(); +/// let rgb_from_hex_ctor = Srgb::::from_hex("#f034e6").unwrap(); +/// assert_eq!(rgb_from_hex_parse, rgb_from_hex_ctor); +/// +/// // The # is optional: /// let rgb_from_hex1: Srgb = "#f034e6".parse().unwrap(); /// let rgb_from_hex2: Srgb = "f034e6".parse().unwrap(); /// assert_eq!(rgb_from_hex1, rgb_from_hex2); @@ -222,6 +233,40 @@ impl Rgb { } } + /// Parses a color hex code into an RGB value. + /// + /// ``` + /// use palette::Srgb; + /// + /// let rgb = Srgb::new(0xf0u8, 0x34, 0xe6); + /// let rgb_from_hex = Srgb::from_hex("#f034e6").unwrap(); + /// assert_eq!(rgb, rgb_from_hex); + /// ``` + /// + /// * Optional `#` at the beginning of the string. + /// * The accepted string length depends on the bit depth. + /// - `#f8b` and `#ff88bb` require 8 bits or higher. + /// - `#ffff8888bbbb` requires 16 bits or higher. + /// - `#ffffffff88888888bbbbbbbb` requires 32 bits or higher. + /// * `f32` accepts formats for `u16` or shorter. + /// * `f64` accepts formats for `u32` or shorter. + /// + /// This function does the same thing as `hex.parse()`: + /// + /// ``` + /// use palette::Srgb; + /// + /// let rgb_from_hex = Srgb::from_hex("#f034e6").unwrap(); + /// let rgb_from_hex_parse: Srgb = "#f034e6".parse().unwrap(); + /// assert_eq!(rgb_from_hex, rgb_from_hex_parse); + /// ``` + pub fn from_hex(hex: &str) -> Result::Err> + where + Self: FromStr, + { + hex.parse() + } + /// Convert the RGB components into another number type. /// /// ``` @@ -523,6 +568,40 @@ impl Alpha, A> { } } + /// Parses a color hex code into an RGBA value. + /// + /// ``` + /// use palette::Srgba; + /// + /// let rgba = Srgba::new(0xf0u8, 0x34, 0xe6, 0xff); + /// let rgba_from_hex = Srgba::from_hex("#f034e6ff").unwrap(); + /// assert_eq!(rgba, rgba_from_hex); + /// ``` + /// + /// * Optional `#` at the beginning of the string. + /// * The accepted string length depends on the bit depth. + /// - `#f8ba` and `#ff88bbaa` require 8 bits or higher. + /// - `#ffff8888bbbbaaaa` requires 16 bits or higher. + /// - `#ffffffff88888888bbbbbbbbaaaaaaaa` requires 32 bits or higher. + /// * `f32` accepts formats for `u16` or shorter. + /// * `f64` accepts formats for `u32` or shorter. + /// + /// This function does the same thing as `hex.parse()`: + /// + /// ``` + /// use palette::Srgba; + /// + /// let rgba_from_hex = Srgba::from_hex("#f034e6ff").unwrap(); + /// let rgba_from_hex_parse: Srgba = "#f034e6ff".parse().unwrap(); + /// assert_eq!(rgba_from_hex, rgba_from_hex_parse); + /// ``` + pub fn from_hex(hex: &str) -> Result::Err> + where + Self: FromStr, + { + hex.parse() + } + /// Convert the RGBA components into other number types. /// /// ``` @@ -1092,12 +1171,12 @@ impl core::fmt::Display for FromHexError { FromHexError::ParseIntError(e) => write!(f, "{}", e), FromHexError::HexFormatError(s) => write!( f, - "{}, please use format '#fff', 'fff', '#ffffff' or 'ffffff'.", + "{}, please use format '#fff', 'fff', '#ffffff', 'ffffff', etc.", s ), FromHexError::RgbaHexFormatError(s) => write!( f, - "{}, please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'.", + "{}, please use format '#ffff', 'ffff', '#ffffffff', 'ffffffff', etc.", s ), } @@ -1118,25 +1197,13 @@ impl std::error::Error for FromHexError { impl FromStr for Rgb { type Err = FromHexError; - /// Parses a color hex code of format '#ff00bb' or '#abc' (with or without the leading '#') into a - /// [`Rgb`] instance. + /// Parses a color hex code of format '#ff00bb' or '#abc' (with or without + /// the leading '#') into an [`Rgb`] value. fn from_str(hex: &str) -> Result { let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); match hex_code.len() { - 3 => { - let red = u8::from_str_radix(&hex_code[..1], 16)?; - let green = u8::from_str_radix(&hex_code[1..2], 16)?; - let blue = u8::from_str_radix(&hex_code[2..3], 16)?; - let col: Rgb = Rgb::new(red * 17, green * 17, blue * 17); - Ok(col) - } - 6 => { - let red = u8::from_str_radix(&hex_code[..2], 16)?; - let green = u8::from_str_radix(&hex_code[2..4], 16)?; - let blue = u8::from_str_radix(&hex_code[4..6], 16)?; - let col: Rgb = Rgb::new(red, green, blue); - Ok(col) - } + 3 => Ok(Self::from_components(rgb_from_hex_4bit(hex_code)?)), + 6 => Ok(Self::from_components(rgb_from_hex_8bit(hex_code)?)), _ => Err(FromHexError::HexFormatError("invalid hex code format")), } } @@ -1145,27 +1212,137 @@ impl FromStr for Rgb { impl FromStr for Rgba { type Err = FromHexError; - /// Parses a color hex code of format '#ff00bbff' or '#abcd' (with or without the leading '#') into a - /// [`Rgba`] instance. + /// Parses a color hex code of format '#ff00bbff' or '#abcd' (with or + /// without the leading '#') into an [`Rgba`] value. fn from_str(hex: &str) -> Result { let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); match hex_code.len() { - 4 => { - let red = u8::from_str_radix(&hex_code[..1], 16)?; - let green = u8::from_str_radix(&hex_code[1..2], 16)?; - let blue = u8::from_str_radix(&hex_code[2..3], 16)?; - let alpha = u8::from_str_radix(&hex_code[3..4], 16)?; - let col: Rgba = Rgba::new(red * 17, green * 17, blue * 17, alpha * 17); - Ok(col) - } - 8 => { - let red = u8::from_str_radix(&hex_code[..2], 16)?; - let green = u8::from_str_radix(&hex_code[2..4], 16)?; - let blue = u8::from_str_radix(&hex_code[4..6], 16)?; - let alpha = u8::from_str_radix(&hex_code[6..8], 16)?; - let col: Rgba = Rgba::new(red, green, blue, alpha); - Ok(col) - } + 4 => Ok(Self::from_components(rgba_from_hex_4bit(hex_code)?)), + 8 => Ok(Self::from_components(rgba_from_hex_8bit(hex_code)?)), + _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgb { + type Err = FromHexError; + + /// Parses a color hex code of format '#ffff0000bbbb', or shorter, (with or + /// without the leading '#') into an [`Rgb`] value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 3 | 6 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 12 => Ok(Self::from_components(rgb_from_hex_16bit(hex_code)?)), + _ => Err(FromHexError::HexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgba { + type Err = FromHexError; + + /// Parses a color hex code of format '#ffff0000bbbbffff', or shorter, (with + /// or without the leading '#') into an [`Rgba`] value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 4 | 8 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 16 => Ok(Self::from_components(rgba_from_hex_16bit(hex_code)?)), + _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgb { + type Err = FromHexError; + + /// Parses a color hex code of format '#ffffffff00000000bbbbbbbb', or + /// shorter, (with or without the leading '#') into an [`Rgb`] + /// value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 3 | 6 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 12 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 24 => Ok(Self::from_components(rgb_from_hex_32bit(hex_code)?)), + _ => Err(FromHexError::HexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgba { + type Err = FromHexError; + + /// Parses a color hex code of format '#ffffffff00000000bbbbbbbbffffffff', + /// or shorter, (with or without the leading '#') into an [`Rgba`] + /// value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 4 | 8 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 16 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 32 => Ok(Self::from_components(rgba_from_hex_32bit(hex_code)?)), + _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgb { + type Err = FromHexError; + + /// Parses a color hex code for 16 bit components or less into an [`Rgb`] value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 3 | 6 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 12 => Ok(Rgb::::from_str(hex_code)?.into_format()), + _ => Err(FromHexError::HexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgba { + type Err = FromHexError; + + /// Parses a color hex code for 16 bit components or less into an [`Rgba`] + /// value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 4 | 8 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 16 => Ok(Rgba::::from_str(hex_code)?.into_format()), + _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgb { + type Err = FromHexError; + + /// Parses a color hex code for 32 bit components or less into an [`Rgb`] value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 3 | 6 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 12 => Ok(Rgb::::from_str(hex_code)?.into_format()), + 24 => Ok(Rgb::::from_str(hex_code)?.into_format()), + _ => Err(FromHexError::HexFormatError("invalid hex code format")), + } + } +} + +impl FromStr for Rgba { + type Err = FromHexError; + + /// Parses a color hex code for 32 bit components or less into an [`Rgba`] + /// value. + fn from_str(hex: &str) -> Result { + let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); + match hex_code.len() { + 4 | 8 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 16 => Ok(Rgba::::from_str(hex_code)?.into_format()), + 32 => Ok(Rgba::::from_str(hex_code)?.into_format()), _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), } } @@ -1368,8 +1545,8 @@ unsafe impl bytemuck::Pod for Rgb where T: bytemuck::Pod {} mod test { use core::str::FromStr; - use crate::encoding::Srgb; use crate::rgb::channels; + use crate::{encoding::Srgb, rgb::FromHexError}; use super::{Rgb, Rgba}; @@ -1525,12 +1702,12 @@ mod test { } #[test] - fn from_str() { + fn from_str_u8() { let c = Rgb::::from_str("#ffffff"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); let c = Rgb::::from_str("#gggggg"); - assert!(c.is_err()); + assert!(matches!(c, Err(FromHexError::ParseIntError(_)))); let c = Rgb::::from_str("#fff"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); @@ -1538,16 +1715,12 @@ mod test { assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(0, 0, 0)); let c = Rgb::::from_str(""); - assert!(c.is_err()); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); let c = Rgb::::from_str("#123456"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(18, 52, 86)); let c = Rgb::::from_str("#iii"); - assert!(c.is_err()); - assert_eq!( - format!("{}", c.err().unwrap()), - "invalid digit found in string" - ); + assert!(matches!(c, Err(FromHexError::ParseIntError(_)))); let c = Rgb::::from_str("#08f"); assert_eq!(c.unwrap(), Rgb::::new(0, 136, 255)); let c = Rgb::::from_str("08f"); @@ -1555,11 +1728,11 @@ mod test { let c = Rgb::::from_str("ffffff"); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); let c = Rgb::::from_str("#12"); - assert!(c.is_err()); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); assert_eq!( format!("{}", c.err().unwrap()), "invalid hex code format, \ - please use format \'#fff\', \'fff\', \'#ffffff\' or \'ffffff\'." + please use format '#fff', 'fff', '#ffffff', 'ffffff', etc." ); let c = Rgb::::from_str("da0bce"); assert_eq!(c.unwrap(), Rgb::::new(218, 11, 206)); @@ -1580,23 +1753,149 @@ mod test { let c = Rgba::::from_str("#ffff"); assert_eq!(c.unwrap(), Rgba::::new(255, 255, 255, 255)); let c = Rgba::::from_str("#gggggggg"); - assert!(c.is_err()); + assert!(matches!(c, Err(FromHexError::ParseIntError(_)))); assert_eq!( format!("{}", c.err().unwrap()), "invalid digit found in string" ); let c = Rgba::::from_str("#fff"); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); assert_eq!( format!("{}", c.err().unwrap()), "invalid hex code format, \ - please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'." + please use format '#ffff', 'ffff', '#ffffffff', 'ffffffff', etc." ); let c = Rgba::::from_str("#ffffff"); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); + } + + #[test] + fn from_str_u16() { + let c = Rgb::::from_str("#f8b"); + assert_eq!(c.unwrap(), Rgb::::new(0xffff, 0x8888, 0xbbbb)); + let c = Rgb::::from_str("#ff88bb"); + assert_eq!(c.unwrap(), Rgb::::new(0xffff, 0x8888, 0xbbbb)); + let c = Rgb::::from_str("#ffff8888bbbb"); + assert_eq!(c.unwrap(), Rgb::::new(0xffff, 0x8888, 0xbbbb)); + let c = Rgb::::from_str("#ffffffff88888888bbbbbbbb"); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); + + let c = Rgba::::from_str("#f8ba"); assert_eq!( - format!("{}", c.err().unwrap()), - "invalid hex code format, \ - please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'." + c.unwrap(), + Rgba::::new(0xffff, 0x8888, 0xbbbb, 0xaaaa) + ); + let c = Rgba::::from_str("#ff88bbaa"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffff, 0x8888, 0xbbbb, 0xaaaa) + ); + let c = Rgba::::from_str("#ffff8888bbbbaaaa"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffff, 0x8888, 0xbbbb, 0xaaaa) + ); + let c = Rgba::::from_str("#ffffffff88888888bbbbbbbbaaaaaaaa"); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); + } + + #[test] + fn from_str_u32() { + let c = Rgb::::from_str("#f8b"); + assert_eq!( + c.unwrap(), + Rgb::::new(0xffffffff, 0x88888888, 0xbbbbbbbb) + ); + let c = Rgb::::from_str("#ff88bb"); + assert_eq!( + c.unwrap(), + Rgb::::new(0xffffffff, 0x88888888, 0xbbbbbbbb) + ); + let c = Rgb::::from_str("#ffff8888bbbb"); + assert_eq!( + c.unwrap(), + Rgb::::new(0xffffffff, 0x88888888, 0xbbbbbbbb) + ); + let c = Rgb::::from_str("#ffffffff88888888bbbbbbbb"); + assert_eq!( + c.unwrap(), + Rgb::::new(0xffffffff, 0x88888888, 0xbbbbbbbb) + ); + let c = Rgb::::from_str("#ffffffffffffffff8888888888888888bbbbbbbbbbbbbbbb"); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); + + let c = Rgba::::from_str("#f8ba"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffffffff, 0x88888888, 0xbbbbbbbb, 0xaaaaaaaa) + ); + let c = Rgba::::from_str("#ff88bbaa"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffffffff, 0x88888888, 0xbbbbbbbb, 0xaaaaaaaa) + ); + let c = Rgba::::from_str("#ffff8888bbbbaaaa"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffffffff, 0x88888888, 0xbbbbbbbb, 0xaaaaaaaa) + ); + let c = Rgba::::from_str("#ffffffff88888888bbbbbbbbaaaaaaaa"); + assert_eq!( + c.unwrap(), + Rgba::::new(0xffffffff, 0x88888888, 0xbbbbbbbb, 0xaaaaaaaa) + ); + let c = Rgba::::from_str( + "#ffffffffffffffff8888888888888888bbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa", + ); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); + } + + #[test] + fn from_str_f32() { + let c = Rgb::::from_str("#fff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffffffffff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffffffffffffffffffffff"); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); + + let c = Rgba::::from_str("#ffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffffffffffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffffffffffffffffffffffffffff"); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); + } + + #[test] + fn from_str_f64() { + let c = Rgb::::from_str("#fff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffffffffff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffffffffffffffffffffff"); + assert_eq!(c.unwrap(), Rgb::::new(1.0, 1.0, 1.0)); + let c = Rgb::::from_str("#ffffffffffffffffffffffffffffffffffffffffffffffff"); + assert!(matches!(c, Err(FromHexError::HexFormatError(_)))); + + let c = Rgba::::from_str("#ffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffffffffffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str("#ffffffffffffffffffffffffffffffff"); + assert_eq!(c.unwrap(), Rgba::::new(1.0, 1.0, 1.0, 1.0)); + let c = Rgba::::from_str( + "#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ); + assert!(matches!(c, Err(FromHexError::RgbaHexFormatError(_)))); } #[test] diff --git a/palette/src/stimulus.rs b/palette/src/stimulus.rs index c467ee264..ad4e56dd3 100644 --- a/palette/src/stimulus.rs +++ b/palette/src/stimulus.rs @@ -238,6 +238,26 @@ macro_rules! convert_uint_to_uint { }; } +macro_rules! convert_uint_to_larger_uint { + ($uint: ident; next $next: ident ($($other: ident),*)) => { + impl IntoStimulus<$next> for $uint { + #[inline] + fn into_stimulus(self) -> $next { + ((self as $next) << Self::BITS) | self as $next + } + } + + $( + impl IntoStimulus<$other> for $uint { + #[inline] + fn into_stimulus(self) -> $other { + $next::from_stimulus(self).into_stimulus() + } + } + )* + }; +} + impl IntoStimulus for f32 { #[inline] fn into_stimulus(self) -> f64 { @@ -254,16 +274,19 @@ impl IntoStimulus for f64 { } convert_double_to_uint!(f64; direct (u8, u16, u32, u64, u128);); -convert_uint_to_uint!(u8; via f32 (u16); via f64 (u32, u64, u128);); +convert_uint_to_larger_uint!(u8; next u16 (u32, u64, u128)); convert_uint_to_float!(u16; via f32 (f32); via f64 (f64);); -convert_uint_to_uint!(u16; via f32 (u8); via f64 (u32, u64, u128);); +convert_uint_to_uint!(u16; via f32 (u8);); +convert_uint_to_larger_uint!(u16; next u32 (u64, u128)); convert_uint_to_float!(u32; via f64 (f32, f64);); -convert_uint_to_uint!(u32; via f64 (u8, u16, u64, u128);); +convert_uint_to_uint!(u32; via f64 (u8, u16);); +convert_uint_to_larger_uint!(u32; next u64 (u128)); convert_uint_to_float!(u64; via f64 (f32, f64);); -convert_uint_to_uint!(u64; via f64 (u8, u16, u32, u128);); +convert_uint_to_uint!(u64; via f64 (u8, u16, u32);); +convert_uint_to_larger_uint!(u64; next u128 ()); convert_uint_to_float!(u128; via f64 (f32, f64);); convert_uint_to_uint!(u128; via f64 (u8, u16, u32, u64););