|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <algorithm> |
| 4 | +#include <cmath> |
| 5 | +#include <cstdint> |
| 6 | + |
| 7 | +struct CabanaColor { |
| 8 | + uint8_t r = 0; |
| 9 | + uint8_t g = 0; |
| 10 | + uint8_t b = 0; |
| 11 | + uint8_t a = 255; |
| 12 | + |
| 13 | + constexpr CabanaColor() = default; |
| 14 | + constexpr CabanaColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) |
| 15 | + : r(red), g(green), b(blue), a(alpha) {} |
| 16 | + |
| 17 | + static CabanaColor fromHsv(float hue, float saturation, float value, float alpha = 1.0f) { |
| 18 | + const float h = hue - std::floor(hue); |
| 19 | + const float c = value * saturation; |
| 20 | + const float x = c * (1.0f - std::fabs(std::fmod(h * 6.0f, 2.0f) - 1.0f)); |
| 21 | + const float m = value - c; |
| 22 | + float red = 0, green = 0, blue = 0; |
| 23 | + switch (static_cast<int>(h * 6.0f) % 6) { |
| 24 | + case 0: red = c; green = x; break; |
| 25 | + case 1: red = x; green = c; break; |
| 26 | + case 2: green = c; blue = x; break; |
| 27 | + case 3: green = x; blue = c; break; |
| 28 | + case 4: red = x; blue = c; break; |
| 29 | + default: red = c; blue = x; break; |
| 30 | + } |
| 31 | + auto channel = [m](float v) { return static_cast<uint8_t>(std::clamp((v + m) * 255.0f, 0.0f, 255.0f) + 0.5f); }; |
| 32 | + return {channel(red), channel(green), channel(blue), |
| 33 | + static_cast<uint8_t>(std::clamp(alpha * 255.0f, 0.0f, 255.0f) + 0.5f)}; |
| 34 | + } |
| 35 | + |
| 36 | + CabanaColor darker(int factor = 200) const { |
| 37 | + if (factor <= 0) return *this; |
| 38 | + if (factor < 100) return lighter(10000 / factor); |
| 39 | + auto [hue, saturation, value] = hsv(); |
| 40 | + return fromHsv(hue, saturation, value * 100.0f / factor, a / 255.0f); |
| 41 | + } |
| 42 | + |
| 43 | + CabanaColor lighter(int factor = 150) const { |
| 44 | + if (factor <= 0) return *this; |
| 45 | + if (factor < 100) return darker(10000 / factor); |
| 46 | + auto [hue, saturation, value] = hsv(); |
| 47 | + const float scaled_value = value * factor / 100.0f; |
| 48 | + if (scaled_value > 1.0f) saturation = std::max(0.0f, saturation - (scaled_value - 1.0f)); |
| 49 | + return fromHsv(hue, saturation, std::min(1.0f, scaled_value), a / 255.0f); |
| 50 | + } |
| 51 | + |
| 52 | + constexpr int red() const { return r; } |
| 53 | + constexpr int green() const { return g; } |
| 54 | + constexpr int blue() const { return b; } |
| 55 | + constexpr int alpha() const { return a; } |
| 56 | + float alphaF() const { return a / 255.0f; } |
| 57 | + void setAlphaF(float alpha) { a = static_cast<uint8_t>(std::clamp(alpha * 255.0f, 0.0f, 255.0f) + 0.5f); } |
| 58 | + |
| 59 | + constexpr bool operator==(const CabanaColor &other) const { |
| 60 | + return r == other.r && g == other.g && b == other.b && a == other.a; |
| 61 | + } |
| 62 | + |
| 63 | +private: |
| 64 | + struct Hsv { float hue; float saturation; float value; }; |
| 65 | + Hsv hsv() const { |
| 66 | + const float red = r / 255.0f, green = g / 255.0f, blue = b / 255.0f; |
| 67 | + const float maximum = std::max({red, green, blue}); |
| 68 | + const float minimum = std::min({red, green, blue}); |
| 69 | + const float delta = maximum - minimum; |
| 70 | + float hue = 0; |
| 71 | + if (delta > 0) { |
| 72 | + if (maximum == red) hue = std::fmod((green - blue) / delta, 6.0f) / 6.0f; |
| 73 | + else if (maximum == green) hue = ((blue - red) / delta + 2.0f) / 6.0f; |
| 74 | + else hue = ((red - green) / delta + 4.0f) / 6.0f; |
| 75 | + if (hue < 0) hue += 1.0f; |
| 76 | + } |
| 77 | + return {hue, maximum == 0 ? 0 : delta / maximum, maximum}; |
| 78 | + } |
| 79 | +}; |
0 commit comments