6 Displays 256, 88 and 16 color tables depending on what the terminal supports.
7 Also provides for conversion between 256 and 88 color values.
9 Note on coding style. I was playing around with using classes as simple
10 namespaces (sort of like modules); ie. having classes that have all
11 staticmethods and never get instatiated.
16 from optparse import OptionParser, make_option
17 from math import ceil, sqrt
20 make_option("-x", "--hex", action="store_true", dest="hex",
21 default=False, help="Include hex color numbers on chart."),
22 make_option("-n", "--numbers", action="store_true", dest="numbers",
23 default=False, help="Include color escape numbers on chart."),
24 make_option("-b", "--block", action="store_true", dest="block",
25 default=True, help="Display as block format (vs cube) [default]."),
26 make_option("-c", "--cube-slice", action="store_true", dest="cube",
27 default=False, help="Display as cube slices (vs block)."),
28 make_option("-v", "--vertical", action="store_true", dest="vertical",
29 default=True, help="Display with vertical orientation [default]."),
30 make_option("-z", "--horizontal", action="store_true", dest="horizontal",
31 default=False, help="Display with horizontal orientation."),
32 make_option("-l", "--rgb", action="store_true", dest="rgb",
33 default=False, help="Long format. RGB values text."),
34 make_option("-r", "--256to88", action="store", dest="reduce",
35 metavar="N", type="int",
36 help="Convert (reduce) 256 color value N to an 88 color value."),
37 make_option("-e", "--88to256", action="store", dest="expand",
38 metavar="N", type="int",
39 help="Convert (expand) 88 color value N to an 256 color value."),
42 version = __doc__.split('\n')[1]
43 parser = OptionParser(version=version, option_list=option_list)
44 (options, args) = parser.parse_args()
47 fg_escape = "\x1b[38;5;%dm"
48 bg_escape = "\x1b[48;5;%dm"
51 class _staticmethods(type):
52 """ Got tired of adding @staticmethod in front of every method.
54 def __new__(m, n, b, d):
55 """ turn all methods in to staticmethods.
56 staticmethod() deals correctly with class attributes.
58 for (n, f) in d.items():
59 d[n] = staticmethod(f)
60 return type.__new__(m, n, b, d)
64 """ Basic 16 color terminal.
67 __metaclass__ = _staticmethods
77 fg = n < 8 and 15 or 0
79 return fg_escape % fg + label % n + clear
81 return fg_escape % fg + label + clear
84 label = term16._label()
86 [bg_escape % n + term16.fg(label, n) + clear
88 [bg_escape % n + term16.fg(label, n) + clear
93 """ display 16 color info
95 print "System colors:"
96 colors = term16._color_table()
98 print ''.join(i for i in r)
100 class term256(term16):
105 """ color rgb lookup dict
107 rgb = "%02x/%02x/%02x"
108 cincr = [0] + [95+40*n for n in range(5)]
109 color_rgb = [rgb % (i, j, k)
110 for i in cincr for j in cincr for k in cincr]
111 color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb))
112 greys = [rgb % (((8+n),)*3) for n in range(0, 240, 10)]
113 greys = dict(zip(range(232, 256), greys))
114 color_rgb.update(greys)
117 def _rgb_color_table():
121 _rgb = term256._rgb_lookup()
122 return [[fg_escape % n + label % (n, _rgb[n]) + clear
123 for n in [i+j for j in range(6)]]
124 for i in range(16, 256, 6)]
127 """ display colors with rgb hex info
129 colors = term256._rgb_color_table()
131 rows, colors = colors[:6], colors[6:]
133 print ''.join(i for i in r)
145 fg = n < 124 and 15 or 0
147 fg = n < 244 and 15 or 0
149 return fg_escape % fg + label % n + clear
151 return fg_escape % fg + label + clear
154 """ compact 256 color info
156 label = term256._label()
157 return [[bg_escape % n + term256.fg(label, n) + clear
158 for n in [i+j for j in range(6)]]
159 for i in range(16, 232, 6)]
162 """ compact grey table
164 label = " " + term256._label()
165 return [[bg_escape % n + term256.fg(label, n) + clear
166 for n in [i+j for j in range(12)]]
167 for i in range(232, 256, 12)]
169 def _compact_display():
170 """ display colors in compact format
172 colors = term256._color_table()
178 print "Greyscale ramp:"
179 greys = term256._grey_table()
181 print ''.join(i for i in r)
184 """ display 256 color info (+ 16 in compact format)
187 print "Xterm RGB values for 6x6x6 color cube and greyscale."
189 term256._rgb_display()
193 print "6x6x6 color cube:"
194 term256._compact_display()
197 class term88(term16):
198 """ xterm-88 or urxvt
202 """ color rgb lookup dict
204 rgb = "%02x/%02x/%02x"
205 cincr = [0, 0x8b, 0xcd, 0xff]
206 color_rgb = [rgb % (i, j, k)
207 for i in cincr for j in cincr for k in cincr]
208 color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb))
209 greys = [rgb % ((n,)*3)
210 for n in [0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7]]
211 greys = dict(zip(range(80, 88), greys))
212 color_rgb.update(greys)
215 def _rgb_color_table():
219 _rgb = term88._rgb_lookup()
220 return [[fg_escape % n + label % (n, _rgb[n]) + clear
221 for n in [i+j for j in range(4)]]
222 for i in range(16, 88, 4)]
225 """ display colors with rgb hex info
227 colors = term88._rgb_color_table()
229 rows, colors = colors[:4], colors[4:]
231 print ''.join(i for i in r)
243 fg = n < 48 and 15 or 0
245 fg = n < 84 and 15 or 0
247 return fg_escape % fg + label % n + clear
249 return fg_escape % fg + label + clear
254 label = term88._label()
255 return [[bg_escape % n + term88.fg(label, n) + clear
256 for n in [i+j for j in range(4)]]
257 for i in range(16, 80, 4)]
260 """ 88 color grey info
262 label = term88._label()
263 return [bg_escape % n + term88.fg(label, n) + clear
264 for n in range(80, 88)]
267 """ display 16 + 88 color info
270 print "Xterm RGB values for 4x4x4 color cube and greyscale."
272 term88._rgb_display()
276 print "4x4x4 color cube:"
277 colors = term88._color_table()
283 print "Greyscale ramp:"
284 greys = term88._grey_table()
285 print ''.join(i for i in greys)
288 if options.horizontal:
289 def _horizontal(colors):
290 size = int(sqrt(len(colors)))
291 for n in (n*size for n in range(size)):
292 colors[n:n+size] = zip(*colors[n:n+size])
294 rows, colors = colors[:size*2], colors[size*2:]
295 for n in range(size):
297 for i in rows[n]+tuple(reversed(rows[n+size])))
300 else: #options.vertical - default
301 def _vertical(colors):
302 size = int(sqrt(len(colors)))
303 top = [colors[n:len(colors):size*2] for n in range(size)]
304 bottom = [colors[n+size:len(colors):size*2]
305 for n in reversed(range(size))]
306 for group in [top, bottom]:
309 print ''.join(i for i in r),
314 size = int(sqrt(len(colors)))
315 if not options.horizontal:
316 for n in (n*size for n in range(size)):
317 colors[n:n+size] = zip(*colors[n:n+size])
320 rows, colors = colors[:half], colors[half:]
321 for n in range(size):
322 for r in rows[n:len(rows):size]:
323 print ''.join(i for i in r),
327 def convert88to256(n):
328 """ 88 (4x4x4) color cube to 256 (6x6x6) color cube values
333 return 234 + (3 * (n - 80))
336 "0->0, 1->1, 2->3, 3->5"
337 return n and n + n-1 or n
342 return 16 + m(x) + (6 * m(y) + 36 * m(z))
344 def convert256to88(n):
345 """ 256 (6x6x6) color cube to 88 (4x4x4) color cube values
352 return 80 + ((n - 234) / 3)
354 def m(n, _ratio=(4./6.)):
356 return int(ceil(_ratio*n))
363 return 16 + m(x) + (4 * m(y) + 16 * m(z))
366 """ detect # of colors supported by terminal and return appropriate
370 num_colors = curses.tigetnum('colors')
372 return {16:term16, 88:term88, 256:term256}.get(num_colors, term16)
376 v = convert256to88(options.reduce)
377 # reconvert back to display reduction in context
378 print "%s (equivalent to 256 value: %s)" % (v, convert88to256(v))
380 print convert88to256(options.expand)
384 print "Your terminal reports that it has no color support."
388 if __name__ == "__main__":