259 lines
10 KiB
C#
259 lines
10 KiB
C#
namespace Nerd_STF.Graphics;
|
|
|
|
public struct RGBA : IColorFloat, IEquatable<RGBA>
|
|
{
|
|
public static RGBA Black => new(0, 0, 0);
|
|
public static RGBA Blue => new(0, 0, 1);
|
|
public static RGBA Clear => new(0, 0, 0, 0);
|
|
public static RGBA Cyan => new(0, 1, 1);
|
|
public static RGBA Gray => new(0.5f, 0.5f, 0.5f);
|
|
public static RGBA Green => new(0, 1, 0);
|
|
public static RGBA Magenta => new(1, 0, 1);
|
|
public static RGBA Orange => new(1, 0.5f, 0);
|
|
public static RGBA Purple => new(0.5f, 0, 1);
|
|
public static RGBA Red => new(1, 0, 0);
|
|
public static RGBA White => new(1, 1, 1);
|
|
public static RGBA Yellow => new(1, 1, 0);
|
|
|
|
public float R
|
|
{
|
|
get => p_r;
|
|
set => p_r = Mathf.Clamp(value, 0, 1);
|
|
}
|
|
public float G
|
|
{
|
|
get => p_g;
|
|
set => p_g = Mathf.Clamp(value, 0, 1);
|
|
}
|
|
public float B
|
|
{
|
|
get => p_b;
|
|
set => p_b = Mathf.Clamp(value, 0, 1);
|
|
}
|
|
public float A
|
|
{
|
|
get => p_a;
|
|
set => p_a = Mathf.Clamp(value, 0, 1);
|
|
}
|
|
|
|
public bool HasBlue => p_b > 0;
|
|
public bool HasGreen => p_g > 0;
|
|
public bool HasRed => p_r > 0;
|
|
public bool IsOpaque => p_a == 1;
|
|
public bool IsVisible => p_a != 0;
|
|
|
|
private float p_r, p_g, p_b, p_a;
|
|
|
|
public RGBA() : this(0, 0, 0, 1) { }
|
|
public RGBA(float all) : this(all, all, all, all) { }
|
|
public RGBA(float all, float a) : this(all, all, all, a) { }
|
|
public RGBA(float r, float g, float b) : this(r, g, b, 1) { }
|
|
public RGBA(float r, float g, float b, float a)
|
|
{
|
|
p_r = Mathf.Clamp(r, 0, 1);
|
|
p_g = Mathf.Clamp(g, 0, 1);
|
|
p_b = Mathf.Clamp(b, 0, 1);
|
|
p_a = Mathf.Clamp(a, 0, 1);
|
|
}
|
|
public RGBA(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
|
|
|
|
public float this[int index]
|
|
{
|
|
get => index switch
|
|
{
|
|
0 => R,
|
|
1 => G,
|
|
2 => B,
|
|
3 => A,
|
|
_ => throw new IndexOutOfRangeException(nameof(index)),
|
|
};
|
|
set
|
|
{
|
|
switch (index)
|
|
{
|
|
case 0:
|
|
R = value;
|
|
break;
|
|
|
|
case 1:
|
|
G = value;
|
|
break;
|
|
|
|
case 2:
|
|
B = value;
|
|
break;
|
|
|
|
case 3:
|
|
A = value;
|
|
break;
|
|
|
|
default: throw new IndexOutOfRangeException(nameof(index));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static RGBA Average(params RGBA[] vals)
|
|
{
|
|
RGBA val = new(0, 0, 0, 0);
|
|
for (int i = 0; i < vals.Length; i++) val += vals[i];
|
|
return val / vals.Length;
|
|
}
|
|
public static RGBA Ceiling(RGBA val) =>
|
|
new(Mathf.Ceiling(val.R), Mathf.Ceiling(val.G), Mathf.Ceiling(val.B), Mathf.Ceiling(val.A));
|
|
public static RGBA Clamp(RGBA val, RGBA min, RGBA max) =>
|
|
new(Mathf.Clamp(val.R, min.R, max.R),
|
|
Mathf.Clamp(val.G, min.G, max.G),
|
|
Mathf.Clamp(val.B, min.B, max.B),
|
|
Mathf.Clamp(val.A, min.A, max.A));
|
|
public static RGBA Floor(RGBA val) =>
|
|
new(Mathf.Floor(val.R), Mathf.Floor(val.G), Mathf.Floor(val.B), Mathf.Floor(val.A));
|
|
public static RGBA Lerp(RGBA a, RGBA b, float t, bool clamp = true) =>
|
|
new(Mathf.Lerp(a.R, b.R, t, clamp), Mathf.Lerp(a.G, b.G, t, clamp), Mathf.Lerp(a.B, b.B, t, clamp),
|
|
Mathf.Lerp(a.A, b.A, t, clamp));
|
|
public static RGBA LerpSquared(RGBA a, RGBA b, float t, bool clamp = true)
|
|
{
|
|
RGBA val = Lerp(a * a, b * b, t, clamp);
|
|
float R = Mathf.Sqrt(val.R), G = Mathf.Sqrt(val.G), B = Mathf.Sqrt(val.B), A = Mathf.Sqrt(val.A);
|
|
return new(R, G, B, A);
|
|
}
|
|
public static RGBA Median(params RGBA[] vals)
|
|
{
|
|
float index = Mathf.Average(0, vals.Length - 1);
|
|
RGBA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
|
|
return Average(valA, valB);
|
|
}
|
|
public static RGBA Max(params RGBA[] vals)
|
|
{
|
|
(float[] Rs, float[] Gs, float[] Bs, float[] As) = SplitArray(vals);
|
|
return new(Mathf.Max(Rs), Mathf.Max(Gs), Mathf.Max(Bs), Mathf.Max(As));
|
|
}
|
|
public static RGBA Min(params RGBA[] vals)
|
|
{
|
|
(float[] Rs, float[] Gs, float[] Bs, float[] As) = SplitArray(vals);
|
|
return new(Mathf.Min(Rs), Mathf.Min(Gs), Mathf.Min(Bs), Mathf.Min(As));
|
|
}
|
|
public static RGBA Round(RGBA val) =>
|
|
new(Mathf.Round(val.R), Mathf.Round(val.G), Mathf.Round(val.B), Mathf.Round(val.A));
|
|
|
|
public static (float[] Rs, float[] Gs, float[] Bs, float[] As) SplitArray(params RGBA[] vals)
|
|
{
|
|
float[] Rs = new float[vals.Length], Gs = new float[vals.Length],
|
|
Bs = new float[vals.Length], As = new float[vals.Length];
|
|
for (int i = 0; i < vals.Length; i++)
|
|
{
|
|
Rs[i] = vals[i].R;
|
|
Gs[i] = vals[i].G;
|
|
Bs[i] = vals[i].B;
|
|
As[i] = vals[i].A;
|
|
}
|
|
return (Rs, Gs, Bs, As);
|
|
}
|
|
|
|
public bool Equals(IColorFloat? col) => col != null && Equals(col.ToRGBA());
|
|
public bool Equals(IColorByte? col) => col != null && Equals(col.ToRGBA());
|
|
public bool Equals(RGBA col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A;
|
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
{
|
|
if (obj == null) return base.Equals(obj);
|
|
Type t = obj.GetType();
|
|
if (t == typeof(RGBA)) return Equals((RGBA)obj);
|
|
else if (t == typeof(CMYKA)) return Equals((IColorFloat)obj);
|
|
else if (t == typeof(HSVA)) return Equals((IColorFloat)obj);
|
|
else if (t == typeof(IColorFloat)) return Equals((IColorFloat)obj);
|
|
else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj);
|
|
else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj);
|
|
else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj);
|
|
else if (t == typeof(IColorByte)) return Equals((IColorByte)obj);
|
|
|
|
return base.Equals(obj);
|
|
}
|
|
public override int GetHashCode() => R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode();
|
|
public string ToString(IFormatProvider provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) +
|
|
" B: " + B.ToString(provider) + " A: " + A.ToString(provider);
|
|
public string ToString(string? provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) +
|
|
" B: " + B.ToString(provider) + " A: " + A.ToString(provider);
|
|
public override string ToString() => ToString((string?)null);
|
|
|
|
public RGBA ToRGBA() => this;
|
|
public CMYKA ToCMYKA()
|
|
{
|
|
float v = Mathf.Max(R, G, B), k = 1 - v;
|
|
if (v == 1) return new(0, 0, 0, 1, A);
|
|
|
|
float kInv = 1 / v, c = 1 - R - k, m = 1 - G - k, y = 1 - B - k;
|
|
return new(c * kInv, m * kInv, y * kInv, k, A);
|
|
}
|
|
public HSVA ToHSVA()
|
|
{
|
|
float cMax = Mathf.Max(R, G, B), cMin = Mathf.Min(R, G, B), delta = cMax - cMin;
|
|
Angle hue = Angle.Zero;
|
|
if (delta != 0)
|
|
{
|
|
float val = 0;
|
|
if (cMax == R) val = (G - B) / delta % 6;
|
|
else if (cMax == G) val = (B - R) / delta + 2;
|
|
else if (cMax == B) val = (R - G) / delta + 4;
|
|
hue = new(val * 60);
|
|
}
|
|
|
|
float sat = cMax == 0 ? 0 : delta / cMax;
|
|
|
|
return new(hue, sat, cMax, A);
|
|
}
|
|
|
|
public RGBAByte ToRGBAByte() => new(Mathf.RoundInt(R * 255), Mathf.RoundInt(G * 255), Mathf.RoundInt(B * 255),
|
|
Mathf.RoundInt(A * 255));
|
|
public CMYKAByte ToCMYKAByte() => ToCMYKA().ToCMYKAByte();
|
|
public HSVAByte ToHSVAByte() => ToHSVA().ToHSVAByte();
|
|
|
|
public float[] ToArray() => new[] { R, G, B, A };
|
|
public Fill<float> ToFill()
|
|
{
|
|
RGBA @this = this;
|
|
return i => @this[i];
|
|
}
|
|
public List<float> ToList() => new() { R, G, B, A };
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
public IEnumerator<float> GetEnumerator()
|
|
{
|
|
yield return R;
|
|
yield return G;
|
|
yield return B;
|
|
yield return A;
|
|
}
|
|
|
|
public object Clone() => new RGBA(R, G, B, A);
|
|
|
|
public Vector3d ToVector() => ((Float3)this).ToVector();
|
|
|
|
public static RGBA operator +(RGBA a, RGBA b) => new(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A);
|
|
public static RGBA operator -(RGBA c) => new(1 - c.R, 1 - c.G, 1 - c.B, c.A != 1 ? 1 - c.A : 1);
|
|
public static RGBA operator -(RGBA a, RGBA b) => new(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A);
|
|
public static RGBA operator *(RGBA a, RGBA b) => new(a.R * b.R, a.G * b.G, a.B * b.B, a.A * b.A);
|
|
public static RGBA operator *(RGBA a, float b) => new(a.R * b, a.G * b, a.B * b, a.A * b);
|
|
public static RGBA operator /(RGBA a, RGBA b) => new(a.R / b.R, a.G / b.G, a.B / b.B, a.A / b.A);
|
|
public static RGBA operator /(RGBA a, float b) => new(a.R / b, a.G / b, a.B / b, a.A / b);
|
|
public static bool operator ==(RGBA a, RGBA b) => a.Equals(b);
|
|
public static bool operator !=(RGBA a, RGBA b) => !a.Equals(b);
|
|
public static bool operator ==(RGBA a, CMYKA b) => a.Equals(b);
|
|
public static bool operator !=(RGBA a, CMYKA b) => !a.Equals(b);
|
|
public static bool operator ==(RGBA a, HSVA b) => a.Equals(b);
|
|
public static bool operator !=(RGBA a, HSVA b) => !a.Equals(b);
|
|
public static bool operator ==(RGBA a, RGBAByte b) => a.Equals((IColorByte?)b);
|
|
public static bool operator !=(RGBA a, RGBAByte b) => !a.Equals((IColorByte?)b);
|
|
public static bool operator ==(RGBA a, CMYKAByte b) => a.Equals((IColorByte?)b);
|
|
public static bool operator !=(RGBA a, CMYKAByte b) => !a.Equals((IColorByte?)b);
|
|
public static bool operator ==(RGBA a, HSVAByte b) => a.Equals((IColorByte?)b);
|
|
public static bool operator !=(RGBA a, HSVAByte b) => !a.Equals((IColorByte?)b);
|
|
|
|
public static implicit operator RGBA(Float3 val) => new(val.x, val.y, val.z);
|
|
public static implicit operator RGBA(Float4 val) => new(val.x, val.y, val.z, val.w);
|
|
public static implicit operator RGBA(CMYKA val) => val.ToRGBA();
|
|
public static implicit operator RGBA(HSVA val) => val.ToRGBA();
|
|
public static implicit operator RGBA(RGBAByte val) => val.ToRGBA();
|
|
public static implicit operator RGBA(CMYKAByte val) => val.ToRGBA();
|
|
public static implicit operator RGBA(HSVAByte val) => val.ToRGBA();
|
|
public static implicit operator RGBA(Fill<float> val) => new(val);
|
|
}
|