Terminal Colors

Posted: Mar 28, 2020.
Tags:

In this post, I explore how terminals display color, a two-stage process involving ANSI escape codes and user-defined color schemes.

Contents

Overview

Terminals traditionally take an input of bytes and display them as white text on a black background. If the input contains specific “control characters,” then the terminal may alter certain display properties of the text, such as the color or font. Old terminals could only display a maximum of 8 colors. However, modern computer screens are capable of displaying 24-bit RGB color, so modern terminals have to implement a mapping of the 8 original colors to RGB values. This mapping can usually be changed according to user-defined color schemes.

ASCII Escape Character

The ANSI ASCII standard represents the escape ESC character by the decimal number 27 (33 in octal, 1B in hexadecimal). This is one of the control characters (0-31 and 127), not one of the printable characters (32-126).

ASCII code 27 is indeed the character corresponding to the Escape key on a keyboard. However, most shells recognize the Escape key as a control character (usually for a keyboard shortcut) and therefore do not translate the Escape key into any text representation. Thus, each programming language has its own method of representing the escape character within a string:

  Bash C Python 3
literal \e, \E \e (non-standard) Python 3.3+ only: \N{ESCAPE}, \N{escape}, \N{ESC}, \N{esc}
octal \033 \33, \033 \33, \033
hexadecimal \x1b \x1b \x1b
Unicode \u1b, \U1b \u001b, \U0000001b

Additional notes

  • For all 3 languages, any place where 1b appears, the capitalized hexadecimal 1B also works.
  • Bash supports Unicode characters of the form \uXXXX (\u + 4 hexadecimal digits) and \UXXXXXXXX (\U + 8 hexadecimal digits), but unlike Python, it allows for leading zeros to be omitted.
  • Most major C compilers, including gcc and clang, support the non-standard \e syntax for the escape character.
  • While C generally supports unicode characters of the form \uXXXX and \UXXXXXXXX, it oddly does not support Unicode control characters. From the C18 specification on universal character names:

    A universal character name shall not specify a character whose short identifier is less than 00A0 other than 0024 ($), 0040 (@), or 0060 (‘)

ANSI Escape Codes for Terminal Graphics

The ANSI escape code standard, formally adopted as ISO/IEC 6429, defines a series of control sequences. Each control sequence begins with a Control Sequence Inducer (CSI), defined as an escape character followed immediately by a bracket: ESC[. In particular, a CSI followed by a certain number of “parameter bytes” (ASCII 0–9:;<=>?) then the letter m forms a control sequence known as a Select Graphic Rendition (SGR). If no parameter bytes are explicitly given, then it is assumed to be 0. SGR parameters can be chained together with a semicolon ; as the delimiter.

Some common SGR parameters are shown below:

Parameter Effect
0 reset all SGR effects to their default
1 bold or increased intensity
2 faint or decreased intensity
4 singly underlined
5 slow blink
30-37 foreground color (8 colors)
38;5;x foreground color (256 colors, non-standard)
38;2;r;g;b foreground color (RGB, non-standard)
40-47 background color (8 colors)
48;5;x background color (256 colors, non-standard)
48;2;r;g;b background color (RGB, non-standard)
90-97 bright foreground color (non-standard)
100-107 bright background color (non-standard)

The 8 actual colors within the ranges (30-37, 40-47, 90-97, 100-107) are defined by the ANSI standard as follows:

Last Digit Color
0 black
1 red
2 green
3 yellow
4 blue
5 magenta
6 cyan
7 white

We put these pieces together to create a SGR command. Thus, ESC[1m specifies bold (or bright) text, and ESC[31m specifies red foreground text. We can chain together parameters; for example, ESC[32;47m specifies green foreground text on a white background.

The following diagram shows a complete example for rendering the word “text” in red with a single underline.

CSI Final Byte \x1b[31;4mtext ESC character in Hex ASCII Parameters

Notes

  • For terminals that support bright foreground colors, ESC[1;3Xm is usually equivalent to ESC[9Xm (where X is a digit in 0-7). However, the reverse does not seem to hold, at least anecdotally: ESC[2;9Xm usually does not render the same as ESC[3Xm.
  • Not all terminals support every effect.
  • Documentation is available for Microsoft Windows Console and Linux.
  • Microsoft and the Linux manual pages both refer to SGR as “Set Graphics Rendition,” instead of “Select Graphic Rendition.”

Examples

  • Bash: printf "\u1b[31mtest\033[ming\n"
  • C: printf("%s\n", "\x1b[31mtest\033[0ming");
  • Python: print('\N{ESC}[31mtest\u001b[0ming')
  • Output: testing

Additional Sources

Color Schemes

The role of terminal color schemes is to map the 8 colors to RGB values. Most terminals support an additional 8 colors corresponding to the bold or bright variants of the original colors. The GitHub repo mbadolato/iTerm2-Color-Schemes provides a sampling of common color schemes.

Windows Colors

While the 8 standard color names are widely used within ANSI and ISO standards documents as well as the Linux community, Microsoft uses slightly different names and ordering of colors.

Windows Console (Command Prompt)

The command color sets the default console foreground and background colors, and it can also be used to list out the supported colors with color /?. The colors are named as follows:

0 = Black       8 = Gray
1 = Blue        9 = Light Blue
2 = Green       A = Light Green
3 = Aqua        B = Light Aqua
4 = Red         C = Light Red
5 = Purple      D = Light Purple
6 = Yellow      E = Light Yellow
7 = White       F = Bright White

Notably, Microsoft renames “cyan” to “aqua” and “magenta” to “purple,” and it names the bold/bright variant of black as “gray.” This ordering is also the ordering of colors that appear in the Windows Console settings.

Windows Console color settings

Windows Terminal (beta)

Windows Terminal now uses the ANSI color names except that it still uses “purple” instead of “magenta.” However, there is an open issue (as of March 28, 2020) where Microsoft seems to be considering “magenta.”

My Preferred Color Scheme

Personally, here is the color scheme that I’ve found to work well for me. It is largely based on Ubuntu’s default terminal color scheme (changes are marked by an asterisk *).

Color RGB Hexadecimal
black* 0, 0, 0 000000
red 204, 0, 0 cc0000
green 78, 154, 6 4e9a06
yellow 196, 160, 0 c4a000
blue* 114, 159, 207 729fcf
magenta 117, 80, 123 75507b
cyan 6, 152, 154 06989a
white 211, 215, 207 d3d7cf
bright black 85, 87, 83 555753
bright red 239, 41, 41 ef2929
bright green 138, 226, 52 8ae234
bright yellow 252, 233, 79 fce94f
bright blue* 50, 175, 255 32afff
bright magenta 173, 127, 168 ad7fa8
bright cyan 52, 226, 226 34e2e2
bright white* 255, 255, 255 ffffff