Move most color_util functions into rgba class

The intent is for every color to be stored in a rgba instance

The rgba class now stores the color in a 32 bit integer to save space

This also removes the unused class rgb and moves everything else into a
cpp file.

Many functions also had weird template parameters. For example
alpha_channel<unsigned short int> would give a 2 byte number with the
alpha channel byte in both bytes.
color_util::hex would return a hex string with alpha channel if unsigned
short int was given and without if unsigned char was given. Even more
curiously those parameters were passed to *_channel and the result
nevertheless truncated to 8bits.
This commit is contained in:
patrick96 2019-10-27 22:06:25 +01:00 committed by Patrick Ziegler
parent 75eb41f5ad
commit b238ec3403
3 changed files with 322 additions and 199 deletions

View File

@ -7,159 +7,47 @@
POLYBAR_NS
static cache<string, unsigned int> g_cache_hex;
static cache<unsigned int, string> g_cache_colors;
struct rgba;
namespace color_util {
template <typename T = unsigned char>
T alpha_channel(const unsigned int value) {
unsigned char a = value >> 24;
if (std::is_same<T, unsigned char>::value)
return a << 8 / 0xff;
else if (std::is_same<T, unsigned short int>::value)
return a << 8 | a << 8 / 0xff;
}
template <typename T = unsigned char>
T red_channel(const unsigned int value) {
unsigned char r = value >> 16;
if (std::is_same<T, unsigned char>::value)
return r << 8 / 0xff;
else if (std::is_same<T, unsigned short int>::value)
return r << 8 | r << 8 / 0xff;
}
template <typename T = unsigned char>
T green_channel(const unsigned int value) {
unsigned char g = value >> 8;
if (std::is_same<T, unsigned char>::value)
return g << 8 / 0xff;
else if (std::is_same<T, unsigned short int>::value)
return g << 8 | g << 8 / 0xff;
}
template <typename T = unsigned char>
T blue_channel(const unsigned int value) {
unsigned char b = value;
if (std::is_same<T, unsigned char>::value)
return b << 8 / 0xff;
else if (std::is_same<T, unsigned short int>::value)
return b << 8 | b << 8 / 0xff;
}
template <typename T = unsigned int>
unsigned int premultiply_alpha(const T value) {
auto a = color_util::alpha_channel(value);
auto r = color_util::red_channel(value) * a / 255;
auto g = color_util::green_channel(value) * a / 255;
auto b = color_util::blue_channel(value) * a / 255;
return (a << 24) | (r << 16) | (g << 8) | b;
}
template <typename T>
string hex(unsigned int color) {
return *g_cache_hex.object(color, [&] {
char s[12];
size_t len = 0;
unsigned char a = alpha_channel<T>(color);
unsigned char r = red_channel<T>(color);
unsigned char g = green_channel<T>(color);
unsigned char b = blue_channel<T>(color);
if (std::is_same<T, unsigned short int>::value) {
len = snprintf(s, sizeof(s), "#%02x%02x%02x%02x", a, r, g, b);
} else if (std::is_same<T, unsigned char>::value) {
len = snprintf(s, sizeof(s), "#%02x%02x%02x", r, g, b);
}
return string(s, len);
}());
}
inline string parse_hex(string hex) {
if (hex[0] != '#')
hex.insert(0, 1, '#');
if (hex.length() == 4)
hex = {'#', hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]};
if (hex.length() == 7)
hex = "#ff" + hex.substr(1);
if (hex.length() != 9)
return "";
return hex;
}
inline unsigned int parse(string hex, unsigned int fallback = 0) {
if ((hex = parse_hex(hex)).empty())
return fallback;
return std::strtoul(&hex[1], nullptr, 16);
}
inline string simplify_hex(string hex) {
// convert #ffrrggbb to #rrggbb
if (hex.length() == 9 && std::toupper(hex[1]) == 'F' && std::toupper(hex[2]) == 'F') {
hex.erase(1, 2);
}
// convert #rrggbb to #rgb
if (hex.length() == 7) {
if (hex[1] == hex[2] && hex[3] == hex[4] && hex[5] == hex[6]) {
hex = {'#', hex[1], hex[3], hex[5]};
}
}
return hex;
}
}
struct rgb {
double r;
double g;
double b;
// clang-format off
explicit rgb(double r, double g, double b) : r(r), g(g), b(b) {}
explicit rgb(unsigned int color) : rgb(
color_util::red_channel<unsigned char>(color_util::premultiply_alpha(color)) / 255.0,
color_util::green_channel<unsigned char>(color_util::premultiply_alpha(color)) / 255.0,
color_util::blue_channel<unsigned char>(color_util::premultiply_alpha(color)) / 255.0) {}
// clang-format on
operator unsigned int() {
// clang-format off
return 0xFF << 24
| static_cast<int>(r * 255) << 16
| static_cast<int>(g * 255) << 8
| static_cast<int>(b * 255);
// clang-format on
}
};
static cache<string, uint32_t> g_cache_hex;
struct rgba {
double r;
double g;
double b;
double a;
/**
* Color value in the form ARGB or A000 depending on the type
*/
uint32_t m_value;
// clang-format off
explicit rgba(double r, double g, double b, double a) : r(r), g(g), b(b), a(a) {}
explicit rgba(unsigned int color) : rgba(
color_util::red_channel<unsigned char>(color) / 255.0,
color_util::green_channel<unsigned char>(color) / 255.0,
color_util::blue_channel<unsigned char>(color) / 255.0,
color_util::alpha_channel<unsigned char>(color) / 255.0) {}
// clang-format on
enum color_type { NONE, ARGB, ALPHA_ONLY } m_type{NONE};
operator unsigned int() {
// clang-format off
return static_cast<int>(a * 255) << 24
| static_cast<int>(r * 255) << 16
| static_cast<int>(g * 255) << 8
| static_cast<int>(b * 255);
// clang-format on
}
explicit rgba();
explicit rgba(uint32_t value, color_type type = ARGB);
explicit rgba(string hex);
operator string() const;
operator uint32_t() const;
bool operator==(const rgba& other) const;
double a() const;
double r() const;
double g() const;
double b() const;
uint8_t a_int() const;
uint8_t r_int() const;
uint8_t g_int() const;
uint8_t b_int() const;
bool has_color() const;
};
namespace color_util {
uint8_t alpha_channel(const uint32_t value);
uint8_t red_channel(const uint32_t value);
uint8_t green_channel(const uint32_t value);
uint8_t blue_channel(const uint32_t value);
string hex(uint32_t color);
string simplify_hex(string hex);
} // namespace color_util
POLYBAR_NS_END

