зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
cc8f5927f1
Коммит
827fe3a33e
|
@ -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>
|
Загрузка…
Ссылка в новой задаче