mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 08:42:13 +00:00
Bug 1465307 - P1: Extend StyleComplexColor to support additive blending. r=hiro,xidorn
Refactored StyleComplexColor to support "complex" blending between background (numeric) color and foreground color (currentColor). Made explicit the distinction between numeric, currentColor and a complex blend in Gecko and Stylo. This is to support SMIL animation, for example, of the form: <animate from="rgb(10,20,30)" by="currentColor" ... /> MozReview-Commit-ID: IUAK8P07gtm --HG-- extra : rebase_source : d3648101c6f65479b21e6f02945731cd5bb57663
This commit is contained in:
parent
70a6147545
commit
7811112058
@ -2165,10 +2165,7 @@ IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
StyleComplexColor color = aBorder.mBorderColor[aSide];
|
||||
// We don't know the foreground color here, so if it's being used
|
||||
// we must assume it might be transparent.
|
||||
if (!color.IsNumericColor()) {
|
||||
return false;
|
||||
}
|
||||
return NS_GET_A(color.mColor) == 255;
|
||||
return !color.MaybeTransparent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,6 +154,7 @@ rusty-enums = [
|
||||
"mozilla::StyleOrient",
|
||||
"mozilla::StyleBoxSizing",
|
||||
"mozilla::StyleClear",
|
||||
"mozilla::StyleComplexColor_Tag",
|
||||
"mozilla::StyleFloat",
|
||||
"mozilla::StyleUserModify",
|
||||
"mozilla::StyleUserInput",
|
||||
|
@ -13,62 +13,66 @@
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static uint32_t
|
||||
BlendColorComponent(uint32_t aBg, uint32_t aFg, uint32_t aFgAlpha)
|
||||
{
|
||||
return RoundingDivideBy255(aBg * (255 - aFgAlpha) + aFg * aFgAlpha);
|
||||
}
|
||||
|
||||
// Blend one RGBA color with another based on a given ratio.
|
||||
// It is a linear interpolation on each channel with alpha premultipled.
|
||||
// Blend one RGBA color with another based on a given ratios.
|
||||
// It is a linear combination of each channel with alpha premultipled.
|
||||
static nscolor
|
||||
LinearBlendColors(nscolor aBg, nscolor aFg, uint_fast8_t aFgRatio)
|
||||
LinearBlendColors(nscolor aBg, float aBgRatio, nscolor aFg, float aFgRatio)
|
||||
{
|
||||
// Common case that either pure background or pure foreground
|
||||
if (aFgRatio == 0) {
|
||||
return aBg;
|
||||
}
|
||||
if (aFgRatio == 255) {
|
||||
return aFg;
|
||||
}
|
||||
// Common case that alpha channel is equal (usually both are opaque)
|
||||
if (NS_GET_A(aBg) == NS_GET_A(aFg)) {
|
||||
auto r = BlendColorComponent(NS_GET_R(aBg), NS_GET_R(aFg), aFgRatio);
|
||||
auto g = BlendColorComponent(NS_GET_G(aBg), NS_GET_G(aFg), aFgRatio);
|
||||
auto b = BlendColorComponent(NS_GET_B(aBg), NS_GET_B(aFg), aFgRatio);
|
||||
return NS_RGBA(r, g, b, NS_GET_A(aFg));
|
||||
}
|
||||
|
||||
constexpr float kFactor = 1.0f / 255.0f;
|
||||
|
||||
float p1 = kFactor * (255 - aFgRatio);
|
||||
float p1 = aBgRatio;
|
||||
float a1 = kFactor * NS_GET_A(aBg);
|
||||
float r1 = a1 * NS_GET_R(aBg);
|
||||
float g1 = a1 * NS_GET_G(aBg);
|
||||
float b1 = a1 * NS_GET_B(aBg);
|
||||
|
||||
float p2 = 1.0f - p1;
|
||||
float p2 = aFgRatio;
|
||||
float a2 = kFactor * NS_GET_A(aFg);
|
||||
float r2 = a2 * NS_GET_R(aFg);
|
||||
float g2 = a2 * NS_GET_G(aFg);
|
||||
float b2 = a2 * NS_GET_B(aFg);
|
||||
|
||||
float a = p1 * a1 + p2 * a2;
|
||||
if (a == 0.0) {
|
||||
if (a <= 0.f) {
|
||||
return NS_RGBA(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (a > 1.f) {
|
||||
a = 1.f;
|
||||
}
|
||||
|
||||
auto r = ClampColor((p1 * r1 + p2 * r2) / a);
|
||||
auto g = ClampColor((p1 * g1 + p2 * g2) / a);
|
||||
auto b = ClampColor((p1 * b1 + p2 * b2) / a);
|
||||
return NS_RGBA(r, g, b, NSToIntRound(a * 255));
|
||||
}
|
||||
|
||||
bool
|
||||
StyleComplexColor::MaybeTransparent() const {
|
||||
// We know that the color is opaque when it's a numeric color with
|
||||
// alpha == 255.
|
||||
// TODO(djg): Should we extend this to check Complex with bgRatio =
|
||||
// 0, and fgRatio * alpha >= 255?
|
||||
return mTag != eNumeric || NS_GET_A(mColor) != 255;
|
||||
}
|
||||
|
||||
nscolor
|
||||
StyleComplexColor::CalcColor(mozilla::ComputedStyle* aStyle) const {
|
||||
// Common case that is numeric color, which is pure background, we
|
||||
// can skip resolving StyleColor().
|
||||
if (mTag == eNumeric) {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aStyle);
|
||||
auto foregroundColor = aStyle->StyleColor()->mColor;
|
||||
return LinearBlendColors(mColor, foregroundColor, mForegroundRatio);
|
||||
auto fgColor = aStyle->StyleColor()->mColor;
|
||||
|
||||
if (mTag == eComplex) {
|
||||
return LinearBlendColors(mColor, mBgRatio, fgColor, mFgRatio);
|
||||
}
|
||||
|
||||
// eForeground and eAuto return the currentcolor.
|
||||
return fgColor;
|
||||
}
|
||||
|
||||
nscolor
|
||||
|
@ -20,46 +20,58 @@ class ComputedStyle;
|
||||
/**
|
||||
* This struct represents a combined color from a numeric color and
|
||||
* the current foreground color (currentcolor keyword).
|
||||
* Conceptually, the formula is "color * (1 - p) + currentcolor * p"
|
||||
* where p is mForegroundRatio. See mozilla::LinearBlendColors for
|
||||
* the actual algorithm.
|
||||
* Conceptually, the formula is "color * q + currentcolor * p"
|
||||
* where p is mFgRatio and q is mBgRatio.
|
||||
*
|
||||
* It can also represent an "auto" value, which is valid for some
|
||||
* properties. See comment of mIsAuto.
|
||||
* properties. See comment of `Tag::eAuto`.
|
||||
*/
|
||||
struct StyleComplexColor
|
||||
class StyleComplexColor final
|
||||
{
|
||||
nscolor mColor;
|
||||
uint8_t mForegroundRatio;
|
||||
// Whether the complex color represents a computed-value time auto
|
||||
// value. This is a flag indicating that this value should not be
|
||||
// interpolatable with other colors. When this flag is set, other
|
||||
// fields represent a currentcolor. Properties can decide whether
|
||||
// that should be used.
|
||||
bool mIsAuto;
|
||||
|
||||
public:
|
||||
static StyleComplexColor FromColor(nscolor aColor) {
|
||||
return {aColor, 0, false};
|
||||
return {aColor, 0, eNumeric};
|
||||
}
|
||||
static StyleComplexColor CurrentColor() {
|
||||
return {NS_RGBA(0, 0, 0, 0), 255, false};
|
||||
return {NS_RGBA(0, 0, 0, 0), 1, eForeground};
|
||||
}
|
||||
static StyleComplexColor Auto() {
|
||||
return {NS_RGBA(0, 0, 0, 0), 255, true};
|
||||
return {NS_RGBA(0, 0, 0, 0), 1, eAuto};
|
||||
}
|
||||
|
||||
bool IsNumericColor() const { return mForegroundRatio == 0; }
|
||||
bool IsCurrentColor() const { return mForegroundRatio == 255; }
|
||||
bool IsAuto() const { return mTag == eAuto; }
|
||||
bool IsCurrentColor() const { return mTag == eForeground; }
|
||||
|
||||
bool operator==(const StyleComplexColor& aOther) const {
|
||||
return mForegroundRatio == aOther.mForegroundRatio &&
|
||||
(IsCurrentColor() || mColor == aOther.mColor) &&
|
||||
mIsAuto == aOther.mIsAuto;
|
||||
if (mTag != aOther.mTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (mTag) {
|
||||
case eAuto:
|
||||
case eForeground:
|
||||
return true;
|
||||
case eNumeric:
|
||||
return mColor == aOther.mColor;
|
||||
case eComplex:
|
||||
return (mBgRatio == aOther.mBgRatio &&
|
||||
mFgRatio == aOther.mFgRatio &&
|
||||
mColor == aOther.mColor);
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected StyleComplexColor type.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator!=(const StyleComplexColor& aOther) const {
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it possible that this StyleComplexColor is transparent?
|
||||
*/
|
||||
bool MaybeTransparent() const;
|
||||
|
||||
/**
|
||||
* Compute the color for this StyleComplexColor, taking into account
|
||||
* the foreground color from aStyle.
|
||||
@ -71,8 +83,43 @@ struct StyleComplexColor
|
||||
* the foreground color from aFrame's ComputedStyle.
|
||||
*/
|
||||
nscolor CalcColor(const nsIFrame* aFrame) const;
|
||||
|
||||
private:
|
||||
enum Tag : uint8_t {
|
||||
// This represents a computed-value time auto value. This
|
||||
// indicates that this value should not be interpolatable with
|
||||
// other colors. Other fields represent a currentcolor and
|
||||
// properties can decide whether that should be used.
|
||||
eAuto,
|
||||
// This represents a numeric color; no currentcolor component.
|
||||
eNumeric,
|
||||
// This represents the current foreground color, currentcolor; no
|
||||
// numeric color component.
|
||||
eForeground,
|
||||
// This represents a linear combination of numeric color and the
|
||||
// foreground color: "mColor * mBgRatio + currentcolor *
|
||||
// mFgRatio".
|
||||
eComplex,
|
||||
};
|
||||
|
||||
StyleComplexColor(nscolor aColor,
|
||||
float aFgRatio,
|
||||
Tag aTag)
|
||||
: mColor(aColor)
|
||||
, mBgRatio(1.f - aFgRatio)
|
||||
, mFgRatio(aFgRatio)
|
||||
, mTag(aTag)
|
||||
{
|
||||
MOZ_ASSERT(mTag != eNumeric || aFgRatio == 0.);
|
||||
MOZ_ASSERT(!(mTag == eAuto || mTag == eForeground) || aFgRatio == 1.);
|
||||
}
|
||||
|
||||
nscolor mColor;
|
||||
float mBgRatio;
|
||||
float mFgRatio;
|
||||
Tag mTag;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_StyleComplexColor_h_
|
||||
|
@ -1185,7 +1185,7 @@ nsComputedDOMStyle::SetValueForWidgetColor(nsROCSSPrimitiveValue* aValue,
|
||||
const StyleComplexColor& aColor,
|
||||
uint8_t aWidgetType)
|
||||
{
|
||||
if (!aColor.mIsAuto) {
|
||||
if (!aColor.IsAuto()) {
|
||||
SetToRGBAColor(aValue, aColor.CalcColor(mComputedStyle));
|
||||
return;
|
||||
}
|
||||
|
@ -4261,7 +4261,8 @@ nsStyleContent::CalcDifference(const nsStyleContent& aNewData) const
|
||||
//
|
||||
|
||||
nsStyleTextReset::nsStyleTextReset(const nsPresContext* aContext)
|
||||
: mTextDecorationLine(NS_STYLE_TEXT_DECORATION_LINE_NONE)
|
||||
: mTextOverflow()
|
||||
, mTextDecorationLine(NS_STYLE_TEXT_DECORATION_LINE_NONE)
|
||||
, mTextDecorationStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID)
|
||||
, mUnicodeBidi(NS_STYLE_UNICODE_BIDI_NORMAL)
|
||||
, mInitialLetterSink(0)
|
||||
@ -4272,9 +4273,15 @@ nsStyleTextReset::nsStyleTextReset(const nsPresContext* aContext)
|
||||
}
|
||||
|
||||
nsStyleTextReset::nsStyleTextReset(const nsStyleTextReset& aSource)
|
||||
: mTextOverflow(aSource.mTextOverflow)
|
||||
, mTextDecorationLine(aSource.mTextDecorationLine)
|
||||
, mTextDecorationStyle(aSource.mTextDecorationStyle)
|
||||
, mUnicodeBidi(aSource.mUnicodeBidi)
|
||||
, mInitialLetterSink(aSource.mInitialLetterSink)
|
||||
, mInitialLetterSize(aSource.mInitialLetterSize)
|
||||
, mTextDecorationColor(aSource.mTextDecorationColor)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsStyleTextReset);
|
||||
*this = aSource;
|
||||
}
|
||||
|
||||
nsStyleTextReset::~nsStyleTextReset()
|
||||
|
@ -2861,7 +2861,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUserInterface
|
||||
|
||||
bool HasCustomScrollbars() const
|
||||
{
|
||||
return !mScrollbarFaceColor.mIsAuto || !mScrollbarTrackColor.mIsAuto;
|
||||
return !mScrollbarFaceColor.IsAuto() || !mScrollbarTrackColor.IsAuto();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,28 +5,21 @@
|
||||
//! Rust helpers to interact with Gecko's StyleComplexColor.
|
||||
|
||||
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
|
||||
use gecko_bindings::structs::{nscolor, StyleComplexColor};
|
||||
use gecko_bindings::structs::StyleComplexColor;
|
||||
use gecko_bindings::structs::StyleComplexColor_Tag as Tag;
|
||||
use values::{Auto, Either};
|
||||
use values::computed::Color as ComputedColor;
|
||||
use values::computed::{Color as ComputedColor, RGBAColor as ComputedRGBA};
|
||||
use values::computed::ComplexColorRatios;
|
||||
use values::computed::ui::ColorOrAuto;
|
||||
|
||||
impl From<nscolor> for StyleComplexColor {
|
||||
fn from(other: nscolor) -> Self {
|
||||
StyleComplexColor {
|
||||
mColor: other,
|
||||
mForegroundRatio: 0,
|
||||
mIsAuto: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleComplexColor {
|
||||
/// Create a `StyleComplexColor` value that represents `currentColor`.
|
||||
pub fn current_color() -> Self {
|
||||
StyleComplexColor {
|
||||
mColor: 0,
|
||||
mForegroundRatio: 255,
|
||||
mIsAuto: false,
|
||||
mBgRatio: 0.,
|
||||
mFgRatio: 1.,
|
||||
mTag: Tag::eForeground,
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,28 +27,66 @@ impl StyleComplexColor {
|
||||
pub fn auto() -> Self {
|
||||
StyleComplexColor {
|
||||
mColor: 0,
|
||||
mForegroundRatio: 255,
|
||||
mIsAuto: true,
|
||||
mBgRatio: 0.,
|
||||
mFgRatio: 1.,
|
||||
mTag: Tag::eAuto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComputedRGBA> for StyleComplexColor {
|
||||
fn from(other: ComputedRGBA) -> Self {
|
||||
StyleComplexColor {
|
||||
mColor: convert_rgba_to_nscolor(&other),
|
||||
mBgRatio: 1.,
|
||||
mFgRatio: 0.,
|
||||
mTag: Tag::eNumeric,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComputedColor> for StyleComplexColor {
|
||||
fn from(other: ComputedColor) -> Self {
|
||||
StyleComplexColor {
|
||||
mColor: convert_rgba_to_nscolor(&other.color).into(),
|
||||
mForegroundRatio: other.foreground_ratio,
|
||||
mIsAuto: false,
|
||||
match other {
|
||||
ComputedColor::Numeric(color) => color.into(),
|
||||
ComputedColor::Foreground => Self::current_color(),
|
||||
ComputedColor::Complex(color, ratios) => {
|
||||
debug_assert!(ratios != ComplexColorRatios::NUMERIC);
|
||||
debug_assert!(ratios != ComplexColorRatios::FOREGROUND);
|
||||
StyleComplexColor {
|
||||
mColor: convert_rgba_to_nscolor(&color).into(),
|
||||
mBgRatio: ratios.bg,
|
||||
mFgRatio: ratios.fg,
|
||||
mTag: Tag::eComplex,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StyleComplexColor> for ComputedColor {
|
||||
fn from(other: StyleComplexColor) -> Self {
|
||||
debug_assert!(!other.mIsAuto);
|
||||
ComputedColor {
|
||||
color: convert_nscolor_to_rgba(other.mColor),
|
||||
foreground_ratio: other.mForegroundRatio,
|
||||
match other.mTag {
|
||||
Tag::eNumeric => {
|
||||
debug_assert!(other.mBgRatio == 1. && other.mFgRatio == 0.);
|
||||
ComputedColor::Numeric(convert_nscolor_to_rgba(other.mColor))
|
||||
}
|
||||
Tag::eForeground => {
|
||||
debug_assert!(other.mBgRatio == 0. && other.mFgRatio == 1.);
|
||||
ComputedColor::Foreground
|
||||
}
|
||||
Tag::eComplex => {
|
||||
debug_assert!(other.mBgRatio != 1. || other.mFgRatio != 0.);
|
||||
debug_assert!(other.mBgRatio != 0. || other.mFgRatio != 1.);
|
||||
ComputedColor::Complex(
|
||||
convert_nscolor_to_rgba(other.mColor),
|
||||
ComplexColorRatios {
|
||||
bg: other.mBgRatio,
|
||||
fg: other.mFgRatio,
|
||||
},
|
||||
)
|
||||
}
|
||||
Tag::eAuto => unreachable!("Unsupport StyleComplexColor with tag eAuto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,7 +102,7 @@ impl From<ColorOrAuto> for StyleComplexColor {
|
||||
|
||||
impl From<StyleComplexColor> for ColorOrAuto {
|
||||
fn from(other: StyleComplexColor) -> Self {
|
||||
if !other.mIsAuto {
|
||||
if other.mTag != Tag::eAuto {
|
||||
Either::First(other.into())
|
||||
} else {
|
||||
Either::Second(Auto)
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
use values::animated::{Animate, Procedure, ToAnimatedZero};
|
||||
use values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use values::computed::ComplexColorRatios;
|
||||
|
||||
/// An animated RGBA color.
|
||||
///
|
||||
@ -91,42 +92,51 @@ impl ComputeSquaredDistance for RGBA {
|
||||
}
|
||||
}
|
||||
|
||||
impl Animate for ComplexColorRatios {
|
||||
#[inline]
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let bg = self.bg.animate(&other.bg, procedure)?;
|
||||
let fg = self.fg.animate(&other.fg, procedure)?;
|
||||
|
||||
Ok(ComplexColorRatios { bg, fg })
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Color {
|
||||
pub color: RGBA,
|
||||
pub foreground_ratio: f32,
|
||||
pub enum Color {
|
||||
Numeric(RGBA),
|
||||
Foreground,
|
||||
Complex(RGBA, ComplexColorRatios),
|
||||
}
|
||||
|
||||
impl Color {
|
||||
fn currentcolor() -> Self {
|
||||
Color {
|
||||
color: RGBA::transparent(),
|
||||
foreground_ratio: 1.,
|
||||
}
|
||||
Color::Foreground
|
||||
}
|
||||
|
||||
/// Returns a transparent intermediate color.
|
||||
pub fn transparent() -> Self {
|
||||
Color {
|
||||
color: RGBA::transparent(),
|
||||
foreground_ratio: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_currentcolor(&self) -> bool {
|
||||
self.foreground_ratio >= 1.
|
||||
}
|
||||
|
||||
fn is_numeric(&self) -> bool {
|
||||
self.foreground_ratio <= 0.
|
||||
Color::Numeric(RGBA::transparent())
|
||||
}
|
||||
|
||||
fn effective_intermediate_rgba(&self) -> RGBA {
|
||||
RGBA {
|
||||
alpha: self.color.alpha * (1. - self.foreground_ratio),
|
||||
..self.color
|
||||
match *self {
|
||||
Color::Numeric(color) => color,
|
||||
Color::Foreground => RGBA::transparent(),
|
||||
Color::Complex(color, ratios) => RGBA {
|
||||
alpha: color.alpha * ratios.bg,
|
||||
..color.clone()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn effective_ratios(&self) -> ComplexColorRatios {
|
||||
match *self {
|
||||
Color::Numeric(..) => ComplexColorRatios::NUMERIC,
|
||||
Color::Foreground => ComplexColorRatios::FOREGROUND,
|
||||
Color::Complex(.., ratios) => ratios,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,50 +146,66 @@ impl Animate for Color {
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
// Common cases are interpolating between two numeric colors,
|
||||
// two currentcolors, and a numeric color and a currentcolor.
|
||||
//
|
||||
// Note: this algorithm assumes self_portion + other_portion
|
||||
// equals to one, so it may be broken for additive operation.
|
||||
// To properly support additive color interpolation, we would
|
||||
// need two ratio fields in computed color types.
|
||||
let (this_weight, other_weight) = procedure.weights();
|
||||
if self.foreground_ratio == other.foreground_ratio {
|
||||
if self.is_currentcolor() {
|
||||
Ok(Color::currentcolor())
|
||||
} else {
|
||||
Ok(Color {
|
||||
color: self.color.animate(&other.color, procedure)?,
|
||||
foreground_ratio: self.foreground_ratio,
|
||||
})
|
||||
|
||||
Ok(match (*self, *other, procedure) {
|
||||
// Any interpolation of currentColor with currentColor returns currentColor.
|
||||
(Color::Foreground, Color::Foreground, Procedure::Interpolate { .. }) => {
|
||||
Color::currentcolor()
|
||||
}
|
||||
} else if self.is_currentcolor() && other.is_numeric() {
|
||||
Ok(Color {
|
||||
color: other.color,
|
||||
foreground_ratio: this_weight as f32,
|
||||
})
|
||||
} else if self.is_numeric() && other.is_currentcolor() {
|
||||
Ok(Color {
|
||||
color: self.color,
|
||||
foreground_ratio: other_weight as f32,
|
||||
})
|
||||
} else {
|
||||
// For interpolating between two complex colors, we need to
|
||||
// generate colors with effective alpha value.
|
||||
let self_color = self.effective_intermediate_rgba();
|
||||
let other_color = other.effective_intermediate_rgba();
|
||||
let color = self_color.animate(&other_color, procedure)?;
|
||||
// Then we compute the final foreground ratio, and derive
|
||||
// the final alpha value from the effective alpha value.
|
||||
let foreground_ratio = self.foreground_ratio
|
||||
.animate(&other.foreground_ratio, procedure)?;
|
||||
let alpha = color.alpha / (1. - foreground_ratio);
|
||||
Ok(Color {
|
||||
color: RGBA {
|
||||
alpha: alpha,
|
||||
..color
|
||||
// Animating two numeric colors.
|
||||
(Color::Numeric(c1), Color::Numeric(c2), _) => {
|
||||
Color::Numeric(c1.animate(&c2, procedure)?)
|
||||
}
|
||||
// Combinations of numeric color and currentColor
|
||||
(Color::Foreground, Color::Numeric(color), _) => Color::Complex(
|
||||
color,
|
||||
ComplexColorRatios {
|
||||
bg: other_weight as f32,
|
||||
fg: this_weight as f32,
|
||||
},
|
||||
foreground_ratio: foreground_ratio,
|
||||
})
|
||||
}
|
||||
),
|
||||
(Color::Numeric(color), Color::Foreground, _) => Color::Complex(
|
||||
color,
|
||||
ComplexColorRatios {
|
||||
bg: this_weight as f32,
|
||||
fg: other_weight as f32,
|
||||
},
|
||||
),
|
||||
|
||||
// Any other animation of currentColor with currentColor is complex.
|
||||
(Color::Foreground, Color::Foreground, _) => Color::Complex(
|
||||
RGBA::transparent(),
|
||||
ComplexColorRatios {
|
||||
bg: 0.,
|
||||
fg: (this_weight + other_weight) as f32,
|
||||
},
|
||||
),
|
||||
|
||||
// Defer to complex calculations
|
||||
_ => {
|
||||
// For interpolating between two complex colors, we need to
|
||||
// generate colors with effective alpha value.
|
||||
let self_color = self.effective_intermediate_rgba();
|
||||
let other_color = other.effective_intermediate_rgba();
|
||||
let color = self_color.animate(&other_color, procedure)?;
|
||||
// Then we compute the final background ratio, and derive
|
||||
// the final alpha value from the effective alpha value.
|
||||
let self_ratios = self.effective_ratios();
|
||||
let other_ratios = other.effective_ratios();
|
||||
let ratios = self_ratios.animate(&other_ratios, procedure)?;
|
||||
let alpha = color.alpha / ratios.bg;
|
||||
let color = RGBA { alpha, ..color };
|
||||
|
||||
if ratios == ComplexColorRatios::NUMERIC {
|
||||
Color::Numeric(color)
|
||||
} else if ratios == ComplexColorRatios::FOREGROUND {
|
||||
Color::Foreground
|
||||
} else {
|
||||
Color::Complex(color, ratios)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,27 +213,26 @@ impl ComputeSquaredDistance for Color {
|
||||
#[inline]
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
// All comments from the Animate impl also applies here.
|
||||
if self.foreground_ratio == other.foreground_ratio {
|
||||
if self.is_currentcolor() {
|
||||
Ok(SquaredDistance::from_sqrt(0.))
|
||||
} else {
|
||||
self.color.compute_squared_distance(&other.color)
|
||||
Ok(match (*self, *other) {
|
||||
(Color::Foreground, Color::Foreground) => SquaredDistance::from_sqrt(0.),
|
||||
(Color::Numeric(c1), Color::Numeric(c2)) => c1.compute_squared_distance(&c2)?,
|
||||
(Color::Foreground, Color::Numeric(color))
|
||||
| (Color::Numeric(color), Color::Foreground) => {
|
||||
// `computed_squared_distance` is symmetic.
|
||||
color.compute_squared_distance(&RGBA::transparent())?
|
||||
+ SquaredDistance::from_sqrt(1.)
|
||||
}
|
||||
} else if self.is_currentcolor() && other.is_numeric() {
|
||||
Ok(
|
||||
RGBA::transparent().compute_squared_distance(&other.color)? +
|
||||
SquaredDistance::from_sqrt(1.),
|
||||
)
|
||||
} else if self.is_numeric() && other.is_currentcolor() {
|
||||
Ok(self.color.compute_squared_distance(&RGBA::transparent())? +
|
||||
SquaredDistance::from_sqrt(1.))
|
||||
} else {
|
||||
let self_color = self.effective_intermediate_rgba();
|
||||
let other_color = other.effective_intermediate_rgba();
|
||||
Ok(self_color.compute_squared_distance(&other_color)? +
|
||||
self.foreground_ratio
|
||||
.compute_squared_distance(&other.foreground_ratio)?)
|
||||
}
|
||||
(_, _) => {
|
||||
let self_color = self.effective_intermediate_rgba();
|
||||
let other_color = other.effective_intermediate_rgba();
|
||||
let self_ratios = self.effective_ratios();
|
||||
let other_ratios = other.effective_ratios();
|
||||
|
||||
self_color.compute_squared_distance(&other_color)?
|
||||
+ self_ratios.bg.compute_squared_distance(&other_ratios.bg)?
|
||||
+ self_ratios.fg.compute_squared_distance(&other_ratios.fg)?
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,17 +10,36 @@ use style_traits::{CssWriter, ToCss};
|
||||
use values::animated::ToAnimatedValue;
|
||||
use values::animated::color::{Color as AnimatedColor, RGBA as AnimatedRGBA};
|
||||
|
||||
/// This struct represents a combined color from a numeric color and
|
||||
/// the current foreground color (currentcolor keyword).
|
||||
/// Conceptually, the formula is "color * (1 - p) + currentcolor * p"
|
||||
/// where p is foreground_ratio.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf)]
|
||||
pub struct Color {
|
||||
/// RGBA color.
|
||||
pub color: RGBA,
|
||||
/// Ratios representing the contribution of color and currentcolor to
|
||||
/// the final color value.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
|
||||
pub struct ComplexColorRatios {
|
||||
/// Numeric color contribution.
|
||||
pub bg: f32,
|
||||
/// Foreground color, aka currentcolor, contribution.
|
||||
pub fg: f32,
|
||||
}
|
||||
|
||||
/// The ratio of currentcolor in complex color.
|
||||
pub foreground_ratio: u8,
|
||||
impl ComplexColorRatios {
|
||||
/// Ratios representing pure numeric color.
|
||||
pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. };
|
||||
/// Ratios representing pure foreground color.
|
||||
pub const FOREGROUND: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. };
|
||||
}
|
||||
|
||||
/// This enum represents a combined color from a numeric color and
|
||||
/// the current foreground color (currentColor keyword).
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
|
||||
pub enum Color {
|
||||
/// Numeric RGBA color.
|
||||
Numeric(RGBA),
|
||||
|
||||
/// The current foreground color.
|
||||
Foreground,
|
||||
|
||||
/// A linear combination of numeric color and currentColor.
|
||||
/// The formula is: `color * bg_ratio + currentColor * fg_ratio`.
|
||||
Complex(RGBA, ComplexColorRatios),
|
||||
}
|
||||
|
||||
/// Computed value type for the specified RGBAColor.
|
||||
@ -31,11 +50,8 @@ pub type ColorPropertyValue = RGBA;
|
||||
|
||||
impl Color {
|
||||
/// Returns a numeric color representing the given RGBA value.
|
||||
pub fn rgba(rgba: RGBA) -> Color {
|
||||
Color {
|
||||
color: rgba,
|
||||
foreground_ratio: 0,
|
||||
}
|
||||
pub fn rgba(color: RGBA) -> Color {
|
||||
Color::Numeric(color)
|
||||
}
|
||||
|
||||
/// Returns a complex color value representing transparent.
|
||||
@ -45,73 +61,53 @@ impl Color {
|
||||
|
||||
/// Returns a complex color value representing currentcolor.
|
||||
pub fn currentcolor() -> Color {
|
||||
Color {
|
||||
color: RGBA::transparent(),
|
||||
foreground_ratio: u8::max_value(),
|
||||
}
|
||||
Color::Foreground
|
||||
}
|
||||
|
||||
/// Whether it is a numeric color (no currentcolor component).
|
||||
pub fn is_numeric(&self) -> bool {
|
||||
self.foreground_ratio == 0
|
||||
matches!(*self, Color::Numeric { .. })
|
||||
}
|
||||
|
||||
/// Whether it is a currentcolor value (no numeric color component).
|
||||
pub fn is_currentcolor(&self) -> bool {
|
||||
self.foreground_ratio == u8::max_value()
|
||||
matches!(*self, Color::Foreground)
|
||||
}
|
||||
|
||||
/// Combine this complex color with the given foreground color into
|
||||
/// a numeric RGBA color. It currently uses linear blending.
|
||||
pub fn to_rgba(&self, fg_color: RGBA) -> RGBA {
|
||||
// Common cases that the complex color is either pure numeric
|
||||
// color or pure currentcolor.
|
||||
if self.is_numeric() {
|
||||
return self.color;
|
||||
}
|
||||
if self.is_currentcolor() {
|
||||
return fg_color.clone();
|
||||
}
|
||||
|
||||
fn blend_color_component(bg: u8, fg: u8, fg_alpha: u8) -> u8 {
|
||||
let bg_ratio = (u8::max_value() - fg_alpha) as u32;
|
||||
let fg_ratio = fg_alpha as u32;
|
||||
let color = bg as u32 * bg_ratio + fg as u32 * fg_ratio;
|
||||
// Rounding divide the number by 255
|
||||
((color + 127) / 255) as u8
|
||||
}
|
||||
|
||||
// Common case that alpha channel is equal (usually both are opaque).
|
||||
let fg_ratio = self.foreground_ratio;
|
||||
if self.color.alpha == fg_color.alpha {
|
||||
let r = blend_color_component(self.color.red, fg_color.red, fg_ratio);
|
||||
let g = blend_color_component(self.color.green, fg_color.green, fg_ratio);
|
||||
let b = blend_color_component(self.color.blue, fg_color.blue, fg_ratio);
|
||||
return RGBA::new(r, g, b, fg_color.alpha);
|
||||
}
|
||||
let (color, ratios) = match *self {
|
||||
// Common cases that the complex color is either pure numeric
|
||||
// color or pure currentcolor.
|
||||
Color::Numeric(color) => return color,
|
||||
Color::Foreground => return fg_color,
|
||||
Color::Complex(color, ratios) => (color, ratios),
|
||||
};
|
||||
|
||||
// For the more complicated case that the alpha value differs,
|
||||
// we use the following formula to compute the components:
|
||||
// alpha = self_alpha * (1 - fg_ratio) + fg_alpha * fg_ratio
|
||||
// color = (self_color * self_alpha * (1 - fg_ratio) +
|
||||
// alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio
|
||||
// color = (self_color * self_alpha * bg_ratio +
|
||||
// fg_color * fg_alpha * fg_ratio) / alpha
|
||||
|
||||
let p1 = (1. / 255.) * (255 - fg_ratio) as f32;
|
||||
let a1 = self.color.alpha_f32();
|
||||
let r1 = a1 * self.color.red_f32();
|
||||
let g1 = a1 * self.color.green_f32();
|
||||
let b1 = a1 * self.color.blue_f32();
|
||||
let p1 = ratios.bg;
|
||||
let a1 = color.alpha_f32();
|
||||
let r1 = a1 * color.red_f32();
|
||||
let g1 = a1 * color.green_f32();
|
||||
let b1 = a1 * color.blue_f32();
|
||||
|
||||
let p2 = 1. - p1;
|
||||
let p2 = ratios.fg;
|
||||
let a2 = fg_color.alpha_f32();
|
||||
let r2 = a2 * fg_color.red_f32();
|
||||
let g2 = a2 * fg_color.green_f32();
|
||||
let b2 = a2 * fg_color.blue_f32();
|
||||
|
||||
let a = p1 * a1 + p2 * a2;
|
||||
if a == 0.0 {
|
||||
if a <= 0. {
|
||||
return RGBA::transparent();
|
||||
}
|
||||
let a = f32::min(a, 1.);
|
||||
|
||||
let inverse_a = 1. / a;
|
||||
let r = (p1 * r1 + p2 * r2) * inverse_a;
|
||||
@ -121,19 +117,9 @@ impl Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Color {
|
||||
fn eq(&self, other: &Color) -> bool {
|
||||
self.foreground_ratio == other.foreground_ratio &&
|
||||
(self.is_currentcolor() || self.color == other.color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RGBA> for Color {
|
||||
fn from(color: RGBA) -> Color {
|
||||
Color {
|
||||
color: color,
|
||||
foreground_ratio: 0,
|
||||
}
|
||||
Color::Numeric(color)
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,12 +128,10 @@ impl ToCss for Color {
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
if self.is_numeric() {
|
||||
self.color.to_css(dest)
|
||||
} else if self.is_currentcolor() {
|
||||
CSSParserColor::CurrentColor.to_css(dest)
|
||||
} else {
|
||||
Ok(())
|
||||
match *self {
|
||||
Color::Numeric(color) => color.to_css(dest),
|
||||
Color::Foreground => CSSParserColor::CurrentColor.to_css(dest),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,17 +141,23 @@ impl ToAnimatedValue for Color {
|
||||
|
||||
#[inline]
|
||||
fn to_animated_value(self) -> Self::AnimatedValue {
|
||||
AnimatedColor {
|
||||
color: self.color.to_animated_value(),
|
||||
foreground_ratio: self.foreground_ratio as f32 * (1. / 255.),
|
||||
match self {
|
||||
Color::Numeric(color) => AnimatedColor::Numeric(color.to_animated_value()),
|
||||
Color::Foreground => AnimatedColor::Foreground,
|
||||
Color::Complex(color, ratios) => {
|
||||
AnimatedColor::Complex(color.to_animated_value(), ratios)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
|
||||
Color {
|
||||
color: RGBA::from_animated_value(animated.color),
|
||||
foreground_ratio: (animated.foreground_ratio * 255.).round() as u8,
|
||||
match animated {
|
||||
AnimatedColor::Numeric(color) => Color::Numeric(RGBA::from_animated_value(color)),
|
||||
AnimatedColor::Foreground => Color::Foreground,
|
||||
AnimatedColor::Complex(color, ratios) => {
|
||||
Color::Complex(RGBA::from_animated_value(color), ratios)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier,
|
||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
|
||||
pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
|
||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||
pub use self::color::{Color, ColorPropertyValue, ComplexColorRatios, RGBAColor};
|
||||
pub use self::column::ColumnCount;
|
||||
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
|
||||
pub use self::effects::{BoxShadow, Filter, SimpleShadow};
|
||||
|
@ -88,11 +88,11 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen
|
||||
};
|
||||
|
||||
Ok(AngleOrNumber::Angle { degrees })
|
||||
},
|
||||
}
|
||||
Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
input.parse_nested_block(|i| CalcNode::parse_angle_or_number(self.0, i))
|
||||
},
|
||||
}
|
||||
t => return Err(location.new_unexpected_token_error(t)),
|
||||
}
|
||||
}
|
||||
@ -119,10 +119,10 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen
|
||||
Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
|
||||
Token::Percentage { unit_value, .. } => {
|
||||
Ok(NumberOrPercentage::Percentage { unit_value })
|
||||
},
|
||||
}
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
input.parse_nested_block(|i| CalcNode::parse_number_or_percentage(self.0, i))
|
||||
},
|
||||
}
|
||||
t => return Err(location.new_unexpected_token_error(t)),
|
||||
}
|
||||
}
|
||||
@ -168,10 +168,10 @@ impl Parse for Color {
|
||||
Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
|
||||
ValueParseErrorKind::InvalidColor(t),
|
||||
)))
|
||||
},
|
||||
}
|
||||
_ => Err(e),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,10 +275,10 @@ impl Color {
|
||||
}
|
||||
return parse_hash_color(ident.as_bytes())
|
||||
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
},
|
||||
}
|
||||
ref t => {
|
||||
return Err(location.new_unexpected_token_error(t.clone()));
|
||||
},
|
||||
}
|
||||
};
|
||||
if value < 0 {
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
@ -358,11 +358,11 @@ impl Color {
|
||||
Keyword::MozVisitedhyperlinktext => pres_context.mVisitedLinkColor,
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
#[cfg(feature = "gecko")]
|
||||
Color::InheritFromBodyQuirk => {
|
||||
_context.map(|context| ComputedColor::rgba(context.device().body_text_color()))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,7 +372,7 @@ impl ToComputedValue for Color {
|
||||
|
||||
fn to_computed_value(&self, context: &Context) -> ComputedColor {
|
||||
let result = self.to_computed_color(Some(context)).unwrap();
|
||||
if result.foreground_ratio != 0 {
|
||||
if !result.is_numeric() {
|
||||
if let Some(longhand) = context.for_non_inherited_property {
|
||||
if longhand.stores_complex_colors_lossily() {
|
||||
context.rule_cache_conditions.borrow_mut().set_uncacheable();
|
||||
@ -383,12 +383,10 @@ impl ToComputedValue for Color {
|
||||
}
|
||||
|
||||
fn from_computed_value(computed: &ComputedColor) -> Self {
|
||||
if computed.is_numeric() {
|
||||
Color::rgba(computed.color)
|
||||
} else if computed.is_currentcolor() {
|
||||
Color::currentcolor()
|
||||
} else {
|
||||
Color::Complex(*computed)
|
||||
match *computed {
|
||||
ComputedColor::Numeric(color) => Color::rgba(color),
|
||||
ComputedColor::Foreground => Color::currentcolor(),
|
||||
ComputedColor::Complex(..) => Color::Complex(*computed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4136,7 +4136,7 @@ GetScrollbarFaceColor(ComputedStyle* aStyle)
|
||||
{
|
||||
StyleComplexColor complexColor =
|
||||
aStyle->StyleUserInterface()->mScrollbarFaceColor;
|
||||
if (complexColor.mIsAuto) {
|
||||
if (complexColor.IsAuto()) {
|
||||
return GetScrollbarFaceColorForAuto();
|
||||
}
|
||||
nscolor color = complexColor.CalcColor(aStyle);
|
||||
@ -4153,7 +4153,7 @@ GetScrollbarTrackColor(ComputedStyle* aStyle)
|
||||
StyleComplexColor complexColor =
|
||||
aStyle->StyleUserInterface()->mScrollbarTrackColor;
|
||||
nscolor color;
|
||||
if (complexColor.mIsAuto) {
|
||||
if (complexColor.IsAuto()) {
|
||||
color = GetScrollbarTrackColorForAuto(aStyle);
|
||||
} else {
|
||||
color = complexColor.CalcColor(aStyle);
|
||||
@ -4263,8 +4263,8 @@ nsNativeThemeWin::DrawCustomScrollbarPart(gfxContext* aContext,
|
||||
const nsRect& aRect,
|
||||
const nsRect& aClipRect)
|
||||
{
|
||||
MOZ_ASSERT(!aStyle->StyleUserInterface()->mScrollbarFaceColor.mIsAuto ||
|
||||
!aStyle->StyleUserInterface()->mScrollbarTrackColor.mIsAuto);
|
||||
MOZ_ASSERT(!aStyle->StyleUserInterface()->mScrollbarFaceColor.IsAuto() ||
|
||||
!aStyle->StyleUserInterface()->mScrollbarTrackColor.IsAuto());
|
||||
|
||||
gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
|
||||
dr(aClipRect.X(), aClipRect.Y(),
|
||||
|
Loading…
Reference in New Issue
Block a user