forked from Shinonome/caelestia-cli
scheme: use material colours for layers & accents
This commit is contained in:
+40
-15
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/python3
|
#!/bin/python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from colorsys import hls_to_rgb, rgb_to_hls
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from colorsys import rgb_to_hls, hls_to_rgb
|
|
||||||
|
|
||||||
light_colours = [
|
light_colours = [
|
||||||
"dc8a78",
|
"dc8a78",
|
||||||
@@ -38,27 +38,50 @@ dark_colours = [
|
|||||||
"b4befe",
|
"b4befe",
|
||||||
]
|
]
|
||||||
|
|
||||||
def hex_to_rgb(hex: str) -> tuple[int, int, int]:
|
colour_names = [
|
||||||
"""Convert a hex string to an RGB tuple in the range [0, 1]."""
|
"rosewater",
|
||||||
return tuple(int(hex[i:i+2], 16) / 255 for i in (0, 2, 4))
|
"flamingo",
|
||||||
|
"pink",
|
||||||
|
"mauve",
|
||||||
|
"red",
|
||||||
|
"maroon",
|
||||||
|
"peach",
|
||||||
|
"yellow",
|
||||||
|
"green",
|
||||||
|
"teal",
|
||||||
|
"sky",
|
||||||
|
"sapphire",
|
||||||
|
"blue",
|
||||||
|
"lavender",
|
||||||
|
]
|
||||||
|
|
||||||
def rgb_to_hex(rgb: tuple[int, int, int]) -> str:
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_to_hex(rgb: tuple[float, float, float]) -> str:
|
||||||
"""Convert an RGB tuple in the range [0, 1] to a hex string."""
|
"""Convert an RGB tuple in the range [0, 1] to a hex string."""
|
||||||
return "".join(f"{round(i * 255):02X}" for i in rgb)
|
return "".join(f"{round(i * 255):02X}" for i in rgb)
|
||||||
|
|
||||||
|
|
||||||
def hex_to_hls(hex: str) -> tuple[float, float, float]:
|
def hex_to_hls(hex: str) -> tuple[float, float, float]:
|
||||||
return rgb_to_hls(*hex_to_rgb(hex))
|
return rgb_to_hls(*hex_to_rgb(hex))
|
||||||
|
|
||||||
|
|
||||||
def adjust(hex: str, light: float = 0, sat: float = 0) -> str:
|
def adjust(hex: str, light: float = 0, sat: float = 0) -> str:
|
||||||
h, l, s = hex_to_hls(hex)
|
h, l, s = hex_to_hls(hex)
|
||||||
l = max(0, min(1, l + light))
|
l = max(0, min(1, l + light))
|
||||||
s = max(0, min(1, s + sat))
|
s = max(0, min(1, s + sat))
|
||||||
return rgb_to_hex(hls_to_rgb(h, l, s))
|
return rgb_to_hex(hls_to_rgb(h, l, s))
|
||||||
|
|
||||||
|
|
||||||
def distance(colour: str, base: str) -> float:
|
def distance(colour: str, base: str) -> float:
|
||||||
r1, g1, b1 = hex_to_rgb(colour)
|
r1, g1, b1 = hex_to_rgb(colour)
|
||||||
r2, g2, b2 = hex_to_rgb(base)
|
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]:
|
def smart_sort(colours: list[str], base: list[str]) -> list[str]:
|
||||||
sorted_colours = [None] * len(colours)
|
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]
|
return [i[0] for i in sorted_colours]
|
||||||
|
|
||||||
|
|
||||||
def mix(a: str, b: str, w: float) -> str:
|
def mix(a: str, b: str, w: float) -> str:
|
||||||
r1, g1, b1 = hex_to_rgb(a)
|
r1, g1, b1 = hex_to_rgb(a)
|
||||||
r2, g2, b2 = hex_to_rgb(b)
|
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]:
|
def mix_colours(colours: list[str], base: list[str], amount: float) -> list[str]:
|
||||||
for i, b in enumerate(base):
|
for i, b in enumerate(base):
|
||||||
colours[i] = mix(colours[i], b, amount)
|
colours[i] = mix(colours[i], b, amount)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
light = sys.argv[1] == "light"
|
light = sys.argv[1] == "light"
|
||||||
|
|
||||||
@@ -101,19 +129,16 @@ if __name__ == "__main__":
|
|||||||
base = light_colours if light else dark_colours
|
base = light_colours if light else dark_colours
|
||||||
|
|
||||||
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.append(adjust(colour, sat=added_sat)) # TODO: optional adjust
|
||||||
|
|
||||||
colours = smart_sort(colours, base) # TODO: optional smart sort
|
colours = smart_sort(colours, base) # TODO: optional smart sort
|
||||||
|
|
||||||
mix_colours(colours, base, 0) # TODO: customize mixing from config
|
mix_colours(colours, base, 0) # TODO: customize mixing from config
|
||||||
|
|
||||||
for colour in colours:
|
for i, colour in enumerate(colours):
|
||||||
print(colour)
|
print(f"{colour_names[i]} {colour}")
|
||||||
|
|
||||||
for layer in sys.argv[4:]:
|
|
||||||
print(layer.split(" ")[0])
|
|
||||||
|
|
||||||
# Success and error colours # TODO: customize mixing
|
# Success and error colours # TODO: customize mixing
|
||||||
print(mix(colours[8], base[8], 0.9)) # Success (green)
|
print(f"success {mix(colours[8], base[8], 0.9)}") # Success (green)
|
||||||
print(mix(colours[4], base[4], 0.9)) # Error (red)
|
print(f"error {mix(colours[4], base[4], 0.9)}") # Error (red)
|
||||||
|
|||||||
+6
-42
@@ -1,52 +1,16 @@
|
|||||||
#!/bin/fish
|
#!/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))
|
set -l src (dirname (status filename))
|
||||||
|
|
||||||
. $src/../util.fish
|
. $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
|
test -f "$argv[1]" && set -l img "$argv[1]" || set -l img $C_STATE/wallpaper/current
|
||||||
set -l img (realpath $img)
|
set -l img (realpath $img)
|
||||||
|
contains -- "$argv[2]" light dark && set -l theme $argv[2] || set -l theme dark
|
||||||
|
|
||||||
if set -q theme
|
# Generate colours
|
||||||
test "$theme" = light && light-theme || dark-theme
|
test $theme = light && set -l lightness 50 || set -l lightness 70
|
||||||
else
|
$src/autoadjust.py $theme (okolors $img -k 14 -w 0 -l $lightness)
|
||||||
# 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
|
# Generate layers and accents
|
||||||
|
$src/genmaterial.py $img $theme | head -c -1 # Trim trailing newline
|
||||||
# 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
|
|
||||||
|
|
||||||
set -l accent (okolors $img -k 4 | cut -d ' ' -f 4)
|
|
||||||
echo -n "accent $accent"
|
|
||||||
|
|||||||
Executable
+109
@@ -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))
|
||||||
+1
-1
@@ -42,7 +42,7 @@ if contains -- "$scheme" $valid_schemes
|
|||||||
set -l mode $flavour
|
set -l mode $flavour
|
||||||
if test -z "$mode"
|
if test -z "$mode"
|
||||||
# Try to use current mode if not provided and current mode exists for flavour, otherwise random 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)
|
contains -- "$mode" $modes || set mode (random choice $modes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -109,8 +109,8 @@ else
|
|||||||
# Generate colour scheme for wallpaper
|
# Generate colour scheme for wallpaper
|
||||||
set -l src (dirname (status filename))
|
set -l src (dirname (status filename))
|
||||||
mkdir -p $src/data/schemes/dynamic
|
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 $chosen_wallpaper dark > $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 light > $src/data/schemes/dynamic/light.txt
|
||||||
if test "$(cat $C_STATE/scheme/current-name.txt 2> /dev/null)" = 'dynamic'
|
if test "$(cat $C_STATE/scheme/current-name.txt 2> /dev/null)" = 'dynamic'
|
||||||
caelestia scheme dynamic $_flag_T > /dev/null
|
caelestia scheme dynamic $_flag_T > /dev/null
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user