Simplified bevy_color Srgba hex string parsing (#12082)
# Objective - Simplify `Srgba` hex string parsing using std hex parsing functions and removing loops in favor of bitwise ops. This is a follow-up of the `bevy_color` upstream PR review: https://github.com/bevyengine/bevy/pull/12013#discussion_r1497408114 ## Solution - Reworked `Srgba::hex` to use `from_str_radix` and some bitwise ops; --------- Co-authored-by: Rob Parrett <robparrett@gmail.com>
This commit is contained in:
parent
78b5e49202
commit
bc2ddce432
@ -98,25 +98,32 @@ impl Srgba {
|
|||||||
let hex = hex.as_ref();
|
let hex = hex.as_ref();
|
||||||
let hex = hex.strip_prefix('#').unwrap_or(hex);
|
let hex = hex.strip_prefix('#').unwrap_or(hex);
|
||||||
|
|
||||||
match *hex.as_bytes() {
|
match hex.len() {
|
||||||
// RGB
|
// RGB
|
||||||
[r, g, b] => {
|
3 => {
|
||||||
let [r, g, b, ..] = decode_hex([r, r, g, g, b, b])?;
|
let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
|
||||||
Ok(Self::rgb_u8(r, g, b))
|
let (r, g, b) = (l & 0x0F, (b & 0xF0) >> 4, b & 0x0F);
|
||||||
|
Ok(Self::rgb_u8(r << 4 | r, g << 4 | g, b << 4 | b))
|
||||||
}
|
}
|
||||||
// RGBA
|
// RGBA
|
||||||
[r, g, b, a] => {
|
4 => {
|
||||||
let [r, g, b, a, ..] = decode_hex([r, r, g, g, b, b, a, a])?;
|
let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
|
||||||
Ok(Self::rgba_u8(r, g, b, a))
|
let (r, g, b, a) = ((l & 0xF0) >> 4, l & 0xF, (b & 0xF0) >> 4, b & 0x0F);
|
||||||
|
Ok(Self::rgba_u8(
|
||||||
|
r << 4 | r,
|
||||||
|
g << 4 | g,
|
||||||
|
b << 4 | b,
|
||||||
|
a << 4 | a,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
// RRGGBB
|
// RRGGBB
|
||||||
[r1, r2, g1, g2, b1, b2] => {
|
6 => {
|
||||||
let [r, g, b, ..] = decode_hex([r1, r2, g1, g2, b1, b2])?;
|
let [_, r, g, b] = u32::from_str_radix(hex, 16)?.to_be_bytes();
|
||||||
Ok(Self::rgb_u8(r, g, b))
|
Ok(Self::rgb_u8(r, g, b))
|
||||||
}
|
}
|
||||||
// RRGGBBAA
|
// RRGGBBAA
|
||||||
[r1, r2, g1, g2, b1, b2, a1, a2] => {
|
8 => {
|
||||||
let [r, g, b, a, ..] = decode_hex([r1, r2, g1, g2, b1, b2, a1, a2])?;
|
let [r, g, b, a] = u32::from_str_radix(hex, 16)?.to_be_bytes();
|
||||||
Ok(Self::rgba_u8(r, g, b, a))
|
Ok(Self::rgba_u8(r, g, b, a))
|
||||||
}
|
}
|
||||||
_ => Err(HexColorError::Length),
|
_ => Err(HexColorError::Length),
|
||||||
@ -304,44 +311,6 @@ impl From<Srgba> for Vec4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts hex bytes to an array of RGB\[A\] components
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// For RGB: *b"ffffff" -> [255, 255, 255, ..]
|
|
||||||
/// For RGBA: *b"E2E2E2FF" -> [226, 226, 226, 255, ..]
|
|
||||||
const fn decode_hex<const N: usize>(mut bytes: [u8; N]) -> Result<[u8; N], HexColorError> {
|
|
||||||
let mut i = 0;
|
|
||||||
while i < bytes.len() {
|
|
||||||
// Convert single hex digit to u8
|
|
||||||
let val = match hex_value(bytes[i]) {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(byte) => return Err(HexColorError::Char(byte as char)),
|
|
||||||
};
|
|
||||||
bytes[i] = val;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
// Modify the original bytes to give an `N / 2` length result
|
|
||||||
i = 0;
|
|
||||||
while i < bytes.len() / 2 {
|
|
||||||
// Convert pairs of u8 to R/G/B/A
|
|
||||||
// e.g `ff` -> [102, 102] -> [15, 15] = 255
|
|
||||||
bytes[i] = bytes[i * 2] * 16 + bytes[i * 2 + 1];
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a single hex digit (a-f/A-F/0-9) as a `u8`
|
|
||||||
const fn hex_value(b: u8) -> Result<u8, u8> {
|
|
||||||
match b {
|
|
||||||
b'0'..=b'9' => Ok(b - b'0'),
|
|
||||||
b'A'..=b'F' => Ok(b - b'A' + 10),
|
|
||||||
b'a'..=b'f' => Ok(b - b'a' + 10),
|
|
||||||
// Wrong hex digit
|
|
||||||
_ => Err(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::testing::assert_approx_eq;
|
use crate::testing::assert_approx_eq;
|
||||||
@ -408,10 +377,15 @@ mod tests {
|
|||||||
assert_eq!(Srgba::hex("000000FF"), Ok(Srgba::BLACK));
|
assert_eq!(Srgba::hex("000000FF"), Ok(Srgba::BLACK));
|
||||||
assert_eq!(Srgba::hex("03a9f4"), Ok(Srgba::rgb_u8(3, 169, 244)));
|
assert_eq!(Srgba::hex("03a9f4"), Ok(Srgba::rgb_u8(3, 169, 244)));
|
||||||
assert_eq!(Srgba::hex("yy"), Err(HexColorError::Length));
|
assert_eq!(Srgba::hex("yy"), Err(HexColorError::Length));
|
||||||
assert_eq!(Srgba::hex("yyy"), Err(HexColorError::Char('y')));
|
|
||||||
assert_eq!(Srgba::hex("#f2a"), Ok(Srgba::rgb_u8(255, 34, 170)));
|
assert_eq!(Srgba::hex("#f2a"), Ok(Srgba::rgb_u8(255, 34, 170)));
|
||||||
assert_eq!(Srgba::hex("#e23030"), Ok(Srgba::rgb_u8(226, 48, 48)));
|
assert_eq!(Srgba::hex("#e23030"), Ok(Srgba::rgb_u8(226, 48, 48)));
|
||||||
assert_eq!(Srgba::hex("#ff"), Err(HexColorError::Length));
|
assert_eq!(Srgba::hex("#ff"), Err(HexColorError::Length));
|
||||||
assert_eq!(Srgba::hex("##fff"), Err(HexColorError::Char('#')));
|
assert_eq!(Srgba::hex("11223344"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
|
||||||
|
assert_eq!(Srgba::hex("1234"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
|
||||||
|
assert_eq!(Srgba::hex("12345678"), Ok(Srgba::rgba_u8(18, 52, 86, 120)));
|
||||||
|
assert_eq!(Srgba::hex("4321"), Ok(Srgba::rgba_u8(68, 51, 34, 17)));
|
||||||
|
|
||||||
|
assert!(matches!(Srgba::hex("yyy"), Err(HexColorError::Parse(_))));
|
||||||
|
assert!(matches!(Srgba::hex("##fff"), Err(HexColorError::Parse(_))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1906,6 +1906,8 @@ impl encase::ShaderSize for LegacyColor {}
|
|||||||
|
|
||||||
#[derive(Debug, Error, PartialEq, Eq)]
|
#[derive(Debug, Error, PartialEq, Eq)]
|
||||||
pub enum HexColorError {
|
pub enum HexColorError {
|
||||||
|
#[error("Invalid hex string")]
|
||||||
|
Parse(#[from] std::num::ParseIntError),
|
||||||
#[error("Unexpected length of hex string")]
|
#[error("Unexpected length of hex string")]
|
||||||
Length,
|
Length,
|
||||||
#[error("Invalid hex char")]
|
#[error("Invalid hex char")]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user