├── .gitignore ├── README.md ├── functions ├── misc └── xfce4-term ├── py ├── color_detect.py └── colorz.py └── wp /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore git files 2 | *.swp 3 | *.swo 4 | 5 | .current 6 | 7 | *.pyc 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wp 2 | 3 | wp is a small tool I use to generate color schemes from images, and manage desktop wallpapers. 4 | 5 | The color extraction scripts were taken from [this blog post](http://charlesleifer.com/blog/using-python-and-k-means-to-find-the-dominant-colors-in-images/) 6 | with normalization reddit user radiosilence. 7 | 8 | ## Dependencies 9 | 10 | As far as I know this only relies on PIL, python image library. I was able to fulfill this dependency with the `python-pillow` package on Arch Linux. 11 | On other systems, `pip install Pillow`. 12 | 13 | ## Usage 14 | 15 | ``` 16 | $ wp add [file] 17 | ``` 18 | 19 | Generates color files .[file].colors and .[file].Xres which can be sourced by shell 20 | scripts and xrdb respectivly. The color files and the image are added to the backgrounds directory. 21 | 22 | ``` 23 | $ wp change [file] 24 | ``` 25 | 26 | Changes the background image to a random image from the ~/.wallpapers directory, or the file passed, and loads the .Xres file 27 | into xrdb so xterm or urxvt will use the colors. It also links a script to ~/.colors. If you `source ~/.colors` in a script 28 | you can use the generated colors with `$COLOR0`, `$COLOR1`, ... 29 | 30 | 31 | ``` 32 | $ wp rm [file] 33 | ``` 34 | 35 | Removes the image and it's color files from the backgrounds directory. 36 | 37 | ``` 38 | $ wp ls 39 | ``` 40 | 41 | Lists the images in the backgrounds folder. 42 | -------------------------------------------------------------------------------- /functions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function indent { 4 | echo ":: $*" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /misc/xfce4-term: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # load generated colors into vars COLOR[0...15] 4 | source ~/.colors 5 | 6 | # This might be a different location on other systems 7 | terminalrc=~/.config/xfce4/terminal/terminalrc 8 | 9 | function 6_to_12 { 10 | echo $* | sed 's/#\(..\)\(..\)\(..\)/#\1\1\2\2\3\3/' 11 | } 12 | 13 | # Build color string 14 | 15 | # Change the order of these colors to change which termcolor is which color 16 | colors="$COLOR1 $COLOR2 $COLOR3 $COLOR4 $COLOR5 $COLOR6 $COLOR7 $COLOR8 $COLOR9 $COLOR10 $COLOR11 $COLOR12 $COLOR13 $COLOR14 $COLOR15" 17 | color_string="ColorPalette=" 18 | for color in $colors; do 19 | color_string="$color_string$(6_to_12 $color);" 20 | done 21 | # Add black to the end 22 | color_string="$color_string$(6_to_12 "#000000")" 23 | 24 | # Find this line 25 | line_match="^ColorPalette=.*$" 26 | 27 | # Replace it with this 28 | line_replace="$color_string" 29 | 30 | 31 | sed -i "s/$line_match/$line_replace/" $terminalrc 32 | -------------------------------------------------------------------------------- /py/color_detect.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import colorsys 3 | from colorz import colorz 4 | from math import sqrt 5 | 6 | try: 7 | import Image 8 | except ImportError: 9 | from PIL import Image 10 | 11 | if len(sys.argv) < 2: 12 | print "Usage: {0} FILENAME [num_colors]".format(sys.argv[0]) 13 | sys.exit() 14 | 15 | 16 | print sys.argv[1] 17 | 18 | WALLPAPER = sys.argv[1] 19 | filename = WALLPAPER.split('/').pop() 20 | COLORS = ".{0}.colors".format(filename) 21 | XRESOURCES = ".{0}.Xres".format(filename) 22 | SAMPLE = ".{0}.sample.png".format(filename) 23 | 24 | cols = '' 25 | xres = '' 26 | 27 | def torgb(hexv): 28 | hexv = hexv[1:] 29 | r, g, b = ( 30 | int(hexv[0:2], 16) / 256.0, 31 | int(hexv[2:4], 16) / 256.0, 32 | int(hexv[4:6], 16) / 256.0, 33 | ) 34 | return r, g, b 35 | 36 | def normalize(hexv, minv=128, maxv=256): 37 | r, g, b = torgb(hexv) 38 | h, s, v = colorsys.rgb_to_hsv(r, g, b) 39 | minv = minv / 256.0 40 | maxv = maxv / 256.0 41 | if v < minv: 42 | v = minv 43 | if v > maxv: 44 | v = maxv 45 | r, g, b = colorsys.hsv_to_rgb(h, s, v) 46 | return '#{:02x}{:02x}{:02x}'.format(int(r * 256), int(g * 256), int(b * 256)) 47 | 48 | def darkness(hexv): 49 | r, g, b = torgb(hexv) 50 | darkness = sqrt((255 - r) ** 2 + (255 - g) ** 2 + (255 - b) ** 2) 51 | return darkness 52 | 53 | def to_hsv(c): 54 | r, g, b = torgb(c) 55 | h, s, v = colorsys.rgb_to_hsv(r, g, b) 56 | return h, s, v 57 | 58 | def hex_color_to_rgb(color): 59 | color = color[1:] if color[0]=="#" else color 60 | return ( 61 | int(color[:2], 16), 62 | int(color[2:4], 16), 63 | int(color[4:], 16) 64 | ) 65 | 66 | def create_sample(f, colors): 67 | im = Image.new("RGB", (1000, 100), "white") 68 | pix = im.load() 69 | 70 | width_sample = im.size[0]/len(colors) 71 | 72 | for i, c in enumerate(colors): 73 | for j in range(width_sample*i, width_sample*i+width_sample): 74 | for k in range(0, 100): 75 | pix[j, k] = hex_color_to_rgb(c) 76 | 77 | im.save(f) 78 | 79 | if __name__ == '__main__': 80 | if len(sys.argv) == 2: 81 | n = 16 82 | else: 83 | n = int(sys.argv[2]) 84 | 85 | 86 | i = 0 87 | # sort by value, saturation, then hue 88 | colors = colorz(WALLPAPER, n=n) 89 | colors.sort(key=lambda x:darkness(x), reverse=True) 90 | for c in colors: 91 | if i == 0: 92 | c = normalize(c, minv=0, maxv=32) 93 | elif i == 8: 94 | c = normalize(c, minv=128, maxv=192) 95 | elif i < 8: 96 | c = normalize(c, minv=160, maxv=224) 97 | else: 98 | c = normalize(c, minv=200, maxv=256) 99 | c = normalize(c, minv=32, maxv=224) 100 | xres += """*color{}: {}\n""".format(i, c) 101 | cols += """export COLOR{}="{}"\n""".format(i, c) 102 | i += 1 103 | 104 | create_sample(SAMPLE, colors) 105 | with open(XRESOURCES, 'w') as f: 106 | f.write(xres) 107 | with open(COLORS, 'w') as f: 108 | f.write(cols) 109 | -------------------------------------------------------------------------------- /py/colorz.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from math import sqrt 3 | import random 4 | try: 5 | import Image 6 | except ImportError: 7 | from PIL import Image 8 | 9 | Point = namedtuple('Point', ('coords', 'n', 'ct')) 10 | Cluster = namedtuple('Cluster', ('points', 'center', 'n')) 11 | 12 | def get_points(img): 13 | points = [] 14 | w, h = img.size 15 | for count, color in img.getcolors(w * h): 16 | points.append(Point(color, 3, count)) 17 | return points 18 | 19 | rtoh = lambda rgb: '#%s' % ''.join(('%02x' % p for p in rgb)) 20 | 21 | def colorz(filename, n=3): 22 | img = Image.open(filename) 23 | img.thumbnail((200, 200)) 24 | w, h = img.size 25 | 26 | points = get_points(img) 27 | clusters = kmeans(points, n, 1) 28 | rgbs = [map(int, c.center.coords) for c in clusters] 29 | return map(rtoh, rgbs) 30 | 31 | def euclidean(p1, p2): 32 | return sqrt(sum([ 33 | (p1.coords[i] - p2.coords[i]) ** 2 for i in range(p1.n) 34 | ])) 35 | 36 | def calculate_center(points, n): 37 | vals = [0.0 for i in range(n)] 38 | plen = 0 39 | for p in points: 40 | plen += p.ct 41 | for i in range(n): 42 | vals[i] += (p.coords[i] * p.ct) 43 | return Point([(v / plen) for v in vals], n, 1) 44 | 45 | def kmeans(points, k, min_diff): 46 | clusters = [Cluster([p], p, p.n) for p in random.sample(points, k)] 47 | 48 | while 1: 49 | plists = [[] for i in range(k)] 50 | 51 | for p in points: 52 | smallest_distance = float('Inf') 53 | for i in range(k): 54 | distance = euclidean(p, clusters[i].center) 55 | if distance < smallest_distance: 56 | smallest_distance = distance 57 | idx = i 58 | plists[idx].append(p) 59 | 60 | diff = 0 61 | for i in range(k): 62 | old = clusters[i] 63 | center = calculate_center(plists[i], old.n) 64 | new = Cluster(plists[i], center, old.n) 65 | clusters[i] = new 66 | diff = max(diff, euclidean(old.center, new.center)) 67 | 68 | if diff < min_diff: 69 | break 70 | 71 | return clusters 72 | -------------------------------------------------------------------------------- /wp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function main { 4 | 5 | # Snippet from SO user Dave Dopson http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in 6 | SOURCE="${BASH_SOURCE[0]}" 7 | # resolve $SOURCE until the file is no longer a symlink 8 | while [ -h "$SOURCE" ]; do 9 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 10 | SOURCE="$(readlink "$SOURCE")" 11 | # if $SOURCE was a relative symlink, we need to resolve it 12 | # relative to the path where the symlink file was located 13 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" 14 | done 15 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 16 | K_MEANS=16 17 | WALLPAPER_DIR=${WALLPAPER_DIR:-~/.wallpapers/} 18 | 19 | while test $# -gt 0; do 20 | case "$1" in 21 | usage | -h | --help) 22 | shift 23 | usage 24 | ;; 25 | n | -n) 26 | shift 27 | K_MEANS=$1 28 | shift 29 | ;; 30 | add | -a | --add) 31 | shift 32 | add $* 33 | ;; 34 | rm | remove | -rm | --remove) 35 | shift 36 | remove $* 37 | ;; 38 | change) 39 | shift 40 | change $* 41 | ;; 42 | current) 43 | shift 44 | current $* 45 | ;; 46 | ls | list | -l | --list) 47 | shift 48 | list $* 49 | ;; 50 | colors) 51 | shift 52 | colors 53 | ;; 54 | slideshow) 55 | shift 56 | slideshow $* 57 | ;; 58 | *) 59 | shift 60 | indent "$1 is not a recognised directive" 61 | ;; 62 | esac 63 | done 64 | } 65 | 66 | #:: Prety print function 67 | function indent { 68 | echo ":: $*" 69 | } 70 | 71 | #:: Confirm user input 72 | function confirm { 73 | indent $1 74 | read -sn 1 ans 75 | case "$ans" in 76 | y|Y|yes|YES|Yes) return 0 ;; 77 | *) return 1 ;; 78 | esac 79 | } 80 | 81 | #:: Directives 82 | 83 | function add { 84 | if [ 0 = $# ]; then 85 | indent "No file argument provided" 86 | exit 1 87 | fi 88 | 89 | files=$* 90 | 91 | for file in $files; do 92 | if [ ! -f $file ]; then 93 | indent "File '$file' doesn't exist" 94 | exit 1 95 | fi 96 | done 97 | 98 | cp $files $WALLPAPER_DIR 99 | cd $WALLPAPER_DIR 100 | 101 | for file in $*; do 102 | echo ":: Generating .$file.colors and .$file.Xres in $PWD" 103 | python2 $DIR/py/color_detect.py $file $K_MEANS 104 | done 105 | } 106 | 107 | function remove { 108 | if [ 0 = $# ]; then 109 | if confirm "Delete current background? [Y/n]"; then 110 | remove $(basename $(get_current)) 111 | else 112 | echo "exiting" 113 | exit 1 114 | fi 115 | fi 116 | 117 | for file in $*; do 118 | indent "Removing $file" 119 | rm ${WALLPAPER_DIR}/${file} 120 | rm ${WALLPAPER_DIR}/.${file}.{colors,Xres} 121 | done 122 | } 123 | 124 | function change { 125 | #:: Select a random background from WALLPAPER_DIR, or use the passed background 126 | if [ -z $1 ]; then 127 | background=$(find $WALLPAPER_DIR -type f \( -name '*.jpg' -o -name '*.png' \) | shuf -n1) 128 | else 129 | background=$WALLPAPER_DIR/$1 130 | 131 | if [ ! -f $background ]; then 132 | indent "$1 does not exist in $WALLPAPER_DIR" 133 | exit 1 134 | fi 135 | fi 136 | 137 | if [ -f ${background}.Xres ] || [ -f ${background}.colors ]; then 138 | indent "Could not find ${background}.Xres or ${background}.colors" 139 | exit 1 140 | fi 141 | 142 | filename=$(basename $background) 143 | dirname=$(dirname $background) 144 | 145 | #:: Set the background 146 | feh --bg-fill $background 147 | 148 | #:: Record the current background 149 | set_current $background 150 | 151 | if [ $? -ne 0 ]; then 152 | indent "Failed to set $background as background" 153 | else 154 | indent "Set $background as background" 155 | 156 | #:: Set the colorscheme 157 | ln -f ${dirname}/.${filename}.colors ~/.colors 158 | xrdb -merge ${dirname}/.${filename}.Xres 159 | fi 160 | } 161 | 162 | function slideshow { 163 | if [ -z $2 ]; then 164 | PAPERS=$(list) 165 | elif [ -e $2 ]; then 166 | PAPERS=$(cat $2) 167 | else 168 | PAPERS=$2 169 | fi 170 | 171 | for img in $PAPERS; do 172 | change $img 173 | if [ ! -z "$1" ]; then 174 | /bin/bash -c "$1" 175 | fi 176 | sleep 7 177 | done 178 | } 179 | 180 | function current { 181 | indent $(get_current) 182 | } 183 | 184 | function get_current { 185 | readlink -f $WALLPAPER_DIR/.current 186 | } 187 | 188 | function set_current { 189 | ln -sf $1 $WALLPAPER_DIR/.current 190 | } 191 | 192 | function list { 193 | ls $* $WALLPAPER_DIR 194 | } 195 | 196 | function colors { 197 | # Original: http://frexx.de/xterm-256-notes/ 198 | # http://frexx.de/xterm-256-notes/data/colortable16.sh 199 | # Modified by Aaron Griffin 200 | # and further by Kazuo Teramoto 201 | FGNAMES=(' black ' ' red ' ' green ' ' yellow' ' blue ' 'magenta' ' cyan ' ' white ') 202 | BGNAMES=('DFT' 'BLK' 'RED' 'GRN' 'YEL' 'BLU' 'MAG' 'CYN' 'WHT') 203 | 204 | echo " ┌──────────────────────────────────────────────────────────────────────────┐" 205 | for b in {0..8}; do 206 | ((b>0)) && bg=$((b+39)) 207 | 208 | echo -en "\033[0m ${BGNAMES[b]} │ " 209 | 210 | for f in {0..7}; do 211 | echo -en "\033[${bg}m\033[$((f+30))m ${FGNAMES[f]} " 212 | done 213 | 214 | echo -en "\033[0m │" 215 | echo -en "\033[0m\n\033[0m │ " 216 | 217 | for f in {0..7}; do 218 | echo -en "\033[${bg}m\033[1;$((f+30))m ${FGNAMES[f]} " 219 | done 220 | 221 | echo -en "\033[0m │" 222 | echo -e "\033[0m" 223 | 224 | ((b<8)) && 225 | echo " ├──────────────────────────────────────────────────────────────────────────┤" 226 | done 227 | echo " └──────────────────────────────────────────────────────────────────────────┘" 228 | } 229 | 230 | function usage { 231 | printf "%b" " $0 [action] [options] 232 | 233 | Actions 234 | - usage: Print this help message. 235 | - n [number] : Number of colors to gather. 236 | - add [file]...: Add file, or files, to the wallpaper database. 237 | - rm [file]...: Remove file, or files, from the wallpaper database. 238 | - change [file]: Set the wallpaper to file, or a random wallpaper 239 | from the wallpaper database. 240 | - slideshow [cmd file]: Rotate through wallpapers, optionally 241 | running cmd each time and using only 242 | wallpapers listed in the file. 243 | - current: List the current background 244 | - ls: List all wallpapers in the database. 245 | - colors: Display the current set of colors. 246 | " 247 | } 248 | 249 | #:: End function declaration, begin executing 250 | main $* 251 | --------------------------------------------------------------------------------