FTXUI 7.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
color.cpp
Go to the documentation of this file.
1// Copyright 2020 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
5
6#include <algorithm> // for clamp
7#include <array> // for array
8#include <cmath>
9#include <cstdint>
10#include <cstdio> // for snprintf
11#include <string>
12
13#include "ftxui/screen/color_info.hpp" // for GetColorInfo, ColorInfo
14#include "ftxui/screen/terminal.hpp" // for ColorSupport, Color, Palette256, TrueColor
15
16#if defined(_MSC_VER)
17#define FTXUI_NOINLINE __declspec(noinline)
18#else
19#define FTXUI_NOINLINE __attribute__((noinline))
20#endif
21
22namespace ftxui {
23namespace {
24const std::array<const char*, 32> palette16code = {
25 "30", "40", //
26 "31", "41", //
27 "32", "42", //
28 "33", "43", //
29 "34", "44", //
30 "35", "45", //
31 "36", "46", //
32 "37", "47", //
33 "90", "100", //
34 "91", "101", //
35 "92", "102", //
36 "93", "103", //
37 "94", "104", //
38 "95", "105", //
39 "96", "106", //
40 "97", "107", //
41};
42
43void AppendNumber(std::string& out, uint8_t n) {
44 if (n >= 100) {
45 out += static_cast<char>('0' + n / 100);
46 n %= 100;
47 out += static_cast<char>('0' + n / 10);
48 n %= 10;
49 out += static_cast<char>('0' + n);
50 } else if (n >= 10) {
51 out += static_cast<char>('0' + n / 10);
52 n %= 10;
53 out += static_cast<char>('0' + n);
54 } else {
55 out += static_cast<char>('0' + n);
56 }
57}
58
59// Index of the Palette256 color closest to (r, g, b), matching an exhaustive
60// search over [16, 255]: ties resolve to the lowest index.
61uint8_t ClosestPalette256(int r, int g, int b) {
62 // Colors [16, 231] form a 6x6x6 cube of levels {0, 95, 135, 175, 215, 255}.
63 auto cube_index = [](int v) {
64 return v < 48 ? 0 : v < 116 ? 1 : (v - 36) / 40;
65 };
66 auto cube_level = [](int i) { return i == 0 ? 0 : 55 + 40 * i; };
67 const int ri = cube_index(r);
68 const int gi = cube_index(g);
69 const int bi = cube_index(b);
70 const int cr = cube_level(ri) - r;
71 const int cg = cube_level(gi) - g;
72 const int cb = cube_level(bi) - b;
73 const int cube_distance = cr * cr + cg * cg + cb * cb;
74
75 // Colors [232, 255] are the grays {8, 18, ..., 238}. The closest one is the
76 // closest to the mean (r + g + b) / 3.
77 const int gray_index = std::clamp((r + g + b - 10) / 30, 0, 23);
78 const int gray_level = 8 + 10 * gray_index;
79 const int dr = gray_level - r;
80 const int dg = gray_level - g;
81 const int db = gray_level - b;
82 const int gray_distance = dr * dr + dg * dg + db * db;
83
84 return gray_distance < cube_distance ? 232 + gray_index
85 : 16 + 36 * ri + 6 * gi + bi;
86}
87
88// Print the color the terminal supports closest to either the RGB color
89// (r, g, b), or the Palette256 color r. Kept out of line, so that printing
90// supported colors stays fast.
91FTXUI_NOINLINE void PrintDegraded(std::string& out,
92 bool is_background_color,
93 bool is_rgb,
94 uint8_t r,
95 uint8_t g,
96 uint8_t b,
97 Terminal::Color support) {
98 if (support == Terminal::Color::Palette1) {
99 out.append(is_background_color ? "49" : "39", 2);
100 return;
101 }
102 const uint8_t index = is_rgb ? ClosestPalette256(r, g, b) : r;
103 if (support == Terminal::Color::Palette256) {
104 out.append(is_background_color ? "48;5;" : "38;5;", 5);
105 AppendNumber(out, index);
106 return;
107 }
108 const uint8_t index_16 = GetColorInfo(Color::Palette256(index)).index_16;
109 out.append(palette16code[2 * index_16 + (is_background_color ? 1 : 0)]);
110}
111
112} // namespace
113
114bool Color::operator==(const Color& rhs) const {
115 return red_ == rhs.red_ && green_ == rhs.green_ && blue_ == rhs.blue_ &&
116 type_ == rhs.type_;
117}
118
119bool Color::operator!=(const Color& rhs) const {
120 return !operator==(rhs);
121}
122
123std::string Color::Print(bool is_background_color) const {
124 std::string out;
125 PrintTo(out, is_background_color);
126 return out;
127}
128
129/// @brief Append the ANSI color code to a string (zero-allocation fast path).
130/// @param out The string to append to.
131/// @param is_background_color Whether this is a background color code.
132void Color::PrintTo(std::string& out, bool is_background_color) const {
133 PrintTo(out, is_background_color, Terminal::ColorSupport());
134}
135
136/// @brief Append the ANSI color code to a string, degraded to the given
137/// terminal color support.
138/// @param out The string to append to.
139/// @param is_background_color Whether this is a background color code.
140/// @param support The terminal color support.
141void Color::PrintTo(std::string& out,
142 bool is_background_color,
143 Terminal::Color support) const {
144 // Degrade the color to what the terminal supports. Both enums are ordered
145 // the same way.
146 if (static_cast<uint8_t>(type_) > static_cast<uint8_t>(support)) {
147 PrintDegraded(out, is_background_color, type_ == ColorType::TrueColor, red_,
148 green_, blue_, support);
149 return;
150 }
151
152 switch (type_) {
153 case ColorType::Palette1:
154 out.append(is_background_color ? "49" : "39", 2);
155 return;
156 case ColorType::Palette16:
157 out.append(palette16code[2 * red_ + (is_background_color ? 1 : 0)]);
158 return;
159 case ColorType::Palette256:
160 out.append(is_background_color ? "48;5;" : "38;5;", 5);
161 AppendNumber(out, red_);
162 return;
163 case ColorType::TrueColor:
164 out.append(is_background_color ? "48;2;" : "38;2;", 5);
165 AppendNumber(out, red_);
166 out += ';';
167 AppendNumber(out, green_);
168 out += ';';
169 AppendNumber(out, blue_);
170 return;
171 }
172}
173
174/// @brief Build a transparent color.
175Color::Color() = default;
176
177/// @brief Build a transparent color.
178Color::Color(Palette1 /*value*/) : Color() {}
179
180/// @brief Build a color using the Palette16 colors.
181Color::Color(Palette16 index)
182 : type_(ColorType::Palette16), red_(index), alpha_(255) {}
183
184/// @brief Build a color using Palette256 colors.
185Color::Color(Palette256 index)
186 : type_(ColorType::Palette256), red_(index), alpha_(255) {}
187
188/// @brief Build a Color from its RGB representation.
189/// https://en.wikipedia.org/wiki/RGB_color_model
190///
191/// @param red The quantity of red [0,255]
192/// @param green The quantity of green [0,255]
193/// @param blue The quantity of blue [0,255]
194/// @param alpha The quantity of alpha [0,255]
195Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
196 : type_(ColorType::TrueColor),
197 red_(red),
198 green_(green),
199 blue_(blue),
200 alpha_(alpha) {}
201
202/// @brief Build a Color from its RGB representation.
203/// https://en.wikipedia.org/wiki/RGB_color_model
204///
205/// @param red The quantity of red [0,255]
206/// @param green The quantity of green [0,255]
207/// @param blue The quantity of blue [0,255]
208// static
209Color Color::RGB(uint8_t red, uint8_t green, uint8_t blue) {
210 return RGBA(red, green, blue, 255);
211}
212
213/// @brief Build a Color from its RGBA representation.
214/// https://en.wikipedia.org/wiki/RGB_color_model
215/// @param red The quantity of red [0,255]
216/// @param green The quantity of green [0,255]
217/// @param blue The quantity of blue [0,255]
218/// @param alpha The quantity of alpha [0,255]
219/// @see Color::RGB
220// static
221Color Color::RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
222 return {red, green, blue, alpha};
223}
224
225/// @brief Build a Color from its HSV representation.
226/// https://en.wikipedia.org/wiki/HSL_and_HSV
227///
228/// @param h The hue of the color [0,255]
229/// @param s The "colorfulness" [0,255].
230/// @param v The "Lightness" [0,255]
231/// @param alpha The quantity of alpha [0,255]
232// static
233Color Color::HSVA(uint8_t h, uint8_t s, uint8_t v, uint8_t alpha) {
234 uint8_t region = h / 43; // NOLINT
235 uint8_t remainder = (h - (region * 43)) * 6; // NOLINT
236 uint8_t p = (v * (255 - s)) >> 8; // NOLINT
237 uint8_t q = (v * (255 - ((s * remainder) >> 8))) >> 8; // NOLINT
238 uint8_t t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; // NOLINT
239
240 // clang-format off
241 switch (region) { // NOLINT
242 case 0: return Color(v,t,p, alpha); // NOLINT
243 case 1: return Color(q,v,p, alpha); // NOLINT
244 case 2: return Color(p,v,t, alpha); // NOLINT
245 case 3: return Color(p,q,v, alpha); // NOLINT
246 case 4: return Color(t,p,v, alpha); // NOLINT
247 case 5: return Color(v,p,q, alpha); // NOLINT
248 } // NOLINT
249 // clang-format on
250 return {0, 0, 0, alpha};
251}
252
253/// @brief Build a Color from its HSV representation.
254/// https://en.wikipedia.org/wiki/HSL_and_HSV
255///
256/// @param h The hue of the color [0,255]
257/// @param s The "colorfulness" [0,255].
258/// @param v The "Lightness" [0,255]
259// static
260Color Color::HSV(uint8_t h, uint8_t s, uint8_t v) {
261 return HSVA(h, s, v, 255);
262}
263
264// static
265Color Color::Interpolate(float t, const Color& a, const Color& b) {
266 if (a.type_ == ColorType::Palette1 || //
267 b.type_ == ColorType::Palette1) {
268 if (t < 0.5F) { // NOLINT
269 return a;
270 } else {
271 return b;
272 }
273 }
274
275 // Gamma correction:
276 // https://en.wikipedia.org/wiki/Gamma_correction
277 auto interp = [t](uint8_t a_u, uint8_t b_u) {
278 constexpr float gamma = 2.2F;
279 const float a_f = std::pow(a_u, gamma);
280 const float b_f = std::pow(b_u, gamma);
281 const float c_f = a_f * (1.0F - t) + //
282 b_f * t;
283 return static_cast<uint8_t>(std::pow(c_f, 1.F / gamma));
284 };
285 return Color::RGB(interp(a.GetRed(), b.GetRed()), //
286 interp(a.GetGreen(), b.GetGreen()), //
287 interp(a.GetBlue(), b.GetBlue())); //
288}
289
290/// @brief The red component of the color [0,255].
291/// Palette colors are resolved to their RGB approximation. Returns 0 for the
292/// default (transparent) color.
293uint8_t Color::GetRed() const {
294 switch (type_) {
295 case ColorType::Palette1:
296 return 0;
297 case ColorType::Palette16:
298 case ColorType::Palette256:
299 return GetColorInfo(Palette256(red_)).red;
300 case ColorType::TrueColor:
301 return red_;
302 }
303 return 0;
304}
305
306/// @brief The green component of the color [0,255].
307/// Palette colors are resolved to their RGB approximation. Returns 0 for the
308/// default (transparent) color.
309uint8_t Color::GetGreen() const {
310 switch (type_) {
311 case ColorType::Palette1:
312 return 0;
313 case ColorType::Palette16:
314 case ColorType::Palette256:
315 return GetColorInfo(Palette256(red_)).green;
316 case ColorType::TrueColor:
317 return green_;
318 }
319 return 0;
320}
321
322/// @brief The blue component of the color [0,255].
323/// Palette colors are resolved to their RGB approximation. Returns 0 for the
324/// default (transparent) color.
325uint8_t Color::GetBlue() const {
326 switch (type_) {
327 case ColorType::Palette1:
328 return 0;
329 case ColorType::Palette16:
330 case ColorType::Palette256:
331 return GetColorInfo(Palette256(red_)).blue;
332 case ColorType::TrueColor:
333 return blue_;
334 }
335 return 0;
336}
337
338/// @brief The alpha component of the color [0,255]. 0 is fully transparent.
339uint8_t Color::GetAlpha() const {
340 return alpha_;
341}
342
343/// @brief Blend two colors together using the alpha channel.
344// static
345Color Color::Blend(const Color& lhs, const Color& rhs) {
346 Color out = Interpolate(float(rhs.alpha_) / 255.F, lhs, rhs);
347 out.alpha_ = lhs.alpha_ + rhs.alpha_ - lhs.alpha_ * rhs.alpha_ / 255;
348 return out;
349}
350
351inline namespace literals {
352
353Color operator""_rgb(unsigned long long int combined) {
354 // assert(combined <= 0xffffffU);
355 auto const red = static_cast<uint8_t>(combined >> 16U);
356 auto const green = static_cast<uint8_t>(combined >> 8U);
357 auto const blue = static_cast<uint8_t>(combined);
358 return {red, green, blue};
359}
360
361} // namespace literals
362
363} // namespace ftxui
#define FTXUI_NOINLINE
Definition color.cpp:19
Color
Color is an enumeration that represents the color support of the terminal.
Definition terminal.hpp:31
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
ColorInfo GetColorInfo(Color::Palette256 index)
Color
Build a transparent color.
Definition elements.hpp:99
The FTXUI ftxui::literals:: namespace.