mirror of
https://github.com/roytam1/basilisk55.git
synced 2026-05-26 15:02:46 +00:00
ported from UXP: Issue #2982 - Follow-up: allow color-mix to work with oklab and oklch (20b2b3b9)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1487,8 +1487,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);
|
||||
|
||||
@@ -7763,7 +7764,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;
|
||||
@@ -7774,6 +7775,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;
|
||||
@@ -7922,16 +7927,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(')');
|
||||
@@ -8183,7 +8191,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;
|
||||
@@ -8201,12 +8210,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;
|
||||
@@ -8224,7 +8237,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "gfxFontConstants.h"
|
||||
#include "imgIRequest.h"
|
||||
#include "imgRequestProxy.h"
|
||||
#include "nsCSSNonSRGBColorSpace.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsCSSProps.h"
|
||||
@@ -1973,6 +1974,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
|
||||
@@ -2280,6 +2287,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;
|
||||
@@ -2488,6 +2497,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;
|
||||
|
||||
@@ -3479,6 +3490,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);
|
||||
@@ -3507,8 +3528,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");
|
||||
@@ -3518,22 +3545,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("%)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -510,8 +510,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)
|
||||
@@ -723,6 +725,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.
|
||||
//
|
||||
@@ -735,7 +739,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); }
|
||||
|
||||
@@ -1909,6 +1913,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;
|
||||
@@ -1975,7 +1982,9 @@ namespace css {
|
||||
|
||||
enum class ColorMixColorSpace {
|
||||
sRGB,
|
||||
HSL
|
||||
HSL,
|
||||
Oklab,
|
||||
Oklch
|
||||
};
|
||||
|
||||
struct ColorMixValue final
|
||||
|
||||
+167
-5
@@ -45,6 +45,7 @@
|
||||
#include "nsIStyleRule.h"
|
||||
#include "nsBidiUtils.h"
|
||||
#include "nsStyleStructInlines.h"
|
||||
#include "nsCSSNonSRGBColorSpace.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsContentUtils.h"
|
||||
@@ -1143,6 +1144,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,
|
||||
@@ -1232,22 +1364,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;
|
||||
@@ -1264,9 +1403,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
|
||||
@@ -1339,10 +1487,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);
|
||||
@@ -1392,7 +1553,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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user