mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
UI: Improve brand color adjustment (#6351)
Uses a more finegrained method of deciding whether or not to adjust the brand color: Instead of simply using the brightness of the color, we calculate the contrast ratio and adjust the color by increasing the contrast until it is sufficient. The thresholds also try to preserve the brand color in its original form, so that the color only gets adjusted if the contrast is very low.
This commit is contained in:
parent
12681eda36
commit
540fb6c9f6
@ -110,5 +110,10 @@ namespace BTCPayServer
|
||||
{
|
||||
return ColorTranslator.FromHtml(html);
|
||||
}
|
||||
|
||||
public string ToHtml(Color color)
|
||||
{
|
||||
return ColorTranslator.ToHtml(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
121
BTCPayServer/Extensions/ColorExtensions.cs
Normal file
121
BTCPayServer/Extensions/ColorExtensions.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BTCPayServer;
|
||||
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static double GetLuminance(this Color color)
|
||||
{
|
||||
var r = color.R / 255.0;
|
||||
var g = color.G / 255.0;
|
||||
var b = color.B / 255.0;
|
||||
|
||||
r = (r <= 0.03928) ? r / 12.92 : Math.Pow((r + 0.055) / 1.055, 2.4);
|
||||
g = (g <= 0.03928) ? g / 12.92 : Math.Pow((g + 0.055) / 1.055, 2.4);
|
||||
b = (b <= 0.03928) ? b / 12.92 : Math.Pow((b + 0.055) / 1.055, 2.4);
|
||||
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
public static double GetContrastRatio(this Color color1, Color color2)
|
||||
{
|
||||
var luminance1 = color1.GetLuminance();
|
||||
var luminance2 = color2.GetLuminance();
|
||||
var lighter = Math.Max(luminance1, luminance2);
|
||||
var darker = Math.Min(luminance1, luminance2);
|
||||
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
public static Color GetAdjustedForegroundForBackground(this Color foregroundColor, Color backgroundColor, double lightnessIncrement = 0.01, double threshold = 4.5)
|
||||
{
|
||||
var contrastRatio = foregroundColor.GetContrastRatio(backgroundColor);
|
||||
if (contrastRatio >= threshold) return foregroundColor;
|
||||
|
||||
RgbToHsl(foregroundColor, out double hue, out double saturation, out double lightness);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
lightness += lightnessIncrement;
|
||||
lightness = Math.Clamp(lightness, 0, 1);
|
||||
|
||||
Color adjustedColor = HslToRgb(hue, saturation, lightness);
|
||||
contrastRatio = adjustedColor.GetContrastRatio(backgroundColor);
|
||||
|
||||
if (contrastRatio >= threshold) return adjustedColor;
|
||||
}
|
||||
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
private static void RgbToHsl(Color color, out double hue, out double saturation, out double lightness)
|
||||
{
|
||||
double r = color.R / 255.0;
|
||||
double g = color.G / 255.0;
|
||||
double b = color.B / 255.0;
|
||||
|
||||
double max = Math.Max(r, Math.Max(g, b));
|
||||
double min = Math.Min(r, Math.Min(g, b));
|
||||
lightness = (max + min) / 2.0;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
hue = saturation = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double delta = max - min;
|
||||
saturation = (lightness > 0.5) ? delta / (2.0 - max - min) : delta / (max + min);
|
||||
|
||||
if (max == r)
|
||||
{
|
||||
hue = (g - b) / delta + (g < b ? 6 : 0);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
hue = (b - r) / delta + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = (r - g) / delta + 4;
|
||||
}
|
||||
|
||||
hue /= 6;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color HslToRgb(double hue, double saturation, double lightness)
|
||||
{
|
||||
double r, g, b;
|
||||
|
||||
if (saturation == 0)
|
||||
{
|
||||
r = g = b = lightness;
|
||||
}
|
||||
else
|
||||
{
|
||||
Func<double, double, double, double> hueToRgb = (p, q, t) =>
|
||||
{
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6.0) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2.0) return q;
|
||||
if (t < 2 / 3.0) return p + (q - p) * (2 / 3.0 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
double q = (lightness < 0.5) ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
|
||||
double p = 2 * lightness - q;
|
||||
|
||||
r = hueToRgb(p, q, hue + 1 / 3.0);
|
||||
g = hueToRgb(p, q, hue);
|
||||
b = hueToRgb(p, q, hue - 1 / 3.0);
|
||||
}
|
||||
|
||||
return Color.FromArgb(
|
||||
(int)Math.Round(r * 255),
|
||||
(int)Math.Round(g * 255),
|
||||
(int)Math.Round(b * 255));
|
||||
}
|
||||
}
|
@ -1,14 +1,17 @@
|
||||
@using BTCPayServer.Services
|
||||
@using System.Drawing
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model StoreBrandingViewModel
|
||||
@inject ThemeSettings Theme
|
||||
@if (!string.IsNullOrEmpty(Model.BrandColor))
|
||||
{
|
||||
var hasCustomeTheme = Theme.CustomTheme && Theme.CustomThemeCssUrl is not null;
|
||||
const double thresholdLight = 1.5;
|
||||
const double thresholdDark = 2.5;
|
||||
var brand = Model.BrandColor;
|
||||
var brandColor = ColorPalette.Default.FromHtml(brand);
|
||||
var brandRgbValues = $"{brandColor.R}, {brandColor.G}, {brandColor.B}";
|
||||
var brightness = brandColor.GetBrightness();
|
||||
var bgLight = ColorPalette.Default.FromHtml("#F8F9FA");
|
||||
var bgDark = ColorPalette.Default.FromHtml("#0d1117");
|
||||
var brandColorAdjustedForLight = brandColor.GetAdjustedForegroundForBackground(bgLight, -.02, thresholdLight);
|
||||
var brandColorAdjustedForDark = brandColor.GetAdjustedForegroundForBackground(bgDark, .02, thresholdDark);
|
||||
var accent = ColorPalette.Default.AdjustBrightness(brand, (float)-.15);
|
||||
var accentColor = ColorPalette.Default.FromHtml(accent);
|
||||
var accentRgbValues = $"{accentColor.R}, {accentColor.G}, {accentColor.B}";
|
||||
@ -28,13 +31,12 @@
|
||||
--btcpay-primary-text-active: @complementVar;
|
||||
}
|
||||
</style>
|
||||
@if (brightness > .5 || (Theme.CustomThemeExtension == ThemeExtension.Dark && brightness < .5))
|
||||
@if (brandColorAdjustedForLight != brandColor)
|
||||
{
|
||||
var brandAdjusted = ColorPalette.Default.AdjustBrightness(brand, (float)(.35-brightness));
|
||||
var brandColorAdjusted = ColorPalette.Default.FromHtml(brandAdjusted);
|
||||
var brandRgbValuesAdjusted = $"{brandColorAdjusted.R}, {brandColorAdjusted.G}, {brandColorAdjusted.B}";
|
||||
var accentAdjusted = ColorPalette.Default.AdjustBrightness(brandAdjusted, (float)-.15);
|
||||
var accentColorAdjusted = ColorPalette.Default.FromHtml(accentAdjusted);
|
||||
var brandAdjusted = ColorPalette.Default.ToHtml(brandColorAdjustedForLight);
|
||||
var brandRgbValuesAdjusted = $"{brandColorAdjustedForLight.R}, {brandColorAdjustedForLight.G}, {brandColorAdjustedForLight.B}";
|
||||
var accentColorAdjusted = ColorPalette.Default.AdjustBrightness(brandColorAdjustedForLight, (float)-.15);
|
||||
var accentAdjusted = ColorPalette.Default.ToHtml(accentColorAdjusted);
|
||||
var accentRgbValuesAdjusted = $"{accentColorAdjusted.R}, {accentColorAdjusted.G}, {accentColorAdjusted.B}";
|
||||
var complementAdjusted = ColorPalette.Default.TextColor(brandAdjusted);
|
||||
var complementVarAdjusted = $"var(--btcpay-{(complementAdjusted == "black" ? "black" : "white")})";
|
||||
@ -68,16 +70,15 @@
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@if (brightness < .5 && (!hasCustomeTheme || Theme.CustomThemeExtension == ThemeExtension.Dark))
|
||||
@if (brandColorAdjustedForDark != brandColor)
|
||||
{
|
||||
var brandAdjusted = ColorPalette.Default.AdjustBrightness(brand, (float)(.5-brightness));
|
||||
var brandColorAdjusted = ColorPalette.Default.FromHtml(brandAdjusted);
|
||||
var brandRgbValuesAdjusted = $"{brandColorAdjusted.R}, {brandColorAdjusted.G}, {brandColorAdjusted.B}";
|
||||
var accentAdjusted = ColorPalette.Default.AdjustBrightness(brandAdjusted, (float).15);
|
||||
var accentColorAdjusted = ColorPalette.Default.FromHtml(accentAdjusted);
|
||||
var brandAdjusted = ColorPalette.Default.ToHtml(brandColorAdjustedForDark);
|
||||
var brandRgbValuesAdjusted = $"{brandColorAdjustedForDark.R}, {brandColorAdjustedForDark.G}, {brandColorAdjustedForDark.B}";
|
||||
var accentColorAdjusted = ColorPalette.Default.AdjustBrightness(brandColorAdjustedForDark, (float).15);
|
||||
var accentAdjusted = ColorPalette.Default.ToHtml(accentColorAdjusted);
|
||||
var accentRgbValuesAdjusted = $"{accentColorAdjusted.R}, {accentColorAdjusted.G}, {accentColorAdjusted.B}";
|
||||
var complementAdjusted = ColorPalette.Default.TextColor(brandAdjusted);
|
||||
var complementVarAdjusted = $"var(--btcpay-{(complementAdjusted == "black" ? "black" : "white")})";
|
||||
var complementAdjusted = ColorPalette.Default.TextColor(brandColorAdjustedForDark);
|
||||
var complementVarAdjusted = $"var(--btcpay-{(complementAdjusted == Color.Black ? "black" : "white")})";
|
||||
<style>
|
||||
:root[data-theme='dark'],
|
||||
:root[data-btcpay-theme='dark'] {
|
||||
|
Loading…
Reference in New Issue
Block a user