Skip to content

Commit

Permalink
Color and number parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mmertama committed Jan 16, 2025
1 parent 56a9ec8 commit a905712
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 10 deletions.
28 changes: 23 additions & 5 deletions gempyrelib/include/gempyre_bitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<std::pair<std::string_view, Color::type>, 147> html_colors = {{
constexpr std::array<std::pair<std::string_view, type>, 147> html_colors = {{
{"AliceBlue", 0xF0F8FF}, {"AntiqueWhite", 0xFAEBD7}, {"Aqua", 0x00FFFF},
{"Aquamarine", 0x7FFFD4}, {"Azure", 0xF0FFFF}, {"Beige", 0xF5F5DC},
{"Bisque", 0xFFE4C4}, {"Black", 0x000000}, {"BlanchedAlmond", 0xFFEBCD},
Expand Down Expand Up @@ -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<Color::type> 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<type> 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<type> get_color(std::string_view color);

}

/// @brief Bitmap for Gempyre Graphics
Expand Down
27 changes: 22 additions & 5 deletions gempyrelib/include/gempyre_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,14 +407,31 @@ inline std::string convert<std::string>(std::string_view source)

/// @brief parse string to value
/// @tparam T
/// @param source
/// @param source
/// @param (optional) base
/// @return value
template <typename T>
std::optional<T> parse(std::string_view source) {
std::istringstream ss(std::string{source});
std::optional<T> parse(std::string_view source, const std::optional<int>& 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<std::istream&>(ss) >> v; //MSVC said it would be otherwise ambiguous
return !ss.fail() ? std::make_optional(v) : std::nullopt;
/*static_cast<std::istream&>(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
Expand Down
40 changes: 40 additions & 0 deletions gempyrelib/src/common/graphics/bitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@

using namespace Gempyre;

constexpr std::optional<Color::type> 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::type> 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<uint32_t>(color.substr(1), 16);
return v ? std::make_optional(rgb_value(*v)) : std::nullopt;
}
if (color.length() == 9) {// #RRGGBBAA
const auto v = GempyreUtils::parse<uint32_t>(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<uint32_t>(color);
return v ? std::make_optional(rgb_value(*v)) : std::nullopt;
}
if (color.length() == 10) {// 0xRRGGBBAA
const auto v = GempyreUtils::parse<uint32_t>(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);
Expand Down
60 changes: 60 additions & 0 deletions test/unittests/unittests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,35 @@
#include "gempyre_graphics.h"
#include "timequeue.h"

TEST(Unittests, parse) {
EXPECT_EQ(GempyreUtils::parse<int>("1"), 1);
EXPECT_EQ(GempyreUtils::parse<int>("0x1"), 1);
EXPECT_EQ(GempyreUtils::parse<unsigned>("1"), 1);
EXPECT_EQ(GempyreUtils::parse<unsigned>("0x1"), 1);

EXPECT_EQ(GempyreUtils::parse<int>("1234567"), 1234567);
EXPECT_EQ(GempyreUtils::parse<int>("0x1234567"), 0x1234567);
EXPECT_EQ(GempyreUtils::parse<unsigned>("1234567"), 1234567);
EXPECT_EQ(GempyreUtils::parse<unsigned>("0x1234567"), 0x1234567);
EXPECT_EQ(GempyreUtils::parse<unsigned>("01234567"), 01234567);

EXPECT_EQ(GempyreUtils::parse<unsigned>("Munkki"), std::nullopt);
EXPECT_EQ(GempyreUtils::parse<unsigned>("12F"), std::nullopt);
EXPECT_EQ(GempyreUtils::parse<unsigned>("12.1"), std::nullopt);
EXPECT_EQ(GempyreUtils::parse<double>("12.1"), 12.1);
EXPECT_EQ(GempyreUtils::parse<double>("012.1"), 12.1);


EXPECT_EQ(GempyreUtils::parse<unsigned>("12F", 16), 0x12F);
EXPECT_EQ(GempyreUtils::parse<unsigned>("12.1", 10), std::nullopt);
EXPECT_EQ(GempyreUtils::parse<double>("12.1", 10), 12.1);
EXPECT_EQ(GempyreUtils::parse<double>("012.1", 8), 12.1);
EXPECT_EQ(GempyreUtils::parse<unsigned>("012", 8), 012);
EXPECT_EQ(GempyreUtils::parse<double>("012", 8), 12);
EXPECT_EQ(GempyreUtils::parse<unsigned>("Munkki", 10), std::nullopt);
}


TEST(Unittests, Test_rgb) {
auto col1 = Gempyre::Color::rgba(0x33, 0x44, 0x55);
EXPECT_EQ(Gempyre::Color::rgb(col1), "#334455");
Expand All @@ -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<std::string_view> 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;
Expand Down

0 comments on commit a905712

Please sign in to comment.