mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-24 19:50:35 +08:00
Color alpha support. (#884)
This commit is contained in:
parent
d6a2049483
commit
ff305147ca
@ -43,6 +43,11 @@ current (development)
|
|||||||
|
|
||||||
### Screen
|
### Screen
|
||||||
- Feature: Add `Box::IsEmpty()`.
|
- Feature: Add `Box::IsEmpty()`.
|
||||||
|
- Feature: Color transparency
|
||||||
|
- Add `Color::RGBA(r,g,b,a)`.
|
||||||
|
- Add `Color::HSVA(r,g,b,a)`.
|
||||||
|
- Add `Color::Blend(Color)`.
|
||||||
|
- Add `Color::IsOpaque()`
|
||||||
|
|
||||||
### Util
|
### Util
|
||||||
- Feature: Support arbitrary `Adapter` for `ConstStringListRef`. See #843.
|
- Feature: Support arbitrary `Adapter` for `ConstStringListRef`. See #843.
|
||||||
|
@ -29,10 +29,16 @@ class Color {
|
|||||||
Color(Palette16 index); // Implicit conversion from index to Color.
|
Color(Palette16 index); // Implicit conversion from index to Color.
|
||||||
Color(Palette256 index); // Implicit conversion from index to Color.
|
Color(Palette256 index); // Implicit conversion from index to Color.
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
Color(uint8_t red, uint8_t green, uint8_t blue);
|
Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255);
|
||||||
static Color RGB(uint8_t red, uint8_t green, uint8_t blue);
|
static Color RGB(uint8_t red, uint8_t green, uint8_t blue);
|
||||||
static Color HSV(uint8_t hue, uint8_t saturation, uint8_t value);
|
static Color HSV(uint8_t hue, uint8_t saturation, uint8_t value);
|
||||||
|
static Color RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha);
|
||||||
|
static Color HSVA(uint8_t hue,
|
||||||
|
uint8_t saturation,
|
||||||
|
uint8_t value,
|
||||||
|
uint8_t alpha);
|
||||||
static Color Interpolate(float t, const Color& a, const Color& b);
|
static Color Interpolate(float t, const Color& a, const Color& b);
|
||||||
|
static Color Blend(const Color& lhs, const Color& rhs);
|
||||||
|
|
||||||
//---------------------------
|
//---------------------------
|
||||||
// List of colors:
|
// List of colors:
|
||||||
@ -310,6 +316,7 @@ class Color {
|
|||||||
bool operator!=(const Color& rhs) const;
|
bool operator!=(const Color& rhs) const;
|
||||||
|
|
||||||
std::string Print(bool is_background_color) const;
|
std::string Print(bool is_background_color) const;
|
||||||
|
bool IsOpaque() const { return alpha_ == 255; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class ColorType : uint8_t {
|
enum class ColorType : uint8_t {
|
||||||
@ -322,6 +329,7 @@ class Color {
|
|||||||
uint8_t red_ = 0;
|
uint8_t red_ = 0;
|
||||||
uint8_t green_ = 0;
|
uint8_t green_ = 0;
|
||||||
uint8_t blue_ = 0;
|
uint8_t blue_ = 0;
|
||||||
|
uint8_t alpha_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline namespace literals {
|
inline namespace literals {
|
||||||
|
@ -38,7 +38,7 @@ struct Pixel {
|
|||||||
|
|
||||||
// The graphemes stored into the pixel. To support combining characters,
|
// The graphemes stored into the pixel. To support combining characters,
|
||||||
// like: a?, this can potentially contain multiple codepoints.
|
// like: a?, this can potentially contain multiple codepoints.
|
||||||
std::string character = " ";
|
std::string character = "";
|
||||||
|
|
||||||
// Colors:
|
// Colors:
|
||||||
Color background_color = Color::Default;
|
Color background_color = Color::Default;
|
||||||
|
@ -23,6 +23,7 @@ class ClearUnder : public NodeDecorator {
|
|||||||
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||||
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||||
screen.PixelAt(x, y) = Pixel();
|
screen.PixelAt(x, y) = Pixel();
|
||||||
|
screen.PixelAt(x, y).character = " "; // Consider the pixel written.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Render(screen);
|
Node::Render(screen);
|
||||||
|
@ -19,11 +19,20 @@ class BgColor : public NodeDecorator {
|
|||||||
: NodeDecorator(std::move(child)), color_(color) {}
|
: NodeDecorator(std::move(child)), color_(color) {}
|
||||||
|
|
||||||
void Render(Screen& screen) override {
|
void Render(Screen& screen) override {
|
||||||
|
if (color_.IsOpaque()) {
|
||||||
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||||
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||||
screen.PixelAt(x, y).background_color = color_;
|
screen.PixelAt(x, y).background_color = color_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||||
|
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||||
|
Color& color = screen.PixelAt(x, y).background_color;
|
||||||
|
color = Color::Blend(color, color_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
NodeDecorator::Render(screen);
|
NodeDecorator::Render(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,16 +45,26 @@ class FgColor : public NodeDecorator {
|
|||||||
: NodeDecorator(std::move(child)), color_(color) {}
|
: NodeDecorator(std::move(child)), color_(color) {}
|
||||||
|
|
||||||
void Render(Screen& screen) override {
|
void Render(Screen& screen) override {
|
||||||
|
if (color_.IsOpaque()) {
|
||||||
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||||
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||||
screen.PixelAt(x, y).foreground_color = color_;
|
screen.PixelAt(x, y).foreground_color = color_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||||
|
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||||
|
Color& color = screen.PixelAt(x, y).foreground_color;
|
||||||
|
color = Color::Blend(color, color_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
NodeDecorator::Render(screen);
|
NodeDecorator::Render(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_;
|
Color color_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/// @brief Set the foreground color of an element.
|
/// @brief Set the foreground color of an element.
|
||||||
|
@ -46,6 +46,57 @@ class DBox : public Node {
|
|||||||
child->SetBox(box);
|
child->SetBox(box);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render(Screen& screen) override {
|
||||||
|
if (children_.size() <= 1) {
|
||||||
|
return Node::Render(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int width = box_.x_max - box_.x_min + 1;
|
||||||
|
const int height = box_.y_max - box_.y_min + 1;
|
||||||
|
std::vector<Pixel> pixels(size_t(width * height));
|
||||||
|
|
||||||
|
for (auto& child : children_) {
|
||||||
|
child->Render(screen);
|
||||||
|
|
||||||
|
// Accumulate the pixels
|
||||||
|
Pixel* acc = pixels.data();
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
auto& pixel = screen.PixelAt(x + box_.x_min, y + box_.y_min);
|
||||||
|
acc->background_color =
|
||||||
|
Color::Blend(acc->background_color, pixel.background_color);
|
||||||
|
acc->automerge = pixel.automerge || acc->automerge;
|
||||||
|
if (pixel.character == "") {
|
||||||
|
acc->foreground_color =
|
||||||
|
Color::Blend(acc->foreground_color, pixel.background_color);
|
||||||
|
} else {
|
||||||
|
acc->blink = pixel.blink;
|
||||||
|
acc->bold = pixel.bold;
|
||||||
|
acc->dim = pixel.dim;
|
||||||
|
acc->inverted = pixel.inverted;
|
||||||
|
acc->underlined = pixel.underlined;
|
||||||
|
acc->underlined_double = pixel.underlined_double;
|
||||||
|
acc->strikethrough = pixel.strikethrough;
|
||||||
|
acc->hyperlink = pixel.hyperlink;
|
||||||
|
acc->character = pixel.character;
|
||||||
|
acc->foreground_color = pixel.foreground_color;
|
||||||
|
}
|
||||||
|
++acc;
|
||||||
|
|
||||||
|
pixel = Pixel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the accumulated pixels:
|
||||||
|
Pixel* acc = pixels.data();
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
screen.PixelAt(x + box_.x_min, y + box_.y_min) = *acc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -73,13 +73,15 @@ Color::Color() = default;
|
|||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
Color::Color(Palette1 /*value*/) : Color() {}
|
Color::Color(Palette1 /*value*/) : Color() {}
|
||||||
|
|
||||||
/// @brief Build a transparent using Palette16 colors.
|
/// @brief Build a color using the Palette16 colors.
|
||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
Color::Color(Palette16 index) : type_(ColorType::Palette16), red_(index) {}
|
Color::Color(Palette16 index)
|
||||||
|
: type_(ColorType::Palette16), red_(index), alpha_(255) {}
|
||||||
|
|
||||||
/// @brief Build a transparent using Palette256 colors.
|
/// @brief Build a color using Palette256 colors.
|
||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
Color::Color(Palette256 index) : type_(ColorType::Palette256), red_(index) {
|
Color::Color(Palette256 index)
|
||||||
|
: type_(ColorType::Palette256), red_(index), alpha_(255) {
|
||||||
if (Terminal::ColorSupport() >= Terminal::Color::Palette256) {
|
if (Terminal::ColorSupport() >= Terminal::Color::Palette256) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -93,9 +95,14 @@ Color::Color(Palette256 index) : type_(ColorType::Palette256), red_(index) {
|
|||||||
/// @param red The quantity of red [0,255]
|
/// @param red The quantity of red [0,255]
|
||||||
/// @param green The quantity of green [0,255]
|
/// @param green The quantity of green [0,255]
|
||||||
/// @param blue The quantity of blue [0,255]
|
/// @param blue The quantity of blue [0,255]
|
||||||
|
/// @param alpha The quantity of alpha [0,255]
|
||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
Color::Color(uint8_t red, uint8_t green, uint8_t blue)
|
Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
|
||||||
: type_(ColorType::TrueColor), red_(red), green_(green), blue_(blue) {
|
: type_(ColorType::TrueColor),
|
||||||
|
red_(red),
|
||||||
|
green_(green),
|
||||||
|
blue_(blue),
|
||||||
|
alpha_(alpha) {
|
||||||
if (Terminal::ColorSupport() == Terminal::Color::TrueColor) {
|
if (Terminal::ColorSupport() == Terminal::Color::TrueColor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -136,7 +143,49 @@ Color::Color(uint8_t red, uint8_t green, uint8_t blue)
|
|||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
// static
|
// static
|
||||||
Color Color::RGB(uint8_t red, uint8_t green, uint8_t blue) {
|
Color Color::RGB(uint8_t red, uint8_t green, uint8_t blue) {
|
||||||
return {red, green, blue};
|
return RGBA(red, green, blue, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Build a Color from its RGBA representation.
|
||||||
|
/// https://en.wikipedia.org/wiki/RGB_color_model
|
||||||
|
/// @param red The quantity of red [0,255]
|
||||||
|
/// @param green The quantity of green [0,255]
|
||||||
|
/// @param blue The quantity of blue [0,255]
|
||||||
|
/// @param alpha The quantity of alpha [0,255]
|
||||||
|
/// @ingroup screen
|
||||||
|
/// @see Color::RGB
|
||||||
|
// static
|
||||||
|
Color Color::RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
|
||||||
|
return {red, green, blue, alpha};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Build a Color from its HSV representation.
|
||||||
|
/// https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||||
|
///
|
||||||
|
/// @param h The hue of the color [0,255]
|
||||||
|
/// @param s The "colorfulness" [0,255].
|
||||||
|
/// @param v The "Lightness" [0,255]
|
||||||
|
/// @param alpha The quantity of alpha [0,255]
|
||||||
|
/// @ingroup screen
|
||||||
|
// static
|
||||||
|
Color Color::HSVA(uint8_t h, uint8_t s, uint8_t v, uint8_t alpha) {
|
||||||
|
uint8_t region = h / 43; // NOLINT
|
||||||
|
uint8_t remainder = (h - (region * 43)) * 6; // NOLINT
|
||||||
|
uint8_t p = (v * (255 - s)) >> 8; // NOLINT
|
||||||
|
uint8_t q = (v * (255 - ((s * remainder) >> 8))) >> 8; // NOLINT
|
||||||
|
uint8_t t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; // NOLINT
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
switch (region) { // NOLINT
|
||||||
|
case 0: return Color(v,t,p, alpha); // NOLINT
|
||||||
|
case 1: return Color(q,v,p, alpha); // NOLINT
|
||||||
|
case 2: return Color(p,v,t, alpha); // NOLINT
|
||||||
|
case 3: return Color(p,q,v, alpha); // NOLINT
|
||||||
|
case 4: return Color(t,p,v, alpha); // NOLINT
|
||||||
|
case 5: return Color(v,p,q, alpha); // NOLINT
|
||||||
|
} // NOLINT
|
||||||
|
// clang-format on
|
||||||
|
return {0, 0, 0, alpha};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Build a Color from its HSV representation.
|
/// @brief Build a Color from its HSV representation.
|
||||||
@ -148,23 +197,7 @@ Color Color::RGB(uint8_t red, uint8_t green, uint8_t blue) {
|
|||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
// static
|
// static
|
||||||
Color Color::HSV(uint8_t h, uint8_t s, uint8_t v) {
|
Color Color::HSV(uint8_t h, uint8_t s, uint8_t v) {
|
||||||
uint8_t region = h / 43; // NOLINT
|
return HSVA(h, s, v, 255);
|
||||||
uint8_t remainder = (h - (region * 43)) * 6; // NOLINT
|
|
||||||
uint8_t p = (v * (255 - s)) >> 8; // NOLINT
|
|
||||||
uint8_t q = (v * (255 - ((s * remainder) >> 8))) >> 8; // NOLINT
|
|
||||||
uint8_t t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; // NOLINT
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
switch (region) { // NOLINT
|
|
||||||
case 0: return Color(v,t,p); // NOLINT
|
|
||||||
case 1: return Color(q,v,p); // NOLINT
|
|
||||||
case 2: return Color(p,v,t); // NOLINT
|
|
||||||
case 3: return Color(p,q,v); // NOLINT
|
|
||||||
case 4: return Color(t,p,v); // NOLINT
|
|
||||||
case 5: return Color(v,p,q); // NOLINT
|
|
||||||
} // NOLINT
|
|
||||||
// clang-format on
|
|
||||||
return {0, 0, 0};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@ -235,6 +268,14 @@ Color Color::Interpolate(float t, const Color& a, const Color& b) {
|
|||||||
interp(a_b, b_b)); //
|
interp(a_b, b_b)); //
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Blend two colors together using the alpha channel.
|
||||||
|
// static
|
||||||
|
Color Color::Blend(const Color& lhs, const Color& rhs) {
|
||||||
|
Color out = Interpolate(float(rhs.alpha_) / 255.F, lhs, rhs);
|
||||||
|
out.alpha_ = lhs.alpha_ + rhs.alpha_ - lhs.alpha_ * rhs.alpha_ / 255;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
inline namespace literals {
|
inline namespace literals {
|
||||||
|
|
||||||
Color operator""_rgb(unsigned long long int combined) {
|
Color operator""_rgb(unsigned long long int combined) {
|
||||||
|
@ -423,8 +423,12 @@ std::string Screen::ToString() const {
|
|||||||
if (!previous_fullwidth) {
|
if (!previous_fullwidth) {
|
||||||
UpdatePixelStyle(this, ss, *previous_pixel_ref, pixel);
|
UpdatePixelStyle(this, ss, *previous_pixel_ref, pixel);
|
||||||
previous_pixel_ref = &pixel;
|
previous_pixel_ref = &pixel;
|
||||||
|
if (pixel.character.empty()) {
|
||||||
|
ss << " ";
|
||||||
|
} else {
|
||||||
ss << pixel.character;
|
ss << pixel.character;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
previous_fullwidth = (string_width(pixel.character) == 2);
|
previous_fullwidth = (string_width(pixel.character) == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user