新增功能:终端颜色表示
This commit is contained in:
		
							parent
							
								
									328853740f
								
							
						
					
					
						commit
						88e8d0b51e
					
				
							
								
								
									
										433
									
								
								src/tui/screen/color.cppm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										433
									
								
								src/tui/screen/color.cppm
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,433 @@ | |||||||
|  | /** | ||||||
|  |  * @file        tui/screen/color.cppm | ||||||
|  |  * @brief       适用于终端的颜色表示 | ||||||
|  |  * @author      KeqingMoe | ||||||
|  |  * @date        2024-12-9 | ||||||
|  |  * @copyright   Copyright (c) 2024 KeqingMoe | ||||||
|  |  * @license     MIT License | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export module kqm.tui.screen:color; | ||||||
|  | import kqm.tui.plat; | ||||||
|  | 
 | ||||||
|  | import std; | ||||||
|  | 
 | ||||||
|  | template <std::floating_point Float, typename T> | ||||||
|  |     requires requires(Float f, T x) { std::pow(x, f); } | ||||||
|  | constexpr auto gamma_correct_interp(Float gamma, Float t, T a, T b) noexcept -> T | ||||||
|  | { | ||||||
|  |     auto af = std::pow(a, gamma); | ||||||
|  |     auto bf = std::pow(b, gamma); | ||||||
|  |     auto f  = std::lerp(af, bf, t); | ||||||
|  |     return static_cast<T>(std::pow(f, 1 / gamma)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <char ch> | ||||||
|  | concept DecDigit = ch >= '0' && ch <= '9'; | ||||||
|  | 
 | ||||||
|  | template <char ch> | ||||||
|  | concept FromAToF = (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); | ||||||
|  | 
 | ||||||
|  | template <char ch> | ||||||
|  | concept HexDigit = DecDigit<ch> || FromAToF<ch>; | ||||||
|  | 
 | ||||||
|  | template <char ch1, char ch2, char... chs> | ||||||
|  | constexpr auto HexLiteral = requires { | ||||||
|  |     requires(ch1 == '0'); | ||||||
|  |     requires(ch2 == 'x' || ch2 == 'X'); | ||||||
|  |     requires(sizeof...(chs) > 0); | ||||||
|  |     requires(HexDigit<chs> && ...); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export namespace kqm::tui | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | inline namespace screen | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @brief 表示忽略平台相关的颜色处理的标签 | ||||||
|  |  */ | ||||||
|  | struct ignore_plat_t | ||||||
|  | { | ||||||
|  |     explicit constexpr ignore_plat_t() noexcept = default; | ||||||
|  | } ignore_plat; | ||||||
|  | 
 | ||||||
