新增功能:终端颜色表示
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