Implement more conversion types and conversion functions
This commit is contained in:
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
@@ -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;}
|
||||
|
196
src/Color4.cpp
196
src/Color4.cpp
@@ -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()};
|
||||
}
|
||||
|
Reference in New Issue
Block a user