Tested HSV and untested CMYK types. This beta will concern colors.

This commit is contained in:
That-One-Nerd 2025-02-13 11:24:19 -05:00
parent 866326863b
commit e070aa097f
12 changed files with 839 additions and 51 deletions

View 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);
}
}

View File

@ -5,6 +5,13 @@
Red,
Green,
Blue,
Alpha
Alpha,
Hue,
Saturation,
Value,
Cyan,
Magenta,
Yellow,
Key
}
}

View 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);
}
}

View File

@ -7,7 +7,8 @@ using System.Linq;
namespace Nerd_STF.Graphics
{
public struct ColorRGB : IColor<ColorRGB>
public struct ColorRGB : IColor<ColorRGB>,
INumberGroup<ColorRGB, double>
#if CS11_OR_GREATER
,IFromTuple<ColorRGB, (double, double, double)>,
IFromTuple<ColorRGB, (double, double, double, double)>,
@ -16,36 +17,36 @@ namespace Nerd_STF.Graphics
{
public static int ChannelCount => 4;
public static ColorRGB Black => new ColorRGB(0, 0, 0, 1);
public static ColorRGB Blue => new ColorRGB(0, 0, 1, 1);
public static ColorRGB Clear => new ColorRGB(0, 0, 0, 0);
public static ColorRGB Cyan => new ColorRGB(0, 1, 1, 1);
public static ColorRGB Black => new ColorRGB(0 , 0 , 0 , 1);
public static ColorRGB Blue => new ColorRGB(0 , 0 , 1 , 1);
public static ColorRGB Clear => new ColorRGB(0 , 0 , 0 , 0);
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 Green => new ColorRGB(0, 1, 0, 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 Purple => new ColorRGB(0.5, 0, 1, 1);
public static ColorRGB Red => new ColorRGB(1, 0, 0, 1);
public static ColorRGB White => new ColorRGB(1, 1, 1, 1);
public static ColorRGB Yellow => new ColorRGB(1, 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 Orange => new ColorRGB(1 , 0.5, 0 , 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 White => new ColorRGB(1 , 1 , 1 , 1);
public static ColorRGB Yellow => new ColorRGB(1 , 1 , 0 , 1);
public double Magnitude => MathE.Sqrt(r * r + g * g + b * b);
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;
this.g = g;
this.b = b;
r = red;
g = green;
b = blue;
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;
this.g = g;
this.b = b;
this.a = a;
r = red;
g = green;
b = blue;
a = alpha;
}
public ColorRGB(IEnumerable<double> nums)
{
@ -160,6 +161,9 @@ namespace Nerd_STF.Graphics
MathE.Pow(avgB, invGamma),
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) =>
new ColorRGB(MathE.Clamp(color.r, min.r, max.r),
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)
{
double aCorrectedR = MathE.Pow(a.r, gamma),
aCorrectedG = MathE.Pow(a.g, gamma),
aCorrectedB = MathE.Pow(a.b, gamma),
bCorrectedR = MathE.Pow(b.r, gamma),
bCorrectedG = MathE.Pow(b.g, gamma),
bCorrectedB = MathE.Pow(b.b, gamma);
double aCorrectedR = MathE.Pow(a.r, gamma), bCorrectedR = MathE.Pow(b.r, gamma),
aCorrectedG = MathE.Pow(a.g, gamma), bCorrectedG = MathE.Pow(b.g, gamma),
aCorrectedB = MathE.Pow(a.b, gamma), bCorrectedB = MathE.Pow(b.b, gamma);
// Gamma doesn't apply to the alpha channel.
double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp),
@ -242,7 +243,7 @@ namespace Nerd_STF.Graphics
public static ColorRGB Sum(IEnumerable<ColorRGB> colors)
{
bool any = false;
ColorRGB result = new ColorRGB(0, 0, 0, 1);
ColorRGB result = new ColorRGB(0, 0, 0, 0);
foreach (ColorRGB color in colors)
{
any = true;
@ -278,9 +279,37 @@ namespace Nerd_STF.Graphics
{
Type type = typeof(TColor);
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();
}
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()
{
@ -307,8 +336,8 @@ namespace Nerd_STF.Graphics
public bool Equals(ColorRGB other)
{
if (a <= 0 && b <= 0) return true;
else return r == other.r && g == other.g && b == other.b;
if (a <= 0 && other.a <= 0) return true;
else return r == other.r && g == other.g && b == other.b && a == other.a;
}
public bool Equals(IColor other) => Equals(other.AsRgb());
#if CS8_OR_GREATER
@ -329,13 +358,11 @@ namespace Nerd_STF.Graphics
ColorRGB copy = this;
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.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, 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, 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, ColorRGB b) => a.Equals(b);

View File

@ -10,24 +10,27 @@ namespace Nerd_STF.Graphics
TColor AsColor<TColor>() where TColor : struct, IColor<TColor>;
ColorRGB AsRgb();
ColorHSV AsHsv();
ColorCMYK AsCmyk();
string HexCode();
bool Equals(IColor other);
}
public interface IColor<TSelf> : IColor,
IEquatable<TSelf>,
INumberGroup<TSelf, double>
IEquatable<TSelf>
#if CS11_OR_GREATER
,IColorPresets<TSelf>
,IColorOperators<TSelf>,
IColorPresets<TSelf>,
IInterpolable<TSelf>
#endif
where TSelf : struct, IColor<TSelf>
{
#if CS11_OR_GREATER
static abstract int ChannelCount { get; }
// TODO: Do all color formats have a gamma value?
static abstract TSelf Average(double gamma, IEnumerable<TSelf> colors);
static abstract TSelf Lerp(double gamma, TSelf a, TSelf b, double t, bool clamp = true);
static abstract TSelf Average(IEnumerable<TSelf> colors);
#endif
}
}

View 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

View File

@ -1,11 +1,7 @@
#if CS11_OR_GREATER
using System;
using System.Collections.Generic;
using System.Text;
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 Blue { get; }

View File

@ -4,6 +4,14 @@ namespace Nerd_STF.Helpers
{
internal static class TargetHelper
{
public static void WriteLine(string content)
{
#if NETSTANDARD1_1
#else
Console.WriteLine(content);
#endif
}
public static T[] EmptyArray<T>()
{
#if NETSTANDARD1_1

View File

@ -86,6 +86,12 @@ namespace Nerd_STF
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);
@ -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, 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[] array) => new ListTuple<T>(array);
public static implicit operator ListTuple<T>(T[] array) => new ListTuple<T>(array)
public struct Enumerator : IEnumerator<T>
{

View File

@ -9,6 +9,7 @@ namespace Nerd_STF.Mathematics
IEquatable<Angle>
#if CS11_OR_GREATER
,IFromTuple<Angle, (double, Angle.Units)>,
IInterpolable<Angle>,
IPresets2d<Angle>
#endif
{
@ -38,6 +39,11 @@ namespace Nerd_STF.Mathematics
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)
{
switch (unit)
@ -127,6 +133,8 @@ namespace Nerd_STF.Mathematics
}
public static Angle Clamp(Angle value, Angle min, Angle max) =>
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(bool normalize, IEnumerable<Angle> values)
{

View File

@ -1,4 +1,5 @@
using Nerd_STF.Exceptions;
using Nerd_STF.Graphics;
using System;
using System.Collections;
using System.Collections.Generic;
@ -127,7 +128,7 @@ namespace Nerd_STF.Mathematics
total += val;
count++;
}
return total;
return total / count;
}
public static Int3 Ceiling(Float3 val) =>
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 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 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);

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Nerd_STF.Exceptions;
using Nerd_STF.Graphics;
namespace Nerd_STF.Mathematics
{
@ -136,7 +137,7 @@ namespace Nerd_STF.Mathematics
total += val;
count++;
}
return total;
return total / count;
}
public static Int4 Ceiling(Float4 val) =>
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 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(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);