From b238ec340317049c371970f931cb7636113b8fde Mon Sep 17 00:00:00 2001 From: patrick96 Date: Sun, 27 Oct 2019 22:06:25 +0100 Subject: [PATCH] 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 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. --- include/utils/color.hpp | 186 ++++++------------------------- src/utils/color.cpp | 174 +++++++++++++++++++++++++++++ tests/unit_tests/utils/color.cpp | 161 +++++++++++++++++--------- 3 files changed, 322 insertions(+), 199 deletions(-) create mode 100644 src/utils/color.cpp diff --git a/include/utils/color.hpp b/include/utils/color.hpp index e461efc4..bfd60617 100644 --- a/include/utils/color.hpp +++ b/include/utils/color.hpp @@ -7,159 +7,47 @@ POLYBAR_NS -static cache g_cache_hex; -static cache g_cache_colors; - -struct rgba; - -namespace color_util { - template - T alpha_channel(const unsigned int value) { - unsigned char a = value >> 24; - if (std::is_same::value) - return a << 8 / 0xff; - else if (std::is_same::value) - return a << 8 | a << 8 / 0xff; - } - - template - T red_channel(const unsigned int value) { - unsigned char r = value >> 16; - if (std::is_same::value) - return r << 8 / 0xff; - else if (std::is_same::value) - return r << 8 | r << 8 / 0xff; - } - - template - T green_channel(const unsigned int value) { - unsigned char g = value >> 8; - if (std::is_same::value) - return g << 8 / 0xff; - else if (std::is_same::value) - return g << 8 | g << 8 / 0xff; - } - - template - T blue_channel(const unsigned int value) { - unsigned char b = value; - if (std::is_same::value) - return b << 8 / 0xff; - else if (std::is_same::value) - return b << 8 | b << 8 / 0xff; - } - - template - 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 - string hex(unsigned int color) { - return *g_cache_hex.object(color, [&] { - char s[12]; - size_t len = 0; - - unsigned char a = alpha_channel(color); - unsigned char r = red_channel(color); - unsigned char g = green_channel(color); - unsigned char b = blue_channel(color); - - if (std::is_same::value) { - len = snprintf(s, sizeof(s), "#%02x%02x%02x%02x", a, r, g, b); - } else if (std::is_same::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(color_util::premultiply_alpha(color)) / 255.0, - color_util::green_channel(color_util::premultiply_alpha(color)) / 255.0, - color_util::blue_channel(color_util::premultiply_alpha(color)) / 255.0) {} - // clang-format on - - operator unsigned int() { - // clang-format off - return 0xFF << 24 - | static_cast(r * 255) << 16 - | static_cast(g * 255) << 8 - | static_cast(b * 255); - // clang-format on - } -}; +static cache 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(color) / 255.0, - color_util::green_channel(color) / 255.0, - color_util::blue_channel(color) / 255.0, - color_util::alpha_channel(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(a * 255) << 24 - | static_cast(r * 255) << 16 - | static_cast(g * 255) << 8 - | static_cast(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 diff --git a/src/utils/color.cpp b/src/utils/color.cpp new file mode 100644 index 00000000..6a638880 --- /dev/null +++ b/src/utils/color.cpp @@ -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(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 diff --git a/tests/unit_tests/utils/color.cpp b/tests/unit_tests/utils/color.cpp index 667b82d7..88454628 100644 --- a/tests/unit_tests/utils/color.cpp +++ b/tests/unit_tests/utils/color.cpp @@ -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(color)); - EXPECT_EQ(0x12, color_util::red_channel(color)); - EXPECT_EQ(0x34, color_util::green_channel(color)); - EXPECT_EQ(0x3434, color_util::green_channel(color)); - EXPECT_EQ(0x56, color_util::blue_channel(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(color)); - EXPECT_EQ(0x1212, color_util::red_channel(color)); - EXPECT_EQ(0x12, color_util::red_channel(color)); - EXPECT_EQ(0x3434, color_util::green_channel(color)); - EXPECT_EQ(0x5656, 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(rgba{0xFF111111})); - EXPECT_EQ(0x00FFFFFF, static_cast(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(colorA)); - unsigned int colorB{0xCC123456}; - EXPECT_EQ("#cc123456"s, color_util::hex(colorB)); - unsigned int colorC{0x00ffffff}; - EXPECT_EQ("#00ffffff"s, color_util::hex(colorC)); +TEST(Rgba, string) { + rgba v{"#1234"}; + + EXPECT_EQ("#11223344", static_cast(v)); + + v = rgba{"#12"}; + + EXPECT_EQ("#12000000", static_cast(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(rgba{"#FF111111"})); + EXPECT_EQ(0x00FFFFFF, static_cast(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"));