|
| 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 | + |
| 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 | + |
| 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 | + |
| 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° in the [HSV cylinder][HSV_CYLINDER]). |
| 147 | + |
| 148 | + |
| 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 | +  |
| 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 | +  |
| 186 | + |
| 187 | +Setting `x1`=0 (since it is always zero) and solving for `yp` we have: |
| 188 | + |
| 189 | +  |
| 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°. |
| 202 | + |
| 203 | +* X-Axis: The 360° 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 | +[](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° and `h2` at 150° 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 | +  |
| 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 | +  |
| 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 | +  |
| 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 | + |
| 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 | +  |
| 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