The first widely recognized color graphics card for the IBM PC was the Color Graphics Adapter (CGA). It could produce 16 distinct colors. Not necessarily displaying them all at the same time, but still. And, for the sake of sanity, I'll ignore things like NTSC color hacking and dithering through the use of block graphics. This 16-color palette carried over into EGA with minor adjustments, and from there carried over into VGA.


VGA 16-color matrix

EGA and VGA allow the palette to be reprogrammed, but this was hardly ever exercised by users or programs. (DOOM's SETUP.EXE included a change of blue to a dark shade, but that is about the extent I remember outliers.)

Outside the PC world, there were of course the Unix workstations. The Sun CG2 card (1985) was capable of 256 simultaneous colors/16.7 million distinct ones. It appears that xterm only started supporting a color palette in 1996 (project color_xterm).

A turn for the worse

Many programs have been written with a basic assumption about what colors the palette will contain. In fact, the ECMA-48 specification (2nd Ed., 1979, page 41 (PDF page 49)) even has formalized what effect escape codes like \e[33;44m should have.

In more modern times, people have come up with plenty of alternate palettes/color schemes for use with terminal programs. Many of these schemes were seemingly designed with an utter disregard for certain color pairings. Programs with background fill, such as exercised by Midnight Commander, look like hot neutron star garbage in many of these palettes. For example, the stock configuration of gnome-terminal/xfce4-terminal comes with "Tango", "Solarized", and the classic "VGA" palette. Activating each of these palettes produces these visual results:


MC with "VGA" palette


MC with GNOME's "Tango" palette


MC with GNOME's "Solarized" palette

Speaking of ECMA-48, "Solarized" is in violation, because 1;33 is not producing anything ordinary humans would consider "yellow". Tango still adheres to that specification, but intuitively has worse legibility than the VGA palette.

Let's quantify how various palettes perform. In the process of conducting these analyses, I have constructed the palcomp utility (part of the consoleet-utils package). An overview of the used palette on a terminal with `palcomp ct`:


VGA


Solarized

From here on, I will leave out the Tango scheme, because it's just sort of a midpoint between VGA and Solarized.

Trivial contrast analysis

Legibility depends on a handful of factors; a prominent one is contrast, i.e. the difference in luminance or color. (Other factors could be text size or thickness, both of which are not relevant, since the focus in this document is on fixed-font terminal use.)

A first indication of how luminance influences legibility can be obtained by turning our images of the color tables to grayscale. This can be done with image manipulation programs. They have various algorithms; the venerable Paint Shop Pro version 5 from the Windows 9x era utilizes a quite inaccurate RGB-to-YCbCr transform[1], whereas GIMP uses a sRGB(BT.709)-to-CIELAB conversion[2].

[1] RGB-to-YCbCr conversion with BT.601 coefficients, using a somewhat well-known formula: Y := 0.299*r + 0.587*g + 0.114*b

[2] e.g. L := 116*(0.212*r^2.4 + 0.715*g^2.4 + 0.072*b^2.4)^0.333 - 16
(this is not the full formula, but an abbreviated metric for people who like trying things out)

This is the output from GIMP:


VGA converted to gray


Solarized converted to gray

A few things become apparent:

1. Pairings with low legibility in color (e.g. cell "26" in VGA also have low legibility in gray.

2. Pairings with moderate legibility in color (e.g. cell "29" in VGA) may have less legibility in gray. (Colored fg/bg having higher legibility is known as the Helmholtz–Kohlrausch effect).
To support users with partial/full colorblindness, color should largely be ignored when computing overall usability scores.

Using the palcomp cxl command, a contrast analysis based on CIELAB lightness value differences between foreground and background color can be produced. (To get the best visual reporting effect, do also set the terminal to use the same palette.)


Lightness-based contrast analysis for VGA


Lightness-based contrast analysis for Solarized

Summing up those numbers gives an indication about the palette's general contrast performance. The last three lines of the CXL analysis printout offer scores for different grid subsections. This allows for comparing palettes for slightly different blocks, while remaining general. For example, few Linux programs exercise intense backgrounds (ECMA-48 codes \e[100m to \e[107m) directly, so perhaps you want to judge a palette just by the 16×8 metric. Some palettes may have been designed with very limited scope, e.g. syntax-highlighted code blocks shown on HTML pages seldomly use foreground intensity, so may warrant to be judged by the 8×8 matric. On the other hand, text selection in the terminal/editor/browser/etc. may use trivial fg–bg inversion, making it necessary to judge even a limited palette by the full 16×16 metric.

When a fg–bg color pair is below a certain threshold (cutoff currently at 7.0), that pair is essentially a lost cause and treated as 0 ("penalized") for the second sum.

Contrast analysis with APCA

Raw luminance is an important factor, but not the only factor for legibility. With self-experimentation, I noticed that white-on-anything (e.g. cells 07, 17, 27, etc. in the table printed by palcomp ct) subjectively feels to have ever-so-slightly better contrast than anything-on-white (e.g. cells 70, 71, 72, etc. in the ct table; bottommost row of the cxl table). It seems that the white background "bleeds" onto the text and diminishes the contrast.

Researches have indeed independently came to the same conclusion in a relatively new contrast metric, APCA. APCA gives background colors a certain "boost" to account for the very phenomenon I intuitively had noticed. (APCA also takes font size and thickness into account, but as stated earlier, this is irrelevant to our fixed-font terminal usecase.)

palcomp has a cxa command which will compute APCA. The contrast ratings for individual fg–bg color pairs are a bit different from cxl, but overall, it generally follows CXL.


APCA contrast analysis for VGA


APCA contrast analysis for Solarized

Scores for various palettes

With these tools in hand, the CXL/CXA scores can be computed for a whole set of palettes. The termux-styling package has heaps of palettes to look at and see how they stack up against each other.

cxl16×16cxl16×8cxa16×16cxa16×8name
23431283 27111477gruvbox-material-dark-*
27071490 30801778tokyonight-day
30002200 32092346white-on-black
30371519 32261613gruvbox-material-light-*
31951586 36041751catppuccin-latte
38321965 37341778base16-embers-*
32401666 38751986catppuccin-frappe
35271783 39291829base16-solarized-dark
37851897 39471798base16-marrakesh-*
37431911 39521838base16-atelierlakeside-*
40262060 42652006base16-atelierheath-*
40252261 43792349iceberg
40332116 44142183base16-chalk-*
39902083 44762274base16-one-dark
36991898 44772283catppuccin-macchiato
39302168 44812561rosé-pine-dawn
41902167 44922159base16-atelierforest-*
42652263 45402322tokyonight-dark
40372195 46292389base16-ashes-*
41972220 46362640gruvbox-light
45552249 47072243base16-codeschool-*
42172231 47562344gruvbox-dark
43012209 48712326base16-atelierdune-*
47752460 49002383base16-apathy-*
42652225 50952564base16-mocha-*
41462121 51332609catppuccin-mocha
50872615 52582899base16-one-light
49742544 53442560base16-grayscale-*
49742544 53442560e-ink
48812524 53472568base16-atelierseaside-*
45392479 53752776base16-tomorrow-*
45392479 53752776tomorrow-night
52992750 54482671base16-greenscreen-*
45312398 54542787base16-eighties-*
48442793 54942994base16-bright-*
45502431 55192906rosé-pine-moon
49882638 55352725base16-brewer-*
46362411 55692791base16-ocean-*
48852545 56442814base16-paraiso-*
48972541 56652781base16-google-*
50842736 58182994base16-harmonic16-*
47562557 58233061base16-flat-*
49432678 58673101base16-bespin-*
52612260 59602392base16-solarized-light
52612515 59602848solarized-*
56002800 59902995black-on-white
47422439 60192968base16-materia
46922706 60203350rydgel
49872538 61583108nord
52792856 62393307rosé-pine
57642957 64393152base16-summerfruit-*
61053532 64763519spacemacs
53542798 65403330base16-railscasts-*
52402620 66223310wild-cherry
56972806 66973235material
58363044 67903409base16-default-*
60603159 68063328gnometerm-new
54132916 69253634base16-snazzy
57722928 70193406base16-twilight-*
55963019 70443718base16-monokai-*
62453260 71293697neon
61983248 71743469smyck
61573317 72383755dracula
60923012 73343450ubuntu
75293629 73993575gotham
63993191 74383489nancy
64943415 76283824base16-colors-*
67273478 76353679gnometerm (tango)
66193574 78054016base16-isotope-*
66453459 78573902base16-3024-*
66323441 78703951base16-londontube-*
72163647 80843922base16-shapeshifter-*
75263912 86534156zenburn
70963662 86864378argonaut
79803957 90614082VGA (palcomp vga)
80553894 91334023VGA saturated (palcomp vgs)
82943905 91453873xterm-R6-sb_right-ansi-3d (1995); 16c
85634261 94684301Windows 3.0 (palcomp win, termux:e-ink-color)
84234299 96694679XFree86 3.1.2e xterm (1996; T.Dickey)

There is no way to sugercoat it; almost all of the modern palettes are terrible for use as a generic palette that needs to cover all use cases. The winners, instead, all almost directly relate the vivid classic CGA palette.

Optimal palette

The lightnesses of the colors can be placed in equidistant steps, which should give the largest lightness differences between any color pair, thus maximizing the CXL score. To that end, the palcomp eq and palcomp loeq commands can be used. When we do this for all the top runners they expectedly get the same rating (except for VGA in loeq mode, which I should go diagnose):

cxlloeq cxleq cxlname
79808808 9094VGA
81509002 9094VGA saturated
82949002 9094xterm-R6-sb_right-ansi-3d 1995
85639002 9094Windows 3.0
84239002 9094XFree86 3.1.2e xterm 1996

To be continued... this article is not finished and warrants more elaboration, e.g. on the optical differences of equalized palettes, and whether (or not) the maximum CXL score has already been achieved. The darkest color has L=11, and the effects of preferring a higher minimum L of, say, 22, at the cost of some illegible pairs, should be looked at.

For the time being, VGA is a sane default palette ready for general use (not specific to any particular application or expectation beyond ECMA-48).