174
src/utils/color.cpp Normal file
View File

@ -0,0 +1,174 @@
#include "utils/color.hpp"
POLYBAR_NS
/**
* Takes a hex string as input and brings it into a normalized form
*
* The input can either be only an alpha channel #AA
* or any of these forms: #RGB, #ARGB, #RRGGBB, #AARRGGBB
*
* Colors without alpha channel will get an alpha channel of FF
* The input does not have to start with '#'
*
* \returns Empty string for malformed input, either AA for the alpha only
* input or an 8 character string of the expanded form AARRGGBB
*/
static string normalize_hex(string hex) {
if (hex.length() == 0) {
return "";
}
// We remove the hash because it makes processing easier
if (hex[0] == '#') {
hex.erase(0, 1);
}
if (hex.length() == 2) {
// We only have an alpha channel
return hex;
}
if (hex.length() == 3) {
// RGB -> ARGB
hex.insert(0, 1, 'f');
}
if (hex.length() == 4) {
// ARGB -> AARRGGBB
hex = {hex[0], hex[0], hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]};
}
if (hex.length() == 6) {
// RRGGBB -> FFRRGGBB
hex.insert(0, 2, 'f');
}
if (hex.length() != 8) {
return "";
}
return hex;
}
rgba::rgba() : m_value(0), m_type(NONE) {}
rgba::rgba(uint32_t value, color_type type) : m_value(value), m_type(type) {}
rgba::rgba(string hex) {
hex = normalize_hex(hex);
if (hex.length() == 0) {
// TODO we need a way to inform the user that their color was malformed
m_value = 0;
m_type = NONE;
} else if (hex.length() == 2) {
m_value = std::strtoul(hex.c_str(), nullptr, 16) << 24;
m_type = ALPHA_ONLY;
} else {
m_value = std::strtoul(hex.c_str(), nullptr, 16);
m_type = ARGB;
}
}
rgba::operator string() const {
char s[10];
size_t len = 0;
len = snprintf(s, 10, "#%08x", m_value);
return string(s, len);
}
bool rgba::operator==(const rgba& other) const {
if (m_type != other.m_type) {
return false;
}
switch (m_type) {
case NONE:
return true;
case ARGB:
return m_value == other.m_value;
case ALPHA_ONLY:
return a_int() == other.a_int();
default:
return false;
}
}
rgba::operator uint32_t() const {
return m_value;
}
double rgba::a() const {
return a_int() / 255.0;
}
double rgba::r() const {
return r_int() / 255.0;
}
double rgba::g() const {
return g_int() / 255.0;
}
double rgba::b() const {
return b_int() / 255.0;
}
uint8_t rgba::a_int() const {
return color_util::alpha_channel(m_value);
}
uint8_t rgba::r_int() const {
return color_util::red_channel(m_value);
}
uint8_t rgba::g_int() const {
return color_util::green_channel(m_value);
}
uint8_t rgba::b_int() const {
return color_util::blue_channel(m_value);
}
uint8_t color_util::alpha_channel(const uint32_t value) {
return (value >> 24) & 0xFF;
}
uint8_t color_util::red_channel(const uint32_t value) {
return (value >> 16) & 0xFF;
}
uint8_t color_util::green_channel(const uint32_t value) {
return (value >> 8) & 0xFF;
}
uint8_t color_util::blue_channel(const uint32_t value) {
return value & 0xFF;
}
string color_util::hex(uint32_t color) {
return *g_cache_hex.object(color, [&] { return static_cast<string>(rgba{color}); }());
}
bool rgba::has_color() const {
return m_type != NONE;
}
string color_util::simplify_hex(string hex) {
// convert #ffrrggbb to #rrggbb
if (hex.length() == 9 && std::toupper(hex[1]) == 'F' && std::toupper(hex[2]) == 'F') {
hex.erase(1, 2);
}
// convert #rrggbb to #rgb
if (hex.length() == 7) {
if (hex[1] == hex[2] && hex[3] == hex[4] && hex[5] == hex[6]) {
hex = {'#', hex[1], hex[3], hex[5]};
}
}
return hex;
}
POLYBAR_NS_END

