/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Color mixing/interpolation.
usesuper::{AbsoluteColor, ColorFlags, ColorSpace}; usecrate::parser::{Parse, ParserContext}; usecrate::values::generics::color::ColorMixFlags; use cssparser::Parser; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss};
/// Return the oklab interpolation method used for default color /// interpolcation. pubconstfn oklab() -> Self { Self {
space: ColorSpace::Oklab,
hue: HueInterpolationMethod::Shorter,
}
}
/// Decides the best method for interpolating between the given colors. /// https://drafts.csswg.org/css-color-4/#interpolation-space pubfn best_interpolation_between(left: &AbsoluteColor, right: &AbsoluteColor) -> Self { // The preferred color space to use for interpolating colors is Oklab. // However, if either of the colors are in legacy rgb(), hsl() or hwb(), // then interpolation is done in sRGB. if !left.is_legacy_syntax() || !right.is_legacy_syntax() { Self::oklab()
} else { Self::srgb()
}
}
}
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 = ColorSpace::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 })
}
}
/// Mix two colors into one. pubfn mix(
interpolation: ColorInterpolationMethod,
left_color: &AbsoluteColor, mut left_weight: f32,
right_color: &AbsoluteColor, mut right_weight: f32,
flags: ColorMixFlags,
) -> AbsoluteColor { // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm letmut alpha_multiplier = 1.0; if flags.contains(ColorMixFlags::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 result = mix_in(
interpolation.space,
left_color,
left_weight,
right_color,
right_weight,
interpolation.hue,
alpha_multiplier,
);
if flags.contains(ColorMixFlags::RESULT_IN_MODERN_SYNTAX) { // If the result *MUST* be in modern syntax, then make sure it is in a // color space that allows the modern syntax. So hsl and hwb will be // converted to srgb. if result.is_legacy_syntax() {
result.to_color_space(ColorSpace::Srgb)
} else {
result
}
} elseif left_color.is_legacy_syntax() && right_color.is_legacy_syntax() { // If both sides of the mix is legacy then convert the result back into // legacy.
result.into_srgb_legacy()
} else {
result
}
}
/// What the outcome of each component should be in a mix result. #[derive(Clone, Copy)] #[repr(u8)] enum ComponentMixOutcome { /// Mix the left and right sides to give the result.
Mix, /// Carry the left side forward to the result.
UseLeft, /// Carry the right side forward to the result.
UseRight, /// The resulting component should also be none.
None,
}
impl AbsoluteColor { /// Calculate the flags that should be carried forward a color before converting /// it to the interpolation color space according to: /// <https://drafts.csswg.org/css-color-4/#interpolation-missing> fn carry_forward_analogous_missing_components(&mutself, source: &AbsoluteColor) { use ColorFlags as F; use ColorSpace as S;
if source.color_space == self.color_space { return;
}
// Reds r, x // Greens g, y // Blues b, z if source.color_space.is_rgb_or_xyz_like() && self.color_space.is_rgb_or_xyz_like() { return;
}
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? let alpha = (alpha * 1000.0).round() / 1000.0;
letmut result = AbsoluteColor::new(color_space, result[0], result[1], result[2], alpha);
// Normalize hue into [0, 360) #[inline] fn normalize_hue(v: f32) -> f32 {
v - 360. * (v / 360.).floor()
}
fn adjust_hue(left: &mut f32, right: &mutf32, 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;
}
} elseif right.is_nan() {
*right = *left;
}
if hue_interpolation == HueInterpolationMethod::Specified { // Angles are not adjusted. They are interpolated like any other // component. return;
}
struct InterpolatedAlpha { /// The adjusted left alpha value.
left: f32, /// The adjusted right alpha value.
right: f32, /// The interpolated alpha value.
interpolated: f32, /// Whether the alpha component should be `none`.
is_none: bool,
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.