From: Iain Patterson Date: Tue, 25 Nov 2008 15:31:34 +0000 (+0000) Subject: Added terminal_colors script. X-Git-Url: http://git.iain.cx/?a=commitdiff_plain;h=6ef0e47d52097e27a858b45ff709300f2ebdd8ba;p=profile.git Added terminal_colors script. git-svn-id: https://svn.cambridge.iain.cx/profile/trunk@154 6be0d1a5-5cfe-0310-89b6-964be062b18b --- diff --git a/terminal_colors b/terminal_colors new file mode 100755 index 0000000..75ef3d2 --- /dev/null +++ b/terminal_colors @@ -0,0 +1,390 @@ +#!/usr/bin/env python + +""" +Version 1.0 + +Displays 256, 88 and 16 color tables depending on what the terminal supports. +Also provides for conversion between 256 and 88 color values. + +Note on coding style. I was playing around with using classes as simple +namespaces (sort of like modules); ie. having classes that have all +staticmethods and never get instatiated. + +""" + +import curses +from optparse import OptionParser, make_option +from math import ceil, sqrt + +option_list = [ + make_option("-x", "--hex", action="store_true", dest="hex", + default=False, help="Include hex color numbers on chart."), + make_option("-n", "--numbers", action="store_true", dest="numbers", + default=False, help="Include color escape numbers on chart."), + make_option("-b", "--block", action="store_true", dest="block", + default=True, help="Display as block format (vs cube) [default]."), + make_option("-c", "--cube-slice", action="store_true", dest="cube", + default=False, help="Display as cube slices (vs block)."), + make_option("-v", "--vertical", action="store_true", dest="vertical", + default=True, help="Display with vertical orientation [default]."), + make_option("-z", "--horizontal", action="store_true", dest="horizontal", + default=False, help="Display with horizontal orientation."), + make_option("-l", "--rgb", action="store_true", dest="rgb", + default=False, help="Long format. RGB values text."), + make_option("-r", "--256to88", action="store", dest="reduce", + metavar="N", type="int", + help="Convert (reduce) 256 color value N to an 88 color value."), + make_option("-e", "--88to256", action="store", dest="expand", + metavar="N", type="int", + help="Convert (expand) 88 color value N to an 256 color value."), + ] + +version = __doc__.split('\n')[1] +parser = OptionParser(version=version, option_list=option_list) +(options, args) = parser.parse_args() + +# output constants +fg_escape = "\x1b[38;5;%dm" +bg_escape = "\x1b[48;5;%dm" +clear = "\x1b[0m" + +class _staticmethods(type): + """ Got tired of adding @staticmethod in front of every method. + """ + def __new__(m, n, b, d): + """ turn all methods in to staticmethods. + staticmethod() deals correctly with class attributes. + """ + for (n, f) in d.items(): + d[n] = staticmethod(f) + return type.__new__(m, n, b, d) + + +class term16(object): + """ Basic 16 color terminal. + """ + + __metaclass__ = _staticmethods + + def _label(): + if options.numbers: + return " %2d " + elif options.hex: + return " %2x " + return " " + + def fg(label, n): + fg = n < 8 and 15 or 0 + try: + return fg_escape % fg + label % n + clear + except TypeError: + return fg_escape % fg + label + clear + + def _color_table(): + label = term16._label() + return [ + [bg_escape % n + term16.fg(label, n) + clear + for n in range(8)], + [bg_escape % n + term16.fg(label, n) + clear + for n in range(8,16)] + ] + + def display(): + """ display 16 color info + """ + print "System colors:" + colors = term16._color_table() + for r in colors: + print ''.join(i for i in r) + +class term256(term16): + """ eg. xterm-256 + """ + + def _rgb_lookup(): + """ color rgb lookup dict + """ + rgb = "%02x/%02x/%02x" + cincr = [0] + [95+40*n for n in range(5)] + color_rgb = [rgb % (i, j, k) + for i in cincr for j in cincr for k in cincr] + color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb)) + greys = [rgb % (((8+n),)*3) for n in range(0, 240, 10)] + greys = dict(zip(range(232, 256), greys)) + color_rgb.update(greys) + return color_rgb + + def _rgb_color_table(): + """ 256 color info + """ + label = "% 4d: %s" + _rgb = term256._rgb_lookup() + return [[fg_escape % n + label % (n, _rgb[n]) + clear + for n in [i+j for j in range(6)]] + for i in range(16, 256, 6)] + + def _rgb_display(): + """ display colors with rgb hex info + """ + colors = term256._rgb_color_table() + while colors: + rows, colors = colors[:6], colors[6:] + for r in zip(*rows): + print ''.join(i for i in r) + print + + def _label(): + if options.numbers: + return "%3d " + elif options.hex: + return " %2x " + return " " + + def fg(label, n): + if n < 232: + fg = n < 124 and 15 or 0 + else: + fg = n < 244 and 15 or 0 + try: + return fg_escape % fg + label % n + clear + except TypeError: + return fg_escape % fg + label + clear + + def _color_table(): + """ compact 256 color info + """ + label = term256._label() + return [[bg_escape % n + term256.fg(label, n) + clear + for n in [i+j for j in range(6)]] + for i in range(16, 232, 6)] + + def _grey_table(): + """ compact grey table + """ + label = " " + term256._label() + return [[bg_escape % n + term256.fg(label, n) + clear + for n in [i+j for j in range(12)]] + for i in range(232, 256, 12)] + + def _compact_display(): + """ display colors in compact format + """ + colors = term256._color_table() + if options.cube: + _cube(colors) + elif options.block: + _block(colors) + print + print "Greyscale ramp:" + greys = term256._grey_table() + for r in greys: + print ''.join(i for i in r) + + def display(): + """ display 256 color info (+ 16 in compact format) + """ + if options.rgb: + print "Xterm RGB values for 6x6x6 color cube and greyscale." + print + term256._rgb_display() + else: + term16.display() + print + print "6x6x6 color cube:" + term256._compact_display() + + +class term88(term16): + """ xterm-88 or urxvt + """ + + def _rgb_lookup(): + """ color rgb lookup dict + """ + rgb = "%02x/%02x/%02x" + cincr = [0, 0x8b, 0xcd, 0xff] + color_rgb = [rgb % (i, j, k) + for i in cincr for j in cincr for k in cincr] + color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb)) + greys = [rgb % ((n,)*3) + for n in [0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7]] + greys = dict(zip(range(80, 88), greys)) + color_rgb.update(greys) + return color_rgb + + def _rgb_color_table(): + """ 256 color info + """ + label = "% 4d: %s" + _rgb = term88._rgb_lookup() + return [[fg_escape % n + label % (n, _rgb[n]) + clear + for n in [i+j for j in range(4)]] + for i in range(16, 88, 4)] + + def _rgb_display(): + """ display colors with rgb hex info + """ + colors = term88._rgb_color_table() + while colors: + rows, colors = colors[:4], colors[4:] + for r in zip(*rows): + print ''.join(i for i in r) + print + + def _label(): + if options.numbers: + return " %2d " + elif options.hex: + return " %2x " + return " " + + def fg(label, n): + if n < 80: + fg = n < 48 and 15 or 0 + else: + fg = n < 84 and 15 or 0 + try: + return fg_escape % fg + label % n + clear + except TypeError: + return fg_escape % fg + label + clear + + def _color_table(): + """ 88 color info + """ + label = term88._label() + return [[bg_escape % n + term88.fg(label, n) + clear + for n in [i+j for j in range(4)]] + for i in range(16, 80, 4)] + + def _grey_table(): + """ 88 color grey info + """ + label = term88._label() + return [bg_escape % n + term88.fg(label, n) + clear + for n in range(80, 88)] + + def display(): + """ display 16 + 88 color info + """ + if options.rgb: + print "Xterm RGB values for 4x4x4 color cube and greyscale." + print + term88._rgb_display() + else: + term16.display() + print + print "4x4x4 color cube:" + colors = term88._color_table() + if options.cube: + _cube(colors) + elif options.block: + _block(colors) + print + print "Greyscale ramp:" + greys = term88._grey_table() + print ''.join(i for i in greys) + +def _cube(colors): + if options.horizontal: + def _horizontal(colors): + size = int(sqrt(len(colors))) + for n in (n*size for n in range(size)): + colors[n:n+size] = zip(*colors[n:n+size]) + while colors: + rows, colors = colors[:size*2], colors[size*2:] + for n in range(size): + print ''.join(i + for i in rows[n]+tuple(reversed(rows[n+size]))) + if colors: print + _horizontal(colors) + else: #options.vertical - default + def _vertical(colors): + size = int(sqrt(len(colors))) + top = [colors[n:len(colors):size*2] for n in range(size)] + bottom = [colors[n+size:len(colors):size*2] + for n in reversed(range(size))] + for group in [top, bottom]: + for rows in group: + for r in rows: + print ''.join(i for i in r), + print + _vertical(colors) + +def _block(colors): + size = int(sqrt(len(colors))) + if not options.horizontal: + for n in (n*size for n in range(size)): + colors[n:n+size] = zip(*colors[n:n+size]) + while colors: + half = size*(size/2) + rows, colors = colors[:half], colors[half:] + for n in range(size): + for r in rows[n:len(rows):size]: + print ''.join(i for i in r), + print + if colors: print + +def convert88to256(n): + """ 88 (4x4x4) color cube to 256 (6x6x6) color cube values + """ + if n < 16: + return n + elif n > 79: + return 234 + (3 * (n - 80)) + else: + def m(n): + "0->0, 1->1, 2->3, 3->5" + return n and n + n-1 or n + b = n - 16 + x = b % 4 + y = (b / 4) % 4 + z = b / 16 + return 16 + m(x) + (6 * m(y) + 36 * m(z)) + +def convert256to88(n): + """ 256 (6x6x6) color cube to 88 (4x4x4) color cube values + """ + if n < 16: + return n + elif n > 231: + if n < 234: + return 0 + return 80 + ((n - 234) / 3) + else: + def m(n, _ratio=(4./6.)): + if n < 2: + return int(ceil(_ratio*n)) + else: + return int(_ratio*n) + b = n - 16 + x = b % 6 + y = (b / 6) % 6 + z = b / 36 + return 16 + m(x) + (4 * m(y) + 16 * m(z)) + +def _terminal(): + """ detect # of colors supported by terminal and return appropriate + terminal class + """ + curses.setupterm() + num_colors = curses.tigetnum('colors') + if num_colors > 0: + return {16:term16, 88:term88, 256:term256}.get(num_colors, term16) + +def main(): + if options.reduce: + v = convert256to88(options.reduce) + # reconvert back to display reduction in context + print "%s (equivalent to 256 value: %s)" % (v, convert88to256(v)) + elif options.expand: + print convert88to256(options.expand) + else: + term = _terminal() + if term is None: + print "Your terminal reports that it has no color support." + else: + term.display() + +if __name__ == "__main__": + main() +