From 627adca77f73b5f35d0df56937a4b49c7b8c0ce5 Mon Sep 17 00:00:00 2001 From: Albin Johansson Date: Fri, 11 Oct 2024 18:40:00 +0200 Subject: [PATCH] Merge color related source files --- source/base/lib/CMakeLists.txt | 1 - .../lib/inc/tactile/base/io/color_parser.hpp | 104 ---------- .../base/lib/inc/tactile/base/meta/color.hpp | 186 +++++++++++++++++- source/base/test/CMakeLists.txt | 2 +- source/base/test/src/io/color_parser_test.cpp | 84 -------- source/base/test/src/meta/color_test.cpp | 165 ++++++++++++++++ source/core/lib/CMakeLists.txt | 2 - .../core/lib/inc/tactile/core/meta/color.hpp | 105 ---------- .../inc/tactile/core/ui/render/primitives.hpp | 1 - source/core/lib/src/meta/color.cpp | 101 ---------- .../lib/src/ui/common/attribute_widgets.cpp | 5 +- source/core/lib/src/ui/dock/tileset_dock.cpp | 1 - .../lib/src/ui/render/orthogonal_renderer.cpp | 3 +- source/core/test/CMakeLists.txt | 1 - source/core/test/src/meta/color_test.cpp | 136 ------------- .../lib/src/tmj_format_attribute_parser.cpp | 1 - 16 files changed, 353 insertions(+), 545 deletions(-) delete mode 100644 source/base/lib/inc/tactile/base/io/color_parser.hpp delete mode 100644 source/base/test/src/io/color_parser_test.cpp create mode 100644 source/base/test/src/meta/color_test.cpp delete mode 100644 source/core/lib/inc/tactile/core/meta/color.hpp delete mode 100644 source/core/lib/src/meta/color.cpp delete mode 100644 source/core/test/src/meta/color_test.cpp diff --git a/source/base/lib/CMakeLists.txt b/source/base/lib/CMakeLists.txt index 292f75723c..b79ee3a733 100644 --- a/source/base/lib/CMakeLists.txt +++ b/source/base/lib/CMakeLists.txt @@ -24,7 +24,6 @@ target_sources(tactile-base "inc/tactile/base/io/save/save_format.hpp" "inc/tactile/base/io/save/save_format_id.hpp" "inc/tactile/base/io/byte_stream.hpp" - "inc/tactile/base/io/color_parser.hpp" "inc/tactile/base/io/file_io.hpp" "inc/tactile/base/io/int_parser.hpp" "inc/tactile/base/io/tile_io.hpp" diff --git a/source/base/lib/inc/tactile/base/io/color_parser.hpp b/source/base/lib/inc/tactile/base/io/color_parser.hpp deleted file mode 100644 index 279f31b429..0000000000 --- a/source/base/lib/inc/tactile/base/io/color_parser.hpp +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) - -#pragma once - -#include // optional, nullopt -#include // string -#include // string_view - -#include "tactile/base/io/int_parser.hpp" -#include "tactile/base/meta/color.hpp" -#include "tactile/base/prelude.hpp" - -namespace tactile { - -/** - * Parses a color from a hexadecimal RGB color code. - * - * \param rgb An RGB color string, on the form \c "#RRGGBB". - * - * \return - * A color if successful; an empty optional otherwise. - */ -[[nodiscard]] -constexpr auto parse_color_rgb(const std::string_view rgb) noexcept -> std::optional -{ - if (rgb.length() != 7 || rgb.front() != '#') { - return std::nullopt; - } - - const auto r = parse(rgb.substr(1, 2), 16); - const auto g = parse(rgb.substr(3, 2), 16); - const auto b = parse(rgb.substr(5, 2), 16); - - if (r.has_value() && g.has_value() && b.has_value()) { - return UColor {static_cast(*r), - static_cast(*g), - static_cast(*b), - 0xFF}; - } - - return std::nullopt; -} - -/** - * Parses a color from a hexadecimal RGBA color code. - * - * \param rgba An RGBA color string, on the form \c "#RRGGBBAA". - * - * \return - * A color if successful; an empty optional otherwise. - */ -[[nodiscard]] -constexpr auto parse_color_rgba(const std::string_view rgba) noexcept -> std::optional -{ - if (rgba.length() != 9 || rgba.front() != '#') { - return std::nullopt; - } - - const auto r = parse(rgba.substr(1, 2), 16); - const auto g = parse(rgba.substr(3, 2), 16); - const auto b = parse(rgba.substr(5, 2), 16); - const auto a = parse(rgba.substr(7, 2), 16); - - if (r.has_value() && g.has_value() && b.has_value() && a.has_value()) { - return UColor {static_cast(*r), - static_cast(*g), - static_cast(*b), - static_cast(*a)}; - } - - return std::nullopt; -} - -/** - * Parses a color from a hexadecimal ARGB color code. - * - * \param argb An ARGB color string, on the form \c "#AARRGGBB". - * - * \return - * A color if successful; an empty optional otherwise. - */ -[[nodiscard]] -constexpr auto parse_color_argb(const std::string_view argb) noexcept -> std::optional -{ - if (argb.length() != 9 || argb.front() != '#') { - return std::nullopt; - } - - const auto a = parse(argb.substr(1, 2), 16); - const auto r = parse(argb.substr(3, 2), 16); - const auto g = parse(argb.substr(5, 2), 16); - const auto b = parse(argb.substr(7, 2), 16); - - if (a.has_value() && r.has_value() && g.has_value() && b.has_value()) { - return UColor {static_cast(*r), - static_cast(*g), - static_cast(*b), - static_cast(*a)}; - } - - return std::nullopt; -} - -} // namespace tactile diff --git a/source/base/lib/inc/tactile/base/meta/color.hpp b/source/base/lib/inc/tactile/base/meta/color.hpp index ff71246ef2..c251f42065 100644 --- a/source/base/lib/inc/tactile/base/meta/color.hpp +++ b/source/base/lib/inc/tactile/base/meta/color.hpp @@ -2,12 +2,26 @@ #pragma once -#include // uint8_t +#include // clamp +#include // uint8_t, uint32_t +#include // format +#include // optional, nullopt +#include // ostream +#include // invalid_argument +#include // string +#include // string_view -#include "tactile/base/prelude.hpp" +#include "tactile/base/io/int_parser.hpp" namespace tactile { +enum class ColorFormat : std::uint8_t +{ + kRgb, + kRgba, + kArgb +}; + /** * Represents an 8-bit RGBA color. * @@ -44,4 +58,172 @@ struct FColor final constexpr auto operator==(const FColor&) const noexcept -> bool = default; }; +[[nodiscard]] +constexpr auto to_ucolor(const FColor& color) -> UColor +{ + const auto to_u8 = [](const float value) { + return static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f); + }; + + return {to_u8(color.red), to_u8(color.green), to_u8(color.blue), to_u8(color.alpha)}; +} + +[[nodiscard]] +constexpr auto to_fcolor(const UColor& color) -> FColor +{ + const auto to_f = [](const std::uint8_t value) { + return static_cast(value) / 255.0f; + }; + + return {to_f(color.red), to_f(color.green), to_f(color.blue), to_f(color.alpha)}; +} + +/** + * Encodes the color as an ABGR color packed into a 32-bit integer. + * + * \return + * An 32-bit integer, where each octet represents a different color channel. + */ +[[nodiscard]] +constexpr auto to_uint32_abgr(const UColor& color) -> std::uint32_t +{ + const auto a = static_cast(color.alpha) << std::uint32_t {24}; + const auto b = static_cast(color.blue) << std::uint32_t {16}; + const auto g = static_cast(color.green) << std::uint32_t {8}; + const auto r = static_cast(color.red) << std::uint32_t {0}; + return a | b | g | r; +} + +/** + * Converts the color to a hexadecimal color code. + * + * \param color The color to convert. + * \param format The output format to use. + * + * \return + * A color code string. + */ +[[nodiscard]] +inline auto to_string(const UColor& color, + const ColorFormat format = ColorFormat::kRgba) -> std::string +{ + switch (format) { + case ColorFormat::kRgb: + return std::format("#{:02X}{:02X}{:02X}", color.red, color.green, color.blue); + + case ColorFormat::kRgba: + return std::format("#{:02X}{:02X}{:02X}{:02X}", + color.red, + color.green, + color.blue, + color.alpha); + + case ColorFormat::kArgb: + return std::format("#{:02X}{:02X}{:02X}{:02X}", + color.alpha, + color.red, + color.green, + color.blue); + } + + throw std::invalid_argument {"bad color format"}; +} + +/** + * Parses a color from a hexadecimal RGB color code. + * + * \param rgb An RGB color string, on the form \c "#RRGGBB". + * + * \return + * A color if successful; an empty optional otherwise. + */ +[[nodiscard]] +constexpr auto parse_color_rgb(const std::string_view rgb) noexcept -> std::optional +{ + if (rgb.length() != 7 || rgb.front() != '#') { + return std::nullopt; + } + + const auto r = parse(rgb.substr(1, 2), 16); + const auto g = parse(rgb.substr(3, 2), 16); + const auto b = parse(rgb.substr(5, 2), 16); + + if (r.has_value() && g.has_value() && b.has_value()) { + return UColor {static_cast(*r), + static_cast(*g), + static_cast(*b), + 0xFF}; + } + + return std::nullopt; +} + +/** + * Parses a color from a hexadecimal RGBA color code. + * + * \param rgba An RGBA color string, on the form \c "#RRGGBBAA". + * + * \return + * A color if successful; an empty optional otherwise. + */ +[[nodiscard]] +constexpr auto parse_color_rgba(const std::string_view rgba) noexcept -> std::optional +{ + if (rgba.length() != 9 || rgba.front() != '#') { + return std::nullopt; + } + + const auto r = parse(rgba.substr(1, 2), 16); + const auto g = parse(rgba.substr(3, 2), 16); + const auto b = parse(rgba.substr(5, 2), 16); + const auto a = parse(rgba.substr(7, 2), 16); + + if (r.has_value() && g.has_value() && b.has_value() && a.has_value()) { + return UColor {static_cast(*r), + static_cast(*g), + static_cast(*b), + static_cast(*a)}; + } + + return std::nullopt; +} + +/** + * Parses a color from a hexadecimal ARGB color code. + * + * \param argb An ARGB color string, on the form \c "#AARRGGBB". + * + * \return + * A color if successful; an empty optional otherwise. + */ +[[nodiscard]] +constexpr auto parse_color_argb(const std::string_view argb) noexcept -> std::optional +{ + if (argb.length() != 9 || argb.front() != '#') { + return std::nullopt; + } + + const auto a = parse(argb.substr(1, 2), 16); + const auto r = parse(argb.substr(3, 2), 16); + const auto g = parse(argb.substr(5, 2), 16); + const auto b = parse(argb.substr(7, 2), 16); + + if (a.has_value() && r.has_value() && g.has_value() && b.has_value()) { + return UColor {static_cast(*r), + static_cast(*g), + static_cast(*b), + static_cast(*a)}; + } + + return std::nullopt; +} + +inline auto operator<<(std::ostream& stream, const UColor& color) -> std::ostream& +{ + return stream << to_string(color); +} + +inline constexpr UColor kColorBlack {0x00, 0x00, 0x00, 0xFF}; +inline constexpr UColor kColorWhite {0xFF, 0xFF, 0xFF, 0xFF}; + } // namespace tactile diff --git a/source/base/test/CMakeLists.txt b/source/base/test/CMakeLists.txt index cdd5e58580..f2cd12484a 100644 --- a/source/base/test/CMakeLists.txt +++ b/source/base/test/CMakeLists.txt @@ -5,11 +5,11 @@ add_executable(tactile-base-test) target_sources(tactile-base-test PRIVATE "src/container/lookup_test.cpp" - "src/io/color_parser_test.cpp" "src/io/int_parser_test.cpp" "src/io/tile_io_test.cpp" "src/meta/attribute_test.cpp" "src/meta/attribute_type_test.cpp" + "src/meta/color_test.cpp" "src/numeric/extent_2d_test.cpp" "src/numeric/index_2d_test.cpp" "src/numeric/offset_2d_test.cpp" diff --git a/source/base/test/src/io/color_parser_test.cpp b/source/base/test/src/io/color_parser_test.cpp deleted file mode 100644 index 729156a056..0000000000 --- a/source/base/test/src/io/color_parser_test.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) - -#include "tactile/base/io/color_parser.hpp" - -#include - -namespace tactile::test { - -class ColorParserTest : public testing::Test -{ - public: - const UColor kBlack {0x00, 0x00, 0x00, 0xFF}; - const UColor kWhite {0xFF, 0xFF, 0xFF, 0xFF}; - const UColor kLimeGreen {0x32, 0xCD, 0x32, 0xFF}; - const UColor kDarkBlue {0x00, 0x00, 0x8B, 0xFF}; - const UColor kHotPink {0xFF, 0x69, 0xB4, 0xFF}; -}; - -// tactile::parse_color_rgb -TEST_F(ColorParserTest, ParseColorRGB) -{ - EXPECT_FALSE(parse_color_rgb("").has_value()); - EXPECT_FALSE(parse_color_rgb("000000").has_value()); // no hashtag - EXPECT_FALSE(parse_color_rgb("#0000000").has_value()); // too long - EXPECT_FALSE(parse_color_rgb("#G00000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_rgb("#00G000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_rgb("#0000G0").has_value()); // invalid digit - - EXPECT_EQ(parse_color_rgb("#F00000"), (UColor {0xF0, 0x00, 0x00, 0xFF})); - EXPECT_EQ(parse_color_rgb("#00F000"), (UColor {0x00, 0xF0, 0x00, 0xFF})); - EXPECT_EQ(parse_color_rgb("#0000F0"), (UColor {0x00, 0x00, 0xF0, 0xFF})); - - EXPECT_EQ(parse_color_rgb("#000000"), kBlack); - EXPECT_EQ(parse_color_rgb("#FFFFFF"), kWhite); - EXPECT_EQ(parse_color_rgb("#32CD32"), kLimeGreen); - EXPECT_EQ(parse_color_rgb("#00008B"), kDarkBlue); - EXPECT_EQ(parse_color_rgb("#FF69B4"), kHotPink); -} - -// tactile::parse_color_rgba -TEST_F(ColorParserTest, ParseColorRGBA) -{ - EXPECT_FALSE(parse_color_rgba("").has_value()); - EXPECT_FALSE(parse_color_rgba("00000000").has_value()); // no hashtag - EXPECT_FALSE(parse_color_rgba("#000000000").has_value()); // too long - EXPECT_FALSE(parse_color_rgba("#G0000000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_rgba("#00G00000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_rgba("#0000G000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_rgba("#000000G0").has_value()); // invalid digit - - EXPECT_EQ(parse_color_rgba("#F00000FF"), (UColor {0xF0, 0x00, 0x00, 0xFF})); - EXPECT_EQ(parse_color_rgba("#00F000FF"), (UColor {0x00, 0xF0, 0x00, 0xFF})); - EXPECT_EQ(parse_color_rgba("#0000F0FF"), (UColor {0x00, 0x00, 0xF0, 0xFF})); - - EXPECT_EQ(parse_color_rgba("#000000FF"), kBlack); - EXPECT_EQ(parse_color_rgba("#FFFFFFFF"), kWhite); - EXPECT_EQ(parse_color_rgba("#32CD32FF"), kLimeGreen); - EXPECT_EQ(parse_color_rgba("#00008BFF"), kDarkBlue); - EXPECT_EQ(parse_color_rgba("#FF69B4FF"), kHotPink); -} - -// tactile::parse_color_argb -TEST_F(ColorParserTest, ParseColorARGB) -{ - EXPECT_FALSE(parse_color_argb("").has_value()); - EXPECT_FALSE(parse_color_argb("00000000").has_value()); // no hashtag - EXPECT_FALSE(parse_color_argb("#000000000").has_value()); // too long - EXPECT_FALSE(parse_color_argb("#G0000000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_argb("#00G00000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_argb("#0000G000").has_value()); // invalid digit - EXPECT_FALSE(parse_color_argb("#000000G0").has_value()); // invalid digit - - EXPECT_EQ(parse_color_argb("#FFF00000"), (UColor {0xF0, 0x00, 0x00, 0xFF})); - EXPECT_EQ(parse_color_argb("#FF00F000"), (UColor {0x00, 0xF0, 0x00, 0xFF})); - EXPECT_EQ(parse_color_argb("#FF0000F0"), (UColor {0x00, 0x00, 0xF0, 0xFF})); - - EXPECT_EQ(parse_color_argb("#FF000000"), kBlack); - EXPECT_EQ(parse_color_argb("#FFFFFFFF"), kWhite); - EXPECT_EQ(parse_color_argb("#FF32CD32"), kLimeGreen); - EXPECT_EQ(parse_color_argb("#FF00008B"), kDarkBlue); - EXPECT_EQ(parse_color_argb("#FFFF69B4"), kHotPink); -} - -} // namespace tactile::test diff --git a/source/base/test/src/meta/color_test.cpp b/source/base/test/src/meta/color_test.cpp new file mode 100644 index 0000000000..eee3335206 --- /dev/null +++ b/source/base/test/src/meta/color_test.cpp @@ -0,0 +1,165 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/base/meta/color.hpp" + +#include + +namespace tactile::core { +namespace { + +inline constexpr UColor kBlack {0x00, 0x00, 0x00, 0xFF}; +inline constexpr UColor kWhite {0xFF, 0xFF, 0xFF, 0xFF}; +inline constexpr UColor kLimeGreen {0x32, 0xCD, 0x32, 0xFF}; +inline constexpr UColor kDarkBlue {0x00, 0x00, 0x8B, 0xFF}; +inline constexpr UColor kHotPink {0xFF, 0x69, 0xB4, 0xFF}; + +} // namespace + +// tactile::to_string +TEST(Color, ToStringRGB) +{ + EXPECT_EQ(to_string(kColorBlack, ColorFormat::kRgb), "#000000"); + EXPECT_EQ(to_string(kColorWhite, ColorFormat::kRgb), "#FFFFFF"); + EXPECT_EQ(to_string(kLimeGreen, ColorFormat::kRgb), "#32CD32"); + EXPECT_EQ(to_string(kDarkBlue, ColorFormat::kRgb), "#00008B"); + EXPECT_EQ(to_string(kHotPink, ColorFormat::kRgb), "#FF69B4"); + + EXPECT_EQ(to_string(UColor {0x1A, 0x2B, 0x3C, 0x4D}, ColorFormat::kRgb), "#1A2B3C"); + EXPECT_EQ(to_string(UColor {0xAB, 0xCD, 0xEF, 0x42}, ColorFormat::kRgb), "#ABCDEF"); +} + +// tactile::to_string +TEST(Color, ToStringRGBA) +{ + EXPECT_EQ(to_string(kColorBlack, ColorFormat::kRgba), "#000000FF"); + EXPECT_EQ(to_string(kColorWhite, ColorFormat::kRgba), "#FFFFFFFF"); + EXPECT_EQ(to_string(kLimeGreen, ColorFormat::kRgba), "#32CD32FF"); + EXPECT_EQ(to_string(kDarkBlue, ColorFormat::kRgba), "#00008BFF"); + EXPECT_EQ(to_string(kHotPink, ColorFormat::kRgba), "#FF69B4FF"); + + EXPECT_EQ(to_string(UColor {0x1A, 0x2B, 0x3C, 0x4D}, ColorFormat::kRgba), "#1A2B3C4D"); + EXPECT_EQ(to_string(UColor {0xAB, 0xCD, 0xEF, 0x42}, ColorFormat::kRgba), "#ABCDEF42"); +} + +// tactile::to_string +TEST(Color, ToStringARGB) +{ + EXPECT_EQ(to_string(kColorBlack, ColorFormat::kArgb), "#FF000000"); + EXPECT_EQ(to_string(kColorWhite, ColorFormat::kArgb), "#FFFFFFFF"); + EXPECT_EQ(to_string(kLimeGreen, ColorFormat::kArgb), "#FF32CD32"); + EXPECT_EQ(to_string(kDarkBlue, ColorFormat::kArgb), "#FF00008B"); + EXPECT_EQ(to_string(kHotPink, ColorFormat::kArgb), "#FFFF69B4"); + + EXPECT_EQ(to_string(UColor {0x1A, 0x2B, 0x3C, 0x4D}), "#4D1A2B3C"); + EXPECT_EQ(to_string(UColor {0xAB, 0xCD, 0xEF, 0x42}), "#42ABCDEF"); +} + +// tactile::parse_color_rgb +TEST(Color, ParseColorRGB) +{ + EXPECT_FALSE(parse_color_rgb("").has_value()); + EXPECT_FALSE(parse_color_rgb("000000").has_value()); // no hashtag + EXPECT_FALSE(parse_color_rgb("#0000000").has_value()); // too long + EXPECT_FALSE(parse_color_rgb("#G00000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_rgb("#00G000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_rgb("#0000G0").has_value()); // invalid digit + + EXPECT_EQ(parse_color_rgb("#F00000"), (UColor {0xF0, 0x00, 0x00, 0xFF})); + EXPECT_EQ(parse_color_rgb("#00F000"), (UColor {0x00, 0xF0, 0x00, 0xFF})); + EXPECT_EQ(parse_color_rgb("#0000F0"), (UColor {0x00, 0x00, 0xF0, 0xFF})); + + EXPECT_EQ(parse_color_rgb("#000000"), kBlack); + EXPECT_EQ(parse_color_rgb("#FFFFFF"), kWhite); + EXPECT_EQ(parse_color_rgb("#32CD32"), kLimeGreen); + EXPECT_EQ(parse_color_rgb("#00008B"), kDarkBlue); + EXPECT_EQ(parse_color_rgb("#FF69B4"), kHotPink); +} + +// tactile::parse_color_rgba +TEST(Color, ParseColorRGBA) +{ + EXPECT_FALSE(parse_color_rgba("").has_value()); + EXPECT_FALSE(parse_color_rgba("00000000").has_value()); // no hashtag + EXPECT_FALSE(parse_color_rgba("#000000000").has_value()); // too long + EXPECT_FALSE(parse_color_rgba("#G0000000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_rgba("#00G00000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_rgba("#0000G000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_rgba("#000000G0").has_value()); // invalid digit + + EXPECT_EQ(parse_color_rgba("#F00000FF"), (UColor {0xF0, 0x00, 0x00, 0xFF})); + EXPECT_EQ(parse_color_rgba("#00F000FF"), (UColor {0x00, 0xF0, 0x00, 0xFF})); + EXPECT_EQ(parse_color_rgba("#0000F0FF"), (UColor {0x00, 0x00, 0xF0, 0xFF})); + + EXPECT_EQ(parse_color_rgba("#000000FF"), kBlack); + EXPECT_EQ(parse_color_rgba("#FFFFFFFF"), kWhite); + EXPECT_EQ(parse_color_rgba("#32CD32FF"), kLimeGreen); + EXPECT_EQ(parse_color_rgba("#00008BFF"), kDarkBlue); + EXPECT_EQ(parse_color_rgba("#FF69B4FF"), kHotPink); +} + +// tactile::parse_color_argb +TEST(Color, ParseColorARGB) +{ + EXPECT_FALSE(parse_color_argb("").has_value()); + EXPECT_FALSE(parse_color_argb("00000000").has_value()); // no hashtag + EXPECT_FALSE(parse_color_argb("#000000000").has_value()); // too long + EXPECT_FALSE(parse_color_argb("#G0000000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_argb("#00G00000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_argb("#0000G000").has_value()); // invalid digit + EXPECT_FALSE(parse_color_argb("#000000G0").has_value()); // invalid digit + + EXPECT_EQ(parse_color_argb("#FFF00000"), (UColor {0xF0, 0x00, 0x00, 0xFF})); + EXPECT_EQ(parse_color_argb("#FF00F000"), (UColor {0x00, 0xF0, 0x00, 0xFF})); + EXPECT_EQ(parse_color_argb("#FF0000F0"), (UColor {0x00, 0x00, 0xF0, 0xFF})); + + EXPECT_EQ(parse_color_argb("#FF000000"), kBlack); + EXPECT_EQ(parse_color_argb("#FFFFFFFF"), kWhite); + EXPECT_EQ(parse_color_argb("#FF32CD32"), kLimeGreen); + EXPECT_EQ(parse_color_argb("#FF00008B"), kDarkBlue); + EXPECT_EQ(parse_color_argb("#FFFF69B4"), kHotPink); +} + +// tactile::to_uint32_abgr [UColor] +TEST(Color, ToUInt32ABGR) +{ + EXPECT_EQ(to_uint32_abgr(kColorBlack), std::uint32_t {0xFF000000}); + EXPECT_EQ(to_uint32_abgr(kColorWhite), std::uint32_t {0xFFFFFFFF}); + EXPECT_EQ(to_uint32_abgr(kLimeGreen), std::uint32_t {0xFF32CD32}); + EXPECT_EQ(to_uint32_abgr(kDarkBlue), std::uint32_t {0xFF8B0000}); + EXPECT_EQ(to_uint32_abgr(kHotPink), std::uint32_t {0xFFB469FF}); +} + +// tactile::to_fcolor +TEST(Color, ToFColor) +{ + const UColor color {0xFF, 0x00, 0x33, 0x66}; + const auto normalized_color = to_fcolor(color); + + EXPECT_FLOAT_EQ(normalized_color.red, 1.0f); + EXPECT_FLOAT_EQ(normalized_color.green, 0.0f); + EXPECT_FLOAT_EQ(normalized_color.blue, 0.2f); + EXPECT_FLOAT_EQ(normalized_color.alpha, 0.4f); +} + +// tactile::parse_color_rgb +// tactile::to_string +TEST(Color, RoundTripConversionRGB) +{ + EXPECT_EQ(parse_color_rgb(to_string(kHotPink, ColorFormat::kRgb)), kHotPink); +} + +// tactile::parse_color_rgba +// tactile::to_string +TEST(Color, RoundTripConversionRGBA) +{ + EXPECT_EQ(parse_color_rgba(to_string(kDarkBlue, ColorFormat::kRgba)), kDarkBlue); +} + +// tactile::parse_color_argb +// tactile::to_string +TEST(Color, RoundTripConversionARGB) +{ + EXPECT_EQ(parse_color_argb(to_string(kLimeGreen, ColorFormat::kArgb)), kLimeGreen); +} + +} // namespace tactile::core diff --git a/source/core/lib/CMakeLists.txt b/source/core/lib/CMakeLists.txt index 405a83534e..8124a41702 100644 --- a/source/core/lib/CMakeLists.txt +++ b/source/core/lib/CMakeLists.txt @@ -63,7 +63,6 @@ target_sources(tactile-core "src/log/terminal_log_sink.cpp" "src/map/map.cpp" "src/map/map_spec.cpp" - "src/meta/color.cpp" "src/meta/meta.cpp" "src/model/model.cpp" "src/model/settings.cpp" @@ -193,7 +192,6 @@ target_sources(tactile-core "inc/tactile/core/log/terminal_log_sink.hpp" "inc/tactile/core/map/map.hpp" "inc/tactile/core/map/map_spec.hpp" - "inc/tactile/core/meta/color.hpp" "inc/tactile/core/meta/meta.hpp" "inc/tactile/core/model/model.hpp" "inc/tactile/core/model/settings.hpp" diff --git a/source/core/lib/inc/tactile/core/meta/color.hpp b/source/core/lib/inc/tactile/core/meta/color.hpp deleted file mode 100644 index 49368102e7..0000000000 --- a/source/core/lib/inc/tactile/core/meta/color.hpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) - -#pragma once - -#include // array -#include // uint32_t -#include // ostream -#include // span -#include // string - -#include "tactile/base/meta/color.hpp" -#include "tactile/base/prelude.hpp" - -namespace tactile::core { - -/** - * Creates a color from floating-point components. - * - * \details - * The provided intensities are clamped to be within [0, 1], and subsequently - * mapped to values in the [0, 255] interval. - * - * \param r The red channel intensity. - * \param g The green channel intensity. - * \param b The blue channel intensity. - * \param a The alpha channel intensity. - * - * \return - * A color. - */ -[[nodiscard]] -auto make_color(float r, float g, float b, float a) -> UColor; - -[[nodiscard]] -auto make_color(std::span rgba) -> UColor; - -[[nodiscard]] -auto make_color(const FColor& fcolor) -> UColor; - -/** - * Converts the color to a hexadecimal RGB color code. - * - * \return - * An RGB color code. - */ -[[nodiscard]] -auto to_string_rgb(const UColor& color) -> std::string; - -/** - * Converts the color to a hexadecimal RGBA color code. - * - * \return - * An RGBA color code. - */ -[[nodiscard]] -auto to_string_rgba(const UColor& color) -> std::string; - -/** - * Converts the color to a hexadecimal ARGB color code. - * - * \return - * An ARGB color code. - */ -[[nodiscard]] -auto to_string_argb(const UColor& color) -> std::string; - -/** - * Encodes the color as an ABGR color packed into a 32-bit integer. - * - * \return - * An 32-bit integer, where each octet represents a different color channel. - */ -[[nodiscard]] -auto to_uint32_abgr(const UColor& color) -> std::uint32_t; - -[[nodiscard]] -auto normalize(const UColor& color) -> FColor; - -/** - * Returns the relative luminance of the color. - * - * \return - * A luminance value in the interval [0, 1]. - * - * \see https://en.wikipedia.org/wiki/Relative_luminance - */ -[[nodiscard]] -auto get_luminance(const UColor& color) -> float; - -/** - * Indicates whether the color is considered dark. - * - * \return - * True if the color is dark; false otherwise. - */ -[[nodiscard]] -auto is_dark(const UColor& color) -> bool; - -inline constexpr UColor kColorBlack {0x00, 0x00, 0x00, 0xFF}; -inline constexpr UColor kColorWhite {0xFF, 0xFF, 0xFF, 0xFF}; -inline constexpr UColor kColorYellow {0xFF, 0xFF, 0x00, 0xFF}; - -auto operator<<(std::ostream& stream, const UColor& color) -> std::ostream&; - -} // namespace tactile::core diff --git a/source/core/lib/inc/tactile/core/ui/render/primitives.hpp b/source/core/lib/inc/tactile/core/ui/render/primitives.hpp index b7eeda9f1c..d9fcc1126d 100644 --- a/source/core/lib/inc/tactile/core/ui/render/primitives.hpp +++ b/source/core/lib/inc/tactile/core/ui/render/primitives.hpp @@ -8,7 +8,6 @@ #include "tactile/base/numeric/vec.hpp" #include "tactile/base/prelude.hpp" -#include "tactile/core/meta/color.hpp" #include "tactile/core/ui/imgui_compat.hpp" namespace tactile::core::ui { diff --git a/source/core/lib/src/meta/color.cpp b/source/core/lib/src/meta/color.cpp deleted file mode 100644 index 810068bd57..0000000000 --- a/source/core/lib/src/meta/color.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) - -#include "tactile/core/meta/color.hpp" - -#include // clamp -#include // pow -#include // format - -#include "tactile/base/io/int_parser.hpp" -#include "tactile/core/util/string_conv.hpp" - -namespace tactile::core { - -auto make_color(const float r, const float g, const float b, const float a) -> UColor -{ - const auto to_u8 = [](const float val) { - return static_cast(std::clamp(val, 0.0f, 1.0f) * 255.0f); - }; - - return {to_u8(r), to_u8(g), to_u8(b), to_u8(a)}; -} - -auto make_color(const std::span rgba) -> UColor -{ - return make_color(rgba[0], rgba[1], rgba[2], rgba[3]); -} - -auto make_color(const FColor& fcolor) -> UColor -{ - return make_color(fcolor.red, fcolor.green, fcolor.blue, fcolor.alpha); -} - -auto to_string_rgb(const UColor& color) -> std::string -{ - return std::format("#{:02X}{:02X}{:02X}", color.red, color.green, color.blue); -} - -auto to_string_rgba(const UColor& color) -> std::string -{ - return std::format("#{:02X}{:02X}{:02X}{:02X}", - color.red, - color.green, - color.blue, - color.alpha); -} - -auto to_string_argb(const UColor& color) -> std::string -{ - return std::format("#{:02X}{:02X}{:02X}{:02X}", - color.alpha, - color.red, - color.green, - color.blue); -} - -auto to_uint32_abgr(const UColor& color) -> std::uint32_t -{ - const auto a = static_cast(color.alpha) << std::uint32_t {24}; - const auto b = static_cast(color.blue) << std::uint32_t {16}; - const auto g = static_cast(color.green) << std::uint32_t {8}; - const auto r = static_cast(color.red) << std::uint32_t {0}; - return a | b | g | r; -} - -auto normalize(const UColor& color) -> FColor -{ - const auto to_f = [](const std::uint8_t val) { return static_cast(val) / 255.0f; }; - - return {to_f(color.red), to_f(color.green), to_f(color.blue), to_f(color.alpha)}; -} - -auto get_luminance(const UColor& color) -> float -{ - // https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ - auto linearize = [](const float channel) { - if (channel <= 0.04045f) { - return channel / 12.92f; - } - - return std::pow((channel + 0.055f) / 1.055f, 2.4f); - }; - - const auto norm_values = normalize(color); - const auto lr = linearize(norm_values.red); - const auto lg = linearize(norm_values.green); - const auto lb = linearize(norm_values.blue); - - return (0.2126f * lr) + (0.7152f * lg) + (0.0722f * lb); -} - -auto is_dark(const UColor& color) -> bool -{ - return get_luminance(color) < 0.3f; -} - -auto operator<<(std::ostream& stream, const UColor& color) -> std::ostream& -{ - return stream << to_string_rgba(color); -} - -} // namespace tactile::core diff --git a/source/core/lib/src/ui/common/attribute_widgets.cpp b/source/core/lib/src/ui/common/attribute_widgets.cpp index 9a559f505a..5a2546f697 100644 --- a/source/core/lib/src/ui/common/attribute_widgets.cpp +++ b/source/core/lib/src/ui/common/attribute_widgets.cpp @@ -10,7 +10,6 @@ #include -#include "tactile/core/meta/color.hpp" #include "tactile/core/platform/file_dialog.hpp" #include "tactile/core/ui/common/buttons.hpp" #include "tactile/core/ui/common/widgets.hpp" @@ -251,9 +250,9 @@ auto push_color_input(const char* id, const Attribute::color_type& color) const auto flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar; - auto rgba_array = normalize(color); + auto rgba_array = to_fcolor(color); if (ImGui::ColorEdit4("##Color", &rgba_array.red, flags)) { - new_color = make_color(rgba_array); + new_color = to_ucolor(rgba_array); } return new_color; diff --git a/source/core/lib/src/ui/dock/tileset_dock.cpp b/source/core/lib/src/ui/dock/tileset_dock.cpp index 2d6e07b65d..788dd9c7c4 100644 --- a/source/core/lib/src/ui/dock/tileset_dock.cpp +++ b/source/core/lib/src/ui/dock/tileset_dock.cpp @@ -12,7 +12,6 @@ #include "tactile/core/event/viewport_events.hpp" #include "tactile/core/io/texture.hpp" #include "tactile/core/map/map.hpp" -#include "tactile/core/meta/color.hpp" #include "tactile/core/meta/meta.hpp" #include "tactile/core/tile/tileset.hpp" #include "tactile/core/ui/canvas_overlay.hpp" diff --git a/source/core/lib/src/ui/render/orthogonal_renderer.cpp b/source/core/lib/src/ui/render/orthogonal_renderer.cpp index 5c7e334653..b8618854ac 100644 --- a/source/core/lib/src/ui/render/orthogonal_renderer.cpp +++ b/source/core/lib/src/ui/render/orthogonal_renderer.cpp @@ -15,7 +15,6 @@ #include "tactile/core/layer/object_layer.hpp" #include "tactile/core/layer/tile_layer.hpp" #include "tactile/core/map/map.hpp" -#include "tactile/core/meta/color.hpp" #include "tactile/core/tile/tileset.hpp" #include "tactile/core/ui/canvas_renderer.hpp" #include "tactile/core/ui/common/window.hpp" @@ -99,7 +98,7 @@ void _render_object(const CanvasRenderer& canvas_renderer, const auto scaled_size = object.size * canvas_scale; const auto screen_pos = canvas_renderer.to_screen_pos(scaled_pos); - constexpr auto line_color = kColorYellow; + constexpr auto line_color = UColor {0xFF, 0xFF, 0x00, 0xFF}; constexpr auto line_thickness = 2.0f; switch (object.type) { diff --git a/source/core/test/CMakeLists.txt b/source/core/test/CMakeLists.txt index c84fdb7ed8..7526814eff 100644 --- a/source/core/test/CMakeLists.txt +++ b/source/core/test/CMakeLists.txt @@ -43,7 +43,6 @@ target_sources(tactile-core-test "src/layer/tile_layer_test.cpp" "src/map/map_spec_test.cpp" "src/map/map_test.cpp" - "src/meta/color_test.cpp" "src/meta/meta_test.cpp" "src/model/settings_test.cpp" "src/numeric/random_test.cpp" diff --git a/source/core/test/src/meta/color_test.cpp b/source/core/test/src/meta/color_test.cpp deleted file mode 100644 index fd2382205c..0000000000 --- a/source/core/test/src/meta/color_test.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) - -#include "tactile/core/meta/color.hpp" - -#include // array - -#include - -#include "tactile/base/io/color_parser.hpp" - -namespace tactile::core { -namespace color_test { - -inline constexpr UColor kLimeGreen {0x32, 0xCD, 0x32, 0xFF}; -inline constexpr UColor kDarkBlue {0x00, 0x00, 0x8B, 0xFF}; -inline constexpr UColor kHotPink {0xFF, 0x69, 0xB4, 0xFF}; - -} // namespace color_test - -// tactile::core::make_color -TEST(UColor, MakeColor) -{ - const std::array values {1.0f, 0.0f, 0.2f, 0.4f}; - const auto color = make_color(values); - - EXPECT_EQ(color.red, 0xFF); - EXPECT_EQ(color.green, 0x00); - EXPECT_EQ(color.blue, 0x33); - EXPECT_EQ(color.alpha, 0x66); -} - -// tactile::core::to_string_rgb -TEST(UColor, ToStringRGB) -{ - EXPECT_EQ(to_string_rgb(kColorBlack), "#000000"); - EXPECT_EQ(to_string_rgb(kColorWhite), "#FFFFFF"); - EXPECT_EQ(to_string_rgb(color_test::kLimeGreen), "#32CD32"); - EXPECT_EQ(to_string_rgb(color_test::kDarkBlue), "#00008B"); - EXPECT_EQ(to_string_rgb(color_test::kHotPink), "#FF69B4"); - - EXPECT_EQ(to_string_rgb(UColor {0x1A, 0x2B, 0x3C, 0x4D}), "#1A2B3C"); - EXPECT_EQ(to_string_rgb(UColor {0xAB, 0xCD, 0xEF, 0x42}), "#ABCDEF"); -} - -// tactile::core::to_string_rgba -TEST(UColor, ToStringRGBA) -{ - EXPECT_EQ(to_string_rgba(kColorBlack), "#000000FF"); - EXPECT_EQ(to_string_rgba(kColorWhite), "#FFFFFFFF"); - EXPECT_EQ(to_string_rgba(color_test::kLimeGreen), "#32CD32FF"); - EXPECT_EQ(to_string_rgba(color_test::kDarkBlue), "#00008BFF"); - EXPECT_EQ(to_string_rgba(color_test::kHotPink), "#FF69B4FF"); - - EXPECT_EQ(to_string_rgba(UColor {0x1A, 0x2B, 0x3C, 0x4D}), "#1A2B3C4D"); - EXPECT_EQ(to_string_rgba(UColor {0xAB, 0xCD, 0xEF, 0x42}), "#ABCDEF42"); -} - -// tactile::core::to_string_argb -TEST(UColor, ToStringARGB) -{ - EXPECT_EQ(to_string_argb(kColorBlack), "#FF000000"); - EXPECT_EQ(to_string_argb(kColorWhite), "#FFFFFFFF"); - EXPECT_EQ(to_string_argb(color_test::kLimeGreen), "#FF32CD32"); - EXPECT_EQ(to_string_argb(color_test::kDarkBlue), "#FF00008B"); - EXPECT_EQ(to_string_argb(color_test::kHotPink), "#FFFF69B4"); - - EXPECT_EQ(to_string_argb(UColor {0x1A, 0x2B, 0x3C, 0x4D}), "#4D1A2B3C"); - EXPECT_EQ(to_string_argb(UColor {0xAB, 0xCD, 0xEF, 0x42}), "#42ABCDEF"); -} - -// tactile::core::to_uint32_abgr [UColor] -TEST(UColor, ToUInt32ABGR) -{ - EXPECT_EQ(to_uint32_abgr(kColorBlack), std::uint32_t {0xFF000000}); - EXPECT_EQ(to_uint32_abgr(kColorWhite), std::uint32_t {0xFFFFFFFF}); - EXPECT_EQ(to_uint32_abgr(color_test::kLimeGreen), std::uint32_t {0xFF32CD32}); - EXPECT_EQ(to_uint32_abgr(color_test::kDarkBlue), std::uint32_t {0xFF8B0000}); - EXPECT_EQ(to_uint32_abgr(color_test::kHotPink), std::uint32_t {0xFFB469FF}); -} - -// tactile::core::normalize [UColor] -TEST(UColor, Normalized) -{ - const UColor color {0xFF, 0x00, 0x33, 0x66}; - const auto normalized_color = normalize(color); - - EXPECT_FLOAT_EQ(normalized_color.red, 1.0f); - EXPECT_FLOAT_EQ(normalized_color.green, 0.0f); - EXPECT_FLOAT_EQ(normalized_color.blue, 0.2f); - EXPECT_FLOAT_EQ(normalized_color.alpha, 0.4f); -} - -// tactile::core::get_luminance [UColor] -TEST(Color, GetLuminance) -{ - // Based on https://planetcalc.com/7778/ - EXPECT_FLOAT_EQ(get_luminance(kColorBlack), 0.0f); - EXPECT_FLOAT_EQ(get_luminance(kColorWhite), 1.0f); - EXPECT_FLOAT_EQ(get_luminance(color_test::kLimeGreen), 0.4457104f); - EXPECT_FLOAT_EQ(get_luminance(color_test::kDarkBlue), 0.0186408f); - EXPECT_FLOAT_EQ(get_luminance(color_test::kHotPink), 0.3465843f); -} - -// tactile::core::is_dark [UColor] -TEST(Color, IsDark) -{ - EXPECT_TRUE(is_dark(kColorBlack)); - EXPECT_TRUE(is_dark(color_test::kDarkBlue)); - - EXPECT_FALSE(is_dark(kColorWhite)); - EXPECT_FALSE(is_dark(color_test::kLimeGreen)); - EXPECT_FALSE(is_dark(color_test::kHotPink)); -} - -// tactile::core::parse_color_rgb -// tactile::core::to_string_rgb -TEST(Color, RoundTripConversionRGB) -{ - EXPECT_EQ(parse_color_rgb(to_string_rgb(color_test::kHotPink)), color_test::kHotPink); -} - -// tactile::core::parse_color_rgba -// tactile::core::to_string_rgba -TEST(Color, RoundTripConversionRGBA) -{ - EXPECT_EQ(parse_color_rgba(to_string_rgba(color_test::kDarkBlue)), color_test::kDarkBlue); -} - -// tactile::core::parse_color_argb -// tactile::core::to_string_argb -TEST(Color, RoundTripConversionARGB) -{ - EXPECT_EQ(parse_color_argb(to_string_argb(color_test::kLimeGreen)), color_test::kLimeGreen); -} - -} // namespace tactile::core diff --git a/source/plugins/tiled_tmj/lib/src/tmj_format_attribute_parser.cpp b/source/plugins/tiled_tmj/lib/src/tmj_format_attribute_parser.cpp index 4709978b35..a18241633c 100644 --- a/source/plugins/tiled_tmj/lib/src/tmj_format_attribute_parser.cpp +++ b/source/plugins/tiled_tmj/lib/src/tmj_format_attribute_parser.cpp @@ -4,7 +4,6 @@ #include // move -#include "tactile/base/io/color_parser.hpp" #include "tactile/base/numeric/saturate_cast.hpp" #include "tactile/runtime/logging.hpp"