Some color things. More to come.

This commit is contained in:
That-One-Nerd 2025-01-23 08:01:59 -05:00
parent 27c64c4291
commit 866326863b
14 changed files with 457 additions and 16 deletions

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Graphics
{
public enum ColorChannel
{
Red,
Green,
Blue,
Alpha
}
}

View File

@ -0,0 +1,359 @@
using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Nerd_STF.Graphics
{
public struct ColorRGB : IColor<ColorRGB>
#if CS11_OR_GREATER
,IFromTuple<ColorRGB, (double, double, double)>,
IFromTuple<ColorRGB, (double, double, double, double)>,
ISplittable<ColorRGB, (double[] Rs, double[] Gs, double[] Bs, double[] As)>
#endif
{
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 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 double Magnitude => MathE.Sqrt(r * r + g * g + b * b);
public double r, g, b, a;
public ColorRGB(double r, double g, double b)
{
this.r = r;
this.g = g;
this.b = b;
a = 1;
}
public ColorRGB(double r, double g, double b, double a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public ColorRGB(IEnumerable<double> nums)
{
r = 0;
g = 0;
b = 0;
a = 1;
int index = 0;
foreach (double item in nums)
{
this[index] = item;
index++;
if (index == 4) break;
}
}
public ColorRGB(Fill<double> fill)
{
r = fill(0);
g = fill(1);
b = fill(2);
a = fill(3);
}
public double this[int index]
{
get
{
switch (index)
{
case 0: return r;
case 1: return g;
case 2: return b;
case 3: return a;
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0: r = value; break;
case 1: g = value; break;
case 2: b = value; break;
case 3: a = value; break;
default: throw new 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 'r': items[i] = r; break;
case 'g': items[i] = g; break;
case 'b': items[i] = b; 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 'r': r = stepper.Current; break;
case 'g': g = stepper.Current; break;
case 'b': b = stepper.Current; break;
case 'a': a = stepper.Current; break;
default: throw new ArgumentException("Invalid key.", nameof(key));
}
}
}
}
public static ColorRGB Average(double gamma, IEnumerable<ColorRGB> colors)
{
double avgR = 0, avgG = 0, avgB = 0, avgA = 0;
int count = 0;
foreach (ColorRGB color in colors)
{
double correctR = MathE.Pow(color.r, gamma),
correctG = MathE.Pow(color.g, gamma),
correctB = MathE.Pow(color.b, gamma);
// Gamma doesn't apply to the alpha channel.
avgR += correctR;
avgG += correctG;
avgB += correctB;
avgA += color.a;
count++;
}
avgR /= count;
avgG /= count;
avgB /= count;
avgA /= count;
double invGamma = 1 / gamma;
return new ColorRGB(MathE.Pow(avgR, invGamma),
MathE.Pow(avgG, invGamma),
MathE.Pow(avgB, invGamma),
avgA);
}
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),
MathE.Clamp(color.b, min.b, max.b),
MathE.Clamp(color.a, min.a, max.a));
public static ColorRGB ClampMagnitude(ColorRGB color, double minMag, double maxMag)
{
ColorRGB copy = color;
ClampMagnitude(ref copy, minMag, maxMag);
return copy;
}
public static void ClampMagnitude(ref ColorRGB color, double minMag, double maxMag)
{
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
double mag = color.Magnitude;
if (mag < minMag)
{
double factor = minMag / mag;
color.r *= factor;
color.g *= factor;
color.b *= factor;
}
else if (mag > maxMag)
{
double factor = maxMag / mag;
color.r *= factor;
color.g *= factor;
color.b *= factor;
}
}
public static double Dot(ColorRGB a, ColorRGB b) => a.r * b.r + a.g * b.g + a.b * b.b;
public static double Dot(IEnumerable<ColorRGB> colors)
{
bool any = false;
double r = 1, g = 1, b = 1;
foreach (ColorRGB c in colors)
{
r *= c.r;
g *= c.g;
b *= c.b;
}
return any ? (r + g + b) : 0;
}
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);
// Gamma doesn't apply to the alpha channel.
double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp),
newG = MathE.Lerp(aCorrectedG, bCorrectedG, t, clamp),
newB = MathE.Lerp(aCorrectedB, bCorrectedB, t, clamp),
newA = MathE.Lerp(a.a, b.a, t, clamp);
double invGamma = 1 / gamma;
return new ColorRGB(MathE.Pow(newR, invGamma),
MathE.Pow(newG, invGamma),
MathE.Pow(newB, invGamma),
newA);
}
#if CS11_OR_GREATER
static ColorRGB IInterpolable<ColorRGB>.Lerp(ColorRGB a, ColorRGB b, double t, bool clamp) => Lerp(1, a, b, t, clamp);
#endif
public static ColorRGB Product(IEnumerable<ColorRGB> colors)
{
bool any = false;
ColorRGB result = new ColorRGB(1, 1, 1, 1);
foreach (ColorRGB color in colors)
{
any = true;
result *= color;
}
return any ? result : Black;
}
public static ColorRGB Sum(IEnumerable<ColorRGB> colors)
{
bool any = false;
ColorRGB result = new ColorRGB(0, 0, 0, 1);
foreach (ColorRGB color in colors)
{
any = true;
result += color;
}
return any ? result : Black;
}
public static (double[] Rs, double[] Gs, double[] Bs, double[] As) SplitArray(IEnumerable<ColorRGB> colors)
{
int count = colors.Count();
double[] Rs = new double[count], Gs = new double[count], Bs = new double[count], As = new double[count];
int index = 0;
foreach (ColorRGB c in colors)
{
Rs[index] = c.r;
Gs[index] = c.g;
Bs[index] = c.b;
As[index] = c.a;
index++;
}
return (Rs, Gs, Bs, As);
}
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
{
{ ColorChannel.Red, r },
{ ColorChannel.Green, g },
{ ColorChannel.Blue, b },
{ ColorChannel.Alpha, a },
};
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
{
Type type = typeof(TColor);
if (type == typeof(ColorRGB)) return (TColor)(object)this;
else throw new InvalidCastException();
}
public ColorRGB AsRgb() => this;
public IEnumerator<double> GetEnumerator()
{
yield return r;
yield return g;
yield return b;
yield return a;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Deconstruct(out double r, out double g, out double b)
{
r = this.r;
g = this.g;
b = this.b;
}
public void Deconstruct(out double r, out double g, out double b, out double a)
{
r = this.r;
g = this.g;
b = this.b;
a = this.a;
}
public bool Equals(ColorRGB other)
{
if (a <= 0 && b <= 0) return true;
else return r == other.r && g == other.g && b == other.b;
}
public bool Equals(IColor other) => Equals(other.AsRgb());
#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() => $"{{ r={r:0.00}, g={g:0.00}, b={b:0.00}, a={a:0.00} }}";
public double[] ToArray() => new double[] { r, g, b, a };
public Fill<double> ToFill()
{
ColorRGB copy = this;
return i => copy[i];
}
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, 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 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);
public static implicit operator ColorRGB(ListTuple<double> tuple)
{
if (tuple.Length == 3) return new ColorRGB(tuple[0], tuple[1], tuple[2]);
else if (tuple.Length == 4) return new ColorRGB(tuple[0], tuple[1], tuple[2], tuple[3]);
else throw new InvalidCastException();
}
public static implicit operator ColorRGB((double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3);
public static implicit operator ColorRGB((double, double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
public static explicit operator ColorRGB(Float3 group) => new ColorRGB(group.x, group.y, group.z);
public static explicit operator ColorRGB(Float4 group) => new ColorRGB(group.x, group.y, group.z, group.w);
public static implicit operator ListTuple<double>(ColorRGB color) => new ListTuple<double>(color.r, color.g, color.b, color.a);
public static implicit operator ValueTuple<double, double, double>(ColorRGB color) => (color.r, color.g, color.b);
public static implicit operator ValueTuple<double, double, double, double>(ColorRGB color) => (color.r, color.g, color.b, color.a);
}
}

View File

@ -0,0 +1,33 @@
using Nerd_STF.Mathematics;
using System;
using System.Collections.Generic;
namespace Nerd_STF.Graphics
{
public interface IColor : INumberGroupBase<double>
{
Dictionary<ColorChannel, double> GetChannels();
TColor AsColor<TColor>() where TColor : struct, IColor<TColor>;
ColorRGB AsRgb();
bool Equals(IColor other);
}
public interface IColor<TSelf> : IColor,
IEquatable<TSelf>,
INumberGroup<TSelf, double>
#if CS11_OR_GREATER
,IColorPresets<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);
#endif
}
}

