mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-19 02:43:27 +00:00
257 lines
7.8 KiB
C++
257 lines
7.8 KiB
C++
//
|
|
// Created by vlad on 2/4/24.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include "omath/linear_algebra/vector4.hpp"
|
|
#include <cstdint>
|
|
|
|
namespace omath
|
|
{
|
|
struct Hsv final
|
|
{
|
|
float hue{};
|
|
float saturation{};
|
|
float value{};
|
|
};
|
|
|
|
class Color final
|
|
{
|
|
Vector4<float> m_value;
|
|
public:
|
|
constexpr const Vector4<float>& value() const
|
|
{
|
|
return m_value;
|
|
}
|
|
constexpr Color(const float r, const float g, const float b, const float a) noexcept: m_value(r, g, b, a)
|
|
{
|
|
m_value.clamp(0.f, 1.f);
|
|
}
|
|
|
|
constexpr explicit Color(const Vector4<float>& value) : m_value(value)
|
|
{
|
|
m_value.clamp(0.f, 1.f);
|
|
}
|
|
constexpr explicit Color() noexcept = default;
|
|
[[nodiscard]]
|
|
constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept
|
|
{
|
|
return Color(Vector4<float>(r, g, b, a) / 255.f);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr static Color from_hsv(float hue, const float saturation, const float value) noexcept
|
|
{
|
|
float r{}, g{}, b{};
|
|
|
|
hue = std::clamp(hue, 0.f, 1.f);
|
|
|
|
const int i = static_cast<int>(hue * 6.f);
|
|
const float f = hue * 6.f - static_cast<float>(i);
|
|
const float p = value * (1 - saturation);
|
|
const float q = value * (1 - f * saturation);
|
|
const float t = value * (1 - (1 - f) * saturation);
|
|
|
|
switch (i % 6)
|
|
{
|
|
case 0:
|
|
r = value, g = t, b = p;
|
|
break;
|
|
case 1:
|
|
r = q, g = value, b = p;
|
|
break;
|
|
case 2:
|
|
r = p, g = value, b = t;
|
|
break;
|
|
case 3:
|
|
r = p, g = q, b = value;
|
|
break;
|
|
case 4:
|
|
r = t, g = p, b = value;
|
|
break;
|
|
case 5:
|
|
r = value, g = p, b = q;
|
|
break;
|
|
default:
|
|
std::unreachable();
|
|
}
|
|
|
|
return {r, g, b, 1.f};
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr static Color from_hsv(const Hsv& hsv) noexcept
|
|
{
|
|
return from_hsv(hsv.hue, hsv.saturation, hsv.value);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr Hsv to_hsv() const noexcept
|
|
{
|
|
Hsv hsv_data;
|
|
|
|
const float& red = m_value.x;
|
|
const float& green = m_value.y;
|
|
const float& blue = m_value.z;
|
|
|
|
const float max = std::max({red, green, blue});
|
|
const float min = std::min({red, green, blue});
|
|
const float delta = max - min;
|
|
|
|
if (delta == 0.f)
|
|
hsv_data.hue = 0.f;
|
|
|
|
else if (max == red)
|
|
hsv_data.hue = 60.f * (std::fmod(static_cast<float>((green - blue) / delta), 6.f));
|
|
else if (max == green)
|
|
hsv_data.hue = 60.f * (((blue - red) / delta) + 2.f);
|
|
else if (max == blue)
|
|
hsv_data.hue = 60.f * (((red - green) / delta) + 4.f);
|
|
|
|
if (hsv_data.hue < 0.f)
|
|
hsv_data.hue += 360.f;
|
|
|
|
hsv_data.hue /= 360.f;
|
|
hsv_data.saturation = max == 0.f ? 0.f : delta / max;
|
|
hsv_data.value = max;
|
|
|
|
return hsv_data;
|
|
}
|
|
constexpr void set_hue(const float hue) noexcept
|
|
{
|
|
auto hsv = to_hsv();
|
|
hsv.hue = hue;
|
|
|
|
*this = from_hsv(hsv);
|
|
}
|
|
|
|
constexpr void set_saturation(const float saturation) noexcept
|
|
{
|
|
auto hsv = to_hsv();
|
|
hsv.saturation = saturation;
|
|
|
|
*this = from_hsv(hsv);
|
|
}
|
|
|
|
constexpr void set_value(const float value) noexcept
|
|
{
|
|
auto hsv = to_hsv();
|
|
hsv.value = value;
|
|
|
|
*this = from_hsv(hsv);
|
|
}
|
|
[[nodiscard]]
|
|
constexpr Color blend(const Color& other, float ratio) const noexcept
|
|
{
|
|
ratio = std::clamp(ratio, 0.f, 1.f);
|
|
return Color(this->m_value * (1.f - ratio) + other.m_value * ratio);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Color red()
|
|
{
|
|
return {1.f, 0.f, 0.f, 1.f};
|
|
}
|
|
[[nodiscard]] static constexpr Color green()
|
|
{
|
|
return {0.f, 1.f, 0.f, 1.f};
|
|
}
|
|
[[nodiscard]] static constexpr Color blue()
|
|
{
|
|
return {0.f, 0.f, 1.f, 1.f};
|
|
}
|
|
#ifdef OMATH_IMGUI_INTEGRATION
|
|
[[nodiscard]]
|
|
ImColor to_im_color() const noexcept
|
|
{
|
|
return {m_value.to_im_vec4()};
|
|
}
|
|
#endif
|
|
[[nodiscard]] std::string to_string() const noexcept
|
|
{
|
|
return std::format("[r:{}, g:{}, b:{}, a:{}]",
|
|
static_cast<int>(m_value.x * 255.f),
|
|
static_cast<int>(m_value.y * 255.f),
|
|
static_cast<int>(m_value.z * 255.f),
|
|
static_cast<int>(m_value.w * 255.f));
|
|
}
|
|
[[nodiscard]] std::string to_rgbf_string() const noexcept
|
|
{
|
|
return std::format("[r:{}, g:{}, b:{}, a:{}]",
|
|
m_value.x, m_value.y, m_value.z, m_value.w);
|
|
}
|
|
[[nodiscard]] std::string to_hsv_string() const noexcept
|
|
{
|
|
const auto [hue, saturation, value] = to_hsv();
|
|
return std::format("[h:{}, s:{}, v:{}]", hue, saturation, value);
|
|
}
|
|
[[nodiscard]] std::wstring to_wstring() const noexcept
|
|
{
|
|
const auto ascii_string = to_string();
|
|
return {ascii_string.cbegin(), ascii_string.cend()};
|
|
}
|
|
|
|
// ReSharper disable once CppInconsistentNaming
|
|
[[nodiscard]] std::u8string to_u8string() const noexcept
|
|
{
|
|
const auto ascii_string = to_string();
|
|
return {ascii_string.cbegin(), ascii_string.cend()};
|
|
}
|
|
};
|
|
} // namespace omath
|
|
template<>
|
|
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
|
|
{
|
|
enum class ColorFormat { rgb, rgbf, hsv };
|
|
ColorFormat color_format = ColorFormat::rgb;
|
|
|
|
constexpr auto parse(std::format_parse_context& ctx)
|
|
{
|
|
const auto it = ctx.begin();
|
|
const auto end = ctx.end();
|
|
|
|
if (it == end || *it == '}')
|
|
return it;
|
|
|
|
const std::string_view spec(it, end);
|
|
|
|
if (spec.starts_with("rgbf"))
|
|
{
|
|
color_format = ColorFormat::rgbf;
|
|
return it + 4;
|
|
}
|
|
if (spec.starts_with("rgb"))
|
|
{
|
|
color_format = ColorFormat::rgb;
|
|
return it + 3;
|
|
}
|
|
if (spec.starts_with("hsv"))
|
|
{
|
|
color_format = ColorFormat::hsv;
|
|
return it + 3;
|
|
}
|
|
|
|
throw std::format_error("Invalid format specifier for omath::Color. Use rgb, rgbf, or hsv.");
|
|
}
|
|
|
|
template<class FormatContext>
|
|
auto format(const omath::Color& col, FormatContext& ctx) const
|
|
{
|
|
std::string str;
|
|
switch (color_format)
|
|
{
|
|
case ColorFormat::rgb: str = col.to_string(); break;
|
|
case ColorFormat::rgbf: str = col.to_rgbf_string(); break;
|
|
case ColorFormat::hsv: str = col.to_hsv_string(); break;
|
|
}
|
|
|
|
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
|
|
return std::format_to(ctx.out(), "{}", str);
|
|
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
|
|
return std::format_to(ctx.out(), L"{}", std::wstring(str.cbegin(), str.cend()));
|
|
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
|
|
return std::format_to(ctx.out(), u8"{}", std::u8string(str.cbegin(), str.cend()));
|
|
|
|
std::unreachable();
|
|
}
|
|
}; |