Skip to content

Commit 656da2e

Browse files
CHAD.JULIANO@ORACLE.COMChad Juliano
authored andcommitted
Adding COLORS.md
1 parent a59de22 commit 656da2e

28 files changed

+912
-429
lines changed

COLORS.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# BASH-UI - Colorspace Manipulation
2+
3+
This document explains the implimentaion of terminal color manipulation in the Bash UI program which
4+
includes functionality for RGB, Gray and [HSV (Hue/Saturation/Value)][HSV_WIKI] colorspaces. See
5+
[README.md][README] for the top-level user documentation.
6+
7+
The following examples use the functions defined in in [bui-term-sgr.sh][TERM_SGR].
8+
9+
[README]: <README.md>
10+
[TERM_SGR]: <src/bui-term-sgr.sh>
11+
12+
## Table of Contents
13+
14+
- [SGR Color Map](#sgr-color-map)
15+
- [XTerm Colorspaces](#xterm-colorspaces)
16+
- [HSV Colorspace](#hsv-colorspace)
17+
- [HSV Overview](#hsv-overview)
18+
- [HSV Implimentation](#hsv-design)
19+
- [Integer Interpolation](#integer-interpolation)
20+
- [HSL Calculation](#hsl-calculation)
21+
- [Gradient Colormaps](#gradient-colormaps)
22+
- [See Also](#see-also)
23+
- [Author](#author)
24+
25+
## SGR Color Map
26+
27+
Terminal colors are set with the [SGR control sequence][SGR_REFERENCE]. The example below shows
28+
a 16x16 map of the XTerm 256-color SGR command from the [sgr-colormap.sh][SGR_SCRIPT].
29+
30+
![](images/bui-color-sgr-map.png "bui-color-sgr-map")
31+
32+
In the above map the color values sent to the SGR control sequence range from 0 to 255. The map is
33+
divided into sections as follows.
34+
35+
| Start | Size | Description |
36+
| ---: | ---: | --- |
37+
| 0 | 8 | ANSI normal |
38+
| 8 | 8 | ANSI highlight |
39+
| 16 | 216 | 6x6x6 RGB colors |
40+
| 232 | 24 | Gray colors |
41+
42+
You can test sending of 256-color SGR commands from the Bash prompt. The below commands will set the
43+
text color attributes for codes `196` (red), `46` (green), and `21` (blue). The trailing command
44+
`\e[0m` will reset the color to default.
45+
46+
```sh
47+
echo -e "\e[38;5;196m Red \e[0m"
48+
echo -e "\e[38;5;46m Green \e[0m"
49+
echo -e "\e[38;5;21m Blue \e[0m"
50+
```
51+
52+
The SGR command from the first Bash line above can be broken down to the following format:
53+
54+
```raw
55+
<CSI><Param>;5;<Color><SGR>
56+
CSI = "\e["
57+
SGR = "m"
58+
Param = [ 38 = foreground | 48 = background ]
59+
Color = [ 0 - 255 ]
60+
```
61+
62+
Setting color attributes is the easy part. The following sections describe how to calulate color
63+
codes to control attributes like intensity or color content.
64+
65+
[SGR_REFERENCE]: <http://vt100.net/docs/vt510-rm/SGR.html>
66+
[SGR_SCRIPT]: <test/sgr-colormap.sh>
67+
68+
## XTerm Colorspaces
69+
70+
Except for HSV there are functions defined to work with 4 separate colorspaces.
71+
72+
| Name | Dimensions | Positions | Description |
73+
| --- | --- | --- | --- |
74+
| `ansi16` | 8x2 | 0-15 | Traditional 8 colors with normal and highlight |
75+
| `xterm240`| 240 | 16-255 | All 240 colors following the 16 ANSI colors |
76+
| `rgb216` | 6x6x6 | 16-231 | 216 colors from `xterm240` parameterized as RGB |
77+
| `grey26` | 26 | 231-255,0 | Includes 24 gray colors not in `rgb216` |
78+
79+
The [rgb-demo.sh][RGB_DEMO] script shown below gives examples of how these can be used.
80+
81+
![](images/bui-color-rgb.png "bui-color-rgb")
82+
83+
Given 3 values for RGB each ranging from 0-5 we can calculate the SGR color.
84+
85+
```raw
86+
<SGR> = <red>*36 + <green>*6 + <blue> + 16
87+
```
88+
89+
Given an SGR color we can separate its RGB components with the below pseudocode.
90+
91+
```
92+
int temp = <SGR>
93+
temp -= 16
94+
95+
int <blue> = temp % 6
96+
temp /= 6
97+
int <green> = temp % 6
98+
temp /= 6
99+
int <red> = temp
100+
```
101+
102+
Parameterized functions like the examples below make these operations easier to build on.
103+
104+
```sh
105+
fn_sgr_ansi16_set $((SGR_ATTR_BG + SGR_ATTR_BRIGHT)) $_color
106+
107+
fn_sgr_rgb216_set $SGR_ATTR_BG 0 0 5
108+
109+
fn_sgr_grey26_set $SGR_ATTR_BG 15
110+
```
111+
112+
[RGB_DEMO]: <test/rgb-demo.sh>
113+
114+
## HSV Colorspace
115+
116+
### HSV Overview
117+
118+
The RGB colorspace is convenient for hardware that needs to drive a display but it can make some
119+
operations difficult. For example computing colors from dull to deep aqua in RGB is difficult. The
120+
[HSV/HSV colorspaces][HSV_WIKI] were developed in the 1970's (and patented) to make these types of
121+
operations easy while minimizing computational overhead.
122+
123+
### HSV Implimentation
124+
125+
Some difficulties implimenting HSV in Bash include the limited number of RGB colors availalbe and
126+
the lack of floating point arithmatic used in popular algorithms. To address this the 6x6x6 `rgb216`
127+
colorspace was rationalized to a 6x6x36 `hsv216` space. The parameters are:
128+
129+
* **Hue[0-35]:** 36 values consisting of 6 colors separated by 6 transitional values corresponding
130+
to red, yellow, greeen, cyan, blue, and magenta.
131+
132+
* **Saturation[0-5]:** 6 values where 0 indicates no color and 5 is the deepest color.
133+
134+
* **Value[0-5]:** 6 values of intensity where 0 is black and 5 is full intensity.
135+
136+
The screenshot below from [hsv-demo.sh][HSV_DEMO] shows 36x6 maps of H and V for different levels of
137+
S. It should be noted that 6x6x36 combinations of parameters lead to 1296 results. This is because
138+
when S or V are less than 5 the colors progressively overlap as they approach the center of the [HSV
139+
cylinder][HSV_CYLINDER].
140+
141+
![](images/bui-color-hsl-sat.png "bui-color-hsl-sat")
142+
143+
The [hsv-demo.sh][HSV_DEMO] script also generates compliment maps as shown in the below screenshot.
144+
Each map displays one of the 6 hues mentioned above where S is varied from -5 to +5. Negative values
145+
of saturation give its compliment (e.g. red and cyan are compliments because they are separated by
146+
180&deg; in the [HSV cylinder][HSV_CYLINDER]).
147+
148+
![](images/bui-color-hsl-comp.png "bui-color-hsl-comp")
149+
150+
These approximate the [HSV swatches][HSV_SWATCHES] on Wikipedia. We can't expect a great
151+
approximation with 216 colors but results may vary between terminals.
152+
153+
[HSV_WIKI]: <https://en.wikipedia.org/wiki/HSL_and_HSV>
154+
[HSV_DEMO]: <test/hsv-demo.sh>
155+
[HSV_SWATCHES]: <https://en.m.wikipedia.org/wiki/Template:Hsv-swatches>
156+
[HSV_CYLINDER]: <https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:Hsl-hsv_models.svg>
157+
158+
### Integer Interpolation
159+
160+
The Bash HSV calculation can't take advantage of popular algorithms because it does not support
161+
floating point operations. Also, it needs to provide exact results due to the limited number of
162+
colors availble.
163+
164+
The implimentation is based on a versatile interpolation function used multiple times in the HSV
165+
algorithm and also for RGB gradients. The interpolation provides a linear maping from the range
166+
[0,`x2`] on the X domain onto range [`y1`,`y2`] on the Y domain. In [set builder][SET_BUILDER] notation this gives:
167+
168+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-eq-dom.png "bui-color-eq-dom")
169+
170+
This is implimented in the `fn_sgr_interp_y` Bash function. The parameters `x2`, `y1`, and `y2`
171+
define the linear map and `xp` is the point being mapped.
172+
173+
```raw
174+
<yp> = fn_sgr_interp_y <x2> <y1> <y2> <xp>
175+
x2 = max x-axis
176+
y1 = min y-axis
177+
y2 = max y-axis
178+
xp = point on the x-axis to map
179+
yp = point in the y-axis to map
180+
```
181+
182+
The mapping is based on a [line equation][LINE_EQUATION] for the point (`xp`,`yp`) through a line
183+
defined by the points (`x1`,`y1`) and (`x2`,`y2`).
184+
185+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-eq-line.png "bui-color-eq-line")
186+
187+
Setting `x1`=0 (since it is always zero) and solving for `yp` we have:
188+
189+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-eq-yp.png "bui-color-eq-yp")
190+
191+
Finally, because we working with integer math we need to consider rounding error. The
192+
`fn_sgr_interp_y` function has an optional flag for nearest rounding. This is because there are
193+
cases where we don't want to round (e.g. the `vMin` calculation below).
194+
195+
[SET_BUILDER]: <https://en.wikipedia.org/wiki/Set-builder_notation>
196+
[LINE_EQUATION]: <https://en.wikipedia.org/wiki/Line_(geometry)#On_the_Cartesian_plane>
197+
198+
### HSL Calculation
199+
200+
The diagram below shows how intensity of RGB channels vary with hue. Each RGB channel is identical
201+
except that they are out of phase by 120&deg;.
202+
203+
* X-Axis: The 360&deg; of hue are related to the 36 `H` values of the calculation by a factor
204+
of 10.
205+
* Y-Axis: This indicates the level of RGB output which is between 0 and 5.
206+
* The red, green, and blue lines show the intensity of each of the RGB channels.
207+
* The top line indicated by `Vmax` determines the brightness and is directly related to the V
208+
paramter.
209+
* The bottom line indicated by `Vmin` deterimes the saturation and is closely related to the S
210+
parameter.
211+
212+
[![](images/bui-color-hsl-sectors.png "Credit: Wikipedia")](https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:HSV-RGB-comparison.svg)
213+
214+
The objective of the calculation is to represent a function for the RGB channels given HSV
215+
parameters. The above diagram has vertical lines labeled `h1` at 90&deg; and `h2` at 150&deg; and
216+
where they intersect the RGB functions there are 4 points are indicated over the line segments.
217+
218+
| Segment | Name | Description |
219+
| 1 | `Vdn` | Downward sloping line segment |
220+
| 2 | `Vmin` | Minimum RGB value |
221+
| 3 | `Vmax` | Maximum RGB value |
222+
| 4 | `Vup` | Upward sloping line segment |
223+
224+
Given the values of these segments within one of the 6 sectors we can calulate all 3 RGB values. The
225+
values are determined as follows from the `interp_y` function. Given `H` we determine `Hsect`
226+
indicating which of the 6 sectors we are in and `Hstep` indicating the position within the sector.
227+
228+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-hsl-eq-sect.png "bui-color-hsl-eq-sect")
229+
230+
Next we calculate `Vmin` and `Vmax`. As `S` increases from 0 to 5, `Vmin` decreases to `V` to 0. We
231+
don't round the `vMin` result because it an input to other parameters that are also rounded which
232+
would compound the error and alter hue. The below notation for `Vmin` says that a map is defined
233+
between 2 ranges using `fn_sgr_interp_y` and applied to `S`.
234+
235+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-hsl-eq-vmx.png "bui-color-hsl-eq-vmx")
236+
237+
Next we calculate `Vup` and `Vdn` based on `S`. `Vdn` increases with the inverse of `Vup` and so the
238+
Y parameters are swapped below.
239+
240+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-hsl-eq-vud.png "bui-color-hsl-eq-vud")
241+
242+
Finally with `Vup`, and `Vdn` we can determine the RGB parameters for each sector based on `Hsect`.
243+
244+
| Sector | Red | Green | Blue |
245+
| --- | --- | --- | --- |
246+
| 0 | `Vmax` | `Vup` | `Vmin` |
247+
| 1 | `Vdn` | `Vmax` | `Vmin` |
248+
| 2 | `Vmin` | `Vmax` | `Vup` |
249+
| 3 | `Vmin` | `Vdn` | `Vmax` |
250+
| 4 | `Vup` | `Vmin` | `Vmax` |
251+
| 5 | `Vmax` | `Vmin` | `Vdn` |
252+
253+
The HSL values are computed by `fn_sgr_hsv216_calc` and they can optionally be cached in a lookup
254+
table if `fn_sgr_hsv216_init` is called.
255+
256+
`fn_sgr_hsv216_get` is used lookup values in the table or calculate on demand and it provides some
257+
additional normalization to handle out of range values for S and H. `fn_sgr_hsv216_set` is called to
258+
invoke the SGR command for an HSV color.
259+
260+
## Gradient Colormaps
261+
262+
The below screenshot from [grad-demo.sh][GRAD_DEMO] shows 2 sets of gradients. The first is
263+
calculated with decreasing saturation and the second with descreasing brightness.
264+
265+
![](images/bui-color-grad.png "bui-color-grad")
266+
267+
The gradient function `fn_sgr_rgb216_grad` calculates an array of RGB colors based on specified
268+
starting and ending RGB colors. The input colors can be calculated in RGB or HSV.
269+
270+
```raw
271+
fn_sgr_rgb216_grad <map-name> <map-size> <start-color> <end-color>
272+
map-name = Name of array to store results
273+
map-size = Number of colors to calculate
274+
start-color = First color in the gradient
275+
end-color = Last color in the gradient
276+
```
277+
278+
Basic steps are as follows. See [grad-demo.sh][GRAD_DEMO] for an example.
279+
280+
1. Calculate start and end colors with `fn_sgr_hsv216_get` or `fn_sgr_rgb216_get`.
281+
1. Call `fn_sgr_rgb216_grad` to calculate the colormap.
282+
1. For each color call `fn_sgr_xterm240_set` to set the foreground or background.
283+
1. Use `fn_sgr_print` or echo to display colors.
284+
285+
The `fn_sgr_rgb216_grad` function uses `fn_sgr_interp_y` to calculate intermediate colors. As a
286+
first step it unpacks the start and end `xterm240` colors into separate RGB channels. For each
287+
gradient index `n` in the set of indexes `N` it computes the gradient color `Gn` having RGB
288+
components by interpolating with map from the index space to the start and end colors.
289+
290+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;![](images/bui-color-grad-eq.png "bui-color-grad-eq")
291+
292+
RGB components are packed into an `xterm240` with `fn_sgr_rgb216_get`.
293+
294+
[GRAD_DEMO]: <test/grad-demo.sh>
295+
296+
## See Also
297+
298+
* [Bash FAQ 037](http://mywiki.wooledge.org/BashFAQ/037)
299+
* [bash:tip_colors_and_formatting](http://misc.flogisoft.com/bash/tip_colors_and_formatting)
300+
* [XTerm Termal Codes](http://www.xfree86.org/4.7.0/ctlseqs.html): Complete reference
301+
* [Console Virtual Terminal Sequences](https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032.aspx): Microsoft docs highlighting common termial codes.
302+
303+
## Author
304+
305+
- [Chad Juliano](https://github.com/chadj2)

0 commit comments

Comments
 (0)