Bug 1216843 - Part 7: Implement color accumulation. r?dholbert draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Mon, 12 Sep 2016 14:34:02 +0900
changeset 412520 d316777c993b4764c7549739d9b3ec7243473211
parent 412499 e57fc4ccec639d95d06aec1038b23c2c0d6f2674
child 412521 162147b9270f422cff4522d1680b830119c4852c
child 412547 ab3d7c0097294adcecf80c14f7319deea7a87abc
push id29192
push userbmo:hiikezoe@mozilla-japan.org
push dateMon, 12 Sep 2016 05:39:50 +0000
reviewersdholbert
bugs1216843
milestone51.0a1
Bug 1216843 - Part 7: Implement color accumulation. r?dholbert MozReview-Commit-ID: Ic7dIrZWvih
layout/style/StyleAnimationValue.cpp
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Utilities for animation of computed style values */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/StyleAnimationValue.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "nsStyleTransformMatrix.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsIStyleRule.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsString.h"
 #include "nsStyleContext.h"
@@ -1157,82 +1158,122 @@ AddCSSValuePercentNumber(const uint32_t 
   // aCoeff2 is 0, then we'll return the value halfway between 1 and
   // aValue1, rather than the value halfway between 0 and aValue1.
   // Note that we do something similar in AddTransformScale().
   float result = (n1 - aInitialVal) * aCoeff1 + (n2 - aInitialVal) * aCoeff2;
   aResult.SetFloatValue(RestrictValue(aValueRestrictions, result + aInitialVal),
                         eCSSUnit_Number);
 }
 
