Implement more conversion types and conversion functions

This commit is contained in:
2024-12-09 19:30:35 -05:00
parent 6fe4f9b38d
commit bb157ff855
3 changed files with 346 additions and 26 deletions

View File

@@ -4,11 +4,91 @@
#include <cstdint>
#include <string>
#include <cmath>
#include <Color3.hpp>
#include <stdexcept>
using u8 = uint8_t;
/// A structure representing color in Red, Green, Blue format, 8 bits-per-color, or 24-bit, or True-color.
struct RGB {
u8 r;
u8 g;
u8 b;
};
struct RGBA {
u8 r;
u8 g;
u8 b;
u8 a;
};
struct RGBf {
float r;
float g;
float b;
};
struct RGBAf {
float r;
float g;
float b;
float a;
};
/// A structure representing color in Hue, Chroma, Value format.
struct HSV {
float h;
float s;
float v;
};
struct HSVA {
float h;
float s;
float v;
float a;
};
struct HSL {
float h;
float s;
float l;
};
struct HSLA {
float h;
float s;
float l;
float a;
};
/// A structure representing a Lum, Chroma, Lightness value.
struct LCH {
float l;
float h;
float c;
};
struct LCHA {
float l;
float h;
float c;
float a;
};
struct CMYK {
float c;
float m;
float y;
float k;
};
/// A type representing a color with alpha.
/// Our default format is RGBA with 8 bits-per-channel.
/// Conversions to other color formats are provided.
class Color4 {
public:
u8 r;
@@ -16,38 +96,101 @@ public:
u8 b;
u8 a;
public:
/// The default constructor does not initialize any members.
Color4() = default;
/// Constructs a new Color4 from a Color3 and an optional alpha value.
explicit Color4(const Color3& color3, u8 alpha = 255);
/// Constructs a new Color4 from red, green, blue channel values, and an optional alpha value.
Color4(u8 red, u8 green, u8 blue, u8 alpha = 255);
/// Constructs a new Color4 from an RGB structure and an optional alpha value.
explicit Color4(const RGB& rgb, u8 alpha = 255);
/// Constructs a new Color4 from an RGBA structure.
explicit Color4(const RGBA& rgba);
/// 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].
explicit Color4(const RGBf& rgb, float alpha = 1.0f);
/// Constructs a new Color4 from a floating-point RGBA structure.
/// /// @note: Normalizes the color values from ranges [0, 1] to [0, 255].
explicit Color4(const RGBAf& rgba);
/// Constructs a new Color4 from an HSV structure.
explicit Color4(const HSV& hsv, float alpha = 1.f);
/// Constructs a new Color4 from an HSVA structure.
explicit Color4(const HSVA& hsva);
explicit Color4(const HSL& hsl, float alpha = 1.f);
explicit Color4(const LCH& lch, float alpha = 1.f);
explicit Color4(const LCHA& lcha);
static Color4 FromColor3(const Color3& color3, u8 alpha = 255);
static Color4 FromHex(const std::string& hexCode, u8 alpha = 255);
static Color4 FromHSL(u8 hue, u8 saturation, u8 lightness, u8 alpha = 255);
static Color4 FromNormalized(float red, float green, float blue, float alpha);
static Color4 FromHexA(const std::string& hexACode);
static Color4 FromHSV(float hue, float saturation, float value, float alpha = 1.f) {
// TODO: implement
}
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 FromLCH(float l, float c, float h, float alpha = 1.f);
public:
[[nodiscard]] Color4 Lerp(const Color4& rhs, float t) const;
static Color4 Lerp(const Color4& lhs, const Color4& rhs, float t);
/// Alternative to Lerp which does not normalize color channels before and after interpolation.
Color4 Lerp2(const Color4& rhs, float t) const;
/// Alternative to Lerp which does not normalize color channels before and after interpolation.
static Color4 Lerp2(const Color4& lhs, const Color4& rhs, float t);
Color4 LerpByHSVA(const Color4& rhs, float t) const;
std::string ToHex() const;
std::string ToHexA() const;
u8 RedChannel() const;
u8 GreenChannel() const;
u8 BlueChannel() const;
u8 AlphaChannel() const;
u8 R() const { return AlphaChannel(); }
u8 G() const { return GreenChannel(); }
u8 B() const { return BlueChannel(); }
u8 A() const { return AlphaChannel(); }
u8 R() const;
u8 G() const;
u8 B() const;
u8 A() const;
float RedChannelNormalized() const;
float GreenChannelNormalized() const;
float BlueChannelNormalized() const;
float AlphaChannelNormalized() const;
[[nodiscard]] float RN() const { return RedChannelNormalized(); }
[[nodiscard]] float GN() const { return GreenChannelNormalized(); }
[[nodiscard]] float BN() const { return BlueChannelNormalized(); }
[[nodiscard]] float AN() const { return AlphaChannelNormalized(); }
[[nodiscard]] float RN() const;
[[nodiscard]] float GN() const;
[[nodiscard]] float BN() const;
[[nodiscard]] float AN() const;
u8* ptr();
[[nodiscard]] const u8* ptr() const;
HSV ToHSV() const;
HSVA ToHSVA() const;
HSL ToHSL() const
{
}
LCH ToLCH() const {
}
};

