foobuzz

by Valentin, July 3 2016, in tech

Displaying colors in the terminal

The next version of my terminal-based ToDo application will include colors. This article is a synthesis of what I learned from implementing this feature.

In the terminal, color is coded via escape codes. Colors are just a specific feature of many existing ANSI escape codes that enable various actions and formatting for the terminal. An ANSI escape code is a sequence of ASCII characters in the following form:

ESC[n1;n2;...L

where ESC is the ESCape ASCII character (#27), L is a letter indicating what kind of formatting to turn on, and n1, n2,... are parameters for the given action/formatting. For example, the letter A (resp. B) is the code for moving the cursor up (resp. down), and its only parameter indicates how many cells the cursor should be moved. To get a demo, you can open a terminal and type:

echo -e "\e[5B"

The prompt for your next command should appear down the first one with a few spare lines. The -e flag allow the interpretation of backslash escape, the \e escape corresponding to the ESC character. The quotes are necessary for the whole ANSI escape code to be interpreted.

All graphical-related codes come with the letter m, with the parameter(s) indicating what kind of formatting to apply. Since such escape code turns "on" the associated formatting for all text following, there's the special parameter 0 (escape code ESC[0m) which resets everything back to normal. Bash also resets everything when a command has terminated, so if you test escape codes with echo as in the previous example, you probably won't have to use the reset escape code manually.

I wish the first example I could give would be parameter 5 which enables blinking, but apparently it's not supported anymore by most terminals :-( (much like the HTML <blink> element isn't supported anymore by browsers. Maybe ESC[5m was implemented by an engineer being drunk too.)

So, I'll directly get to colors. There's 9 different parameters reserved for foreground colors (color of the text). Parameters 30 through 37 encode the 8 primary colors: black, red, green, yellow, blue, magenta, cyan, white. So, for a green hello world, you would type:

echo -e "\e[32mHello, world!"

These colors correspond to the colors you obtain when you "turn on" the components of the RGB coordinates. For example, black is (0, 0, 0), red is (R, 0, 0), magenta is (R, 0, B), etc. Since there are 3 components there are 2^3 = 8 primary colors. For some reason escape codes [30-37] render dark flavors of the colors. For example, red is only RGB(205, 0, 0)[1]. Yellow is the most obvious one, you can try it yourself:

echo -e "\e[33mSupposed to be yellow!"

It turns out that if you mix colors with bold (parameter number 1), colors are pushed to their full intensity:

echo -e "\e[1;33mActually yellow!"

Then there's the parameter number 38. This one awaits other parameters after itself. Either one, either three. In the case of three additional parameters, those parameters indicates the RGB coordinates of the color. So-called "true colors" are only supported by some advanced terminal emulators, but not by xterm[2]. You can try it out with yellow again:

echo -e "\e[38;0;255;255mActually yellow!"

In the case of only one additional parameter, this parameter should be a number between 0 and 255 (included) corresponding to a color in the 256-colors palette. Now, what is "the 256-colors palette". Well, while there isn't one unique standard to store colors in only one byte, xterm comes with its specific palette which you can find here. It seems that 256 colors are supported quite widely.

This is how this palette work:

  • Codes in the range [0;7] are the 8 primary colors in dark mode (same as produced by the parameters 30 through 37 previously seen)
  • Codes in the range [8;15] are the bright version of the 8 primary colors (same as produced by parameters 30 through 37 mixed with bold)
  • Codes in the range [16;231] are all the other colorful colors (isn't that a nice term to designate non gray-level colors). Code 16 is (0, 0, 0) and then coordinates are incremented with regular steps. The first step is a 95 increment, then there are 4 other steps which are 40 increment. So it goes this way:

    16  (0, 0, 0)
    17  (0, 0, 95)
    18  (0, 0, 135)
    19  (0, 0, 175)
    20  (0, 0, 215)
    21  (0, 0, 255)
    22  (0, 95, 0)
    23  (0, 95, 95)
    24  (0, 95, 135)
    ...
    231 (255, 255, 255)

  • Codes in [232;255] are gray levels (R = G = B). It starts at (8, 8, 8) and increments by steps of 10, arriving at (238, 238, 238) after 23 steps.

Finally, there's parameter number 39 that sets the foreground color to its default. It can be used as a foreground-color-specific reset.

Parameters 40 through 49 define the same behavior, but for background color.

Links

[1] RGB values for the 8 primary colors in different environments

[2] List of terminal emulators supporting true colors