summaryrefslogtreecommitdiff
path: root/scheme
diff options
context:
space:
mode:
Diffstat (limited to 'scheme')
-rwxr-xr-xscheme/autoadjust.py53
-rwxr-xr-xscheme/gen-scheme.fish48
-rwxr-xr-xscheme/genmaterial.py109
-rwxr-xr-xscheme/main.fish2
4 files changed, 155 insertions, 57 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