1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00

Issue #2982 - Follow-up: allow color-mix to work with oklab and oklch

Tag #2489 and #3003
This commit is contained in:
Basilisk-Dev
2026-05-08 14:29:43 -04:00
committed by roytam1
parent e85f778708
commit 20b2b3b9f5
7 changed files with 451 additions and 56 deletions
+181 -25
View File
@@ -6,6 +6,7 @@
#ifndef nsCSSNonSRGBColorSpace_h___
#define nsCSSNonSRGBColorSpace_h___
#include <algorithm>
#include <cmath>
#include "mozilla/MathAlgorithms.h"
@@ -18,7 +19,46 @@ namespace css {
static constexpr float kLabLightnessMax = 100.0f;
static constexpr float kLchPercentScaleC = 150.0f;
static constexpr float kOklabPercentScaleAB = 0.4f;
static constexpr float kOklchPowerlessChromaEpsilon = 0.000004f;
static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489;
static constexpr double kDegreesPerRadian = 57.295779513082320876798154814105;
struct OklabColor {
float mL;
float mA;
float mB;
};
struct OklchColor {
float mL;
float mChroma;
float mHue;
};
struct LinearSRGBColor {
float mR;
float mG;
float mB;
};
inline float
NormalizeHue(float aHue)
{
float hue = std::fmod(aHue, 360.0f);
if (hue < 0.0f) {
hue += 360.0f;
}
return hue;
}
inline float
EncodedSRGBToLinear(float aValue)
{
if (aValue <= 0.04045f) {
return aValue / 12.92f;
}
return std::pow((aValue + 0.055f) / 1.055f, 2.4f);
}
inline float
LinearSRGBToEncoded(float aValue)
@@ -44,43 +84,159 @@ LinearSRGBToColor(float aLinearR, float aLinearG, float aLinearB,
aAlpha);
}
inline nscolor
OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
inline LinearSRGBColor
OklabToLinearSRGB(float aL, float aA, float aB)
{
// Per CSS Color, the lightness component for Oklab/Oklch is clamped.
float lightness = mozilla::clamped(aL, 0.0f, 1.0f);
uint8_t alpha =
nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f));
// Treat values extremely close to zero as zero to avoid tiny floating-point
// representation differences for percentage inputs.
static constexpr float kLightnessEndpointEpsilon = 0.000002f;
if (lightness <= kLightnessEndpointEpsilon) {
return NS_RGBA(0, 0, 0, alpha);
}
float lRoot = lightness + 0.3963377774f * aA + 0.2158037573f * aB;
float mRoot = lightness - 0.1055613458f * aA - 0.0638541728f * aB;
float sRoot = lightness - 0.0894841775f * aA - 1.2914855480f * aB;
float lRoot = aL + 0.3963377774f * aA + 0.2158037573f * aB;
float mRoot = aL - 0.1055613458f * aA - 0.0638541728f * aB;
float sRoot = aL - 0.0894841775f * aA - 1.2914855480f * aB;
float l = lRoot * lRoot * lRoot;
float m = mRoot * mRoot * mRoot;
float s = sRoot * sRoot * sRoot;
float linearR = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
return {
4.0767416361f * l - 3.3077115393f * m + 0.2309699032f * s,
-1.2684379733f * l + 2.6097573493f * m - 0.3413193760f * s,
-0.0041960761f * l - 0.7034186179f * m + 1.7076146941f * s
};
}
return LinearSRGBToColor(linearR, linearG, linearB, alpha);
inline OklabColor
LinearSRGBToOklabColor(float aLinearR, float aLinearG, float aLinearB)
{
float l = std::cbrt(0.4122214695f * aLinearR +
0.5363325373f * aLinearG +
0.0514459933f * aLinearB);
float m = std::cbrt(0.2119034958f * aLinearR +
0.6806995506f * aLinearG +
0.1073969535f * aLinearB);
float s = std::cbrt(0.0883024592f * aLinearR +
0.2817188391f * aLinearG +
0.6299787017f * aLinearB);
return {
0.2104542683f * l + 0.7936177747f * m - 0.0040720430f * s,
1.9779985324f * l - 2.4285922420f * m + 0.4505937096f * s,
0.0259040425f * l + 0.7827717125f * m - 0.8086757549f * s
};
}
inline OklabColor
SRGBToOklabColor(nscolor aColor)
{
float linearR = EncodedSRGBToLinear(NS_GET_R(aColor) / 255.0f);
float linearG = EncodedSRGBToLinear(NS_GET_G(aColor) / 255.0f);
float linearB = EncodedSRGBToLinear(NS_GET_B(aColor) / 255.0f);
return LinearSRGBToOklabColor(linearR, linearG, linearB);
}
inline OklchColor
OklabToOklchColor(const OklabColor& aColor)
{
float chroma = std::sqrt(aColor.mA * aColor.mA + aColor.mB * aColor.mB);
float hue = std::atan2(aColor.mB, aColor.mA) * kDegreesPerRadian;
if (hue < 0.0f) {
hue += 360.0f;
}
return { aColor.mL, chroma, hue };
}
inline nscolor
OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
{
// Per CSS Color, the lightness component for Oklab/Oklch is clamped at
// parsed-value time.
float lightness = mozilla::clamped(aL, 0.0f, 1.0f);
uint8_t alpha =
nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f));
OklabColor mapped = { lightness, aA, aB };
auto isInSRGBGamut = [](const OklabColor& aColor) {
LinearSRGBColor linear =
OklabToLinearSRGB(aColor.mL, aColor.mA, aColor.mB);
return linear.mR >= 0.0f && linear.mR <= 1.0f &&
linear.mG >= 0.0f && linear.mG <= 1.0f &&
linear.mB >= 0.0f && linear.mB <= 1.0f;
};
auto clippedOklab = [](const OklabColor& aColor) {
LinearSRGBColor linear =
OklabToLinearSRGB(aColor.mL, aColor.mA, aColor.mB);
return LinearSRGBToOklabColor(
mozilla::clamped(linear.mR, 0.0f, 1.0f),
mozilla::clamped(linear.mG, 0.0f, 1.0f),
mozilla::clamped(linear.mB, 0.0f, 1.0f));
};
auto deltaEOK = [](const OklabColor& aColor1, const OklabColor& aColor2) {
float deltaL = aColor1.mL - aColor2.mL;
float deltaA = aColor1.mA - aColor2.mA;
float deltaB = aColor1.mB - aColor2.mB;
return std::sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
};
if (lightness <= 0.0f) {
mapped = { 0.0f, 0.0f, 0.0f };
} else if (lightness >= 1.0f) {
mapped = { 1.0f, 0.0f, 0.0f };
} else if (!isInSRGBGamut(mapped)) {
// CSS Color 4 binary search gamut mapping with local MINDE, targeting sRGB.
static constexpr float kJND = 0.02f;
static constexpr float kGamutMapEpsilon = 0.0001f;
OklchColor origin = OklabToOklchColor(mapped);
OklabColor clipped = clippedOklab(mapped);
float delta = deltaEOK(clipped, mapped);
if (delta < kJND) {
mapped = clipped;
} else {
float min = 0.0f;
float max = origin.mChroma;
bool minInGamut = true;
OklabColor current = mapped;
while (max - min > kGamutMapEpsilon) {
float chroma = (min + max) / 2.0f;
current.mL = origin.mL;
current.mA = chroma * std::cos(origin.mHue * kRadiansPerDegree);
current.mB = chroma * std::sin(origin.mHue * kRadiansPerDegree);
if (minInGamut && isInSRGBGamut(current)) {
min = chroma;
continue;
}
clipped = clippedOklab(current);
delta = deltaEOK(clipped, current);
if (delta < kJND) {
if (kJND - delta < kGamutMapEpsilon) {
mapped = clipped;
break;
}
minInGamut = false;
min = chroma;
} else {
max = chroma;
}
mapped = clipped;
}
}
}
LinearSRGBColor linear =
OklabToLinearSRGB(mapped.mL, mapped.mA, mapped.mB);
return LinearSRGBToColor(linear.mR, linear.mG, linear.mB, alpha);
}
inline nscolor
OklchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha)
{
double hueRadians = aHue * kRadiansPerDegree;
float a = aChroma * std::cos(hueRadians);
float b = aChroma * std::sin(hueRadians);
float chroma = std::max(aChroma, 0.0f);
double hueRadians = NormalizeHue(aHue) * kRadiansPerDegree;
float a = chroma * std::cos(hueRadians);
float b = chroma * std::sin(hueRadians);
return OklabToSRGBColor(aL, a, b, aAlpha);
}
+27 -11
View File
@@ -1504,8 +1504,9 @@ protected:
bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
float& aOpacity);
bool ParseLCHColor(nscolor& aColor);
bool ParseOKLabColor(nscolor& aColor);
bool ParseOKLCHColor(nscolor& aColor);
bool ParseOKLabColor(float& aL, float& aA, float& aB, float& aAlpha);
bool ParseOKLCHColor(float& aL, float& aChroma, float& aHue,
float& aAlpha);
bool ParseOKLabComponent(float& aComponent, float aPercentScale,
Maybe<char> aSeparator);
@@ -7748,7 +7749,7 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue)
return CSSParseResult::Error;
}
// Check for supported color spaces: srgb or hsl
// Check for supported color spaces.
if (!GetToken(true) || mToken.mType != eCSSToken_Ident) {
SkipUntil(')');
return CSSParseResult::Error;
@@ -7759,6 +7760,10 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue)
colorSpace = mozilla::css::ColorMixColorSpace::sRGB;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl")) {
colorSpace = mozilla::css::ColorMixColorSpace::HSL;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("oklab")) {
colorSpace = mozilla::css::ColorMixColorSpace::Oklab;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("oklch")) {
colorSpace = mozilla::css::ColorMixColorSpace::Oklch;
} else {
SkipUntil(')');
return CSSParseResult::Error;
@@ -7907,16 +7912,19 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue)
return CSSParseResult::Error;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("oklab")) {
if (ParseOKLabColor(rgba)) {
aValue.SetColorValue(rgba);
float l, a, b, alpha;
if (ParseOKLabColor(l, a, b, alpha)) {
aValue.SetFloatColorValue(l, a, b, alpha, eCSSUnit_OklabColor);
return CSSParseResult::Ok;
}
SkipUntil(')');
return CSSParseResult::Error;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("oklch")) {
if (ParseOKLCHColor(rgba)) {
aValue.SetColorValue(rgba);
float l, chroma, hue, alpha;
if (ParseOKLCHColor(l, chroma, hue, alpha)) {
aValue.SetFloatColorValue(l, chroma, hue, alpha,
eCSSUnit_OklchColor);
return CSSParseResult::Ok;
}
SkipUntil(')');
@@ -8168,7 +8176,8 @@ CSSParserImpl::ParseLCHColor(nscolor& aColor)
}
bool
CSSParserImpl::ParseOKLabColor(nscolor& aColor)
CSSParserImpl::ParseOKLabColor(float& aL, float& aA, float& aB,
float& aAlpha)
{
const char commaSeparator = ',';
float l, a, b, alpha;
@@ -8186,12 +8195,16 @@ CSSParserImpl::ParseOKLabColor(nscolor& aColor)
return false;
}
aColor = OklabToSRGBColor(l, a, b, alpha);
aL = mozilla::clamped(l, 0.0f, 1.0f);
aA = a;
aB = b;
aAlpha = alpha;
return true;
}
bool
CSSParserImpl::ParseOKLCHColor(nscolor& aColor)
CSSParserImpl::ParseOKLCHColor(float& aL, float& aChroma, float& aHue,
float& aAlpha)
{
const char commaSeparator = ',';
float l, chroma, hue, alpha;
@@ -8209,7 +8222,10 @@ CSSParserImpl::ParseOKLCHColor(nscolor& aColor)
return false;
}
aColor = OklchToSRGBColor(l, chroma, hue, alpha);
aL = mozilla::clamped(l, 0.0f, 1.0f);
aChroma = std::max(chroma, 0.0f);
aHue = NormalizeHue(hue);
aAlpha = alpha;
return true;
}
+50 -7
View File
@@ -18,6 +18,7 @@
#include "gfxFontConstants.h"
#include "imgIRequest.h"
#include "imgRequestProxy.h"
#include "nsCSSNonSRGBColorSpace.h"
#include "nsIDocument.h"
#include "nsNetUtil.h"
#include "nsPresContext.h"
@@ -1965,6 +1966,12 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
case mozilla::css::ColorMixColorSpace::HSL:
aResult.AppendLiteral("hsl");
break;
case mozilla::css::ColorMixColorSpace::Oklab:
aResult.AppendLiteral("oklab");
break;
case mozilla::css::ColorMixColorSpace::Oklch:
aResult.AppendLiteral("oklch");
break;
}
// append color1 and color2
@@ -2272,6 +2279,8 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
case eCSSUnit_PercentageRGBAColor: break;
case eCSSUnit_HSLColor: break;
case eCSSUnit_HSLAColor: break;
case eCSSUnit_OklabColor: break;
case eCSSUnit_OklchColor: break;
case eCSSUnit_ComplexColor: break;
case eCSSUnit_ColorMix: break;
case eCSSUnit_Percent: aResult.Append(char16_t('%')); break;
@@ -2480,6 +2489,8 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
case eCSSUnit_PercentageRGBAColor:
case eCSSUnit_HSLColor:
case eCSSUnit_HSLAColor:
case eCSSUnit_OklabColor:
case eCSSUnit_OklchColor:
n += mValue.mFloatColor->SizeOfIncludingThis(aMallocSizeOf);
break;
@@ -3476,6 +3487,16 @@ nsCSSValueFloatColor::GetColorValue(nsCSSUnit aUnit) const
NSToIntRound(mozilla::clamped(mAlpha, 0.0f, 1.0f) * 255.0f));
}
if (aUnit == eCSSUnit_OklabColor) {
return css::OklabToSRGBColor(mComponent1, mComponent2, mComponent3,
mAlpha);
}
if (aUnit == eCSSUnit_OklchColor) {
return css::OklchToSRGBColor(mComponent1, mComponent2, mComponent3,
mAlpha);
}
// HSL color
MOZ_ASSERT(aUnit == eCSSUnit_HSLColor ||
aUnit == eCSSUnit_HSLAColor);
@@ -3504,8 +3525,14 @@ nsCSSValueFloatColor::AppendToString(nsCSSUnit aUnit, nsAString& aResult) const
bool showAlpha = (mAlpha != 1.0f);
bool isHSL = (aUnit == eCSSUnit_HSLColor ||
aUnit == eCSSUnit_HSLAColor);
bool isOklab = aUnit == eCSSUnit_OklabColor;
bool isOklch = aUnit == eCSSUnit_OklchColor;
if (isHSL) {
if (isOklab) {
aResult.AppendLiteral("oklab");
} else if (isOklch) {
aResult.AppendLiteral("oklch");
} else if (isHSL) {
aResult.AppendLiteral("hsl");
} else {
aResult.AppendLiteral("rgb");
@@ -3515,22 +3542,38 @@ nsCSSValueFloatColor::AppendToString(nsCSSUnit aUnit, nsAString& aResult) const
} else {
aResult.Append('(');
}
if (isHSL) {
if (isOklab || isOklch) {
aResult.AppendFloat(mComponent1);
aResult.Append(' ');
aResult.AppendFloat(mComponent2);
aResult.Append(' ');
aResult.AppendFloat(mComponent3);
} else if (isHSL) {
aResult.AppendFloat(mComponent1 * 360.0f);
aResult.AppendLiteral(", ");
} else {
aResult.AppendFloat(mComponent1 * 100.0f);
aResult.AppendLiteral("%, ");
}
aResult.AppendFloat(mComponent2 * 100.0f);
aResult.AppendLiteral("%, ");
aResult.AppendFloat(mComponent3 * 100.0f);
if (showAlpha) {
if (!isOklab && !isOklch) {
aResult.AppendFloat(mComponent2 * 100.0f);
aResult.AppendLiteral("%, ");
aResult.AppendFloat(mComponent3 * 100.0f);
}
if (showAlpha) {
if (isOklab || isOklch) {
aResult.AppendLiteral(" / ");
} else {
aResult.AppendLiteral("%, ");
}
aResult.AppendFloat(mAlpha);
aResult.Append(')');
} else {
aResult.AppendLiteral("%)");
if (isOklab || isOklch) {
aResult.Append(')');
} else {
aResult.AppendLiteral("%)");
}
}
}
+13 -4
View File
@@ -520,8 +520,10 @@ enum nsCSSUnit {
// allowed.
eCSSUnit_HSLColor = 89, // (nsCSSValueFloatColor*)
eCSSUnit_HSLAColor = 90, // (nsCSSValueFloatColor*)
eCSSUnit_ComplexColor = 91, // (ComplexColorValue*)
eCSSUnit_ColorMix = 92, // (ColorMixValue*)
eCSSUnit_OklabColor = 91, // (nsCSSValueFloatColor*)
eCSSUnit_OklchColor = 92, // (nsCSSValueFloatColor*)
eCSSUnit_ComplexColor = 93, // (ComplexColorValue*)
eCSSUnit_ColorMix = 94, // (ColorMixValue*)
eCSSUnit_Percent = 100, // (float) 1.0 == 100%) value is percentage of something
eCSSUnit_Number = 101, // (float) value is numeric (usually multiplier, different behavior than percent)
@@ -733,6 +735,8 @@ public:
// eCSSUnit_PercentageRGBAColor -- rgba(%,%,%,float)
// eCSSUnit_HSLColor -- hsl(float,%,%)
// eCSSUnit_HSLAColor -- hsla(float,%,%,float)
// eCSSUnit_OklabColor -- oklab(float,float,float,float)
// eCSSUnit_OklchColor -- oklch(float,float,float,float)
//
// - IsNumericColorUnit returns true for any of the above units.
//
@@ -745,7 +749,7 @@ public:
{ return eCSSUnit_RGBColor <= aUnit && aUnit <= eCSSUnit_ShortHexColorAlpha; }
static bool IsFloatColorUnit(nsCSSUnit aUnit)
{ return eCSSUnit_PercentageRGBColor <= aUnit &&
aUnit <= eCSSUnit_HSLAColor; }
aUnit <= eCSSUnit_OklchColor; }
static bool IsNumericColorUnit(nsCSSUnit aUnit)
{ return IsIntegerColorUnit(aUnit) || IsFloatColorUnit(aUnit); }
@@ -1927,6 +1931,9 @@ private:
// [0, 1] for hue represents
// [0deg, 360deg].
//
// OklabColor stores L in mComponent1, a in mComponent2, b in mComponent3.
// OklchColor stores L in mComponent1, C in mComponent2, H in mComponent3.
//
// [-float::max(), float::max()] for PercentageRGBColor, PercentageRGBAColor.
// 1.0 means 100%.
float mComponent1;
@@ -1993,7 +2000,9 @@ namespace css {
enum class ColorMixColorSpace {
sRGB,
HSL
HSL,
Oklab,
Oklch
};
struct ColorMixValue final
+167 -5
View File
@@ -43,6 +43,7 @@
#include "nsIStyleRule.h"
#include "nsBidiUtils.h"
#include "nsStyleStructInlines.h"
#include "nsCSSNonSRGBColorSpace.h"
#include "nsCSSProps.h"
#include "nsTArray.h"
#include "nsContentUtils.h"
@@ -1152,6 +1153,137 @@ SetPairCoords(const nsCSSValue& aValue,
return cX;
}
static void
GetColorMixPremultipliedWeights(float aAlpha1, float aAlpha2,
float aWeight1, float aWeight2,
float& aPremultipliedWeight1,
float& aPremultipliedWeight2,
float& aAlpha)
{
float alphaWeight1 = aWeight1 * aAlpha1;
float alphaWeight2 = aWeight2 * aAlpha2;
aAlpha = alphaWeight1 + alphaWeight2;
if (aAlpha <= 0.0f) {
aPremultipliedWeight1 = 0.0f;
aPremultipliedWeight2 = 0.0f;
return;
}
aPremultipliedWeight1 = alphaWeight1 / aAlpha;
aPremultipliedWeight2 = alphaWeight2 / aAlpha;
}
static css::OklabColor
OklchToOklabColor(const css::OklchColor& aColor)
{
float hueRadians = aColor.mHue * css::kRadiansPerDegree;
return {
aColor.mL,
aColor.mChroma * std::cos(hueRadians),
aColor.mChroma * std::sin(hueRadians)
};
}
static void
GetColorMixColorComponents(const nsCSSValue& aValue, nscolor aResolvedColor,
css::OklabColor& aOklab,
css::OklchColor& aOklch,
float& aAlpha)
{
nsCSSUnit unit = aValue.GetUnit();
if (unit == eCSSUnit_OklabColor) {
const nsCSSValueFloatColor* color = aValue.GetFloatColorValue();
aOklab = { color->Comp1(), color->Comp2(), color->Comp3() };
aOklch = css::OklabToOklchColor(aOklab);
aAlpha = mozilla::clamped(color->Alpha(), 0.0f, 1.0f);
return;
}
if (unit == eCSSUnit_OklchColor) {
const nsCSSValueFloatColor* color = aValue.GetFloatColorValue();
aOklch = { color->Comp1(), color->Comp2(), color->Comp3() };
aOklab = OklchToOklabColor(aOklch);
aAlpha = mozilla::clamped(color->Alpha(), 0.0f, 1.0f);
return;
}
aOklab = css::SRGBToOklabColor(aResolvedColor);
aOklch = css::OklabToOklchColor(aOklab);
aAlpha = NS_GET_A(aResolvedColor) / 255.0f;
}
static float
InterpolateColorMixHue(float aHue1, float aHue2, float aWeight1,
float aWeight2)
{
float hueDiff = aHue2 - aHue1;
if (hueDiff > 180.0f) {
aHue1 += 360.0f;
} else if (hueDiff < -180.0f) {
aHue2 += 360.0f;
}
float hue = aHue1 * aWeight1 + aHue2 * aWeight2;
hue = std::fmod(hue, 360.0f);
if (hue < 0.0f) {
hue += 360.0f;
}
return hue;
}
static nscolor
MixColorsInOklab(const css::OklabColor& aColor1, float aAlpha1,
const css::OklabColor& aColor2, float aAlpha2,
float aWeight1, float aWeight2, float aAlphaMultiplier)
{
float premultipliedWeight1, premultipliedWeight2, alpha;
GetColorMixPremultipliedWeights(aAlpha1, aAlpha2, aWeight1, aWeight2,
premultipliedWeight1, premultipliedWeight2,
alpha);
alpha *= aAlphaMultiplier;
if (alpha <= 0.0f) {
return NS_RGBA(0, 0, 0, 0);
}
return css::OklabToSRGBColor(
aColor1.mL * premultipliedWeight1 + aColor2.mL * premultipliedWeight2,
aColor1.mA * premultipliedWeight1 + aColor2.mA * premultipliedWeight2,
aColor1.mB * premultipliedWeight1 + aColor2.mB * premultipliedWeight2,
alpha);
}
static nscolor
MixColorsInOklch(css::OklchColor aColor1, float aAlpha1,
css::OklchColor aColor2, float aAlpha2,
float aWeight1, float aWeight2, float aAlphaMultiplier)
{
if (aColor1.mChroma <= css::kOklchPowerlessChromaEpsilon) {
aColor1.mHue = aColor2.mHue;
}
if (aColor2.mChroma <= css::kOklchPowerlessChromaEpsilon) {
aColor2.mHue = aColor1.mHue;
}
float premultipliedWeight1, premultipliedWeight2, alpha;
GetColorMixPremultipliedWeights(aAlpha1, aAlpha2, aWeight1, aWeight2,
premultipliedWeight1, premultipliedWeight2,
alpha);
alpha *= aAlphaMultiplier;
if (alpha <= 0.0f) {
return NS_RGBA(0, 0, 0, 0);
}
float hue = InterpolateColorMixHue(aColor1.mHue, aColor2.mHue,
aWeight1, aWeight2);
return css::OklchToSRGBColor(
aColor1.mL * premultipliedWeight1 + aColor2.mL * premultipliedWeight2,
aColor1.mChroma * premultipliedWeight1 +
aColor2.mChroma * premultipliedWeight2,
hue,
alpha);
}
static bool
SetColor(const nsCSSValue& aValue,
const nscolor aParentColor,
@@ -1240,22 +1372,29 @@ SetColor(const nsCSSValue& aValue,
const mozilla::css::ColorMixValue* colorMix = aValue.GetColorMixValue();
if (colorMix) {
nscolor color1, color2;
bool resolvedColor1, resolvedColor2;
// XXX: This is a hack to avoid recursive calls to SetColor when either color resolves
// to NS_COLOR_CURRENTCOLOR, as it would result in re-evaluation of the color.
// Instead of recursing, we reach up to set either color to the parent color, instead.
if (colorMix->mColor1.GetUnit() == eCSSUnit_EnumColor && colorMix->mColor1.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
color1 = aParentColor;
resolvedColor1 = true;
} else {
SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext, color1, aConditions);
resolvedColor1 =
SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext,
color1, aConditions);
}
if (colorMix->mColor2.GetUnit() == eCSSUnit_EnumColor && colorMix->mColor2.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
color2 = aParentColor;
resolvedColor2 = true;
} else {
SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext, color2, aConditions);
resolvedColor2 =
SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext,
color2, aConditions);
}
if (color1 && color2) {
if (resolvedColor1 && resolvedColor2) {
// interpolate each RGBA component with proper percentage handling
float w1 = colorMix->mWeight1;
float w2 = colorMix->mWeight2;
@@ -1272,9 +1411,18 @@ SetColor(const nsCSSValue& aValue,
w1 = w2 = 0.5f;
sum = 1.0f;
}
float alphaMultiplier = std::min(sum, 1.0f);
float norm1 = w1 / sum;
float norm2 = w2 / sum;
css::OklabColor oklab1, oklab2;
css::OklchColor oklch1, oklch2;
float oklabAlpha1, oklabAlpha2;
GetColorMixColorComponents(colorMix->mColor1, color1, oklab1,
oklch1, oklabAlpha1);
GetColorMixColorComponents(colorMix->mColor2, color2, oklab2,
oklch2, oklabAlpha2);
if (colorMix->mColorSpace == mozilla::css::ColorMixColorSpace::HSL) {
// HSL color space mixing
@@ -1347,10 +1495,23 @@ SetColor(const nsCSSValue& aValue,
// Convert back to RGB
nscolor hslResult = NS_HSL2RGB(h, s, l);
uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f);
uint8_t aInt = (uint8_t)mozilla::clamped(
a * alphaMultiplier * 255.0f + 0.5f, 0.0f, 255.0f);
aResult = NS_RGBA(NS_GET_R(hslResult), NS_GET_G(hslResult), NS_GET_B(hslResult), aInt);
result = true;
} else if (colorMix->mColorSpace ==
mozilla::css::ColorMixColorSpace::Oklab) {
aResult = MixColorsInOklab(oklab1, oklabAlpha1,
oklab2, oklabAlpha2,
norm1, norm2, alphaMultiplier);
result = true;
} else if (colorMix->mColorSpace ==
mozilla::css::ColorMixColorSpace::Oklch) {
aResult = MixColorsInOklch(oklch1, oklabAlpha1,
oklch2, oklabAlpha2,
norm1, norm2, alphaMultiplier);
result = true;
} else {
// sRGB color space mixing with proper alpha premultiplication
float r1 = NS_GET_R(color1);
@@ -1400,7 +1561,8 @@ SetColor(const nsCSSValue& aValue,
uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f);
uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f);
uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f);
uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f);
uint8_t aInt = (uint8_t)mozilla::clamped(
a * alphaMultiplier * 255.0f + 0.5f, 0.0f, 255.0f);
aResult = NS_RGBA(rInt, gInt, bInt, aInt);
result = true;
+4
View File
@@ -4332,6 +4332,10 @@ var gCSSProperties = {
"oklab(100% 0 0)",
"oklab(60% 0.1 -0.1 / 75%)",
"oklch(70% 0.2 180deg / 40%)",
"color-mix(in oklab, red, blue)",
"color-mix(in oklch, red, blue)",
"color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))",
"color-mix(in oklch, oklch(0.1 0.2 30), oklch(0.5 0.6 70))",
],
invalid_values: [
"#f",
+9 -4
View File
@@ -406,14 +406,19 @@ var noframe_container = document.getElementById("content");
["lch(150% 50 60 / 1)", "rgb(255, 255, 255)"],
["oklab(0 0 0 / 1)", "rgb(0, 0, 0)"],
["oklab(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
["oklab(150% 0.5 0.2 / 1)", "rgb(255, 0, 0)"],
["oklab(150% 0.5 0.2 / 1)", "rgb(255, 255, 255)"],
["oklch(0 0 0 / 1)", "rgb(0, 0, 0)"],
["oklch(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
["oklch(0.0001% 0.2 45 / 1)", "rgb(0, 0, 0)"],
["oklch(99.9999% 0.2 45 / 1)", "rgb(255, 207, 132)"],
["oklch(99.9999% 0.2 45 / 1)", "rgb(255, 251, 242)"],
["oklch(0% 1.1 60 / 1)", "rgb(0, 0, 0)"],
["oklch(100% 110 60 / 1)", "rgb(0, 255, 0)"],
["oklch(150% 0.5 50 / 1)", "rgb(255, 0, 0)"],
["oklch(100% 110 60 / 1)", "rgb(255, 255, 255)"],
["oklch(150% 0.5 50 / 1)", "rgb(255, 255, 255)"],
["color-mix(in oklab, red, blue)", "rgb(140, 83, 162)"],
["color-mix(in oklch, red, blue)", "rgb(183, 0, 190)"],
["color-mix(in oklab, red, transparent)", "rgba(255, 0, 0, 0.5)"],
["color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))", "rgb(83, 24, 0)"],
["color-mix(in oklch, oklch(0.1 0.2 30), oklch(0.5 0.6 70))", "rgb(84, 23, 0)"],
];
var p = document.createElement("p");