diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h index 04affbef832c..6ff4341e514a 100644 --- a/gfx/2d/Types.h +++ b/gfx/2d/Types.h @@ -766,25 +766,6 @@ struct sRGBColor { return a > 0.f ? sRGBColor(r / a, g / a, b / a, a) : *this; } - // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done in - // unpremultiplied space, which is what SVG gradients and cairo gradients - // expect. - constexpr static sRGBColor InterpolatePremultiplied(const sRGBColor& aC1, - const sRGBColor& aC2, - float aFrac) { - double other = 1 - aFrac; - return sRGBColor( - aC2.r * aFrac + aC1.r * other, aC2.g * aFrac + aC1.g * other, - aC2.b * aFrac + aC1.b * other, aC2.a * aFrac + aC1.a * other); - } - - constexpr static sRGBColor Interpolate(const sRGBColor& aC1, - const sRGBColor& aC2, float aFrac) { - return InterpolatePremultiplied(aC1.Premultiplied(), aC2.Premultiplied(), - aFrac) - .Unpremultiplied(); - } - // The "Unusual" prefix is to avoid unintentionally using this function when // ToABGR(), which is much more common, is needed. uint32_t UnusualToARGB() const { diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index d403389de214..8d00ab7f9d64 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -1702,5 +1702,21 @@ DeviceColor ToDeviceColor(const StyleRGBA& aColor) { return ToDeviceColor(aColor.ToColor()); } +sRGBColor ToSRGBColor(const StyleAnimatedRGBA& aColor) { + const auto ToComponent = [](float aF) -> float { + float component = std::min(std::max(0.0f, aF), 1.0f); + if (MOZ_UNLIKELY(!std::isfinite(component))) { + return 0.0f; + } + return component; + }; + return {ToComponent(aColor.red), ToComponent(aColor.green), + ToComponent(aColor.blue), ToComponent(aColor.alpha)}; +} + +DeviceColor ToDeviceColor(const StyleAnimatedRGBA& aColor) { + return ToDeviceColor(ToSRGBColor(aColor)); +} + } // namespace gfx } // namespace mozilla diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 8df39bbd2cd5..6d7f5e2400b6 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -374,6 +374,7 @@ class gfxUtils { namespace mozilla { struct StyleRGBA; +struct StyleAnimatedRGBA; namespace gfx { @@ -384,9 +385,12 @@ namespace gfx { * color is returned unchanged (other than a type change to Moz2D Color, if * applicable). */ -DeviceColor ToDeviceColor(const sRGBColor& aColor); -DeviceColor ToDeviceColor(const StyleRGBA& aColor); -DeviceColor ToDeviceColor(nscolor aColor); +DeviceColor ToDeviceColor(const sRGBColor&); +DeviceColor ToDeviceColor(const StyleRGBA&); +DeviceColor ToDeviceColor(const StyleAnimatedRGBA&); +DeviceColor ToDeviceColor(nscolor); + +sRGBColor ToSRGBColor(const StyleAnimatedRGBA&); /** * Performs a checked multiply of the given width, height, and bytes-per-pixel diff --git a/layout/painting/nsCSSRenderingGradients.cpp b/layout/painting/nsCSSRenderingGradients.cpp index 8bcdb33bb2fa..89555984c3e1 100644 --- a/layout/painting/nsCSSRenderingGradients.cpp +++ b/layout/painting/nsCSSRenderingGradients.cpp @@ -246,6 +246,19 @@ static float Interpolate(float aF1, float aF2, float aFrac) { return aF1 + aFrac * (aF2 - aF1); } +static StyleAnimatedRGBA Interpolate(const StyleAnimatedRGBA& aLeft, + const StyleAnimatedRGBA& aRight, + float aFrac) { + // NOTE: This has to match the interpolation method that WebRender uses which + // right now is sRGB. In the future we should implement interpolation in more + // gradient color-spaces. + static constexpr auto kMethod = StyleColorInterpolationMethod{ + StyleColorSpace::Srgb, + StyleHueInterpolationMethod::Shorter, + }; + return Servo_InterpolateColor(&kMethod, &aLeft, &aRight, aFrac); +} + static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim) { NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension"); @@ -284,7 +297,7 @@ static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect, const nsTArray& aStops, const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd, - sRGBColor* aOutEdgeColor) { + StyleAnimatedRGBA* aOutEdgeColor) { gfxFloat topLeft = LinearGradientStopPositionForPoint( aGradientStart, aGradientEnd, aPatternMatrix.TransformPoint(aRect.TopLeft())); @@ -322,8 +335,8 @@ static void ResolveMidpoints(nsTArray& stops) { continue; } - sRGBColor color1 = stops[x - 1].mColor; - sRGBColor color2 = stops[x + 1].mColor; + const auto& color1 = stops[x - 1].mColor; + const auto& color2 = stops[x + 1].mColor; float offset1 = stops[x - 1].mPosition; float offset2 = stops[x + 1].mPosition; float offset = stops[x].mPosition; @@ -376,16 +389,18 @@ static void ResolveMidpoints(nsTArray& stops) { // points were chosen since it is the minimum number of stops that always // give the smoothest appearace regardless of midpoint position and // difference in luminance of the end points. - float relativeOffset = + const float relativeOffset = (newStop.mPosition - offset1) / (offset2 - offset1); - float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint)); + const float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint)); - gfx::Float red = color1.r + multiplier * (color2.r - color1.r); - gfx::Float green = color1.g + multiplier * (color2.g - color1.g); - gfx::Float blue = color1.b + multiplier * (color2.b - color1.b); - gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a); + const float red = color1.red + multiplier * (color2.red - color1.red); + const float green = + color1.green + multiplier * (color2.green - color1.green); + const float blue = color1.blue + multiplier * (color2.blue - color1.blue); + const float alpha = + color1.alpha + multiplier * (color2.alpha - color1.alpha); - newStop.mColor = sRGBColor(red, green, blue, alpha); + newStop.mColor = {red, green, blue, alpha}; } stops.ReplaceElementsAt(x, 1, newStops, 9); @@ -393,9 +408,10 @@ static void ResolveMidpoints(nsTArray& stops) { } } -static sRGBColor TransparentColor(sRGBColor aColor) { - aColor.a = 0; - return aColor; +static StyleAnimatedRGBA TransparentColor(const StyleAnimatedRGBA& aColor) { + auto color = aColor; + color.alpha = 0.0f; + return color; } // Adjusts and adds color stops in such a way that drawing the gradient with @@ -410,21 +426,21 @@ static void ResolvePremultipliedAlpha(nsTArray& aStops) { // if the left and right stop have the same alpha value, we don't need // to do anything. Hardstops should be instant, and also should never // require dealing with interpolation. - if (leftStop.mColor.a == rightStop.mColor.a || + if (leftStop.mColor.alpha == rightStop.mColor.alpha || leftStop.mPosition == rightStop.mPosition) { continue; } // Is the stop on the left 100% transparent? If so, have it adopt the color // of the right stop - if (leftStop.mColor.a == 0) { + if (leftStop.mColor.alpha == 0) { aStops[x - 1].mColor = TransparentColor(rightStop.mColor); continue; } // Is the stop on the right completely transparent? // If so, duplicate it and assign it the color on the left. - if (rightStop.mColor.a == 0) { + if (rightStop.mColor.alpha == 0) { ColorStop newStop = rightStop; newStop.mColor = TransparentColor(leftStop.mColor); aStops.InsertElementAt(x, newStop); @@ -434,20 +450,16 @@ static void ResolvePremultipliedAlpha(nsTArray& aStops) { // Now handle cases where one or both of the stops are partially // transparent. - if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) { - sRGBColor premulLeftColor = leftStop.mColor.Premultiplied(); - sRGBColor premulRightColor = rightStop.mColor.Premultiplied(); + if (leftStop.mColor.alpha != 1.0f || rightStop.mColor.alpha != 1.0f) { // Calculate how many extra steps. We do a step per 10% transparency. size_t stepCount = - NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / + NSToIntFloor(fabsf(leftStop.mColor.alpha - rightStop.mColor.alpha) / kAlphaIncrementPerGradientStep); for (size_t y = 1; y < stepCount; y++) { float frac = static_cast(y) / stepCount; ColorStop newStop( Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false, - sRGBColor::InterpolatePremultiplied(premulLeftColor, - premulRightColor, frac) - .Unpremultiplied()); + Interpolate(leftStop.mColor, rightStop.mColor, frac)); aStops.InsertElementAt(x, newStop); x++; } @@ -458,7 +470,7 @@ static void ResolvePremultipliedAlpha(nsTArray& aStops) { static ColorStop InterpolateColorStop(const ColorStop& aFirst, const ColorStop& aSecond, double aPosition, - const sRGBColor& aDefault) { + const StyleAnimatedRGBA& aDefault) { MOZ_ASSERT(aFirst.mPosition <= aPosition); MOZ_ASSERT(aPosition <= aSecond.mPosition); @@ -467,10 +479,9 @@ static ColorStop InterpolateColorStop(const ColorStop& aFirst, return ColorStop(aPosition, false, aDefault); } - return ColorStop( - aPosition, false, - sRGBColor::Interpolate(aFirst.mColor, aSecond.mColor, - (aPosition - aFirst.mPosition) / delta)); + return ColorStop(aPosition, false, + Interpolate(aFirst.mColor, aSecond.mColor, + (aPosition - aFirst.mPosition) / delta)); } // Clamp and extend the given ColorStop array in-place to fit exactly into the @@ -482,8 +493,8 @@ static void ClampColorStops(nsTArray& aStops) { // with a single colour. if (aStops.Length() < 2 || aStops[0].mPosition > 1 || aStops.LastElement().mPosition < 0) { - sRGBColor c = aStops[0].mPosition > 1 ? aStops[0].mColor - : aStops.LastElement().mColor; + const auto& c = aStops[0].mPosition > 1 ? aStops[0].mColor + : aStops.LastElement().mColor; aStops.Clear(); aStops.AppendElement(ColorStop(0, false, c)); return; @@ -529,16 +540,18 @@ static void ClampColorStops(nsTArray& aStops) { namespace mozilla { template -static sRGBColor GetSpecifiedColor( +static StyleAnimatedRGBA GetSpecifiedColor( const StyleGenericGradientItem& aItem, const ComputedStyle& aStyle) { if (aItem.IsInterpolationHint()) { - return sRGBColor(); + return {0.0f, 0.0f, 0.0f, 0.0f}; } - const StyleColor& color = aItem.IsSimpleColorStop() - ? aItem.AsSimpleColorStop() - : aItem.AsComplexColorStop().color; - return sRGBColor::FromABGR(color.CalcColor(aStyle)); + const StyleColor& c = aItem.IsSimpleColorStop() + ? aItem.AsSimpleColorStop() + : aItem.AsComplexColorStop().color; + nscolor color = c.CalcColor(aStyle); + return {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f, + NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f}; } static Maybe GetSpecifiedGradientPosition( @@ -841,8 +854,8 @@ void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, // XXX Color interpolation (in cairo, too) should use the // CSS 'color-interpolation' property! float frac = float((0.0 - pos) / (nextPos - pos)); - mStops[i].mColor = sRGBColor::InterpolatePremultiplied( - mStops[i].mColor, mStops[i + 1].mColor, frac); + mStops[i].mColor = + Interpolate(mStops[i].mColor, mStops[i + 1].mColor, frac); } } } @@ -959,8 +972,8 @@ void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, // gradient with radius of 0 -> just paint the last stop color. // We use firstStop offset to keep |stops| with same units (will later // normalize to 0). - sRGBColor firstColor(mStops[0].mColor); - sRGBColor lastColor(mStops.LastElement().mColor); + auto firstColor(mStops[0].mColor); + auto lastColor(mStops.LastElement().mColor); mStops.Clear(); if (!mGradient->Repeating() && !zeroRadius) { @@ -1064,13 +1077,13 @@ void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill); gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft(); - sRGBColor edgeColor; + StyleAnimatedRGBA edgeColor{0.0f}; if (mGradient->IsLinear() && !isRepeat && RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops, gradientStart, gradientEnd, &edgeColor)) { - edgeColor.a *= aOpacity; - aContext.SetColor(edgeColor); + edgeColor.alpha *= aOpacity; + aContext.SetColor(ToSRGBColor(edgeColor)); } else { aContext.SetMatrixDouble( aContext.CurrentMatrixDouble().Copy().PreTranslate( diff --git a/layout/painting/nsCSSRenderingGradients.h b/layout/painting/nsCSSRenderingGradients.h index b5d59a120ae1..fdc4abf7f6b0 100644 --- a/layout/painting/nsCSSRenderingGradients.h +++ b/layout/painting/nsCSSRenderingGradients.h @@ -30,11 +30,11 @@ class DisplayListBuilder; // a color. struct ColorStop { ColorStop() : mPosition(0), mIsMidpoint(false) {} - ColorStop(double aPosition, bool aIsMidPoint, const gfx::sRGBColor& aColor) + ColorStop(double aPosition, bool aIsMidPoint, const StyleAnimatedRGBA& aColor) : mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {} double mPosition; // along the gradient line; 0=start, 1=end bool mIsMidpoint; - gfx::sRGBColor mColor; + StyleAnimatedRGBA mColor; }; class nsCSSGradientRenderer final { diff --git a/servo/components/style/values/animated/color.rs b/servo/components/style/values/animated/color.rs index cdfa45dc4e7d..e79961661b91 100644 --- a/servo/components/style/values/animated/color.rs +++ b/servo/components/style/values/animated/color.rs @@ -19,7 +19,7 @@ use std::f32::consts::PI; /// range `[0.0, 1.0]`. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero, ToAnimatedValue)] #[repr(C)] -pub struct RGBA { +pub struct AnimatedRGBA { /// The red component. pub red: f32, /// The green component. @@ -30,6 +30,8 @@ pub struct RGBA { pub alpha: f32, } +use self::AnimatedRGBA as RGBA; + const RAD_PER_DEG: f32 = PI / 180.0; const DEG_PER_RAD: f32 = 180.0 / PI; diff --git a/servo/components/style/values/computed/color.rs b/servo/components/style/values/computed/color.rs index 573cb6fe500d..1229793c7143 100644 --- a/servo/components/style/values/computed/color.rs +++ b/servo/components/style/values/computed/color.rs @@ -4,7 +4,7 @@ //! Computed color values. -use crate::values::animated::color::RGBA as AnimatedRGBA; +use crate::values::animated::color::AnimatedRGBA; use crate::values::animated::ToAnimatedValue; use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto}; use crate::values::computed::percentage::Percentage; diff --git a/servo/components/style/values/generics/color.rs b/servo/components/style/values/generics/color.rs index 7ab3880bc1a6..65acfee9061a 100644 --- a/servo/components/style/values/generics/color.rs +++ b/servo/components/style/values/generics/color.rs @@ -9,7 +9,7 @@ use style_traits::{CssWriter, ParseError, ToCss}; use crate::values::{Parse, ParserContext, Parser}; use crate::values::specified::percentage::ToPercentage; use crate::values::animated::ToAnimatedValue; -use crate::values::animated::color::RGBA as AnimatedRGBA; +use crate::values::animated::color::AnimatedRGBA; /// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index c797519b66e2..eca29a62f475 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -136,6 +136,8 @@ use style::traversal::DomTraversal; use style::traversal_flags::{self, TraversalFlags}; use style::use_counters::UseCounters; use style::values::animated::{Animate, Procedure, ToAnimatedZero}; +use style::values::animated::color::AnimatedRGBA; +use style::values::generics::color::ColorInterpolationMethod; use style::values::computed::easing::ComputedLinearStop; use style::values::computed::font::{FontFamily, FontFamilyList, GenericFontFamily, FontWeight, FontStyle, FontStretch}; use style::values::computed::{self, Context, ToComputedValue}; @@ -799,7 +801,7 @@ pub extern "C" fn Servo_AnimationValue_Color( color: structs::nscolor, ) -> Strong { use style::gecko::values::convert_nscolor_to_rgba; - use style::values::animated::color::{Color, RGBA as AnimatedRGBA}; + use style::values::animated::color::Color; let property = LonghandId::from_nscsspropertyid(color_property) .expect("We don't have shorthand property animation value"); @@ -7497,7 +7499,7 @@ pub unsafe extern "C" fn Servo_InvalidateForViewportUnits( } #[no_mangle] -pub unsafe extern "C" fn Servo_CreatePiecewiseLinearFunction( +pub extern "C" fn Servo_CreatePiecewiseLinearFunction( entries: &style::OwnedSlice, result: &mut PiecewiseLinearFunction, ) { @@ -7509,9 +7511,26 @@ pub unsafe extern "C" fn Servo_CreatePiecewiseLinearFunction( } #[no_mangle] -pub unsafe extern "C" fn Servo_PiecewiseLinearFunctionAt( +pub extern "C" fn Servo_PiecewiseLinearFunctionAt( function: &PiecewiseLinearFunction, progress: f32, ) -> f32 { function.at(progress) } + +#[no_mangle] +pub extern "C" fn Servo_InterpolateColor( + interpolation: &ColorInterpolationMethod, + left: &AnimatedRGBA, + right: &AnimatedRGBA, + progress: f32, +) -> AnimatedRGBA { + style::values::animated::color::Color::mix( + interpolation, + left, + progress, + right, + 1.0 - progress, + /* normalize_weights = */ false, + ) +} diff --git a/testing/web-platform/tests/css/css-backgrounds/gradient-wrong-interpolation-crash.html b/testing/web-platform/tests/css/css-backgrounds/gradient-wrong-interpolation-crash.html new file mode 100644 index 000000000000..5aefb6429711 --- /dev/null +++ b/testing/web-platform/tests/css/css-backgrounds/gradient-wrong-interpolation-crash.html @@ -0,0 +1,7 @@ + + +