Bug 1772555 - Use style interpolation code for gradients. r=nical

This ensures they're clamped on Animated -> sRGB conversion, and in the
future we'll have to implement different color spaces so we'll need to
use it anyways.

Differential Revision: https://phabricator.services.mozilla.com/D149792
This commit is contained in:
Emilio Cobos Álvarez 2022-06-22 17:00:56 +00:00
Родитель cc8f5927f1
Коммит 827fe3a33e
10 изменённых файлов: 115 добавлений и 73 удалений

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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<ColorStop>& 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<ColorStop>& 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<ColorStop>& 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<ColorStop>& 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<ColorStop>& 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<ColorStop>& 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<float>(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<ColorStop>& 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<ColorStop>& 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<ColorStop>& aStops) {
namespace mozilla {
template <typename T>
static sRGBColor GetSpecifiedColor(
static StyleAnimatedRGBA GetSpecifiedColor(
const StyleGenericGradientItem<StyleColor, T>& 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<double> 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(

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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).

Просмотреть файл

@ -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<RawServoAnimationValue> {
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<ComputedLinearStop>,
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,
)
}

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<link rel=help href="https://buzil.la/1772555">
<style>
* {
background: center / cover border-box, currentcolor right 55882ch center / cover content-box radial-gradient(58ch 94% ellipse at left 28Q top 34%, hsl(60 71% 7% / 0.3204252445367216) -1610731402em, -31pt, hsla(2.9234077762890767rad, 58%, 56%, 0%) 26%) local repeat-x;
}
</style>