Add support for alpha channel in colors
This makes it easier to define faces that lighten/darken whatever they apply on.
This commit is contained in:
parent
c6906be475
commit
ccecd5bd8e
|
@ -6,9 +6,9 @@ A 'face' refers how the specified text is displayed, it has a foreground
|
||||||
color, a background color, and some attributes. The value of a face has the
|
color, a background color, and some attributes. The value of a face has the
|
||||||
following format:
|
following format:
|
||||||
|
|
||||||
---------------------------------------
|
-----------------------------------------
|
||||||
[fg_color][,bg_color][+attributes][@base]
|
[fg_color][,bg_color][+attributes][@base]
|
||||||
---------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
'fg_color', 'bg_color'::
|
'fg_color', 'bg_color'::
|
||||||
a color whose value can be expressed in the following formats:
|
a color whose value can be expressed in the following formats:
|
||||||
|
@ -20,7 +20,11 @@ following format:
|
||||||
keep the existing color
|
keep the existing color
|
||||||
*rgb:RRGGBB*:::
|
*rgb:RRGGBB*:::
|
||||||
hexadecimal value
|
hexadecimal value
|
||||||
|
*rgba:RRGGBBAA*:::
|
||||||
|
hexadecimal value
|
||||||
if unspecified, *default* is used.
|
if unspecified, *default* is used.
|
||||||
|
Alpha values are used to blend the face onto either its base or the existing
|
||||||
|
color. For technical reasons alpha values must be >16 if specified.
|
||||||
|
|
||||||
'attributes'::
|
'attributes'::
|
||||||
string whose individual letters set an attribute:
|
string whose individual letters set an attribute:
|
||||||
|
|
19
src/color.cc
19
src/color.cc
|
@ -34,6 +34,13 @@ bool is_color_name(StringView color)
|
||||||
return contains(color_names, color);
|
return contains(color_names, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Color::validate_alpha()
|
||||||
|
{
|
||||||
|
static_assert(RGB == 17);
|
||||||
|
if (a < RGB)
|
||||||
|
throw runtime_error("Colors alpha must be > 16");
|
||||||
|
}
|
||||||
|
|
||||||
Color str_to_color(StringView color)
|
Color str_to_color(StringView color)
|
||||||
{
|
{
|
||||||
auto it = find_if(color_names, [&](const char* c){ return color == c; });
|
auto it = find_if(color_names, [&](const char* c){ return color == c; });
|
||||||
|
@ -55,6 +62,11 @@ Color str_to_color(StringView color)
|
||||||
return { (unsigned char)(hval(color[4]) * 16 + hval(color[5])),
|
return { (unsigned char)(hval(color[4]) * 16 + hval(color[5])),
|
||||||
(unsigned char)(hval(color[6]) * 16 + hval(color[7])),
|
(unsigned char)(hval(color[6]) * 16 + hval(color[7])),
|
||||||
(unsigned char)(hval(color[8]) * 16 + hval(color[9])) };
|
(unsigned char)(hval(color[8]) * 16 + hval(color[9])) };
|
||||||
|
if (color.length() == 13 and color.substr(0_byte, 5_byte) == "rgba:")
|
||||||
|
return { (unsigned char)(hval(color[5]) * 16 + hval(color[6])),
|
||||||
|
(unsigned char)(hval(color[7]) * 16 + hval(color[8])),
|
||||||
|
(unsigned char)(hval(color[9]) * 16 + hval(color[10])),
|
||||||
|
(unsigned char)(hval(color[11]) * 16 + hval(color[12])) };
|
||||||
|
|
||||||
throw runtime_error(format("unable to parse color: '{}'", color));
|
throw runtime_error(format("unable to parse color: '{}'", color));
|
||||||
return Color::Default;
|
return Color::Default;
|
||||||
|
@ -62,10 +74,13 @@ Color str_to_color(StringView color)
|
||||||
|
|
||||||
String to_string(Color color)
|
String to_string(Color color)
|
||||||
{
|
{
|
||||||
if (color.color == Color::RGB)
|
if (color.isRGB())
|
||||||
{
|
{
|
||||||
char buffer[11];
|
char buffer[14];
|
||||||
|
if (color.a == 255)
|
||||||
sprintf(buffer, "rgb:%02x%02x%02x", color.r, color.g, color.b);
|
sprintf(buffer, "rgb:%02x%02x%02x", color.r, color.g, color.b);
|
||||||
|
else
|
||||||
|
sprintf(buffer, "rgba:%02x%02x%02x%02x", color.r, color.g, color.b, color.a);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
23
src/color.hh
23
src/color.hh
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
#include "meta.hh"
|
#include "meta.hh"
|
||||||
|
#include "assert.hh"
|
||||||
|
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
{
|
{
|
||||||
|
@ -12,7 +13,7 @@ class StringView;
|
||||||
|
|
||||||
struct Color
|
struct Color
|
||||||
{
|
{
|
||||||
enum NamedColor : char
|
enum NamedColor : unsigned char
|
||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
Black,
|
Black,
|
||||||
|
@ -34,15 +35,27 @@ struct Color
|
||||||
RGB,
|
RGB,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
NamedColor color;
|
NamedColor color;
|
||||||
|
unsigned char a;
|
||||||
|
};
|
||||||
unsigned char r = 0;
|
unsigned char r = 0;
|
||||||
unsigned char g = 0;
|
unsigned char g = 0;
|
||||||
unsigned char b = 0;
|
unsigned char b = 0;
|
||||||
|
|
||||||
|
constexpr bool isRGB() const { return a >= RGB; }
|
||||||
|
|
||||||
constexpr Color() : Color{Default} {}
|
constexpr Color() : Color{Default} {}
|
||||||
constexpr Color(NamedColor c) : color{c} {}
|
constexpr Color(NamedColor c) : color{c} {}
|
||||||
constexpr Color(unsigned char r, unsigned char g, unsigned char b)
|
constexpr Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255)
|
||||||
: color{RGB}, r{r}, g{g}, b{b} {}
|
: a{a}, r{r}, g{g}, b{b}
|
||||||
|
{
|
||||||
|
validate_alpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void validate_alpha();
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr bool operator==(Color lhs, Color rhs)
|
constexpr bool operator==(Color lhs, Color rhs)
|
||||||
|
@ -66,8 +79,8 @@ bool is_color_name(StringView color);
|
||||||
|
|
||||||
constexpr size_t hash_value(const Color& val)
|
constexpr size_t hash_value(const Color& val)
|
||||||
{
|
{
|
||||||
return val.color == Color::RGB ?
|
return val.isRGB() ?
|
||||||
hash_values(val.color, val.r, val.g, val.b)
|
hash_values(val.a, val.r, val.g, val.b)
|
||||||
: hash_value(val.color);
|
: hash_value(val.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2328,7 +2328,7 @@ const CommandDesc set_face_cmd = {
|
||||||
"set-face <scope> <name> <facespec>: set face <name> to refer to <facespec> in <scope>\n"
|
"set-face <scope> <name> <facespec>: set face <name> to refer to <facespec> in <scope>\n"
|
||||||
"\n"
|
"\n"
|
||||||
"facespec format is <fg color>[,<bg color>][+<attributes>][@<base>]\n"
|
"facespec format is <fg color>[,<bg color>][+<attributes>][@<base>]\n"
|
||||||
"colors are either a color name, or rgb:###### values.\n"
|
"colors are either a color name, rgb:######, or rgba:######## values.\n"
|
||||||
"attributes is a combination of:\n"
|
"attributes is a combination of:\n"
|
||||||
" u: underline, i: italic, b: bold, r: reverse,\n"
|
" u: underline, i: italic, b: bold, r: reverse,\n"
|
||||||
" B: blink, d: dim,\n"
|
" B: blink, d: dim,\n"
|
||||||
|
|
10
src/face.hh
10
src/face.hh
|
@ -50,6 +50,14 @@ struct Face
|
||||||
|
|
||||||
inline Face merge_faces(const Face& base, const Face& face)
|
inline Face merge_faces(const Face& base, const Face& face)
|
||||||
{
|
{
|
||||||
|
auto alpha_blend = [](Color base, Color color) {
|
||||||
|
auto blend = [&](unsigned char Color::*field) {
|
||||||
|
int blended = (base.*field * (255 - color.a) + color.*field * color.a) / 255;
|
||||||
|
return static_cast<unsigned char>(blended <= 255 ? blended : 255);
|
||||||
|
};
|
||||||
|
return Color{blend(&Color::r), blend(&Color::g), blend(&Color::b), base.a};
|
||||||
|
};
|
||||||
|
|
||||||
auto choose = [&](Color Face::*color, Attribute final_attr) {
|
auto choose = [&](Color Face::*color, Attribute final_attr) {
|
||||||
if (face.attributes & final_attr)
|
if (face.attributes & final_attr)
|
||||||
return face.*color;
|
return face.*color;
|
||||||
|
@ -57,6 +65,8 @@ inline Face merge_faces(const Face& base, const Face& face)
|
||||||
return base.*color;
|
return base.*color;
|
||||||
if (face.*color == Color::Default)
|
if (face.*color == Color::Default)
|
||||||
return base.*color;
|
return base.*color;
|
||||||
|
if ((base.*color).isRGB() and (face.*color).isRGB() and (face.*color).a != 255)
|
||||||
|
return alpha_blend(base.*color, face.*color);
|
||||||
return face.*color;
|
return face.*color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -252,7 +252,7 @@ int NCursesUI::Palette::get_color(Color color)
|
||||||
return it->value;
|
return it->value;
|
||||||
else if (m_change_colors and can_change_color() and COLORS > 16)
|
else if (m_change_colors and can_change_color() and COLORS > 16)
|
||||||
{
|
{
|
||||||
kak_assert(color.color == Color::RGB);
|
kak_assert(color.isRGB());
|
||||||
if (m_next_color > COLORS)
|
if (m_next_color > COLORS)
|
||||||
m_next_color = 16;
|
m_next_color = 16;
|
||||||
init_color(m_next_color,
|
init_color(m_next_color,
|
||||||
|
@ -264,7 +264,7 @@ int NCursesUI::Palette::get_color(Color color)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
kak_assert(color.color == Color::RGB);
|
kak_assert(color.isRGB());
|
||||||
int lowestDist = INT_MAX;
|
int lowestDist = INT_MAX;
|
||||||
int closestCol = -1;
|
int closestCol = -1;
|
||||||
for (int i = 0; i < std::min(256, COLORS); ++i)
|
for (int i = 0; i < std::min(256, COLORS); ++i)
|
||||||
|
|
|
@ -127,7 +127,7 @@ private:
|
||||||
void write_field(Color color)
|
void write_field(Color color)
|
||||||
{
|
{
|
||||||
write_field(color.color);
|
write_field(color.color);
|
||||||
if (color.color == Color::RGB)
|
if (color.isRGB())
|
||||||
{
|
{
|
||||||
write_field(color.r);
|
write_field(color.r);
|
||||||
write_field(color.g);
|
write_field(color.g);
|
||||||
|
@ -305,7 +305,7 @@ struct MsgReader::Reader<Color> {
|
||||||
{
|
{
|
||||||
Color res;
|
Color res;
|
||||||
res.color = Reader<Color::NamedColor>::read(reader);
|
res.color = Reader<Color::NamedColor>::read(reader);
|
||||||
if (res.color == Color::RGB)
|
if (res.isRGB())
|
||||||
{
|
{
|
||||||
res.r = Reader<unsigned char>::read(reader);
|
res.r = Reader<unsigned char>::read(reader);
|
||||||
res.g = Reader<unsigned char>::read(reader);
|
res.g = Reader<unsigned char>::read(reader);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user