Some color format work and indexed colors.

This commit is contained in:
That-One-Nerd 2025-02-18 09:06:41 -05:00
parent e070aa097f
commit 6182db049e
9 changed files with 442 additions and 7 deletions

View File

@ -271,8 +271,8 @@ namespace Nerd_STF.Graphics
// Inlined version of AsRgb().AsHsv() // Inlined version of AsRgb().AsHsv()
double diffK = 1 - k; double diffK = 1 - k;
double r = (1 - c) * diffK, g = (1 - m) * diffK, b = (1 - y) * diffK; double r = (1 - c) * diffK, g = (1 - m) * diffK, b = (1 - y) * diffK;
double[] items = new double[] { r, g, b }; double[] group = new double[] { r, g, b };
double cMax = MathE.Max(items), cMin = MathE.Min(items), delta = cMax - cMin; double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
Angle h; Angle h;
if (delta == 0) h = Angle.Zero; if (delta == 0) h = Angle.Zero;
@ -343,7 +343,7 @@ namespace Nerd_STF.Graphics
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, 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, 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 ColorCMYK operator *(ColorCMYK a, double b) => new ColorCMYK(a.c, a.m, a.y, b == 0 ? 1 : MathE.Clamp(a.k / b, 0, 1), 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, 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);

View File

@ -12,6 +12,7 @@
Cyan, Cyan,
Magenta, Magenta,
Yellow, Yellow,
Key Key,
Index
} }
} }

View File

