├── requirements.txt ├── setup.cfg ├── colorz.png ├── .gitignore ├── LICENSE ├── setup.py ├── README.rst └── colorz.py /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow 2 | scipy 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | -------------------------------------------------------------------------------- /colorz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metakirby5/colorz/HEAD/colorz.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ethan Chan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | from setuptools import setup 4 | from os import path 5 | 6 | here = path.abspath(path.dirname(__file__)) 7 | 8 | # Get the long description from the README file 9 | with codecs.open(path.join(here, 'README.rst'), encoding='utf-8') as f: 10 | long_description = f.read() 11 | 12 | setup( 13 | name='colorz', 14 | version='1.0.3', 15 | description='Color scheme generator.', 16 | long_description=long_description, 17 | url='https://github.com/metakirby5/colorz', 18 | author='Ethan Chan', 19 | author_email='metakirby5@gmail.com', 20 | license='MIT', 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Environment :: Console', 24 | 'Intended Audience :: End Users/Desktop', 25 | 'Topic :: Utilities', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | 'Operating System :: POSIX :: Linux', 29 | 30 | 'Programming Language :: Python :: 2', 31 | 'Programming Language :: Python :: 3', 32 | ], 33 | keywords='colorz color scheme theme generator', 34 | py_modules=['colorz'], 35 | install_requires=[ 36 | 'Pillow', 37 | 'scipy', 38 | ], 39 | entry_points={ 40 | 'console_scripts': [ 41 | 'colorz=colorz:main', 42 | ], 43 | }, 44 | ) 45 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | colorz 3 | ======== 4 | 5 | |Sample Usage| 6 | 7 | A k-means color scheme generator. 8 | 9 | Installation 10 | ------------ 11 | 12 | :: 13 | 14 | pip install colorz 15 | 16 | or just move ``colorz.py`` to somewhere in your ``$PATH``. 17 | If you do the latter, you must install the dependencies in the 18 | following section manually. 19 | 20 | Arch Linux: 21 | 22 | :: 23 | 24 | git clone https://aur.archlinux.org/colorz.git 25 | cd colorz 26 | makepkg -si 27 | 28 | Dependencies 29 | ------------ 30 | 31 | - Python (2 or 3) 32 | - Pillow 33 | - scipy 34 | 35 | Usage 36 | ----- 37 | 38 | :: 39 | 40 | usage: colorz [-h] [-n NUM_COLORS] [--minv MINV] [--maxv MAXV] [--bold BOLD] 41 | [--font-size FONT_SIZE] [--bg-color BG_COLOR] [--no-bg-img] 42 | [--no-preview] 43 | image 44 | 45 | A color scheme generator. Takes an image (local or online) and grabs the most 46 | dominant colors using kmeans. Also creates bold colors by adding value to the 47 | dominant colors. Finally, outputs the colors to stdout (one normal and one 48 | bold per line, space delimited) and generates an HTML preview of the color 49 | scheme. 50 | 51 | positional arguments: 52 | image the image file or url to generate from. 53 | 54 | optional arguments: 55 | -h, --help show this help message and exit 56 | -n NUM_COLORS number of colors to generate (excluding bold). 57 | Default: 6 58 | --minv MINV minimum value for the colors. Default: 170 59 | --maxv MAXV maximum value for the colors. Default: 200 60 | --bold BOLD how much value to add for bold colors. Default: 50 61 | --font-size FONT_SIZE 62 | what font size to use, in rem. Default: 1 63 | --bg-color BG_COLOR what background color to use, in hex format. Default: 64 | #272727 65 | --no-bg-img whether or not to use a background image in the 66 | preview. Default: background image on 67 | --no-preview whether or not to generate and show the preview. 68 | Default: preview on 69 | 70 | Thanks to 71 | --------- 72 | 73 | - http://charlesleifer.com/blog/using-python-and-k-means-to-find-the-dominant-colors-in-images/ 74 | - https://gist.github.com/radiosilence/3946121 75 | 76 | .. |Sample Usage| image:: http://i.imgur.com/QVLSXqK.png 77 | :target: colorz.png 78 | :alt: Color preview. 79 | -------------------------------------------------------------------------------- /colorz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | A color scheme generator. 6 | 7 | Takes an image (local or online) and grabs the most dominant colors 8 | using kmeans. 9 | Also creates bold colors by adding value to the dominant colors. 10 | 11 | Finally, outputs the colors to stdout 12 | (one normal and one bold per line, space delimited) and 13 | generates an HTML preview of the color scheme. 14 | """ 15 | 16 | import os 17 | import webbrowser 18 | from sys import exit 19 | from io import BytesIO 20 | from tempfile import NamedTemporaryFile 21 | from argparse import ArgumentParser 22 | from PIL import Image 23 | from numpy import array 24 | from scipy.cluster.vq import kmeans 25 | from colorsys import rgb_to_hsv, hsv_to_rgb 26 | 27 | # Python3 compatibility 28 | try: 29 | from urllib2 import urlopen 30 | except ImportError: 31 | from urllib.request import urlopen 32 | 33 | DEFAULT_NUM_COLORS = 6 34 | DEFAULT_MINV = 170 35 | DEFAULT_MAXV = 200 36 | DEFAULT_BOLD_ADD = 50 37 | DEFAULT_FONT_SIZE = 1 38 | DEFAULT_BG_COLOR = '#272727' 39 | 40 | THUMB_SIZE = (200, 200) 41 | SCALE = 256.0 42 | 43 | 44 | def down_scale(x): 45 | return x / SCALE 46 | 47 | 48 | def up_scale(x): 49 | return int(x * SCALE) 50 | 51 | 52 | def hexify(rgb): 53 | return '#%s' % ''.join('%02x' % p for p in rgb) 54 | 55 | 56 | def get_colors(img): 57 | """ 58 | Returns a list of all the image's colors. 59 | """ 60 | w, h = img.size 61 | return [color[:3] for count, color in img.convert('RGB').getcolors(w * h)] 62 | 63 | 64 | def clamp(color, min_v, max_v): 65 | """ 66 | Clamps a color such that the value is between min_v and max_v. 67 | """ 68 | h, s, v = rgb_to_hsv(*map(down_scale, color)) 69 | min_v, max_v = map(down_scale, (min_v, max_v)) 70 | v = min(max(min_v, v), max_v) 71 | return tuple(map(up_scale, hsv_to_rgb(h, s, v))) 72 | 73 | 74 | def order_by_hue(colors): 75 | """ 76 | Orders colors by hue. 77 | """ 78 | hsvs = [rgb_to_hsv(*map(down_scale, color)) for color in colors] 79 | hsvs.sort(key=lambda t: t[0]) 80 | return [tuple(map(up_scale, hsv_to_rgb(*hsv))) for hsv in hsvs] 81 | 82 | 83 | def brighten(color, brightness): 84 | """ 85 | Adds or subtracts value to a color. 86 | """ 87 | h, s, v = rgb_to_hsv(*map(down_scale, color)) 88 | return tuple(map(up_scale, hsv_to_rgb(h, s, v + down_scale(brightness)))) 89 | 90 | 91 | def colorz(fd, n=DEFAULT_NUM_COLORS, min_v=DEFAULT_MINV, max_v=DEFAULT_MAXV, 92 | bold_add=DEFAULT_BOLD_ADD, order_colors=True): 93 | """ 94 | Get the n most dominant colors of an image. 95 | Clamps value to between min_v and max_v. 96 | 97 | Creates bold colors using bold_add. 98 | Total number of colors returned is 2*n, optionally ordered by hue. 99 | Returns as a list of pairs of RGB triples. 100 | 101 | For terminal colors, the hue order is: 102 | red, yellow, green, cyan, blue, magenta 103 | """ 104 | img = Image.open(fd) 105 | img.thumbnail(THUMB_SIZE) 106 | 107 | obs = get_colors(img) 108 | clamped = [clamp(color, min_v, max_v) for color in obs] 109 | clusters, _ = kmeans(array(clamped).astype(float), n) 110 | colors = order_by_hue(clusters) if order_colors else clusters 111 | return list(zip(colors, [brighten(c, bold_add) for c in colors])) 112 | 113 | 114 | def html_preview(colors, font_size=DEFAULT_FONT_SIZE, 115 | bg_color=DEFAULT_BG_COLOR, bg_img=None, 116 | fd=None): 117 | """ 118 | Creates an HTML preview of each color. 119 | 120 | Returns the Python file object for the HTML file. 121 | """ 122 | 123 | fd = fd or NamedTemporaryFile(mode='wt', suffix='.html', delete=False) 124 | 125 | # Initial CSS styling is empty 126 | style = "" 127 | 128 | # Create the main body 129 | body = '\n'.join([""" 130 |