diff --git a/components/style/color/mix.rs b/components/style/color/mix.rs index 90755bbd7f2..8d114324956 100644 --- a/components/style/color/mix.rs +++ b/components/style/color/mix.rs @@ -4,18 +4,13 @@ //! Color mixing/interpolation. -use super::ColorSpace; +use super::{AbsoluteColor, ColorComponents, ColorSpace}; use crate::parser::{Parse, ParserContext}; use crate::values::animated::color::AnimatedRGBA as RGBA; use cssparser::Parser; -use euclid::default::{Transform3D, Vector3D}; -use std::f32::consts::PI; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; -const RAD_PER_DEG: f32 = PI / 180.0; -const DEG_PER_RAD: f32 = 180.0 / PI; - /// A hue-interpolation-method as defined in [1]. /// /// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method @@ -140,12 +135,12 @@ trait ModelledColor: Clone + Copy + From + Into { /// Mix two colors into one. pub fn mix( interpolation: &ColorInterpolationMethod, - left_color: &RGBA, + left_color: &AbsoluteColor, mut left_weight: f32, - right_color: &RGBA, + right_color: &AbsoluteColor, mut right_weight: f32, normalize_weights: bool, -) -> RGBA { +) -> AbsoluteColor { // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm let mut alpha_multiplier = 1.0; if normalize_weights { @@ -160,26 +155,8 @@ pub fn mix( } } - let mix_function = match interpolation.space { - ColorSpace::Srgb => mix_in::, - ColorSpace::SrgbLinear => mix_in::, - ColorSpace::XyzD65 => mix_in::, - ColorSpace::XyzD50 => mix_in::, - ColorSpace::Lab => mix_in::, - ColorSpace::Hwb => mix_in::, - ColorSpace::Hsl => mix_in::, - ColorSpace::Lch => mix_in::, - - ColorSpace::Oklab | - ColorSpace::Oklch | - ColorSpace::DisplayP3 | - ColorSpace::A98Rgb | - ColorSpace::ProphotoRgb | - ColorSpace::Rec2020 => { - todo!() - }, - }; - mix_function( + mix_in( + interpolation.space, left_color, left_weight, right_color, @@ -189,34 +166,49 @@ pub fn mix( ) } -fn mix_in( - left_color: &RGBA, +fn mix_in( + color_space: ColorSpace, + left_color: &AbsoluteColor, left_weight: f32, - right_color: &RGBA, + right_color: &AbsoluteColor, right_weight: f32, hue_interpolation: HueInterpolationMethod, alpha_multiplier: f32, -) -> RGBA -where - S: ModelledColor, -{ - let left = S::from(*left_color); - let right = S::from(*right_color); +) -> AbsoluteColor { + // Convert both colors into the interpolation color space. + let left = left_color.to_color_space(color_space); + let left = left.raw_components(); - let color = S::lerp(&left, left_weight, &right, right_weight, hue_interpolation); - let mut rgba = RGBA::from(color.into()); - if alpha_multiplier != 1.0 { - rgba.alpha *= alpha_multiplier; - } + let right = right_color.to_color_space(color_space); + let right = right.raw_components(); + + let result = interpolate_premultiplied( + &left, + left_weight, + &right, + right_weight, + color_space.hue_index(), + hue_interpolation, + ); + + let alpha = if alpha_multiplier != 1.0 { + result[3] * alpha_multiplier + } else { + result[3] + }; // FIXME: In rare cases we end up with 0.999995 in the alpha channel, // so we reduce the precision to avoid serializing to // rgba(?, ?, ?, 1). This is not ideal, so we should look into // ways to avoid it. Maybe pre-multiply all color components and // then divide after calculations? - rgba.alpha = (rgba.alpha * 1000.0).round() / 1000.0; + let alpha = (alpha * 1000.0).round() / 1000.0; - rgba + AbsoluteColor::new( + color_space, + ColorComponents(result[0], result[1], result[2]), + alpha, + ) } fn interpolate_premultiplied_component( @@ -232,6 +224,7 @@ fn interpolate_premultiplied_component( } // Normalize hue into [0, 360) +#[inline] fn normalize_hue(v: f32) -> f32 { v - 360. * (v / 360.).floor() } @@ -352,642 +345,3 @@ fn interpolate_premultiplied( result } - -macro_rules! impl_lerp { - ($ty:ident, $hue_index:expr) => { - // These ensure the transmutes below are sound. - const_assert_eq!(std::mem::size_of::<$ty>(), std::mem::size_of::() * 4); - const_assert_eq!(std::mem::align_of::<$ty>(), std::mem::align_of::()); - impl ModelledColor for $ty { - fn lerp( - left: &Self, - left_weight: f32, - right: &Self, - right_weight: f32, - hue_interpolation: HueInterpolationMethod, - ) -> Self { - use std::mem::transmute; - unsafe { - transmute::<[f32; 4], Self>(interpolate_premultiplied( - transmute::<&Self, &[f32; 4]>(left), - left_weight, - transmute::<&Self, &[f32; 4]>(right), - right_weight, - $hue_index, - hue_interpolation, - )) - } - } - } - }; -} - -impl_lerp!(RGBA, None); - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct LinearRGBA { - red: f32, - green: f32, - blue: f32, - alpha: f32, -} - -impl_lerp!(LinearRGBA, None); - -/// An animated XYZ D65 colour. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct XYZD65A { - x: f32, - y: f32, - z: f32, - alpha: f32, -} - -impl_lerp!(XYZD65A, None); - -/// An animated XYZ D50 colour. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct XYZD50A { - x: f32, - y: f32, - z: f32, - alpha: f32, -} - -impl_lerp!(XYZD50A, None); - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct LABA { - pub lightness: f32, - pub a: f32, - pub b: f32, - pub alpha: f32, -} - -impl_lerp!(LABA, None); - -/// An animated LCHA colour. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct LCHA { - pub lightness: f32, - pub chroma: f32, - pub hue: f32, - pub alpha: f32, -} - -impl_lerp!(LCHA, Some(2)); - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct OKLABA { - pub lightness: f32, - pub a: f32, - pub b: f32, - pub alpha: f32, -} - -impl_lerp!(OKLABA, None); - -/// An animated OKLCHA colour. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct OKLCHA { - pub lightness: f32, - pub chroma: f32, - pub hue: f32, - pub alpha: f32, -} - -impl_lerp!(OKLCHA, Some(2)); - -/// An animated hwb() color. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct HWBA { - hue: f32, - white: f32, - black: f32, - alpha: f32, -} - -impl_lerp!(HWBA, Some(0)); - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct HSLA { - hue: f32, - sat: f32, - light: f32, - alpha: f32, -} - -impl_lerp!(HSLA, Some(0)); - -// https://drafts.csswg.org/css-color/#rgb-to-hsl -// -// We also return min/max for the hwb conversion. -fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) { - let RGBA { - red, - green, - blue, - alpha, - } = rgba; - let max = red.max(green).max(blue); - let min = red.min(green).min(blue); - let mut hue = std::f32::NAN; - let mut sat = 0.; - let light = (min + max) / 2.; - let d = max - min; - - if d != 0. { - sat = if light == 0.0 || light == 1.0 { - 0. - } else { - (max - light) / light.min(1. - light) - }; - - if max == red { - hue = (green - blue) / d + if green < blue { 6. } else { 0. } - } else if max == green { - hue = (blue - red) / d + 2.; - } else { - hue = (red - green) / d + 4.; - } - - hue *= 60.; - } - - ( - HSLA { - hue, - sat, - light, - alpha, - }, - min, - max, - ) -} - -impl From for HSLA { - fn from(rgba: RGBA) -> Self { - rgb_to_hsl(rgba).0 - } -} - -impl From for RGBA { - fn from(hsla: HSLA) -> Self { - // cssparser expects hue in the 0..1 range. - let hue_normalized = normalize_hue(hsla.hue) / 360.; - let (r, g, b) = cssparser::hsl_to_rgb(hue_normalized, hsla.sat, hsla.light); - RGBA::new(r, g, b, hsla.alpha) - } -} - -impl From for HWBA { - // https://drafts.csswg.org/css-color/#rgb-to-hwb - fn from(rgba: RGBA) -> Self { - let (hsl, min, max) = rgb_to_hsl(rgba); - Self { - hue: hsl.hue, - white: min, - black: 1. - max, - alpha: rgba.alpha, - } - } -} - -impl From for RGBA { - fn from(hwba: HWBA) -> Self { - let hue_normalized = normalize_hue(hwba.hue) / 360.; - let (r, g, b) = cssparser::hwb_to_rgb(hue_normalized, hwba.white, hwba.black); - RGBA::new(r, g, b, hwba.alpha) - } -} - -impl From for LinearRGBA { - fn from(rgba: RGBA) -> Self { - fn linearize(value: f32) -> f32 { - let sign = if value < 0. { -1. } else { 1. }; - let abs = value.abs(); - if abs < 0.04045 { - return value / 12.92; - } - - sign * ((abs + 0.055) / 1.055).powf(2.4) - } - Self { - red: linearize(rgba.red), - green: linearize(rgba.green), - blue: linearize(rgba.blue), - alpha: rgba.alpha, - } - } -} - -impl From for RGBA { - fn from(lrgba: LinearRGBA) -> Self { - fn delinearize(value: f32) -> f32 { - let sign = if value < 0. { -1. } else { 1. }; - let abs = value.abs(); - - if abs > 0.0031308 { - sign * (1.055 * abs.powf(1. / 2.4) - 0.055) - } else { - 12.92 * value - } - } - Self { - red: delinearize(lrgba.red), - green: delinearize(lrgba.green), - blue: delinearize(lrgba.blue), - alpha: lrgba.alpha, - } - } -} - -impl From for XYZD50A { - fn from(d65: XYZD65A) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - #[cfg_attr(rustfmt, rustfmt_skip)] - const BRADFORD: Transform3D = Transform3D::new( - 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0., - 0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0., - -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., - 0., 0., 0., 1., - ); - - let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z)); - Self { - x: d50.x, - y: d50.y, - z: d50.z, - alpha: d65.alpha, - } - } -} - -impl From for XYZD65A { - fn from(d50: XYZD50A) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - #[cfg_attr(rustfmt, rustfmt_skip)] - const BRADFORD_INVERSE: Transform3D = Transform3D::new( - 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0., - -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0., - 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0., - 0., 0., 0., 1., - ); - let d65 = BRADFORD_INVERSE.transform_vector3d(Vector3D::new(d50.x, d50.y, d50.z)); - Self { - x: d65.x, - y: d65.y, - z: d65.z, - alpha: d50.alpha, - } - } -} - -impl From for XYZD65A { - fn from(lrgba: LinearRGBA) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - #[cfg_attr(rustfmt, rustfmt_skip)] - const LSRGB_TO_XYZ: Transform3D = Transform3D::new( - 0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0., - 0.357584339383878, 0.715168678767756, 0.11919477979462598, 0., - 0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0., - 0., 0., 0., 1., - ); - let linear_rgb = Vector3D::new(lrgba.red, lrgba.green, lrgba.blue); - let xyz = LSRGB_TO_XYZ.transform_vector3d(linear_rgb); - Self { - x: xyz.x, - y: xyz.y, - z: xyz.z, - alpha: lrgba.alpha, - } - } -} - -impl From for LinearRGBA { - fn from(d65: XYZD65A) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - #[cfg_attr(rustfmt, rustfmt_skip)] - const XYZ_TO_LSRGB: Transform3D = Transform3D::new( - 3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0., - -1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0., - -0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0., - 0., 0., 0., 1., - ); - - let xyz = Vector3D::new(d65.x, d65.y, d65.z); - let rgb = XYZ_TO_LSRGB.transform_vector3d(xyz); - Self { - red: rgb.x, - green: rgb.y, - blue: rgb.z, - alpha: d65.alpha, - } - } -} - -impl From for RGBA { - fn from(d65: XYZD65A) -> Self { - Self::from(LinearRGBA::from(d65)) - } -} - -impl From for XYZD65A { - /// Convert an RGBA colour to XYZ as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab - fn from(rgba: RGBA) -> Self { - Self::from(LinearRGBA::from(rgba)) - } -} - -impl From for LABA { - /// Convert an XYZ colour to LAB as specified in [1] and [2]. - /// - /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab - /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(xyza: XYZD50A) -> Self { - const WHITE: [f32; 3] = [0.96422, 1., 0.82521]; - - fn compute_f(value: f32) -> f32 { - const EPSILON: f32 = 216. / 24389.; - const KAPPA: f32 = 24389. / 27.; - - if value > EPSILON { - value.cbrt() - } else { - (KAPPA * value + 16.) / 116. - } - } - - // 4. Convert D50-adapted XYZ to Lab. - let f = [ - compute_f(xyza.x / WHITE[0]), - compute_f(xyza.y / WHITE[1]), - compute_f(xyza.z / WHITE[2]), - ]; - - let lightness = 116. * f[1] - 16.; - let a = 500. * (f[0] - f[1]); - let b = 200. * (f[1] - f[2]); - - LABA { - lightness, - a, - b, - alpha: xyza.alpha, - } - } -} - -impl From for LCHA { - /// Convert a LAB color to LCH as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(laba: LABA) -> Self { - let hue = laba.b.atan2(laba.a) * DEG_PER_RAD; - let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt(); - LCHA { - lightness: laba.lightness, - chroma, - hue, - alpha: laba.alpha, - } - } -} - -impl From for OKLCHA { - /// Convert an OKLAB color to OKLCH as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(oklaba: OKLABA) -> Self { - let hue = oklaba.b.atan2(oklaba.a) * DEG_PER_RAD; - let chroma = (oklaba.a * oklaba.a + oklaba.b * oklaba.b).sqrt(); - OKLCHA { - lightness: oklaba.lightness, - chroma, - hue, - alpha: oklaba.alpha, - } - } -} - -impl From for LABA { - /// Convert a LCH color to LAB as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(lcha: LCHA) -> Self { - let hue_radians = lcha.hue * RAD_PER_DEG; - let a = lcha.chroma * hue_radians.cos(); - let b = lcha.chroma * hue_radians.sin(); - LABA { - lightness: lcha.lightness, - a, - b, - alpha: lcha.alpha, - } - } -} - -impl From for OKLABA { - /// Convert a OKLCH color to OKLAB as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(oklcha: OKLCHA) -> Self { - let hue_radians = oklcha.hue * RAD_PER_DEG; - let a = oklcha.chroma * hue_radians.cos(); - let b = oklcha.chroma * hue_radians.sin(); - OKLABA { - lightness: oklcha.lightness, - a, - b, - alpha: oklcha.alpha, - } - } -} - -impl From for XYZD50A { - /// Convert a CIELAB color to XYZ as specified in [1] and [2]. - /// - /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined - /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(laba: LABA) -> Self { - // 1. Convert LAB to (D50-adapated) XYZ. - const KAPPA: f32 = 24389. / 27.; - const EPSILON: f32 = 216. / 24389.; - const WHITE: [f32; 3] = [0.96422, 1., 0.82521]; - - let f1 = (laba.lightness + 16f32) / 116f32; - let f0 = (laba.a / 500.) + f1; - let f2 = f1 - laba.b / 200.; - - let x = if f0.powf(3.) > EPSILON { - f0.powf(3.) - } else { - (116. * f0 - 16.) / KAPPA - }; - let y = if laba.lightness > KAPPA * EPSILON { - ((laba.lightness + 16.) / 116.).powf(3.) - } else { - laba.lightness / KAPPA - }; - let z = if f2.powf(3.) > EPSILON { - f2.powf(3.) - } else { - (116. * f2 - 16.) / KAPPA - }; - - Self { - x: x * WHITE[0], - y: y * WHITE[1], - z: z * WHITE[2], - alpha: laba.alpha, - } - } -} - -impl From for OKLABA { - fn from(xyza: XYZD65A) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - #[cfg_attr(rustfmt, rustfmt_skip)] - const XYZ_TO_LMS: Transform3D = Transform3D::new( - 0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0., - 0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0., - -0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0., - 0., 0., 0., 1., - ); - - #[cfg_attr(rustfmt, rustfmt_skip)] - const LMS_TO_OKLAB: Transform3D = Transform3D::new( - 0.2104542553, 1.9779984951, 0.0259040371, 0., - 0.7936177850, -2.4285922050, 0.7827717662, 0., - -0.0040720468, 0.4505937099, -0.8086757660, 0., - 0., 0., 0., 1., - ); - - let lms = XYZ_TO_LMS.transform_vector3d(Vector3D::new(xyza.x, xyza.y, xyza.z)); - let lab = LMS_TO_OKLAB.transform_vector3d(Vector3D::new( - lms.x.cbrt(), - lms.y.cbrt(), - lms.z.cbrt(), - )); - - Self { - lightness: lab.x, - a: lab.y, - b: lab.z, - alpha: xyza.alpha, - } - } -} - -impl From for XYZD65A { - fn from(oklaba: OKLABA) -> Self { - // https://drafts.csswg.org/css-color-4/#color-conversion-code - // Given OKLab, convert to XYZ relative to D65 - #[cfg_attr(rustfmt, rustfmt_skip)] - const LMS_TO_XYZ: Transform3D = Transform3D::new( - 1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0., - -0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0., - 0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0., - 0., 0., 0., 1., - ); - - #[cfg_attr(rustfmt, rustfmt_skip)] - const OKLAB_TO_LMS: Transform3D = Transform3D::new( - 0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0., - 0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0., - 0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0., - 0., 0., 0., 1., - ); - - let lms = - OKLAB_TO_LMS.transform_vector3d(Vector3D::new(oklaba.lightness, oklaba.a, oklaba.b)); - let xyz = LMS_TO_XYZ.transform_vector3d(Vector3D::new( - lms.x.powf(3.0), - lms.y.powf(3.0), - lms.z.powf(3.0), - )); - - Self { - x: xyz.x, - y: xyz.y, - z: xyz.z, - alpha: oklaba.alpha, - } - } -} - -impl From for RGBA { - fn from(d50: XYZD50A) -> Self { - Self::from(XYZD65A::from(d50)) - } -} - -impl From for XYZD50A { - fn from(rgba: RGBA) -> Self { - Self::from(XYZD65A::from(rgba)) - } -} - -impl From for LABA { - fn from(rgba: RGBA) -> Self { - Self::from(XYZD50A::from(rgba)) - } -} - -impl From for RGBA { - fn from(laba: LABA) -> Self { - Self::from(XYZD50A::from(laba)) - } -} - -impl From for RGBA { - fn from(oklaba: OKLABA) -> Self { - Self::from(XYZD65A::from(oklaba)) - } -} - -impl From for OKLABA { - fn from(rgba: RGBA) -> Self { - Self::from(XYZD65A::from(rgba)) - } -} - -impl From for LCHA { - fn from(rgba: RGBA) -> Self { - Self::from(LABA::from(rgba)) - } -} - -impl From for RGBA { - fn from(lcha: LCHA) -> Self { - Self::from(LABA::from(lcha)) - } -} - -impl From for RGBA { - fn from(oklcha: OKLCHA) -> Self { - Self::from(OKLABA::from(oklcha)) - } -} - -impl From for OKLCHA { - fn from(rgba: RGBA) -> Self { - Self::from(OKLABA::from(rgba)) - } -} diff --git a/components/style/color/mod.rs b/components/style/color/mod.rs index 6d2de49ada7..64925c8c43b 100644 --- a/components/style/color/mod.rs +++ b/components/style/color/mod.rs @@ -93,6 +93,8 @@ pub enum ColorSpace { XyzD50, /// A color specified with the color(..) function and the "xyz-d65" or "xyz" /// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)". + /// NOTE: https://drafts.csswg.org/css-color-4/#resolving-color-function-values + /// specifies that `xyz` is an alias for the `xyz-d65` color space. #[parse(aliases = "xyz")] XyzD65, } @@ -109,6 +111,21 @@ impl ColorSpace { pub fn is_polar(&self) -> bool { matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch) } + + /// Returns an index of the hue component in the color space, otherwise + /// `None`. + #[inline] + pub fn hue_index(&self) -> Option { + match self { + Self::Hsl | Self::Hwb => Some(0), + Self::Lch | Self::Oklch => Some(2), + + _ => { + debug_assert!(!self.is_polar()); + None + }, + } + } } bitflags! { @@ -144,7 +161,6 @@ pub struct AbsoluteColor { /// let srgb = AbsoluteColor::new(ColorSpace::Srgb, 1.0, 0.0, 0.0, 0.0); /// let floats = color_components_as!(&srgb, [f32; 4]); // [1.0, 0.0, 0.0, 0.0] /// ``` -#[macro_export] macro_rules! color_components_as { ($c:expr, $t:ty) => {{ // This macro is not an inline function, because we can't use the @@ -173,6 +189,12 @@ impl AbsoluteColor { } } + /// Return all the components of the color in an array. (Includes alpha) + #[inline] + pub fn raw_components(&self) -> &[f32; 4] { + unsafe { color_components_as!(self, [f32; 4]) } + } + /// Return the alpha component. #[inline] pub fn alpha(&self) -> f32 { diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index 4ce7e18bc2c..535ed5b84d6 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -5,6 +5,7 @@ //! Animated types for CSS colors. use crate::color::mix::ColorInterpolationMethod; +use crate::color::{AbsoluteColor, ColorComponents, ColorSpace}; use crate::values::animated::{Animate, Procedure, ToAnimatedZero}; use crate::values::computed::Percentage; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; @@ -27,6 +28,29 @@ pub struct AnimatedRGBA { pub alpha: f32, } +impl From for AnimatedRGBA { + fn from(value: AbsoluteColor) -> Self { + let srgb = value.to_color_space(ColorSpace::Srgb); + + Self::new( + srgb.components.0, + srgb.components.1, + srgb.components.2, + srgb.alpha, + ) + } +} + +impl From for AbsoluteColor { + fn from(value: AnimatedRGBA) -> Self { + Self::new( + ColorSpace::Srgb, + ColorComponents(value.red, value.green, value.blue), + value.alpha, + ) + } +} + use self::AnimatedRGBA as RGBA; impl RGBA { @@ -54,12 +78,13 @@ impl Animate for RGBA { let (left_weight, right_weight) = procedure.weights(); Ok(crate::color::mix::mix( &ColorInterpolationMethod::srgb(), - self, + &AbsoluteColor::from(self.clone()), left_weight as f32, - other, + &AbsoluteColor::from(other.clone()), right_weight as f32, /* normalize_weights = */ false, - )) + ) + .into()) } } diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs index 26dcf1bce02..9f31dc1c9a6 100644 --- a/components/style/values/generics/color.rs +++ b/components/style/values/generics/color.rs @@ -5,6 +5,7 @@ //! Generic types for color properties. use crate::color::mix::ColorInterpolationMethod; +use crate::color::AbsoluteColor; use crate::values::animated::color::AnimatedRGBA; use crate::values::animated::ToAnimatedValue; use crate::values::specified::percentage::ToPercentage; @@ -97,18 +98,19 @@ impl ColorMix, Percentage> { RGBA: Clone + ToAnimatedValue, Percentage: ToPercentage, { - let left = self.left.as_numeric()?.clone().to_animated_value(); - let right = self.right.as_numeric()?.clone().to_animated_value(); - Some(ToAnimatedValue::from_animated_value( - crate::color::mix::mix( - &self.interpolation, - &left, - self.left_percentage.to_percentage(), - &right, - self.right_percentage.to_percentage(), - self.normalize_weights, - ), - )) + let left = AbsoluteColor::from(self.left.as_numeric()?.clone().to_animated_value()); + let right = AbsoluteColor::from(self.right.as_numeric()?.clone().to_animated_value()); + + let mixed = crate::color::mix::mix( + &self.interpolation, + &left, + self.left_percentage.to_percentage(), + &right, + self.right_percentage.to_percentage(), + self.normalize_weights, + ); + + Some(ToAnimatedValue::from_animated_value(mixed.into())) } }