diff --git a/layout/style/nsCSSNonSRGBColorSpace.h b/layout/style/nsCSSNonSRGBColorSpace.h index c1704cae4b..4ce2e56a1b 100644 --- a/layout/style/nsCSSNonSRGBColorSpace.h +++ b/layout/style/nsCSSNonSRGBColorSpace.h @@ -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 diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index b599e400d8..f597497bda 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -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; diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 5a5f157882..3827ab898d 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -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)", diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html index 4e7d629a06..55ba9edb30 100644 --- a/layout/style/test/test_computed_style.html +++ b/layout/style/test/test_computed_style.html @@ -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)"],