Generating wallpaper-consistent terminal colors with K-means++

K-means algorithm

Assume that we have to following image:

Vg3ve2E.jpg

We can represent the image in 3D-space as follows:

scatter0

Suppose that we want to find eight dominant colors in the image. We could create a histogram and take the eight most common values. This would be fine, but often, very similar colors would repeat. Instead, we can try to find eight centroids in the image and find clusters of points beloning to each centroid (say, a point p belongs to a centroid p_{\text{centroid}} if \|p - p_{\text{centroid}}\|_2 < R for some radius R). All points not belonging to a centroid will be penalized using some heuristic. This is basically the K-means clustering algorithm.

Usage

The algorithm can be used to generate set of dominant colors to be used for instance in the terminal. Running the above algorithm on the image, we get

colors.png

or as list

['#321331', '#e1a070', '#621d39', '#e05a40', '#9ed8aa', '#8b3860', '#f7d188', '#ab3031']

with some minor adjustment

{
"color": [
"#ab3031","#621d39","#e05a40","#e1a070","#9ed8aa","#521f50","#8b3860","#f7d188","#ab5a5b","#623b4b","#e08c7b","#e1b99c","#c4d8c8","#715171","#8b5770","#f7e4c0"
],
"foreground": "#c5c8c6",
"background": "#282a2e"
}

In iTerm, it might look like this

scrot

This can be achieved using the following code:

#!/usr/bin/env python
import sys, os
from sklearn.cluster import KMeans
from PIL import Image

nbrcentroids = 10
# Constant increase in colors
beta = 10
# Multplicative factor in background
gamma = 0.4
rgb2hex = lambda rgb: '#%s' % ''.join(('%02x' % min(p + beta, 255) for p in rgb))
darken = lambda rgb : (p * gamma for p in rgb)

def getcentroids(filename, n=8):
    img = Image.open(filename)
    img.thumbnail((100, 100))
    # Run K-means algorithm on image
    kmeans = KMeans(init='k-means++', n_clusters=n)
    kmeans.fit(list(img.getdata()))
    # Get centroids from solution
    rgbs = [map(int, c) for c in kmeans.cluster_centers_]
    return rgbs

def set_colors_gnome(centroids):
    centroids = sorted(centroids, key=lambda rgb: sum(c**2 for c in rgb))
    prefix = 'gsettings set org.pantheon.terminal.settings '
    # Set background and foreground
    os.system(prefix + 'background \"%s\"' % rgb2hex(darken(centroids[0])))
    os.system(prefix + 'foreground \"%s\"' % rgb2hex(centroids[-1]))
    # Set ANSI colors
    colors = ':'.join(rgb2hex(centroid) for centroid in centroids[1:-1])
    os.system(prefix + 'palette \"' + colors + ':' + colors + '\"')

def bar(mode):
    write = sys.stdout.write
    for i in range(0, nbrcentroids):
        write('\033[0;3%dmBAR ' % i)
    write('\n')

centroids = getcentroids(sys.argv[1], n=nbrcentroids)
set_colors_gnome(centroids)
bar(0);

It is on Github too!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s