Bug 1467622 - P4: Correct background/foreground ratio mixing. r?xidorn draft
authorDan Glastonbury <dan.glastonbury@gmail.com>
Tue, 03 Jul 2018 14:15:06 +1000
changeset 817197 10a88f3b27f6c1e503b3e383b54324eb96caeb9f
parent 817196 d3fafe12ac682bc53b3ee2efe7833b1c39f919c1
child 817198 1024922585cbe104662f3a8a5fc978e43d335f9d
push id115979
push userbmo:dglastonbury@mozilla.com
push dateThu, 12 Jul 2018 06:16:43 +0000
reviewersxidorn
bugs1467622
milestone63.0a1
Bug 1467622 - P4: Correct background/foreground ratio mixing. r?xidorn MozReview-Commit-ID: InAZVcH2Vkt
servo/components/style/values/animated/color.rs
servo/components/style/values/generics/color.rs
--- a/servo/components/style/values/animated/color.rs
+++ b/servo/components/style/values/animated/color.rs
@@ -121,61 +121,113 @@ impl Animate for Color {
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         use self::GenericColor::*;
 
         // Common cases are interpolating between two numeric colors,
         // two currentcolors, and a numeric color and a currentcolor.
         let (this_weight, other_weight) = procedure.weights();
 
         Ok(match (*self, *other, procedure) {
-            // Any interpolation of currentColor with currentColor returns currentColor.
-            (Foreground, Foreground, Procedure::Interpolate { .. }) => Color::currentcolor(),
+            // Any interpolation of currentcolor with currentcolor returns currentcolor.
+            (Foreground, Foreground, Procedure::Interpolate { .. }) => Foreground,
             // Animating two numeric colors.
             (Numeric(c1), Numeric(c2), _) => Numeric(c1.animate(&c2, procedure)?),
-            // Combinations of numeric color and currentColor
+            // Combinations of numeric color and currentcolor
             (Foreground, Numeric(color), _) => Self::with_ratios(
                 color,
                 ComplexColorRatios {
                     bg: other_weight as f32,
                     fg: this_weight as f32,
                 },
             ),
             (Numeric(color), Foreground, _) => Self::with_ratios(
                 color,
                 ComplexColorRatios {
                     bg: this_weight as f32,
                     fg: other_weight as f32,
                 },
             ),
 
-            // Any other animation of currentColor with currentColor.
+            // Any other animation of currentcolor with currentcolor.
             (Foreground, Foreground, _) => Self::with_ratios(
                 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 };
+                // Compute the "scaled" contribution for `color`.
+                fn scaled_rgba(color: &Color) -> RGBA {
+                    match *color {
+                        GenericColor::Numeric(color) => color,
+                        GenericColor::Foreground => RGBA::transparent(),
+                        GenericColor::Complex(color, ratios) => RGBA {
+                            red: color.red * ratios.bg,
+                            green: color.green * ratios.bg,
+                            blue: color.blue * ratios.bg,
+                            alpha: color.alpha * ratios.bg,
+                        },
+                    }
+                }
 
-                Self::with_ratios(color, ratios)
+                // Each `Color`, represents a complex combination of foreground color and
+                // background color where fg and bg represent the overall
+                // contributions. ie:
+                //
+                //    color = { bg * mColor, fg * foreground }
+                //          =   { bg_color , fg_color }
+                //          =     bg_color + fg_color
+                //
+                // where `foreground` is `currentcolor`, and `bg_color`,
+                // `fg_color` are the scaled background and foreground
+                // contributions.
+                //
+                // Each operation, lerp, addition, or accumulate, can be
+                // represented as a scaled-addition each complex color. ie:
+                //
+                //    p * col1 + q * col2
+                //
+                // where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for
+                // addition, etc.
+                //
+                // Therefore:
+                //
+                //    col1 op col2
+                //    = p * col1 + q * col2
+                //    = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 }
+                //    = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2)
+                //    = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2
+                //    = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2)
+                //    = (bg_color1 op bg_color2) + (fg_color1 op fg_color2)
+                //
+                // fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground,
+                // so the final color is:
+                //
+                //    = { bg_color, fg_color }
+                //    = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground }
+
+                // To perform the operation on two complex colors, we need to
+                // generate the scaled contributions of each background color
+                // component.
+                let bg_color1 = scaled_rgba(self);
+                let bg_color2 = scaled_rgba(other);
+                // Perform bg_color1 op bg_color2
+                let bg_color = bg_color1.animate(&bg_color2, procedure)?;
+
+                // Calculate the final foreground color ratios; perform
+                // animation on effective fg ratios.
+                let ComplexColorRatios { fg: fg1, .. } = self.effective_ratios();
+                let ComplexColorRatios { fg: fg2, .. } = other.effective_ratios();
+                // Perform fg1 op fg2
+                let fg = fg1.animate(&fg2, procedure)?;
+
+                Self::with_ratios(bg_color, ComplexColorRatios { bg: 1., fg })
             }
         })
     }
 }
 
 impl ComputeSquaredDistance for Color {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
--- a/servo/components/style/values/generics/color.rs
+++ b/servo/components/style/values/generics/color.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Generic types for color properties.
 
 /// Ratios representing the contribution of color and currentcolor to
 /// the final color value.
-#[derive(Animate, Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue)]
 pub struct ComplexColorRatios {
     /// Numeric color contribution.
     pub bg: f32,
     /// Foreground color, aka currentcolor, contribution.
     pub fg: f32,
 }
 
 impl ComplexColorRatios {