Bug 1299741 part 6 - Use RGBAColorData for color calculation in StyleAnimationValue. r=birtles draft
authorXidorn Quan <me@upsuper.org>
Fri, 16 Sep 2016 15:36:39 +1000
changeset 415365 2e914c26fac4613c25d5f1381693617a9bbbe412
parent 415364 b76c7d39ca21e4316a09524bbbc918a12ab81194
child 415366 722d2aee06e59cdf061d0daae43d8dbc0a9641b4
push id29862
push userxquan@mozilla.com
push dateTue, 20 Sep 2016 08:44:59 +0000
reviewersbirtles
bugs1299741, 1216843
milestone52.0a1
Bug 1299741 part 6 - Use RGBAColorData for color calculation in StyleAnimationValue. r=birtles This makes it easier for reusing code for calculating ComplexColor. Note that this patch also includes two fixes to the logic: 1. Fix the condition on when DiluteColor should be called instead of AddWeightedColors. See discussion in bug 1216843 comment 199 to 204. 2. Simplify DiluteColor to not using premultiplied color. See discussion in bug 1216843 comment 205 to 206. MozReview-Commit-ID: DOXMpDkwhAK
layout/style/StyleAnimationValue.cpp
layout/style/StyleAnimationValue.h
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/Likely.h"
 #include "gfxMatrix.h"
 #include "gfxQuaternion.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla;
