diff options
| -rwxr-xr-x | scheme/autoadjust.py | 53 | ||||
| -rwxr-xr-x | scheme/gen-scheme.fish | 48 | ||||
| -rwxr-xr-x | scheme/genmaterial.py | 109 | ||||
| -rwxr-xr-x | scheme/main.fish | 2 | ||||
| -rwxr-xr-x | wallpaper.fish | 4 |
5 files changed, 157 insertions, 59 deletions
diff --git a/scheme/autoadjust.py b/scheme/autoadjust.py index 991ac7e..03ca248 100755 --- a/scheme/autoadjust.py +++ b/scheme/autoadjust.py @@ -1,8 +1,8 @@ #!/bin/python3 import sys +from colorsys import hls_to_rgb, rgb_to_hls from math import sqrt -from colorsys import rgb_to_hls, hls_to_rgb light_colours = [ "dc8a78", @@ -38,27 +38,50 @@ dark_colours = [ "b4befe", ] -def hex_to_rgb(hex: str) -> tuple[int, int, int]: +colour_names = [ + "rosewater", + "flamingo", + "pink", + "mauve", + "red", + "maroon", + "peach", + "yellow", + "green", + "teal", + "sky", + "sapphire", + "blue", + "lavender", +] + + +def hex_to_rgb(hex: str) -> tuple[float, float, float]: """Convert a hex string to an RGB tuple in the range [0, 1].""" - return tuple(int(hex[i:i+2], 16) / 255 for i in (0, 2, 4)) + return tuple(int(hex[i : i + 2], 16) / 255 for i in (0, 2, 4)) + -def rgb_to_hex(rgb: tuple[int, int, int]) -> str: +def rgb_to_hex(rgb: tuple[float, float, float]) -> str: """Convert an RGB tuple in the range [0, 1] to a hex string.""" return "".join(f"{round(i * 255):02X}" for i in rgb) + def hex_to_hls(hex: str) -> tuple[float, float, float]: return rgb_to_hls(*hex_to_rgb(hex)) + def adjust(hex: str, light: float = 0, sat: float = 0) -> str: h, l, s = hex_to_hls(hex) l = max(0, min(1, l + light)) s = max(0, min(1, s + sat)) return rgb_to_hex(hls_to_rgb(h, l, s)) + def distance(colour: str, base: str) -> float: r1, g1, b1 = hex_to_rgb(colour) r2, g2, b2 = hex_to_rgb(base) - return sqrt((r2 - r1)**2 + (g2 - g1)**2 + (b2 - b1)**2) + return sqrt((r2 - r1) ** 2 + (g2 - g1) ** 2 + (b2 - b1) ** 2) + def smart_sort(colours: list[str], base: list[str]) -> list[str]: sorted_colours = [None] * len(colours) @@ -85,15 +108,20 @@ def smart_sort(colours: list[str], base: list[str]) -> list[str]: return [i[0] for i in sorted_colours] + def mix(a: str, b: str, w: float) -> str: r1, g1, b1 = hex_to_rgb(a) r2, g2, b2 = hex_to_rgb(b) - return rgb_to_hex((r1 * (1 - w) + r2 * w, g1 * (1 - w) + g2 * w, b1 * (1 - w) + b2 * w)) + return rgb_to_hex( + (r1 * (1 - w) + r2 * w, g1 * (1 - w) + g2 * w, b1 * (1 - w) + b2 * w) + ) + def mix_colours(colours: list[str], base: list[str], amount: float) -> list[str]: for i, b in enumerate(base): colours[i] = mix(colours[i], b, amount) + if __name__ == "__main__": light = sys.argv[1] == "light" @@ -101,19 +129,16 @@ if __name__ == "__main__": base = light_colours if light else dark_colours colours = [] - for colour in sys.argv[3].split(" ")[1:]: + for colour in sys.argv[3].split(" "): colours.append(adjust(colour, sat=added_sat)) # TODO: optional adjust colours = smart_sort(colours, base) # TODO: optional smart sort mix_colours(colours, base, 0) # TODO: customize mixing from config - for colour in colours: - print(colour) - - for layer in sys.argv[4:]: - print(layer.split(" ")[0]) + for i, colour in enumerate(colours): + print(f"{colour_names[i]} {colour}") # Success and error colours # TODO: customize mixing - print(mix(colours[8], base[8], 0.9)) # Success (green) - print(mix(colours[4], base[4], 0.9)) # Error (red) + print(f"success {mix(colours[8], base[8], 0.9)}") # Success (green) + print(f"error {mix(colours[4], base[4], 0.9)}") # Error (red) diff --git a/scheme/gen-scheme.fish b/scheme/gen-scheme.fish index 4f2cf04..ae807cb 100755 --- a/scheme/gen-scheme.fish +++ b/scheme/gen-scheme.fish @@ -1,52 +1,16 @@ #!/bin/fish -function nl-echo - # Echo with newlines - for a in $argv - echo $a - end -end - -function light-theme - set -g light_vals 50,10,16,22,34,46,59,69,78,85,97,94,90 - set -g colour_scheme light -end - -function dark-theme - set -g light_vals 70,90,75,63,52,42,32,26,20,16,12,9,6 - set -g colour_scheme dark -end - set -l src (dirname (status filename)) . $src/../util.fish -if test "$argv[1]" = --theme - set theme $argv[2] - set -e argv[1..2] -end - test -f "$argv[1]" && set -l img "$argv[1]" || set -l img $C_STATE/wallpaper/current set -l img (realpath $img) +contains -- "$argv[2]" light dark && set -l theme $argv[2] || set -l theme dark -if set -q theme - test "$theme" = light && light-theme || dark-theme -else - # Light theme if background lighter than foreground - set -l bg_fg ($src/getlightness.py (okolors $img -k 2 | string split ' ')) - test "$bg_fg[1]" -gt "$bg_fg[2]" && light-theme || dark-theme -end - -echo -n $colour_scheme > $C_STATE/scheme/dynamic-mode.txt - -# 2nd line except first element is the palette -# The first element in lines 3+ are the layers -set -l names rosewater flamingo pink mauve red maroon peach yellow green teal sky sapphire blue lavender text subtext1 subtext0 overlay2 overlay1 overlay0 surface2 surface1 surface0 base mantle crust success error -set -l colours ($src/autoadjust.py $colour_scheme (okolors $img -k 15 -w 0 -l $light_vals)) - -for i in (seq 1 (count $colours)) - echo "$names[$i] $colours[$i]" -end +# Generate colours +test $theme = light && set -l lightness 50 || set -l lightness 70 +$src/autoadjust.py $theme (okolors $img -k 14 -w 0 -l $lightness) -set -l accent (okolors $img -k 4 | cut -d ' ' -f 4) -echo -n "accent $accent" +# Generate layers and accents +$src/genmaterial.py $img $theme | head -c -1 # Trim trailing newline diff --git a/scheme/genmaterial.py b/scheme/genmaterial.py new file mode 100755 index 0000000..fb9bbe6 --- /dev/null +++ b/scheme/genmaterial.py @@ -0,0 +1,109 @@ +#!/bin/python + +import math +import sys +from colorsys import hls_to_rgb, rgb_to_hls + +from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors +from materialyoucolor.hct import Hct +from materialyoucolor.quantize import QuantizeCelebi +from materialyoucolor.score.score import Score +from PIL import Image + + +def darken(rgb: tuple[int, int, int], amount: float) -> tuple[int, int, int]: + h, l, s = rgb_to_hls(*tuple(i / 255 for i in rgb)) + return tuple(round(i * 255) for i in hls_to_rgb(h, max(0, l - amount), s)) + + +def mix( + rgb1: tuple[int, int, int], rgb2: tuple[int, int, int], amount: float +) -> tuple[int, int, int]: + return tuple(round(rgb1[i] * (1 - amount) + rgb2[i] * amount) for i in range(3)) + + +def calculate_optimal_size( + width: int, height: int, bitmap_size: int = 128 +) -> (int, int): + image_area = width * height + bitmap_area = bitmap_size**2 + scale = math.sqrt(bitmap_area / image_area) if image_area > bitmap_area else 1 + return max(1, round(width * scale)), max(1, round(height * scale)) + + +num_args = len(sys.argv) +if num_args < 2: + print('Usage: <path/to/image> [ "light" | "dark" ] [ <material_scheme> ]') + sys.exit(1) + +img = sys.argv[1] +is_dark = num_args < 3 or sys.argv[2] != "light" +scheme = "vibrant" if num_args < 4 else sys.argv[3] + +with Image.open(sys.argv[1]) as image: + if image.format == "GIF": + image.seek(1) + + if image.mode in ["L", "P"]: + image = image.convert("RGB") + + width, height = image.size + opt_width, opt_height = calculate_optimal_size(width, height) + if opt_width < width or opt_height < height: + image = image.resize((opt_width, opt_height), Image.Resampling.BICUBIC) + colours = QuantizeCelebi(list(image.getdata()), 128) + + hct = Hct.from_int(Score.score(colours)[0]) + + +if scheme == "fruitsalad": + from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme +elif scheme == "expressive": + from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme +elif scheme == "monochrome": + from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme +elif scheme == "rainbow": + from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme +elif scheme == "tonalspot": + from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme +elif scheme == "neutral": + from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme +elif scheme == "fidelity": + from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme +elif scheme == "content": + from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme +else: + from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme + + +scheme = Scheme(hct, is_dark, 0.0) + + +colours = {} + +for color in vars(MaterialDynamicColors).keys(): + color_name = getattr(MaterialDynamicColors, color) + if hasattr(color_name, "get_hct"): + colours[color] = color_name.get_hct(scheme).to_rgba()[0:3] + +colours = { + "primary": colours["primary"], + "secondary": colours["secondary"], + "tertiary": colours["tertiary"], + "text": colours["onBackground"], + "subtext1": colours["onSurfaceVariant"], + "subtext0": colours["outline"], + "overlay2": mix(colours["surface"], colours["outline"], 0.86), + "overlay1": mix(colours["surface"], colours["outline"], 0.71), + "overlay0": mix(colours["surface"], colours["outline"], 0.57), + "surface2": mix(colours["surface"], colours["outline"], 0.43), + "surface1": mix(colours["surface"], colours["outline"], 0.29), + "surface0": mix(colours["surface"], colours["outline"], 0.14), + "base": colours["surface"], + "mantle": darken(colours["surface"], 0.03), + "crust": darken(colours["surface"], 0.05), + "accent": colours["primary"], # FIXME: for compatibility +} + +for name, colour in colours.items(): + print("{} {:02X}{:02X}{:02X}".format(name, *colour)) diff --git a/scheme/main.fish b/scheme/main.fish index 8c11a09..129856a 100755 --- a/scheme/main.fish +++ b/scheme/main.fish @@ -42,7 +42,7 @@ if contains -- "$scheme" $valid_schemes set -l mode $flavour if test -z "$mode" # Try to use current mode if not provided and current mode exists for flavour, otherwise random mode - set $mode (cat $C_STATE/scheme/current-mode.txt 2> /dev/null) + set mode (cat $C_STATE/scheme/current-mode.txt 2> /dev/null) contains -- "$mode" $modes || set mode (random choice $modes) end diff --git a/wallpaper.fish b/wallpaper.fish index 0db1f7d..24e5780 100755 --- a/wallpaper.fish +++ b/wallpaper.fish @@ -109,8 +109,8 @@ else # Generate colour scheme for wallpaper set -l src (dirname (status filename)) mkdir -p $src/data/schemes/dynamic - $src/scheme/gen-scheme.fish --theme dark $chosen_wallpaper > $src/data/schemes/dynamic/dark.txt - $src/scheme/gen-scheme.fish --theme light $chosen_wallpaper > $src/data/schemes/dynamic/light.txt + $src/scheme/gen-scheme.fish $chosen_wallpaper dark > $src/data/schemes/dynamic/dark.txt + $src/scheme/gen-scheme.fish $chosen_wallpaper light > $src/data/schemes/dynamic/light.txt if test "$(cat $C_STATE/scheme/current-name.txt 2> /dev/null)" = 'dynamic' caelestia scheme dynamic $_flag_T > /dev/null end |