From a905712d5bc617edcf1d6b6a644b02ac030efbd6 Mon Sep 17 00:00:00 2001 From: Markus Mertama Date: Thu, 16 Jan 2025 12:19:19 +0200 Subject: [PATCH] Color and number parsing --- gempyrelib/include/gempyre_bitmap.h | 28 +++++++++-- gempyrelib/include/gempyre_utils.h | 27 ++++++++-- gempyrelib/src/common/graphics/bitmap.cpp | 40 +++++++++++++++ test/unittests/unittests.cpp | 60 +++++++++++++++++++++++ 4 files changed, 145 insertions(+), 10 deletions(-) diff --git a/gempyrelib/include/gempyre_bitmap.h b/gempyrelib/include/gempyre_bitmap.h index a30ade15..0e47da1f 100644 --- a/gempyrelib/include/gempyre_bitmap.h +++ b/gempyrelib/include/gempyre_bitmap.h @@ -129,6 +129,19 @@ namespace Gempyre { return Gempyre::Color::rgba(Gempyre::Color::r(col), Gempyre::Color::g(col), Gempyre::Color::b(col), alpha_val); } + + [[nodiscard]] + ///@brief Apply given alpha to a color + static inline auto rgb_value(uint32_t col) { + return Gempyre::Color::rgba(0xFF & (col >> 16), 0xFF & (col >> 8), 0xFF & col, 0xFF); + } + + [[nodiscard]] + ///@brief Apply given alpha to a color + static inline auto rgba_value(uint32_t col) { + return Gempyre::Color::rgba(0xFF & (col >> 24), 0xFF & (col >> 16), 0xFF & (col >> 8), 0xFF & col); + } + /// @brief Black static constexpr Color::type Black = Color::rgba(0, 0, 0, 0xFF); @@ -154,10 +167,9 @@ namespace Gempyre { static constexpr Color::type Lime = Green; /// @brief Transparent static constexpr Color::type Transparent = Color::rgba(0, 0, 0, 0); - } /// @brief HTML colors - constexpr std::array, 147> html_colors = {{ + constexpr std::array, 147> html_colors = {{ {"AliceBlue", 0xF0F8FF}, {"AntiqueWhite", 0xFAEBD7}, {"Aqua", 0x00FFFF}, {"Aquamarine", 0x7FFFD4}, {"Azure", 0xF0FFFF}, {"Beige", 0xF5F5DC}, {"Bisque", 0xFFE4C4}, {"Black", 0x000000}, {"BlanchedAlmond", 0xFFEBCD}, @@ -207,12 +219,18 @@ namespace Gempyre { {"WhiteSmoke", 0xF5F5F5}, {"Yellow", 0xFFFF00}, {"YellowGreen", 0x9ACD32} }}; + [[nodiscard]] inline bool is_equal(type a, type b) { + return (a & 0xFFFFFF) == (b & 0xFFFFFF); + } [[nodiscard]] ///@brief find a HTML color as Color::type - inline constexpr std::optional from_html_name(std::string_view name) { - const auto it = std::find_if(std::begin(html_colors), std::end(html_colors), [&](const auto& p) {return p.first == name;}); - return it != std::end(html_colors) ? std::make_optional(it->second) : std::nullopt; + constexpr std::optional from_html_name(std::string_view name); + + [[nodiscard]] + ///@brief get color from string, where string is a HTML color name, #RRGGBB, #RRGGBBAA, 0xRRGGBB or 0xRRGGBBAA + std::optional get_color(std::string_view color); + } /// @brief Bitmap for Gempyre Graphics diff --git a/gempyrelib/include/gempyre_utils.h b/gempyrelib/include/gempyre_utils.h index d993da7a..97f421c7 100644 --- a/gempyrelib/include/gempyre_utils.h +++ b/gempyrelib/include/gempyre_utils.h @@ -407,14 +407,31 @@ inline std::string convert(std::string_view source) /// @brief parse string to value /// @tparam T -/// @param source +/// @param source +/// @param (optional) base /// @return value template -std::optional parse(std::string_view source) { - std::istringstream ss(std::string{source}); +std::optional parse(std::string_view source, const std::optional& forced_base = std::nullopt) { + if (source.empty()) + return std::nullopt; + std::stringstream ss; + if (forced_base) { + ss << std::setbase(*forced_base) << source; + } else { + auto base = std::dec; + if (source.size() > 1 && source[0] == '0') { + if (source.size() > 2 && (source[1] == 'x' || source[1] == 'X')) { + base = std::hex; + } else { + base = std::oct; + } + } + ss << base << source; + } T v; - static_cast(ss) >> v; //MSVC said it would be otherwise ambiguous - return !ss.fail() ? std::make_optional(v) : std::nullopt; + /*static_cast(ss)*/ + ss >> v; //MSVC said it would be otherwise ambiguous + return !ss.fail() && ss.eof() ? std::make_optional(v) : std::nullopt; } /// @brief make lower case string diff --git a/gempyrelib/src/common/graphics/bitmap.cpp b/gempyrelib/src/common/graphics/bitmap.cpp index 453b5151..01a1eacc 100644 --- a/gempyrelib/src/common/graphics/bitmap.cpp +++ b/gempyrelib/src/common/graphics/bitmap.cpp @@ -10,6 +10,46 @@ using namespace Gempyre; +constexpr std::optional Color::from_html_name(std::string_view name) { + const auto it = std::find_if(std::begin(html_colors), std::end(html_colors), [&](const auto& p) {return p.first == name;}); + return it != std::end(html_colors) ? std::make_optional(rgba( + (it->second >> 16) & 0xFF, + (it->second >> 8) & 0xFF, + (it->second) & 0xFF, + 0xFF + )) : std::nullopt; + } + + std::optional Color::get_color(std::string_view color) { + if (color.empty()) + return std::nullopt; + if (color[0] == '#') { + if (color.length() == 7) {// #RRGGBB + const auto v = GempyreUtils::parse(color.substr(1), 16); + return v ? std::make_optional(rgb_value(*v)) : std::nullopt; + } + if (color.length() == 9) {// #RRGGBBAA + const auto v = GempyreUtils::parse(color.substr(1), 16); + return v ? std::make_optional(rgba_value(*v)) : std::nullopt; + } + } + if (color[0] == '0') { + if (color.length() == 8) {// 0xRRGGBB + const auto v = GempyreUtils::parse(color); + return v ? std::make_optional(rgb_value(*v)) : std::nullopt; + } + if (color.length() == 10) {// 0xRRGGBBAA + const auto v = GempyreUtils::parse(color); + return v ? std::make_optional(rgba_value(*v)) : std::nullopt; + } + } else { + const auto named = from_html_name(color); + if (named) + return named; + } + return std::nullopt; + } + Bitmap::Bitmap(int width, int height) { if(width > 0 && height > 0) create(width, height); diff --git a/test/unittests/unittests.cpp b/test/unittests/unittests.cpp index 6b566388..074dd540 100644 --- a/test/unittests/unittests.cpp +++ b/test/unittests/unittests.cpp @@ -6,6 +6,35 @@ #include "gempyre_graphics.h" #include "timequeue.h" +TEST(Unittests, parse) { + EXPECT_EQ(GempyreUtils::parse("1"), 1); + EXPECT_EQ(GempyreUtils::parse("0x1"), 1); + EXPECT_EQ(GempyreUtils::parse("1"), 1); + EXPECT_EQ(GempyreUtils::parse("0x1"), 1); + + EXPECT_EQ(GempyreUtils::parse("1234567"), 1234567); + EXPECT_EQ(GempyreUtils::parse("0x1234567"), 0x1234567); + EXPECT_EQ(GempyreUtils::parse("1234567"), 1234567); + EXPECT_EQ(GempyreUtils::parse("0x1234567"), 0x1234567); + EXPECT_EQ(GempyreUtils::parse("01234567"), 01234567); + + EXPECT_EQ(GempyreUtils::parse("Munkki"), std::nullopt); + EXPECT_EQ(GempyreUtils::parse("12F"), std::nullopt); + EXPECT_EQ(GempyreUtils::parse("12.1"), std::nullopt); + EXPECT_EQ(GempyreUtils::parse("12.1"), 12.1); + EXPECT_EQ(GempyreUtils::parse("012.1"), 12.1); + + + EXPECT_EQ(GempyreUtils::parse("12F", 16), 0x12F); + EXPECT_EQ(GempyreUtils::parse("12.1", 10), std::nullopt); + EXPECT_EQ(GempyreUtils::parse("12.1", 10), 12.1); + EXPECT_EQ(GempyreUtils::parse("012.1", 8), 12.1); + EXPECT_EQ(GempyreUtils::parse("012", 8), 012); + EXPECT_EQ(GempyreUtils::parse("012", 8), 12); + EXPECT_EQ(GempyreUtils::parse("Munkki", 10), std::nullopt); +} + + TEST(Unittests, Test_rgb) { auto col1 = Gempyre::Color::rgba(0x33, 0x44, 0x55); EXPECT_EQ(Gempyre::Color::rgb(col1), "#334455"); @@ -28,6 +57,37 @@ TEST(Unittests, Test_rgb) { EXPECT_EQ(Gempyre::Color::rgba(col5), "#112233CC"); } +TEST(Unittests, Test_colors) { + EXPECT_EQ(*Gempyre::Color::get_color("Magenta"), Gempyre::Color::Magenta); + EXPECT_EQ(Gempyre::Color::get_color("Pagenta"), std::nullopt); + EXPECT_EQ(Gempyre::Color::to_string(Gempyre::Color::Magenta), std::string("#FF00FF")); + EXPECT_EQ(*Gempyre::Color::get_color(Gempyre::Color::to_string(Gempyre::Color::Magenta)), Gempyre::Color::Magenta); + const std::vector cyans { + "0x00FFFF", + "0x00FFFFFF", + "0x00ffff", + "0x00ffffff", + "0x00ffff00", + "0x00FFFF00", + "#00FFFF", + "#00FFFFFF", + "#00ffff", + "#00ffffff", + "#00ffff00", + "#00FFFF00"}; + + for (const auto& cy : cyans) { + EXPECT_TRUE(Gempyre::Color::is_equal(*Gempyre::Color::get_color(cy), Gempyre::Color::Cyan)) << "cy:" << cy; + } + + constexpr auto c1 = "#112233"; + EXPECT_EQ(Gempyre::Color::get_color(c1), Gempyre::Color::rgba(0x11, 0x22, 0x33, 0xFF)); + constexpr auto c2 = "0x112233"; + EXPECT_EQ(Gempyre::Color::get_color(c2), Gempyre::Color::rgba(0x11, 0x22, 0x33, 0xFF)); + + constexpr auto c3 = "0x11XX33"; + EXPECT_EQ(Gempyre::Color::get_color(c3), std::nullopt); +} TEST(Unittests, test_timequeue) { Gempyre::TimeQueue tq;