using System;
using System.Runtime.InteropServices;
namespace Godot
{
///
/// A color represented by red, green, blue, and alpha (RGBA) components.
/// The alpha component is often used for transparency.
/// Values are in floating-point and usually range from 0 to 1.
/// Some properties (such as CanvasItem.modulate) may accept values
/// greater than 1 (overbright or HDR colors).
///
/// If you want to supply values in a range of 0 to 255, you should use
/// and the `r8`/`g8`/`b8`/`a8` properties.
///
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Color : IEquatable
{
///
/// The color's red component, typically on the range of 0 to 1.
///
public float r;
///
/// The color's green component, typically on the range of 0 to 1.
///
public float g;
///
/// The color's blue component, typically on the range of 0 to 1.
///
public float b;
///
/// The color's alpha (transparency) component, typically on the range of 0 to 1.
///
public float a;
///
/// Wrapper for that uses the range 0 to 255 instead of 0 to 1.
///
/// Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.
public int r8
{
get
{
return (int)Math.Round(r * 255.0f);
}
set
{
r = value / 255.0f;
}
}
///
/// Wrapper for that uses the range 0 to 255 instead of 0 to 1.
///
/// Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.
public int g8
{
get
{
return (int)Math.Round(g * 255.0f);
}
set
{
g = value / 255.0f;
}
}
///
/// Wrapper for that uses the range 0 to 255 instead of 0 to 1.
///
/// Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.
public int b8
{
get
{
return (int)Math.Round(b * 255.0f);
}
set
{
b = value / 255.0f;
}
}
///
/// Wrapper for that uses the range 0 to 255 instead of 0 to 1.
///
/// Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.
public int a8
{
get
{
return (int)Math.Round(a * 255.0f);
}
set
{
a = value / 255.0f;
}
}
///
/// The HSV hue of this color, on the range 0 to 1.
///
/// Getting is a long process, refer to the source code for details. Setting uses .
public float h
{
get
{
float max = Math.Max(r, Math.Max(g, b));
float min = Math.Min(r, Math.Min(g, 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.0f;
if (h < 0)
{
h += 1.0f;
}
return h;
}
set
{
this = FromHSV(value, s, v, a);
}
}
///
/// The HSV saturation of this color, on the range 0 to 1.
///
/// Getting is equivalent to the ratio between the min and max RGB value. Setting uses .
public float s
{
get
{
float max = Math.Max(r, Math.Max(g, b));
float min = Math.Min(r, Math.Min(g, b));
float delta = max - min;
return max == 0 ? 0 : delta / max;
}
set
{
this = FromHSV(h, value, v, a);
}
}
///
/// The HSV value (brightness) of this color, on the range 0 to 1.
///
/// Getting is equivalent to using `Max()` on the RGB components. Setting uses .
public float v
{
get
{
return Math.Max(r, Math.Max(g, b));
}
set
{
this = FromHSV(h, s, value, a);
}
}
///
/// Access color components using their index.
///
/// `[0]` is equivalent to `.r`, `[1]` is equivalent to `.g`, `[2]` is equivalent to `.b`, `[3]` is equivalent to `.a`.
public float this[int index]
{
get
{
switch (index)
{
case 0:
return r;
case 1:
return g;
case 2:
return b;
case 3:
return a;
default:
throw new IndexOutOfRangeException();
}
}
set
{
switch (index)
{
case 0:
r = value;
return;
case 1:
g = value;
return;
case 2:
b = value;
return;
case 3:
a = value;
return;
default:
throw new IndexOutOfRangeException();
}
}
}
///
/// Returns a new color resulting from blending this color over another.
/// If the color is opaque, the result is also opaque.
/// The second color may have a range of alpha values.
///
/// The color to blend over.
/// This color blended over `over`.
public Color Blend(Color over)
{
Color res;
float sa = 1.0f - over.a;
res.a = a * sa + over.a;
if (res.a == 0)
{
return new Color(0, 0, 0, 0);
}
res.r = (r * a * sa + over.r * over.a) / res.a;
res.g = (g * a * sa + over.g * over.a) / res.a;
res.b = (b * a * sa + over.b * over.a) / res.a;
return res;
}
///
/// Returns a new color with all components clamped between the
/// components of `min` and `max` using
/// .
///
/// The color with minimum allowed values.
/// The color with maximum allowed values.
/// The color with all components clamped.
public Color Clamp(Color? min = null, Color? max = null)
{
Color minimum = min ?? new Color(0, 0, 0, 0);
Color maximum = max ?? new Color(1, 1, 1, 1);
return new Color
(
(float)Mathf.Clamp(r, minimum.r, maximum.r),
(float)Mathf.Clamp(g, minimum.g, maximum.g),
(float)Mathf.Clamp(b, minimum.b, maximum.b),
(float)Mathf.Clamp(a, minimum.a, maximum.a)
);
}
///
/// Returns a new color resulting from making this color darker
/// by the specified ratio (on the range of 0 to 1).
///
/// The ratio to darken by.
/// The darkened color.
public Color Darkened(float amount)
{
Color res = this;
res.r = res.r * (1.0f - amount);
res.g = res.g * (1.0f - amount);
res.b = res.b * (1.0f - amount);
return res;
}
///
/// Returns the inverted color: `(1 - r, 1 - g, 1 - b, a)`.
///
/// The inverted color.
public Color Inverted()
{
return new Color(
1.0f - r,
1.0f - g,
1.0f - b,
a
);
}
///
/// Returns a new color resulting from making this color lighter
/// by the specified ratio (on the range of 0 to 1).
///
/// The ratio to lighten by.
/// The darkened color.
public Color Lightened(float amount)
{
Color res = this;
res.r = res.r + (1.0f - res.r) * amount;
res.g = res.g + (1.0f - res.g) * amount;
res.b = res.b + (1.0f - res.b) * amount;
return res;
}
///
/// Returns the result of the linear interpolation between
/// this color and `to` by amount `weight`.
///
/// The destination color for interpolation.
/// A value on the range of 0.0 to 1.0, representing the amount of interpolation.
/// The resulting color of the interpolation.
public Color Lerp(Color to, float weight)
{
return new Color
(
Mathf.Lerp(r, to.r, weight),
Mathf.Lerp(g, to.g, weight),
Mathf.Lerp(b, to.b, weight),
Mathf.Lerp(a, to.a, weight)
);
}
///
/// Returns the result of the linear interpolation between
/// this color and `to` by color amount `weight`.
///
/// The destination color for interpolation.
/// A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.
/// The resulting color of the interpolation.
public Color Lerp(Color to, Color weight)
{
return new Color
(
Mathf.Lerp(r, to.r, weight.r),
Mathf.Lerp(g, to.g, weight.g),
Mathf.Lerp(b, to.b, weight.b),
Mathf.Lerp(a, to.a, weight.a)
);
}
///
/// Returns the color converted to an unsigned 32-bit integer in ABGR
/// format (each byte represents a color channel).
/// ABGR is the reversed version of the default format.
///
/// A uint representing this color in ABGR32 format.
public uint ToAbgr32()
{
uint c = (byte)Math.Round(a * 255);
c <<= 8;
c |= (byte)Math.Round(b * 255);
c <<= 8;
c |= (byte)Math.Round(g * 255);
c <<= 8;
c |= (byte)Math.Round(r * 255);
return c;
}
///
/// Returns the color converted to an unsigned 64-bit integer in ABGR
/// format (each word represents a color channel).
/// ABGR is the reversed version of the default format.
///
/// A ulong representing this color in ABGR64 format.
public ulong ToAbgr64()
{
ulong c = (ushort)Math.Round(a * 65535);
c <<= 16;
c |= (ushort)Math.Round(b * 65535);
c <<= 16;
c |= (ushort)Math.Round(g * 65535);
c <<= 16;
c |= (ushort)Math.Round(r * 65535);
return c;
}
///
/// Returns the color converted to an unsigned 32-bit integer in ARGB
/// format (each byte represents a color channel).
/// ARGB is more compatible with DirectX, but not used much in Godot.
///
/// A uint representing this color in ARGB32 format.
public uint ToArgb32()
{
uint c = (byte)Math.Round(a * 255);
c <<= 8;
c |= (byte)Math.Round(r * 255);
c <<= 8;
c |= (byte)Math.Round(g * 255);
c <<= 8;
c |= (byte)Math.Round(b * 255);
return c;
}
///
/// Returns the color converted to an unsigned 64-bit integer in ARGB
/// format (each word represents a color channel).
/// ARGB is more compatible with DirectX, but not used much in Godot.
///
/// A ulong representing this color in ARGB64 format.
public ulong ToArgb64()
{
ulong c = (ushort)Math.Round(a * 65535);
c <<= 16;
c |= (ushort)Math.Round(r * 65535);
c <<= 16;
c |= (ushort)Math.Round(g * 65535);
c <<= 16;
c |= (ushort)Math.Round(b * 65535);
return c;
}
///
/// Returns the color converted to an unsigned 32-bit integer in RGBA
/// format (each byte represents a color channel).
/// RGBA is Godot's default and recommended format.
///
/// A uint representing this color in RGBA32 format.
public uint ToRgba32()
{
uint c = (byte)Math.Round(r * 255);
c <<= 8;
c |= (byte)Math.Round(g * 255);
c <<= 8;
c |= (byte)Math.Round(b * 255);
c <<= 8;
c |= (byte)Math.Round(a * 255);
return c;
}
///
/// Returns the color converted to an unsigned 64-bit integer in RGBA
/// format (each word represents a color channel).
/// RGBA is Godot's default and recommended format.
///
/// A ulong representing this color in RGBA64 format.
public ulong ToRgba64()
{
ulong c = (ushort)Math.Round(r * 65535);
c <<= 16;
c |= (ushort)Math.Round(g * 65535);
c <<= 16;
c |= (ushort)Math.Round(b * 65535);
c <<= 16;
c |= (ushort)Math.Round(a * 65535);
return c;
}
///
/// Returns the color's HTML hexadecimal color string in RGBA format.
///
/// Whether or not to include alpha. If false, the color is RGB instead of RGBA.
/// A string for the HTML hexadecimal representation of this color.
public string ToHTML(bool includeAlpha = true)
{
var txt = string.Empty;
txt += ToHex32(r);
txt += ToHex32(g);
txt += ToHex32(b);
if (includeAlpha)
{
txt += ToHex32(a);
}
return txt;
}
///
/// Constructs a color from RGBA values, typically on the range of 0 to 1.
///
/// The color's red component, typically on the range of 0 to 1.
/// The color's green component, typically on the range of 0 to 1.
/// The color's blue component, typically on the range of 0 to 1.
/// The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.
public Color(float r, float g, float b, float a = 1.0f)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
///
/// Constructs a color from an existing color and an alpha value.
///
/// The color to construct from. Only its RGB values are used.
/// The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.
public Color(Color c, float a = 1.0f)
{
r = c.r;
g = c.g;
b = c.b;
this.a = a;
}
///
/// Constructs a color from an unsigned 32-bit integer in RGBA format
/// (each byte represents a color channel).
///
/// The uint representing the color.
public Color(uint rgba)
{
a = (rgba & 0xFF) / 255.0f;
rgba >>= 8;
b = (rgba & 0xFF) / 255.0f;
rgba >>= 8;
g = (rgba & 0xFF) / 255.0f;
rgba >>= 8;
r = (rgba & 0xFF) / 255.0f;
}
///
/// Constructs a color from an unsigned 64-bit integer in RGBA format
/// (each word represents a color channel).
///
/// The ulong representing the color.
public Color(ulong rgba)
{
a = (rgba & 0xFFFF) / 65535.0f;
rgba >>= 16;
b = (rgba & 0xFFFF) / 65535.0f;
rgba >>= 16;
g = (rgba & 0xFFFF) / 65535.0f;
rgba >>= 16;
r = (rgba & 0xFFFF) / 65535.0f;
}
///
/// Constructs a color either from an HTML color code or from a
/// standardized color name. Supported
/// color names are the same as the constants.
///
/// The HTML color code or color name to construct from.
public Color(string code)
{
if (HtmlIsValid(code))
{
this = FromHTML(code);
}
else
{
this = Named(code);
}
}
///
/// Constructs a color either from an HTML color code or from a
/// standardized color name, with `alpha` on the range of 0 to 1. Supported
/// color names are the same as the constants.
///
/// The HTML color code or color name to construct from.
/// The alpha (transparency) value, typically on the range of 0 to 1.
public Color(string code, float alpha)
{
this = new Color(code);
a = alpha;
}
///
/// Constructs a color from the HTML hexadecimal color string in RGBA format.
///
/// A string for the HTML hexadecimal representation of this color.
private static Color FromHTML(string rgba)
{
Color c;
if (rgba.Length == 0)
{
c.r = 0f;
c.g = 0f;
c.b = 0f;
c.a = 1.0f;
return c;
}
if (rgba[0] == '#')
{
rgba = rgba.Substring(1);
}
// If enabled, use 1 hex digit per channel instead of 2.
// Other sizes aren't in the HTML/CSS spec but we could add them if desired.
bool isShorthand = rgba.Length < 5;
bool alpha;
if (rgba.Length == 8)
{
alpha = true;
}
else if (rgba.Length == 6)
{
alpha = false;
}
else if (rgba.Length == 4)
{
alpha = true;
}
else if (rgba.Length == 3)
{
alpha = false;
}
else
{
throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
}
c.a = 1.0f;
if (isShorthand)
{
c.r = ParseCol4(rgba, 0) / 15f;
c.g = ParseCol4(rgba, 1) / 15f;
c.b = ParseCol4(rgba, 2) / 15f;
if (alpha)
{
c.a = ParseCol4(rgba, 3) / 15f;
}
}
else
{
c.r = ParseCol8(rgba, 0) / 255f;
c.g = ParseCol8(rgba, 2) / 255f;
c.b = ParseCol8(rgba, 4) / 255f;
if (alpha)
{
c.a = ParseCol8(rgba, 6) / 255f;
}
}
if (c.r < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
}
if (c.g < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
}
if (c.b < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
}
if (c.a < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
}
return c;
}
///
/// Returns a color constructed from integer red, green, blue, and alpha channels.
/// Each channel should have 8 bits of information ranging from 0 to 255.
///
/// The red component represented on the range of 0 to 255.
/// The green component represented on the range of 0 to 255.
/// The blue component represented on the range of 0 to 255.
/// The alpha (transparency) component represented on the range of 0 to 255.
/// The constructed color.
public static Color Color8(byte r8, byte g8, byte b8, byte a8 = 255)
{
return new Color(r8 / 255f, g8 / 255f, b8 / 255f, a8 / 255f);
}
///
/// Returns a color according to the standardized name, with the
/// specified alpha value. Supported color names are the same as
/// the constants defined in .
///
/// The name of the color.
/// The constructed color.
private static Color Named(string name)
{
name = name.Replace(" ", String.Empty);
name = name.Replace("-", String.Empty);
name = name.Replace("_", String.Empty);
name = name.Replace("'", String.Empty);
name = name.Replace(".", String.Empty);
name = name.ToUpper();
if (!Colors.namedColors.ContainsKey(name))
{
throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}");
}
return Colors.namedColors[name];
}
///
/// Constructs a color from an HSV profile, with values on the
/// range of 0 to 1. This is equivalent to using each of
/// the `h`/`s`/`v` properties, but much more efficient.
///
/// The HSV hue, typically on the range of 0 to 1.
/// The HSV saturation, typically on the range of 0 to 1.
/// The HSV value (brightness), typically on the range of 0 to 1.
/// The alpha (transparency) value, typically on the range of 0 to 1.
/// The constructed color.
public static Color FromHSV(float hue, float saturation, float value, float alpha = 1.0f)
{
if (saturation == 0)
{
// Achromatic (grey)
return new Color(value, value, value, alpha);
}
int i;
float f, p, q, t;
hue *= 6.0f;
hue %= 6f;
i = (int)hue;
f = hue - i;
p = value * (1 - saturation);
q = value * (1 - saturation * f);
t = value * (1 - saturation * (1 - f));
switch (i)
{
case 0: // Red is the dominant color
return new Color(value, t, p, alpha);
case 1: // Green is the dominant color
return new Color(q, value, p, alpha);
case 2:
return new Color(p, value, t, alpha);
case 3: // Blue is the dominant color
return new Color(p, q, value, alpha);
case 4:
return new Color(t, p, value, alpha);
default: // (5) Red is the dominant color
return new Color(value, p, q, alpha);
}
}
///
/// Converts a color to HSV values. This is equivalent to using each of
/// the `h`/`s`/`v` properties, but much more efficient.
///
/// Output parameter for the HSV hue.
/// Output parameter for the HSV saturation.
/// Output parameter for the HSV value.
public void ToHSV(out float hue, out float saturation, out float value)
{
float max = (float)Mathf.Max(r, Mathf.Max(g, b));
float min = (float)Mathf.Min(r, Mathf.Min(g, b));
float delta = max - min;
if (delta == 0)
{
hue = 0;
}
else
{
if (r == max)
{
hue = (g - b) / delta; // Between yellow & magenta
}
else if (g == max)
{
hue = 2 + (b - r) / delta; // Between cyan & yellow
}
else
{
hue = 4 + (r - g) / delta; // Between magenta & cyan
}
hue /= 6.0f;
if (hue < 0)
{
hue += 1.0f;
}
}
saturation = max == 0 ? 0 : 1f - 1f * min / max;
value = max;
}
private static int ParseCol4(string str, int ofs)
{
char character = str[ofs];
if (character >= '0' && character <= '9')
{
return character - '0';
}
else if (character >= 'a' && character <= 'f')
{
return character + (10 - 'a');
}
else if (character >= 'A' && character <= 'F')
{
return character + (10 - 'A');
}
return -1;
}
private static int ParseCol8(string str, int ofs)
{
return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1);
}
private string ToHex32(float val)
{
byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255));
return b.HexEncode();
}
internal static bool HtmlIsValid(string color)
{
if (color.Length == 0)
{
return false;
}
if (color[0] == '#')
{
color = color.Substring(1);
}
// Check if the amount of hex digits is valid.
int len = color.Length;
if (!(len == 3 || len == 4 || len == 6 || len == 8))
{
return false;
}
// Check if each hex digit is valid.
for (int i = 0; i < len; i++)
{
if (ParseCol4(color, i) == -1)
{
return false;
}
}
return true;
}
public static Color operator +(Color left, Color right)
{
left.r += right.r;
left.g += right.g;
left.b += right.b;
left.a += right.a;
return left;
}
public static Color operator -(Color left, Color right)
{
left.r -= right.r;
left.g -= right.g;
left.b -= right.b;
left.a -= right.a;
return left;
}
public static Color operator -(Color color)
{
return Colors.White - color;
}
public static Color operator *(Color color, float scale)
{
color.r *= scale;
color.g *= scale;
color.b *= scale;
color.a *= scale;
return color;
}
public static Color operator *(float scale, Color color)
{
color.r *= scale;
color.g *= scale;
color.b *= scale;
color.a *= scale;
return color;
}
public static Color operator *(Color left, Color right)
{
left.r *= right.r;
left.g *= right.g;
left.b *= right.b;
left.a *= right.a;
return left;
}
public static Color operator /(Color color, float scale)
{
color.r /= scale;
color.g /= scale;
color.b /= scale;
color.a /= scale;
return color;
}
public static Color operator /(Color left, Color right)
{
left.r /= right.r;
left.g /= right.g;
left.b /= right.b;
left.a /= right.a;
return left;
}
public static bool operator ==(Color left, Color right)
{
return left.Equals(right);
}
public static bool operator !=(Color left, Color right)
{
return !left.Equals(right);
}
public static bool operator <(Color left, Color right)
{
if (Mathf.IsEqualApprox(left.r, right.r))
{
if (Mathf.IsEqualApprox(left.g, right.g))
{
if (Mathf.IsEqualApprox(left.b, right.b))
{
return left.a < right.a;
}
return left.b < right.b;
}
return left.g < right.g;
}
return left.r < right.r;
}
public static bool operator >(Color left, Color right)
{
if (Mathf.IsEqualApprox(left.r, right.r))
{
if (Mathf.IsEqualApprox(left.g, right.g))
{
if (Mathf.IsEqualApprox(left.b, right.b))
{
return left.a > right.a;
}
return left.b > right.b;
}
return left.g > right.g;
}
return left.r > right.r;
}
public override bool Equals(object obj)
{
if (obj is Color)
{
return Equals((Color)obj);
}
return false;
}
public bool Equals(Color other)
{
return r == other.r && g == other.g && b == other.b && a == other.a;
}
///
/// Returns true if this color and `other` are approximately equal, by running
/// on each component.
///
/// The other color to compare.
/// Whether or not the colors are approximately equal.
public bool IsEqualApprox(Color other)
{
return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a);
}
public override int GetHashCode()
{
return r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode() ^ a.GetHashCode();
}
public override string ToString()
{
return String.Format("({0}, {1}, {2}, {3})", r.ToString(), g.ToString(), b.ToString(), a.ToString());
}
public string ToString(string format)
{
return String.Format("({0}, {1}, {2}, {3})", r.ToString(format), g.ToString(format), b.ToString(format), a.ToString(format));
}
}
}