+// Returns Tuple(Red, Green, Blue, Alpha).
+static Tuple<double, double, double, double>
+GetPremultipliedColor(const nsCSSValue& aValue)
+{
+  // PercentageRGBColor and PercentageRGBAColor component value might be
+  // greater than 1.0 in case when the color value is accumulated, so we
+  // can't use nsCSSValue::GetColorValue() here because that function
+  // clamps its values.
+  if (aValue.GetUnit() == eCSSUnit_PercentageRGBColor ||
+      aValue.GetUnit() == eCSSUnit_PercentageRGBAColor) {
+    nsCSSValueFloatColor* floatColor = aValue.GetFloatColorValue();
+    double alpha = floatColor->Alpha();
+    return MakeTuple(
+        floatColor->Comp1() * 255.0 * alpha,
+        floatColor->Comp2() * 255.0 * alpha,
+        floatColor->Comp3() * 255.0 * alpha,
+        alpha);
+  }
+
+  nscolor color = aValue.GetColorValue();
+  double alpha = NS_GET_A(color) * (1.0 / 255.0);
+  return MakeTuple(
+      NS_GET_R(color) * alpha,
+      NS_GET_G(color) * alpha,
+      NS_GET_B(color) * alpha,
+      alpha);
+}
+
+enum class ColorAdditionType {
+  Clamped, // Clamp each color channel after adding.
+  Unclamped // Do not clamp color channels after adding.
+};
+
+// |aAdditionType| should be Clamped in case of interpolation or SMIL
+// animation (e.g. 'by' attribute). For now, Unclamped is only for
+// accumulation.
 static void
 AddWeightedColors(double aCoeff1, const nsCSSValue& aValue1,
                   double aCoeff2, const nsCSSValue& aValue2,
+                  ColorAdditionType aAdditionType,
                   nsCSSValue& aResult)
 {
   MOZ_ASSERT(aValue1.IsNumericColorUnit() && aValue2.IsNumericColorUnit(),
              "The unit should be color");
-  nscolor color1 = aValue1.GetColorValue();
-  nscolor color2 = aValue2.GetColorValue();
   // FIXME (spec): The CSS transitions spec doesn't say whether
   // colors are premultiplied, but things work better when they are,
   // so use premultiplication.  Spec issue is still open per
   // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
 
   // To save some math, scale the alpha down to a 0-1 scale, but
   // leave the color components on a 0-255 scale.
-  double A1 = NS_GET_A(color1) * (1.0 / 255.0);
-  double R1 = NS_GET_R(color1) * A1;
-  double G1 = NS_GET_G(color1) * A1;
-  double B1 = NS_GET_B(color1) * A1;
-  double A2 = NS_GET_A(color2) * (1.0 / 255.0);
-  double R2 = NS_GET_R(color2) * A2;
-  double G2 = NS_GET_G(color2) * A2;
-  double B2 = NS_GET_B(color2) * A2;
+
+  double R1, G1, B1, A1;
+  Tie(R1, G1, B1, A1) = GetPremultipliedColor(aValue1);
+  double R2, G2, B2, A2;
+  Tie(R2, G2, B2, A2) = GetPremultipliedColor(aValue2);
   double Aresf = (A1 * aCoeff1 + A2 * aCoeff2);
   if (Aresf <= 0.0) {
     aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
     return;
   }
 
   if (Aresf > 1.0) {
     Aresf = 1.0;
   }
 
   double factor = 1.0 / Aresf;
-  uint8_t Ares = NSToIntRound(Aresf * 255.0);
-  uint8_t Rres = ClampColor((R1 * aCoeff1 + R2 * aCoeff2) * factor);
-  uint8_t Gres = ClampColor((G1 * aCoeff1 + G2 * aCoeff2) * factor);
-  uint8_t Bres = ClampColor((B1 * aCoeff1 + B2 * aCoeff2) * factor);
-  aResult.SetColorValue(NS_RGBA(Rres, Gres, Bres, Ares));
+  double Rres = (R1 * aCoeff1 + R2 * aCoeff2) * factor;
+  double Gres = (G1 * aCoeff1 + G2 * aCoeff2) * factor;
+  double Bres = (B1 * aCoeff1 + B2 * aCoeff2) * factor;
+
+  if (aAdditionType == ColorAdditionType::Clamped) {
+    aResult.SetColorValue(
+      NS_RGBA(ClampColor(Rres), ClampColor(Gres), ClampColor(Bres),
+              NSToIntRound(Aresf * 255.0)));
+    return;
+  }
+
+  Rres = Rres * (1.0 / 255.0);
+  Gres = Gres * (1.0 / 255.0);
+  Bres = Bres * (1.0 / 255.0);
+
+  aResult.SetFloatColorValue(Rres, Gres, Bres, Aresf,
+                             eCSSUnit_PercentageRGBAColor);
 }
 
 // Multiplies |aValue| color by |aDilutionRation| with premultiplication.
 // The result is stored in |aResult|.
 // (The logic here should pretty closely match AddWeightedColors()' logic.)
 static void
 DiluteColor(const nsCSSValue& aValue, double aDilutionRatio,
             nsCSSValue& aResult)
 {
   MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color");
   MOZ_ASSERT(aDilutionRatio >= 0.0 && aDilutionRatio <= 1.0,
              "Dilution ratio should be in [0, 1]");
 
-  nscolor color = aValue.GetColorValue();
-  double A = NS_GET_A(color) * (1.0 / 255.0);
+  // Premultiplication
+  double R, G, B, A;
+  Tie(R, G, B, A) = GetPremultipliedColor(aValue);
   double Aresf = A * aDilutionRatio;
   if (Aresf <= 0.0) {
     aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
     return;
   }
 
-  // Premultiplication
-  double R = NS_GET_R(color) * A;
-  double G = NS_GET_G(color) * A;
-  double B = NS_GET_B(color) * A;
-
   double factor = 1.0 / Aresf;
   aResult.SetColorValue(
     NS_RGBA(ClampColor(R * aDilutionRatio * factor),
             ClampColor(G * aDilutionRatio * factor),
             ClampColor(B * aDilutionRatio * factor),
             NSToIntRound(Aresf * 255.0)));
 }
 
@@ -1256,28 +1297,33 @@ AddShadowItems(double aCoeff1, const nsC
                      // blur radius must be nonnegative
                      (i == 2) ? CSS_PROPERTY_VALUE_NONNEGATIVE : 0);
   }
 
   const nsCSSValue& color1 = array1->Item(4);
   const nsCSSValue& color2 = array2->Item(4);
   const nsCSSValue& inset1 = array1->Item(5);
   const nsCSSValue& inset2 = array2->Item(5);
