20210815

Terminal color waterfall

Some days you just need... 

 

If you have Python3 installed...

  • pip3 install --user scipy numpy sty
  • copy and save the code below to a file named ./colors
  • chmod +x ./colors
  • optionally move colors to a directory on your $PATH, or add its containing directory to $PATH
  • when you need to clear your mind, run colors.

This is designed to work in the terminal, even if you haven't started an X server. It uses only the core subset of ANSI color codes and only those characters that are mapped in (almost) all terminals. Sampling is biased toward a tertiary color pallet.

#!/usr/bin/env python3

import os,sys
from pylab import *
from numpy import *
from sty   import *
columns, rows = os.get_terminal_size(0)

'''
Approximate RGB values of linux terminal color codes. 
The first 8 colors are background colors and are slightly darker.
The next 8 colors are foreground colors and are slightly brighter. 
'''
rgb = array([[0,0,0],[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]])
rgb = concatenate([rgb*0.9,rgb*0.8+0.2])

'''
There are 8 background and 16 foreground colors supported on the linux terminal.
Construct all possible combinations with shading characters ░ and ▒. 
There are more foreground than background colors, so draw pure colors with █ 
(drawing with a blank space could only use the 8 background colors). 
'''
colors = {tuple(rgb[j]):bg(0)+fg(j)+'█' for j in range(16)}
for i in range(8):
    for j in range(16):
        if i!=j:
            colors[tuple((rgb[i]  +rgb[j])/2)] = bg(i)+fg(j)+'▒'
            colors[tuple((rgb[i]*2+rgb[j])/3)] = bg(i)+fg(j)+'░'

'''
Use one-dimensional drift-diffusion to sample a color waterfall. 
- Define color similarity using a radial-basis kernel. 
- Adjust transition probabilities so the 
  stationary distribution of the Markov chain is uniform.
- Defie a prior to concetrate colors away from grays and extreme values.
'''
cc = array([*colors.keys()])
D  = sum((cc[None,:,:]-cc[:,None,:])**2,axis=2)
P  = exp(-D*4)
P  = P/sum(P,axis=1)[:,None]
for i in range(100):
    P = P/sum(P,axis=0)
    P = P/sum(P,axis=1)[:,None]
P = log(P)
prior = -(norm(cc-0.5,axis=1)-0.57)**2*100
NCOLORS = len(cc)
i = 0
previous_line = int32(linspace(0,NCOLORS-1,columns))

try:
    while True:
        line = ''
        next_line = []
        for c in range(columns):
            p = exp(
                P[previous_line[(c-1+columns)%columns]]+
                P[previous_line[(c+1)%columns]]+
                P[previous_line[c]]+
                prior)
            p = p/sum(p)
            i = np.random.choice(arange(NCOLORS),p=p)
            line += colors[tuple(cc[i])]
            next_line.append(i)
        previous_line = int32(next_line)
        print('\n'+line,end='',flush=True)
finally:
    print(rs.all)
    sys.exit(0)

1 comment:

  1. I like it! (I had to also include a pip3 install of 'matplotlib' before it worked, though)

    ReplyDelete