From 3eb3474324b7cb4ca35475d66c78cf9cbd4ba073 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Wed, 1 Apr 2026 16:17:05 +0200 Subject: [PATCH] no-bug: Rewrite color filter algorithm --- src/zen/boosts/ZenBoostsManager.sys.mjs | 2 +- src/zen/boosts/jar.inc.mn | 2 +- src/zen/boosts/nsZenBoostsBackend.cpp | 251 +++++++++--------- src/zen/boosts/nsZenBoostsBackend.h | 7 +- ...ditor.xhtml => zen-boost-editor.inc.xhtml} | 8 +- 5 files changed, 135 insertions(+), 135 deletions(-) rename src/zen/boosts/{zen-boost-editor.xhtml => zen-boost-editor.inc.xhtml} (98%) diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index ef6b0f0ec..2e7fc1b70 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -120,7 +120,7 @@ class nsZenBoostsManager { brightness: 0.5, saturation: 0.5, - contrast: 0.25, + contrast: 0.5, fontFamily: "", diff --git a/src/zen/boosts/jar.inc.mn b/src/zen/boosts/jar.inc.mn index a4974dc3f..b17e886b6 100644 --- a/src/zen/boosts/jar.inc.mn +++ b/src/zen/boosts/jar.inc.mn @@ -8,7 +8,7 @@ content/browser/zen-styles/zen-advanced-color-options.css (../../zen/boosts/zen-advanced-color-options.css) # Windows -* content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml) +* content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.inc.xhtml) # Images content/browser/zen-images/boost-indicator.svg (../../zen/images/boost-indicator.svg) diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index bd1b4ad2b..e0f952e47 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -84,63 +84,79 @@ nsZenAccentOklab nsZenBoostsBackend::mCachedAccent{0}; namespace { -struct Lab { - float L, a, b; -}; - -struct RGB { - float r, g, b; -}; - /** - * @brief Clamps a value to the range [0, 255] using branchless operations. - * @param v The value to clamp. - * @return The clamped value in the range [0, 255]. + * @brief Converts an sRGB color component to linear space. + * @param c The sRGB color component value (0.0 to 1.0). + * @return The linear color component value. */ -static __inline int32_t clamp255(int32_t v) { - // llvm x86 is poor at ternary operator, so use branchless min/max. - v = v & ~(v >> 31); - return (v | ((255 - v) >> 31)) & 255; +static inline float srgbToLinear(float c) { + return c <= 0.04045f ? c * (1.0f / 12.92f) + : std::pow((c + 0.055f) * (1.0f / 1.055f), 2.4f); } /** - * @brief A fast approximation of the cube root function using bit manipulation - * and two Newton-Raphson iterations. This is used to optimize the Oklab color - * conversion in the color filtering process. + * @brief Converts a linear color component to sRGB space. + * @param c The linear color component value. + * @return The sRGB color component value (0.0 to 1.0). */ -inline static float fast_cbrt(float x) { - // Bit-level initial approximation (works for positive floats only — fine - // here) - uint32_t bits; - memcpy(&bits, &x, 4); - bits = (bits / 3) + 0x2A512400u; // magic constant for cube root - float y; - memcpy(&y, &bits, 4); - // Two Newton-Raphson iterations: y = y - (y³ - x) / (3y²) - y = (2.0f / 3.0f) * y + (1.0f / 3.0f) * x / (y * y); - y = (2.0f / 3.0f) * y + (1.0f / 3.0f) * x / (y * y); - return y; +static inline float linearToSrgb(float c) { + c = std::max(0.0f, c); + return c <= 0.0031308f ? 12.92f * c + : 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f; +} + +/* + * @brief Fast approximation of the cube root of a number. + * @param x The input value. + * @return The approximate cube root of the input value. + */ +static inline float fastCbrt(float x) { + if (x == 0.0f) return 0.0f; + float a = std::abs(x); + union { + float f; + uint32_t i; + } u = {a}; + u.i = u.i / 3 + 0x2a504a2e; + float y = u.f; + y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f); + y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f); + return x < 0.0f ? -y : y; } /** - * @brief Converts an Oklab color back to the RGB color space. - * @param c The Oklab color to convert. - * @return The corresponding RGB color. + * @brief Precomputes the Oklab values for a given accent color. This allows us + * to efficiently apply the accent color as a filter to other colors without + * having to convert the accent color from sRGB to Oklab space on every filter + * operation. + * @param aAccentColor The accent color in nscolor format. + * @return A struct containing the precomputed Oklab values and contrast factor + * for the accent color. */ -[[nodiscard]] -static inline auto oklab2rgb(Lab c) -> RGB { - float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b; - float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b; - float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b; +inline static auto zenPrecomputeAccent(nscolor aAccentColor) { + const float inv255 = 1.0f / 255.0f; - // Cubing is just 2 multiplies — no cbrtf needed on the way back - return { - 4.0767416621f * (l_ * l_ * l_) - 3.3077115913f * (m_ * m_ * m_) + - 0.2309699292f * (s_ * s_ * s_), - -1.2684380046f * (l_ * l_ * l_) + 2.6097574011f * (m_ * m_ * m_) - - 0.3413193965f * (s_ * s_ * s_), - -0.0041960863f * (l_ * l_ * l_) - 0.7034186147f * (m_ * m_ * m_) + - 1.7076147010f * (s_ * s_ * s_), + const float r = NS_GET_R(aAccentColor) * inv255; + const float g = NS_GET_G(aAccentColor) * inv255; + const float b = NS_GET_B(aAccentColor) * inv255; + + const float lr = srgbToLinear(r); + const float lg = srgbToLinear(g); + const float lb = srgbToLinear(b); + + const float l_ = + fastCbrt(0.4122214708f * lr + 0.5363325363f * lg + 0.0514459929f * lb); + const float m_ = + fastCbrt(0.2119034982f * lr + 0.6806995451f * lg + 0.1073969566f * lb); + const float s_ = + fastCbrt(0.0883024619f * lr + 0.2817188376f * lg + 0.6299787005f * lb); + + return nsZenAccentOklab{ + .accentNS = aAccentColor, + .accL = 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_, + .accA = 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_, + .accB = 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_, + .contrastFactor = NS_GET_CONTRAST(aAccentColor) * inv255, }; } @@ -155,85 +171,71 @@ static inline auto oklab2rgb(Lab c) -> RGB { * @return The filtered color with transformations applied. */ [[nodiscard]] -static inline Lab rgb2oklab_fast(RGB c) { - float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; - float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b; - float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b; - float l_ = fast_cbrt(l), m_ = fast_cbrt(m), s_ = fast_cbrt(s); - return { - 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_, - 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_, - 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_, - }; -} +static inline nscolor zenFilterColorChannel(nscolor aOriginalColor, + const nsZenAccentOklab& aAccent) { + const uint8_t oL = NS_GET_A(aOriginalColor); + if (oL == 0) { + return aOriginalColor; + } -inline static auto zenPrecomputeAccent(nscolor aAccentColor) - -> nsZenAccentOklab { - constexpr float kInv255 = 1.0f / 255.0f; - RGB rgb = {NS_GET_R(aAccentColor) * kInv255, NS_GET_G(aAccentColor) * kInv255, - NS_GET_B(aAccentColor) * kInv255}; - auto lab = rgb2oklab_fast(rgb); - float contrast = NS_GET_CONTRAST(aAccentColor); - float vibranceBase = 1.0f - ((contrast - 128.0f) * (1.0f / 128.0f)); - return {lab.L, lab.a, lab.b, vibranceBase, 0.25f + lab.L}; -} + const float inv255 = 1.0f / 255.0f; + const float blendFactor = oL * inv255; + const float preserveFactor = 1.0f - aAccent.contrastFactor; -/** - * @brief Applies a color filter to transform an original color toward an accent - * color. Preserves the original color's perceived luminance while shifting - * hue/chroma toward the accent. Uses the alpha channel of the accent color to - * store contrast information. - * @param aOriginalColor The original color to filter. - * @param aAccentColor The accent color to filter toward (alpha channel contains - * contrast value). - * @return The filtered color with transformations applied. - */ -[[nodiscard]] -static nscolor zenFilterColorChannel(nscolor aOriginalColor, - const nsZenAccentOklab& aAccent) { - const uint8_t a1 = NS_GET_A(aOriginalColor); - if (a1 == 0) return aOriginalColor; + // sRGB -> linear + const float lr = srgbToLinear(NS_GET_R(aOriginalColor) * inv255); + const float lg = srgbToLinear(NS_GET_G(aOriginalColor) * inv255); + const float lb = srgbToLinear(NS_GET_B(aOriginalColor) * inv255); - constexpr float kInv255 = 1.0f / 255.0f; - constexpr float kTint = 0.6f; - constexpr float kInvTint = 1.0f - kTint; + // Linear RGB -> LMS -> cube root -> Oklab (fused) + const float l_ = + fastCbrt(0.4122214708f * lr + 0.5363325363f * lg + 0.0514459929f * lb); + const float m_ = + fastCbrt(0.2119034982f * lr + 0.6806995451f * lg + 0.1073969566f * lb); + const float s_ = + fastCbrt(0.0883024619f * lr + 0.2817188376f * lg + 0.6299787005f * lb); - RGB orig = { - NS_GET_R(aOriginalColor) * kInv255, - NS_GET_G(aOriginalColor) * kInv255, - NS_GET_B(aOriginalColor) * kInv255, - }; - const auto lab = rgb2oklab_fast(orig); + const float origL = + 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_; + const float origA = + 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_; + const float origB = + 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_; - const float aBlend = kInvTint * lab.a + kTint * aAccent.a; - const float bBlend = kInvTint * lab.b + kTint * aAccent.b; + // Blend chroma toward accent + const float fA = origA + (aAccent.accA - origA) * blendFactor; + const float fB = origB + (aAccent.accB - origB) * blendFactor; - // Avoid sqrt: compare squared chroma against squared threshold (0.4^2 = 0.16) - // vibranceFactor = chromaMixed/chromaBlend which simplifies to just - // vibranceFactor since the sqrt cancels in the normalize+scale round-trip - // (see below) - const float chromaSq = aBlend * aBlend + bBlend * bBlend; - const float saturation = - (chromaSq < 0.16f) ? chromaSq * (1.0f / 0.16f) : 1.0f; - const float vibranceFactor = - 1.0f + aAccent.vibranceBase * (1.0f - saturation); + // Luminance: preserve spread at low contrast, flatten at high + const float deltaL = origL - aAccent.accL; + const float baseL = origL + (aAccent.accL - origL) * blendFactor; + const float fL = std::clamp(baseL + deltaL * preserveFactor, 0.0f, 1.0f); - // sqrt cancellation: chromaMixed/chromaBlend = - // (chromaBlend*vibranceFactor)/chromaBlend = vibranceFactor, so we multiply - // directly with no sqrt needed - const float aMixed = aBlend * vibranceFactor; - const float bMixed = bBlend * vibranceFactor; + // Oklab -> LMS + const float fl_ = fL + 0.3963377774f * fA + 0.2158037573f * fB; + const float fm_ = fL - 0.1055613458f * fA - 0.0638541728f * fB; + const float fs_ = fL - 0.0894841775f * fA - 1.2914855480f * fB; - float LMixed = 0.5f + (lab.L - 0.5f) * (1.0f + aAccent.vibranceBase * 0.5f); - LMixed *= aAccent.accentLOffset; - if (LMixed < 0.0f) LMixed = 0.0f; - if (LMixed > 1.0f) LMixed = 1.0f; + // Cube + const float fl = fl_ * fl_ * fl_; + const float fm = fm_ * fm_ * fm_; + const float fs = fs_ * fs_ * fs_; - const auto rgb = oklab2rgb({LMixed, aMixed, bMixed}); + // LMS -> linear RGB + const float rF = 4.0767416621f * fl - 3.3077115913f * fm + 0.2309699292f * fs; + const float gF = + -1.2684380046f * fl + 2.6097574011f * fm - 0.3413193965f * fs; + const float bF = + -0.0041960863f * fl - 0.7034186147f * fm + 1.7076147010f * fs; - return NS_RGBA(clamp255((int32_t)(rgb.r * 255.0f + 0.5f)), - clamp255((int32_t)(rgb.g * 255.0f + 0.5f)), - clamp255((int32_t)(rgb.b * 255.0f + 0.5f)), a1); + // Linear -> sRGB -> uint8 + return NS_RGBA(static_cast(std::clamp( + linearToSrgb(rF) * 255.0f + 0.5f, 0.0f, 255.0f)), + static_cast(std::clamp( + linearToSrgb(gF) * 255.0f + 0.5f, 0.0f, 255.0f)), + static_cast(std::clamp( + linearToSrgb(bF) * 255.0f + 0.5f, 0.0f, 255.0f)), + oL); } /** @@ -279,18 +281,18 @@ inline static void GetZenBoostsDataFromBrowsingContext( if (!zenBoosts || (zenBoosts->mCurrentFrameIsAnonymousContent)) { return; } + auto browsingContext = zenBoosts->GetCurrentBrowsingContext(); if (aPresContext) { if (auto document = aPresContext->Document()) { - if (auto browsingContext = document->GetBrowsingContext()) { - *aData = browsingContext->ZenBoostsData(); - *aIsInverted = browsingContext->IsZenBoostsInverted(); - } + browsingContext = document->GetBrowsingContext(); } - } else if (auto currentBrowsingContext = - zenBoosts->GetCurrentBrowsingContext()) { - *aData = currentBrowsingContext->ZenBoostsData(); - *aIsInverted = currentBrowsingContext->IsZenBoostsInverted(); } + if (!browsingContext) { + return; + } + browsingContext = browsingContext->Top(); + *aData = browsingContext->ZenBoostsData(); + *aIsInverted = browsingContext->IsZenBoostsInverted(); } } // namespace @@ -360,8 +362,7 @@ auto nsZenBoostsBackend::ResolveStyleColor(mozilla::StyleAbsoluteColor aColor) return aColor; } const auto resultColor = FilterColorFromPresContext(aColor.ToColor()); - aColor = mozilla::StyleAbsoluteColor::FromColor(resultColor); - return aColor; + return mozilla::StyleAbsoluteColor::FromColor(resultColor); } } // namespace zen diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index d02efb7bc..f621a07bc 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -17,10 +17,9 @@ using ZenBoostData = nscolor; // For now, Zen boosts data is just a color. namespace zen { struct nsZenAccentOklab { - float L, a, b; - float vibranceBase; // 1.0f - ((contrast - 128) / 128) - float accentLOffset; // 0.25f + L, precomputed - nscolor accentNS; // Used to keep track of the original accent color + nscolor accentNS; + float accL, accA, accB; + float contrastFactor; }; class nsZenBoostsBackend final { diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.inc.xhtml similarity index 98% rename from src/zen/boosts/zen-boost-editor.xhtml rename to src/zen/boosts/zen-boost-editor.inc.xhtml index f700a64c4..afe355a74 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.inc.xhtml @@ -38,7 +38,7 @@ @@ -73,7 +73,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -101,7 +101,7 @@ - +