-  if (color1.GetUnit() != color2.GetUnit() ||
+  if ((color1.GetUnit() != color2.GetUnit() &&
+       (!color1.IsNumericColorUnit() || !color2.IsNumericColorUnit())) ||
       inset1.GetUnit() != inset2.GetUnit()) {
     // We don't know how to animate between color and no-color, or
     // between inset and not-inset.
+    // NOTE: In case when both colors' units are eCSSUnit_Null, that means
+    // neither color value was specified, so we can interpolate.
     return false;
   }
 
   if (color1.GetUnit() != eCSSUnit_Null) {
     if (aCoeff2 == 0.0 && aCoeff1 != 1.0) {
       DiluteColor(color1, aCoeff1, resultArray->Item(4));
     } else {
-      AddWeightedColors(aCoeff1, color1, aCoeff2, color2, resultArray->Item(4));
+      AddWeightedColors(aCoeff1, color1, aCoeff2, color2,
+                        ColorAdditionType::Clamped,
+                        resultArray->Item(4));
     }
   }
 
   MOZ_ASSERT(inset1 == inset2, "should match");
   resultArray->Item(5) = inset1;
 
   nsCSSValueList *resultItem = new nsCSSValueList;
   resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
@@ -2421,17 +2467,19 @@ StyleAnimationValue::AddWeighted(nsCSSPr
       // We are using AddWeighted() with a zero aCoeff2 for colors to
       // pretend AddWeighted() against transparent color, i.e. rgba(0, 0, 0, 0).
       // But unpremultiplication in AddWeightedColors() does not work well
       // for such cases, so we use another function named DiluteColor() which
       // has a similar logic to AddWeightedColors().
       if (aCoeff2 == 0.0) {
         DiluteColor(*value1, aCoeff1, *resultColor);
       } else {
-        AddWeightedColors(aCoeff1, *value1, aCoeff2, *value2, *resultColor);
+        AddWeightedColors(aCoeff1, *value1, aCoeff2, *value2,
+                          ColorAdditionType::Clamped,
+                          *resultColor);
       }
       aResultValue.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
       return true;
     }
     case eUnit_Calc: {
       PixelCalcValue v1 = ExtractCalcValue(aValue1);
       PixelCalcValue v2 = ExtractCalcValue(aValue2);
       double len = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength;
@@ -2812,19 +2860,30 @@ StyleAnimationValue::Accumulate(nsCSSPro
                                 StyleAnimationValue& aDest,
                                 const StyleAnimationValue& aValueToAccumulate,
                                 uint64_t aCount)
 {
   Unit commonUnit =
     GetCommonUnit(aProperty, aDest.GetUnit(), aValueToAccumulate.GetUnit());
   switch (commonUnit) {
     // FIXME: implement them!
-    //case eUnit_Color:
     //case eUnit_Shadow:
     //case eUnit_Filter:
+    case eUnit_Color: {
+      auto resultColor = MakeUnique<nsCSSValue>();
+      AddWeightedColors(1.0,
+                        *aDest.GetCSSValueValue(),
+                        aCount,
+                        *aValueToAccumulate.GetCSSValueValue(),
+                        ColorAdditionType::Unclamped,
+                        *resultColor);
+
+      aDest.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
+      return true;
+    }
     default:
       return Add(aProperty, aDest, aValueToAccumulate, aCount);
   }
   MOZ_ASSERT_UNREACHABLE("Can't accumulate using the given common unit");
   return false;
 }
 
 already_AddRefed<css::StyleRule>
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
@@ -1,11 +1,8 @@
 [iterationComposite.html]
   type: testharness
-  [iterationComposite of <color> type animation]
-    expected: FAIL
-
   [iterationComposite of filter drop-shadow animation]
     expected: FAIL
 
   [iterationComposite of box-shadow animation]
     expected: FAIL