Fix HSV to RGB conversion once and for all

This commit is contained in:
2025-04-22 17:11:24 -04:00
parent 8f49ba5e49
commit fe25751b07
4 changed files with 156 additions and 60 deletions

View File

@@ -3,6 +3,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <format> #include <format>
#include <Color4.hpp>
// Gets set to whatever your terminal emulator is configured for. // Gets set to whatever your terminal emulator is configured for.
// This means black can be shown as purple if configured that way. // This means black can be shown as purple if configured that way.

View File

@@ -32,40 +32,40 @@ public:
public: public:
/// The default constructor does not initialize any members. /// The default constructor does not initialize any members.
constexpr Color4() = default; Color4() = default;
/// Constructs a new Color4 from a Color3 and an optional alpha value. /// Constructs a new Color4 from a Color3 and an optional alpha value.
constexpr explicit Color4(const Color3& color3, u8 alpha = 255); explicit Color4(const Color3& color3, u8 alpha = 255);
/// Constructs a new Color4 from red, green, blue channel values, and an optional alpha value. /// Constructs a new Color4 from red, green, blue channel values, and an optional alpha value.
constexpr Color4(u8 red, u8 green, u8 blue, u8 alpha = 255); Color4(u8 red, u8 green, u8 blue, u8 alpha = 255);
/// Constructs a new Color4 from an RGB structure and an optional alpha value. /// Constructs a new Color4 from an RGB structure and an optional alpha value.
constexpr explicit Color4(const RGB& rgb, u8 alpha = 255); explicit Color4(const RGB& rgb, u8 alpha = 255);
/// Constructs a new Color4 from an RGBA structure. /// Constructs a new Color4 from an RGBA structure.
constexpr explicit Color4(const RGBA& rgba); explicit Color4(const RGBA& rgba);
/// Constructs a new Color4 from a floating-point RGB structure and an optional alpha value. /// Constructs a new Color4 from a floating-point RGB structure and an optional alpha value.
/// @note: Normalizes the color values from ranges [0, 1] to [0, 255]. /// @note: Normalizes the color values from ranges [0, 1] to [0, 255].
constexpr explicit Color4(const RGBf& rgb, float alpha = 1.0f); explicit Color4(const RGBf& rgb, float alpha = 1.0f);
/// Constructs a new Color4 from a floating-point RGBA structure. /// Constructs a new Color4 from a floating-point RGBA structure.
/// /// @note: Normalizes the color values from ranges [0, 1] to [0, 255]. /// /// @note: Normalizes the color values from ranges [0, 1] to [0, 255].
constexpr explicit Color4(const RGBAf& rgba); explicit Color4(const RGBAf& rgba);
/// Constructs a new Color4 from an HSV structure. /// Constructs a new Color4 from an HSV structure.
constexpr explicit Color4(const HSV& hsv, float alpha = 1.f); explicit Color4(const HSV& hsv, float alpha = 1.f);
/// Constructs a new Color4 from an HSVA structure. /// Constructs a new Color4 from an HSVA structure.
constexpr explicit Color4(const HSVA& hsva); explicit Color4(const HSVA& hsva);
/// TODO: HSL to RGB constructor /// TODO: HSL to RGB constructor
constexpr explicit Color4(const HSL& hsl, float alpha = 1.f); explicit Color4(const HSL& hsl, float alpha = 1.f);
// TODO: LCH to RGB constructor // TODO: LCH to RGB constructor
constexpr explicit Color4(const LCH& lch, float alpha = 1.f); explicit Color4(const LCH& lch, float alpha = 1.f);
// TODO: LCHA to RGB constructor // TODO: LCHA to RGB constructor
constexpr explicit Color4(const LCHA& lcha); explicit Color4(const LCHA& lcha);
static Color4 FromColor3(const Color3& color3, u8 alpha = 255); static Color4 FromColor3(const Color3& color3, u8 alpha = 255);
@@ -73,10 +73,11 @@ public:
static Color4 FromHexA(const std::string& hexACode); static Color4 FromHexA(const std::string& hexACode);
static Color4 FromHSV(float hue, float saturation, float value, float alpha = 1.f) { static Color4 FromHSV(const HSV& hsv, float alpha = 1.f);
// TODO: implement
return {}; /// @param hue The hue value represented in degrees [0-360]
}
static Color4 FromHSV(float hue, float saturation, float value, float alpha = 1.f);
static Color4 FromHSL(float hue, float saturation, float lightness, float alpha = 1.f); static Color4 FromHSL(float hue, float saturation, float lightness, float alpha = 1.f);
static Color4 FromNormalized(float red, float green, float blue, float alpha = 1.f); static Color4 FromNormalized(float red, float green, float blue, float alpha = 1.f);
static Color4 FromLCH(float l, float c, float h, float alpha = 1.f); static Color4 FromLCH(float l, float c, float h, float alpha = 1.f);

View File

@@ -21,5 +21,15 @@ int main()
mcolor::printAnsiColorTable(); mcolor::printAnsiColorTable();
mcolor::printRGBConsoleTest(); mcolor::printRGBConsoleTest();
for (float i = 0; i < 360; i+=10.f)
{
HSVA hsva {i, 1.f, 1.f};
Color4 c = Color4(hsva);
std::cout << std::format("{}hue:{} rgb: {},{},{}", toEscapeCode(c), i, c.r, c.g, c.b) << std::endl;
}
return 0; return 0;
} }

View File

