Created
November 20, 2015 04:12
-
-
Save yuttie/f833742d5cfd343ead9d to your computer and use it in GitHub Desktop.
Find a color for terminals that is perceptually similar to a given color
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| from math import * | |
| from random import * | |
| import sys | |
| # ANSI colors | |
| ANSI_COLORS = [(0x00, 0x00, 0x00), | |
| (0x80, 0x00, 0x00), | |
| (0x00, 0x80, 0x00), | |
| (0x80, 0x80, 0x00), | |
| (0x00, 0x00, 0x80), | |
| (0x80, 0x00, 0x80), | |
| (0x00, 0x80, 0x80), | |
| (0xc0, 0xc0, 0xc0), | |
| (0x80, 0x80, 0x80), | |
| (0xff, 0x00, 0x00), | |
| (0x00, 0xff, 0x00), | |
| (0xff, 0xff, 0x00), | |
| (0x00, 0x00, 0xff), | |
| (0xff, 0x00, 0xff), | |
| (0x00, 0xff, 0xff), | |
| (0xff, 0xff, 0xff)] | |
| # xterm's 256 colors | |
| CUBE_INTENSITIES = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] | |
| def index2xtermrgb(i): | |
| if i < 16: | |
| return ANSI_COLORS[i] | |
| elif i < 232: | |
| tmp, b = divmod(i - 16, 6) | |
| tmp, g = divmod(tmp, 6) | |
| tmp, r = divmod(tmp, 6) | |
| r = CUBE_INTENSITIES[r] | |
| g = CUBE_INTENSITIES[g] | |
| b = CUBE_INTENSITIES[b] | |
| return (r, g, b) | |
| else: | |
| v = 10 * (i - 232) + 8 | |
| return (v, v, v) | |
| XTERM_COLORS = [index2xtermrgb(i) for i in range(256)] | |
| # Conversion | |
| def rgb2xyz(color): | |
| def srgb2linear(c): | |
| a = 0.055 | |
| return c / 12.92 if c <= 0.04045 else ((c + a) / (1 + a))**2.4 | |
| r, g, b = color | |
| r = r / 0xff | |
| g = g / 0xff | |
| b = b / 0xff | |
| r_lin = srgb2linear(r) | |
| g_lin = srgb2linear(g) | |
| b_lin = srgb2linear(b) | |
| x = 0.4124 * r_lin + 0.3576 * g_lin + 0.1805 * b_lin | |
| y = 0.2126 * r_lin + 0.7152 * g_lin + 0.0722 * b_lin | |
| z = 0.0193 * r_lin + 0.1192 * g_lin + 0.9505 * b_lin | |
| return (x, y, z) | |
| def xyz2lab(color): | |
| def f(t): | |
| return t**(1/3) if t > (6/29)**3 else (29/6)**2 * t / 3 + 4/29 | |
| x_n = 0.95047 | |
| y_n = 1.00000 | |
| z_n = 1.08883 | |
| x, y, z = color | |
| l = 116 * f(y / y_n) - 16 | |
| a = 500 * (f(x / x_n) - f(y / y_n)) | |
| b = 200 * (f(y / y_n) - f(z / z_n)) | |
| return (l, a, b) | |
| # Approximation | |
| def distance(color1, color2): | |
| r1, g1, b1 = color1 | |
| r2, g2, b2 = color2 | |
| return sqrt((r2 - r1)**2 + (g2 - g1)**2 + (b2 - b1)**2) | |
| def deltaE(color1, color2): | |
| l1, a1, b1 = color1 | |
| l2, a2, b2 = color2 | |
| d_l = l1 - l2 | |
| c1 = sqrt(a1**2 + b1**2) | |
| c2 = sqrt(a2**2 + b2**2) | |
| d_c = c1 - c2 | |
| d_a = a1 - a2 | |
| d_b = b1 - b2 | |
| d_h2 = d_a**2 + d_b**2 - d_c**2 | |
| d_h = 0 if d_h2 < 0 else sqrt(d_h2) | |
| k_l = 1 | |
| k_c = 1 | |
| k_h = 1 | |
| k1 = 0.045 | |
| k2 = 0.015 | |
| s_l = 1 | |
| s_c = 1 + k1 * c1 | |
| s_h = 1 + k2 * c1 | |
| d_e2 = (d_l / (k_l * s_l))**2 + (d_c / (k_c * s_c))**2 + (d_h / (k_h * s_h))**2 | |
| d_e = 0 if d_e2 < 0 else sqrt(d_e2) | |
| return d_e | |
| def approx(color, palette, dist_func = distance): | |
| min_i = -1 | |
| min_dist = 1000000000 | |
| for i, c in enumerate(palette): | |
| d = dist_func(color, c) | |
| if d < min_dist: | |
| min_i = i | |
| min_dist = d | |
| return min_i | |
| # Benchmark | |
| XTERM_COLORS_LAB = list(map(lambda c: xyz2lab(rgb2xyz(c)), XTERM_COLORS)) | |
| print('''<!doctype html> | |
| <head> | |
| <style> | |
| .g { | |
| display: inline-flex; | |
| flex-direction: row; | |
| width: 4em; | |
| height: 4em; | |
| } | |
| .c { | |
| flex: auto; | |
| } | |
| </style> | |
| </head> | |
| ''') | |
| count = 0 | |
| for rgb in ((r, g, b) for r in range(0xff) for g in range(0xff) for b in range(0xff)): | |
| lab = xyz2lab(rgb2xyz(rgb)) | |
| i_srgb = approx(rgb, XTERM_COLORS) | |
| i_lab = approx(lab, XTERM_COLORS_LAB) | |
| i_lab_deltae = approx(lab, XTERM_COLORS_LAB, deltaE) | |
| if i_srgb != i_lab or i_srgb != i_lab_deltae: | |
| print('<div class="g">') | |
| print(' <div class="c" style="background:#{:02x}{:02x}{:02x};"></div>'.format(*XTERM_COLORS[i_srgb])) | |
| print(' <div class="c" style="background:#{:02x}{:02x}{:02x};"></div>'.format(*rgb)) | |
| print(' <div class="c" style="background:#{:02x}{:02x}{:02x};"></div>'.format(*XTERM_COLORS[i_lab])) | |
| print(' <div class="c" style="background:#{:02x}{:02x}{:02x};"></div>'.format(*XTERM_COLORS[i_lab_deltae])) | |
| print('</div>') | |
| count = count + 1 | |
| sys.stderr.write("\r{:d}".format(count)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment