diff options
| -rw-r--r-- | src/caelestia/utils/material/__init__.py | 19 | ||||
| -rw-r--r-- | src/caelestia/utils/material/generator.py | 133 | ||||
| -rw-r--r-- | src/caelestia/utils/material/score.py | 71 | ||||
| -rw-r--r-- | src/caelestia/utils/scheme.py | 4 | ||||
| -rw-r--r-- | src/caelestia/utils/theme.py | 32 | ||||
| -rw-r--r-- | src/caelestia/utils/wallpaper.py | 5 |
6 files changed, 110 insertions, 154 deletions
diff --git a/src/caelestia/utils/material/__init__.py b/src/caelestia/utils/material/__init__.py index 447a952..b96849f 100644 --- a/src/caelestia/utils/material/__init__.py +++ b/src/caelestia/utils/material/__init__.py @@ -8,20 +8,18 @@ from caelestia.utils.material.score import score from caelestia.utils.paths import compute_hash, scheme_cache_dir, wallpaper_thumbnail_path -def get_score_for_image(image: Path | str, cache_base: Path) -> tuple[list[Hct], list[Hct]]: +def get_score_for_image(image: Path | str, cache_base: Path) -> Hct: cache = cache_base / "score.json" try: - with cache.open("r") as f: - return [[Hct.from_int(c) for c in li] for li in json.load(f)] - except (IOError, json.JSONDecodeError): + return Hct.from_int(cache.read_text()) + except (IOError, TypeError): pass s = score(str(image)) cache.parent.mkdir(parents=True, exist_ok=True) - with cache.open("w") as f: - json.dump([[c.to_int() for c in li] for li in s], f) + cache.write_text(str(s.to_int())) return s @@ -32,10 +30,8 @@ def get_colours_for_image(image: Path | str = wallpaper_thumbnail_path, scheme=N scheme = get_scheme() - flavour = scheme.flavour if scheme.flavour in ("default", "alt1", "alt2") else "default" - cache_base = scheme_cache_dir / compute_hash(image) - cache = (cache_base / scheme.variant / flavour / scheme.mode).with_suffix(".json") + cache = (cache_base / scheme.variant / scheme.mode).with_suffix(".json") try: with cache.open("r") as f: @@ -43,9 +39,8 @@ def get_colours_for_image(image: Path | str = wallpaper_thumbnail_path, scheme=N except (IOError, json.JSONDecodeError): pass - primaries, colours = get_score_for_image(image, cache_base) - i = ["default", "alt1", "alt2"].index(flavour) - scheme = gen_scheme(scheme, primaries[i], colours) + primary = get_score_for_image(image, cache_base) + scheme = gen_scheme(scheme, primary) cache.parent.mkdir(parents=True, exist_ok=True) with cache.open("w") as f: diff --git a/src/caelestia/utils/material/generator.py b/src/caelestia/utils/material/generator.py index 584d375..222bdd4 100644 --- a/src/caelestia/utils/material/generator.py +++ b/src/caelestia/utils/material/generator.py @@ -4,7 +4,6 @@ from materialyoucolor.dynamiccolor.material_dynamic_colors import ( MaterialDynamicColors, ) from materialyoucolor.hct import Hct -from materialyoucolor.hct.cam16 import Cam16 from materialyoucolor.scheme.scheme_content import SchemeContent from materialyoucolor.scheme.scheme_expressive import SchemeExpressive from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity @@ -14,13 +13,52 @@ from materialyoucolor.scheme.scheme_neutral import SchemeNeutral from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant +from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double def hex_to_hct(hex: str) -> Hct: return Hct.from_int(int(f"0xFF{hex}", 16)) -light_colours = [ +light_gruvbox = [ + hex_to_hct("FDF9F3"), + hex_to_hct("FF6188"), + hex_to_hct("A9DC76"), + hex_to_hct("FC9867"), + hex_to_hct("FFD866"), + hex_to_hct("F47FD4"), + hex_to_hct("78DCE8"), + hex_to_hct("333034"), + hex_to_hct("121212"), + hex_to_hct("FF6188"), + hex_to_hct("A9DC76"), + hex_to_hct("FC9867"), + hex_to_hct("FFD866"), + hex_to_hct("F47FD4"), + hex_to_hct("78DCE8"), + hex_to_hct("333034"), +] + +dark_gruvbox = [ + hex_to_hct("282828"), + hex_to_hct("CC241D"), + hex_to_hct("98971A"), + hex_to_hct("D79921"), + hex_to_hct("458588"), + hex_to_hct("B16286"), + hex_to_hct("689D6A"), + hex_to_hct("A89984"), + hex_to_hct("928374"), + hex_to_hct("FB4934"), + hex_to_hct("B8BB26"), + hex_to_hct("FABD2F"), + hex_to_hct("83A598"), + hex_to_hct("D3869B"), + hex_to_hct("8EC07C"), + hex_to_hct("EBDBB2"), +] + +light_catppuccin = [ hex_to_hct("dc8a78"), hex_to_hct("dd7878"), hex_to_hct("ea76cb"), @@ -37,7 +75,7 @@ light_colours = [ hex_to_hct("7287fd"), ] -dark_colours = [ +dark_catppuccin = [ hex_to_hct("f5e0dc"), hex_to_hct("f2cdcd"), hex_to_hct("f5c2e7"), @@ -69,8 +107,6 @@ colour_names = [ "sapphire", "blue", "lavender", - "success", - "error", ] @@ -84,8 +120,11 @@ def mix(a: Hct, b: Hct, w: float) -> Hct: return Hct.from_int(Blend.cam16_ucs(a.to_int(), b.to_int(), w)) -def harmonize(a: Hct, b: Hct) -> Hct: - return Hct.from_int(Blend.harmonize(a.to_int(), b.to_int())) +def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct: + difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue) + rotation_degrees = min(difference_degrees_ * 0.8, 100) + output_hue = sanitize_degrees_double(from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue)) + return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone * (1 + tone_boost)) def lighten(colour: Hct, amount: float) -> Hct: @@ -98,39 +137,6 @@ def darken(colour: Hct, amount: float) -> Hct: return Hct.from_hct(colour.hue, colour.chroma + diff / 5, colour.tone - diff) -def distance(colour: Cam16, base: Cam16) -> float: - return colour.distance(base) - - -def smart_sort(colours: list[Hct], base: list[Hct]) -> dict[str, Hct]: - sorted_colours = [None] * len(colours) - distances = {} - - cams = [(c, Cam16.from_int(c.to_int())) for c in colours] - base_cams = [Cam16.from_int(c.to_int()) for c in base] - - for colour, cam in cams: - dist = [(i, distance(cam, b)) for i, b in enumerate(base_cams)] - dist.sort(key=lambda x: x[1]) - distances[colour] = dist - - for colour in colours: - while len(distances[colour]) > 0: - i, dist = distances[colour][0] - - if sorted_colours[i] is None: - sorted_colours[i] = colour, dist - break - elif sorted_colours[i][1] > dist: - old = sorted_colours[i][0] - sorted_colours[i] = colour, dist - colour = old - - distances[colour].pop(0) - - return {colour_names[i]: c[0] for i, c in enumerate(sorted_colours)} - - def get_scheme(scheme: str) -> DynamicScheme: if scheme == "content": return SchemeContent @@ -151,20 +157,10 @@ def get_scheme(scheme: str) -> DynamicScheme: return SchemeVibrant -def gen_scheme(scheme, primary: Hct, colours: list[Hct]) -> dict[str, str]: +def gen_scheme(scheme, primary: Hct) -> dict[str, str]: light = scheme.mode == "light" - base = light_colours if light else dark_colours - - # Sort colours and turn into dict - colours = smart_sort(colours, base) - # Harmonize colours - for name, hct in colours.items(): - if scheme.variant == "monochrome": - colours[name] = grayscale(hct, light) - else: - harmonized = harmonize(hct, primary) - colours[name] = darken(harmonized, 0.35) if light else lighten(harmonized, 0.65) + colours = {} # Material colours primary_scheme = get_scheme(scheme.variant)(primary, not light, 0) @@ -173,6 +169,22 @@ def gen_scheme(scheme, primary: Hct, colours: list[Hct]) -> dict[str, str]: if hasattr(colour_name, "get_hct"): colours[colour] = colour_name.get_hct(primary_scheme) + # Harmonize terminal colours + for i, hct in enumerate(light_gruvbox if light else dark_gruvbox): + if scheme.variant == "monochrome": + colours[f"term{i}"] = grayscale(hct, light) + else: + colours[f"term{i}"] = harmonize( + hct, colours["primary_paletteKeyColor"], (0.35 if i < 8 else 0.2) * (-1 if light else 1) + ) + + # Harmonize named colours + for i, hct in enumerate(light_catppuccin if light else dark_catppuccin): + if scheme.variant == "monochrome": + colours[colour_names[i]] = grayscale(hct, light) + else: + colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if light else 0.05)) + # FIXME: deprecated stuff colours["text"] = colours["onBackground"] colours["subtext1"] = colours["onSurfaceVariant"] @@ -186,9 +198,22 @@ def gen_scheme(scheme, primary: Hct, colours: list[Hct]) -> dict[str, str]: colours["base"] = colours["surface"] colours["mantle"] = darken(colours["surface"], 0.03) colours["crust"] = darken(colours["surface"], 0.05) - colours["success"] = harmonize(base[8], primary) # For debugging # print("\n".join(["{}: \x1b[48;2;{};{};{}m \x1b[0m".format(n, *c.to_rgba()[:3]) for n, c in colours.items()])) - return {k: hex(v.to_int())[4:] for k, v in colours.items()} + colours = {k: hex(v.to_int())[4:] for k, v in colours.items()} + + # Extended material + if light: + colours["success"] = "4F6354" + colours["onSuccess"] = "FFFFFF" + colours["successContainer"] = "D1E8D5" + colours["onSuccessContainer"] = "0C1F13" + else: + colours["success"] = "B5CCBA" + colours["onSuccess"] = "213528" + colours["successContainer"] = "374B3E" + colours["onSuccessContainer"] = "D1E9D6" + + return colours diff --git a/src/caelestia/utils/material/score.py b/src/caelestia/utils/material/score.py index 6599b90..a6126a6 100644 --- a/src/caelestia/utils/material/score.py +++ b/src/caelestia/utils/material/score.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -import sys from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer from materialyoucolor.hct import Hct from materialyoucolor.quantize import ImageQuantizeCelebi -from materialyoucolor.utils.math_utils import difference_degrees, sanitize_degrees_int +from materialyoucolor.utils.math_utils import sanitize_degrees_int class Score: @@ -20,10 +19,7 @@ class Score: pass @staticmethod - def score(colors_to_population: dict, filter_enabled: bool = False) -> tuple[list[Hct], list[Hct]]: - desired = 14 - dislike_filter = True - + def score(colors_to_population: dict, filter_enabled: bool = False) -> Hct: colors_hct = [] hue_population = [0] * 360 population_sum = 0 @@ -60,22 +56,6 @@ class Score: scored_hct.sort(key=lambda x: x["score"], reverse=True) - # Choose distinct colours - chosen_colors = [] - for difference_degrees_ in range(90, -1, -1): - chosen_colors.clear() - for item in scored_hct: - hct = item["hct"] - duplicate_hue = any( - difference_degrees(hct.hue, chosen_hct.hue) < difference_degrees_ for chosen_hct in chosen_colors - ) - if not duplicate_hue: - chosen_colors.append(hct) - if len(chosen_colors) >= desired: - break - if len(chosen_colors) >= desired: - break - # Get primary colour primary = None for cutoff in range(20, -1, -1): @@ -86,51 +66,8 @@ class Score: if primary: break - # Ensure primary exists - if not primary: - return Score.score(colors_to_population, False) + return DislikeAnalyzer.fix_if_disliked(primary) if primary else Score.score(colors_to_population, False) - # Choose distinct primaries - chosen_primaries = [primary] - for difference_degrees_ in range(90, 14, -1): - chosen_primaries = [primary] - for item in scored_hct: - hct = item["hct"] - duplicate_hue = any( - difference_degrees(hct.hue, chosen_hct.hue) < difference_degrees_ for chosen_hct in chosen_primaries - ) - if not duplicate_hue: - chosen_primaries.append(hct) - if len(chosen_primaries) >= 3: - break - if len(chosen_primaries) >= 3: - break - # Fix disliked colours - if dislike_filter: - for i, chosen_hct in enumerate(chosen_primaries): - chosen_primaries[i] = DislikeAnalyzer.fix_if_disliked(chosen_hct) - for i, chosen_hct in enumerate(chosen_colors): - chosen_colors[i] = DislikeAnalyzer.fix_if_disliked(chosen_hct) - - # Ensure enough colours - if len(chosen_colors) < desired: - return Score.score(colors_to_population, False) - - return chosen_primaries, chosen_colors - - -def score(image: str) -> tuple[list[Hct], list[Hct]]: +def score(image: str) -> Hct: return Score.score(ImageQuantizeCelebi(image, 1, 128)) - - -if __name__ == "__main__": - img = sys.argv[1] - mode = sys.argv[2] if len(sys.argv) > 2 else "hex" - - colours = Score.score(ImageQuantizeCelebi(img, 1, 128)) - for t in colours: - if mode != "hex": - print("".join(["\x1b[48;2;{};{};{}m \x1b[0m".format(*c.to_rgba()[:3]) for c in t])) - if mode != "swatch": - print(" ".join(["{:02X}{:02X}{:02X}".format(*c.to_rgba()[:3]) for c in t])) diff --git a/src/caelestia/utils/scheme.py b/src/caelestia/utils/scheme.py index 1e95376..e096436 100644 --- a/src/caelestia/utils/scheme.py +++ b/src/caelestia/utils/scheme.py @@ -238,7 +238,7 @@ def get_scheme_names() -> list[str]: def get_scheme_flavours(name: str = None) -> list[str]: if name is not None: if name == "dynamic": - return ["default", "alt1", "alt2"] + return ["default"] else: return [f.name for f in (scheme_data_dir / name).iterdir() if f.is_dir()] @@ -247,7 +247,7 @@ def get_scheme_flavours(name: str = None) -> list[str]: if scheme_flavours is None: name = get_scheme().name if name == "dynamic": - scheme_flavours = ["default", "alt1", "alt2"] + scheme_flavours = ["default"] else: scheme_flavours = [f.name for f in (scheme_data_dir / name).iterdir() if f.is_dir()] diff --git a/src/caelestia/utils/theme.py b/src/caelestia/utils/theme.py index 95f83c6..2e5521e 100644 --- a/src/caelestia/utils/theme.py +++ b/src/caelestia/utils/theme.py @@ -47,22 +47,22 @@ def gen_sequences(colours: dict[str, str]) -> str: + c2s(colours["surface"], 11) + c2s(colours["secondary"], 12) + c2s(colours["secondary"], 17) - + c2s(colours["surfaceContainer"], 4, 0) - + c2s(colours["red"], 4, 1) - + c2s(colours["green"], 4, 2) - + c2s(colours["yellow"], 4, 3) - + c2s(colours["blue"], 4, 4) - + c2s(colours["pink"], 4, 5) - + c2s(colours["teal"], 4, 6) - + c2s(colours["onSurfaceVariant"], 4, 7) - + c2s(colours["outline"], 4, 8) - + c2s(colours["red"], 4, 9) - + c2s(colours["green"], 4, 10) - + c2s(colours["yellow"], 4, 11) - + c2s(colours["blue"], 4, 12) - + c2s(colours["pink"], 4, 13) - + c2s(colours["teal"], 4, 14) - + c2s(colours["onSurface"], 4, 15) + + c2s(colours["term0"], 4, 0) + + c2s(colours["term1"], 4, 1) + + c2s(colours["term2"], 4, 2) + + c2s(colours["term3"], 4, 3) + + c2s(colours["term4"], 4, 4) + + c2s(colours["term5"], 4, 5) + + c2s(colours["term6"], 4, 6) + + c2s(colours["term7"], 4, 7) + + c2s(colours["term8"], 4, 8) + + c2s(colours["term9"], 4, 9) + + c2s(colours["term10"], 4, 10) + + c2s(colours["term11"], 4, 11) + + c2s(colours["term12"], 4, 12) + + c2s(colours["term13"], 4, 13) + + c2s(colours["term14"], 4, 14) + + c2s(colours["term15"], 4, 15) + c2s(colours["primary"], 4, 16) + c2s(colours["secondary"], 4, 17) + c2s(colours["tertiary"], 4, 18) diff --git a/src/caelestia/utils/wallpaper.py b/src/caelestia/utils/wallpaper.py index e9453e8..300d746 100644 --- a/src/caelestia/utils/wallpaper.py +++ b/src/caelestia/utils/wallpaper.py @@ -92,13 +92,12 @@ def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None: cache = wallpapers_cache_dir / compute_hash(wall) name = "dynamic" - flavour = scheme.flavour if scheme.flavour in ("default", "alt1", "alt2") else "default" if not no_smart: scheme = Scheme( { "name": name, - "flavour": flavour, + "flavour": "default", "mode": get_smart_mode(wall, cache), "variant": scheme.variant, "colours": scheme.colours, @@ -107,7 +106,7 @@ def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None: return { "name": name, - "flavour": flavour, + "flavour": "default", "mode": scheme.mode, "variant": scheme.variant, "colours": get_colours_for_image(get_thumb(wall, cache), scheme), |