Issue #3003 - Add CSS lch() color parsing support

This commit is contained in:
Basilisk-Dev 2026-04-02 16:43:54 -04:00 • committed by EAZYBLACK
commit b0d43e27e9
4 changed files with 121 additions and 11 deletions

View file

@ -15,6 +15,8 @@
namespace mozilla {
namespace css {
static constexpr float kLabLightnessMax = 100.0f;
static constexpr float kLchPercentScaleC = 150.0f;
static constexpr float kOklabPercentScaleAB = 0.4f;
static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489;
@ -27,6 +29,21 @@ LinearSRGBToEncoded(float aValue)
return 1.055f * std::pow(aValue, 1.0f / 2.4f) - 0.055f;
}
inline nscolor
LinearSRGBToColor(float aLinearR, float aLinearG, float aLinearB,
uint8_t aAlpha)
{
float r = mozilla::clamped(LinearSRGBToEncoded(aLinearR), 0.0f, 1.0f);
float g = mozilla::clamped(LinearSRGBToEncoded(aLinearG), 0.0f, 1.0f);
float b = mozilla::clamped(LinearSRGBToEncoded(aLinearB), 0.0f, 1.0f);
return NS_RGBA(
NSToIntRound(r * 255.0f),
NSToIntRound(g * 255.0f),
NSToIntRound(b * 255.0f),
aAlpha);
}
inline nscolor
OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
{
@ -55,15 +72,7 @@ OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
float r = mozilla::clamped(LinearSRGBToEncoded(linearR), 0.0f, 1.0f);
float g = mozilla::clamped(LinearSRGBToEncoded(linearG), 0.0f, 1.0f);
float b = mozilla::clamped(LinearSRGBToEncoded(linearB), 0.0f, 1.0f);
return NS_RGBA(
NSToIntRound(r * 255.0f),
NSToIntRound(g * 255.0f),
NSToIntRound(b * 255.0f),
alpha);
return LinearSRGBToColor(linearR, linearG, linearB, alpha);
}
inline nscolor
@ -75,6 +84,70 @@ OklchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha)
return OklabToSRGBColor(aL, a, b, aAlpha);
}
inline nscolor
LabToSRGBColor(float aL, float aA, float aB, float aAlpha)
{
float lightness = mozilla::clamped(aL, 0.0f, kLabLightnessMax);
uint8_t alpha =
nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f));
if (lightness <= 0.0f) {
return NS_RGBA(0, 0, 0, alpha);
}
if (lightness >= kLabLightnessMax) {
return NS_RGBA(255, 255, 255, alpha);
}
static constexpr float kLabEpsilon = 216.0f / 24389.0f;
static constexpr float kLabKappa = 24389.0f / 27.0f;
static constexpr float kD50WhitePointX = 0.9642956764295677f;
static constexpr float kD50WhitePointZ = 0.8251046025104602f;
float fy = (lightness + 16.0f) / 116.0f;
float fx = aA / 500.0f + fy;
float fz = fy - aB / 200.0f;
float fx3 = fx * fx * fx;
float fy3 = fy * fy * fy;
float fz3 = fz * fz * fz;
float x = fx3 > kLabEpsilon ? fx3 : (116.0f * fx - 16.0f) / kLabKappa;
float y = lightness > kLabKappa * kLabEpsilon ? fy3 : lightness / kLabKappa;
float z = fz3 > kLabEpsilon ? fz3 : (116.0f * fz - 16.0f) / kLabKappa;
x *= kD50WhitePointX;
z *= kD50WhitePointZ;
float adaptedX = 0.955473421488075f * x - 0.02309845494876471f * y +
0.06325924320057072f * z;
float adaptedY = -0.0283697093338637f * x + 1.0099953980813041f * y +
0.021041441191917323f * z;
float adaptedZ = 0.012314014864481998f * x - 0.020507649298898964f * y +
1.330365926242124f * z;
float linearR = (12831.0f / 3959.0f) * adaptedX +
(-329.0f / 214.0f) * adaptedY +
(-1974.0f / 3959.0f) * adaptedZ;
float linearG = (-851781.0f / 878810.0f) * adaptedX +
(1648619.0f / 878810.0f) * adaptedY +
(36519.0f / 878810.0f) * adaptedZ;
float linearB = (705.0f / 12673.0f) * adaptedX +
(-2585.0f / 12673.0f) * adaptedY +
(705.0f / 667.0f) * adaptedZ;
return LinearSRGBToColor(linearR, linearG, linearB, alpha);
}
inline nscolor
LchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha)
{
float chroma = aChroma < 0.0f ? 0.0f : aChroma;
double hueRadians = aHue * kRadiansPerDegree;
float a = chroma * std::cos(hueRadians);
float b = chroma * std::sin(hueRadians);
return LabToSRGBColor(aL, a, b, aAlpha);
}
} // namespace css
} // namespace mozilla