View File

@ -0,0 +1,24 @@
#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>
{
static abstract TSelf Black { get; }
static abstract TSelf Blue { get; }
static abstract TSelf Clear { get; }
static abstract TSelf Cyan { get; }
static abstract TSelf Gray { get; }
static abstract TSelf Green { get; }
static abstract TSelf Magenta { get; }
static abstract TSelf Orange { get; }
static abstract TSelf Purple { get; }
static abstract TSelf Red { get; }
static abstract TSelf White { get; }
static abstract TSelf Yellow { get; }
}
}
#endif

View File

@ -46,7 +46,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 2) break;
}
}
public Float2(Fill<double> fill)

View File

@ -49,7 +49,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 3) break;
}
}
public Float3(Fill<double> fill)

View File

@ -53,7 +53,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 4) break;
}
}
public Float4(Fill<double> fill)

View File

@ -7,7 +7,8 @@ namespace Nerd_STF.Mathematics
{
public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>,
IEnumerable<TItem>,
IEquatable<TSelf>
IEquatable<TSelf>,
INumberGroupBase<TItem>
#if CS11_OR_GREATER
, IInterpolable<TSelf>,
ISimpleMathOperations<TSelf>,
@ -17,11 +18,5 @@ namespace Nerd_STF.Mathematics
#if CS11_OR_GREATER
where TItem : INumber<TItem>
#endif
{
TItem this[int index] { get; set; }
TItem[] ToArray();
Fill<TItem> ToFill();
List<TItem> ToList();
}
{ }
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Numerics;
namespace Nerd_STF.Mathematics
{
public interface INumberGroupBase<TItem>
#if CS11_OR_GREATER
where TItem : INumber<TItem>
#endif
{
TItem this[int index] { get; set; }
TItem[] ToArray();
Fill<TItem> ToFill();
List<TItem> ToList();
}
}

View File

@ -5,7 +5,6 @@ using System.Numerics;
namespace Nerd_STF.Mathematics
{
public interface ISimpleMathOperations<TSelf> : IAdditionOperators<TSelf, TSelf, TSelf>,
ISubtractionOperators<TSelf, TSelf, TSelf>,
IMultiplyOperators<TSelf, TSelf, TSelf>
where TSelf : ISimpleMathOperations<TSelf>
{

View File

@ -44,7 +44,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 2) break;
}
}
public Int2(Fill<int> fill)

View File

@ -47,7 +47,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 3) break;
}
}
public Int3(Fill<int> fill)

View File

@ -51,7 +51,7 @@ namespace Nerd_STF.Mathematics
{
this[index] = item;
index++;
if (index >= 2) break;
if (index == 4) break;
}
}
public Int4(Fill<int> fill)

View File

@ -703,7 +703,11 @@ namespace Nerd_STF.Mathematics
public static IEquation Cot(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Cot(inputRad[x], terms));
public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num); // !!TODO!!: Bring back Newton's
// YOU CANNOT USE POW HERE!!!
// The CordicHelper uses the Sqrt function for the Pow method.
// It'll cause a stack overflow.
// !!TODO!! - Bring back Newton's
public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num);
public static IEquation Sqrt(IEquation equ) =>
new Equation((double x) => Sqrt(equ.Get(x)));