diff --git a/include/godot/core/Color.h b/include/godot/core/Color.h new file mode 100644 index 00000000..9e7b251b --- /dev/null +++ b/include/godot/core/Color.h @@ -0,0 +1,467 @@ +#ifndef COLOR_H +#define COLOR_H + +#include + +#include + +#include + +namespace godot { + +// @Todo move these to a more global file. + +// or should I? 🤔 +#define MIN(a, b) (a < b ? a : b) +#define MAX(a, b) (a > b ? a : b) + +struct Color { + + +private: + static float _parse_col(const String& p_str, int p_ofs) { + + int ig=0; + + for(int i=0;i<2;i++) { + + int c= (int) (wchar_t) 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; + + } +public: + + union { + + struct { + float r; + float g; + float b; + float a; + }; + float components[4]; + }; + + bool operator==(const Color &p_color) const { return (r==p_color.r && g==p_color.g && b==p_color.b && a==p_color.a ); } + bool operator!=(const Color &p_color) const { return (r!=p_color.r || g!=p_color.g || b!=p_color.b || a!=p_color.a ); } + + uint32_t to_32() const + { + + uint32_t c=(uint8_t)(a*255); + c<<=8; + c|=(uint8_t)(r*255); + c<<=8; + c|=(uint8_t)(g*255); + c<<=8; + c|=(uint8_t)(b*255); + + return c; + } + + uint32_t to_ARGB32() const + { + uint32_t c=(uint8_t)(a*255); + c<<=8; + c|=(uint8_t)(r*255); + c<<=8; + c|=(uint8_t)(g*255); + c<<=8; + c|=(uint8_t)(b*255); + + return c; + } + + float gray() const + { + return (r+g+b)/3.0; + } + + float 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 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 get_v() const + { + float max = MAX( r, g ); + max = MAX( max, b ); + return max; + } + + void set_hsv(float p_h, float p_s, float p_v, float p_alpha=1.0) + { + 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 = ::fmod(p_h,6); + i = ::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; + } + } + + float& operator[](int idx) { + return components[idx]; + } + const float& operator[](int idx) const { + return components[idx]; + } + + void invert() + { + r=1.0-r; + g=1.0-g; + b=1.0-b; + } + + void contrast() + { + r=::fmod(r+0.5,1.0); + g=::fmod(g+0.5,1.0); + b=::fmod(b+0.5,1.0); + } + Color inverted() const + { + Color c=*this; + c.invert(); + return c; + } + Color contrasted() const + { + Color c=*this; + c.contrast(); + return c; + } + + Color linear_interpolate(const Color& p_b, float p_t) const { + + Color res=*this; + + res.r+= (p_t * (p_b.r-r)); + res.g+= (p_t * (p_b.g-g)); + res.b+= (p_t * (p_b.b-b)); + res.a+= (p_t * (p_b.a-a)); + + return res; + } + + Color blend(const Color& p_over) const { + + + Color res; + float sa = 1.0 - p_over.a; + res.a = a*sa+p_over.a; + if (res.a==0) { + return Color(0,0,0,0); + } else { + res.r = (r*a*sa + p_over.r * p_over.a)/res.a; + res.g = (g*a*sa + p_over.g * p_over.a)/res.a; + res.b = (b*a*sa + p_over.b * p_over.a)/res.a; + } + return res; + } + + Color to_linear() const { + + return Color( + r<0.04045 ? r * (1.0 / 12.92) : ::pow((r + 0.055) * (1.0 / (1 + 0.055)), 2.4), + g<0.04045 ? g * (1.0 / 12.92) : ::pow((g + 0.055) * (1.0 / (1 + 0.055)), 2.4), + b<0.04045 ? b * (1.0 / 12.92) : ::pow((b + 0.055) * (1.0 / (1 + 0.055)), 2.4), + a + ); + } + + static 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); + } + + static 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); + + bool alpha=false; + + if (color.length()==8) { + alpha=true; + } else if (color.length()==6) { + alpha=false; + } else { + // @Todo error reporting + // ERR_EXPLAIN("Invalid Color Code: "+p_color); + // ERR_FAIL_V(Color()); + return Color(); + } + + int a=255; + if (alpha) { + a=_parse_col(color,0); + if (a<0) { + // @Todo error reporting + // ERR_EXPLAIN("Invalid Color Code: "+p_color); + // ERR_FAIL_V(Color()); + return Color(); + } + } + + int from=alpha?2:0; + + int r=_parse_col(color,from+0); + if (r<0) { + // @Todo error reporting + // ERR_EXPLAIN("Invalid Color Code: "+p_color); + // ERR_FAIL_V(Color()); + return Color(); + } + int g=_parse_col(color,from+2); + if (g<0) { + // @Todo error reporting + // ERR_EXPLAIN("Invalid Color Code: "+p_color); + // ERR_FAIL_V(Color()); + return Color(); + } + int b=_parse_col(color,from+4); + if (b<0) { + // @Todo error reporting + // ERR_EXPLAIN("Invalid Color Code: "+p_color); + // ERR_FAIL_V(Color()); + return Color(); + } + + return Color(r/255.0,g/255.0,b/255.0,a/255.0); + } + + static bool 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; + } + + int a=255; + if (alpha) { + 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; + } + +private: +#ifndef CLAMP +#define CLAMP(m_a,m_min,m_max) (((m_a)<(m_min))?(m_min):(((m_a)>(m_max))?m_max:m_a)) +#endif + static String _to_hex(float p_val) { + + int v = p_val * 255; + v = CLAMP(v,0,255); + String ret; + + for(int i=0;i<2;i++) { + + wchar_t 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 wchar_t*)c; + ret = cs + ret; + } + + return ret; + + } + +public: + + String to_html(bool p_alpha=true) 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; + } + + bool operator<(const Color& p_color) const; //used in set keys + operator String() const + { + return String(); // @Todo + } + + /** + * No construct parameters, r=0, g=0, b=0. a=255 + */ + Color() { + r=0; g=0; b=0; a=1.0; + } + + /** + * RGB / RGBA construct parameters. Alpha is optional, but defaults to 1.0 + */ + Color(float p_r,float p_g,float p_b,float p_a=1.0) { r=p_r; g=p_g; b=p_b; a=p_a; } +}; + +bool Color::operator<(const Color& p_color) const { + + if (r==p_color.r) { + if (g==p_color.g) { + if(b==p_color.b) { + return (a