diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 68a8fc3d6..7a9cf72b7 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -1,4 +1,4 @@ -use crate::{Rgba, fast_round, linear_f32_from_linear_u8}; +use crate::{Rgba, fast_round, mul_frac_round}; /// This format is used for space-efficient color representation (32 bits). /// @@ -131,35 +131,10 @@ impl Color32 { /// but for transparent colors what you get back might be slightly different (rounding errors). #[inline] pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { - use std::sync::OnceLock; - match a { - // common-case optimization: - 0 => Self::TRANSPARENT, - - // common-case optimization: - 255 => Self::from_rgb(r, g, b), - - a => { - static LOOKUP_TABLE: OnceLock> = OnceLock::new(); - let lut = LOOKUP_TABLE.get_or_init(|| { - (0..=u16::MAX) - .map(|i| { - let [value, alpha] = i.to_ne_bytes(); - fast_round(value as f32 * linear_f32_from_linear_u8(alpha)) - }) - .collect() - }); - - let [r, g, b] = - [r, g, b].map(|value| lut[usize::from(u16::from_ne_bytes([value, a]))]); - Self::from_rgba_premultiplied(r, g, b, a) - } - } + Self::from_rgba_unmultiplied_const(r, g, b, a) } - /// Same as [`Self::from_rgba_unmultiplied`], but can be used in a const context. - /// - /// It is slightly slower when operating on non-const data. + /// This is the same as [`Self::from_rgba_unmultiplied`], but for const contexts. #[inline] pub const fn from_rgba_unmultiplied_const(r: u8, g: u8, b: u8, a: u8) -> Self { match a { @@ -170,9 +145,9 @@ impl Color32 { 255 => Self::from_rgb(r, g, b), a => { - let r = fast_round(r as f32 * linear_f32_from_linear_u8(a)); - let g = fast_round(g as f32 * linear_f32_from_linear_u8(a)); - let b = fast_round(b as f32 * linear_f32_from_linear_u8(a)); + let r = mul_frac_round(r, a); + let g = mul_frac_round(g, a); + let b = mul_frac_round(b, a); Self::from_rgba_premultiplied(r, g, b, a) } } @@ -535,4 +510,14 @@ mod test { Color32::from_rgba_unmultiplied(255, 0, 0, 128) ); } + + #[test] + fn mul_frac_round_vs_old() { + for x in (0..=255u8).step_by(4) { + for a in (1..=255u8).step_by(4) { + let old = fast_round(x as f32 * crate::linear_f32_from_linear_u8(a)); + assert_eq!(old, mul_frac_round(x, a)); + } + } + } } diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index ea7cff6f7..02ea96c51 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -134,6 +134,17 @@ const fn fast_round(r: f32) -> u8 { (r + 0.5) as _ // rust does a saturating cast since 1.45 } +/// Compute val * (frac/255) with no floating point or divisions. +#[inline] +const fn mul_frac_round(val: u8, frac: u8) -> u8 { + // Treat this as a simple fixed point calculation + let p = (val as u16) * (frac as u16) + 128; + ((p + (p >> 8)) >> 8) as u8 + // Logic split out a bit more. + //let p = (val as u16) * (frac as u16) + 127; // + 127 to round or remove to truncate. + //return ((p + 1 + (p >> 8)) >> 8) as u8; +} + #[test] pub fn test_srgba_conversion() { for b in 0..=255 {