新增功能:终端颜色表示

This commit is contained in:
keqingmoe 2024-12-15 17:30:42 +08:00
parent 328853740f
commit 88e8d0b51e

433
src/tui/screen/color.cppm Normal file
View 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);
}
}
}
}