|  | struct color | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     /** | ||||||
|  |      * @brief 默认构造函数,产生透明的颜色(或表示终端默认颜色) | ||||||
|  |      */ | ||||||
|  |     constexpr color() noexcept : color{palette1::transparent} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 同默认构造函数 | ||||||
|  |      */ | ||||||
|  |     constexpr color(palette1 index) noexcept : colors{index} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 16 种预设颜色构造 | ||||||
|  |      */ | ||||||
|  |     constexpr color(palette16 index) noexcept : colors{index} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 256 种预设颜色构造 | ||||||
|  |      */ | ||||||
|  |     constexpr color(palette256 index) noexcept | ||||||
|  |     { | ||||||
|  |         if (plat::color_depth >= 8uz) colors = index; | ||||||
|  |         else colors = get_nearest_palette16(index); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 256 种预设颜色构造,强制使用原本的颜色(不转换到最近的受支持颜色) | ||||||
|  |      */ | ||||||
|  |     constexpr color(ignore_plat_t, palette256 index) noexcept : colors{index} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGB 真彩色构造 | ||||||
|  |      */ | ||||||
|  |     constexpr color(rgb_t rgb) noexcept : color{rgb.red, rgb.green, rgb.blue} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGB 真彩色构造,强制使用原本的颜色(不转换到最近的受支持颜色) | ||||||
|  |      */ | ||||||
|  |     constexpr color(ignore_plat_t, rgb_t rgb) noexcept : colors{rgb} {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGBA 构造 | ||||||
|  |      */ | ||||||
|  |     constexpr color(u8 red, u8 green, u8 blue, u8 alpha = 255) noexcept : alpha{alpha} | ||||||
|  |     { | ||||||
|  |         if (auto rgb = rgb_t{red, green, blue}; plat::color_depth >= 24uz) colors = rgb; | ||||||
|  |         else if (auto p256 = get_nearest_palette256(rgb); plat::color_depth >= 8uz) colors = p256; | ||||||
|  |         else colors = get_nearest_palette16(p256); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGBA 真彩色构造,强制使用原本的颜色(不转换到最近的受支持颜色) | ||||||
|  |      */ | ||||||
|  |     constexpr color(ignore_plat_t, u8 red, u8 green, u8 blue, u8 alpha = 255) noexcept | ||||||
|  |         : colors{rgb_t{red,green,blue}}, alpha{alpha} | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 打印颜色的 ANSI 控制序列到输出迭代器 | ||||||
|  |      *  | ||||||
|  |      * @tparam Out  | ||||||
|  |      * @param out  | ||||||
|  |      * @param is_bg  | ||||||
|  |      * @return Out  | ||||||
|  |      */ | ||||||
|  |     template <std::output_iterator<char> Out> | ||||||
|  |     auto print_to(Out out, bool is_bg) -> Out | ||||||
|  |     { | ||||||
|  |         struct visitor | ||||||
|  |         { | ||||||
|  |             Out out; | ||||||
|  |             bool is_bg; | ||||||
|  |             char bg_ch; | ||||||
|  |             constexpr auto operator()(palette1) -> Out | ||||||
|  |             { | ||||||
|  |                 return std::format_to(out, "\e[{}9m", bg_ch); | ||||||
|  |             } | ||||||
|  |             constexpr auto operator()(palette16 x) -> Out | ||||||
|  |             { | ||||||
|  |                 auto idx  = std::to_underlying(x); | ||||||
|  |                 auto code = is_bg ? palette16_bg_code[idx] : palette16_fg_code[idx]; | ||||||
|  |                 return std::format_to(out, "\e[{}m", code); | ||||||
|  |             } | ||||||
|  |             constexpr auto operator()(palette256 x) -> Out | ||||||
|  |             { | ||||||
|  |                 return std::format_to(out, "\e[{}8;5;{}m", bg_ch, std::to_underlying(x)); | ||||||
|  |             } | ||||||
|  |             constexpr auto operator()(rgb_t rgb) -> Out | ||||||
|  |             { | ||||||
|  |                 return std::format_to(out, "\e[{}8;2;{};{};{}m", bg_ch, rgb.red, rgb.green, rgb.blue); | ||||||
|  |             } | ||||||
|  |         } vis{out, is_bg, is_bg ? '4' : '3'}; | ||||||
|  |         return std::visit(vis, colors); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 获得颜色对应的 ANSI 控制序列 | ||||||
|  |      *  | ||||||
|  |      * @param is_bg  | ||||||
|  |      * @return std::string  | ||||||
|  |      */ | ||||||
|  |     auto print(bool is_bg) -> std::string | ||||||
|  |     { | ||||||
|  |         auto ret = std::string{}; | ||||||
|  |         print_to(std::back_inserter(ret), is_bg); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 打印前景色的 ANSI 控制序列到输出迭代器 | ||||||
|  |      *  | ||||||
|  |      * @tparam Out  | ||||||
|  |      * @param out  | ||||||
|  |      * @return Out  | ||||||
|  |      */ | ||||||
|  |     template <std::output_iterator<char> Out> | ||||||
|  |     auto fg_to(Out out) -> Out | ||||||
|  |     { | ||||||
|  |         return print_to(out, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 获得前景色的 ANSI 控制序列 | ||||||
|  |      *  | ||||||
|  |      * @return std::string  | ||||||
|  |      */ | ||||||
|  |     auto fg() -> std::string | ||||||
|  |     { | ||||||
|  |         auto ret = std::string{}; | ||||||
|  |         fg_to(std::back_inserter(ret)); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 打印背景色的 ANSI 控制序列到输出迭代器 | ||||||
|  |      *  | ||||||
|  |      * @tparam Out  | ||||||
|  |      * @param out  | ||||||
|  |      * @return Out  | ||||||
|  |      */ | ||||||
|  |     template <std::output_iterator<char> Out> | ||||||
|  |     auto bg_to(Out out) -> Out | ||||||
|  |     { | ||||||
|  |         return print_to(out, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 获得背景色的 ANSI 控制序列 | ||||||
|  |      *  | ||||||
|  |      * @return std::string  | ||||||
|  |      */ | ||||||
|  |     auto bg() -> std::string | ||||||
|  |     { | ||||||
|  |         auto ret = std::string{}; | ||||||
|  |         bg_to(std::back_inserter(ret)); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 颜色正规化 | ||||||
|  |      *  | ||||||
|  |      * @return rgb_t  | ||||||
|  |      */ | ||||||
|  |     constexpr auto normalize() const noexcept -> rgb_t | ||||||
|  |     { | ||||||
|  |         constexpr auto vis = [](auto&& x) static { | ||||||
|  |             return colors::normalize(x); | ||||||
|  |         }; | ||||||
|  |         return std::visit(vis, colors); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 判断颜色是否为完全不透明 | ||||||
|  |      *  | ||||||
|  |      * @return true  | ||||||
|  |      * @return false  | ||||||
|  |      */ | ||||||
|  |     constexpr auto is_opaque() const noexcept -> bool | ||||||
|  |     { | ||||||
|  |         return alpha == 255; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGBA 构造颜色 | ||||||
|  |      *  | ||||||
|  |      * @param red  | ||||||
|  |      * @param green  | ||||||
|  |      * @param blue  | ||||||
|  |      * @param alpha  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     static constexpr auto rgba(u8 red, u8 green, u8 blue, u8 alpha) noexcept -> color | ||||||
|  |     { | ||||||
|  |         return color{red, green, blue, alpha}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 RGB 构造颜色 | ||||||
|  |      *  | ||||||
|  |      * @param red  | ||||||
|  |      * @param green  | ||||||
|  |      * @param blue  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     static constexpr auto rgb(u8 red, u8 green, u8 blue) noexcept -> color | ||||||
|  |     { | ||||||
|  |         return color{red, green, blue}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 HSVA 构造颜色 | ||||||
|  |      *  | ||||||
|  |      * @param h  | ||||||
|  |      * @param s  | ||||||
|  |      * @param v  | ||||||
|  |      * @param alpha  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     static constexpr auto hsva(u8 h, u8 s, u8 v, u8 alpha) noexcept -> color | ||||||
|  |     { | ||||||
|  |         auto region    = h / 43; | ||||||
|  |         auto remainder = h % 43 * 6; | ||||||
|  |         auto p0        = v * (255 - s) >> 8; | ||||||
|  |         auto q0        = v * (255 - (s * remainder >> 8)) >> 8; | ||||||
|  |         auto t0        = v * (255 - (s * (255 - remainder) >> 8)) >> 8; | ||||||
|  |         auto [p, q, t] = std::tuple<u8, u8, u8>{p0, q0, t0}; | ||||||
|  |         switch (region) { | ||||||
|  |         case 0: | ||||||
|  |             return color{v, t, p, alpha}; | ||||||
|  |         case 1: | ||||||
|  |             return color{q, v, p, alpha}; | ||||||
|  |         case 2: | ||||||
|  |             return color{p, v, t, alpha}; | ||||||
|  |         case 3: | ||||||
|  |             return color{p, q, v, alpha}; | ||||||
|  |         case 4: | ||||||
|  |             return color{t, p, v, alpha}; | ||||||
|  |         case 5: | ||||||
|  |             return color{v, p, q, alpha}; | ||||||
|  |         default: | ||||||
|  |             std::unreachable(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 从 HSV 构造颜色 | ||||||
|  |      *  | ||||||
|  |      * @param h  | ||||||
|  |      * @param s  | ||||||
|  |      * @param v  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     static constexpr auto hsv(u8 h, u8 s, u8 v) noexcept -> color | ||||||
|  |     { | ||||||
|  |         return hsva(h, s, v, 255); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 插值两种颜色 | ||||||
|  |      *  | ||||||
|  |      * @tparam Float  | ||||||
|  |      * @param a  | ||||||
|  |      * @param b  | ||||||
|  |      * @param t  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     template <std::floating_point Float> | ||||||
|  |     static constexpr auto interpolate(color a, color b, Float t) noexcept -> color | ||||||
|  |     { | ||||||
|  |         constexpr auto vis = [](auto&& x) static { | ||||||
|  |             return colors::normalize(x); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         auto [r1, g1, b1] = std::visit(vis, a.colors); | ||||||
|  |         auto [r2, g2, b2] = std::visit(vis, a.colors); | ||||||
|  | 
 | ||||||
|  |         constexpr auto gamma = Float{2.2}; | ||||||
|  |         return rgb(gamma_correct_interp(gamma, t, r1, r2), | ||||||
|  |                    gamma_correct_interp(gamma, t, g1, g2), | ||||||
|  |                    gamma_correct_interp(gamma, t, b1, b2)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 混合两种颜色 | ||||||
|  |      *  | ||||||
|  |      * @param a  | ||||||
|  |      * @param b  | ||||||
|  |      * @return color  | ||||||
|  |      */ | ||||||
|  |     static constexpr auto blend(color a, color b) noexcept -> color | ||||||
|  |     { | ||||||
|  |         auto ret  = interpolate(a, b, b.alpha / 256.0); | ||||||
|  |         ret.alpha = a.alpha + b.alpha - a.alpha * b.alpha / 256; | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @brief 判断两种颜色是否相等 | ||||||
|  |      *  | ||||||
|  |      * @param lhs  | ||||||
|  |      * @param rhs  | ||||||
|  |      * @return true  | ||||||
|  |      * @return false  | ||||||
|  |      */ | ||||||
|  |     friend auto operator==(color lhs, color rhs) noexcept -> bool | ||||||
|  |     { | ||||||
|  |         constexpr auto vis = [](auto&& x) static { | ||||||
|  |             return colors::normalize(x); | ||||||
|  |         }; | ||||||
|  |         auto lhs_rgb = std::visit(vis, lhs.colors); | ||||||
|  |         auto rhs_rgb = std::visit(vis, rhs.colors); | ||||||
|  |         return lhs_rgb == rhs_rgb; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::variant<palette1, palette16, palette256, rgb_t> colors; | ||||||
|  |     u8 alpha{255}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | inline namespace literals | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @brief RGB 颜色字面量 | ||||||
|  |  */ | ||||||
|  | template <char... chs> | ||||||
|  |     requires requires { | ||||||
|  |         requires HexLiteral<chs...>; | ||||||
|  |         requires(sizeof...(chs) == 5uz || sizeof...(chs) == 8uz); | ||||||
|  |     } | ||||||
|  | constexpr auto operator""_rgb() noexcept -> color | ||||||
|  | { | ||||||
|  |     auto s = std::array{chs...}; | ||||||
|  | 
 | ||||||
|  |     auto rgb = std::uintmax_t{}; | ||||||
|  |     std::from_chars(s.begin() + 2uz, s.end(), rgb, 16); | ||||||
|  | 
 | ||||||
|  |     auto r = u8{}, g = u8{}, b = u8{}; | ||||||
|  | 
 | ||||||
|  |     r = rgb >> 16; | ||||||
|  |     g = rgb >> 8; | ||||||
|  |     b = rgb; | ||||||
|  | 
 | ||||||
|  |     return color::rgb(r, g, b); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @brief RGBA 颜色字面量 | ||||||
|  |  */ | ||||||
|  | template <char... chs> | ||||||
|  |     requires requires { | ||||||
|  |         requires HexLiteral<chs...>; | ||||||
|  |         requires(sizeof...(chs) == 6uz || sizeof...(chs) == 10uz); | ||||||
|  |     } | ||||||
|  | constexpr auto operator""_rgba() noexcept -> color | ||||||
|  | { | ||||||
|  |     auto s = std::array{chs...}; | ||||||
|  | 
 | ||||||
|  |     auto rgba = std::uintmax_t{}; | ||||||
|  |     std::from_chars(s.begin() + 2uz, s.end(), rgba, 16); | ||||||
|  | 
 | ||||||
|  |     auto r = u8{}, g = u8{}, b = u8{}, a = u8{}; | ||||||
|  | 
 | ||||||
|  |     r = rgba >> 24; | ||||||
|  |     g = rgba >> 16; | ||||||
|  |     b = rgba >> 8; | ||||||
|  |     a = rgba; | ||||||
|  | 
 | ||||||
|  |     return color::rgba(r, g, b, a); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user