View file

@ -1237,6 +1237,7 @@ protected:
ComponentType& aA);
bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
float& aOpacity);
bool ParseLCHColor(nscolor& aColor);
bool ParseOKLabColor(nscolor& aColor);
bool ParseOKLCHColor(nscolor& aColor);
bool ParseOKLabComponent(float& aComponent, float aPercentScale,
@ -7623,6 +7624,14 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue)
SkipUntil(')');
return CSSParseResult::Error;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("lch")) {
if (ParseLCHColor(rgba)) {
aValue.SetColorValue(rgba);
return CSSParseResult::Ok;
}
SkipUntil(')');
return CSSParseResult::Error;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("oklab")) {
if (ParseOKLabColor(rgba)) {
aValue.SetColorValue(rgba);
@ -7868,6 +7877,22 @@ CSSParserImpl::ParseOKLabComponent(float& aComponent, float aPercentScale,
return true;
}
bool
CSSParserImpl::ParseLCHColor(nscolor& aColor)
{
float l, chroma, hue, alpha;
if (!ParseOKLabComponent(l, kLabLightnessMax, Nothing()) ||
!ParseOKLabComponent(chroma, kLchPercentScaleC, Nothing()) ||
!ParseHue(hue) ||
!ParseColorOpacityAndCloseParen(alpha, '/')) {
return false;
}
aColor = LchToSRGBColor(l, chroma, hue, alpha);
return true;
}
bool
CSSParserImpl::ParseOKLabColor(nscolor& aColor)
{
@ -8862,6 +8887,7 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue,
tk->mIdent.LowerCaseEqualsLiteral("hsl") ||
tk->mIdent.LowerCaseEqualsLiteral("rgba") ||
tk->mIdent.LowerCaseEqualsLiteral("hsla") ||
tk->mIdent.LowerCaseEqualsLiteral("lch") ||
tk->mIdent.LowerCaseEqualsLiteral("oklab") ||
tk->mIdent.LowerCaseEqualsLiteral("oklch") ||
tk->mIdent.LowerCaseEqualsLiteral("color-mix"))))
@ -11117,7 +11143,8 @@ CSSParserImpl::ParseGradientInterpolationMethod()
mToken.mIdent.LowerCaseEqualsLiteral("oklab")) {
isPolarColorSpace = false;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl") ||
mToken.mIdent.LowerCaseEqualsLiteral("oklch")) {
mToken.mIdent.LowerCaseEqualsLiteral("oklch") ||
mToken.mIdent.LowerCaseEqualsLiteral("lch")) {
isPolarColorSpace = true;
} else {
return CSSParseResult::Error;

View file

@ -124,6 +124,7 @@ var validGradientAndElementValues = [
"linear-gradient(.414rad, red, blue)",
"linear-gradient(90deg in srgb, yellow, purple)",
"linear-gradient(90deg in hsl, yellow, purple)",
"linear-gradient(90deg in lch, yellow, purple)",
"linear-gradient(90deg in oklch, yellow, purple)",
"linear-gradient(in oklab, yellow, purple)",
@ -4302,7 +4303,8 @@ var gCSSProperties = {
"hsl(0rad, 0%, 0%)",
"hsl(0turn, 0%, 0%)",
"hsl(1turn, 0%, 0%)",
/* oklab() and oklch(). */
/* lch(), oklab(), and oklch(). */
"lch(0 0 0)",
"oklab(0 0 0)",
"oklch(0 0 0)",
],
@ -4327,6 +4329,7 @@ var gCSSProperties = {
"hsl(0.5grad 400% 500% / 9.0)",
"hsl(33rad 100% 90% / 4)",
"hsl(0.33turn, 40%, 40%, 10%)",
"lch(70% 50 180deg / 40%)",
"oklab(100% 0 0)",
"oklab(60% 0.1 -0.1 / 75%)",
"oklch(70% 0.2 180deg / 40%)",
@ -4361,6 +4364,8 @@ var gCSSProperties = {
"rgb(0, 0, 0 /)",
"hsl(0 0% 0% /)",
"hsl(0, 0%, 0% /)",
"lch(0, 0 0)",
"lch(0 0 / 1)",
"oklab(0, 0 0)",
"oklab(0 0 / 1)",
"oklch(0, 0 0)",

View file

@ -399,6 +399,11 @@ var noframe_container = document.getElementById("content");
["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
["lch(0 0 0 / 1)", "rgb(0, 0, 0)"],
["lch(100% 0 0 / 0.5)", "rgba(255, 255, 255, 0.5)"],
["lch(52.2345% 72.2 56.2 / 1)", "rgb(198, 93, 6)"],
["lch(29.69% 45.553% 327.1 / 1)", "rgb(129, 0, 129)"],
["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)"],