View File

@ -1,68 +1,129 @@
#include "common/test.hpp"
#include "utils/color.hpp"
#include "common/test.hpp"
using namespace polybar;
TEST(String, rgb) {
unsigned int color{0x123456};
EXPECT_EQ(0, color_util::alpha_channel<unsigned char>(color));
EXPECT_EQ(0x12, color_util::red_channel<unsigned char>(color));
EXPECT_EQ(0x34, color_util::green_channel<unsigned char>(color));
EXPECT_EQ(0x3434, color_util::green_channel<unsigned short int>(color));
EXPECT_EQ(0x56, color_util::blue_channel<unsigned char>(color));
TEST(Rgba, constructor) {
rgba v{"invalid"};
EXPECT_FALSE(v.has_color());
EXPECT_TRUE(0x33 / 255.0 == rgb{0xFF112233}.b);
EXPECT_TRUE(0x51 / 255.0 == rgb{0x88449933}.g);
EXPECT_TRUE(0xff0f0f0f == rgb{0xee111111});
EXPECT_TRUE(0xff0a141e == rgb{0x99112233});
v = rgba{"#f"};
EXPECT_FALSE(v.has_color());
v = rgba{"#12"};
EXPECT_EQ(rgba::ALPHA_ONLY, v.m_type);
v = rgba{"#ff"};
EXPECT_EQ(0xff000000, v.m_value);
v = rgba{"#fff"};
EXPECT_EQ(0xffffffff, v.m_value);
v = rgba{"#890"};
EXPECT_EQ(0xFF889900, v.m_value);
v = rgba{"#a890"};
EXPECT_EQ(0xaa889900, v.m_value);
v = rgba{"#55888777"};
EXPECT_EQ(0x55888777, v.m_value);
v = rgba{"#88aaaaaa"};
EXPECT_EQ(0x88aaaaaa, v.m_value);
v = rgba{"#00aaaaaa"};
EXPECT_EQ(0x00aaaaaa, v.m_value);
v = rgba{"#00FFFFFF"};
EXPECT_EQ(0x00FFFFFF, v.m_value);
}
TEST(String, rgba) {
unsigned int color{0xCC123456};
EXPECT_EQ(0xCCCC, color_util::alpha_channel<unsigned short int>(color));
EXPECT_EQ(0x1212, color_util::red_channel<unsigned short int>(color));
EXPECT_EQ(0x12, color_util::red_channel<unsigned char>(color));
EXPECT_EQ(0x3434, color_util::green_channel<unsigned short int>(color));
EXPECT_EQ(0x5656, color_util::blue_channel<unsigned short int>(color));
EXPECT_EQ(0xCC / 255.0, rgba{0xCC112233}.a);
EXPECT_EQ(0x99 / 255.0, rgba{0x88449933}.g);
EXPECT_EQ(0xFF111111, static_cast<unsigned int>(rgba{0xFF111111}));
EXPECT_EQ(0x00FFFFFF, static_cast<unsigned int>(rgba{0x00FFFFFF}));
TEST(Rgba, parse) {
EXPECT_EQ(0xffffffff, rgba{"#fff"}.m_value);
EXPECT_EQ(0xffffffff, rgba{"fff"}.m_value);
EXPECT_EQ(0xff112233, rgba{"#123"}.m_value);
EXPECT_EQ(0xff112233, rgba{"123"}.m_value);
EXPECT_EQ(0xff888888, rgba{"#888888"}.m_value);
EXPECT_EQ(0xff888888, rgba{"888888"}.m_value);
EXPECT_EQ(0x00aa00aa, rgba{"#00aa00aa"}.m_value);
EXPECT_EQ(0x00aa00aa, rgba{"00aa00aa"}.m_value);
EXPECT_EQ(0x11223344, rgba{"#1234"}.m_value);
EXPECT_EQ(0x11223344, rgba{"1234"}.m_value);
EXPECT_EQ(0xaa000000, rgba{"#aa"}.m_value);
EXPECT_EQ(0xaa000000, rgba{"aa"}.m_value);
}
TEST(String, hex) {
unsigned int colorA{0x123456};
EXPECT_EQ("#123456"s, color_util::hex<unsigned char>(colorA));
unsigned int colorB{0xCC123456};
EXPECT_EQ("#cc123456"s, color_util::hex<unsigned short int>(colorB));
unsigned int colorC{0x00ffffff};
EXPECT_EQ("#00ffffff"s, color_util::hex<unsigned short int>(colorC));
TEST(Rgba, string) {
rgba v{"#1234"};
EXPECT_EQ("#11223344", static_cast<string>(v));
v = rgba{"#12"};
EXPECT_EQ("#12000000", static_cast<string>(v));
}
TEST(String, parseHex) {
EXPECT_EQ("#ffffffff", color_util::parse_hex("#fff"));
EXPECT_EQ("#ff112233", color_util::parse_hex("#123"));
EXPECT_EQ("#ff888888", color_util::parse_hex("#888888"));
EXPECT_EQ("#00aa00aa", color_util::parse_hex("#00aa00aa"));
TEST(Rgba, eq) {
rgba v(0x12, rgba::NONE);
EXPECT_TRUE(v == rgba(0, rgba::NONE));
EXPECT_TRUE(v == rgba(0x11, rgba::NONE));
EXPECT_FALSE(v == rgba{0x1234});
v = rgba{0xCC123456};
EXPECT_TRUE(v == rgba{0xCC123456});
EXPECT_FALSE(v == rgba(0xCC123456, rgba::NONE));
v = rgba{"#aa"};
EXPECT_TRUE(v == rgba(0xaa000000, rgba::ALPHA_ONLY));
EXPECT_FALSE(v == rgba(0xaa000000, rgba::ARGB));
EXPECT_FALSE(v == rgba(0xab000000, rgba::ALPHA_ONLY));
}
TEST(String, parse) {
EXPECT_EQ(0, color_util::parse("invalid"));
EXPECT_EQ(0, color_util::parse("#f"));
EXPECT_EQ(0, color_util::parse("#ff"));
EXPECT_EQ(0xFF999999, color_util::parse("invalid", 0xFF999999));
EXPECT_EQ(0x00111111, color_util::parse("invalid", 0x00111111));
EXPECT_EQ(0xFF000000, color_util::parse("invalid", 0xFF000000));
EXPECT_EQ(0xffffffff, color_util::parse("#fff"));
EXPECT_EQ(0xFF889900, color_util::parse("#890"));
EXPECT_EQ(0x55888777, color_util::parse("#55888777"));
EXPECT_EQ(0x88aaaaaa, color_util::parse("#88aaaaaa"));
EXPECT_EQ(0x00aaaaaa, color_util::parse("#00aaaaaa"));
EXPECT_EQ(0x00FFFFFF, color_util::parse("#00FFFFFF"));
TEST(Rgba, hasColor) {
rgba v{"#"};
EXPECT_FALSE(v.has_color());
v = rgba{"#ff"};
EXPECT_TRUE(v.has_color());
v = rgba{"#cc123456"};
EXPECT_TRUE(v.has_color());
v = rgba(0x1243, rgba::NONE);
EXPECT_FALSE(v.has_color());
}
TEST(String, simplify) {
TEST(ColorUtil, rgba) {
uint32_t color{0xCC123456};
EXPECT_EQ(0xCC, color_util::alpha_channel(color));
EXPECT_EQ(0x12, color_util::red_channel(color));
EXPECT_EQ(0x34, color_util::green_channel(color));
EXPECT_EQ(0x56, color_util::blue_channel(color));
EXPECT_EQ(0xCC / 255.0, rgba{0xCC112233}.a());
EXPECT_EQ(0x99 / 255.0, rgba{0x88449933}.g());
EXPECT_EQ(0xFF111111, static_cast<uint32_t>(rgba{"#FF111111"}));
EXPECT_EQ(0x00FFFFFF, static_cast<uint32_t>(rgba{"#00FFFFFF"}));
}
TEST(ColorUtil, hex) {
uint32_t colorA{0x123456};
EXPECT_EQ("#00123456"s, color_util::hex(colorA));
uint32_t colorB{0xCC123456};
EXPECT_EQ("#cc123456"s, color_util::hex(colorB));
uint32_t colorC{0x00ffffff};
EXPECT_EQ("#00ffffff"s, color_util::hex(colorC));
}
TEST(ColorUtil, simplify) {
EXPECT_EQ("#111", color_util::simplify_hex("#FF111111"));
EXPECT_EQ("#234", color_util::simplify_hex("#ff223344"));
EXPECT_EQ("#ee223344", color_util::simplify_hex("#ee223344"));