Skip to content

Latest commit

 

History

History
132 lines (102 loc) · 6.43 KB

README.md

File metadata and controls

132 lines (102 loc) · 6.43 KB

Technicolor

Handy functions for colorizing terminal output.

Installation

Download technicolor and put in on your path.

wget https://raw.githubusercontent.com/edemko/technicolor/master/technicolor -O /bin/technicolor

It's a Bourne Shell script, so it should require nothing special on Unixen and Unix-alikes.

Usage

Technicolor can be used as an executable:

  • technicolor test Print number of colors supported. If there are few enough colors, each code will be printed in the corresponding color.
  • technicolor code <RGB> Print the terminal color code nearest the given rgb-space hex code. The code is colorized, and some background in that color is also produced. The leading hash is optional (but why would you ever actually use it?).
  • technicolor <RGB> <TEXT> [FLAGS…] acts as a colorized echo—like. Arguments are like withaf (see below), but trailing trailing newline isn't suppressed.
  • technicolor <ARGS> <RGB> <TEXT> [FLAGS…] acts as a colorized echo. Arguments are like withcol (see below), but trailing trailing newline isn't suppressed. Note that <ARGS> here is mandatory, as otherwise it will act as withaf.
  • technicolor (with no arguments) does nothing.

Or it can be sourced —source "$(which technicolor)" or . "$(which technicolor)"— to provide a number of functions:

  • withcol <ARGS…> <TEXT> Where ARGS are any combination of -f<RGB>, -b<RGB> or -a<ATTR> for foreground, background, and attribute respectively. Colors are interpreted with rgb2sgr. Recognized attributes are bold, ul, rev, blink, invis, so, and ul. This sets the colors and attributes (later flags may override earlier ones), prints the TEXT (i.e. everything after ARGS is passed to echo -n), and finally resets all colors and attributes (i.e. tput sgr0). If ARGS includes a -s flag, then the reset code is not produced. Though it is possible to use withcol '' to clear attributes, I recommend using nocol. This is intended for use colorizing part of a larger string, as in echo "$(withcol -f00 -b [ERROR]) bus factor was too low!"; to include a trailing newline, use technicolor as a script instead.
  • withaf <RGB> <TEXT> <ARGS...> As withcol, but only sets the foreground color. This is included because it is such a common use case. This is intended for use colorizing part of a larger string, as in echo "$(withaf -ff0 [WARNING]) look both ways before crossing"; to include a trailing newline, use technicolor as a script instead.
  • nocol Clear all colors and attributes.
  • rgb2sgr Convert a 3- or 6-digit RGB hexcode into the nearest code for tput.

Specifying Colors

Terminals may not (tend not to?) support 24-bit color. In fact, the ones I use commonly come with at best 256 colors like it was 1995. Nevertheless, technicolor accepts hexcodes, and attempts to select a nearby terminal color.

These hexcodes may be specified as either full six-digit hexcodes or as three-digit hexcodes. The three-digit codes are equivalent to the six-digit codes obtained by multiplying each channel by 16. E.g. 70f === 7000f0. Note that there is no leading #, as this would be quite inconvenient in shell. Nevertheless, we accept and ignore a leading hash, since it may be generated by another program.

Since terminal color spaces can be quite baroque, I will now detail the algorithms I use to select an approximate color.

256-Colors

I use three different strategies to select the correct color from xterm256 color space. First, the 16 ANSI colors are given special treatment, then greyscale is attempted when appropriate, and finally an RGB color cube is used. See bash-ui for more information on this color encoding.

Known "primary" colors use the ansi16 subspace (codes 0-15). In general, these match the regex [0d]{3} for 0-7, and [0f]{3} for 8-15, but a handful don't follow this pattern (see list by). I retrieved these by approximating the xterm colors listed on Wikipedia so that they could be produced with three-digit codes. When specifying a six-digit number, primaries are detected when the low nybble in each channel is 0, but if the high nybble if f, then the low nybble is also allowed to be f without changing the color.

  • 00f is "blue" (replacing 00d)
  • eee is "white" (replacing ddd)
  • 888 is "bright black"
  • 55f is "bright blue" (00f is used for "blue")

If all three channels are identical, the grey26 subspace—a 26-color greyscale space—is used. I simply perform a linear transform from 0-255 to 0-25. (modulo integer division inaccuracies).

Finally, xterm256 includes an rgb216 subspace, which is sort-of a 6x6x6 RGB color cube. Each channel is linearly transformed from 0-255 to 0-5 (again modulo integer division inaccuracies). The actual "cube" quite unintuitive unfortunately; e.g. #77f looks rather more lavender than light blue to me. Nevertheless, programmers who are too creative for the plain ANSI codes can always test-and-check with technicolor test.

Why Only Hexcodes?

Hexcodes have a well-defined semantics outside of any particular color encoding scheme. Thus, if you use a hexcode instead of directly using ANSI or SGR codes, your colors will be "more" "portable".

I'm open to adding support for specifying SGR codes directly, as long as they can be translated into other color spaces "reliably". Of course, HSV would be great as well, I just don't want to do the math when RGB gets me where I personally need to go.

I may even decide someday to allow for named colors configurable by the system. That way, one could write green to specify colors like "the user's favorite green", or some such nonsense.

Limitations

At the moment, I am only handling 256-color terminals. Support for 16-color terminals could be added relatively easily. I don't currently have a good handle on the capabilities for true color (16 million colors).

True Color Notes

True color seems to be as easy as echo -e "\e[38;2;${r};${g};${b}m" (foreground) and echo -e "\e[48;2;${r};${g};${b}m" background. The real question is how to determine it per-terminal rather than setting it for the box (which may be connected to by an unexpected terminal)?