+using namespace mozilla::css;
 using namespace mozilla::gfx;
 
 // HELPER METHODS
 // --------------
 /*
  * Given two units, this method returns a common unit that they can both be
  * converted into, if possible.  This is intended to facilitate
  * interpolation, distance-computation, and addition between "similar" units.
@@ -482,41 +483,63 @@ CalcPositionCoordSquareDistance(const ns
   float difflen = calcVal2.mLength - calcVal1.mLength;
   float diffpct = calcVal2.mPercent - calcVal1.mPercent;
   return difflen * difflen + diffpct * diffpct;
 }
 
 // CLASS METHODS
 // -------------
 
+static RGBAColorData
+ExtractColor(const nsCSSValue& aValue)
+{
+  MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color");
+  // 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();
+    return {
+      floatColor->Comp1(),
+      floatColor->Comp2(),
+      floatColor->Comp3(),
+      floatColor->Alpha()
+    };
+  }
+  return RGBAColorData(aValue.GetColorValue());
+}
+
+static RGBAColorData
+ExtractColor(const StyleAnimationValue& aValue)
+{
+  MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Color);
+  nsCSSValue* value = aValue.GetCSSValueValue();
+  MOZ_ASSERT(value, "CSS value must be valid");
+  return ExtractColor(*value);
+}
+
 double
-StyleAnimationValue::ComputeColorDistance(nscolor aStartColor,
-                                          nscolor aEndColor)
+StyleAnimationValue::ComputeColorDistance(const RGBAColorData& aStartColor,
+                                          const RGBAColorData& aEndColor)
 {
   // http://www.w3.org/TR/smil-animation/#animateColorElement says
   // that we should use Euclidean RGB cube distance.  However, we
   // have to extend that to RGBA.  For now, we'll just use the
   // Euclidean distance in the (part of the) 4-cube of premultiplied
   // colors.
-
-  // Get a color component on a 0-1 scale, which is much easier to
-  // deal with when working with alpha.
-#define GET_COMPONENT(component_, color_) \
-  (NS_GET_##component_(color_) * (1.0 / 255.0))
-
-  double startA = GET_COMPONENT(A, aStartColor);
-  double startR = GET_COMPONENT(R, aStartColor) * startA;
-  double startG = GET_COMPONENT(G, aStartColor) * startA;
-  double startB = GET_COMPONENT(B, aStartColor) * startA;
-  double endA = GET_COMPONENT(A, aEndColor);
-  double endR = GET_COMPONENT(R, aEndColor) * endA;
-  double endG = GET_COMPONENT(G, aEndColor) * endA;
-  double endB = GET_COMPONENT(B, aEndColor) * endA;
-
-#undef GET_COMPONENT
+  double startA = aStartColor.mA;
+  double startR = aStartColor.mR * startA;
+  double startG = aStartColor.mG * startA;
+  double startB = aStartColor.mB * startA;
+  double endA = aEndColor.mA;
+  double endR = aEndColor.mR * endA;
+  double endG = aEndColor.mG * endA;
+  double endB = aEndColor.mB * endA;
 
   double diffA = startA - endA;
   double diffR = startR - endR;
   double diffG = startG - endG;
   double diffB = startB - endB;
   return sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB);
 }
 
@@ -586,19 +609,18 @@ StyleAnimationValue::ComputeDistance(nsC
     }
     case eUnit_Float: {
       float startFloat = aStartValue.GetFloatValue();
       float endFloat = aEndValue.GetFloatValue();
       aDistance = Abs(double(endFloat) - double(startFloat));
       return true;
     }
     case eUnit_Color: {
-      nscolor startColor = aStartValue.GetCSSValueValue()->GetColorValue();
-      nscolor endColor = aEndValue.GetCSSValueValue()->GetColorValue();
-      aDistance = ComputeColorDistance(startColor, endColor);
+      aDistance = ComputeColorDistance(ExtractColor(aStartValue),
+                                       ExtractColor(aEndValue));
       return true;
     }
     case eUnit_Calc: {
       PixelCalcValue v1 = ExtractCalcValue(aStartValue);
       PixelCalcValue v2 = ExtractCalcValue(aEndValue);
       float difflen = v2.mLength - v1.mLength;
       float diffpct = v2.mPercent - v1.mPercent;
       aDistance = sqrt(difflen * difflen + diffpct * diffpct);
@@ -845,18 +867,17 @@ StyleAnimationValue::ComputeDistance(nsC
                        color2.IsNumericColorUnit()) ||
                       (color1.GetUnit() == color2.GetUnit())) &&
                      inset1 == inset2,
                      "AddWeighted should have failed");
         }
 #endif
 
         if (color1.GetUnit() != eCSSUnit_Null) {
-          double colorDistance =
-            StyleAnimationValue::ComputeColorDistance(color1.GetColorValue(),
+          double colorDistance = ComputeColorDistance(color1.GetColorValue(),
                                                       color2.GetColorValue());
           squareDistance += colorDistance * colorDistance;
         }
 
         shadow1 = shadow1->mNext;
         shadow2 = shadow2->mNext;
         MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length");
       }
@@ -1141,129 +1162,69 @@ 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).
-// Red, Green, and Blue are scaled to the [0, 255] range, and Alpha is scaled
-// to the [0, 1] range (though values are allowed to fall outside of these
-// ranges).
-static Tuple<double, double, double, double>
-GetPremultipliedColorComponents(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)
+// Unclamped AddWeightedColors.
+static RGBAColorData
+AddWeightedColors(double aCoeff1, const RGBAColorData& aValue1,
+                  double aCoeff2, const RGBAColorData& aValue2)
 {
-  MOZ_ASSERT(aValue1.IsNumericColorUnit() && aValue2.IsNumericColorUnit(),
-             "The unit should be color");
-  // 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 R1, G1, B1, A1;
-  Tie(R1, G1, B1, A1) = GetPremultipliedColorComponents(aValue1);
-  double R2, G2, B2, A2;
-  Tie(R2, G2, B2, A2) = GetPremultipliedColorComponents(aValue2);
-  double Aresf = (A1 * aCoeff1 + A2 * aCoeff2);
-  if (Aresf <= 0.0) {
-    aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
-    return;
+  float factor1 = aValue1.mA * aCoeff1;
+  float factor2 = aValue2.mA * aCoeff2;
+  float resultA = factor1 + factor2;
+  if (resultA <= 0.0) {
+    return {0, 0, 0, 0};
   }
 
-  if (Aresf > 1.0) {
-    Aresf = 1.0;
-  }
-
-  double factor = 1.0 / Aresf;
-  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;
+  if (resultA > 1.0) {
+    resultA = 1.0;
   }
 
-  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);
+  float resultFactor = 1.0f / resultA;
+  return RGBAColorData(
+    (aValue1.mR * factor1 + aValue2.mR * factor2) * resultFactor,
+    (aValue1.mG * factor1 + aValue2.mG * factor2) * resultFactor,
+    (aValue1.mB * factor1 + aValue2.mB * factor2) * resultFactor,
+    resultA);
 }
 
-// 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)
+// Multiplies |aValue| color by |aDilutionRation|.
+static nscolor
+DiluteColor(const RGBAColorData& aValue, double aDilutionRatio)
 {
-  MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color");
   MOZ_ASSERT(aDilutionRatio >= 0.0 && aDilutionRatio <= 1.0,
              "Dilution ratio should be in [0, 1]");
-
-  // Premultiplication
-  double R, G, B, A;
-  Tie(R, G, B, A) = GetPremultipliedColorComponents(aValue);
-  double Aresf = A * aDilutionRatio;
-  if (Aresf <= 0.0) {
-    aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
-    return;
-  }
-
-  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)));
+  float resultA = aValue.mA * aDilutionRatio;
+  return resultA <= 0.0 ? NS_RGBA(0, 0, 0, 0)
+                        : aValue.WithAlpha(resultA).ToColor();
+}
+
+// Clamped AddWeightedColors.
+static nscolor
+AddWeightedColorsAndClamp(double aCoeff1, const RGBAColorData& aValue1,
+                          double aCoeff2, const RGBAColorData& aValue2)
+{
+  // 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().
+  return aCoeff2 == 0.0
+    ? DiluteColor(aValue1, aCoeff1)
+    : AddWeightedColors(aCoeff1, aValue1, aCoeff2, aValue2).ToColor();
 }
 
 void
 AppendToCSSValueList(UniquePtr<nsCSSValueList>& aHead,
                      UniquePtr<nsCSSValueList>&& aValueToAppend,
                      nsCSSValueList** aTail)
 {
   MOZ_ASSERT(!aHead == !*aTail,
@@ -1293,37 +1254,40 @@ AddWeightedShadowItems(double aCoeff1, c
 
   for (size_t i = 0; i < 4; ++i) {
     AddCSSValuePixel(aCoeff1, array1->Item(i), aCoeff2, array2->Item(i),
                      resultArray->Item(i),
                      // 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& colorValue1 = array1->Item(4);
+  const nsCSSValue& colorValue2 = array2->Item(4);
   const nsCSSValue& inset1 = array1->Item(5);
   const nsCSSValue& inset2 = array2->Item(5);
-  if ((color1.GetUnit() != color2.GetUnit() &&
-       (!color1.IsNumericColorUnit() || !color2.IsNumericColorUnit())) ||
+  if ((colorValue1.GetUnit() != colorValue2.GetUnit() &&
+       (!colorValue1.IsNumericColorUnit() ||
+        !colorValue2.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 nullptr;
   }
 
-  if (color1.GetUnit() != eCSSUnit_Null) {
-    if (aCoeff2 == 0.0 && aCoeff1 != 1.0) {
-      DiluteColor(color1, aCoeff1, resultArray->Item(4));
+  if (colorValue1.GetUnit() != eCSSUnit_Null) {
+    RGBAColorData color1 = ExtractColor(colorValue1);
+    RGBAColorData color2 = ExtractColor(colorValue2);
+    if (aColorAdditionType == ColorAdditionType::Clamped) {
+      resultArray->Item(4).SetColorValue(
+        AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2));
     } else {
-      AddWeightedColors(aCoeff1, color1, aCoeff2, color2,
-                        aColorAdditionType,
-                        resultArray->Item(4));
+      resultArray->Item(4).SetRGBAColorValue(
+        AddWeightedColors(aCoeff1, color1, aCoeff2, color2));
     }
   }
 
   MOZ_ASSERT(inset1 == inset2, "should match");
   resultArray->Item(5) = inset1;
 
   auto resultItem = MakeUnique<nsCSSValueList>();
   resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
@@ -2546,33 +2510,21 @@ StyleAnimationValue::AddWeighted(nsCSSPr
     }
     case eUnit_Float: {
       aResultValue.SetFloatValue(RestrictValue(aProperty,
         aCoeff1 * aValue1.GetFloatValue() +
         aCoeff2 * aValue2.GetFloatValue()));
       return true;
     }
     case eUnit_Color: {
-      const nsCSSValue* value1 = aValue1.GetCSSValueValue();
-      const nsCSSValue* value2 = aValue2.GetCSSValueValue();
-      MOZ_ASSERT(value1 && value2, "Both of CSS value should be valid");
+      RGBAColorData color1 = ExtractColor(aValue1);
+      RGBAColorData color2 = ExtractColor(aValue2);
       auto resultColor = MakeUnique<nsCSSValue>();
-
-      // 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,
-                          ColorAdditionType::Clamped,
-                          *resultColor);
-      }
+      resultColor->SetColorValue(
+        AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2));
       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;
       double pct = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent;
@@ -2920,24 +2872,21 @@ StyleAnimationValue::Accumulate(nsCSSPro
                               ColorAdditionType::Unclamped);
       if (!result) {
         return false;
       }
       aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow);
       return true;
     }
     case eUnit_Color: {
+      RGBAColorData color1 = ExtractColor(aDest);
+      RGBAColorData color2 = ExtractColor(aValueToAccumulate);
       auto resultColor = MakeUnique<nsCSSValue>();
-      AddWeightedColors(1.0,
-                        *aDest.GetCSSValueValue(),
-                        aCount,
-                        *aValueToAccumulate.GetCSSValueValue(),
-                        ColorAdditionType::Unclamped,
-                        *resultColor);
-
+      resultColor->SetRGBAColorValue(
+        AddWeightedColors(1.0, color1, aCount, color2));
       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;
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -63,17 +63,18 @@ public:
    * Calculates a measure of 'distance' between two colors.
    *
    * @param aStartColor The start of the interval for which the distance
    *                    should be calculated.
    * @param aEndColor   The end of the interval for which the distance
    *                    should be calculated.
    * @return the result of the calculation.
    */
-  static double ComputeColorDistance(nscolor aStartColor, nscolor aEndColor);
+  static double ComputeColorDistance(const css::RGBAColorData& aStartColor,
+                                     const css::RGBAColorData& aEndColor);
 
   /**
    * Calculates a measure of 'distance' between two values.
    *
    * This measure of Distance is guaranteed to be proportional to
    * portions passed to Interpolate, Add, or AddWeighted.  However, for
    * some types of StyleAnimationValue it may not produce sensible results
    * for paced animation.