1 /** 2 Color in RGBA format with some support for ARGB format. 3 4 Copyright: 5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur. 6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md) 7 Copyright (c) 2017 Godot-D contributors 8 Copyright (c) 2022 Godot-DLang contributors 9 10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License) 11 12 13 */ 14 module godot.color; 15 16 import godot.api.types; 17 import godot.string; 18 19 import std.math; 20 import std.algorithm.comparison; 21 import core.stdc.stddef : wchar_t; 22 23 /** 24 A color is represented as red, green and blue (r,g,b) components. Additionally, “a” represents the alpha component, often used for transparency. Values are in floating point and usually range from 0 to 1. Some methods (such as set_modulate(color)) may accept values > 1. 25 */ 26 struct Color { 27 @nogc nothrow: 28 29 union { 30 struct { 31 float r = 0f; 32 float g = 0f; 33 float b = 0f; 34 float a = 1f; 35 } 36 37 float[4] components; 38 } 39 40 this(float r, float g, float b, float a = 1f) { 41 this.r = r; 42 this.g = g; 43 this.b = b; 44 this.a = a; 45 } 46 47 static float _parseCol(in string p_str, int p_ofs) { 48 int ig = 0; 49 50 for (int i = 0; i < 2; i++) { 51 int c = cast(int) cast(wchar_t) p_str[i + p_ofs]; 52 int v = 0; 53 54 if (c >= '0' && c <= '9') { 55 v = c - '0'; 56 } else if (c >= 'a' && c <= 'f') { 57 v = c - 'a'; 58 v += 10; 59 } else if (c >= 'A' && c <= 'F') { 60 v = c - 'A'; 61 v += 10; 62 } else { 63 return -1; 64 } 65 66 if (i == 0) 67 ig += v * 16; 68 else 69 ig += v; 70 } 71 72 return ig; 73 74 } 75 76 uint to32() const { 77 78 uint c = cast(ubyte)(r * 255); 79 c <<= 8; 80 c |= cast(ubyte)(g * 255); 81 c <<= 8; 82 c |= cast(ubyte)(b * 255); 83 c <<= 8; 84 c |= cast(ubyte)(a * 255); 85 86 return c; 87 } 88 89 @property { 90 ubyte r8() const { 91 return cast(ubyte)(r * 255); 92 } 93 94 ubyte g8() const { 95 return cast(ubyte)(g * 255); 96 } 97 98 ubyte b8() const { 99 return cast(ubyte)(b * 255); 100 } 101 102 ubyte a8() const { 103 return cast(ubyte)(a * 255); 104 } 105 106 void r8(ubyte value) { 107 r = (1f / 255f) * value; 108 } 109 110 void g8(ubyte value) { 111 g = (1f / 255f) * value; 112 } 113 114 void b8(ubyte value) { 115 b = (1f / 255f) * value; 116 } 117 118 void a8(ubyte value) { 119 a = (1f / 255f) * value; 120 } 121 } 122 123 uint toARGB32() const { 124 uint c = cast(ubyte)(a * 255); 125 c <<= 8; 126 c |= cast(ubyte)(r * 255); 127 c <<= 8; 128 c |= cast(ubyte)(g * 255); 129 c <<= 8; 130 c |= cast(ubyte)(b * 255); 131 132 return c; 133 } 134 135 float gray() const { 136 return (r + g + b) / 3.0; 137 } 138 139 float hue() const { 140 141 float minv = min(r, g); 142 minv = min(minv, b); 143 float maxv = max(r, g); 144 maxv = max(maxv, b); 145 146 float delta = maxv - minv; 147 148 if (delta == 0) 149 return 0; 150 151 float h; 152 if (r == maxv) 153 h = (g - b) / delta; // between yellow & magenta 154 else if (g == maxv) 155 h = 2 + (b - r) / delta; // between cyan & yellow 156 else 157 h = 4 + (r - g) / delta; // between magenta & cyan 158 159 h /= 6.0; 160 if (h < 0) 161 h += 1.0; 162 163 return h; 164 } 165 166 float saturation() const { 167 float minv = min(r, g); 168 minv = min(minv, b); 169 float maxv = max(r, g); 170 maxv = max(maxv, b); 171 float delta = maxv - minv; 172 return (maxv != 0) ? (delta / maxv) : 0; 173 174 } 175 176 float value() const { 177 float maxv = max(r, g); 178 maxv = max(maxv, b); 179 return maxv; 180 } 181 182 void setHsv(float p_h, float p_s, float p_v, float p_alpha) { 183 int i; 184 float f, p, q, t; 185 a = p_alpha; 186 187 if (p_s == 0) { 188 // acp_hromatic (grey) 189 r = g = b = p_v; 190 return; 191 } 192 193 p_h *= 6.0; 194 p_h = fmod(p_h, 6); 195 i = cast(int) floor(p_h); 196 197 f = p_h - i; 198 p = p_v * (1 - p_s); 199 q = p_v * (1 - p_s * f); 200 t = p_v * (1 - p_s * (1 - f)); 201 202 switch (i) { 203 case 0: // Red is the dominant color 204 r = p_v; 205 g = t; 206 b = p; 207 break; 208 case 1: // Green is the dominant color 209 r = q; 210 g = p_v; 211 b = p; 212 break; 213 case 2: 214 r = p; 215 g = p_v; 216 b = t; 217 break; 218 case 3: // Blue is the dominant color 219 r = p; 220 g = q; 221 b = p_v; 222 break; 223 case 4: 224 r = t; 225 g = p; 226 b = p_v; 227 break; 228 default: // (5) Red is the dominant color 229 r = p_v; 230 g = p; 231 b = q; 232 break; 233 } 234 } 235 236 void invert() { 237 r = 1.0 - r; 238 g = 1.0 - g; 239 b = 1.0 - b; 240 } 241 242 void contrast() { 243 r = fmod(r + 0.5, 1.0); 244 g = fmod(g + 0.5, 1.0); 245 b = fmod(b + 0.5, 1.0); 246 } 247 248 Color inverted() const { 249 Color c = this; 250 c.invert(); 251 return c; 252 } 253 254 Color contrasted() const { 255 Color c = this; 256 c.contrast(); 257 return c; 258 } 259 260 Color linearInterpolate(in Color p_b, float p_t) const { 261 262 Color res = this; 263 264 res.r += (p_t * (p_b.r - r)); 265 res.g += (p_t * (p_b.g - g)); 266 res.b += (p_t * (p_b.b - b)); 267 res.a += (p_t * (p_b.a - a)); 268 269 return res; 270 } 271 272 alias lerp = linearInterpolate; 273 274 Color blend(in Color p_over) const { 275 276 Color res; 277 float sa = 1.0 - p_over.a; 278 res.a = a * sa + p_over.a; 279 if (res.a == 0) { 280 return Color(0, 0, 0, 0); 281 } else { 282 res.r = (r * a * sa + p_over.r * p_over.a) / res.a; 283 res.g = (g * a * sa + p_over.g * p_over.a) / res.a; 284 res.b = (b * a * sa + p_over.b * p_over.a) / res.a; 285 } 286 return res; 287 } 288 289 Color toLinear() const { 290 return Color( 291 r < 0.04045 ? r * (1.0 / 12.92) : pow((r + 0.055) * (1.0 / (1 + 0.055)), 2.4), 292 g < 0.04045 ? g * (1.0 / 12.92) : pow((g + 0.055) * (1.0 / (1 + 0.055)), 2.4), 293 b < 0.04045 ? b * (1.0 / 12.92) : pow((b + 0.055) * (1.0 / (1 + 0.055)), 2.4), 294 a 295 ); 296 } 297 298 Color hex(uint p_hex) { 299 float a = (p_hex & 0xFF) / 255.0; 300 p_hex >>= 8; 301 float b = (p_hex & 0xFF) / 255.0; 302 p_hex >>= 8; 303 float g = (p_hex & 0xFF) / 255.0; 304 p_hex >>= 8; 305 float r = (p_hex & 0xFF) / 255.0; 306 307 return Color(r, g, b, a); 308 } 309 310 /+Color html(in ref String color) 311 { 312 if (color.length()==0) 313 return Color(); 314 if (color[0]=='#') 315 color=color.substr(1,color.length()-1); 316 317 bool alpha=false; 318 319 if (color.length()==8) { 320 alpha=true; 321 } else if (color.length()==6) { 322 alpha=false; 323 } else { 324 ERR_PRINT(String("Invalid Color Code: ") + p_color); 325 ERR_FAIL_V(Color()); 326 } 327 328 int a=255; 329 if (alpha) { 330 a=_parse_col(color,0); 331 if (a<0) { 332 ERR_PRINT("Invalid Color Code: "+p_color); 333 ERR_FAIL_V(Color()); 334 } 335 } 336 337 int from=alpha?2:0; 338 339 int r=_parse_col(color,from+0); 340 if (r<0) { 341 ERR_PRINT("Invalid Color Code: "+p_color); 342 ERR_FAIL_V(Color()); 343 } 344 int g=_parse_col(color,from+2); 345 if (g<0) { 346 ERR_PRINT("Invalid Color Code: "+p_color); 347 ERR_FAIL_V(Color()); 348 } 349 int b=_parse_col(color,from+4); 350 if (b<0) { 351 ERR_PRINT("Invalid Color Code: "+p_color); 352 ERR_FAIL_V(Color()); 353 } 354 355 return Color(r/255.0,g/255.0,b/255.0,a/255.0); 356 } 357 358 bool html_is_valid(const String& p_color) 359 { 360 String color = p_color; 361 362 if (color.length()==0) 363 return false; 364 if (color[0]=='#') 365 color=color.substr(1,color.length()-1); 366 367 bool alpha=false; 368 369 if (color.length()==8) { 370 alpha=true; 371 } else if (color.length()==6) { 372 alpha=false; 373 } else { 374 return false; 375 } 376 377 int a=255; 378 if (alpha) { 379 a=_parse_col(color,0); 380 if (a<0) { 381 return false; 382 } 383 } 384 385 int from=alpha?2:0; 386 387 int r=_parse_col(color,from+0); 388 if (r<0) { 389 return false; 390 } 391 int g=_parse_col(color,from+2); 392 if (g<0) { 393 return false; 394 } 395 int b=_parse_col(color,from+4); 396 if (b<0) { 397 return false; 398 } 399 400 return true; 401 }+/ 402 403 private static char[2] _toHex(float p_val) { 404 char[2] ret; 405 406 int v = cast(int)(p_val * 255); 407 v = clamp(v, 0, 255); 408 409 foreach (int i; 0 .. 2) { 410 int lv = cast(char)(v & 0xF); 411 if (lv < 10) 412 ret[0] = cast(char)('0' + lv); 413 else 414 ret[0] = cast(char)('a' + lv - 10); 415 416 v >>= 4; 417 } 418 419 return ret; 420 } 421 422 char[8] toHtml() const { 423 char[8] ret; 424 ret[0 .. 2] = _toHex(r); 425 ret[2 .. 4] = _toHex(g); 426 ret[4 .. 6] = _toHex(b); 427 ret[6 .. 8] = _toHex(a); 428 return ret; 429 } 430 431 Color opBinary(string op)(in Color other) const 432 if (op == "+" || op == "-" || op == "*" || op == "/") { 433 Color ret; 434 ret.r = mixin("r " ~ op ~ "other.r"); 435 ret.b = mixin("b " ~ op ~ "other.b"); 436 ret.g = mixin("g " ~ op ~ "other.g"); 437 ret.a = mixin("a " ~ op ~ "other.a"); 438 return ret; 439 } 440 441 void opOpAssign(string op)(in Color other) 442 if (op == "+" || op == "-" || op == "*" || op == "/") { 443 r = mixin("r " ~ op ~ "other.r"); 444 b = mixin("b " ~ op ~ "other.b"); 445 g = mixin("g " ~ op ~ "other.g"); 446 } 447 448 Color opUnary(string op : "-")() { 449 return Color(-r, -g, -b, -a); 450 } 451 452 Color opBinary(string op)(in real_t scalar) const 453 if (op == "*" || op == "/") { 454 Color ret; 455 ret.r = mixin("r " ~ op ~ " scalar"); 456 ret.g = mixin("g " ~ op ~ " scalar"); 457 ret.b = mixin("b " ~ op ~ " scalar"); 458 ret.a = mixin("a " ~ op ~ " scalar"); 459 return ret; 460 } 461 462 Color opBinaryRight(string op)(in real_t scalar) const 463 if (op == "*") { 464 Color ret; 465 ret.r = mixin("r " ~ op ~ " scalar"); 466 ret.g = mixin("g " ~ op ~ " scalar"); 467 ret.b = mixin("b " ~ op ~ " scalar"); 468 ret.a = mixin("a " ~ op ~ " scalar"); 469 return ret; 470 } 471 472 void opOpAssign(string op)(in real_t scalar) if (op == "*" || op == "/") { 473 r = mixin("r " ~ op ~ " scalar"); 474 g = mixin("g " ~ op ~ " scalar"); 475 b = mixin("b " ~ op ~ " scalar"); 476 a = mixin("a " ~ op ~ " scalar"); 477 } 478 479 /+bool operator<(const Color& p_color) const { 480 481 if (r==p_color.r) { 482 if (g==p_color.g) { 483 if(b==p_color.b) { 484 return (a<p_color.a); 485 } else 486 return (b<p_color.b); 487 } else 488 return g<p_color.g; 489 } else 490 return r<p_color.r; 491 492 }+/ 493 }