-
-
Save jwodder/4bf350e2d72b547b22dc9de52148ccbe to your computer and use it in GitHub Desktop.
Print a 256-colour test pattern in the terminal
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 argparse | |
from dataclasses import dataclass | |
from typing import Optional | |
# John Thorvald Wodder II, 2016-2021. | |
# Tom Hale, 2016. MIT Licence. | |
# Print out 256 colours, with each number printed in its corresponding colour | |
# See http://askubuntu.com/questions/821157/print-a-256-color-test-pattern-in-the-terminal/821163#821163 | |
PRINTABLE_COLOURS = 256 | |
@dataclass | |
class ColorBlockPrinter: | |
# True: colored text on white/black background | |
# False: white/black text on colored background | |
reverse: bool | |
fg: Optional[int] | |
bg: Optional[int] | |
# Print a coloured block with the number of that colour | |
def print_colour(self, colour): | |
contrast = contrast_colour(colour) | |
if self.reverse: | |
fg = colour | |
bg = contrast | |
else: | |
fg = contrast | |
bg = colour | |
if self.fg is not None: | |
fg = self.fg | |
if self.bg is not None: | |
bg = self.bg | |
print(colorize(f"{colour:3}", fg=fg, bg=bg), end=" ") | |
# Starting at ``start``, print a run of ``qty`` colours | |
def print_run(self, start, qty): | |
for i in range(start, min(start + qty, PRINTABLE_COLOURS)): | |
self.print_colour(i) | |
print(" ", end="") | |
# Print blocks of colours | |
def print_blocks(self, start, end, block_cols, block_rows, blocks_per_line): | |
# ``end`` is inclusive | |
block_length = block_cols * block_rows | |
# Print sets of blocks | |
for i in range(start, end + 1, blocks_per_line * block_length): | |
print() # Space before each set of blocks | |
# For each block row | |
for _ in range(block_rows): | |
# Print block columns for all blocks on the line | |
for block in range(blocks_per_line): | |
self.print_run(i + (block * block_length), block_cols) | |
i += block_cols # Prepare to print the next row | |
print() | |
def run(self): | |
# The first 16 colours are spread over the whole spectrum | |
self.print_run(0, 16) | |
print() | |
# 6x6x6 colour cube between 16 and 231 inclusive | |
self.print_blocks(16, 231, 6, 6, 3) | |
# Not 50, but 24 Shades of Grey | |
self.print_blocks(232, 255, 12, 2, 1) | |
def colorize(txt, fg=None, bg=None): | |
def sgrcolor(index, start): | |
if index is None: | |
return () | |
elif index == -1: | |
return (start + 9,) | |
elif index < 16: | |
a, b = divmod(index, 8) | |
return (start + 60 * a + b,) | |
else: | |
return (start + 8, 5, index) | |
s = ";".join(map(str, sgrcolor(fg, 30) + sgrcolor(bg, 40))) | |
return f"\x1B[{s}m{txt}\x1B[0m" if s else txt | |
# Return a colour that contrasts with the given colour | |
def contrast_colour(colour): | |
if colour < 16: # Initial 16 ANSI colours | |
return 15 if colour == 0 else 0 | |
# Greyscale # rgb_R = rgb_G = rgb_B = (number - 232) * 10 + 8 | |
if colour > 231: # Greyscale ramp | |
return 15 if colour < 244 else 0 | |
# All other colours: | |
# 6x6x6 colour cube = 16 + 36*R + 6*G + B # Where RGB are [0..5] | |
# See http://stackoverflow.com/a/27165165/5353461 | |
# r=(colour-16) // 36 | |
g = ((colour - 16) % 36) // 6 | |
# b=(colour-16) % 6 | |
# If luminance is bright, print number in black, white otherwise. | |
# Green contributes 587/1000 to human perceived luminance - ITU R-REC-BT.601 | |
return 0 if g > 2 else 15 | |
# Uncomment the below for more precise luminance calculations | |
# # Calculate percieved brightness | |
# # See https://www.w3.org/TR/AERT#color-contrast | |
# # and http://www.itu.int/rec/R-REC-BT.601 | |
# # Luminance is in range 0..5000 as each value is 0..5 | |
# luminance=(r * 299) + (g * 587) + (b * 114) | |
# return 0 if luminance > 2500 else 15 | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"--bg", type=int, help="Use a constant background color", metavar="INT" | |
) | |
parser.add_argument( | |
"--fg", type=int, help="Use a constant foreground color", metavar="INT" | |
) | |
parser.add_argument( | |
"--reverse", | |
action="store_true", | |
help="Color the text instead of the backgrounds", | |
) | |
args = parser.parse_args() | |
colorer = ColorBlockPrinter(reverse=args.reverse, fg=args.fg, bg=args.bg) | |
colorer.run() | |
if __name__ == "__main__": | |
main() # pragma: no cover |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment