diff --git a/core/palette.py b/core/palette.py index 4ab5177942ec95cef0df00f707257e2ad68ec4a9..6398e9812dadc93cb25b8fbc16e8e274a09f2359 100644 --- a/core/palette.py +++ b/core/palette.py @@ -7,6 +7,7 @@ # Python modules import itertools +from typing import Tuple # Material 2014 color scheme from # https://material.io/design/color/the-color-system.html#tools-for-picking-colors @@ -328,6 +329,8 @@ BW = [ "#FFFFFF", # White ] +FG = BW # Foreground + COLOR_PALETTES = [ RED50, PINK50, @@ -370,3 +373,34 @@ def get_avatar_bg_color(n: int) -> str: # i = n * _AC_PRIME % len(AVATAR_COLORS) return AVATAR_COLORS[i] + + +def split_rgb(color: str) -> Tuple[int, int, int]: + """ + Split color #RRGGBB to a tuple of (R, G, B) + :param color: + :return: + """ + return int(color[1:3], 16), int(color[3:5], 16), int(color[5:], 16) + + +def get_fg_color(color: str) -> str: + """ + Return contrast foreground color + :param color: + :return: + """ + + def distance(c1: Tuple[int, int, int], c2: Tuple[int, int, int]): + return abs(c1[0] - c2[0]) + abs(c1[1] - c2[1]) + abs(c1[2] - c2[2]) + + bg_rgb = split_rgb(color) + best_fg = FG[0] + best_distance = distance(split_rgb(best_fg), bg_rgb) + for fg in FG[1:]: + dist = distance(split_rgb(fg), bg_rgb) + if dist <= best_distance: + continue + best_fg = fg + best_distance = dist + return best_fg diff --git a/services/ui/models/me.py b/services/ui/models/me.py index 5fb22e6e5ca6aa75ff2f0aab871ba163acb8e961..072dc27b506f1980317ccba31d20c57294b5ae91 100644 --- a/services/ui/models/me.py +++ b/services/ui/models/me.py @@ -33,6 +33,10 @@ class MeResponse(BaseModel): avatar_label: str = Field( title="Avatar Label", description="Letters to be used when avatar is missed or empty" ) + avatar_label_fg: str = Field( + title="Avatar Label Foreground", + description="CSS background to be used along with avatar_label", + ) avatar_label_bg: str = Field( title="Avatar Label Background", description="CSS background to be used along with avatar_label", diff --git a/services/ui/paths/me.py b/services/ui/paths/me.py index 73ac8e75821bcb1bbfbf900ff68a9f81508351dd..e6d6a705ceeba19e0e94e046604bdbe6c2b04982 100644 --- a/services/ui/paths/me.py +++ b/services/ui/paths/me.py @@ -12,7 +12,7 @@ from fastapi import APIRouter, Depends from noc.config import config from noc.aaa.models.user import User from noc.core.service.deps.user import get_current_user -from noc.core.palette import get_avatar_bg_color +from noc.core.palette import get_avatar_bg_color, get_fg_color from ..models.me import MeResponse, GroupItem router = APIRouter() @@ -20,6 +20,7 @@ router = APIRouter() @router.get("/api/ui/me", response_model=MeResponse, tags=["ui"]) def get_me(user: User = Depends(get_current_user)): + label_bg = get_avatar_bg_color(user.id) return MeResponse( id=str(user.id), username=user.username, @@ -30,5 +31,6 @@ def get_me(user: User = Depends(get_current_user)): language=user.preferred_language or config.language, avatar_url=user.avatar_url, avatar_label=user.avatar_label, - avatar_label_bg=get_avatar_bg_color(user.id), + avatar_label_fg=get_fg_color(label_bg), + avatar_label_bg=label_bg, ) diff --git a/tests/test_palette.py b/tests/test_palette.py index d7fbfe1eb7ac40715d0230bcc2e33494de4bab0b..253d6f9ebfddc9f9b484b5c4bc6839cc098fef5d 100644 --- a/tests/test_palette.py +++ b/tests/test_palette.py @@ -6,7 +6,7 @@ # ---------------------------------------------------------------------- # Python modules -from typing import Set, List +from typing import Set, List, Tuple import re # Third-party modules @@ -18,8 +18,11 @@ from noc.core.palette import ( COLOR_PALETTES, TONE_PALETTES, BW, + FG, AVATAR_COLORS, get_avatar_bg_color, + split_rgb, + get_fg_color, ) ALL_COLORS: Set[str] = set() @@ -62,3 +65,21 @@ def test_get_avatar_bg_color(): # Check repeatability round1r = [get_avatar_bg_color(i) for i in range(1, n + 1)] assert round1 == round1r + + +@pytest.mark.parametrize("color", ALL_COLORS) +def test_split_rgb(color: str): + r, g, b = split_rgb(color) + assert color[0] == "#" + assert color[1:3] == "%02X" % r + assert color[3:5] == "%02X" % g + assert color[5:] == "%02X" % b + + +@pytest.mark.parametrize("color", [c for c in ALL_COLORS if c not in FG]) +def test_get_fg_color(color: str): + def distance(c1: Tuple[int, int, int], c2: Tuple[int, int, int]): + return abs(c1[0] - c2[0]) + abs(c1[1] - c2[1]) + abs(c1[2] - c2[2]) + + fg = get_fg_color(color) + assert distance(split_rgb(color), split_rgb(fg)) >= 3 * 128