From ce6fdf0ab9e113f02fd41744e2176d5c3b538c48 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:49:44 +1000 Subject: feat: up to 3 options for dynamic scheme Have up to 3 options for base colour for dynamic scheme Variants moved to new command Also remove parallel dependency --- README.md | 1 + completions/caelestia.fish | 7 +++- install/scripts.fish | 2 +- main.fish | 13 ++++++++ scheme/autoadjust.py | 80 ++++++++++++++++++++++++++++------------------ scheme/gen-scheme.fish | 37 +++++++++++---------- scheme/score.py | 59 ++++++++++++++++++++++------------ wallpaper.fish | 23 ++++--------- 8 files changed, 134 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index f10900a..3dfd494 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ COMMAND := help | install | shell | toggle | workspace-action | scheme | screens toggle: toggle a special workspace workspace-action: execute a Hyprland workspace dispatcher in the current group scheme: change the current colour scheme + variant: change the current scheme variant screenshot: take a screenshot record: take a screen recording clipboard: open clipboard history diff --git a/completions/caelestia.fish b/completions/caelestia.fish index 26c983c..90f3314 100644 --- a/completions/caelestia.fish +++ b/completions/caelestia.fish @@ -1,6 +1,6 @@ set -l seen '__fish_seen_subcommand_from' set -l has_opt '__fish_contains_opt' -set -l commands help install shell toggle workspace-action scheme screenshot record clipboard clipboard-delete emoji-picker wallpaper pip +set -l commands help install shell toggle workspace-action scheme variant screenshot record clipboard clipboard-delete emoji-picker wallpaper pip set -l not_seen "not $seen $commands" # Disable file completions @@ -13,6 +13,7 @@ complete -c caelestia -n $not_seen -a 'shell' -d 'Start the shell or message it' complete -c caelestia -n $not_seen -a 'toggle' -d 'Toggle a special workspace' complete -c caelestia -n $not_seen -a 'workspace-action' -d 'Exec a dispatcher in the current group' complete -c caelestia -n $not_seen -a 'scheme' -d 'Switch the current colour scheme' +complete -c caelestia -n $not_seen -a 'variant' -d 'Switch the current scheme variant' complete -c caelestia -n $not_seen -a 'screenshot' -d 'Take a screenshot' complete -c caelestia -n $not_seen -a 'record' -d 'Take a screen recording' complete -c caelestia -n $not_seen -a 'clipboard' -d 'Open clipboard history' @@ -69,6 +70,10 @@ for scheme in $commands end end +# Variant +set -l commands vibrant tonalspot expressive fidelity fruitsalad rainbow neutral content monochrome +complete -c caelestia -n "$seen variant && not $seen $commands" -a "$commands" + # Record set -l not_seen "$seen record && not $has_opt -s h help" complete -c caelestia -n "$not_seen && not $has_opt -s s sound && not $has_opt -s r region && not $has_opt -s c compression && not $has_opt -s H hwaccel" \ diff --git a/install/scripts.fish b/install/scripts.fish index 5041ff3..8da6221 100755 --- a/install/scripts.fish +++ b/install/scripts.fish @@ -2,7 +2,7 @@ . (dirname (status filename))/util.fish -install-deps git hyprland-git hyprpaper-git imagemagick wl-clipboard fuzzel-git socat foot jq python xdg-user-dirs python-materialyoucolor-git app2unit-git parallel +install-deps git hyprland-git hyprpaper-git imagemagick wl-clipboard fuzzel-git socat foot jq python xdg-user-dirs python-materialyoucolor-git app2unit-git install-optional-deps 'equibop-bin (discord client)' 'btop (system monitor)' 'wf-recorder (screen recorder)' 'grim (screenshot tool)' 'zen-browser (web browser)' 'spotify-adblock (music player)' set -l dist $C_DATA/scripts diff --git a/main.fish b/main.fish index 0c572ba..0ae2863 100755 --- a/main.fish +++ b/main.fish @@ -44,6 +44,18 @@ if test "$argv[1]" = scheme exit end +if test "$argv[1]" = variant + set -l variants vibrant tonalspot expressive fidelity fruitsalad rainbow neutral content monochrome + if contains -- "$argv[2]" $variants + echo -n $argv[2] > $C_STATE/scheme/current-variant.txt + $src/scheme/gen-scheme.fish + else + error "Invalid variant: $argv[2]" + end + + exit +end + if test "$argv[1]" = install set -l valid_modules scripts btop discord firefox fish foot fuzzel hypr safeeyes shell slurp spicetify gtk qt vscode if test "$argv[2]" = all @@ -76,6 +88,7 @@ echo ' shell: start the shell or message it' echo ' toggle: toggle a special workspace' echo ' workspace-action: execute a Hyprland workspace dispatcher in the current group' echo ' scheme: change the current colour scheme' +echo ' variant: change the current scheme variant' echo ' screenshot: take a screenshot' echo ' record: take a screen recording' echo ' clipboard: open clipboard history' diff --git a/scheme/autoadjust.py b/scheme/autoadjust.py index dd007ab..7ee28d7 100755 --- a/scheme/autoadjust.py +++ b/scheme/autoadjust.py @@ -2,6 +2,7 @@ import sys from colorsys import hls_to_rgb, rgb_to_hls +from pathlib import Path from materialyoucolor.dynamiccolor.material_dynamic_colors import ( DynamicScheme, @@ -17,7 +18,6 @@ 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.color_utils import argb_from_rgb light_colours = [ "dc8a78", @@ -101,7 +101,9 @@ def grayscale(hls: HLS, light: bool) -> HLS: def mix(a: HLS, b: HLS, w: float) -> HLS: r1, g1, b1 = hls_to_rgb(*a) r2, g2, b2 = hls_to_rgb(*b) - return rgb_to_hls(r1 * (1 - w) + r2 * w, g1 * (1 - w) + g2 * w, b1 * (1 - w) + b2 * w) + return rgb_to_hls( + r1 * (1 - w) + r2 * w, g1 * (1 - w) + g2 * w, b1 * (1 - w) + b2 * w + ) def darken(colour: HLS, amount: float) -> HLS: @@ -161,16 +163,23 @@ def get_scheme(scheme: str) -> DynamicScheme: return SchemeVibrant +def get_alt(i: int) -> str: + names = ["default", "alt1", "alt2"] + return names[i] + + if __name__ == "__main__": light = sys.argv[1] == "light" scheme = sys.argv[2] - colours_in = sys.argv[3].split(" ") + primaries = sys.argv[3].split(" ") + colours_in = sys.argv[4].split(" ") + out_path = sys.argv[5] base = light_colours if light else dark_colours chroma_mult = 1.5 if light else 1.2 # Convert to HLS - colours = [hex_to_hls(c) for c in colours_in[1:]] + colours = [hex_to_hls(c) for c in colours_in] # Sort colours and turn into dict colours = smart_sort(colours, base) @@ -192,36 +201,45 @@ if __name__ == "__main__": colour.chroma *= chroma_mult - colours[name] = hex_to_hls("{:02X}{:02X}{:02X}".format(*colour.to_rgba()[:3])) + colours[name] = hex_to_hls( + "{:02X}{:02X}{:02X}".format(*colour.to_rgba()[:3]) + ) # Success and error colours colours["success"] = mix(colours["green"], hex_to_hls(base[8]), 0.8) colours["error"] = mix(colours["red"], hex_to_hls(base[4]), 0.8) # Layers and accents - material = {} - primary_scheme = MatScheme(Hct.from_int(int(f"0xFF{colours_in[0]}", 16)), not light, 0) - for colour in vars(MaterialDynamicColors).keys(): - colour_name = getattr(MaterialDynamicColors, colour) - if hasattr(colour_name, "get_hct"): - rgb = colour_name.get_hct(primary_scheme).to_rgba()[:3] - material[colour] = hex_to_hls("{:02X}{:02X}{:02X}".format(*rgb)) - - colours["primary"] = material["primary"] - colours["secondary"] = material["secondary"] - colours["tertiary"] = material["tertiary"] - colours["text"] = material["onBackground"] - colours["subtext1"] = material["onSurfaceVariant"] - colours["subtext0"] = material["outline"] - colours["overlay2"] = mix(material["surface"], material["outline"], 0.86) - colours["overlay1"] = mix(material["surface"], material["outline"], 0.71) - colours["overlay0"] = mix(material["surface"], material["outline"], 0.57) - colours["surface2"] = mix(material["surface"], material["outline"], 0.43) - colours["surface1"] = mix(material["surface"], material["outline"], 0.29) - colours["surface0"] = mix(material["surface"], material["outline"], 0.14) - colours["base"] = material["surface"] - colours["mantle"] = darken(material["surface"], 0.03) - colours["crust"] = darken(material["surface"], 0.05) - - for name, colour in colours.items(): - print(f"{name} {hls_to_hex(*colour)}") + for i, primary in enumerate(primaries): + material = {} + primary_scheme = MatScheme( + Hct.from_int(int(f"0xFF{primary}", 16)), not light, 0 + ) + for colour in vars(MaterialDynamicColors).keys(): + colour_name = getattr(MaterialDynamicColors, colour) + if hasattr(colour_name, "get_hct"): + rgb = colour_name.get_hct(primary_scheme).to_rgba()[:3] + material[colour] = hex_to_hls("{:02X}{:02X}{:02X}".format(*rgb)) + + colours["primary"] = material["primary"] + colours["secondary"] = material["secondary"] + colours["tertiary"] = material["tertiary"] + colours["text"] = material["onBackground"] + colours["subtext1"] = material["onSurfaceVariant"] + colours["subtext0"] = material["outline"] + colours["overlay2"] = mix(material["surface"], material["outline"], 0.86) + colours["overlay1"] = mix(material["surface"], material["outline"], 0.71) + colours["overlay0"] = mix(material["surface"], material["outline"], 0.57) + colours["surface2"] = mix(material["surface"], material["outline"], 0.43) + colours["surface1"] = mix(material["surface"], material["outline"], 0.29) + colours["surface0"] = mix(material["surface"], material["outline"], 0.14) + colours["base"] = material["surface"] + colours["mantle"] = darken(material["surface"], 0.03) + colours["crust"] = darken(material["surface"], 0.05) + + out_file = Path(f"{out_path}/{scheme}/{get_alt(i)}/{sys.argv[1]}.txt") + out_file.parent.mkdir(parents=True, exist_ok=True) + colour_arr = [ + f"{name} {hls_to_hex(*colour)}" for name, colour in colours.items() + ] + out_file.write_text("\n".join(colour_arr)) diff --git a/scheme/gen-scheme.fish b/scheme/gen-scheme.fish index c034980..488ee27 100755 --- a/scheme/gen-scheme.fish +++ b/scheme/gen-scheme.fish @@ -5,30 +5,29 @@ set -l src (dirname (status filename)) . $src/../util.fish test -f "$argv[1]" && set -l img (realpath "$argv[1]") || set -l img $C_STATE/wallpaper/thumbnail.jpg -contains -- "$argv[2]" light dark && set -l theme $argv[2] || set -l theme dark set -l variants vibrant tonalspot expressive fidelity fruitsalad rainbow neutral content monochrome -set -l hash (sha1sum $img | cut -d ' ' -f 1) +contains -- "$argv[2]" $variants && set -l variant $argv[2] || set -l variant (cat $C_STATE/scheme/current-variant.txt 2> /dev/null) +contains -- "$variant" $variants || set -l variant tonalspot -# Cache schemes -mkdir -p $C_CACHE/schemes -set -l dirty_variants -if test -d $C_CACHE/schemes/$hash - for variant in $variants - test -f $C_CACHE/schemes/$hash/$variant/$theme.txt || set -a dirty_variants $variant - end -else - set dirty_variants $variants -end +set -l hash (sha1sum $img | cut -d ' ' -f 1) -if test -n "$dirty_variants" - # Generate schemes for variants that need it +# Cache scheme +if ! test -d $C_CACHE/schemes/$hash/$variant set -l colours ($src/score.py $img) - parallel "mkdir -p $C_CACHE/schemes/$hash/{} && $src/autoadjust.py $theme {} '$colours' | head -c -1 > $C_CACHE/schemes/$hash/{}/$theme.txt" ::: $dirty_variants + $src/autoadjust.py dark $variant $colours $C_CACHE/schemes/$hash + $src/autoadjust.py light $variant $colours $C_CACHE/schemes/$hash end -# Copy schemes from cache -for variant in $variants - mkdir -p $src/../data/schemes/dynamic/$variant - cp $C_CACHE/schemes/$hash/$variant/$theme.txt $src/../data/schemes/dynamic/$variant/$theme.txt +# Copy scheme from cache +rm -rf $src/../data/schemes/dynamic +cp -r $C_CACHE/schemes/$hash/$variant $src/../data/schemes/dynamic + +# Update if current +set -l variant (string match -gr 'dynamic-(.*)' (cat $C_STATE/scheme/current-name.txt 2> /dev/null)) +if test -n "$variant" + # If variant doesn't exist, use default + test -d $src/../data/schemes/dynamic/$variant || set -l variant default + # Apply scheme + $src/main.fish dynamic $variant $MODE > /dev/null end diff --git a/scheme/score.py b/scheme/score.py index 531046f..92ebbb7 100755 --- a/scheme/score.py +++ b/scheme/score.py @@ -2,10 +2,10 @@ import sys -from materialyoucolor.quantize import ImageQuantizeCelebi -from materialyoucolor.hct import Hct -from materialyoucolor.utils.math_utils import sanitize_degrees_int, difference_degrees 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 class Score: @@ -20,7 +20,7 @@ class Score: pass @staticmethod - def score(colors_to_population: dict) -> list[int]: + def score(colors_to_population: dict) -> tuple[list[Hct], list[Hct]]: desired = 14 filter_enabled = False dislike_filter = True @@ -72,7 +72,8 @@ class Score: chosen_colors = [] for difference_degrees_ in range(90, 0, -1): chosen_colors.clear() - for hct in [item["hct"] for item in scored_hct]: + 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 @@ -85,31 +86,49 @@ class Score: break # Get primary colour - got_primary = False + primary = None for cutoff in range(20, 0, -1): for item in scored_hct: if item["hct"].chroma > cutoff and item["hct"].tone > cutoff * 3: - chosen_colors.insert(0, item["hct"]) - got_primary = True + primary = item["hct"] + break + if primary: + break + + # 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 got_primary: + if len(chosen_primaries) >= 3: break # Fix disliked colours if dislike_filter: - for chosen_hct in chosen_colors: - chosen_colors[chosen_colors.index(chosen_hct)] = ( - DislikeAnalyzer.fix_if_disliked(chosen_hct) - ) + 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) - return chosen_colors + return chosen_primaries, chosen_colors if __name__ == "__main__": img = sys.argv[1] - - colours = ImageQuantizeCelebi(img, 1, 128) - colours = [c.to_rgba()[:3] for c in Score.score(colours)] - - # print("".join(["\x1b[48;2;{};{};{}m \x1b[0m".format(*colour) for colour in colours])) - print(" ".join(["{:02X}{:02X}{:02X}".format(*colour) for colour in colours])) + mode = sys.argv[2] if len(sys.argv) > 2 else "hex" + + colours = Score.score(ImageQuantizeCelebi(img, 1, 128)) + for l in colours: + if mode != "hex": + print("".join(["\x1b[48;2;{};{};{}m \x1b[0m".format(*c.to_rgba()[:3]) for c in l])) + if mode != "swatch": + print(" ".join(["{:02X}{:02X}{:02X}".format(*c.to_rgba()[:3]) for c in l])) diff --git a/wallpaper.fish b/wallpaper.fish index 37563bc..d41add3 100755 --- a/wallpaper.fish +++ b/wallpaper.fish @@ -114,24 +114,15 @@ else end cp $thumb_path $state_dir/thumbnail.jpg + # Light/dark mode detection if not specified + if ! set -q _flag_T + set -l lightness (magick $state_dir/thumbnail.jpg -format '%[fx:int(mean*100)]' info:) + test $lightness -ge 60 && set _flag_T light || set _flag_T dark + end + # Generate colour scheme for wallpaper set -l src (dirname (status filename)) - $src/scheme/gen-scheme.fish $state_dir/thumbnail.jpg dark & - $src/scheme/gen-scheme.fish $state_dir/thumbnail.jpg light & - if test -f $C_STATE/scheme/current-name.txt - set -l variant (string match -gr 'dynamic-(.*)' (cat $C_STATE/scheme/current-name.txt)) - if test -n "$variant" - # Light/dark mode detection if not specified - if ! set -q _flag_T - set -l lightness (magick $state_dir/thumbnail.jpg -format '%[fx:int(mean*100)]' info:) - test $lightness -ge 60 && set _flag_T light || set _flag_T dark - end - # Wait for async scheme gen to finish - wait - # Apply scheme - caelestia scheme dynamic $variant $_flag_T > /dev/null - end - end + MODE=$_flag_T $src/scheme/gen-scheme.fish & # Store the wallpaper chosen mkdir -p $state_dir -- cgit v1.2.3-freya