mirror of
https://github.com/Relintai/sfw.git
synced 2025-04-07 17:01:48 +02:00
574 lines
12 KiB
C++
574 lines
12 KiB
C++
/*************************************************************************/
|
|
/* color.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* PANDEMONIUM ENGINE */
|
|
/* https://github.com/Relintai/pandemonium_engine */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2022-present Péter Magyar. */
|
|
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "color.h"
|
|
|
|
#include "core/containers/rb_map.h"
|
|
#include "core/math/color_names.inc"
|
|
#include "core/math/math_funcs.h"
|
|
#include "core/string/print_string.h"
|
|
|
|
uint32_t Color::to_argb32() const {
|
|
uint32_t c = (uint8_t)Math::round(a * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(r * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(g * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(b * 255);
|
|
|
|
return c;
|
|
}
|
|
|
|
uint32_t Color::to_abgr32() const {
|
|
uint32_t c = (uint8_t)Math::round(a * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(b * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(g * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(r * 255);
|
|
|
|
return c;
|
|
}
|
|
|
|
uint32_t Color::to_rgba32() const {
|
|
uint32_t c = (uint8_t)Math::round(r * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(g * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(b * 255);
|
|
c <<= 8;
|
|
c |= (uint8_t)Math::round(a * 255);
|
|
|
|
return c;
|
|
}
|
|
|
|
uint64_t Color::to_abgr64() const {
|
|
uint64_t c = (uint16_t)Math::round(a * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(b * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(g * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(r * 65535);
|
|
|
|
return c;
|
|
}
|
|
|
|
uint64_t Color::to_argb64() const {
|
|
uint64_t c = (uint16_t)Math::round(a * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(r * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(g * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(b * 65535);
|
|
|
|
return c;
|
|
}
|
|
|
|
uint64_t Color::to_rgba64() const {
|
|
uint64_t c = (uint16_t)Math::round(r * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(g * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(b * 65535);
|
|
c <<= 16;
|
|
c |= (uint16_t)Math::round(a * 65535);
|
|
|
|
return c;
|
|
}
|
|
|
|
float Color::get_h() const {
|
|
float min = MIN(r, g);
|
|
min = MIN(min, b);
|
|
float max = MAX(r, g);
|
|
max = MAX(max, b);
|
|
|
|
float delta = max - min;
|
|
|
|
if (delta == 0) {
|
|
return 0;
|
|
}
|
|
|
|
float h;
|
|
if (r == max) {
|
|
h = (g - b) / delta; // between yellow & magenta
|
|
} else if (g == max) {
|
|
h = 2 + (b - r) / delta; // between cyan & yellow
|
|
} else {
|
|
h = 4 + (r - g) / delta; // between magenta & cyan
|
|
}
|
|
|
|
h /= 6.0;
|
|
if (h < 0) {
|
|
h += 1.0;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
float Color::get_s() const {
|
|
float min = MIN(r, g);
|
|
min = MIN(min, b);
|
|
float max = MAX(r, g);
|
|
max = MAX(max, b);
|
|
|
|
float delta = max - min;
|
|
|
|
return (max != 0) ? (delta / max) : 0;
|
|
}
|
|
|
|
float Color::get_v() const {
|
|
float max = MAX(r, g);
|
|
max = MAX(max, b);
|
|
return max;
|
|
}
|
|
|
|
void Color::set_hsv(float p_h, float p_s, float p_v, float p_alpha) {
|
|
int i;
|
|
float f, p, q, t;
|
|
a = p_alpha;
|
|
|
|
if (p_s == 0) {
|
|
// acp_hromatic (grey)
|
|
r = g = b = p_v;
|
|
return;
|
|
}
|
|
|
|
p_h *= 6.0;
|
|
p_h = Math::fmod(p_h, 6);
|
|
i = Math::floor(p_h);
|
|
|
|
f = p_h - i;
|
|
p = p_v * (1 - p_s);
|
|
q = p_v * (1 - p_s * f);
|
|
t = p_v * (1 - p_s * (1 - f));
|
|
|
|
switch (i) {
|
|
case 0: // Red is the dominant color
|
|
r = p_v;
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
case 1: // Green is the dominant color
|
|
r = q;
|
|
g = p_v;
|
|
b = p;
|
|
break;
|
|
case 2:
|
|
r = p;
|
|
g = p_v;
|
|
b = t;
|
|
break;
|
|
case 3: // Blue is the dominant color
|
|
r = p;
|
|
g = q;
|
|
b = p_v;
|
|
break;
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
b = p_v;
|
|
break;
|
|
default: // (5) Red is the dominant color
|
|
r = p_v;
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Color::is_equal_approx(const Color &p_color) const {
|
|
return Math::is_equal_approx(r, p_color.r) && Math::is_equal_approx(g, p_color.g) && Math::is_equal_approx(b, p_color.b) && Math::is_equal_approx(a, p_color.a);
|
|
}
|
|
|
|
Color Color::clamp(const Color &p_min, const Color &p_max) const {
|
|
return Color(
|
|
CLAMP(r, p_min.r, p_max.r),
|
|
CLAMP(g, p_min.g, p_max.g),
|
|
CLAMP(b, p_min.b, p_max.b),
|
|
CLAMP(a, p_min.a, p_max.a));
|
|
}
|
|
|
|
void Color::invert() {
|
|
r = 1.0 - r;
|
|
g = 1.0 - g;
|
|
b = 1.0 - b;
|
|
}
|
|
void Color::contrast() {
|
|
r = Math::fmod(r + 0.5, 1.0);
|
|
g = Math::fmod(g + 0.5, 1.0);
|
|
b = Math::fmod(b + 0.5, 1.0);
|
|
}
|
|
|
|
Color Color::hex(uint32_t p_hex) {
|
|
float a = (p_hex & 0xFF) / 255.0;
|
|
p_hex >>= 8;
|
|
float b = (p_hex & 0xFF) / 255.0;
|
|
p_hex >>= 8;
|
|
float g = (p_hex & 0xFF) / 255.0;
|
|
p_hex >>= 8;
|
|
float r = (p_hex & 0xFF) / 255.0;
|
|
|
|
return Color(r, g, b, a);
|
|
}
|
|
|
|
Color Color::hex64(uint64_t p_hex) {
|
|
float a = (p_hex & 0xFFFF) / 65535.0;
|
|
p_hex >>= 16;
|
|
float b = (p_hex & 0xFFFF) / 65535.0;
|
|
p_hex >>= 16;
|
|
float g = (p_hex & 0xFFFF) / 65535.0;
|
|
p_hex >>= 16;
|
|
float r = (p_hex & 0xFFFF) / 65535.0;
|
|
|
|
return Color(r, g, b, a);
|
|
}
|
|
|
|
Color Color::from_rgbe9995(uint32_t p_rgbe) {
|
|
float r = p_rgbe & 0x1ff;
|
|
float g = (p_rgbe >> 9) & 0x1ff;
|
|
float b = (p_rgbe >> 18) & 0x1ff;
|
|
float e = (p_rgbe >> 27);
|
|
float m = Math::pow(2, e - 15.0 - 9.0);
|
|
|
|
float rd = r * m;
|
|
float gd = g * m;
|
|
float bd = b * m;
|
|
|
|
return Color(rd, gd, bd, 1.0f);
|
|
}
|
|
|
|
static float _parse_col(const String &p_str, int p_ofs) {
|
|
int ig = 0;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
int c = p_str[i + p_ofs];
|
|
int v = 0;
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
v = c - '0';
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
v = c - 'a';
|
|
v += 10;
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
v = c - 'A';
|
|
v += 10;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if (i == 0) {
|
|
ig += v * 16;
|
|
} else {
|
|
ig += v;
|
|
}
|
|
}
|
|
|
|
return ig;
|
|
}
|
|
|
|
Color Color::inverted() const {
|
|
Color c = *this;
|
|
c.invert();
|
|
return c;
|
|
}
|
|
|
|
Color Color::contrasted() const {
|
|
Color c = *this;
|
|
c.contrast();
|
|
return c;
|
|
}
|
|
|
|
Color Color::html(const String &p_color) {
|
|
String color = p_color;
|
|
if (color.length() == 0) {
|
|
return Color();
|
|
}
|
|
if (color[0] == '#') {
|
|
color = color.substr(1, color.length() - 1);
|
|
}
|
|
if (color.length() == 3 || color.length() == 4) {
|
|
String exp_color;
|
|
for (int i = 0; i < color.length(); i++) {
|
|
exp_color += color[i];
|
|
exp_color += color[i];
|
|
}
|
|
color = exp_color;
|
|
}
|
|
|
|
bool alpha = false;
|
|
|
|
if (color.length() == 8) {
|
|
alpha = true;
|
|
} else if (color.length() == 6) {
|
|
alpha = false;
|
|
} else {
|
|
ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_color + ".");
|
|
}
|
|
|
|
int a = 255;
|
|
if (alpha) {
|
|
a = _parse_col(color, 0);
|
|
ERR_FAIL_COND_V_MSG(a < 0, Color(), "Invalid color code: " + p_color + ".");
|
|
}
|
|
|
|
int from = alpha ? 2 : 0;
|
|
|
|
int r = _parse_col(color, from + 0);
|
|
ERR_FAIL_COND_V_MSG(r < 0, Color(), "Invalid color code: " + p_color + ".");
|
|
int g = _parse_col(color, from + 2);
|
|
ERR_FAIL_COND_V_MSG(g < 0, Color(), "Invalid color code: " + p_color + ".");
|
|
int b = _parse_col(color, from + 4);
|
|
ERR_FAIL_COND_V_MSG(b < 0, Color(), "Invalid color code: " + p_color + ".");
|
|
|
|
return Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
|
|
}
|
|
|
|
bool Color::html_is_valid(const String &p_color) {
|
|
String color = p_color;
|
|
|
|
if (color.length() == 0) {
|
|
return false;
|
|
}
|
|
if (color[0] == '#') {
|
|
color = color.substr(1, color.length() - 1);
|
|
}
|
|
|
|
bool alpha = false;
|
|
|
|
if (color.length() == 8) {
|
|
alpha = true;
|
|
} else if (color.length() == 6) {
|
|
alpha = false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (alpha) {
|
|
int a = _parse_col(color, 0);
|
|
if (a < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int from = alpha ? 2 : 0;
|
|
|
|
int r = _parse_col(color, from + 0);
|
|
if (r < 0) {
|
|
return false;
|
|
}
|
|
int g = _parse_col(color, from + 2);
|
|
if (g < 0) {
|
|
return false;
|
|
}
|
|
int b = _parse_col(color, from + 4);
|
|
if (b < 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Color Color::named(const String &p_name) {
|
|
if (_named_colors.empty()) {
|
|
_populate_named_colors(); // from color_names.inc
|
|
}
|
|
String name = p_name;
|
|
// Normalize name
|
|
name = name.replace(" ", "");
|
|
name = name.replace("-", "");
|
|
name = name.replace("_", "");
|
|
name = name.replace("'", "");
|
|
name = name.replace(".", "");
|
|
name = name.to_lower();
|
|
|
|
const RBMap<String, Color>::Element *color = _named_colors.find(name);
|
|
ERR_FAIL_NULL_V_MSG(color, Color(), "Invalid color name: " + p_name + ".");
|
|
return color->value();
|
|
}
|
|
|
|
String _to_hex(float p_val) {
|
|
int v = Math::round(p_val * 255);
|
|
v = CLAMP(v, 0, 255);
|
|
String ret;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
CharType c[2] = { 0, 0 };
|
|
int lv = v & 0xF;
|
|
if (lv < 10) {
|
|
c[0] = '0' + lv;
|
|
} else {
|
|
c[0] = 'a' + lv - 10;
|
|
}
|
|
|
|
v >>= 4;
|
|
String cs = (const CharType *)c;
|
|
ret = cs + ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
String Color::to_html(bool p_alpha) const {
|
|
String txt;
|
|
txt += _to_hex(r);
|
|
txt += _to_hex(g);
|
|
txt += _to_hex(b);
|
|
if (p_alpha) {
|
|
txt = _to_hex(a) + txt;
|
|
}
|
|
return txt;
|
|
}
|
|
|
|
Color Color::from_hsv(float p_h, float p_s, float p_v, float p_a) const {
|
|
Color c;
|
|
c.set_hsv(p_h, p_s, p_v, p_a);
|
|
return c;
|
|
}
|
|
|
|
// FIXME: Remove once Pandemonium 3.1 has been released
|
|
float Color::gray() const {
|
|
WARN_DEPRECATED_MSG("'Color.gray()' is deprecated and will be removed in a future version. Use 'Color.v' for a better grayscale approximation.");
|
|
return (r + g + b) / 3.0;
|
|
}
|
|
|
|
Color::operator String() const {
|
|
return "(" + String::num(r, 4) + ", " + String::num(g, 4) + ", " + String::num(b, 4) + ", " + String::num(a, 4) + ")";
|
|
}
|
|
|
|
Color Color::operator+(const Color &p_color) const {
|
|
return Color(
|
|
r + p_color.r,
|
|
g + p_color.g,
|
|
b + p_color.b,
|
|
a + p_color.a);
|
|
}
|
|
|
|
void Color::operator+=(const Color &p_color) {
|
|
r = r + p_color.r;
|
|
g = g + p_color.g;
|
|
b = b + p_color.b;
|
|
a = a + p_color.a;
|
|
}
|
|
|
|
Color Color::operator-(const Color &p_color) const {
|
|
return Color(
|
|
r - p_color.r,
|
|
g - p_color.g,
|
|
b - p_color.b,
|
|
a - p_color.a);
|
|
}
|
|
|
|
void Color::operator-=(const Color &p_color) {
|
|
r = r - p_color.r;
|
|
g = g - p_color.g;
|
|
b = b - p_color.b;
|
|
a = a - p_color.a;
|
|
}
|
|
|
|
Color Color::operator*(const Color &p_color) const {
|
|
return Color(
|
|
r * p_color.r,
|
|
g * p_color.g,
|
|
b * p_color.b,
|
|
a * p_color.a);
|
|
}
|
|
|
|
Color Color::operator*(const real_t &rvalue) const {
|
|
return Color(
|
|
r * rvalue,
|
|
g * rvalue,
|
|
b * rvalue,
|
|
a * rvalue);
|
|
}
|
|
|
|
void Color::operator*=(const Color &p_color) {
|
|
r = r * p_color.r;
|
|
g = g * p_color.g;
|
|
b = b * p_color.b;
|
|
a = a * p_color.a;
|
|
}
|
|
|
|
void Color::operator*=(const real_t &rvalue) {
|
|
r = r * rvalue;
|
|
g = g * rvalue;
|
|
b = b * rvalue;
|
|
a = a * rvalue;
|
|
}
|
|
|
|
Color Color::operator/(const Color &p_color) const {
|
|
return Color(
|
|
r / p_color.r,
|
|
g / p_color.g,
|
|
b / p_color.b,
|
|
a / p_color.a);
|
|
}
|
|
|
|
Color Color::operator/(const real_t &rvalue) const {
|
|
return Color(
|
|
r / rvalue,
|
|
g / rvalue,
|
|
b / rvalue,
|
|
a / rvalue);
|
|
}
|
|
|
|
void Color::operator/=(const Color &p_color) {
|
|
r = r / p_color.r;
|
|
g = g / p_color.g;
|
|
b = b / p_color.b;
|
|
a = a / p_color.a;
|
|
}
|
|
|
|
void Color::operator/=(const real_t &rvalue) {
|
|
if (rvalue == 0) {
|
|
r = 1.0;
|
|
g = 1.0;
|
|
b = 1.0;
|
|
a = 1.0;
|
|
} else {
|
|
r = r / rvalue;
|
|
g = g / rvalue;
|
|
b = b / rvalue;
|
|
a = a / rvalue;
|
|
}
|
|
};
|
|
|
|
Color Color::operator-() const {
|
|
return Color(
|
|
1.0 - r,
|
|
1.0 - g,
|
|
1.0 - b,
|
|
1.0 - a);
|
|
}
|