mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
style: Part 1 - Move color mixing code to color module
No functional changes, just moving the code to the shared library in preperation for replacing it with the new AbsoluteColor. Differential Revision: https://phabricator.services.mozilla.com/D169928
This commit is contained in:
parent
c4ae5c93a8
commit
e963abe2e9
5 changed files with 1050 additions and 1044 deletions
1032
components/style/color/mix.rs
Normal file
1032
components/style/color/mix.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@
|
|||
|
||||
/// cbindgen:ignore
|
||||
pub mod convert;
|
||||
pub mod mix;
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ToCss};
|
||||
|
|
|
@ -4,19 +4,11 @@
|
|||
|
||||
//! Animated types for CSS colors.
|
||||
|
||||
use crate::color::mix::ColorInterpolationMethod;
|
||||
use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
|
||||
use crate::values::computed::Percentage;
|
||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use crate::values::generics::color::{
|
||||
ColorInterpolationMethod, GenericColor, GenericColorMix, HueInterpolationMethod,
|
||||
InterpolationColorSpace,
|
||||
};
|
||||
use euclid::default::{Transform3D, Vector3D};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
// TODO(tlouw): The code does not use the new color conversions that were added
|
||||
// for new color spaces and needs to be refectored, see:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1812545
|
||||
use crate::values::generics::color::{GenericColor, GenericColorMix};
|
||||
|
||||
/// An animated RGBA color.
|
||||
///
|
||||
|
@ -37,9 +29,6 @@ pub struct AnimatedRGBA {
|
|||
|
||||
use self::AnimatedRGBA as RGBA;
|
||||
|
||||
const RAD_PER_DEG: f32 = PI / 180.0;
|
||||
const DEG_PER_RAD: f32 = 180.0 / PI;
|
||||
|
||||
impl RGBA {
|
||||
/// Returns a transparent color.
|
||||
#[inline]
|
||||
|
@ -63,7 +52,7 @@ impl Animate for RGBA {
|
|||
#[inline]
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let (left_weight, right_weight) = procedure.weights();
|
||||
Ok(Color::mix(
|
||||
Ok(crate::color::mix::mix(
|
||||
&ColorInterpolationMethod::srgb(),
|
||||
self,
|
||||
left_weight as f32,
|
||||
|
@ -109,79 +98,6 @@ impl Color {
|
|||
clone.simplify(Some(¤t_color));
|
||||
*clone.as_numeric().unwrap()
|
||||
}
|
||||
|
||||
/// Mix two colors into one.
|
||||
pub fn mix(
|
||||
interpolation: &ColorInterpolationMethod,
|
||||
left_color: &RGBA,
|
||||
mut left_weight: f32,
|
||||
right_color: &RGBA,
|
||||
mut right_weight: f32,
|
||||
normalize_weights: bool,
|
||||
) -> RGBA {
|
||||
// https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
|
||||
let mut alpha_multiplier = 1.0;
|
||||
if normalize_weights {
|
||||
let sum = left_weight + right_weight;
|
||||
if sum != 1.0 {
|
||||
let scale = 1.0 / sum;
|
||||
left_weight *= scale;
|
||||
right_weight *= scale;
|
||||
if sum < 1.0 {
|
||||
alpha_multiplier = sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mix_function = match interpolation.space {
|
||||
InterpolationColorSpace::Srgb => Self::mix_in::<RGBA>,
|
||||
InterpolationColorSpace::LinearSrgb => Self::mix_in::<LinearRGBA>,
|
||||
InterpolationColorSpace::Xyz => Self::mix_in::<XYZD65A>,
|
||||
InterpolationColorSpace::XyzD50 => Self::mix_in::<XYZD50A>,
|
||||
InterpolationColorSpace::Lab => Self::mix_in::<LABA>,
|
||||
InterpolationColorSpace::Hwb => Self::mix_in::<HWBA>,
|
||||
InterpolationColorSpace::Hsl => Self::mix_in::<HSLA>,
|
||||
InterpolationColorSpace::Lch => Self::mix_in::<LCHA>,
|
||||
};
|
||||
mix_function(
|
||||
left_color,
|
||||
left_weight,
|
||||
right_color,
|
||||
right_weight,
|
||||
interpolation.hue,
|
||||
alpha_multiplier,
|
||||
)
|
||||
}
|
||||
|
||||
fn mix_in<S>(
|
||||
left_color: &RGBA,
|
||||
left_weight: f32,
|
||||
right_color: &RGBA,
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
rgba
|
||||
}
|
||||
}
|
||||
|
||||
impl Animate for Color {
|
||||
|
@ -217,794 +133,3 @@ impl ToAnimatedZero for Color {
|
|||
Ok(Color::rgba(RGBA::transparent()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A color modelled in a specific color space (such as sRGB or CIE XYZ).
|
||||
///
|
||||
/// For now, colors modelled in other spaces need to be convertible to and from
|
||||
/// `RGBA` because we use sRGB for displaying colors.
|
||||
trait ModelledColor: Clone + Copy + From<RGBA> + Into<RGBA> {
|
||||
/// Linearly interpolate between the left and right colors.
|
||||
///
|
||||
/// The HueInterpolationMethod parameter is only for color spaces where the hue is
|
||||
/// represented as an angle (e.g., CIE LCH).
|
||||
fn lerp(
|
||||
left_bg: &Self,
|
||||
left_weight: f32,
|
||||
right_bg: &Self,
|
||||
right_weight: f32,
|
||||
hue_interpolation: HueInterpolationMethod,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
fn interpolate_premultiplied_component(
|
||||
left: f32,
|
||||
left_weight: f32,
|
||||
left_alpha: f32,
|
||||
right: f32,
|
||||
right_weight: f32,
|
||||
right_alpha: f32,
|
||||
inverse_of_result_alpha: f32,
|
||||
) -> f32 {
|
||||
(left * left_weight * left_alpha + right * right_weight * right_alpha) * inverse_of_result_alpha
|
||||
}
|
||||
|
||||
// Normalize hue into [0, 360)
|
||||
fn normalize_hue(v: f32) -> f32 {
|
||||
v - 360. * (v / 360.).floor()
|
||||
}
|
||||
|
||||
fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolationMethod) {
|
||||
// Adjust the hue angle as per
|
||||
// https://drafts.csswg.org/css-color/#hue-interpolation.
|
||||
//
|
||||
// If both hue angles are NAN, they should be set to 0. Otherwise, if a
|
||||
// single hue angle is NAN, it should use the other hue angle.
|
||||
if left.is_nan() {
|
||||
if right.is_nan() {
|
||||
*left = 0.;
|
||||
*right = 0.;
|
||||
} else {
|
||||
*left = *right;
|
||||
}
|
||||
} else if right.is_nan() {
|
||||
*right = *left;
|
||||
}
|
||||
|
||||
if hue_interpolation == HueInterpolationMethod::Specified {
|
||||
// Angles are not adjusted. They are interpolated like any other
|
||||
// component.
|
||||
return;
|
||||
}
|
||||
|
||||
*left = normalize_hue(*left);
|
||||
*right = normalize_hue(*right);
|
||||
|
||||
match hue_interpolation {
|
||||
// https://drafts.csswg.org/css-color/#shorter
|
||||
HueInterpolationMethod::Shorter => {
|
||||
let delta = *right - *left;
|
||||
|
||||
if delta > 180. {
|
||||
*left += 360.;
|
||||
} else if delta < -180. {
|
||||
*right += 360.;
|
||||
}
|
||||
},
|
||||
// https://drafts.csswg.org/css-color/#longer
|
||||
HueInterpolationMethod::Longer => {
|
||||
let delta = *right - *left;
|
||||
if 0. < delta && delta < 180. {
|
||||
*left += 360.;
|
||||
} else if -180. < delta && delta < 0. {
|
||||
*right += 360.;
|
||||
}
|
||||
},
|
||||
// https://drafts.csswg.org/css-color/#increasing
|
||||
HueInterpolationMethod::Increasing => {
|
||||
if *right < *left {
|
||||
*right += 360.;
|
||||
}
|
||||
},
|
||||
// https://drafts.csswg.org/css-color/#decreasing
|
||||
HueInterpolationMethod::Decreasing => {
|
||||
if *left < *right {
|
||||
*left += 360.;
|
||||
}
|
||||
},
|
||||
HueInterpolationMethod::Specified => unreachable!("Handled above"),
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_hue(
|
||||
mut left: f32,
|
||||
left_weight: f32,
|
||||
mut right: f32,
|
||||
right_weight: f32,
|
||||
hue_interpolation: HueInterpolationMethod,
|
||||
) -> f32 {
|
||||
adjust_hue(&mut left, &mut right, hue_interpolation);
|
||||
left * left_weight + right * right_weight
|
||||
}
|
||||
|
||||
fn interpolate_premultiplied(
|
||||
left: &[f32; 4],
|
||||
left_weight: f32,
|
||||
right: &[f32; 4],
|
||||
right_weight: f32,
|
||||
hue_index: Option<usize>,
|
||||
hue_interpolation: HueInterpolationMethod,
|
||||
) -> [f32; 4] {
|
||||
let left_alpha = left[3];
|
||||
let right_alpha = right[3];
|
||||
let result_alpha = (left_alpha * left_weight + right_alpha * right_weight).min(1.);
|
||||
let mut result = [0.; 4];
|
||||
if result_alpha <= 0. {
|
||||
return result;
|
||||
}
|
||||
|
||||
let inverse_of_result_alpha = 1. / result_alpha;
|
||||
for i in 0..3 {
|
||||
let is_hue = hue_index == Some(i);
|
||||
result[i] = if is_hue {
|
||||
interpolate_hue(
|
||||
left[i],
|
||||
left_weight,
|
||||
right[i],
|
||||
right_weight,
|
||||
hue_interpolation,
|
||||
)
|
||||
} else {
|
||||
interpolate_premultiplied_component(
|
||||
left[i],
|
||||
left_weight,
|
||||
left_alpha,
|
||||
right[i],
|
||||
right_weight,
|
||||
right_alpha,
|
||||
inverse_of_result_alpha,
|
||||
)
|
||||
};
|
||||
}
|
||||
result[3] = result_alpha;
|
||||
|
||||
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::<f32>() * 4);
|
||||
const_assert_eq!(std::mem::align_of::<$ty>(), std::mem::align_of::<f32>());
|
||||
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<RGBA> for HSLA {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
rgb_to_hsl(rgba).0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HSLA> 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<RGBA> 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<HWBA> 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<RGBA> 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<LinearRGBA> 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<XYZD65A> 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<f32> = 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<XYZD50A> 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<f32> = 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<LinearRGBA> 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<f32> = 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<XYZD65A> 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<f32> = 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<XYZD65A> for RGBA {
|
||||
fn from(d65: XYZD65A) -> Self {
|
||||
Self::from(LinearRGBA::from(d65))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> 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<XYZD50A> 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<LABA> 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<OKLABA> 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<LCHA> 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<OKLCHA> 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<LABA> 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<XYZD65A> 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<f32> = 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<f32> = 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<OKLABA> 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<f32> = 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<f32> = 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<XYZD50A> for RGBA {
|
||||
fn from(d50: XYZD50A) -> Self {
|
||||
Self::from(XYZD65A::from(d50))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for XYZD50A {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(XYZD65A::from(rgba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for LABA {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(XYZD50A::from(rgba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LABA> for RGBA {
|
||||
fn from(laba: LABA) -> Self {
|
||||
Self::from(XYZD50A::from(laba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OKLABA> for RGBA {
|
||||
fn from(oklaba: OKLABA) -> Self {
|
||||
Self::from(XYZD65A::from(oklaba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for OKLABA {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(XYZD65A::from(rgba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for LCHA {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(LABA::from(rgba))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LCHA> for RGBA {
|
||||
fn from(lcha: LCHA) -> Self {
|
||||
Self::from(LABA::from(lcha))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OKLCHA> for RGBA {
|
||||
fn from(oklcha: OKLCHA) -> Self {
|
||||
Self::from(OKLABA::from(oklcha))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for OKLCHA {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(OKLABA::from(rgba))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
//! Generic types for color properties.
|
||||
|
||||
use crate::color::mix::ColorInterpolationMethod;
|
||||
use crate::values::animated::color::AnimatedRGBA;
|
||||
use crate::values::animated::ToAnimatedValue;
|
||||
use crate::values::specified::percentage::ToPercentage;
|
||||
use crate::values::{Parse, Parser, ParserContext};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, ToCss};
|
||||
use style_traits::{CssWriter, ToCss};
|
||||
|
||||
/// This struct represents a combined color from a numeric color and
|
||||
/// the current foreground color (currentcolor keyword).
|
||||
|
@ -24,158 +24,6 @@ pub enum GenericColor<RGBA, Percentage> {
|
|||
ColorMix(Box<GenericColorMix<Self, Percentage>>),
|
||||
}
|
||||
|
||||
/// A color space as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum InterpolationColorSpace {
|
||||
/// The sRGB color space.
|
||||
Srgb,
|
||||
/// The linear-sRGB color space.
|
||||
LinearSrgb,
|
||||
/// The CIEXYZ color space.
|
||||
#[parse(aliases = "xyz-d65")]
|
||||
Xyz,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-color-xyz
|
||||
XyzD50,
|
||||
/// The CIELAB color space.
|
||||
Lab,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl
|
||||
Hsl,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb
|
||||
Hwb,
|
||||
/// The CIELAB color space, expressed in cylindrical coordinates.
|
||||
Lch,
|
||||
// TODO: Oklab, Lch
|
||||
}
|
||||
|
||||
impl InterpolationColorSpace {
|
||||
/// Returns whether this is a `<polar-color-space>`.
|
||||
pub fn is_polar(self) -> bool {
|
||||
match self {
|
||||
Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false,
|
||||
Self::Hsl | Self::Hwb | Self::Lch => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hue-interpolation-method as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum HueInterpolationMethod {
|
||||
/// https://drafts.csswg.org/css-color-4/#shorter
|
||||
Shorter,
|
||||
/// https://drafts.csswg.org/css-color-4/#longer
|
||||
Longer,
|
||||
/// https://drafts.csswg.org/css-color-4/#increasing
|
||||
Increasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#decreasing
|
||||
Decreasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#specified
|
||||
Specified,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-color-4/#color-interpolation-method
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
ToShmem,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToResolvedValue,
|
||||
)]
|
||||
#[repr(C)]
|
||||
pub struct ColorInterpolationMethod {
|
||||
/// The color-space the interpolation should be done in.
|
||||
pub space: InterpolationColorSpace,
|
||||
/// The hue interpolation method.
|
||||
pub hue: HueInterpolationMethod,
|
||||
}
|
||||
|
||||
impl ColorInterpolationMethod {
|
||||
/// Returns the srgb interpolation method.
|
||||
pub fn srgb() -> Self {
|
||||
Self {
|
||||
space: InterpolationColorSpace::Srgb,
|
||||
hue: HueInterpolationMethod::Shorter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ColorInterpolationMethod {
|
||||
fn parse<'i, 't>(
|
||||
_: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_ident_matching("in")?;
|
||||
let space = InterpolationColorSpace::parse(input)?;
|
||||
// https://drafts.csswg.org/css-color-4/#hue-interpolation
|
||||
// Unless otherwise specified, if no specific hue interpolation
|
||||
// algorithm is selected by the host syntax, the default is shorter.
|
||||
let hue = if space.is_polar() {
|
||||
input
|
||||
.try_parse(|input| -> Result<_, ParseError<'i>> {
|
||||
let hue = HueInterpolationMethod::parse(input)?;
|
||||
input.expect_ident_matching("hue")?;
|
||||
Ok(hue)
|
||||
})
|
||||
.unwrap_or(HueInterpolationMethod::Shorter)
|
||||
} else {
|
||||
HueInterpolationMethod::Shorter
|
||||
};
|
||||
Ok(Self { space, hue })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for ColorInterpolationMethod {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
dest.write_str("in ")?;
|
||||
self.space.to_css(dest)?;
|
||||
if self.hue != HueInterpolationMethod::Shorter {
|
||||
dest.write_char(' ')?;
|
||||
self.hue.to_css(dest)?;
|
||||
dest.write_str(" hue")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A restricted version of the css `color-mix()` function, which only supports
|
||||
/// percentages.
|
||||
///
|
||||
|
@ -249,17 +97,18 @@ impl<RGBA, Percentage> ColorMix<GenericColor<RGBA, Percentage>, Percentage> {
|
|||
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
|
||||
Percentage: ToPercentage,
|
||||
{
|
||||
use crate::values::animated::color::Color as AnimatedColor;
|
||||
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(AnimatedColor::mix(
|
||||
&self.interpolation,
|
||||
&left,
|
||||
self.left_percentage.to_percentage(),
|
||||
&right,
|
||||
self.right_percentage.to_percentage(),
|
||||
self.normalize_weights,
|
||||
)))
|
||||
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,
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,12 @@
|
|||
//! Specified color values.
|
||||
|
||||
use super::AllowQuirks;
|
||||
use crate::color::mix::ColorInterpolationMethod;
|
||||
use crate::color::{AbsoluteColor, ColorComponents, ColorSpace};
|
||||
use crate::media_queries::Device;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
|
||||
use crate::values::generics::color::{
|
||||
ColorInterpolationMethod, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
|
||||
};
|
||||
use crate::values::generics::color::{GenericCaretColor, GenericColorMix, GenericColorOrAuto};
|
||||
use crate::values::specified::calc::CalcNode;
|
||||
use crate::values::specified::Percentage;
|
||||
use crate::values::CustomIdent;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue