Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions loader/include/Geode/utils/ColorCode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once

#include <string_view>
#include <ccTypes.h>
Comment thread
SMJSProductions marked this conversation as resolved.
#include <Geode/Result.hpp>

namespace geode {
template<typename T>
concept CanSetColor3B = requires(T object) {
object.setColor(cocos2d::ccColor3B());
object.setOpacity(GLubyte());
};
Comment thread
SMJSProductions marked this conversation as resolved.
template<typename T>
concept CanSetColor4B = requires(T object) { object.setColor(cocos2d::ccColor4B()); };
template<typename T>
concept CanSetColor4F = requires(T object) { object.setColor(cocos2d::ccColor4F()); };

struct GEODE_DLL Color final {
/// Parses the given hex string using one of the following formats:
/// #R/G/B, #RR/GG/BB, #RGB, #RGBA, #RRGGBB or #RRGGBBAA
/// Where the # prefix is optional and / means it's 1 byte/nibble representing all channels
/// @returns The parsed result
static Result<Color> parse(std::string_view hex);
private:
static GLubyte getByte(uint32_t colorNum, size_t index, bool isShort);
public:
operator cocos2d::ccColor3B() const;
operator cocos2d::ccColor4B() const;
operator cocos2d::ccColor4F() const;
operator cocos2d::extension::HSV() const;
Color& operator=(Color&& other) noexcept = default;
Color& operator=(const Color& other) noexcept = default;

GLubyte r;
GLubyte g;
GLubyte b;
GLubyte a;

Color(Color&& other) noexcept = default;
Color(const Color& other) noexcept = default;
/// @returns The equivalent of #000000{a}
Color(GLubyte alpha = 0);
Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha = 0xFF);
Color(const cocos2d::ccColor3B& color, GLubyte alpha = 0xFF);
Color(const cocos2d::ccColor4B& color);
Color(const cocos2d::ccColor4F& color);
Color(const cocos2d::extension::HSV& hsv, GLubyte alpha = 0xFF);

void applyH(double hue);
void applyHue(double hue);
void applyS(double saturation);
void applySaturation(double saturation);
void applyV(double value);
void applyValue(double value);
void applyB(double brightness);
void applyBrightness(double brightness);
void applyHSV(const cocos2d::extension::HSV& hsv);
cocos2d::ccColor3B to3B() const;
cocos2d::ccColor4B to4B() const;
cocos2d::ccColor4F to4F() const;
cocos2d::extension::HSV toHSV() const;
/// @returns True if alpha is 0
bool isInvisible() const;
/// @returns True if alpha is bigger than 0 and smaller than 255
bool isTranslucent() const;
/// @returns True if alpha is 255
bool isOpaque() const;
template<typename T> requires (CanSetColor3B<T> || CanSetColor4B<T> || CanSetColor4F<T>)
inline void applyTo(T* node) const {
if constexpr (CanSetColor3B<T>) {
node->setColor(this->to3B());
node->setOpacity(a);
} else if constexpr (CanSetColor4B<T>) {
node->setColor(this->to4B());
} else {
node->setColor(this->to4F());
}
}
};
}
205 changes: 205 additions & 0 deletions loader/src/utils/ColorCode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include <Geode/utils/ColorCode.hpp>

using namespace geode::prelude;

Result<Color> Color::parse(std::string_view hex) {
if (hex.starts_with("#")) {
hex.remove_prefix(1);
}

const size_t size = hex.size();

// If the string is an exact size of 1, 2, 3, 4, 6 or 8
if (!size || (size > 4 && size != 6 && size != 8)) {
return Err("Unsupported size");
}

GEODE_UNWRAP_INTO(const uint32_t colorNum, utils::numFromString<uint32_t>(hex, 16));
ccColor4B color;

if (size <= 2) {
color.r = color.g = color.b = Color::getByte(colorNum, 0, size == 1);
color.a = 0xFF;
} else if (size % 3 == 0) {
const bool isShort = size == 3;

color.r = Color::getByte(colorNum, 2, isShort);
color.g = Color::getByte(colorNum, 1, isShort);
color.b = Color::getByte(colorNum, 0, isShort);
color.a = 0xFF;
} else {
const bool isShort = size == 4;

color.r = Color::getByte(colorNum, 3, isShort);
color.g = Color::getByte(colorNum, 2, isShort);
color.b = Color::getByte(colorNum, 1, isShort);
color.a = Color::getByte(colorNum, 0, isShort);
}

return Ok(Color(color));
}

GLubyte Color::getByte(uint32_t colorNum, size_t index, bool isShort) {
const uint32_t shiftSize = (isShort ? 4 : 8) * index;
const GLubyte byte = (colorNum >> shiftSize) & 0xFF;

// If it's short, mirror the nibble
return isShort ? (byte << 4) | byte : byte;
}


Color::operator ccColor3B() const {
return { r, g, b };
}

Color::operator ccColor4B() const {
return { r, g, b, a };
}

Color::operator ccColor4F() const {
return { r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f };
}

Color::operator HSV() const {
const double red = r / 255.0;
const double green = g / 255.0;
const double blue = b / 255.0;
const double max = std::max({ red, green, blue });
const double delta = max - std::min({ red, green, blue });
HSV hsv;

if (delta == 0) {
hsv.h = 0;
} else if (max == red) {
hsv.h = 60 * std::fmod((green - blue) / delta, 6);
} else if (max == green) {
hsv.h = 60 * ((blue - red) / delta + 2);
} else {
hsv.h = 60 * ((red - green) / delta + 4);
}

if (hsv.h < 0) {
hsv.h += 360;
}

hsv.s = max == 0 ? 0 : delta / max;
hsv.v = max;

return hsv;
}

Color::Color(GLubyte alpha): r(0x00), g(0x00), b(0x00), a(alpha) { }

Color::Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha): r(red), g(green), b(blue), a(alpha) { }

Color::Color(const ccColor3B& color, GLubyte alpha): r(color.r), g(color.g), b(color.b), a(alpha) { }

Color::Color(const ccColor4B& color): r(color.r), g(color.g), b(color.b), a(color.a) { }

Color::Color(const ccColor4F& color): r(std::round(color.r * 255)), g(std::round(color.g * 255)), b(std::round(color.b * 255)), a(std::round(color.a * 255)) { }

Color::Color(const HSV& hsv, GLubyte alpha): a(alpha) {
double correctedHue = std::fmod(hsv.h, 360);

if (correctedHue < 0) {
correctedHue += 360;
}

const double c = hsv.v * hsv.s;
const double x = c * (1 - std::abs(std::fmod(correctedHue / 60, 2) - 1));
const double m = hsv.v - c;
double rp, gp, bp;

if (correctedHue < 60) {
rp = c; gp = x; bp = 0;
} else if (correctedHue < 120) {
rp = x; gp = c; bp = 0;
} else if (correctedHue < 180) {
rp = 0; gp = c; bp = x;
} else if (correctedHue < 240) {
rp = 0; gp = x; bp = c;
} else if (correctedHue < 300) {
rp = x; gp = 0; bp = c;
} else {
rp = c; gp = 0; bp = x;
}

r = std::round((rp + m) * 255);
g = std::round((gp + m) * 255);
b = std::round((bp + m) * 255);
}

void Color::applyH(double hue) {
this->applyHSV({ hue, 1, 1 });
}

void Color::applyHue(double hue) {
this->applyHSV({ hue, 1, 1 });
}

void Color::applyS(double saturation) {
this->applyHSV({ 0, saturation, 1 });
}

void Color::applySaturation(double saturation) {
this->applyHSV({ 0, saturation, 1 });
}

void Color::applyV(double value) {
this->applyHSV({ 0, 1, value });
}

void Color::applyValue(double value) {
this->applyHSV({ 0, 1, value });
}

void Color::applyB(double brightness) {
this->applyHSV({ 0, 1, brightness });
}

void Color::applyBrightness(double brightness) {
this->applyHSV({ 0, 1, brightness });
}

void Color::applyHSV(const HSV& hsv) {
HSV currentHSV = *this;

currentHSV.h = std::fmod(currentHSV.h + hsv.h, 360);

if (currentHSV.h < 0) {
currentHSV.h += 360;
}

currentHSV.s = std::clamp<double>(currentHSV.s * hsv.s, 0, 1);
currentHSV.v = std::clamp<double>(currentHSV.v * hsv.v, 0, 1);

*this = currentHSV;
}

ccColor3B Color::to3B() const {
return *this;
}

ccColor4B Color::to4B() const {
return *this;
}

ccColor4F Color::to4F() const {
return *this;
}

HSV Color::toHSV() const {
return *this;
}

bool Color::isInvisible() const {
return a == 0x00;
}

bool Color::isTranslucent() const {
return a > 0x00 && a < 0xFF;
}

bool Color::isOpaque() const {
return a == 0xFF;
}
Loading