# Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # # https://github.com/dylanaraps/pywal # https://github.com/dylanaraps/pywal/blob/master/pywal/backends/wal.py # https://github.com/dylanaraps/pywal/blob/master/pywal/util.py """ Misc helper functions. """ import colorsys import json import logging import os import platform import re import shutil import subprocess import sys class Color: """Color formats.""" alpha_num = "100" def __init__(self, hex_color): self.hex_color = hex_color def __str__(self): return self.hex_color @property def rgb(self): """Convert a hex color to rgb.""" return "%s,%s,%s" % (*hex_to_rgb(self.hex_color),) @property def xrgba(self): """Convert a hex color to xrdb rgba.""" return hex_to_xrgba(self.hex_color) @property def rgba(self): """Convert a hex color to rgba.""" return "rgba(%s,%s,%s,%s)" % (*hex_to_rgb(self.hex_color), self.alpha_dec) @property def alpha(self): """Add URxvt alpha value to color.""" return "[%s]%s" % (self.alpha_num, self.hex_color) @property def alpha_dec(self): """Export the alpha value as a decimal number in [0, 1].""" return int(self.alpha_num) / 100 @property def decimal(self): """Export color in decimal.""" return "%s%s" % ("#", int(self.hex_color[1:], 16)) @property def decimal_strip(self): """Strip '#' from decimal color.""" return int(self.hex_color[1:], 16) @property def octal(self): """Export color in octal.""" return "%s%s" % ("#", oct(int(self.hex_color[1:], 16))[2:]) @property def octal_strip(self): """Strip '#' from octal color.""" return oct(int(self.hex_color[1:], 16))[2:] @property def strip(self): """Strip '#' from color.""" return self.hex_color[1:] @property def red(self): """Red value as float between 0 and 1.""" return "%.3f" % (hex_to_rgb(self.hex_color)[0] / 255.0) @property def green(self): """Green value as float between 0 and 1.""" return "%.3f" % (hex_to_rgb(self.hex_color)[1] / 255.0) @property def blue(self): """Blue value as float between 0 and 1.""" return "%.3f" % (hex_to_rgb(self.hex_color)[2] / 255.0) def lighten(self, percent): """Lighten color by percent.""" percent = float(re.sub(r"[\D\.]", "", str(percent))) return Color(lighten_color(self.hex_color, percent / 100)) def darken(self, percent): """Darken color by percent.""" percent = float(re.sub(r"[\D\.]", "", str(percent))) return Color(darken_color(self.hex_color, percent / 100)) def saturate(self, percent): """Saturate a color.""" percent = float(re.sub(r"[\D\.]", "", str(percent))) return Color(saturate_color(self.hex_color, percent / 100)) def read_file(input_file): """Read data from a file and trim newlines.""" with open(input_file, "r") as file: return file.read().splitlines() def read_file_json(input_file): """Read data from a json file.""" with open(input_file, "r") as json_file: return json.load(json_file) def read_file_raw(input_file): """Read data from a file as is, don't strip newlines or other special characters.""" with open(input_file, "r") as file: return file.readlines() def save_file(data, export_file): """Write data to a file.""" create_dir(os.path.dirname(export_file)) try: with open(export_file, "w") as file: file.write(data) except PermissionError: logging.warning("Couldn't write to %s.", export_file) def save_file_json(data, export_file): """Write data to a json file.""" create_dir(os.path.dirname(export_file)) with open(export_file, "w") as file: json.dump(data, file, indent=4) def create_dir(directory): """Alias to create the cache dir.""" os.makedirs(directory, exist_ok=True) def setup_logging(): """Logging config.""" logging.basicConfig( format=( "[%(levelname)s\033[0m] " "\033[1;31m%(module)s\033[0m: " "%(message)s" ), level=logging.INFO, stream=sys.stdout, ) logging.addLevelName(logging.ERROR, "\033[1;31mE") logging.addLevelName(logging.INFO, "\033[1;32mI") logging.addLevelName(logging.WARNING, "\033[1;33mW") def hex_to_rgb(color): """Convert a hex color to rgb.""" return tuple(bytes.fromhex(color.strip("#"))) def hex_to_xrgba(color): """Convert a hex color to xrdb rgba.""" col = color.lower().strip("#") return "%s%s/%s%s/%s%s/ff" % (*col,) def rgb_to_hex(color): """Convert an rgb color to hex.""" return "#%02x%02x%02x" % (*color,) def darken_color(color, amount): """Darken a hex color.""" color = [int(col * (1 - amount)) for col in hex_to_rgb(color)] return rgb_to_hex(color) def lighten_color(color, amount): """Lighten a hex color.""" color = [int(col + (255 - col) * amount) for col in hex_to_rgb(color)] return rgb_to_hex(color) def blend_color(color, color2): """Blend two colors together.""" r1, g1, b1 = hex_to_rgb(color) r2, g2, b2 = hex_to_rgb(color2) r3 = int(0.5 * r1 + 0.5 * r2) g3 = int(0.5 * g1 + 0.5 * g2) b3 = int(0.5 * b1 + 0.5 * b2) return rgb_to_hex((r3, g3, b3)) def saturate_color(color, amount): """Saturate a hex color.""" r, g, b = hex_to_rgb(color) r, g, b = [x / 255.0 for x in (r, g, b)] h, l, s = colorsys.rgb_to_hls(r, g, b) s = amount r, g, b = colorsys.hls_to_rgb(h, l, s) r, g, b = [x * 255.0 for x in (r, g, b)] return rgb_to_hex((int(r), int(g), int(b))) def rgb_to_yiq(color): """Sort a list of colors.""" return colorsys.rgb_to_yiq(*hex_to_rgb(color)) def disown(cmd): """Call a system command in the background, disown it and hide it's output.""" subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def get_pid(name): """Check if process is running by name.""" if not shutil.which("pidof"): return False try: if platform.system() != "Darwin": subprocess.check_output(["pidof", "-s", name]) else: subprocess.check_output(["pidof", name]) except subprocess.CalledProcessError: return False return True """ Generate a colorscheme using imagemagick. """ import logging import re import shutil import subprocess import sys def imagemagick(color_count, img, magick_command): """Call Imagemagick to generate a scheme.""" flags = ["-resize", "25%", "-colors", str(color_count), "-unique-colors", "txt:-"] img += "[0]" return subprocess.check_output([*magick_command, img, *flags]).splitlines() def has_im(): """Check to see if the user has im installed.""" if shutil.which("magick"): return ["magick"] if shutil.which("convert"): return ["convert"] logging.error("Imagemagick wasn't found on your system.") logging.error("Try another backend. (wal --backend)") sys.exit(1) def gen_colors(img): """Format the output from imagemagick into a list of hex colors.""" magick_command = has_im() for i in range(0, 20, 1): raw_colors = imagemagick(16 + i, img, magick_command) if len(raw_colors) > 16: break if i == 19: logging.error("Imagemagick couldn't generate a suitable palette.") sys.exit(1) else: logging.warning("Imagemagick couldn't generate a palette.") logging.warning("Trying a larger palette size %s", 16 + i) return [re.search("#.{6}", str(col)).group(0) for col in raw_colors[1:]] def adjust(colors, light): """Adjust the generated colors and store them in a dict that we will later save in json format.""" raw_colors = colors[:1] + colors[8:16] + colors[8:-1] # Manually adjust colors. if light: for color in raw_colors: color = saturate_color(color, 0.5) raw_colors[0] = lighten_color(colors[-1], 0.85) raw_colors[7] = colors[0] raw_colors[8] = darken_color(colors[-1], 0.4) raw_colors[15] = colors[0] else: # Darken the background color slightly. if raw_colors[0][1] != "0": raw_colors[0] = darken_color(raw_colors[0], 0.40) raw_colors[7] = blend_color(raw_colors[7], "#EEEEEE") raw_colors[8] = darken_color(raw_colors[7], 0.30) raw_colors[15] = blend_color(raw_colors[15], "#EEEEEE") return raw_colors def get(img, light=False): """Get colorscheme.""" colors = gen_colors(img) return adjust(colors, light) if __name__ == "__main__" and len(sys.argv) > 1: print(get(sys.argv[1], sys.argv[2] == "1" if len(sys.argv) > 2 else False))