@@ -24,63 +24,84 @@ void print_builtin_color_list() {
#endif #endif
} }
constexpr Color4::Color4(const RGB &rgb, u8 alpha): Color4(rgb.r, rgb.g, rgb.b, alpha) {} Color4::Color4(const RGB &rgb, u8 alpha): Color4(rgb.r, rgb.g, rgb.b, alpha) {}
constexpr Color4::Color4(const RGBA &rgba): Color4(rgba.r, rgba.g, rgba.b, rgba.a) {} Color4::Color4(const RGBA &rgba): Color4(rgba.r, rgba.g, rgba.b, rgba.a) {}
constexpr Color4::Color4(const RGBf &rgb, float alpha): Color4(rgb.r * 255, rgb.g * 255, rgb.b * 255, alpha * 255) { } Color4::Color4(const RGBf &rgb, float alpha): Color4(rgb.r * 255, rgb.g * 255, rgb.b * 255, alpha * 255) { }
constexpr Color4::Color4(const RGBAf &rgba): Color4(rgba.r * 255, rgba.g * 255, rgba.b * 255, rgba.a * 255) { } Color4::Color4(const RGBAf &rgba): Color4(rgba.r * 255, rgba.g * 255, rgba.b * 255, rgba.a * 255) { }
constexpr Color4::Color4(const HSV &hsv, float alpha) { Color4::Color4(const HSV &hsv, float alpha) {
float h = hsv.h;
float s = hsv.s;
float v = hsv.v;
float M = 255*hsv.v;
float m = M*(1-hsv.s);
float z = (M-m)*(1 - std::abs( std::fmod(hsv.h/60.f, 2) - 1)); float hue = hsv.h;
float saturation = hsv.s;
float value = hsv.v;
if (0 <= h <= 60) { float hh, p, q, t, ff;
r = M; long i;
g = z + m; float rn, gn, bn;
b = m;
} if (saturation <= 0.0) { // < is bogus, just shuts up warnings.
if (60 <= h < 120) { r = value*255;
r = z + m; g = value*255;
g = M; b = value*255;
b = m; return;
} //return Color4(RGBAf{rn, bn, gn, alpha});
if (120 <= h < 180) {
r = m;
g = M;
b = z + m;
} }
if (180 <= h < 240) { hh = hue;
r = m; if (hh >= 360.f) hh = 0;
g = z + m; hh /= 60.f;
b = M; i = (long)hh;
} ff = hh - i;
p = value * (1.f - saturation);
q = value * (1.f - (saturation * ff));
t = value * (1.f - (saturation * (1.f - ff)));
if (240 <= h < 300) { switch(i) {
r = z + m; case 0:
g = m; rn = value;
b = M; gn = t;
bn = p;
break;
case 1:
rn = q;
gn = value;
bn = p;
break;
case 2:
rn = p;
gn = value;
bn = t;
break;
case 3:
rn = p;
gn = q;
bn = value;
break;
case 4:
rn = t;
gn = p;
bn = value;
break;
case 5:
default:
rn = value;
gn = p;
bn = q;
break;
} }
this->r = rn * 255;
if (300 <= h < 360) { this->g = gn * 255;
r = M; this->b = bn * 255;
g = m; this->a = alpha * 255;
b = z + m; //return Color4(RGBAf{rn, gn, bn, alpha});
}
a = alpha;
} }
constexpr Color4::Color4(const Color3 &color3, u8 alpha) {r = color3.r; g = color3.g; b = color3.b; a = alpha;} Color4::Color4(const Color3 &color3, u8 alpha) {r = color3.r; g = color3.g; b = color3.b; a = alpha;}
constexpr Color4::Color4(u8 red, u8 green, u8 blue, u8 alpha): r(red), g(green), b(blue), a(alpha) { Color4::Color4(u8 red, u8 green, u8 blue, u8 alpha): r(red), g(green), b(blue), a(alpha) {
#if !defined(WIN32) #if !defined(WIN32)
list.push_back(*this); list.push_back(*this);
#endif #endif
@@ -356,3 +377,66 @@ std::string Color4::ToHexAlpha() const {
std::string as = decimal_to_hex(a); std::string as = decimal_to_hex(a);
return "#" + rs + gs + bs + as; return "#" + rs + gs + bs + as;
} }
Color4 Color4::FromHSV(float hue, float saturation, float value, float alpha) {
float hh, p, q, t, ff;
long i;
float rn, gn, bn;
if (saturation <= 0.0) { // < is bogus, just shuts up warnings.
rn = value;
bn = value;
gn = value;
return Color4(RGBAf{rn, bn, gn, alpha});
}
hh = hue;
if (hh >= 360.f) hh = 0;
hh /= 60.f;
i = (long)hh;
ff = hh - i;
p = value * (1.f - saturation);
q = value * (1.f - (saturation * ff));
t = value * (1.f - (saturation * (1.f - ff)));
switch(i) {
case 0:
rn = value;
gn = t;
bn = p;
break;
case 1:
rn = q;
gn = value;
bn = p;
break;
case 2:
rn = p;
gn = value;
bn = t;
break;
case 3:
rn = p;
gn = q;
bn = value;
break;
case 4:
rn = t;
gn = p;
bn = value;
break;
case 5:
default:
rn = value;
gn = p;
bn = q;
break;
}
return Color4(RGBAf{rn, gn, bn, alpha});
}
Color4 Color4::FromHSV(const HSV &hsv, float alpha) {
return FromHSV(hsv.h, hsv.s, hsv.v, alpha);
}
Color4::Color4(const HSVA &hsva) : Color4(HSV{hsva.h,hsva.s,hsva.v}, hsva.a) {}