254 lines
11 KiB
C#

namespace Nerd_STF.Graphics;
public struct HSVA : IColorFloat, IEquatable<HSVA>
{
public static HSVA Black => new(Angle.Zero, 0, 0);
public static HSVA Blue => new(new Angle(240), 1, 1);
public static HSVA Clear => new(Angle.Zero, 0, 0, 0);
public static HSVA Cyan => new(new Angle(180), 1, 1);
public static HSVA Gray => new(Angle.Zero, 0, 0.5f);
public static HSVA Green => new(new Angle(120), 1, 1);
public static HSVA Magenta => new(new Angle(300), 1, 1);
public static HSVA Orange => new(new Angle(30), 1, 1);
public static HSVA Purple => new(new Angle(270), 1, 1);
public static HSVA Red => new(Angle.Zero, 1, 1);
public static HSVA White => new(Angle.Zero, 0, 1);
public static HSVA Yellow => new(new Angle(60), 1, 1);
public Angle H
{
get => p_h;
set => p_h = value.Bounded;
}
public float S
{
get => p_s;
set => p_s = Mathf.Clamp(value, 0, 1);
}
public float V
{
get => p_v;
set => p_v = Mathf.Clamp(value, 0, 1);
}
public float A
{
get => p_a;
set => p_a = Mathf.Clamp(value, 0, 1);
}
private Angle p_h;
private float p_s, p_v, p_a;
public bool HasColor => p_s != 0 && p_v != 0;
public bool IsOpaque => p_a == 1;
public bool IsVisible => p_a != 0;
public HSVA() : this(Angle.Zero, 0, 0, 1) { }
public HSVA(Angle h, float s, float v) : this(h, s, v, 1) { }
public HSVA(Angle h, float s, float v, float a)
{
p_h = h.Bounded;
p_s = Mathf.Clamp(s, 0, 1);
p_v = Mathf.Clamp(v, 0, 1);
p_a = Mathf.Clamp(a, 0, 1);
}
public HSVA(Angle h, Fill<float> fill) : this(h, fill(0), fill(1), fill(2)) { }
public HSVA(Angle h, Fill<int> fill) : this(h, fill(0), fill(1), fill(2)) { }
public HSVA(float h, float s, float v) : this(new Angle(h, Angle.Type.Normalized), s, v) { }
public HSVA(float h, float s, float v, float a) : this(new Angle(h, Angle.Type.Normalized), s, v, a) { }
public HSVA(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public HSVA(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public float this[int index]
{
get => index switch
{
0 => H.Normalized,
1 => S,
2 => V,
3 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
H = new(Mathf.Clamp(value, 0, 1), Angle.Type.Normalized);
break;
case 1:
S = Mathf.Clamp(value, 0, 1);
break;
case 2:
V = Mathf.Clamp(value, 0, 1);
break;
case 3:
A = Mathf.Clamp(value, 0, 1);
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public static HSVA Average(params HSVA[] vals)
{
HSVA val = new(Angle.Zero, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static HSVA Ceiling(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Ceiling(val.H, type),
Mathf.Ceiling(val.S), Mathf.Ceiling(val.V), Mathf.Ceiling(val.A));
public static HSVA Clamp(HSVA val, HSVA min, HSVA max) =>
new(Angle.Clamp(val.H, min.H, max.H),
Mathf.Clamp(val.S, min.S, max.S),
Mathf.Clamp(val.V, min.V, max.V),
Mathf.Clamp(val.A, min.A, max.A));
public static HSVA Floor(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Floor(val.H, type),
Mathf.Floor(val.S), Mathf.Floor(val.V), Mathf.Floor(val.A));
public static HSVA Lerp(HSVA a, HSVA b, float t, bool clamp = true) =>
new(Angle.Lerp(a.H, b.H, t, clamp), Mathf.Lerp(a.S, b.S, t, clamp), Mathf.Lerp(a.V, b.V, t, clamp),
Mathf.Lerp(a.A, b.A, t, clamp));
public static HSVA Median(params HSVA[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
HSVA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static HSVA Max(params HSVA[] vals)
{
(Angle[] Hs, float[] Ss, float[] Vs, float[] As) = SplitArray(vals);
return new(Angle.Max(Hs), Mathf.Max(Ss), Mathf.Max(Vs), Mathf.Max(As));
}
public static HSVA Min(params HSVA[] vals)
{
(Angle[] Hs, float[] Ss, float[] Vs, float[] As) = SplitArray(vals);
return new(Angle.Min(Hs), Mathf.Min(Ss), Mathf.Min(Vs), Mathf.Min(As));
}
public static HSVA Round(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Round(val.H, type),
Mathf.Round(val.S), Mathf.Round(val.V), Mathf.Round(val.A));
public static (Angle[] Hs, float[] Ss, float[] Vs, float[] As) SplitArray(params HSVA[] vals)
{
Angle[] Hs = new Angle[vals.Length];
float[] Ss = new float[vals.Length], Vs = new float[vals.Length],
As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].H;
Ss[i] = vals[i].S;
Vs[i] = vals[i].V;
As[i] = vals[i].A;
}
return (Hs, Ss, Vs, As);
}
public static (float[] Hs, float[] Ss, float[] Vs, float[] As) SplitArrayNormalized(params HSVA[] vals)
{
float[] Hs = new float[vals.Length], Ss = new float[vals.Length],
Vs = new float[vals.Length], As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].H.Normalized;
Ss[i] = vals[i].S;
Vs[i] = vals[i].V;
As[i] = vals[i].A;
}
return (Hs, Ss, Vs, As);
}
public bool Equals(IColorFloat? col) => col != null && Equals(col.ToHSVA());
public bool Equals(IColorByte? col) => col != null && Equals(col.ToHSVA());
public bool Equals(HSVA col) => S == 0 && col.S == 0 || V == 0 && col.V == 0 || A == 0 && col.A == 0
|| H == col.H && S == col.S && V == col.V && 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(HSVA)) return Equals((HSVA)obj);
else if (t == typeof(CMYKA)) return Equals((IColorFloat)obj);
else if (t == typeof(RGBA)) 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() => H.GetHashCode() ^ S.GetHashCode() ^ V.GetHashCode() ^ A.GetHashCode();
public string ToString(IFormatProvider provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider)
+ " V: " + V.ToString(provider) + " A: " + A.ToString(provider);
public string ToString(string? provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider)
+ " V: " + V.ToString(provider) + " A: " + A.ToString(provider);
public override string ToString() => ToString((string?)null);
public RGBA ToRGBA()
{
float d = H.Degrees, c = V * S, x = c * (1 - Mathf.Absolute(d / 60 % 2 - 1)), m = V - c;
(float r, float g, float b) vals = (0, 0, 0);
if (d < 60) vals = (c, x, 0);
else if (d < 120) vals = (x, c, 0);
else if (d < 180) vals = (0, c, x);
else if (d < 240) vals = (0, x, c);
else if (d < 300) vals = (x, 0, c);
else if (d < 360) vals = (c, 0, x);
return new(vals.r + m, vals.g + m, vals.b + m, A);
}
public CMYKA ToCMYKA() => ToRGBA().ToCMYKA();
public HSVA ToHSVA() => this;
public RGBAByte ToRGBAByte() => ToRGBA().ToRGBAByte();
public CMYKAByte ToCMYKAByte() => ToRGBA().ToCMYKAByte();
public HSVAByte ToHSVAByte() => new(Mathf.RoundInt(H.Normalized * 255), Mathf.RoundInt(S * 255),
Mathf.RoundInt(V * 255), Mathf.RoundInt(A * 255));
public float[] ToArray() => new[] { H.Normalized, S, V, A };
public Fill<float> ToFill()
{
HSVA @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { H.Normalized, S, V, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return H.Normalized;
yield return S;
yield return V;
yield return A;
}
public object Clone() => new HSVA(H, S, V, A);
public static HSVA operator +(HSVA a, HSVA b) => new(a.H + b.H, a.S + b.S, a.V + b.V, a.A + b.A);
public static HSVA operator -(HSVA c) => new(1 - c.H.Normalized, 1 - c.S, 1 - c.V, c.A != 1 ? 1 - c.A : 1);
public static HSVA operator -(HSVA a, HSVA b) => new(a.H - b.H, a.S - b.S, a.V - b.V, a.A - b.A);
public static HSVA operator *(HSVA a, float b) => new(a.H * b, a.S * b, a.V * b, a.A * b);
public static HSVA operator /(HSVA a, float b) => new(a.H / b, a.S / b, a.V / b, a.A / b);
public static bool operator ==(HSVA a, RGBA b) => a.Equals(b);
public static bool operator !=(HSVA a, RGBA b) => !a.Equals(b);
public static bool operator ==(HSVA a, CMYKA b) => a.Equals(b);
public static bool operator !=(HSVA a, CMYKA b) => !a.Equals(b);
public static bool operator ==(HSVA a, HSVA b) => a.Equals(b);
public static bool operator !=(HSVA a, HSVA b) => !a.Equals(b);
public static bool operator ==(HSVA a, RGBAByte b) => a.Equals((IColorByte?)b);
public static bool operator !=(HSVA a, RGBAByte b) => !a.Equals((IColorByte?)b);
public static bool operator ==(HSVA a, CMYKAByte b) => a.Equals((IColorByte?)b);
public static bool operator !=(HSVA a, CMYKAByte b) => !a.Equals((IColorByte?)b);
public static bool operator ==(HSVA a, HSVAByte b) => a.Equals((IColorByte?)b);
public static bool operator !=(HSVA a, HSVAByte b) => !a.Equals((IColorByte?)b);
public static explicit operator HSVA(Float3 val) => new(val.x, val.y, val.z);
public static explicit operator HSVA(Float4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator HSVA(CMYKA val) => val.ToHSVA();
public static implicit operator HSVA(RGBA val) => val.ToHSVA();
public static implicit operator HSVA(RGBAByte val) => val.ToHSVA();
public static implicit operator HSVA(CMYKAByte val) => val.ToHSVA();
public static implicit operator HSVA(HSVAByte val) => val.ToHSVA();
public static implicit operator HSVA(Fill<float> val) => new(val);
}