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"));