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:
Dan Glastonbury 2018-05-23 15:23:26 +10:00
parent 70a6147545
commit 7811112058
13 changed files with 368 additions and 268 deletions

View File

@ -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();
}
/**

View File

@ -154,6 +154,7 @@ rusty-enums = [
"mozilla::StyleOrient",
"mozilla::StyleBoxSizing",
"mozilla::StyleClear",
"mozilla::StyleComplexColor_Tag",
"mozilla::StyleFloat",
"mozilla::StyleUserModify",
"mozilla::StyleUserInput",

View File

@ -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

View File

@ -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_

View File

@ -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;
}

View File

@ -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()

View File

@ -2861,7 +2861,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUserInterface
bool HasCustomScrollbars() const
{
return !mScrollbarFaceColor.mIsAuto || !mScrollbarTrackColor.mIsAuto;
return !mScrollbarFaceColor.IsAuto() || !mScrollbarTrackColor.IsAuto();
}
};

View File

@ -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)

View File

@ -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)?
}
})
}
}

View File

@ -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)
}
}
}
}

View File

@ -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};

View File

@ -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),
}
}
}

View File

@ -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(),