- commit
- 3d8839acaf0c4741a128bf3eb8f8229520a5a53f
- parent
- 5a568136591570a7f6f869a38ac4c1a186106bfc
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2023-11-05 09:48
lint
Diffstat
| M | xiwal/__main__.py | 40 | ++++++++++++++++++++-------------------- |
| M | xiwal/image.py | 26 | +++++++++++++------------- |
| M | xiwal/lch.py | 148 | ++++++++++++++++++++++++++++++------------------------------ |
| M | xiwal/scheme.py | 86 | ++++++++++++++++++++++++++++++------------------------------ |
| M | xiwal/term.py | 30 | +++++++++++++++--------------- |
5 files changed, 165 insertions, 165 deletions
diff --git a/xiwal/__main__.py b/xiwal/__main__.py
@@ -1,5 +1,5 @@1 -1 import sys2 1 import argparse -1 2 import sys 3 3 4 4 from . import image 5 5 from . import scheme @@ -7,32 +7,32 @@ from . import term 7 7 8 8 9 9 def parse_args():10 -1 parser = argparse.ArgumentParser()11 -1 parser.add_argument('--apply', '-a', action='store_true')12 -1 parser.add_argument('--image', '-i')13 -1 parser.add_argument('-n', type=int, default=8)14 -1 parser.add_argument('colors', nargs='*')15 -1 return parser.parse_args()-1 10 parser = argparse.ArgumentParser() -1 11 parser.add_argument('--apply', '-a', action='store_true') -1 12 parser.add_argument('--image', '-i') -1 13 parser.add_argument('-n', type=int, default=8) -1 14 parser.add_argument('colors', nargs='*') -1 15 return parser.parse_args() 16 16 17 17 18 18 def main():19 -1 args = parse_args()-1 19 args = parse_args() 20 2021 -1 colors = []22 -1 if args.image:23 -1 colors += list(image.extract_colors(args.image, args.n))24 -1 colors += args.colors-1 21 colors = [] -1 22 if args.image: -1 23 colors += list(image.extract_colors(args.image, args.n)) -1 24 colors += args.colors 25 2526 -1 if len(colors) < 6:27 -1 sys.exit('Need at least 6 colors')-1 26 if len(colors) < 6: -1 27 sys.exit('Need at least 6 colors') 28 2829 -1 s = scheme.colors2scheme(colors)-1 29 s = scheme.colors2scheme(colors) 30 3031 -1 print(';'.join(s))32 -1 term.palette(s)33 -1 if args.apply:34 -1 term.apply(s)-1 31 print(';'.join(s)) -1 32 term.palette(s) -1 33 if args.apply: -1 34 term.apply(s) 35 35 36 36 37 37 if __name__ == '__main__':38 -1 main()-1 38 main()
diff --git a/xiwal/image.py b/xiwal/image.py
@@ -3,16 +3,16 @@ import subprocess 3 3 4 4 5 5 def extract_colors(path, colors):6 -1 cmd = [7 -1 'convert', path,8 -1 '-resize', '25%',9 -1 '-alpha', 'deactivate',10 -1 '-colors', str(colors),11 -1 '-unique-colors',12 -1 'txt:-'13 -1 ]14 -1 output = subprocess.check_output(cmd)15 -1 for line in output.splitlines()[1:]:16 -1 line = line.decode('ascii')17 -1 match = re.search(r'#[0-9A-F]{6}', line)18 -1 yield match.group()-1 6 cmd = [ -1 7 'convert', path, -1 8 '-resize', '25%', -1 9 '-alpha', 'deactivate', -1 10 '-colors', str(colors), -1 11 '-unique-colors', -1 12 'txt:-' -1 13 ] -1 14 output = subprocess.check_output(cmd) -1 15 for line in output.splitlines()[1:]: -1 16 line = line.decode('ascii') -1 17 match = re.search(r'#[0-9A-F]{6}', line) -1 18 yield match.group()
diff --git a/xiwal/lch.py b/xiwal/lch.py
@@ -7,119 +7,119 @@ import math 7 7 8 8 9 9 def _srgb2rgb(c):10 -1 c = c / 255.011 -1 if c <= 0.04045:12 -1 c = c / 12.9213 -1 else:14 -1 c = ((c + 0.055) / 1.055) ** 2.415 -1 return c-1 10 c = c / 255.0 -1 11 if c <= 0.04045: -1 12 c = c / 12.92 -1 13 else: -1 14 c = ((c + 0.055) / 1.055) ** 2.4 -1 15 return c 16 16 17 17 18 18 def _rgb2srgb(c):19 -1 if c <= 0.0031308:20 -1 c = c * 12.9221 -1 else:22 -1 c = 1.055 * (c ** (1 / 2.4)) - 0.05523 -1 return c * 255-1 19 if c <= 0.0031308: -1 20 c = c * 12.92 -1 21 else: -1 22 c = 1.055 * (c ** (1 / 2.4)) - 0.055 -1 23 return c * 255 24 24 25 25 26 26 def rgb2lab(rgb):27 -1 r, g, b = map(_srgb2rgb, rgb)-1 27 r, g, b = map(_srgb2rgb, rgb) 28 2829 -1 l = 0.4121656120 * r + 0.5362752080 * g + 0.0514575653 * b30 -1 m = 0.2118591070 * r + 0.6807189584 * g + 0.1074065790 * b31 -1 s = 0.0883097947 * r + 0.2818474174 * g + 0.6302613616 * b-1 29 l = 0.4121656120 * r + 0.5362752080 * g + 0.0514575653 * b -1 30 m = 0.2118591070 * r + 0.6807189584 * g + 0.1074065790 * b -1 31 s = 0.0883097947 * r + 0.2818474174 * g + 0.6302613616 * b 32 3233 -1 l_ = l ** (1 / 3)34 -1 m_ = m ** (1 / 3)35 -1 s_ = s ** (1 / 3)-1 33 l_ = l ** (1 / 3) -1 34 m_ = m ** (1 / 3) -1 35 s_ = s ** (1 / 3) 36 3637 -1 L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_38 -1 a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_39 -1 b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_-1 37 L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_ -1 38 a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_ -1 39 b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_ 40 4041 -1 return L, a, b-1 41 return L, a, b 42 42 43 43 44 44 def lab2rgb(lab):45 -1 L, a, b = lab-1 45 L, a, b = lab 46 4647 -1 l_ = L + 0.3963377774 * a + 0.2158037573 * b48 -1 m_ = L - 0.1055613458 * a - 0.0638541728 * b49 -1 s_ = L - 0.0894841775 * a - 1.2914855480 * b-1 47 l_ = L + 0.3963377774 * a + 0.2158037573 * b -1 48 m_ = L - 0.1055613458 * a - 0.0638541728 * b -1 49 s_ = L - 0.0894841775 * a - 1.2914855480 * b 50 5051 -1 l = l_ ** 352 -1 m = m_ ** 353 -1 s = s_ ** 3-1 51 l = l_ ** 3 -1 52 m = m_ ** 3 -1 53 s = s_ ** 3 54 5455 -1 r = +4.0767245293 * l - 3.3072168827 * m + 0.2307590544 * s56 -1 g = -1.2681437731 * l + 2.6093323231 * m - 0.3411344290 * s57 -1 b = -0.0041119885 * l - 0.7034763098 * m + 1.7068625689 * s-1 55 r = +4.0767245293 * l - 3.3072168827 * m + 0.2307590544 * s -1 56 g = -1.2681437731 * l + 2.6093323231 * m - 0.3411344290 * s -1 57 b = -0.0041119885 * l - 0.7034763098 * m + 1.7068625689 * s 58 5859 -1 r = max(0, min(1, r))60 -1 g = max(0, min(1, g))61 -1 b = max(0, min(1, b))-1 59 r = max(0, min(1, r)) -1 60 g = max(0, min(1, g)) -1 61 b = max(0, min(1, b)) 62 6263 -1 return tuple(map(_rgb2srgb, (r, g, b)))-1 63 return tuple(map(_rgb2srgb, (r, g, b))) 64 64 65 65 66 66 def rgb2lch(rgb):67 -1 l, a, b = rgb2lab(rgb)68 -1 c = (a ** 2 + b ** 2) ** 0.569 -1 h = 070 -1 if abs(a) > 0.0001 or abs(b) > 0.0001:71 -1 h = math.atan2(b, a)72 -1 h = (h + 4 * math.pi) % (2 * math.pi)73 -1 return l, c, h-1 67 l, a, b = rgb2lab(rgb) -1 68 c = (a ** 2 + b ** 2) ** 0.5 -1 69 h = 0 -1 70 if abs(a) > 0.0001 or abs(b) > 0.0001: -1 71 h = math.atan2(b, a) -1 72 h = (h + 4 * math.pi) % (2 * math.pi) -1 73 return l, c, h 74 74 75 75 76 76 def _lch2rgb(lch):77 -1 l, c, h = lch78 -1 a = math.cos(h) * c79 -1 b = math.sin(h) * c80 -1 return lab2rgb((l, a, b))-1 77 l, c, h = lch -1 78 a = math.cos(h) * c -1 79 b = math.sin(h) * c -1 80 return lab2rgb((l, a, b)) 81 81 82 82 83 83 def lch2rgb(lch):84 -1 rgb = _lch2rgb(lch)-1 84 rgb = _lch2rgb(lch) 85 8586 -1 if any(x < 0 or x > 255 for x in rgb):87 -1 c_min = 088 -1 c_max = lch[1]-1 86 if any(x < 0 or x > 255 for x in rgb): -1 87 c_min = 0 -1 88 c_max = lch[1] 89 8990 -1 while c_max - c_min > 0.01:91 -1 c_tmp = (c_min + c_max) / 292 -1 rgb = _lch2rgb((lch[0], c_tmp, lch[2]))93 -1 if any(x < 0 or x > 255 for x in rgb):94 -1 c_max = c_tmp95 -1 else:96 -1 c_min = c_tmp-1 90 while c_max - c_min > 0.01: -1 91 c_tmp = (c_min + c_max) / 2 -1 92 rgb = _lch2rgb((lch[0], c_tmp, lch[2])) -1 93 if any(x < 0 or x > 255 for x in rgb): -1 94 c_max = c_tmp -1 95 else: -1 96 c_min = c_tmp 97 9798 -1 return rgb-1 98 return rgb 99 99 100 100 101 101 def format_hex(rgb):102 -1 return '#{:02x}{:02x}{:02x}'.format(*[int(x) for x in rgb])-1 102 return '#{:02x}{:02x}{:02x}'.format(*[int(x) for x in rgb]) 103 103 104 104 105 105 def parse_hex(s):106 -1 s = s.lstrip('#')107 -1 if len(s) == 6:108 -1 r = int(s[0:2], 16)109 -1 g = int(s[2:4], 16)110 -1 b = int(s[4:6], 16)111 -1 elif len(s) == 3:112 -1 r = int(s[0], 16) * 17113 -1 g = int(s[1], 16) * 17114 -1 b = int(s[2], 16) * 17115 -1 else:116 -1 raise ValueError('Invalid hex color: %s' % s)117 -1 return r, g, b-1 106 s = s.lstrip('#') -1 107 if len(s) == 6: -1 108 r = int(s[0:2], 16) -1 109 g = int(s[2:4], 16) -1 110 b = int(s[4:6], 16) -1 111 elif len(s) == 3: -1 112 r = int(s[0], 16) * 17 -1 113 g = int(s[1], 16) * 17 -1 114 b = int(s[2], 16) * 17 -1 115 else: -1 116 raise ValueError('Invalid hex color: %s' % s) -1 117 return r, g, b 118 118 119 119 120 120 def from_hex(s):121 -1 return rgb2lch(parse_hex(s))-1 121 return rgb2lch(parse_hex(s)) 122 122 123 123 124 124 def to_hex(lch):125 -1 return format_hex(lch2rgb(lch))-1 125 return format_hex(lch2rgb(lch))
diff --git a/xiwal/scheme.py b/xiwal/scheme.py
@@ -1,5 +1,5 @@1 -1 import math2 1 import functools -1 2 import math 3 3 4 4 from . import lch 5 5 @@ -26,64 +26,64 @@ ORDER = [0, 2, 1, 4, 5, 3] 26 26 27 27 28 28 def permutate(a, n):29 -1 if n == 0:30 -1 yield ()31 -1 else:32 -1 for i in range(len(a)):33 -1 for rest in permutate(a[:i] + a[i + 1:], n - 1):34 -1 yield (a[i], *rest)-1 29 if n == 0: -1 30 yield () -1 31 else: -1 32 for i in range(len(a)): -1 33 for rest in permutate(a[:i] + a[i + 1:], n - 1): -1 34 yield (a[i], *rest) 35 35 36 36 37 37 @functools.lru_cache(maxsize=32) 38 38 def distance(color, i):39 -1 hue = math.pi / 3 * ORDER[i] + OFFSET40 -1 d = abs(color[2] - hue)41 -1 if d > math.pi:42 -1 d = 2 * math.pi - d-1 39 hue = math.pi / 3 * ORDER[i] + OFFSET -1 40 d = abs(color[2] - hue) -1 41 if d > math.pi: -1 42 d = 2 * math.pi - d 43 4344 -1 c = color[1]45 -1 if i in [0, 1]:46 -1 c = max(c, C_RG)47 -1 d += (c - color[1]) / (c + color[1])-1 44 c = color[1] -1 45 if i in [0, 1]: -1 46 c = max(c, C_RG) -1 47 d += (c - color[1]) / (c + color[1]) 48 4849 -1 return d ** 4 * c, c-1 49 return d ** 4 * c, c 50 50 51 51 52 52 def score(colors):53 -1 sum_score = 054 -1 sum_chroma = 0-1 53 sum_score = 0 -1 54 sum_chroma = 0 55 5556 -1 for i, color in enumerate(colors):57 -1 score, chroma = distance(color, i)58 -1 sum_score += score59 -1 sum_chroma += chroma-1 56 for i, color in enumerate(colors): -1 57 score, chroma = distance(color, i) -1 58 sum_score += score -1 59 sum_chroma += chroma 60 6061 -1 return sum_score / sum_chroma-1 61 return sum_score / sum_chroma 62 62 63 63 64 64 def scheme(colors, dominant):65 -1 c_grey = min(dominant[1], C_GREY)-1 65 c_grey = min(dominant[1], C_GREY) 66 6667 -1 yield L_DARK[0], c_grey, dominant[2]68 -1 for i in range(6):69 -1 c = colors[i][1] * C_FACTOR70 -1 if i in [0, 1]:71 -1 c = max(c, C_RG)72 -1 yield L_DARK[i + 1], c, colors[i][2]73 -1 yield L_DARK[7], c_grey, dominant[2]-1 67 yield L_DARK[0], c_grey, dominant[2] -1 68 for i in range(6): -1 69 c = colors[i][1] * C_FACTOR -1 70 if i in [0, 1]: -1 71 c = max(c, C_RG) -1 72 yield L_DARK[i + 1], c, colors[i][2] -1 73 yield L_DARK[7], c_grey, dominant[2] 74 7475 -1 yield L_LIGHT[0], c_grey, dominant[2]76 -1 for i in range(6):77 -1 c = colors[i][1] * C_FACTOR78 -1 if i in [0, 1]:79 -1 c = max(c, C_RG)80 -1 yield L_LIGHT[i + 1], c, colors[i][2]81 -1 yield L_LIGHT[7], c_grey, dominant[2]-1 75 yield L_LIGHT[0], c_grey, dominant[2] -1 76 for i in range(6): -1 77 c = colors[i][1] * C_FACTOR -1 78 if i in [0, 1]: -1 79 c = max(c, C_RG) -1 80 yield L_LIGHT[i + 1], c, colors[i][2] -1 81 yield L_LIGHT[7], c_grey, dominant[2] 82 82 83 83 84 84 def colors2scheme(colors):85 -1 colors = [lch.from_hex(c) for c in colors]86 -1 dominant = colors[0]87 -1 colors = min(permutate(colors, 6), key=score)88 -1 s = scheme(colors, dominant)89 -1 return [lch.to_hex(c) for c in s]-1 85 colors = [lch.from_hex(c) for c in colors] -1 86 dominant = colors[0] -1 87 colors = min(permutate(colors, 6), key=score) -1 88 s = scheme(colors, dominant) -1 89 return [lch.to_hex(c) for c in s]
diff --git a/xiwal/term.py b/xiwal/term.py
@@ -1,25 +1,25 @@1 -1 import os2 1 import glob -1 2 import os 3 3 4 4 from . import lch 5 5 6 6 7 7 def apply(scheme, cachefile='~/.cache/wal/sequences'):8 -1 cachefile = os.path.expanduser(cachefile)9 -1 os.makedirs(os.path.dirname(cachefile), exist_ok=True)-1 8 cachefile = os.path.expanduser(cachefile) -1 9 os.makedirs(os.path.dirname(cachefile), exist_ok=True) 10 1011 -1 for path in [cachefile] + glob.glob('/dev/pts/[0-9]*'):12 -1 with open(path, 'w') as tty:13 -1 for i in range(16):14 -1 tty.write('\033]4;%i;%s\033\\' % (i, scheme[i]))15 -1 tty.write('\033]%i;%s\033\\' % (11, scheme[0]))16 -1 tty.write('\033]%i;%s\033\\' % (10, scheme[15]))-1 11 for path in [cachefile, *glob.glob('/dev/pts/[0-9]*')]: -1 12 with open(path, 'w') as tty: -1 13 for i in range(16): -1 14 tty.write('\033]4;%i;%s\033\\' % (i, scheme[i])) -1 15 tty.write('\033]%i;%s\033\\' % (11, scheme[0])) -1 16 tty.write('\033]%i;%s\033\\' % (10, scheme[15])) 17 17 18 18 19 19 def palette(scheme):20 -1 s = []21 -1 for i in range(16):22 -1 r, g, b = lch.parse_hex(scheme[i])23 -1 s.append('\033[48;2;%i;%i;%im \033[0m' % (r, g, b))24 -1 print(''.join(s[:8]))25 -1 print(''.join(s[8:]))-1 20 s = [] -1 21 for i in range(16): -1 22 r, g, b = lch.parse_hex(scheme[i]) -1 23 s.append('\033[48;2;%i;%i;%im \033[0m' % (r, g, b)) -1 24 print(''.join(s[:8])) -1 25 print(''.join(s[8:]))