Last active
November 5, 2023 12:56
-
-
Save Cenness/5edf05c5faa97d7c82a7ee372abb038e to your computer and use it in GitHub Desktop.
Print out color pairs with their contrast
This file contains 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
[ | |
{ | |
"type": "colordef", | |
"BLACK": [ 33, 34, 37 ], | |
"RED": [ 254, 63, 22 ], | |
"GREEN": [ 60, 168, 103 ], | |
"BROWN": [ 223, 112, 30 ], | |
"BLUE": [ 0, 106, 170 ], | |
"MAGENTA": [ 235, 82, 177 ], | |
"CYAN": [ 7, 163, 167 ], | |
"DGRAY": [ 138, 139, 172 ], | |
"LRED": [ 255, 158, 138 ], | |
"LGREEN": [ 152, 206, 119 ], | |
"YELLOW": [ 212, 182, 52 ], | |
"LBLUE": [ 30, 171, 255 ], | |
"LMAGENTA": [ 247, 156, 188 ], | |
"LCYAN": [ 58, 206, 192 ], | |
"GRAY": [ 184, 186, 188 ], | |
"WHITE": [ 255, 255, 255 ] | |
} | |
] |
This file contains 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/env python3 | |
import os, sys, argparse, json | |
def translate(value, value_min_range, value_max_range, min_range, max_range): | |
value_span = value_max_range - value_min_range | |
span = max_range - min_range | |
scaled = float(value - value_min_range) / float(value_span) | |
return min_range + (scaled * span) | |
def rgb_as_int(rgb1, rgb2): | |
n_rgb1 = tuple([translate(c, 0, 255, 0, 1) for c in rgb1]) | |
n_rgb2 = tuple([translate(c, 0, 255, 0, 1) for c in rgb2]) | |
return rgb(n_rgb1, n_rgb2) | |
# https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio | |
def rgb(rgb1, rgb2): | |
for r, g, b in (rgb1, rgb2): | |
if not 0.0 <= r <= 1.0: | |
raise ValueError("r is out of valid range (0.0 - 1.0)") | |
if not 0.0 <= g <= 1.0: | |
raise ValueError("g is out of valid range (0.0 - 1.0)") | |
if not 0.0 <= b <= 1.0: | |
raise ValueError("b is out of valid range (0.0 - 1.0)") | |
l1 = _relative_luminance(*rgb1) | |
l2 = _relative_luminance(*rgb2) | |
if l1 > l2: | |
contrast = (l1 + 0.05) / (l2 + 0.05) | |
else: | |
contrast = (l2 + 0.05) / (l1 + 0.05) | |
if contrast >= 7.0: | |
return "AAA" | |
elif contrast >= 4.5: | |
return "AA" | |
else: | |
return round(contrast, 3) | |
# https://www.w3.org/TR/WCAG21/#dfn-relative-luminance | |
def _relative_luminance(r, g, b): | |
r = _linearize(r) | |
g = _linearize(g) | |
b = _linearize(b) | |
return 0.2126 * r + 0.7152 * g + 0.0722 * b | |
def _linearize(v): | |
if v <= 0.03928: | |
return v / 12.92 | |
else: | |
return ((v + 0.055) / 1.055) ** 2.4 | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"-j", | |
"--colors", | |
dest="base_colors", | |
type=str, | |
help="path to base colors json", | |
default="", | |
) | |
parser.add_argument( | |
"-p", | |
"--print", | |
dest="print", | |
type=str, | |
help="print out color pairs, you'll need terminal with 24-bit color support enabled; enabled by default", | |
default="yes", | |
choices=["yes", "no"], | |
) | |
args = parser.parse_args(args=None if sys.argv[1:] else ["--help"]) | |
with open(args.base_colors, "rt") as f: | |
bc_json = json.load(f)[0] | |
colors = [] | |
column_width = 0 | |
for record in bc_json: | |
if record != "type": | |
column_width = max(column_width, len(record) + 2) | |
pair = { | |
"name": record, | |
"rgb": (bc_json[record][0], bc_json[record][1], bc_json[record][2]), | |
} | |
colors.append(pair) | |
for c1 in colors: | |
for c2 in colors: | |
if c1["name"] != c2["name"]: | |
if args.print == "yes": | |
print( | |
f'{c1["name"]:>{column_width}s} {c2["name"]:>{column_width}s} {rgb_as_int(c1["rgb"], c2["rgb"]):>7} \x1b[48;2;{c1["rgb"][0]};{c1["rgb"][1]};{c1["rgb"][2]}m\x1b[38;2;{c2["rgb"][0]};{c2["rgb"][1]};{c2["rgb"][2]}m {c2["rgb"][0]:3} {c2["rgb"][1]:3} {c2["rgb"][2]:3} \x1b[0m' | |
) | |
else: | |
print( | |
f'{c1["name"]:>{column_width}s} {c2["name"]:>{column_width}s} {rgb_as_int(c1["rgb"], c2["rgb"]):>7}' | |
) |
This file contains 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/env python3 | |
import os, sys, argparse, json | |
## https://github.com/Myndex/SAPC-APCA/blob/6ef8c9982973ac4514fa69c717b838d9c0093251/documentation/APCA-W3-LaTeX.md | |
## direct contrast, without font and score interpolation | |
def lightness_contrast(rgb1, rgb2): | |
s_apc = _lightness_difference(*rgb1, *rgb2) | |
w_offset = 0.027 | |
w_clamp = 0.1 | |
if abs(s_apc) < w_clamp: | |
return 0 | |
if s_apc > 0: | |
return abs(round((s_apc - w_offset) * 100.0, 1)) | |
else: | |
return abs(round((s_apc + w_offset) * 100.0, 1)) | |
def _lightness_difference(r1, g1, b1, r2, g2, b2): | |
w_scale = 1.14 | |
d_min = 0.0005 | |
n_tx = 0.57 | |
n_bg = 0.56 | |
r_tx = 0.62 | |
r_bg = 0.65 | |
y_bg = _luminance(r1, g1, b1) | |
y_tx = _luminance(r2, g2, b2) | |
if abs(y_bg - y_tx) < d_min: | |
return 0 | |
if y_bg > y_tx: | |
return (y_bg**n_bg - y_tx**n_tx) * w_scale | |
else: | |
return (y_bg**r_bg - y_tx**r_tx) * w_scale | |
def _luminance(r, g, b): | |
s_trc = 2.4 | |
b_thrsh = 0.022 | |
b_clip = 1.414 | |
y_r = ((r / 255.0) ** s_trc) * 0.2126729 | |
y_g = ((g / 255.0) ** s_trc) * 0.7151522 | |
y_b = ((b / 255.0) ** s_trc) * 0.0721750 | |
y = y_r + y_g + y_b | |
if y < b_thrsh: | |
return y + ((b_thrsh - y) ** b_clip) | |
else: | |
return y | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"-j", | |
"--colors", | |
dest="base_colors", | |
type=str, | |
help="path to json", | |
default="", | |
) | |
parser.add_argument( | |
"-p", | |
"--print", | |
dest="print", | |
type=str, | |
help="print out color pairs", | |
default="yes", | |
choices=["yes", "no"], | |
) | |
args = parser.parse_args(args=None if sys.argv[1:] else ["--help"]) | |
with open(args.base_colors, "rt") as f: | |
bc_json = json.load(f)[0] | |
colors = [] | |
column_width = 0 | |
for record in bc_json: | |
if record != "type": | |
column_width = max(column_width, len(record) + 2) | |
pair = { | |
"name": record, | |
"rgb": (bc_json[record][0], bc_json[record][1], bc_json[record][2]), | |
} | |
colors.append(pair) | |
for c1 in colors: | |
for c2 in colors: | |
if c1["name"] != c2["name"]: | |
if args.print == "yes": | |
print( | |
f'{c1["name"]:>{column_width}s} {c2["name"]:>{column_width}s} {lightness_contrast(c1["rgb"], c2["rgb"]):>6} \x1b[48;2;{c1["rgb"][0]};{c1["rgb"][1]};{c1["rgb"][2]}m\x1b[38;2;{c2["rgb"][0]};{c2["rgb"][1]};{c2["rgb"][2]}m {c2["rgb"][0]:3} {c2["rgb"][1]:3} {c2["rgb"][2]:3} \x1b[0m' | |
) | |
else: | |
print( | |
f'{c1["name"]:>{column_width}s} {c2["name"]:>{column_width}s} {lightness_contrast(c1["rgb"], c2["rgb"]):>6}' | |
) |
Author
Cenness
commented
Nov 5, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment