diff --git a/src/tui/screen/color.cppm b/src/tui/screen/color.cppm new file mode 100644 index 0000000..a5d4614 --- /dev/null +++ b/src/tui/screen/color.cppm @@ -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 + 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(std::pow(f, 1 / gamma)); +} + +template +concept DecDigit = ch >= '0' && ch <= '9'; + +template +concept FromAToF = (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); + +template +concept HexDigit = DecDigit || FromAToF; + +template +constexpr auto HexLiteral = requires { + requires(ch1 == '0'); + requires(ch2 == 'x' || ch2 == 'X'); + requires(sizeof...(chs) > 0); + requires(HexDigit && ...); +}; + +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 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 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 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{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 + 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 colors; + u8 alpha{255}; +}; + +inline namespace literals +{ + +/** + * @brief RGB 颜色字面量 + */ +template + requires requires { + requires HexLiteral; + 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 + requires requires { + requires HexLiteral; + 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); +} + +} + +} + +}