|
|
|
|
@@ -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<uint8_t>(std::clamp(
|
|
|
|
|
linearToSrgb(rF) * 255.0f + 0.5f, 0.0f, 255.0f)),
|
|
|
|
|
static_cast<uint8_t>(std::clamp(
|
|
|
|
|
linearToSrgb(gF) * 255.0f + 0.5f, 0.0f, 255.0f)),
|
|
|
|
|
static_cast<uint8_t>(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
|
|
|
|
|
|