@ -0,0 +1,158 @@
using Nerd_STF.Graphics.Formats;
using Nerd_STF.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Nerd_STF.Graphics
{
// TODO: Should this be a ref struct?
public class ColorPalette<TColor> : IEnumerable<TColor>, IEquatable<ColorPalette<TColor>>
where TColor : struct, IColor<TColor>
{
public int BitDepth { get; private set; }
public int Length => colors.Length;
private TColor[] colors;
private IndexedColor<TColor>[] indexedColors;
#pragma warning disable CS8618
private ColorPalette() { }
#pragma warning restore CS8618
public ColorPalette(int colors)
{
int size = GetSizeFor(colors, out int bits);
this.colors = new TColor[size];
indexedColors = new IndexedColor<TColor>[size];
for (int i = 0; i < size; i++) indexedColors[i] = new IndexedColor<TColor>(this, i);
BitDepth = bits;
}
public ColorPalette(ReadOnlySpan<TColor> colors)
{
int size = GetSizeFor(colors.Length, out int bits);
this.colors = new TColor[size];
colors.CopyTo(this.colors);
indexedColors = new IndexedColor<TColor>[size];
for (int i = 0; i < size; i++) indexedColors[i] = new IndexedColor<TColor>(this, i);
BitDepth = bits;
}
public static ColorPalette<TColor> FromBitDepth(int bits)
{
int size = 1 << bits;
ColorPalette<TColor> palette = new ColorPalette<TColor>()
{
BitDepth = bits,
colors = new TColor[size],
indexedColors = new IndexedColor<TColor>[size]
};
for (int i = 0; i < size; i++) palette.indexedColors[i] = new IndexedColor<TColor>(palette, i);
return palette;
}
public IndexedColor<TColor> this[int index] => indexedColors[index];
public ref TColor Color(int index) => ref colors[index];
public void Clear()
{
for (int i = 0; i < colors.Length; i++)
{
colors[i] = default;
}
}
public bool Contains(TColor color)
{
for (int i = 0; i < Length; i++)
{
if (colors[i].Equals(color)) return true;
}
return false;
}
public bool Contains(Predicate<TColor> predicate)
{
for (int i = 0; i < Length; i++)
{
if (predicate(colors[i])) return true;
}
return false;
}
public void CopyTo(Span<TColor> destination) => CopyTo(0, destination, 0, Length);
public void CopyTo(int sourceIndex, Span<TColor> destination, int destIndex, int count)
{
for (int i = 0; i < count; i++)
{
destination[destIndex + i] = colors[sourceIndex + i];
}
}
public void Expand(int newSize)
{
int newLength = GetSizeFor(newSize, out int bits);
if (newLength <= Length) return; // Contraction not currently supported.
TColor[] newColors = new TColor[newLength];
IndexedColor<TColor>[] newIndexedColors = new IndexedColor<TColor>[newLength];
Array.Copy(colors, newColors, colors.Length);
Array.Copy(indexedColors, newIndexedColors, indexedColors.Length);
for (int i = Length; i < newLength; i++) newIndexedColors[i] = new IndexedColor<TColor>(this, i);
colors = newColors;
indexedColors = newIndexedColors;
BitDepth = bits;
}
#if CS8_OR_GREATER
public bool ReferenceEquals(ColorPalette<TColor>? other)
#else
public bool ReferenceEquals(ColorPalette<TColor> other)
#endif
{
return ReferenceEquals(this, other);
}
#if CS8_OR_GREATER
public bool Equals(ColorPalette<TColor>? other)
#else
public bool Equals(ColorPalette<TColor> other)
#endif
{
if (other is null) return false;
else if (Length != other.Length) return false;
for (int i = 0; i < Length; i++)
{
if (!colors[i].Equals(other.colors[i])) return false;
}
return true;
}
#if CS8_OR_GREATER
public override bool Equals(object? other)
#else
public override bool Equals(object other)
#endif
{
if (other is null) return false;
else if (other is ColorPalette<TColor> otherColor) return Equals(otherColor);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{BitDepth} BPP Palette: {typeof(TColor).Name}[{Length}]";
public IEnumerator<TColor> GetEnumerator()
{
for (int i = 0; i < colors.Length; i++) yield return colors[i];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private static int GetSizeFor(int colors, out int bitDepth)
{
int maxSize = 1;
bitDepth = 0;
colors--;
while (colors > 0)
{
maxSize <<= 1;
colors >>= 1;
bitDepth++;
}
return maxSize;
}
}
}

View File

@ -287,7 +287,8 @@ namespace Nerd_STF.Graphics
public ColorHSV AsHsv() public ColorHSV AsHsv()
{ {
// Thanks https://www.rapidtables.com/convert/color/rgb-to-hsv.html // Thanks https://www.rapidtables.com/convert/color/rgb-to-hsv.html
double cMax = MathE.Max(this), cMin = MathE.Min(this), delta = cMax - cMin; double[] group = new double[] { r, g, b };
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
Angle h; Angle h;
if (delta == 0) h = Angle.Zero; if (delta == 0) h = Angle.Zero;
@ -302,7 +303,7 @@ namespace Nerd_STF.Graphics
public ColorCMYK AsCmyk() public ColorCMYK AsCmyk()
{ {
// Thanks https://www.rapidtables.com/convert/color/rgb-to-cmyk.html // Thanks https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
double diffK = MathE.Max(this), invDiffK = 1 / diffK; double diffK = MathE.Max(new double[] { r, g, b }), invDiffK = 1 / diffK;
return new ColorCMYK((diffK - r) * invDiffK, return new ColorCMYK((diffK - r) * invDiffK,
(diffK - g) * invDiffK, (diffK - g) * invDiffK,
(diffK - b) * invDiffK, (diffK - b) * invDiffK,

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Nerd_STF.Graphics.Formats
{
public interface IColorFormat
{
int ChannelCount { get; }
int BitLength { get; }
Dictionary<ColorChannel, int> BitsPerChannel { get; }
byte[] GetBitfield(ColorChannel channel);
byte[] GetBits();
IColor GetColor();
// TODO: Bitwriter?
// write to stream
}
public interface IColorFormat<TSelf, TColor> : IColorFormat,
IEquatable<TSelf>
where TSelf : IColorFormat<TSelf, TColor>
where TColor : struct, IColor<TColor>
{
#if CS11_OR_GREATER
new static abstract int BitLength { get; }
int IColorFormat.BitLength => TSelf.BitLength;
new static abstract Dictionary<ColorChannel, int> BitsPerChannel { get; }
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => TSelf.BitsPerChannel;
new static abstract byte[] GetBitfield(ColorChannel channel);
byte[] IColorFormat.GetBitfield(ColorChannel channel) => TSelf.GetBitfield(channel);
static abstract TSelf FromColor(IColor color);
static abstract TSelf FromColor(TColor color);
#endif
new TColor GetColor();
#if CS8_OR_GREATER
IColor IColorFormat.GetColor() => GetColor();
#endif
void SetColor(TColor color);
#if CS11_OR_GREATER
static abstract TSelf operator +(TSelf a, TSelf b);
static abstract TSelf operator *(TSelf a, TSelf b);
static abstract bool operator ==(TSelf a, TSelf b);
static abstract bool operator !=(TSelf a, TSelf b);
#endif
}
}

View File

@ -0,0 +1,80 @@
using Nerd_STF.Helpers;
using Nerd_STF.Mathematics;
using System;
using System.Collections.Generic;
namespace Nerd_STF.Graphics.Formats
{
public class IndexedColor<TColor> : IColorFormat, IEquatable<IndexedColor<TColor>>
where TColor : struct, IColor<TColor>
{
public int BitLength => palette.BitDepth;
public int Index { get; }
int IColorFormat.ChannelCount => 1;
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => new Dictionary<ColorChannel, int>()
{
{ ColorChannel.Index, palette.BitDepth }
};
byte[] IColorFormat.GetBitfield(ColorChannel channel)
{
byte[] buf = new byte[MathE.Ceiling(palette.BitDepth / 8.0)];
if (channel != ColorChannel.Index) return buf; // All zeroes.
int wholes = palette.BitDepth / 8, parts = palette.BitDepth % 8;
for (int i = 0; i < wholes; i++) buf[i] = 0xFF;
for (int i = 0; i < parts; i++) buf[wholes] = (byte)((buf[wholes] << 1) + 1);
return buf;
}
private readonly ColorPalette<TColor> palette;
public IndexedColor(ColorPalette<TColor> palette, int index)
{
this.palette = palette;
Index = index;
}
public ColorPalette<TColor> GetPalette() => palette;
public ref TColor Color() => ref palette.Color(Index);
IColor IColorFormat.GetColor() => Color();
public byte[] GetBits()
{
byte[] buf = new byte[MathE.Ceiling(palette.BitDepth / 8.0)];
int bitIndex = 0, byteIndex = 0, remaining = Index;
while (remaining > 0)
{
buf[byteIndex] |= (byte)((remaining & 1) << bitIndex);
remaining >>= 1;
bitIndex++;
if (bitIndex == 8)
{
bitIndex = 0;
byteIndex++;
}
}
return buf;
}
public bool ReferenceEquals(IndexedColor<TColor> other) => ReferenceEquals(this, other);
#if CS8_OR_GREATER
public bool Equals(IndexedColor<TColor>? other)
#else
public bool Equals(IndexedColor<TColor> other)
#endif
=> !(other is null) && Color().Equals(other.Color());
#if CS8_OR_GREATER
public override bool Equals(object? other)
#else
public override bool Equals(object other)
#endif
{
if (other is null) return false;
else if (other is IndexedColor<TColor> otherIndexed) return Equals(otherIndexed);
else if (other is TColor otherColor) return Color().Equals(otherColor);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"#0x{Index:X}: {Color()}";
}
}

View File

@ -0,0 +1,141 @@
using Nerd_STF.Mathematics;
using System;
using System.Collections.Generic;
using System.IO;
namespace Nerd_STF.Graphics.Formats
{
public class R8G8B8A8 : IColorFormat<R8G8B8A8, ColorRGB>
{
public static int ChannelCount => 4;
public static int BitLength => 32;
public static Dictionary<ColorChannel, int> BitsPerChannel { get; } = new Dictionary<ColorChannel, int>()
{
{ ColorChannel.Red, 8 },
{ ColorChannel.Green, 8 },
{ ColorChannel.Blue, 8 },
{ ColorChannel.Alpha, 8 }
};
int IColorFormat.ChannelCount => ChannelCount;
int IColorFormat.BitLength => BitLength;
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => BitsPerChannel;
public byte R
{
get => r;
set => r = value;
}
public byte G
{
get => g;
set => g = value;
}
public byte B
{
get => b;
set => b = value;
}
public byte A
{
get => a;
set => a = value;
}
private byte r, g, b, a;
public R8G8B8A8(ColorRGB color)
{
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
}
public R8G8B8A8(byte r, byte g, byte b, byte a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public static R8G8B8A8 FromColor(IColor color) => new R8G8B8A8(color.AsRgb());
public static R8G8B8A8 FromColor(ColorRGB color) => new R8G8B8A8(color);
public static byte[] GetBitfield(ColorChannel channel)
{
byte[] buf = new byte[4];
switch (channel)
{
case ColorChannel.Red: buf[0] = 0xFF; break;
case ColorChannel.Green: buf[1] = 0xFF; break;
case ColorChannel.Blue: buf[2] = 0xFF; break;
case ColorChannel.Alpha: buf[3] = 0xFF; break;
}
return buf;
}
byte[] IColorFormat.GetBitfield(ColorChannel channel) => GetBitfield(channel);
#if CS8_OR_GREATER
public bool Equals(R8G8B8A8? other) =>
#else
public bool Equals(R8G8B8A8 other) =>
#endif
!(other is null) && r == other.r && g == other.g && b == other.b && a == other.a;
#if CS8_OR_GREATER
public override bool Equals(object? obj)
#else
public override bool Equals(object obj)
#endif
{
if (obj is null) return false;
else if (obj is R8G8B8A8 formatObj) return Equals(formatObj);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{{ r={r}, g={g}, b={b}, a={a} }}";
public byte[] GetBits() => new byte[] { r, g, b, a };
public ColorRGB GetColor()
{
const double inv255 = 0.00392156862745; // Constant for 1/255
return new ColorRGB(r * inv255,
g * inv255,
b * inv255,
a * inv255);
}
IColor IColorFormat.GetColor() => GetColor();
public void SetColor(ColorRGB color)
{
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
}
public void SetColor(byte r, byte g, byte b, byte a)
{
this.r = (byte)MathE.Clamp(r * 255, 0, 255);
this.g = (byte)MathE.Clamp(g * 255, 0, 255);
this.b = (byte)MathE.Clamp(b * 255, 0, 255);
this.a = (byte)MathE.Clamp(a * 255, 0, 255);
}
public static R8G8B8A8 operator +(R8G8B8A8 a, R8G8B8A8 b)
{
return new R8G8B8A8((byte)MathE.Clamp(a.r + b.r, 0, 255),
(byte)MathE.Clamp(a.g + b.g, 0, 255),
(byte)MathE.Clamp(a.b + b.b, 0, 255),
(byte)MathE.Clamp(a.a + b.a, 0, 255));
}
public static R8G8B8A8 operator *(R8G8B8A8 a, R8G8B8A8 b)
{
const double inv255 = 0.00392156862745; // Constant for 1/255
return new R8G8B8A8((byte)MathE.Clamp(a.r * b.r * inv255, 0, 255),
(byte)MathE.Clamp(a.g * b.g * inv255, 0, 255),
(byte)MathE.Clamp(a.b * b.b * inv255, 0, 255),
(byte)MathE.Clamp(a.a * b.a * inv255, 0, 255));
}
public static bool operator ==(R8G8B8A8 a, R8G8B8A8 b) => a.Equals(b);
public static bool operator !=(R8G8B8A8 a, R8G8B8A8 b) => !a.Equals(b);
}
}

View File

@ -4,6 +4,7 @@ namespace Nerd_STF.Graphics
public interface IColorOperators<TSelf> where TSelf : IColorOperators<TSelf> public interface IColorOperators<TSelf> where TSelf : IColorOperators<TSelf>
{ {
static abstract TSelf operator +(TSelf a, TSelf b); static abstract TSelf operator +(TSelf a, TSelf b);
static abstract TSelf operator *(TSelf a, TSelf b);
static abstract TSelf operator *(TSelf a, double 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, IColor b); static abstract bool operator !=(TSelf a, IColor b);

View File

@ -111,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>
{ {