Skip to content

Commit

Permalink
style: Extend StyleComplexColor to support additive blending.
Browse files Browse the repository at this point in the history
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" ... />

Bug: 1465307
Reviewed-by: hiro,xidorn
MozReview-Commit-ID: IUAK8P07gtm
  • Loading branch information
djg authored and emilio committed Jun 12, 2018
1 parent 3816143 commit 255fe05
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 204 deletions.
81 changes: 56 additions & 25 deletions components/style/gecko_bindings/sugar/style_complex_color.rs
Expand Up @@ -5,57 +5,88 @@
//! 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,
}
}

/// Create a `StyleComplexColor` value that represents `auto`.
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"),
}
}
}
Expand All @@ -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)
Expand Down
189 changes: 107 additions & 82 deletions components/style/values/animated/color.rs
Expand Up @@ -6,6 +6,7 @@

use values::animated::{Animate, Procedure, ToAnimatedZero};
use values::distance::{ComputeSquaredDistance, SquaredDistance};
use values::computed::ComplexColorRatios;

/// An animated RGBA color.
///
Expand Down Expand Up @@ -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.,
}
Color::Numeric(RGBA::transparent())
}

fn is_currentcolor(&self) -> bool {
self.foreground_ratio >= 1.
}

fn is_numeric(&self) -> bool {
self.foreground_ratio <= 0.
fn effective_intermediate_rgba(&self) -> RGBA {
match *self {
Color::Numeric(color) => color,
Color::Foreground => RGBA::transparent(),
Color::Complex(color, ratios) => RGBA {
alpha: color.alpha * ratios.bg,
..color.clone()
},
}
}

fn effective_intermediate_rgba(&self) -> RGBA {
RGBA {
alpha: self.color.alpha * (1. - self.foreground_ratio),
..self.color
fn effective_ratios(&self) -> ComplexColorRatios {
match *self {
Color::Numeric(..) => ComplexColorRatios::NUMERIC,
Color::Foreground => ComplexColorRatios::FOREGROUND,
Color::Complex(.., ratios) => ratios,
}
}
}
Expand All @@ -136,78 +146,93 @@ 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()
}
// Animating two numeric colors.
(Color::Numeric(c1), Color::Numeric(c2), _) => {
Color::Numeric(c1.animate(&c2, procedure)?)
}
} 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
// 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)
}
}
})
}
}

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

Expand Down

0 comments on commit 255fe05

Please sign in to comment.