Some color format work and indexed colors.
This commit is contained in:
parent
e070aa097f
commit
6182db049e
@ -271,8 +271,8 @@ namespace Nerd_STF.Graphics
|
||||
// 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;
|
||||
double[] group = new double[] { r, g, b };
|
||||
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
|
||||
Angle h;
|
||||
|
||||
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, 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, ColorCMYK b) => a.Equals(b);
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
Cyan,
|
||||
Magenta,
|
||||
Yellow,
|
||||
Key
|
||||
Key,
|
||||
Index
|
||||
}
|
||||
}
|
||||
|
||||
158
Nerd_STF/Graphics/ColorPalette.cs
Normal file
158
Nerd_STF/Graphics/ColorPalette.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -287,7 +287,8 @@ namespace Nerd_STF.Graphics
|
||||
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;
|
||||
double[] group = new double[] { r, g, b };
|
||||
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
|
||||
Angle h;
|
||||
|
||||
if (delta == 0) h = Angle.Zero;
|
||||
@ -302,7 +303,7 @@ namespace Nerd_STF.Graphics
|
||||
public ColorCMYK AsCmyk()
|
||||
{
|
||||
// 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,
|
||||
(diffK - g) * invDiffK,
|
||||
(diffK - b) * invDiffK,
|
||||
|
||||
53
Nerd_STF/Graphics/Formats/IColorFormat.cs
Normal file
53
Nerd_STF/Graphics/Formats/IColorFormat.cs
Normal 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
|
||||
}
|
||||
}
|
||||
80
Nerd_STF/Graphics/Formats/IndexedColor.cs
Normal file
80
Nerd_STF/Graphics/Formats/IndexedColor.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
141
Nerd_STF/Graphics/Formats/R8G8B8A8.cs
Normal file
141
Nerd_STF/Graphics/Formats/R8G8B8A8.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ 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, 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);
|
||||
|
||||
@ -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, 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>
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user