View File

@@ -1,4 +1,5 @@
#include <Color3.hpp>
#include <stdexcept>
u8 Color3::RedChannel() const { return r; }
@@ -15,11 +16,17 @@ float Color3::GreenChannelNormalized() const { return static_cast<float>(g) / 25
Color3::Color3(u8 R, u8 G, u8 B) : r(R), g(G), b(B) {}
Color3 Color3::FromHex(const std::string &hexCode) {
// TODO: Support hex codes without the #
// TODO: Support 9-character hex codes (with alpha), but raise a warning that suggests using Color4 instead.
if (hexCode.length() == 7) {
u8 r, g, b;
std::sscanf(hexCode.c_str(), "#%02x%02x%02x", &r, &g, &b);
return {r, g, b};
}
throw std::invalid_argument("Invalid hex code");
}
u8 *Color3::ptr() { return &r;}
const u8 *Color3::ptr() const { return &r;}

View File

@@ -2,10 +2,92 @@
Color4::Color4(u8 red, u8 green, u8 blue, u8 alpha) : r(red), g(green), b(blue), a(alpha) {}
Color4::Color4(const RGB &rgb, u8 alpha): Color4(rgb.r, rgb.g, rgb.b, alpha) {}
Color4::Color4(const RGBA &rgba): Color4(rgba.r, rgba.g, rgba.b, rgba.a) {}
Color4::Color4(const RGBf &rgb, float alpha): Color4(rgb.r * 255, rgb.g * 255, rgb.b * 255, alpha * 255) { }
Color4::Color4(const RGBAf &rgba): Color4(rgba.r * 255, rgba.g * 255, rgba.b * 255, rgba.a * 255) { }
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));
if (0 <= h <= 60) {
r = M;
g = z + m;
b = m;
}
if (60 <= h < 120) {
r = z + m;
g = M;
b = m;
}
if (120 <= h < 180) {
r = m;
g = M;
b = z + m;
}
if (180 <= h < 240) {
r = m;
g = z + m;
b = M;
}
if (240 <= h < 300) {
r = z + m;
g = m;
b = M;
}
if (300 <= h < 360) {
r = M;
g = m;
b = z + m;
}
a = alpha;
}
Color4::Color4(const Color3 &color3, u8 alpha) {r = color3.r; g = color3.g; b = color3.b; a = alpha;}
Color4 Color4::FromColor3(const Color3 &color3, u8 alpha) {return Color4(color3, alpha);}
Color4 Color4::FromHex(const std::string &hexCode, u8 alpha) {
// TODO: Support hex codes without the #
// TODO: Support 9-character hex codes (with alpha), but raise a warning that suggests using FromHexA explicitly instead.
if (hexCode.length() == 7) {
u8 r, g, b;
std::sscanf(hexCode.c_str(), "#%02x%02x%02x", &r, &g, &b);
return {r, g, b, alpha};
}
if (hexCode.length() == 9) {
u8 r, g, b, a;
std::sscanf(hexCode.c_str(), "#%02x%02x%02x%02x", &r, &g, &b, &a);
return {r, g, b, a};
}
throw std::invalid_argument("Invalid hex code");
}
Color4 Color4::FromHexA(const std::string &hexACode) {
if (hexACode.length() == 9) {
u8 r, g, b, a;
std::sscanf(hexACode.c_str(), "#%02x%02x%02x%02x", &r, &g, &b, &a);
return {r, g, b, a};
}
throw std::invalid_argument("Invalid hex code");
}
u8 Color4::RedChannel() const { return r;}
u8 Color4::GreenChannel() const { return g;}
@@ -14,6 +96,14 @@ u8 Color4::BlueChannel() const {return b;}
u8 Color4::AlphaChannel() const {return a;}
u8 Color4::R() const { return AlphaChannel(); }
u8 Color4::G() const { return GreenChannel(); }
u8 Color4::B() const { return BlueChannel(); }
u8 Color4::A() const { return AlphaChannel(); }
float Color4::RedChannelNormalized() const {return static_cast<float>(r/255.f); }
float Color4::GreenChannelNormalized() const {return static_cast<float>(g/255.f); }
@@ -21,7 +111,17 @@ float Color4::GreenChannelNormalized() const {return static_cast<float>(g/255.f)
float Color4::BlueChannelNormalized() const {return static_cast<float>(b/255.f); }
float Color4::AlphaChannelNormalized() const {return static_cast<float>(a/255.f); }
float Color4::RN() const { return RedChannelNormalized(); }
float Color4::GN() const { return GreenChannelNormalized(); }
float Color4::BN() const { return BlueChannelNormalized(); }
float Color4::AN() const { return AlphaChannelNormalized(); }
Color4 Color4::FromHex(const std::string &hexCode, u8 alpha) {
// TODO: Support alpha in hex code.
u8 r, g, b;
std::sscanf(hexCode.c_str(), "#%02x%02x%02x", &r, &g, &b);
return {r, g, b, alpha};
@@ -40,23 +140,63 @@ Color4 Color4::FromNormalized(float red, float green, float blue, float alpha) {
Color4 Color4::Lerp(const Color4 &rhs, float t) const {
/// Performs the interpolation in normalized color space (0-1) and converts it back.
/// Trust me.
float rn = RN();
float rhs_rn = rhs.RN();
float bn = BN();
float rhs_bn = rhs.BN();
float gn = GN();
float rhs_gn = rhs.GN();
float an = AN();
float rhs_an = rhs.AN();
return FromNormalized(
rn + (rhs_rn - rn) * t,
gn + (rhs_gn - gn) * t,
bn + (rhs_bn - bn) * t,
an + (rhs_an - an) * t
std::lerp(RN(), rhs.RN(), t),
std::lerp(GN(), rhs.GN(), t),
std::lerp(BN(), rhs.BN(), t),
std::lerp(AN(), rhs.AN(), t)
);
}
Color4 Color4::Lerp2(const Color4& rhs, float t) const {
return Color4(
std::lerp(R(), rhs.R(), t),
std::lerp(G(), rhs.G(), t),
std::lerp(B(), rhs.B(), t),
std::lerp(A(), rhs.A(), t)
);
}
Color4 Color4::Lerp2(const Color4 &lhs, const Color4 &rhs, float t) {
return lhs.Lerp2(rhs, t);
}
Color4 Color4::LerpByHSVA(const Color4 &rhs, float t) const {
HSVA a = this->ToHSVA();
HSVA b = rhs.ToHSVA();
// Hue interpolation
float h;
float d = b.h - a.h;
if (a.h > b.h) {
auto h3 = b.h; // b.h2?
b.h = a.h;
a.h = h3;
d = -d;
t = 1 - t;
}
if (d > 0.5f) { // 180 deg
a.h = a.h + 1; // 360 deg
h = std::fmod(a.h + t * (b.h - a.h), 1.f);
}
if (d <= 0.5f) { // 180 deg
h = a.h + t * d;
}
return FromHSV(
h,
std::lerp(a.s, b.s, t),
std::lerp(a.v, b.v, t),
std::lerp(a.a, b.a, t));
}
Color4 Color4::Lerp(const Color4 &lhs, const Color4 &rhs, float t) {
return lhs.Lerp(rhs, t);
}
@@ -64,3 +204,33 @@ Color4 Color4::Lerp(const Color4 &lhs, const Color4 &rhs, float t) {
u8 *Color4::ptr() { return (&r);}
const u8 *Color4::ptr() const { return (&r);}
HSV Color4::ToHSV() const {
float rn = RN();
float gn = GN();
float bn = BN();
auto Cmax = std::max(std::max(rn, gn), bn); // TODO: Replace with initializer list.
auto Cmin = std::min(std::max(rn, gn), bn); // TODO: Replace with initializer list.
float delta = Cmax - Cmin;
float hue = 0.0;
if (Cmax == rn) hue = 60.f * std::fmod((gn - bn) / delta, 6.0);
if (Cmax == gn) hue = 60.f * ((gn - rn) / delta) + 2;
if (Cmax == bn) hue = 60.f * ((rn - gn)/ delta) + 4;
float saturation = 0.f;
if (Cmax == 0) saturation = 0.f;
else saturation = delta / Cmax;
float value = Cmax;
return {hue, saturation, value};
}
HSVA Color4::ToHSVA() const {
auto hsv = ToHSV();
return {hsv.h, hsv.s, hsv.v, AN()};
}