diff --git a/.pylintrc b/.pylintrc index 08f340e..e41fb8c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,10 @@ good-names=i,j,k,n,x,y,fg,bg,r,g,b,i3,r1,r2,r3,g1,g2,g3,b1,b2,b3,h,s,v # too-many-branches: # Disabled as it's a non-issue and only occurs in the # process_args() function. -disable=inconsistent-return-statements,too-many-branches +# too-many-statements: +# Disabled as it's a non-issue and only occurs in the +# process_args() function. +disable=inconsistent-return-statements,too-many-branches,too-many-statements [SIMILARITIES] ignore-imports=y diff --git a/.travis.yml b/.travis.yml index 3a0a043..11a9bfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,16 +7,14 @@ matrix: - os: linux python: 3.6 - before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y imagemagick; fi install: - - pip install flake8 pylint pyroma + - pip install flake8 pylint script: - flake8 pywal tests setup.py - pylint pywal tests setup.py - - pyroma . - python setup.py test diff --git a/pywal/__main__.py b/pywal/__main__.py index a47deb3..05eea99 100644 --- a/pywal/__main__.py +++ b/pywal/__main__.py @@ -36,6 +36,10 @@ def get_args(args): arg.add_argument("-b", metavar="background", help="Custom background color to use.") + arg.add_argument("--backend", metavar="backend", + help="Which color backend to use.", + const="list_backends", type=str, nargs="?", default="wal") + arg.add_argument("-c", action="store_true", help="Delete all cached colorschemes.") @@ -102,6 +106,10 @@ def process_args(args): reload.colors() sys.exit(0) + if args.backend == "list_backends": + print("Available backends:", colors.list_backends()) + sys.exit(0) + if args.q: sys.stdout = sys.stderr = open(os.devnull, "w") @@ -120,7 +128,7 @@ def process_args(args): if args.i: image_file = image.get(args.i) - colors_plain = colors.get(image_file, light=args.l) + colors_plain = colors.get(image_file, args.l, args.backend) if args.f: colors_plain = colors.file(args.f) diff --git a/pywal/backends/__init__.py b/pywal/backends/__init__.py new file mode 100644 index 0000000..9a116e8 --- /dev/null +++ b/pywal/backends/__init__.py @@ -0,0 +1,10 @@ +r""" +Hh ____ +HP "HHF:. `._ :.,-'"" "-. +F F" :::..'"" "-. `. +F , \ \ "BACKENDS" +F j\ / ; `. - sorry +| j `. ` A \ +| | ;_ . 8 \ +J F\_,'| "`-----.\ j `. \ +""" diff --git a/pywal/backends/colorthief.py b/pywal/backends/colorthief.py new file mode 100644 index 0000000..180af5f --- /dev/null +++ b/pywal/backends/colorthief.py @@ -0,0 +1,64 @@ +""" +Generate a colorscheme using ColorThief. +""" +import sys + +try: + from colorthief import ColorThief + +except ImportError: + print("error: ColorThief wasn't found on your system.", + "Try another backend. (wal --backend)") + sys.exit(1) + +from .. import util + + +def gen_colors(img): + """Loop until 16 colors are generated.""" + color_cmd = ColorThief(img).get_palette + + for i in range(0, 10, 1): + raw_colors = color_cmd(color_count=8 + i) + + if len(raw_colors) >= 8: + break + + elif i == 19: + print("colors: ColorThief couldn't generate a suitable palette", + "for the image. Exiting...") + sys.exit(1) + + else: + print("colors: ColorThief couldn't create a suitable palette, " + "trying a larger palette size", 8 + i) + + return [util.rgb_to_hex(color) for color in raw_colors] + + +def adjust(cols, light): + """Create palette.""" + cols.sort(key=util.rgb_to_yiq) + raw_colors = [*cols, *cols] + + if light: + raw_colors[0] = util.lighten_color(cols[0], 0.90) + raw_colors[7] = util.darken_color(cols[0], 0.75) + + else: + for color in raw_colors: + color = util.lighten_color(color, 0.40) + + raw_colors[0] = util.darken_color(cols[0], 0.80) + raw_colors[7] = util.lighten_color(cols[0], 0.60) + + raw_colors[8] = util.lighten_color(cols[0], 0.20) + raw_colors[15] = raw_colors[7] + + return raw_colors + + +def get(img, light=False): + """Get colorscheme.""" + cols = gen_colors(img) + return adjust(cols, light) diff --git a/pywal/backends/colorz.py b/pywal/backends/colorz.py new file mode 100644 index 0000000..0c5148d --- /dev/null +++ b/pywal/backends/colorz.py @@ -0,0 +1,36 @@ +""" +Generate a colorscheme using Colorz. +""" +import shutil +import subprocess +import sys + +from .. import colors +from .. import util + + +def gen_colors(img): + """Generate a colorscheme using Colorz.""" + cmd = ["colorz", "-n", "6", "--bold", "0", "--no-preview"] + return subprocess.check_output([*cmd, img]).splitlines() + + +def adjust(cols, light): + """Create palette.""" + bg = util.blend_color("#555555", cols[1]) + + raw_colors = [bg, *cols, "#FFFFFF", + "#333333", *cols, "#FFFFFF"] + + return colors.generic_adjust(raw_colors, light) + + +def get(img, light=False): + """Get colorscheme.""" + if not shutil.which("colorz"): + print("error: Colorz wasn't found on your system.", + "Try another backend. (wal --backend)") + sys.exit(1) + + cols = [col.decode('UTF-8').split()[0] for col in gen_colors(img)] + return adjust(cols, light) diff --git a/pywal/backends/haishoku.py b/pywal/backends/haishoku.py new file mode 100644 index 0000000..413c9d7 --- /dev/null +++ b/pywal/backends/haishoku.py @@ -0,0 +1,36 @@ +""" +Generate a colorscheme using Haishoku. +""" +import sys + +try: + from haishoku.haishoku import Haishoku + +except ImportError: + print("error: Haishoku wasn't found on your system.", + "Try another backend. (wal --backend)") + sys.exit(1) + +from .. import colors +from .. import util + + +def gen_colors(img): + """Generate a colorscheme using Colorz.""" + palette = Haishoku.getPalette(img) + return [util.rgb_to_hex(col[1]) for col in palette] + + +def adjust(cols, light): + """Create palette.""" + cols.sort(key=util.rgb_to_yiq) + raw_colors = [*cols, *cols] + raw_colors[0] = util.lighten_color(cols[0], 0.40) + + return colors.generic_adjust(raw_colors, light) + + +def get(img, light=False): + """Get colorscheme.""" + cols = gen_colors(img) + return adjust(cols, light) diff --git a/pywal/backends/schemer2.py b/pywal/backends/schemer2.py new file mode 100644 index 0000000..87873ec --- /dev/null +++ b/pywal/backends/schemer2.py @@ -0,0 +1,34 @@ +""" +Generate a colorscheme using Schemer2. +""" +import shutil +import subprocess +import sys + +from .. import colors +from .. import util + + +def gen_colors(img): + """Generate a colorscheme using Colorz.""" + cmd = ["schemer2", "-format", "img::colors", "-minBright", "75", "-in"] + return subprocess.check_output([*cmd, img]).splitlines() + + +def adjust(cols, light): + """Create palette.""" + cols.sort(key=util.rgb_to_yiq) + raw_colors = [*cols[8:], *cols[8:]] + + return colors.generic_adjust(raw_colors, light) + + +def get(img, light=False): + """Get colorscheme.""" + if not shutil.which("schemer2"): + print("error: Schemer2 wasn't found on your system.", + "Try another backend. (wal --backend)") + sys.exit(1) + + cols = [col.decode('UTF-8') for col in gen_colors(img)] + return adjust(cols, light) diff --git a/pywal/backends/wal.py b/pywal/backends/wal.py new file mode 100644 index 0000000..678c66c --- /dev/null +++ b/pywal/backends/wal.py @@ -0,0 +1,87 @@ +""" +Generate a colorscheme using imagemagick. +""" +import re +import shutil +import subprocess +import sys + +from .. import util + + +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", "convert"] + + elif shutil.which("convert"): + return ["convert"] + + print("error: ImageMagick wasn't found on your system.", + "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 + + elif i == 19: + print("colors: Imagemagick couldn't generate a suitable palette", + "for the image. Exiting...") + sys.exit(1) + + else: + print("colors: Imagemagick couldn't generate a suitable palette, " + "trying a larger palette size", 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 = util.saturate_color(color, 0.5) + + raw_colors[0] = util.lighten_color(colors[-1], 0.85) + raw_colors[7] = colors[0] + raw_colors[8] = util.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] = util.darken_color(raw_colors[0], 0.25) + + raw_colors[7] = util.blend_color(raw_colors[7], "#EEEEEE") + raw_colors[8] = util.darken_color(raw_colors[7], 0.30) + raw_colors[15] = util.blend_color(raw_colors[15], "#EEEEEE") + + return raw_colors + + +def get(img, light=False): + """Get colorscheme.""" + colors = gen_colors(img) + return adjust(colors, light) diff --git a/pywal/colors.py b/pywal/colors.py index f47ac33..06863ac 100644 --- a/pywal/colors.py +++ b/pywal/colors.py @@ -1,114 +1,89 @@ """ -Generate a colorscheme using imagemagick. +Generate a palette using various backends. """ import os import re -import shutil -import subprocess import sys -from .settings import CACHE_DIR, COLOR_COUNT, __cache_version__ from . import util +from .settings import CACHE_DIR, MODULE_DIR, __cache_version__ -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 list_backends(): + """List color backends.""" + return [b.name.replace(".py", "") for b in + os.scandir(os.path.join(MODULE_DIR, "backends")) + if "__" not in b.name] -def has_im(): - """Check to see if the user has im installed.""" - if shutil.which("magick"): - return ["magick", "convert"] +def colors_to_dict(colors, img): + """Convert list of colors to pywal format.""" + return { + "wallpaper": img, + "alpha": util.Color.alpha_num, - elif shutil.which("convert"): - return ["convert"] + "special": { + "background": colors[0], + "foreground": colors[15], + "cursor": colors[1] + }, - print("error: imagemagick not found, exiting...\n" - "error: wal requires imagemagick to function.") - sys.exit(1) + "colors": { + "color0": colors[0], + "color1": colors[1], + "color2": colors[2], + "color3": colors[3], + "color4": colors[4], + "color5": colors[5], + "color6": colors[6], + "color7": colors[7], + "color8": colors[8], + "color9": colors[9], + "color10": colors[10], + "color11": colors[11], + "color12": colors[12], + "color13": colors[13], + "color14": colors[14], + "color15": colors[15] + } + } -def gen_colors(img, color_count): - """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(color_count + i, img, magick_command) - - if len(raw_colors) > 16: - break - - elif i == 19: - print("colors: Imagemagick couldn't generate a suitable scheme", - "for the image. Exiting...") - sys.exit(1) - - else: - print("colors: Imagemagick couldn't generate a %s color palette, " - "trying a larger palette size %s." - % (color_count, color_count + i)) - - return [re.search("#.{6}", str(col)).group(0) for col in raw_colors[1:]] - - -def create_palette(img, colors, light): - """Sort 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] - +def generic_adjust(colors, light): + """Generic color adjustment for themers.""" if light: - # Manually adjust colors. - raw_colors[7] = raw_colors[0] - raw_colors[0] = util.lighten_color(raw_colors[15], 0.85) - raw_colors[15] = raw_colors[7] - raw_colors[8] = util.lighten_color(raw_colors[7], 0.25) + for color in colors: + color = util.saturate_color(color, 0.50) + color = util.darken_color(color, 0.4) + + colors[0] = util.lighten_color(colors[0], 0.9) + colors[7] = util.darken_color(colors[0], 0.75) + colors[8] = util.darken_color(colors[0], 0.25) + colors[15] = colors[7] else: - # Darken the background color slightly. - if raw_colors[0][1] != "0": - raw_colors[0] = util.darken_color(raw_colors[0], 0.25) - - # Manually adjust colors. - raw_colors[7] = util.blend_color(raw_colors[7], "#EEEEEE") - raw_colors[8] = util.darken_color(raw_colors[7], 0.30) - raw_colors[15] = util.blend_color(raw_colors[15], "#EEEEEE") - - colors = {"wallpaper": img, "alpha": util.Color.alpha_num, - "special": {}, "colors": {}} - colors["special"]["background"] = raw_colors[0] - colors["special"]["foreground"] = raw_colors[15] - colors["special"]["cursor"] = raw_colors[15] - - if light: - for i, color in enumerate(raw_colors): - colors["colors"]["color%s" % i] = util.saturate_color(color, 0.5) - - colors["colors"]["color0"] = raw_colors[0] - colors["colors"]["color7"] = raw_colors[15] - colors["colors"]["color8"] = util.darken_color(raw_colors[0], 0.5) - colors["colors"]["color15"] = raw_colors[15] - - else: - for i, color in enumerate(raw_colors): - colors["colors"]["color%s" % i] = color + colors[0] = util.darken_color(colors[0], 0.75) + colors[7] = util.lighten_color(colors[0], 0.75) + colors[8] = util.lighten_color(colors[0], 0.25) + colors[15] = colors[7] return colors -def get(img, cache_dir=CACHE_DIR, - color_count=COLOR_COUNT, light=False): - """Get the colorscheme.""" - # home_dylan_img_jpg_1.2.2.json +def cache_fname(img, backend, light, cache_dir): + """Create the cache file name.""" color_type = "light" if light else "dark" - cache_file = re.sub("[/|\\|.]", "_", img) - cache_file = os.path.join(cache_dir, "schemes", "%s_%s_%s.json" - % (cache_file, color_type, __cache_version__)) + file_name = re.sub("[/|\\|.]", "_", img) + + file_parts = [file_name, color_type, backend, __cache_version__] + return [cache_dir, "schemes", "%s_%s_%s_%s.json" % (*file_parts,)] + + +def get(img, light=False, backend="wal", cache_dir=CACHE_DIR): + """Generate a palette.""" + # home_dylan_img_jpg_backend_1.2.2.json + cache_name = cache_fname(img, backend, light, cache_dir) + cache_file = os.path.join(*cache_name) if os.path.isfile(cache_file): colors = file(cache_file) @@ -118,8 +93,15 @@ def get(img, cache_dir=CACHE_DIR, else: print("wal: Generating a colorscheme...") - colors = gen_colors(img, color_count) - colors = create_palette(img, colors, light) + # Dynamically import the backend we want to use. + # This keeps the dependencies "optional". + try: + __import__("pywal.backends.%s" % backend) + except ImportError: + backend = "wal" + + backend = sys.modules["pywal.backends.%s" % backend] + colors = colors_to_dict(getattr(backend, "get")(img, light), img) util.save_file_json(colors, cache_file) print("wal: Generation complete.") diff --git a/pywal/settings.py b/pywal/settings.py index ca82333..fd12105 100644 --- a/pywal/settings.py +++ b/pywal/settings.py @@ -21,5 +21,4 @@ HOME = os.getenv("HOME", os.getenv("USERPROFILE")) CACHE_DIR = os.path.join(HOME, ".cache", "wal") MODULE_DIR = os.path.dirname(__file__) CONF_DIR = os.path.join(HOME, ".config", "wal") -COLOR_COUNT = 16 OS = platform.uname()[0] diff --git a/pywal/util.py b/pywal/util.py index 43ee3fb..65d4557 100644 --- a/pywal/util.py +++ b/pywal/util.py @@ -144,6 +144,11 @@ def saturate_color(color, amount): 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."""