Tested HSV and untested CMYK types. This beta will concern colors.
This commit is contained in:
parent
866326863b
commit
e070aa097f
365
Nerd_STF/Graphics/ColorCMYK.cs
Normal file
365
Nerd_STF/Graphics/ColorCMYK.cs
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
using Nerd_STF.Mathematics;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nerd_STF.Graphics
|
||||||
|
{
|
||||||
|
public struct ColorCMYK : IColor<ColorCMYK>,
|
||||||
|
IEnumerable<double>
|
||||||
|
#if CS11_OR_GREATER
|
||||||
|
,IFromTuple<ColorCMYK, (double, double, double, double)>,
|
||||||
|
IFromTuple<ColorCMYK, (double, double, double, double, double)>,
|
||||||
|
ISplittable<ColorCMYK, (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As)>
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
public static int ChannelCount => 5;
|
||||||
|
|
||||||
|
public static ColorCMYK Black => new ColorCMYK(0 , 0 , 0 , 1 , 1);
|
||||||
|
public static ColorCMYK Blue => new ColorCMYK(1 , 1 , 0 , 0 , 1);
|
||||||
|
public static ColorCMYK Clear => new ColorCMYK(0 , 0 , 0 , 0 , 0);
|
||||||
|
public static ColorCMYK Cyan => new ColorCMYK(1 , 0 , 0 , 0 , 1);
|
||||||
|
public static ColorCMYK Gray => new ColorCMYK(0 , 0 , 0 , 0.5, 1);
|
||||||
|
public static ColorCMYK Green => new ColorCMYK(1 , 0 , 1 , 0 , 1);
|
||||||
|
public static ColorCMYK Magenta => new ColorCMYK(0 , 1 , 0 , 0 , 1);
|
||||||
|
public static ColorCMYK Orange => new ColorCMYK(0 , 0.5, 1 , 0 , 1);
|
||||||
|
public static ColorCMYK Purple => new ColorCMYK(0.5, 1 , 0 , 0 , 1);
|
||||||
|
public static ColorCMYK Red => new ColorCMYK(0 , 1 , 1 , 0 , 1);
|
||||||
|
public static ColorCMYK White => new ColorCMYK(0 , 0 , 0 , 0 , 1);
|
||||||
|
public static ColorCMYK Yellow => new ColorCMYK(0 , 0 , 1 , 0 , 1);
|
||||||
|
|
||||||
|
public double c, m, y, k, a;
|
||||||
|
|
||||||
|
public ColorCMYK(double cyan, double magenta, double yellow, double black)
|
||||||
|
{
|
||||||
|
c = cyan;
|
||||||
|
m = magenta;
|
||||||
|
y = yellow;
|
||||||
|
k = black;
|
||||||
|
a = 1;
|
||||||
|
}
|
||||||
|
public ColorCMYK(double cyan, double magenta, double yellow, double black, double alpha)
|
||||||
|
{
|
||||||
|
c = cyan;
|
||||||
|
m = magenta;
|
||||||
|
y = yellow;
|
||||||
|
k = black;
|
||||||
|
a = alpha;
|
||||||
|
}
|
||||||
|
public ColorCMYK(IEnumerable<double> nums)
|
||||||
|
{
|
||||||
|
c = 0;
|
||||||
|
m = 0;
|
||||||
|
y = 0;
|
||||||
|
k = 0;
|
||||||
|
a = 1;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (double item in nums)
|
||||||
|
{
|
||||||
|
this[index] = item;
|
||||||
|
index++;
|
||||||
|
if (index == 5) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ColorCMYK(Fill<double> fill)
|
||||||
|
{
|
||||||
|
c = fill(0);
|
||||||
|
m = fill(1);
|
||||||
|
y = fill(2);
|
||||||
|
k = fill(3);
|
||||||
|
a = fill(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: return c;
|
||||||
|
case 1: return m;
|
||||||
|
case 2: return y;
|
||||||
|
case 3: return k;
|
||||||
|
case 4: return a;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: c = value; break;
|
||||||
|
case 1: m = value; break;
|
||||||
|
case 2: y = value; break;
|
||||||
|
case 3: k = value; break;
|
||||||
|
case 4: a = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ListTuple<double> this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double[] items = new double[key.Length];
|
||||||
|
for (int i = 0; i < key.Length; i++)
|
||||||
|
{
|
||||||
|
char c = key[i];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'c': items[i] = c; break;
|
||||||
|
case 'm': items[i] = m; break;
|
||||||
|
case 'y': items[i] = y; break;
|
||||||
|
case 'k': items[i] = k; break;
|
||||||
|
case 'a': items[i] = a; break;
|
||||||
|
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ListTuple<double>(items);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
IEnumerator<double> stepper = value.GetEnumerator();
|
||||||
|
for (int i = 0; i < key.Length; i++)
|
||||||
|
{
|
||||||
|
char c = key[i];
|
||||||
|
stepper.MoveNext();
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'c': this.c = stepper.Current; break;
|
||||||
|
case 'm': m = stepper.Current; break;
|
||||||
|
case 'y': y = stepper.Current; break;
|
||||||
|
case 'k': k = stepper.Current; break;
|
||||||
|
case 'a': a = stepper.Current; break;
|
||||||
|
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ColorCMYK Average(double gamma, IEnumerable<ColorCMYK> colors)
|
||||||
|
{
|
||||||
|
double avgC = 0, avgM = 0, avgY = 0, avgK = 0, avgA = 0;
|
||||||
|
int count = 0;
|
||||||
|
foreach (ColorCMYK color in colors)
|
||||||
|
{
|
||||||
|
double correctC = MathE.Pow(color.c, gamma),
|
||||||
|
correctM = MathE.Pow(color.m, gamma),
|
||||||
|
correctY = MathE.Pow(color.y, gamma),
|
||||||
|
correctK = MathE.Pow(color.k, gamma);
|
||||||
|
// Gamma doesn't apply to the alpha channel.
|
||||||
|
|
||||||
|
avgC += correctC;
|
||||||
|
avgM += correctM;
|
||||||
|
avgY += correctY;
|
||||||
|
avgK += correctK;
|
||||||
|
avgA += color.a;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
avgC /= count;
|
||||||
|
avgM /= count;
|
||||||
|
avgY /= count;
|
||||||
|
avgK /= count;
|
||||||
|
avgA /= count;
|
||||||
|
double invGamma = 1 / gamma;
|
||||||
|
return new ColorCMYK(MathE.Pow(avgC, invGamma),
|
||||||
|
MathE.Pow(avgM, invGamma),
|
||||||
|
MathE.Pow(avgY, invGamma),
|
||||||
|
MathE.Pow(avgK, invGamma),
|
||||||
|
avgA);
|
||||||
|
}
|
||||||
|
#if CS11_OR_GREATER
|
||||||
|
static ColorCMYK IColor<ColorCMYK>.Average(IEnumerable<ColorCMYK> colors) => Average(1, colors);
|
||||||
|
#endif
|
||||||
|
public static ColorCMYK Clamp(ColorCMYK color, ColorCMYK min, ColorCMYK max) =>
|
||||||
|
new ColorCMYK(MathE.Clamp(color.c, min.c, max.c),
|
||||||
|
MathE.Clamp(color.m, min.m, max.m),
|
||||||
|
MathE.Clamp(color.y, min.y, max.y),
|
||||||
|
MathE.Clamp(color.k, min.k, max.k),
|
||||||
|
MathE.Clamp(color.a, min.a, max.a));
|
||||||
|
public static ColorCMYK Lerp(double gamma, ColorCMYK a, ColorCMYK b, double t, bool clamp = true)
|
||||||
|
{
|
||||||
|
double aCorrectedC = MathE.Pow(a.c, gamma), bCorrectedC = MathE.Pow(b.c, gamma),
|
||||||
|
aCorrectedM = MathE.Pow(a.m, gamma), bCorrectedM = MathE.Pow(b.m, gamma),
|
||||||
|
aCorrectedY = MathE.Pow(a.y, gamma), bCorrectedY = MathE.Pow(b.y, gamma),
|
||||||
|
aCorrectedK = MathE.Pow(a.k, gamma), bCorrectedK = MathE.Pow(b.k, gamma);
|
||||||
|
|
||||||
|
double newC = MathE.Lerp(aCorrectedC, bCorrectedC, t, clamp),
|
||||||
|
newM = MathE.Lerp(aCorrectedM, bCorrectedM, t, clamp),
|
||||||
|
newY = MathE.Lerp(aCorrectedY, bCorrectedY, t, clamp),
|
||||||
|
newK = MathE.Lerp(aCorrectedK, bCorrectedK, t, clamp),
|
||||||
|
newA = MathE.Lerp(a.a, b.a, t, clamp);
|
||||||
|
|
||||||
|
double invGamma = 1 / gamma;
|
||||||
|
return new ColorCMYK(MathE.Pow(newC, invGamma),
|
||||||
|
MathE.Pow(newM, invGamma),
|
||||||
|
MathE.Pow(newY, invGamma),
|
||||||
|
MathE.Pow(newK, invGamma),
|
||||||
|
newA);
|
||||||
|
}
|
||||||
|
#if CS11_OR_GREATER
|
||||||
|
static ColorCMYK IInterpolable<ColorCMYK>.Lerp(ColorCMYK a, ColorCMYK b, double t, bool clamp) => Lerp(1, a, b, t, clamp);
|
||||||
|
#endif
|
||||||
|
public static ColorCMYK Product(IEnumerable<ColorCMYK> colors)
|
||||||
|
{
|
||||||
|
bool any = false;
|
||||||
|
ColorCMYK result = new ColorCMYK(1, 1, 1, 1, 1);
|
||||||
|
foreach (ColorCMYK color in colors)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
result *= color;
|
||||||
|
}
|
||||||
|
return any ? result : Black;
|
||||||
|
}
|
||||||
|
public static ColorCMYK Sum(IEnumerable<ColorCMYK> colors)
|
||||||
|
{
|
||||||
|
bool any = false;
|
||||||
|
ColorCMYK result = new ColorCMYK(0, 0, 0, 0);
|
||||||
|
foreach (ColorCMYK color in colors)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
result += color;
|
||||||
|
}
|
||||||
|
return any ? result : Black;
|
||||||
|
}
|
||||||
|
public static (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As) SplitArray(IEnumerable<ColorCMYK> colors)
|
||||||
|
{
|
||||||
|
int count = colors.Count();
|
||||||
|
double[] Cs = new double[count], Ms = new double[count], Ys = new double[count], Ks = new double[count], As = new double[count];
|
||||||
|
int index = 0;
|
||||||
|
foreach (ColorCMYK c in colors)
|
||||||
|
{
|
||||||
|
Cs[index] = c.c;
|
||||||
|
Ms[index] = c.m;
|
||||||
|
Ys[index] = c.y;
|
||||||
|
Ks[index] = c.k;
|
||||||
|
As[index] = c.a;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return (Cs, Ms, Ys, Ks, As);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||||
|
{
|
||||||
|
{ ColorChannel.Cyan, c },
|
||||||
|
{ ColorChannel.Magenta, m },
|
||||||
|
{ ColorChannel.Yellow, y },
|
||||||
|
{ ColorChannel.Key, k },
|
||||||
|
{ ColorChannel.Alpha, a }
|
||||||
|
};
|
||||||
|
|
||||||
|
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||||
|
{
|
||||||
|
Type type = typeof(TColor);
|
||||||
|
if (type == typeof(ColorRGB)) return (TColor)(object)AsRgb();
|
||||||
|
else if (type == typeof(ColorHSV)) return (TColor)(object)AsHsv();
|
||||||
|
else if (type == typeof(ColorCMYK)) return (TColor)(object)this;
|
||||||
|
else throw new InvalidCastException();
|
||||||
|
}
|
||||||
|
public ColorRGB AsRgb()
|
||||||
|
{
|
||||||
|
// Thanks https://www.rapidtables.com/convert/color/cmyk-to-rgb.html
|
||||||
|
double diffK = 1 - k;
|
||||||
|
return new ColorRGB((1 - c) * diffK,
|
||||||
|
(1 - m) * diffK,
|
||||||
|
(1 - y) * diffK);
|
||||||
|
}
|
||||||
|
public ColorHSV AsHsv()
|
||||||
|
{
|
||||||
|
// Inlined version of AsRgb().AsHsv()
|
||||||
|
double diffK = 1 - k;
|
||||||
|
double r = (1 - c) * diffK, g = (1 - m) * diffK, b = (1 - y) * diffK;
|
||||||
|
double[] items = new double[] { r, g, b };
|
||||||
|
double cMax = MathE.Max(items), cMin = MathE.Min(items), delta = cMax - cMin;
|
||||||
|
Angle h;
|
||||||
|
|
||||||
|
if (delta == 0) h = Angle.Zero;
|
||||||
|
else if (cMax == r) h = Angle.FromDegrees(60 * MathE.ModAbs((g - b) / delta, 6));
|
||||||
|
else if (cMax == g) h = Angle.FromDegrees(60 * ((b - r) / delta + 2));
|
||||||
|
else if (cMax == b) h = Angle.FromDegrees(60 * ((r - g) / delta + 4));
|
||||||
|
else h = Angle.Zero;
|
||||||
|
|
||||||
|
double s = cMax == 0 ? 0 : delta / cMax;
|
||||||
|
return new ColorHSV(h, s, cMax);
|
||||||
|
}
|
||||||
|
public ColorCMYK AsCmyk() => this;
|
||||||
|
|
||||||
|
public string HexCode() => AsRgb().HexCode();
|
||||||
|
|
||||||
|
public IEnumerator<double> GetEnumerator()
|
||||||
|
{
|
||||||
|
yield return c;
|
||||||
|
yield return m;
|
||||||
|
yield return y;
|
||||||
|
yield return k;
|
||||||
|
yield return a;
|
||||||
|
}
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public void Deconstruct(out double c, out double m, out double y, out double k)
|
||||||
|
{
|
||||||
|
c = this.c;
|
||||||
|
m = this.m;
|
||||||
|
y = this.y;
|
||||||
|
k = this.k;
|
||||||
|
}
|
||||||
|
public void Deconstruct(out double c, out double m, out double y, out double k, out double a)
|
||||||
|
{
|
||||||
|
c = this.c;
|
||||||
|
m = this.m;
|
||||||
|
y = this.y;
|
||||||
|
k = this.k;
|
||||||
|
a = this.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ColorCMYK other)
|
||||||
|
{
|
||||||
|
if (a <= 0 && other.a <= 0) return true;
|
||||||
|
else if (k >= 1 && other.k >= 1) return true;
|
||||||
|
else return c == other.c && m == other.m && y == other.y && k == other.k && a == other.a;
|
||||||
|
}
|
||||||
|
public bool Equals(IColor other) => Equals(other.AsCmyk());
|
||||||
|
#if CS8_OR_GREATER
|
||||||
|
public override bool Equals(object? other)
|
||||||
|
#else
|
||||||
|
public override bool Equals(object other)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (other is IColor color) return Equals(color.AsRgb());
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
public override int GetHashCode() => base.GetHashCode();
|
||||||
|
public override string ToString() => $"{{ c={c:0.00}, m={m:0.00}, y={y:0.00}, k={k:0.00}, a={a:0.00} }}";
|
||||||
|
|
||||||
|
public double[] ToArray() => new double[] { c, m, y, k, a };
|
||||||
|
public Fill<double> ToFill()
|
||||||
|
{
|
||||||
|
ColorCMYK copy = this;
|
||||||
|
return i => copy[i];
|
||||||
|
}
|
||||||
|
public List<double> ToList() => new List<double> { c, m, y, k, a };
|
||||||
|
|
||||||
|
public static ColorCMYK operator +(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c + b.c, a.m + b.m, a.y + b.y, a.k + b.k, 1 - (1 - a.a) * (1 - b.a));
|
||||||
|
public static ColorCMYK operator *(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c * b.c, a.m * b.m, a.y * b.y, a.k * b.k, a.a * b.a);
|
||||||
|
public static ColorCMYK operator *(ColorCMYK a, double b) => new ColorCMYK(a.c * b, a.m * b, a.y * b, a.k * b, a.a);
|
||||||
|
public static bool operator ==(ColorCMYK a, IColor b) => a.Equals(b.AsCmyk());
|
||||||
|
public static bool operator !=(ColorCMYK a, IColor b) => !a.Equals(b.AsCmyk());
|
||||||
|
public static bool operator ==(ColorCMYK a, ColorCMYK b) => a.Equals(b);
|
||||||
|
public static bool operator !=(ColorCMYK a, ColorCMYK b) => !a.Equals(b);
|
||||||
|
|
||||||
|
public static implicit operator ColorCMYK(ListTuple<double> tuple)
|
||||||
|
{
|
||||||
|
if (tuple.Length == 4) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||||
|
else if (tuple.Length == 5) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3], tuple[4]);
|
||||||
|
else throw new InvalidCastException();
|
||||||
|
}
|
||||||
|
public static implicit operator ColorCMYK((double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||||
|
public static implicit operator ColorCMYK((double, double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
||||||
|
|
||||||
|
public static implicit operator ListTuple<double>(ColorCMYK color) => new ListTuple<double>(color.c, color.m, color.y, color.k, color.a);
|
||||||
|
public static implicit operator ValueTuple<double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k);
|
||||||
|
public static implicit operator ValueTuple<double, double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k, color.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,13 @@
|
|||||||
Red,
|
Red,
|
||||||
Green,
|
Green,
|
||||||
Blue,
|
Blue,
|
||||||
Alpha
|
Alpha,
|
||||||
|
Hue,
|
||||||
|
Saturation,
|
||||||
|
Value,
|
||||||
|
Cyan,
|
||||||
|
Magenta,
|
||||||
|
Yellow,
|
||||||
|
Key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
350
Nerd_STF/Graphics/ColorHSV.cs
Normal file
350
Nerd_STF/Graphics/ColorHSV.cs
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
using Nerd_STF.Helpers;
|
||||||
|
using Nerd_STF.Mathematics;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nerd_STF.Graphics
|
||||||
|
{
|
||||||
|
public struct ColorHSV : IColor<ColorHSV>,
|
||||||
|
IEnumerable<double>
|
||||||
|
#if CS11_OR_GREATER
|
||||||
|
,IFromTuple<ColorHSV, (Angle, double, double)>,
|
||||||
|
IFromTuple<ColorHSV, (double, double, double)>,
|
||||||
|
IFromTuple<ColorHSV, (Angle, double, double, double)>,
|
||||||
|
IFromTuple<ColorHSV, (double, double, double, double)>,
|
||||||
|
ISplittable<ColorHSV, (Angle[] Hs, double[] Ss, double[] Vs, double[] As)>
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
public static int ChannelCount => 4;
|
||||||
|
|
||||||
|
public static ColorHSV Black => new ColorHSV(Angle.FromDegrees( 0), 0, 0 , 1);
|
||||||
|
public static ColorHSV Blue => new ColorHSV(Angle.FromDegrees(240), 1, 1 , 1);
|
||||||
|
public static ColorHSV Clear => new ColorHSV(Angle.FromDegrees( 0), 0, 0 , 0);
|
||||||
|
public static ColorHSV Cyan => new ColorHSV(Angle.FromDegrees(180), 1, 1 , 1);
|
||||||
|
public static ColorHSV Gray => new ColorHSV(Angle.FromDegrees( 0), 0, 0.5, 1);
|
||||||
|
public static ColorHSV Green => new ColorHSV(Angle.FromDegrees(120), 1, 1 , 1);
|
||||||
|
public static ColorHSV Magenta => new ColorHSV(Angle.FromDegrees(300), 1, 1 , 1);
|
||||||
|
public static ColorHSV Orange => new ColorHSV(Angle.FromDegrees( 30), 1, 1 , 1);
|
||||||
|
public static ColorHSV Purple => new ColorHSV(Angle.FromDegrees(270), 1, 1 , 1);
|
||||||
|
public static ColorHSV Red => new ColorHSV(Angle.FromDegrees( 0), 1, 1 , 1);
|
||||||
|
public static ColorHSV White => new ColorHSV(Angle.FromDegrees( 0), 0, 1 , 1);
|
||||||
|
public static ColorHSV Yellow => new ColorHSV(Angle.FromDegrees( 60), 0, 1 , 1);
|
||||||
|
|
||||||
|
public Angle h;
|
||||||
|
public double s, v, a;
|
||||||
|
|
||||||
|
public ColorHSV(Angle hue)
|
||||||
|
{
|
||||||
|
h = hue.Normalized;
|
||||||
|
s = 1;
|
||||||
|
v = 1;
|
||||||
|
a = 1;
|
||||||
|
}
|
||||||
|
public ColorHSV(double hue)
|
||||||
|
{
|
||||||
|
h = Angle.FromRevolutions(hue);
|
||||||
|
s = 1;
|
||||||
|
v = 1;
|
||||||
|
a = 1;
|
||||||
|
}
|
||||||
|
public ColorHSV(Angle hue, double saturation, double value)
|
||||||
|
{
|
||||||
|
h = hue.Normalized;
|
||||||
|
s = saturation;
|
||||||
|
v = value;
|
||||||
|
a = 1;
|
||||||
|
}
|
||||||
|
public ColorHSV(double hue, double saturation, double value)
|
||||||
|
{
|
||||||
|
h = Angle.FromRevolutions(hue);
|
||||||
|
s = saturation;
|
||||||
|
v = value;
|
||||||
|
a = 1;
|
||||||
|
}
|
||||||
|
public ColorHSV(Angle hue, double saturation, double value, double alpha)
|
||||||
|
{
|
||||||
|
h = hue.Normalized;
|
||||||
|
s = saturation;
|
||||||
|
v = value;
|
||||||
|
a = alpha;
|
||||||
|
}
|
||||||
|
public ColorHSV(double hue, double saturation, double value, double alpha)
|
||||||
|
{
|
||||||
|
h = Angle.FromRevolutions(hue);
|
||||||
|
s = saturation;
|
||||||
|
v = value;
|
||||||
|
a = alpha;
|
||||||
|
}
|
||||||
|
public ColorHSV(IEnumerable<double> nums)
|
||||||
|
{
|
||||||
|
h = Angle.Zero;
|
||||||
|
s = 0;
|
||||||
|
v = 0;
|
||||||
|
a = 1;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (double item in nums)
|
||||||
|
{
|
||||||
|
this[index] = item;
|
||||||
|
index++;
|
||||||
|
if (index == 4) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ColorHSV(Fill<double> fill)
|
||||||
|
{
|
||||||
|
h = Angle.FromRevolutions(fill(0) % 1);
|
||||||
|
s = fill(1);
|
||||||
|
v = fill(2);
|
||||||
|
a = fill(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: return h.Revolutions;
|
||||||
|
case 1: return s;
|
||||||
|
case 2: return v;
|
||||||
|
case 3: return a;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: h = Angle.FromRevolutions(value); break;
|
||||||
|
case 1: s = value; break;
|
||||||
|
case 2: v = value; break;
|
||||||
|
case 3: a = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ListTuple<double> this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double[] items = new double[key.Length];
|
||||||
|
for (int i = 0; i < key.Length; i++)
|
||||||
|
{
|
||||||
|
char c = key[i];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'h': items[i] = h.Revolutions; break;
|
||||||
|
case 'H': items[i] = h.Degrees; break;
|
||||||
|
case 's': items[i] = s; break;
|
||||||
|
case 'v': items[i] = v; break;
|
||||||
|
case 'a': items[i] = a; break;
|
||||||
|
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ListTuple<double>(items);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
IEnumerator<double> stepper = value.GetEnumerator();
|
||||||
|
for (int i = 0; i < key.Length; i++)
|
||||||
|
{
|
||||||
|
char c = key[i];
|
||||||
|
stepper.MoveNext();
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'h': h = Angle.FromRevolutions(stepper.Current); break;
|
||||||
|
case 'H': h = Angle.FromDegrees(stepper.Current); break;
|
||||||
|
case 's': s = stepper.Current; break;
|
||||||
|
case 'v': v = stepper.Current; break;
|
||||||
|
case 'a': a = stepper.Current; break;
|
||||||
|
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ColorHSV Average(IEnumerable<ColorHSV> colors)
|
||||||
|
{
|
||||||
|
Angle avgH = Angle.Zero;
|
||||||
|
double avgS = 0, avgV = 0, avgA = 0;
|
||||||
|
int count = 0;
|
||||||
|
foreach (ColorHSV color in colors)
|
||||||
|
{
|
||||||
|
avgH += color.h;
|
||||||
|
avgS += color.s;
|
||||||
|
avgV += color.v;
|
||||||
|
avgA += color.a;
|
||||||
|
}
|
||||||
|
return new ColorHSV(avgH / count, avgS / count, avgV / count, avgA / count);
|
||||||
|
}
|
||||||
|
public static ColorHSV Clamp(ColorHSV color, ColorHSV min, ColorHSV max) =>
|
||||||
|
new ColorHSV(Angle.Clamp(color.h, min.h, max.h),
|
||||||
|
MathE.Clamp(color.s, min.s, max.s),
|
||||||
|
MathE.Clamp(color.v, min.v, max.v),
|
||||||
|
MathE.Clamp(color.a, min.a, max.a));
|
||||||
|
public static ColorHSV Lerp(ColorHSV a, ColorHSV b, double t, bool clamp = true) =>
|
||||||
|
new ColorHSV(Angle.Lerp(a.h, b.h, t, clamp),
|
||||||
|
MathE.Lerp(a.s, b.s, t, clamp),
|
||||||
|
MathE.Lerp(a.v, b.v, t, clamp),
|
||||||
|
MathE.Lerp(a.a, b.a, t, clamp));
|
||||||
|
public static ColorHSV Product(IEnumerable<ColorHSV> colors)
|
||||||
|
{
|
||||||
|
bool any = false;
|
||||||
|
ColorHSV result = new ColorHSV(Angle.Full, 1, 1, 1);
|
||||||
|
foreach (ColorHSV color in colors)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
result *= color;
|
||||||
|
}
|
||||||
|
return any ? result : Black;
|
||||||
|
}
|
||||||
|
public static ColorHSV Sum(IEnumerable<ColorHSV> colors)
|
||||||
|
{
|
||||||
|
bool any = false;
|
||||||
|
ColorHSV result = new ColorHSV(Angle.Zero, 0, 0, 0);
|
||||||
|
foreach (ColorHSV color in colors)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
result += color;
|
||||||
|
}
|
||||||
|
return any ? result : Black;
|
||||||
|
}
|
||||||
|
public static (Angle[] Hs, double[] Ss, double[] Vs, double[] As) SplitArray(IEnumerable<ColorHSV> colors)
|
||||||
|
{
|
||||||
|
int count = colors.Count();
|
||||||
|
Angle[] Hs = new Angle[count];
|
||||||
|
double[] Ss = new double[count], Vs = new double[count], As = new double[count];
|
||||||
|
int index = 0;
|
||||||
|
foreach (ColorHSV c in colors)
|
||||||
|
{
|
||||||
|
Hs[index] = c.h;
|
||||||
|
Ss[index] = c.s;
|
||||||
|
Vs[index] = c.v;
|
||||||
|
As[index] = c.a;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return (Hs, Ss, Vs, As);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||||
|
{
|
||||||
|
{ ColorChannel.Hue, h.Revolutions },
|
||||||
|
{ ColorChannel.Saturation, s },
|
||||||
|
{ ColorChannel.Value, v },
|
||||||
|
{ ColorChannel.Alpha, a }
|
||||||
|
};
|
||||||
|
|
||||||
|
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||||
|
{
|
||||||
|
Type type = typeof(TColor);
|
||||||
|
if (type == typeof(ColorRGB)) return (TColor)(object)AsRgb();
|
||||||
|
else if (type == typeof(ColorHSV)) return (TColor)(object)this;
|
||||||
|
else if (type == typeof(ColorCMYK)) return (TColor)(object)AsCmyk();
|
||||||
|
else throw new InvalidCastException();
|
||||||
|
}
|
||||||
|
public ColorRGB AsRgb()
|
||||||
|
{
|
||||||
|
// Thanks https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
||||||
|
double H = h.Normalized.Degrees,
|
||||||
|
c = v * s,
|
||||||
|
x = c * (1 - MathE.Abs(MathE.ModAbs(H / 60, 2) - 1)),
|
||||||
|
m = v - c;
|
||||||
|
|
||||||
|
ColorRGB color;
|
||||||
|
if (H < 60) color = new ColorRGB(c, x, 0);
|
||||||
|
else if (H < 120) color = new ColorRGB(x, c, 0);
|
||||||
|
else if (H < 180) color = new ColorRGB(0, c, x);
|
||||||
|
else if (H < 240) color = new ColorRGB(0, x, c);
|
||||||
|
else if (H < 300) color = new ColorRGB(x, 0, c);
|
||||||
|
else if (H < 360) color = new ColorRGB(c, 0, x);
|
||||||
|
else throw new ArgumentOutOfRangeException();
|
||||||
|
|
||||||
|
color.r += m;
|
||||||
|
color.g += m;
|
||||||
|
color.b += m;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
public ColorHSV AsHsv() => this;
|
||||||
|
public ColorCMYK AsCmyk() => AsRgb().AsCmyk();
|
||||||
|
|
||||||
|
public string HexCode() => AsRgb().HexCode();
|
||||||
|
|
||||||
|
public IEnumerator<double> GetEnumerator()
|
||||||
|
{
|
||||||
|
yield return h.Revolutions;
|
||||||
|
yield return s;
|
||||||
|
yield return v;
|
||||||
|
yield return a;
|
||||||
|
}
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public void Deconstruct(out Angle h, out double s, out double v)
|
||||||
|
{
|
||||||
|
h = this.h;
|
||||||
|
s = this.s;
|
||||||
|
v = this.v;
|
||||||
|
}
|
||||||
|
public void Deconstruct(out Angle h, out double s, out double v, out double a)
|
||||||
|
{
|
||||||
|
h = this.h;
|
||||||
|
s = this.s;
|
||||||
|
v = this.v;
|
||||||
|
a = this.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ColorHSV other)
|
||||||
|
{
|
||||||
|
if (a <= 0 && other.a <= 0) return true;
|
||||||
|
else if (v <= 0 && other.v <= 0) return true;
|
||||||
|
else if (s <= 0 && other.s <= 0) return true;
|
||||||
|
else return h == other.h && s == other.s && v == other.v && a == other.a;
|
||||||
|
}
|
||||||
|
public bool Equals(IColor other) => Equals(other.AsHsv());
|
||||||
|
#if CS8_OR_GREATER
|
||||||
|
public override bool Equals(object? other)
|
||||||
|
#else
|
||||||
|
public override bool Equals(object other)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (other is IColor color) return Equals(color.AsHsv());
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
public override int GetHashCode() => base.GetHashCode();
|
||||||
|
public override string ToString() => $"{{ h={h.Degrees:0}°, s={s:0.00}, v={v:0.00}, a={a:0.00} }}";
|
||||||
|
|
||||||
|
public double[] ToArray() => new double[] { h.Revolutions, s, v, a };
|
||||||
|
public Fill<double> ToFill()
|
||||||
|
{
|
||||||
|
ColorHSV copy = this;
|
||||||
|
return i => copy[i];
|
||||||
|
}
|
||||||
|
public List<double> ToList() => new List<double> { h.Revolutions, s, v, a };
|
||||||
|
|
||||||
|
public static ColorHSV operator +(ColorHSV a, ColorHSV b) => new ColorHSV(a.h + b.h, a.s + b.s, a.v + b.v, 1 - (1 - a.a) * (1 - b.a));
|
||||||
|
public static ColorHSV operator *(ColorHSV a, ColorHSV b) => new ColorHSV(Angle.FromRevolutions(a.h.Revolutions * b.h.Revolutions), a.s * b.s, a.v * b.v, a.a * b.a);
|
||||||
|
public static ColorHSV operator *(ColorHSV a, double b) => new ColorHSV(a.h, a.s * b, a.v, a.a);
|
||||||
|
public static bool operator ==(ColorHSV a, IColor b) => a.Equals(b.AsHsv());
|
||||||
|
public static bool operator !=(ColorHSV a, IColor b) => !a.Equals(b.AsHsv());
|
||||||
|
public static bool operator ==(ColorHSV a, ColorHSV b) => a.Equals(b);
|
||||||
|
public static bool operator !=(ColorHSV a, ColorHSV b) => !a.Equals(b);
|
||||||
|
|
||||||
|
public static implicit operator ColorHSV(ListTuple<double> tuple)
|
||||||
|
{
|
||||||
|
if (tuple.Length == 3) return new ColorHSV(tuple[0], tuple[1], tuple[2]);
|
||||||
|
else if (tuple.Length == 4) return new ColorHSV(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||||
|
else throw new InvalidCastException();
|
||||||
|
}
|
||||||
|
public static implicit operator ColorHSV((Angle, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||||
|
public static implicit operator ColorHSV((double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||||
|
public static implicit operator ColorHSV((Angle, double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||||
|
public static implicit operator ColorHSV((double, double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||||
|
|
||||||
|
public static implicit operator ListTuple<double>(ColorHSV color) => new ListTuple<double>(color.h.Revolutions, color.s, color.v, color.a);
|
||||||
|
public static implicit operator ValueTuple<Angle, double, double>(ColorHSV color) => (color.h, color.s, color.v);
|
||||||
|
public static implicit operator ValueTuple<double, double, double>(ColorHSV color) => (color.h.Revolutions, color.s, color.v);
|
||||||
|
public static implicit operator ValueTuple<Angle, double, double, double>(ColorHSV color) => (color.h, color.s, color.v, color.a);
|
||||||
|
public static implicit operator ValueTuple<double, double, double, double>(ColorHSV color) => (color.h.Revolutions, color.s, color.v, color.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,8 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Nerd_STF.Graphics
|
namespace Nerd_STF.Graphics
|
||||||
{
|
{
|
||||||
public struct ColorRGB : IColor<ColorRGB>
|
public struct ColorRGB : IColor<ColorRGB>,
|
||||||
|
INumberGroup<ColorRGB, double>
|
||||||
#if CS11_OR_GREATER
|
#if CS11_OR_GREATER
|
||||||
,IFromTuple<ColorRGB, (double, double, double)>,
|
,IFromTuple<ColorRGB, (double, double, double)>,
|
||||||
IFromTuple<ColorRGB, (double, double, double, double)>,
|
IFromTuple<ColorRGB, (double, double, double, double)>,
|
||||||
@ -16,36 +17,36 @@ namespace Nerd_STF.Graphics
|
|||||||
{
|
{
|
||||||
public static int ChannelCount => 4;
|
public static int ChannelCount => 4;
|
||||||
|
|
||||||
public static ColorRGB Black => new ColorRGB(0, 0, 0, 1);
|
public static ColorRGB Black => new ColorRGB(0 , 0 , 0 , 1);
|
||||||
public static ColorRGB Blue => new ColorRGB(0, 0, 1, 1);
|
public static ColorRGB Blue => new ColorRGB(0 , 0 , 1 , 1);
|
||||||
public static ColorRGB Clear => new ColorRGB(0, 0, 0, 0);
|
public static ColorRGB Clear => new ColorRGB(0 , 0 , 0 , 0);
|
||||||
public static ColorRGB Cyan => new ColorRGB(0, 1, 1, 1);
|
public static ColorRGB Cyan => new ColorRGB(0 , 1 , 1 , 1);
|
||||||
public static ColorRGB Gray => new ColorRGB(0.5, 0.5, 0.5, 1);
|
public static ColorRGB Gray => new ColorRGB(0.5, 0.5, 0.5, 1);
|
||||||
public static ColorRGB Green => new ColorRGB(0, 1, 0, 1);
|
public static ColorRGB Green => new ColorRGB(0 , 1 , 0 , 1);
|
||||||
public static ColorRGB Magenta => new ColorRGB(1, 0, 1, 1);
|
public static ColorRGB Magenta => new ColorRGB(1 , 0 , 1 , 1);
|
||||||
public static ColorRGB Orange => new ColorRGB(1, 0.5, 0, 1);
|
public static ColorRGB Orange => new ColorRGB(1 , 0.5, 0 , 1);
|
||||||
public static ColorRGB Purple => new ColorRGB(0.5, 0, 1, 1);
|
public static ColorRGB Purple => new ColorRGB(0.5, 0 , 1 , 1);
|
||||||
public static ColorRGB Red => new ColorRGB(1, 0, 0, 1);
|
public static ColorRGB Red => new ColorRGB(1 , 0 , 0 , 1);
|
||||||
public static ColorRGB White => new ColorRGB(1, 1, 1, 1);
|
public static ColorRGB White => new ColorRGB(1 , 1 , 1 , 1);
|
||||||
public static ColorRGB Yellow => new ColorRGB(1, 1, 0, 1);
|
public static ColorRGB Yellow => new ColorRGB(1 , 1 , 0 , 1);
|
||||||
|
|
||||||
public double Magnitude => MathE.Sqrt(r * r + g * g + b * b);
|
public double Magnitude => MathE.Sqrt(r * r + g * g + b * b);
|
||||||
|
|
||||||
public double r, g, b, a;
|
public double r, g, b, a;
|
||||||
|
|
||||||
public ColorRGB(double r, double g, double b)
|
public ColorRGB(double red, double green, double blue)
|
||||||
{
|
{
|
||||||
this.r = r;
|
r = red;
|
||||||
this.g = g;
|
g = green;
|
||||||
this.b = b;
|
b = blue;
|
||||||
a = 1;
|
a = 1;
|
||||||
}
|
}
|
||||||
public ColorRGB(double r, double g, double b, double a)
|
public ColorRGB(double red, double green, double blue, double alpha)
|
||||||
{
|
{
|
||||||
this.r = r;
|
r = red;
|
||||||
this.g = g;
|
g = green;
|
||||||
this.b = b;
|
b = blue;
|
||||||
this.a = a;
|
a = alpha;
|
||||||
}
|
}
|
||||||
public ColorRGB(IEnumerable<double> nums)
|
public ColorRGB(IEnumerable<double> nums)
|
||||||
{
|
{
|
||||||
@ -160,6 +161,9 @@ namespace Nerd_STF.Graphics
|
|||||||
MathE.Pow(avgB, invGamma),
|
MathE.Pow(avgB, invGamma),
|
||||||
avgA);
|
avgA);
|
||||||
}
|
}
|
||||||
|
#if CS11_OR_GREATER
|
||||||
|
static ColorRGB IColor<ColorRGB>.Average(IEnumerable<ColorRGB> colors) => Average(1, colors);
|
||||||
|
#endif
|
||||||
public static ColorRGB Clamp(ColorRGB color, ColorRGB min, ColorRGB max) =>
|
public static ColorRGB Clamp(ColorRGB color, ColorRGB min, ColorRGB max) =>
|
||||||
new ColorRGB(MathE.Clamp(color.r, min.r, max.r),
|
new ColorRGB(MathE.Clamp(color.r, min.r, max.r),
|
||||||
MathE.Clamp(color.g, min.g, max.g),
|
MathE.Clamp(color.g, min.g, max.g),
|
||||||
@ -206,12 +210,9 @@ namespace Nerd_STF.Graphics
|
|||||||
}
|
}
|
||||||
public static ColorRGB Lerp(double gamma, ColorRGB a, ColorRGB b, double t, bool clamp = true)
|
public static ColorRGB Lerp(double gamma, ColorRGB a, ColorRGB b, double t, bool clamp = true)
|
||||||
{
|
{
|
||||||
double aCorrectedR = MathE.Pow(a.r, gamma),
|
double aCorrectedR = MathE.Pow(a.r, gamma), bCorrectedR = MathE.Pow(b.r, gamma),
|
||||||
aCorrectedG = MathE.Pow(a.g, gamma),
|
aCorrectedG = MathE.Pow(a.g, gamma), bCorrectedG = MathE.Pow(b.g, gamma),
|
||||||
aCorrectedB = MathE.Pow(a.b, gamma),
|
aCorrectedB = MathE.Pow(a.b, gamma), bCorrectedB = MathE.Pow(b.b, gamma);
|
||||||
bCorrectedR = MathE.Pow(b.r, gamma),
|
|
||||||
bCorrectedG = MathE.Pow(b.g, gamma),
|
|
||||||
bCorrectedB = MathE.Pow(b.b, gamma);
|
|
||||||
// Gamma doesn't apply to the alpha channel.
|
// Gamma doesn't apply to the alpha channel.
|
||||||
|
|
||||||
double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp),
|
double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp),
|
||||||
@ -242,7 +243,7 @@ namespace Nerd_STF.Graphics
|
|||||||
public static ColorRGB Sum(IEnumerable<ColorRGB> colors)
|
public static ColorRGB Sum(IEnumerable<ColorRGB> colors)
|
||||||
{
|
{
|
||||||
bool any = false;
|
bool any = false;
|
||||||
ColorRGB result = new ColorRGB(0, 0, 0, 1);
|
ColorRGB result = new ColorRGB(0, 0, 0, 0);
|
||||||
foreach (ColorRGB color in colors)
|
foreach (ColorRGB color in colors)
|
||||||
{
|
{
|
||||||
any = true;
|
any = true;
|
||||||
@ -278,9 +279,37 @@ namespace Nerd_STF.Graphics
|
|||||||
{
|
{
|
||||||
Type type = typeof(TColor);
|
Type type = typeof(TColor);
|
||||||
if (type == typeof(ColorRGB)) return (TColor)(object)this;
|
if (type == typeof(ColorRGB)) return (TColor)(object)this;
|
||||||
|
else if (type == typeof(ColorHSV)) return (TColor)(object)AsHsv();
|
||||||
|
else if (type == typeof(ColorCMYK)) return (TColor)(object)AsCmyk();
|
||||||
else throw new InvalidCastException();
|
else throw new InvalidCastException();
|
||||||
}
|
}
|
||||||
public ColorRGB AsRgb() => this;
|
public ColorRGB AsRgb() => this;
|
||||||
|
public ColorHSV AsHsv()
|
||||||
|
{
|
||||||
|
// Thanks https://www.rapidtables.com/convert/color/rgb-to-hsv.html
|
||||||
|
double cMax = MathE.Max(this), cMin = MathE.Min(this), delta = cMax - cMin;
|
||||||
|
Angle h;
|
||||||
|
|
||||||
|
if (delta == 0) h = Angle.Zero;
|
||||||
|
else if (cMax == r) h = Angle.FromDegrees(60 * MathE.ModAbs((g - b) / delta, 6));
|
||||||
|
else if (cMax == g) h = Angle.FromDegrees(60 * ((b - r) / delta + 2));
|
||||||
|
else if (cMax == b) h = Angle.FromDegrees(60 * ((r - g) / delta + 4));
|
||||||
|
else h = Angle.Zero;
|
||||||
|
|
||||||
|
double s = cMax == 0 ? 0 : delta / cMax;
|
||||||
|
return new ColorHSV(h, s, cMax);
|
||||||
|
}
|
||||||
|
public ColorCMYK AsCmyk()
|
||||||
|
{
|
||||||
|
// Thanks https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
|
||||||
|
double diffK = MathE.Max(this), invDiffK = 1 / diffK;
|
||||||
|
return new ColorCMYK((diffK - r) * invDiffK,
|
||||||
|
(diffK - g) * invDiffK,
|
||||||
|
(diffK - b) * invDiffK,
|
||||||
|
1 - diffK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HexCode() => $"#{(int)(r * 255):X2}{(int)(g * 255):X2}{(int)(b * 255):X2}";
|
||||||
|
|
||||||
public IEnumerator<double> GetEnumerator()
|
public IEnumerator<double> GetEnumerator()
|
||||||
{
|
{
|
||||||
@ -307,8 +336,8 @@ namespace Nerd_STF.Graphics
|
|||||||
|
|
||||||
public bool Equals(ColorRGB other)
|
public bool Equals(ColorRGB other)
|
||||||
{
|
{
|
||||||
if (a <= 0 && b <= 0) return true;
|
if (a <= 0 && other.a <= 0) return true;
|
||||||
else return r == other.r && g == other.g && b == other.b;
|
else return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||||
}
|
}
|
||||||
public bool Equals(IColor other) => Equals(other.AsRgb());
|
public bool Equals(IColor other) => Equals(other.AsRgb());
|
||||||
#if CS8_OR_GREATER
|
#if CS8_OR_GREATER
|
||||||
@ -329,13 +358,11 @@ namespace Nerd_STF.Graphics
|
|||||||
ColorRGB copy = this;
|
ColorRGB copy = this;
|
||||||
return i => copy[i];
|
return i => copy[i];
|
||||||
}
|
}
|
||||||
public List<double> ToList() => new List<double>() { r, g, b, a };
|
public List<double> ToList() => new List<double> { r, g, b, a };
|
||||||
|
|
||||||
public static ColorRGB operator +(ColorRGB a) => a;
|
public static ColorRGB operator +(ColorRGB a, ColorRGB b) => new ColorRGB(a.r + b.r, a.g + b.g, a.b + b.b, 1 - (1 - a.a) * (1 - b.a));
|
||||||
public static ColorRGB operator +(ColorRGB a, ColorRGB b) => new ColorRGB(a.r + b.r, a.g + b.g, a.b + b.b, 1 - (1 - a.a) * (1 - b.b));
|
|
||||||
public static ColorRGB operator -(ColorRGB a) => new ColorRGB(1 - a.r, 1 - a.g, 1 - a.b);
|
|
||||||
public static ColorRGB operator *(ColorRGB a, ColorRGB b) => new ColorRGB(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a);
|
public static ColorRGB operator *(ColorRGB a, ColorRGB b) => new ColorRGB(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a);
|
||||||
public static ColorRGB operator *(ColorRGB a, double b) => new ColorRGB(a.r * b, a.g * b, a.b * b);
|
public static ColorRGB operator *(ColorRGB a, double b) => new ColorRGB(a.r * b, a.g * b, a.b * b, a.a);
|
||||||
public static bool operator ==(ColorRGB a, IColor b) => a.Equals(b.AsRgb());
|
public static bool operator ==(ColorRGB a, IColor b) => a.Equals(b.AsRgb());
|
||||||
public static bool operator !=(ColorRGB a, IColor b) => !a.Equals(b.AsRgb());
|
public static bool operator !=(ColorRGB a, IColor b) => !a.Equals(b.AsRgb());
|
||||||
public static bool operator ==(ColorRGB a, ColorRGB b) => a.Equals(b);
|
public static bool operator ==(ColorRGB a, ColorRGB b) => a.Equals(b);
|
||||||
|
|||||||
@ -10,24 +10,27 @@ namespace Nerd_STF.Graphics
|
|||||||
|
|
||||||
TColor AsColor<TColor>() where TColor : struct, IColor<TColor>;
|
TColor AsColor<TColor>() where TColor : struct, IColor<TColor>;
|
||||||
ColorRGB AsRgb();
|
ColorRGB AsRgb();
|
||||||
|
ColorHSV AsHsv();
|
||||||
|
ColorCMYK AsCmyk();
|
||||||
|
|
||||||
|
string HexCode();
|
||||||
|
|
||||||
bool Equals(IColor other);
|
bool Equals(IColor other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IColor<TSelf> : IColor,
|
public interface IColor<TSelf> : IColor,
|
||||||
IEquatable<TSelf>,
|
IEquatable<TSelf>
|
||||||
INumberGroup<TSelf, double>
|
|
||||||
#if CS11_OR_GREATER
|
#if CS11_OR_GREATER
|
||||||
,IColorPresets<TSelf>
|
,IColorOperators<TSelf>,
|
||||||
|
IColorPresets<TSelf>,
|
||||||
|
IInterpolable<TSelf>
|
||||||
#endif
|
#endif
|
||||||
where TSelf : struct, IColor<TSelf>
|
where TSelf : struct, IColor<TSelf>
|
||||||
{
|
{
|
||||||
#if CS11_OR_GREATER
|
#if CS11_OR_GREATER
|
||||||
static abstract int ChannelCount { get; }
|
static abstract int ChannelCount { get; }
|
||||||
|
|
||||||
// TODO: Do all color formats have a gamma value?
|
static abstract TSelf Average(IEnumerable<TSelf> colors);
|
||||||
static abstract TSelf Average(double gamma, IEnumerable<TSelf> colors);
|
|
||||||
static abstract TSelf Lerp(double gamma, TSelf a, TSelf b, double t, bool clamp = true);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
Nerd_STF/Graphics/IColorOperators.cs
Normal file
14
Nerd_STF/Graphics/IColorOperators.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#if CS11_OR_GREATER
|
||||||
|
namespace Nerd_STF.Graphics
|
||||||
|
{
|
||||||
|
public interface IColorOperators<TSelf> where TSelf : IColorOperators<TSelf>
|
||||||
|
{
|
||||||
|
static abstract TSelf operator +(TSelf a, TSelf b);
|
||||||
|
static abstract TSelf operator *(TSelf a, double b);
|
||||||
|
static abstract bool operator ==(TSelf a, IColor b);
|
||||||
|
static abstract bool operator !=(TSelf a, IColor b);
|
||||||
|
static abstract bool operator ==(TSelf a, TSelf b);
|
||||||
|
static abstract bool operator !=(TSelf a, TSelf b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,11 +1,7 @@
|
|||||||
#if CS11_OR_GREATER
|
#if CS11_OR_GREATER
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Nerd_STF.Graphics
|
namespace Nerd_STF.Graphics
|
||||||
{
|
{
|
||||||
public interface IColorPresets<TSelf> where TSelf : struct, IColor<TSelf>, IColorPresets<TSelf>
|
public interface IColorPresets<TSelf> where TSelf : IColorPresets<TSelf>
|
||||||
{
|
{
|
||||||
static abstract TSelf Black { get; }
|
static abstract TSelf Black { get; }
|
||||||
static abstract TSelf Blue { get; }
|
static abstract TSelf Blue { get; }
|
||||||
|
|||||||
@ -4,6 +4,14 @@ namespace Nerd_STF.Helpers
|
|||||||
{
|
{
|
||||||
internal static class TargetHelper
|
internal static class TargetHelper
|
||||||
{
|
{
|
||||||
|
public static void WriteLine(string content)
|
||||||
|
{
|
||||||
|
#if NETSTANDARD1_1
|
||||||
|
#else
|
||||||
|
Console.WriteLine(content);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public static T[] EmptyArray<T>()
|
public static T[] EmptyArray<T>()
|
||||||
{
|
{
|
||||||
#if NETSTANDARD1_1
|
#if NETSTANDARD1_1
|
||||||
|
|||||||
@ -86,6 +86,12 @@ namespace Nerd_STF
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Fill<T> ToFill()
|
||||||
|
{
|
||||||
|
T[] items = this.items;
|
||||||
|
return i => items[i];
|
||||||
|
}
|
||||||
|
|
||||||
public static bool operator ==(ListTuple<T> a, ListTuple<T> b) => a.Equals(b);
|
public static bool operator ==(ListTuple<T> a, ListTuple<T> b) => a.Equals(b);
|
||||||
public static bool operator !=(ListTuple<T> a, ListTuple<T> b) => !a.Equals(b);
|
public static bool operator !=(ListTuple<T> a, ListTuple<T> b) => !a.Equals(b);
|
||||||
|
|
||||||
@ -105,7 +111,7 @@ namespace Nerd_STF
|
|||||||
public static implicit operator ListTuple<T>((T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
public static implicit operator ListTuple<T>((T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
||||||
public static implicit operator ListTuple<T>((T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6);
|
public static implicit operator ListTuple<T>((T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6);
|
||||||
public static implicit operator ListTuple<T>((T, T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6, tuple.Item7);
|
public static implicit operator ListTuple<T>((T, T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6, tuple.Item7);
|
||||||
public static implicit operator ListTuple<T>(T[] array) => new ListTuple<T>(array);
|
public static implicit operator ListTuple<T>(T[] array) => new ListTuple<T>(array)
|
||||||
|
|
||||||
public struct Enumerator : IEnumerator<T>
|
public struct Enumerator : IEnumerator<T>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -9,6 +9,7 @@ namespace Nerd_STF.Mathematics
|
|||||||
IEquatable<Angle>
|
IEquatable<Angle>
|
||||||
#if CS11_OR_GREATER
|
#if CS11_OR_GREATER
|
||||||
,IFromTuple<Angle, (double, Angle.Units)>,
|
,IFromTuple<Angle, (double, Angle.Units)>,
|
||||||
|
IInterpolable<Angle>,
|
||||||
IPresets2d<Angle>
|
IPresets2d<Angle>
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@ -38,6 +39,11 @@ namespace Nerd_STF.Mathematics
|
|||||||
|
|
||||||
private readonly double revTheta;
|
private readonly double revTheta;
|
||||||
|
|
||||||
|
public static Angle FromDegrees(double deg) => new Angle(deg, Units.Degrees);
|
||||||
|
public static Angle FromRadians(double rad) => new Angle(rad, Units.Radians);
|
||||||
|
public static Angle FromGradians(double grade) => new Angle(grade, Units.Gradians);
|
||||||
|
public static Angle FromRevolutions(double turns) => new Angle(turns, Units.Revolutions);
|
||||||
|
|
||||||
public Angle(double theta, Units unit)
|
public Angle(double theta, Units unit)
|
||||||
{
|
{
|
||||||
switch (unit)
|
switch (unit)
|
||||||
@ -127,6 +133,8 @@ namespace Nerd_STF.Mathematics
|
|||||||
}
|
}
|
||||||
public static Angle Clamp(Angle value, Angle min, Angle max) =>
|
public static Angle Clamp(Angle value, Angle min, Angle max) =>
|
||||||
new Angle(MathE.Clamp(value.revTheta, min.revTheta, max.revTheta));
|
new Angle(MathE.Clamp(value.revTheta, min.revTheta, max.revTheta));
|
||||||
|
public static Angle Lerp(Angle a, Angle b, double t, bool clamp = true) =>
|
||||||
|
new Angle(MathE.Lerp(a.revTheta, b.revTheta, t, clamp));
|
||||||
public static Angle Max(IEnumerable<Angle> values) => Max(false, values);
|
public static Angle Max(IEnumerable<Angle> values) => Max(false, values);
|
||||||
public static Angle Max(bool normalize, IEnumerable<Angle> values)
|
public static Angle Max(bool normalize, IEnumerable<Angle> values)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Nerd_STF.Exceptions;
|
using Nerd_STF.Exceptions;
|
||||||
|
using Nerd_STF.Graphics;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -127,7 +128,7 @@ namespace Nerd_STF.Mathematics
|
|||||||
total += val;
|
total += val;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return total;
|
return total / count;
|
||||||
}
|
}
|
||||||
public static Int3 Ceiling(Float3 val) =>
|
public static Int3 Ceiling(Float3 val) =>
|
||||||
new Int3(MathE.Ceiling(val.x),
|
new Int3(MathE.Ceiling(val.x),
|
||||||
@ -314,6 +315,7 @@ namespace Nerd_STF.Mathematics
|
|||||||
public static bool operator ==(Float3 a, Float3 b) => a.Equals(b);
|
public static bool operator ==(Float3 a, Float3 b) => a.Equals(b);
|
||||||
public static bool operator !=(Float3 a, Float3 b) => !a.Equals(b);
|
public static bool operator !=(Float3 a, Float3 b) => !a.Equals(b);
|
||||||
|
|
||||||
|
public static explicit operator Float3(ColorRGB color) => new Float3(color.r, color.g, color.b);
|
||||||
public static implicit operator Float3(Float2 floats) => new Float3(floats.x, floats.y, 0);
|
public static implicit operator Float3(Float2 floats) => new Float3(floats.x, floats.y, 0);
|
||||||
public static explicit operator Float3(Float4 floats) => new Float3(floats.x, floats.y, floats.z);
|
public static explicit operator Float3(Float4 floats) => new Float3(floats.x, floats.y, floats.z);
|
||||||
public static implicit operator Float3(Int2 ints) => new Float3(ints.x, ints.y, 0);
|
public static implicit operator Float3(Int2 ints) => new Float3(ints.x, ints.y, 0);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Nerd_STF.Exceptions;
|
using Nerd_STF.Exceptions;
|
||||||
|
using Nerd_STF.Graphics;
|
||||||
|
|
||||||
namespace Nerd_STF.Mathematics
|
namespace Nerd_STF.Mathematics
|
||||||
{
|
{
|
||||||
@ -136,7 +137,7 @@ namespace Nerd_STF.Mathematics
|
|||||||
total += val;
|
total += val;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return total;
|
return total / count;
|
||||||
}
|
}
|
||||||
public static Int4 Ceiling(Float4 val) =>
|
public static Int4 Ceiling(Float4 val) =>
|
||||||
new Int4(MathE.Ceiling(val.w),
|
new Int4(MathE.Ceiling(val.w),
|
||||||
@ -336,6 +337,7 @@ namespace Nerd_STF.Mathematics
|
|||||||
public static bool operator ==(Float4 a, Float4 b) => a.Equals(b);
|
public static bool operator ==(Float4 a, Float4 b) => a.Equals(b);
|
||||||
public static bool operator !=(Float4 a, Float4 b) => !a.Equals(b);
|
public static bool operator !=(Float4 a, Float4 b) => !a.Equals(b);
|
||||||
|
|
||||||
|
public static explicit operator Float4(ColorRGB color) => new Float4(color.a, color.r, color.g, color.b);
|
||||||
public static implicit operator Float4(Int2 ints) => new Float4(0, ints.x, ints.y, 0);
|
public static implicit operator Float4(Int2 ints) => new Float4(0, ints.x, ints.y, 0);
|
||||||
public static implicit operator Float4(Int3 ints) => new Float4(0, ints.x, ints.y, ints.z);
|
public static implicit operator Float4(Int3 ints) => new Float4(0, ints.x, ints.y, ints.z);
|
||||||
public static implicit operator Float4(Int4 ints) => new Float4(ints.w, ints.x, ints.y, ints.z);
|
public static implicit operator Float4(Int4 ints) => new Float4(ints.w, ints.x, ints.y, ints.z);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user