mirror of
https://github.com/DarthAffe/HPPH.git
synced 2025-12-12 13:28:37 +00:00
Changed current quantization method name to SimpleColorPalette, added placeholder for a more quality focused implementation
This commit is contained in:
parent
15981f82d6
commit
c897b34690
@ -37,6 +37,17 @@ internal class Quantize : IGeneratorFeature
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
Color{{colorFormat.Format}}[] colors = PixelHelper.CreateSimpleColorPalette<Color{{colorFormat.Format}}>(MemoryMarshal.Cast<byte, Color{{colorFormat.Format}}>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
@ -50,6 +61,7 @@ internal class Quantize : IGeneratorFeature
|
|||||||
public partial interface IColorFormat
|
public partial interface IColorFormat
|
||||||
{
|
{
|
||||||
internal IColor[] CreateColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
internal IColor[] CreateColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
||||||
|
internal IColor[] CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,19 +6,19 @@ public static partial class ReferencePixelHelper
|
|||||||
{
|
{
|
||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
public static T[] CreateColorPalette<T>(Image<T> image, int paletteSize)
|
public static T[] CreateSimpleColorPalette<T>(Image<T> image, int paletteSize)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
=> CreateColorPalette<T>(image.ToArray(), paletteSize);
|
=> CreateSimpleColorPalette<T>(image.ToArray(), paletteSize);
|
||||||
|
|
||||||
public static T[] CreateColorPalette<T>(RefImage<T> image, int paletteSize)
|
public static T[] CreateSimpleColorPalette<T>(RefImage<T> image, int paletteSize)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
=> CreateColorPalette<T>(image.ToArray(), paletteSize);
|
=> CreateSimpleColorPalette<T>(image.ToArray(), paletteSize);
|
||||||
|
|
||||||
public static T[] CreateColorPalette<T>(Span<T> colors, int paletteSize)
|
public static T[] CreateSimpleColorPalette<T>(Span<T> colors, int paletteSize)
|
||||||
where T : unmanaged, IColor =>
|
where T : unmanaged, IColor =>
|
||||||
CreateColorPalette<T>(colors.ToArray().Cast<IColor>().ToArray(), paletteSize).Select(x => T.Create(x.R, x.G, x.B, x.A)).Cast<T>().ToArray();
|
CreateSimpleColorPalette<T>(colors.ToArray().Cast<IColor>().ToArray(), paletteSize).Select(x => T.Create(x.R, x.G, x.B, x.A)).Cast<T>().ToArray();
|
||||||
|
|
||||||
private static IColor[] CreateColorPalette<T>(IColor[] colors, int paletteSize)
|
private static IColor[] CreateSimpleColorPalette<T>(IColor[] colors, int paletteSize)
|
||||||
where T : struct, IColor
|
where T : struct, IColor
|
||||||
{
|
{
|
||||||
if (paletteSize == 0) return [];
|
if (paletteSize == 0) return [];
|
||||||
|
|||||||
@ -3,68 +3,73 @@ using HPPH.Reference;
|
|||||||
namespace HPPH.Test.PixelHelper;
|
namespace HPPH.Test.PixelHelper;
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class CreateColorPaletteTests
|
public class CreateSimpleColorPaletteTests
|
||||||
{
|
{
|
||||||
private static IEnumerable<string> GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories);
|
private static IEnumerable<string> GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories);
|
||||||
|
|
||||||
private static int[] Sizes => [0, 1, 2, 16, 64];
|
private static int[] SimpleSizes => [0, 1, 2, 16, 64];
|
||||||
|
private static int[] Sizes => [0, 1, 2, 9, 16, 53];
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, Dictionary<Type, Dictionary<int, IColor[]>>> _simpleReference = [];
|
||||||
private static readonly Dictionary<string, Dictionary<Type, Dictionary<int, IColor[]>>> _reference = [];
|
private static readonly Dictionary<string, Dictionary<Type, Dictionary<int, IColor[]>>> _reference = [];
|
||||||
|
|
||||||
[ClassInitialize]
|
[ClassInitialize]
|
||||||
public static void Initialize(TestContext context)
|
public static void Initialize(TestContext context)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
_reference[image] = [];
|
_simpleReference[image] = [];
|
||||||
|
|
||||||
Initialize<ColorRGB>(image, sizes);
|
Initialize<ColorRGB>(image);
|
||||||
Initialize<ColorBGR>(image, sizes);
|
Initialize<ColorBGR>(image);
|
||||||
Initialize<ColorRGBA>(image, sizes);
|
Initialize<ColorRGBA>(image);
|
||||||
Initialize<ColorBGRA>(image, sizes);
|
Initialize<ColorBGRA>(image);
|
||||||
Initialize<ColorARGB>(image, sizes);
|
Initialize<ColorARGB>(image);
|
||||||
Initialize<ColorABGR>(image, sizes);
|
Initialize<ColorABGR>(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Initialize<T>(string image, ReadOnlySpan<int> sizes)
|
private static void Initialize<T>(string image)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
_reference[image][typeof(T)] = [];
|
_simpleReference[image][typeof(T)] = [];
|
||||||
|
|
||||||
Image<T> img = ImageHelper.GetImage<T>(image);
|
Image<T> img = ImageHelper.GetImage<T>(image);
|
||||||
foreach (int size in sizes)
|
|
||||||
|
foreach (int size in SimpleSizes)
|
||||||
|
_simpleReference[image][typeof(T)][size] = [.. ReferencePixelHelper.CreateSimpleColorPalette(img, size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
foreach (int size in Sizes)
|
||||||
_reference[image][typeof(T)][size] = [.. ReferencePixelHelper.CreateColorPalette(img, size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
_reference[image][typeof(T)][size] = [.. ReferencePixelHelper.CreateColorPalette(img, size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateColorPaletteReadOnlySpan()
|
public void CreateSimpleColorPaletteReadOnlySpan()
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
foreach (int size in sizes)
|
foreach (int size in sizes)
|
||||||
{
|
{
|
||||||
CreateColorPaletteReadOnlySpan<ColorRGB>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorRGB>(image, size);
|
||||||
CreateColorPaletteReadOnlySpan<ColorBGR>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorBGR>(image, size);
|
||||||
CreateColorPaletteReadOnlySpan<ColorRGBA>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorRGBA>(image, size);
|
||||||
CreateColorPaletteReadOnlySpan<ColorBGRA>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorBGRA>(image, size);
|
||||||
CreateColorPaletteReadOnlySpan<ColorARGB>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorARGB>(image, size);
|
||||||
CreateColorPaletteReadOnlySpan<ColorABGR>(image, size);
|
CreateSimpleColorPaletteReadOnlySpan<ColorABGR>(image, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateColorPaletteReadOnlySpan<T>(string image, int size)
|
private void CreateSimpleColorPaletteReadOnlySpan<T>(string image, int size)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
||||||
ReadOnlySpan<T> span = data;
|
ReadOnlySpan<T> span = data;
|
||||||
|
|
||||||
T[] test = [.. span.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
T[] test = [.. span.CreateSimpleColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
IColor[] reference = _reference[image][typeof(T)][size];
|
IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
for (int i = 0; i < reference.Length; i++)
|
for (int i = 0; i < reference.Length; i++)
|
||||||
@ -72,32 +77,32 @@ public class CreateColorPaletteTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateColorPaletteSpan()
|
public void CreateSimpleColorPaletteSpan()
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
foreach (int size in sizes)
|
foreach (int size in sizes)
|
||||||
{
|
{
|
||||||
CreateColorPaletteSpan<ColorRGB>(image, size);
|
CreateSimpleColorPaletteSpan<ColorRGB>(image, size);
|
||||||
CreateColorPaletteSpan<ColorBGR>(image, size);
|
CreateSimpleColorPaletteSpan<ColorBGR>(image, size);
|
||||||
CreateColorPaletteSpan<ColorRGBA>(image, size);
|
CreateSimpleColorPaletteSpan<ColorRGBA>(image, size);
|
||||||
CreateColorPaletteSpan<ColorBGRA>(image, size);
|
CreateSimpleColorPaletteSpan<ColorBGRA>(image, size);
|
||||||
CreateColorPaletteSpan<ColorARGB>(image, size);
|
CreateSimpleColorPaletteSpan<ColorARGB>(image, size);
|
||||||
CreateColorPaletteSpan<ColorABGR>(image, size);
|
CreateSimpleColorPaletteSpan<ColorABGR>(image, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateColorPaletteSpan<T>(string image, int size)
|
private void CreateSimpleColorPaletteSpan<T>(string image, int size)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
||||||
Span<T> span = data;
|
Span<T> span = data;
|
||||||
|
|
||||||
T[] test = [.. span.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
T[] test = [.. span.CreateSimpleColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
IColor[] reference = _reference[image][typeof(T)][size];
|
IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
for (int i = 0; i < reference.Length; i++)
|
for (int i = 0; i < reference.Length; i++)
|
||||||
@ -105,31 +110,31 @@ public class CreateColorPaletteTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateColorPaletteImage()
|
public void CreateSimpleColorPaletteImage()
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
foreach (int size in sizes)
|
foreach (int size in sizes)
|
||||||
{
|
{
|
||||||
CreateColorPaletteImage<ColorRGB>(image, size);
|
CreateSimpleColorPaletteImage<ColorRGB>(image, size);
|
||||||
CreateColorPaletteImage<ColorBGR>(image, size);
|
CreateSimpleColorPaletteImage<ColorBGR>(image, size);
|
||||||
CreateColorPaletteImage<ColorRGBA>(image, size);
|
CreateSimpleColorPaletteImage<ColorRGBA>(image, size);
|
||||||
CreateColorPaletteImage<ColorBGRA>(image, size);
|
CreateSimpleColorPaletteImage<ColorBGRA>(image, size);
|
||||||
CreateColorPaletteImage<ColorARGB>(image, size);
|
CreateSimpleColorPaletteImage<ColorARGB>(image, size);
|
||||||
CreateColorPaletteImage<ColorABGR>(image, size);
|
CreateSimpleColorPaletteImage<ColorABGR>(image, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateColorPaletteImage<T>(string image, int size)
|
private void CreateSimpleColorPaletteImage<T>(string image, int size)
|
||||||
where T : struct, IColor
|
where T : struct, IColor
|
||||||
{
|
{
|
||||||
IImage data = ImageHelper.GetImage<T>(image);
|
IImage data = ImageHelper.GetImage<T>(image);
|
||||||
|
|
||||||
IColor[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
IColor[] test = [.. data.CreateSimpleColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
IColor[] reference = _reference[image][typeof(T)][size];
|
IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
for (int i = 0; i < reference.Length; i++)
|
for (int i = 0; i < reference.Length; i++)
|
||||||
@ -137,31 +142,31 @@ public class CreateColorPaletteTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateColorPaletteGenericImage()
|
public void CreateSimpleColorPaletteGenericImage()
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
foreach (int size in sizes)
|
foreach (int size in sizes)
|
||||||
{
|
{
|
||||||
CreateColorPaletteGenericImage<ColorRGB>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorRGB>(image, size);
|
||||||
CreateColorPaletteGenericImage<ColorBGR>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorBGR>(image, size);
|
||||||
CreateColorPaletteGenericImage<ColorRGBA>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorRGBA>(image, size);
|
||||||
CreateColorPaletteGenericImage<ColorBGRA>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorBGRA>(image, size);
|
||||||
CreateColorPaletteGenericImage<ColorARGB>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorARGB>(image, size);
|
||||||
CreateColorPaletteGenericImage<ColorABGR>(image, size);
|
CreateSimpleColorPaletteGenericImage<ColorABGR>(image, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateColorPaletteGenericImage<T>(string image, int size)
|
private void CreateSimpleColorPaletteGenericImage<T>(string image, int size)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
Image<T> data = ImageHelper.GetImage<T>(image);
|
Image<T> data = ImageHelper.GetImage<T>(image);
|
||||||
|
|
||||||
T[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
T[] test = [.. data.CreateSimpleColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
IColor[] reference = _reference[image][typeof(T)][size];
|
IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
for (int i = 0; i < reference.Length; i++)
|
for (int i = 0; i < reference.Length; i++)
|
||||||
@ -169,34 +174,196 @@ public class CreateColorPaletteTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateColorPaletteRefImage()
|
public void CreateSimpleColorPaletteRefImage()
|
||||||
{
|
{
|
||||||
ReadOnlySpan<int> sizes = Sizes;
|
ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
foreach (string image in GetTestImages())
|
foreach (string image in GetTestImages())
|
||||||
{
|
{
|
||||||
foreach (int size in sizes)
|
foreach (int size in sizes)
|
||||||
{
|
{
|
||||||
CreateColorPaletteRefImage<ColorRGB>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorRGB>(image, size);
|
||||||
CreateColorPaletteRefImage<ColorBGR>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorBGR>(image, size);
|
||||||
CreateColorPaletteRefImage<ColorRGBA>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorRGBA>(image, size);
|
||||||
CreateColorPaletteRefImage<ColorBGRA>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorBGRA>(image, size);
|
||||||
CreateColorPaletteRefImage<ColorARGB>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorARGB>(image, size);
|
||||||
CreateColorPaletteRefImage<ColorABGR>(image, size);
|
CreateSimpleColorPaletteRefImage<ColorABGR>(image, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateColorPaletteRefImage<T>(string image, int size)
|
private void CreateSimpleColorPaletteRefImage<T>(string image, int size)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
RefImage<T> data = ImageHelper.GetImage<T>(image).AsRefImage();
|
RefImage<T> data = ImageHelper.GetImage<T>(image).AsRefImage();
|
||||||
|
|
||||||
T[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
T[] test = [.. data.CreateSimpleColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
IColor[] reference = _reference[image][typeof(T)][size];
|
IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
for (int i = 0; i < reference.Length; i++)
|
for (int i = 0; i < reference.Length; i++)
|
||||||
Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public void CreateColorPaletteReadOnlySpan()
|
||||||
|
//{
|
||||||
|
// ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
|
// foreach (string image in GetTestImages())
|
||||||
|
// {
|
||||||
|
// foreach (int size in sizes)
|
||||||
|
// {
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorRGB>(image, size);
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorBGR>(image, size);
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorRGBA>(image, size);
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorBGRA>(image, size);
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorARGB>(image, size);
|
||||||
|
// CreateColorPaletteReadOnlySpan<ColorABGR>(image, size);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void CreateColorPaletteReadOnlySpan<T>(string image, int size)
|
||||||
|
// where T : unmanaged, IColor
|
||||||
|
//{
|
||||||
|
// T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
||||||
|
// ReadOnlySpan<T> span = data;
|
||||||
|
|
||||||
|
// T[] test = [.. span.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
// IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
|
// Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
|
// for (int i = 0; i < reference.Length; i++)
|
||||||
|
// Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public void CreateColorPaletteSpan()
|
||||||
|
//{
|
||||||
|
// ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
|
// foreach (string image in GetTestImages())
|
||||||
|
// {
|
||||||
|
// foreach (int size in sizes)
|
||||||
|
// {
|
||||||
|
// CreateColorPaletteSpan<ColorRGB>(image, size);
|
||||||
|
// CreateColorPaletteSpan<ColorBGR>(image, size);
|
||||||
|
// CreateColorPaletteSpan<ColorRGBA>(image, size);
|
||||||
|
// CreateColorPaletteSpan<ColorBGRA>(image, size);
|
||||||
|
// CreateColorPaletteSpan<ColorARGB>(image, size);
|
||||||
|
// CreateColorPaletteSpan<ColorABGR>(image, size);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void CreateColorPaletteSpan<T>(string image, int size)
|
||||||
|
// where T : unmanaged, IColor
|
||||||
|
//{
|
||||||
|
// T[] data = ImageHelper.GetColorsFromImage<T>(image);
|
||||||
|
// Span<T> span = data;
|
||||||
|
|
||||||
|
// T[] test = [.. span.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
// IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
|
// Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
|
// for (int i = 0; i < reference.Length; i++)
|
||||||
|
// Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public void CreateColorPaletteImage()
|
||||||
|
//{
|
||||||
|
// ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
|
// foreach (string image in GetTestImages())
|
||||||
|
// {
|
||||||
|
// foreach (int size in sizes)
|
||||||
|
// {
|
||||||
|
// CreateColorPaletteImage<ColorRGB>(image, size);
|
||||||
|
// CreateColorPaletteImage<ColorBGR>(image, size);
|
||||||
|
// CreateColorPaletteImage<ColorRGBA>(image, size);
|
||||||
|
// CreateColorPaletteImage<ColorBGRA>(image, size);
|
||||||
|
// CreateColorPaletteImage<ColorARGB>(image, size);
|
||||||
|
// CreateColorPaletteImage<ColorABGR>(image, size);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void CreateColorPaletteImage<T>(string image, int size)
|
||||||
|
// where T : struct, IColor
|
||||||
|
//{
|
||||||
|
// IImage data = ImageHelper.GetImage<T>(image);
|
||||||
|
|
||||||
|
// IColor[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
// IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
|
// Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
|
// for (int i = 0; i < reference.Length; i++)
|
||||||
|
// Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public void CreateColorPaletteGenericImage()
|
||||||
|
//{
|
||||||
|
// ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
|
// foreach (string image in GetTestImages())
|
||||||
|
// {
|
||||||
|
// foreach (int size in sizes)
|
||||||
|
// {
|
||||||
|
// CreateColorPaletteGenericImage<ColorRGB>(image, size);
|
||||||
|
// CreateColorPaletteGenericImage<ColorBGR>(image, size);
|
||||||
|
// CreateColorPaletteGenericImage<ColorRGBA>(image, size);
|
||||||
|
// CreateColorPaletteGenericImage<ColorBGRA>(image, size);
|
||||||
|
// CreateColorPaletteGenericImage<ColorARGB>(image, size);
|
||||||
|
// CreateColorPaletteGenericImage<ColorABGR>(image, size);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void CreateColorPaletteGenericImage<T>(string image, int size)
|
||||||
|
// where T : unmanaged, IColor
|
||||||
|
//{
|
||||||
|
// Image<T> data = ImageHelper.GetImage<T>(image);
|
||||||
|
|
||||||
|
// T[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
// IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
|
// Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
|
// for (int i = 0; i < reference.Length; i++)
|
||||||
|
// Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public void CreateColorPaletteRefImage()
|
||||||
|
//{
|
||||||
|
// ReadOnlySpan<int> sizes = SimpleSizes;
|
||||||
|
// foreach (string image in GetTestImages())
|
||||||
|
// {
|
||||||
|
// foreach (int size in sizes)
|
||||||
|
// {
|
||||||
|
// CreateColorPaletteRefImage<ColorRGB>(image, size);
|
||||||
|
// CreateColorPaletteRefImage<ColorBGR>(image, size);
|
||||||
|
// CreateColorPaletteRefImage<ColorRGBA>(image, size);
|
||||||
|
// CreateColorPaletteRefImage<ColorBGRA>(image, size);
|
||||||
|
// CreateColorPaletteRefImage<ColorARGB>(image, size);
|
||||||
|
// CreateColorPaletteRefImage<ColorABGR>(image, size);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void CreateColorPaletteRefImage<T>(string image, int size)
|
||||||
|
// where T : unmanaged, IColor
|
||||||
|
//{
|
||||||
|
// RefImage<T> data = ImageHelper.GetImage<T>(image).AsRefImage();
|
||||||
|
|
||||||
|
// T[] test = [.. data.CreateColorPalette(size).OrderBy(x => x.R).ThenBy(x => x.G).ThenBy(x => x.B).ThenBy(x => x.A)];
|
||||||
|
|
||||||
|
// IColor[] reference = _simpleReference[image][typeof(T)][size];
|
||||||
|
// Assert.AreEqual(reference.Length, test.Length, $"Palette Size differs for image {image}, size {size}");
|
||||||
|
|
||||||
|
// for (int i = 0; i < reference.Length; i++)
|
||||||
|
// Assert.AreEqual(reference[i], test[i], $"Index {i} differs for image {image}, size {size}");
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatABGR
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorABGR[] colors = PixelHelper.CreateSimpleColorPalette<ColorABGR>(MemoryMarshal.Cast<byte, ColorABGR>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatARGB
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorARGB[] colors = PixelHelper.CreateSimpleColorPalette<ColorARGB>(MemoryMarshal.Cast<byte, ColorARGB>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatBGR
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorBGR[] colors = PixelHelper.CreateSimpleColorPalette<ColorBGR>(MemoryMarshal.Cast<byte, ColorBGR>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatBGRA
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorBGRA[] colors = PixelHelper.CreateSimpleColorPalette<ColorBGRA>(MemoryMarshal.Cast<byte, ColorBGRA>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatRGB
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorRGB[] colors = PixelHelper.CreateSimpleColorPalette<ColorRGB>(MemoryMarshal.Cast<byte, ColorRGB>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -17,5 +17,16 @@ public sealed partial class ColorFormatRGBA
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe IColor[] IColorFormat.CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize)
|
||||||
|
{
|
||||||
|
ColorRGBA[] colors = PixelHelper.CreateSimpleColorPalette<ColorRGBA>(MemoryMarshal.Cast<byte, ColorRGBA>(data), paletteSize);
|
||||||
|
|
||||||
|
IColor[] result = new IColor[colors.Length];
|
||||||
|
for(int i = 0; i < colors.Length; i++)
|
||||||
|
result[i] = colors[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@ -3,4 +3,5 @@
|
|||||||
public partial interface IColorFormat
|
public partial interface IColorFormat
|
||||||
{
|
{
|
||||||
internal IColor[] CreateColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
internal IColor[] CreateColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
||||||
|
internal IColor[] CreateSimpleColorPalette(ReadOnlySpan<byte> data, int paletteSize);
|
||||||
}
|
}
|
||||||
@ -66,24 +66,126 @@ public static partial class PixelHelper
|
|||||||
public static T[] CreateColorPalette<T>(this Span<T> colors, int paletteSize)
|
public static T[] CreateColorPalette<T>(this Span<T> colors, int paletteSize)
|
||||||
where T : unmanaged, IColor
|
where T : unmanaged, IColor
|
||||||
{
|
{
|
||||||
if (paletteSize < 0) throw new ArgumentException("PaletteSize Can't be < 0", nameof(paletteSize));
|
throw new NotImplementedException("This requires some more research and will be implemented later");
|
||||||
|
|
||||||
|
//TODO DarthAffe 21.07.2024: Run some tests how the result to performance ratio is with the options described at http://blog.pkh.me/p/39-improving-color-quantization-heuristics.html
|
||||||
|
|
||||||
|
//if (paletteSize < 0) throw new ArgumentException("PaletteSize can't be < 0", nameof(paletteSize));
|
||||||
|
//if (paletteSize == 0) return [];
|
||||||
|
|
||||||
|
//Span<ColorCube<T>> cubes = new ColorCube<T>[paletteSize];
|
||||||
|
//cubes[0] = new ColorCube<T>(0, colors.Length, SortTarget.None);
|
||||||
|
|
||||||
|
//for (int i = 0; i < paletteSize; i++)
|
||||||
|
//{
|
||||||
|
// int splitCubeIndex = 0;
|
||||||
|
|
||||||
|
// for (int j = 0; j < paletteSize; j++)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// cubes[splitCubeIndex].Split(colors, out ColorCube<T> a, out ColorCube<T> b);
|
||||||
|
// cubes[splitCubeIndex] = a;
|
||||||
|
// cubes[i + 1] = b;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//T[] result = new T[cubes.Length];
|
||||||
|
//for (int i = 0; i < cubes.Length; i++)
|
||||||
|
// result[i] = Average(cubes[i].Slice(colors));
|
||||||
|
|
||||||
|
//return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IColor[] CreateSimpleColorPalette(this IImage image, int paletteSize)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(image);
|
||||||
|
|
||||||
|
int dataLength = image.SizeInBytes;
|
||||||
|
byte[] array = ArrayPool<byte>.Shared.Rent(dataLength);
|
||||||
|
Span<byte> buffer = array.AsSpan()[..dataLength];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image.CopyTo(buffer);
|
||||||
|
return image.ColorFormat.CreateSimpleColorPalette(buffer, paletteSize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T[] CreateSimpleColorPalette<T>(this IImage<T> image, int paletteSize)
|
||||||
|
where T : unmanaged, IColor
|
||||||
|
=> image.AsRefImage().CreateSimpleColorPalette(paletteSize);
|
||||||
|
|
||||||
|
public static T[] CreateSimpleColorPalette<T>(this RefImage<T> image, int paletteSize)
|
||||||
|
where T : unmanaged, IColor
|
||||||
|
{
|
||||||
|
int dataLength = image.Width * image.Height;
|
||||||
|
T[] array = ArrayPool<T>.Shared.Rent(dataLength);
|
||||||
|
Span<T> buffer = array.AsSpan()[..(dataLength)];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image.CopyTo(buffer);
|
||||||
|
return CreateSimpleColorPalette(buffer, paletteSize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<T>.Shared.Return(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T[] CreateSimpleColorPalette<T>(this ReadOnlySpan<T> colors, int paletteSize)
|
||||||
|
where T : unmanaged, IColor
|
||||||
|
{
|
||||||
|
T[] buffer = ArrayPool<T>.Shared.Rent(colors.Length);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Span<T> colorBuffer = buffer.AsSpan()[..colors.Length];
|
||||||
|
colors.CopyTo(colorBuffer);
|
||||||
|
|
||||||
|
return CreateSimpleColorPalette(colorBuffer, paletteSize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<T>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe T[] CreateSimpleColorPalette<T>(this Span<T> colors, int paletteSize)
|
||||||
|
where T : unmanaged, IColor
|
||||||
|
{
|
||||||
|
if (paletteSize < 0) throw new ArgumentException("PaletteSize can't be < 0", nameof(paletteSize));
|
||||||
if (paletteSize == 0) return [];
|
if (paletteSize == 0) return [];
|
||||||
|
|
||||||
|
if (!BitOperations.IsPow2(paletteSize)) throw new ArgumentException("PaletteSize has to be a power of 2", nameof(paletteSize));
|
||||||
|
|
||||||
int splits = BitOperations.Log2((uint)paletteSize);
|
int splits = BitOperations.Log2((uint)paletteSize);
|
||||||
|
|
||||||
Span<ColorCube<T>> cubes = new ColorCube<T>[1 << splits];
|
ColorCube<T>[] cubes = new ColorCube<T>[1 << splits];
|
||||||
cubes[0] = new ColorCube<T>(0, colors.Length, SortTarget.None);
|
cubes[0] = new ColorCube<T>(0, colors.Length, SortTarget.None);
|
||||||
|
|
||||||
int currentIndex = 0;
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
|
||||||
for (int i = 0; i < splits; i++)
|
|
||||||
|
int colorsLength = colors.Length;
|
||||||
|
fixed (T* colorsPtr = colors)
|
||||||
{
|
{
|
||||||
int currentCubeCount = 1 << i;
|
//HACK DarthAffe 21.07.2024: This is dangerous and needs to be treated carefully! It's used in the anonymous method used to run the splits in parallel and absolutely can't be used outside this fixed block!
|
||||||
Span<ColorCube<T>> currentCubes = cubes[..currentCubeCount];
|
T* unsafeColorsPtr = colorsPtr;
|
||||||
for (int j = 0; j < currentCubes.Length; j++)
|
|
||||||
|
for (int i = 0; i < splits; i++)
|
||||||
{
|
{
|
||||||
currentCubes[j].Split(colors, out ColorCube<T> a, out ColorCube<T> b);
|
int currentCubeCount = 1 << i;
|
||||||
currentCubes[j] = a;
|
|
||||||
cubes[++currentIndex] = b;
|
Parallel.For(0, currentCubeCount, parallelOptions, CreateCubes);
|
||||||
|
|
||||||
|
void CreateCubes(int index)
|
||||||
|
{
|
||||||
|
cubes[index].Split(new Span<T>(unsafeColorsPtr, colorsLength), out ColorCube<T> a, out ColorCube<T> b);
|
||||||
|
cubes[index] = a;
|
||||||
|
cubes[currentCubeCount + index] = b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,5 +21,7 @@ public static unsafe partial class PixelHelper
|
|||||||
public static partial void SortByAlpha<T>(this Span<T> colors)
|
public static partial void SortByAlpha<T>(this Span<T> colors)
|
||||||
where T : unmanaged, IColor;
|
where T : unmanaged, IColor;
|
||||||
|
|
||||||
|
//TODO DarthAffe 21.07.2024: Add CIE-Sorting
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ public static unsafe partial class PixelHelper
|
|||||||
|
|
||||||
return T.ColorFormat.BytesPerPixel switch
|
return T.ColorFormat.BytesPerPixel switch
|
||||||
{
|
{
|
||||||
// DarthAffe 05.07.2024: Important: The sum of 3-byte colors are result in 4 byte data!
|
// DarthAffe 05.07.2024: Important: The sum of 3-byte colors result in 4 byte data!
|
||||||
3 => Unsafe.BitCast<Generic4LongData, TSum>(Sum(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
|
3 => Unsafe.BitCast<Generic4LongData, TSum>(Sum(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
|
||||||
4 => Unsafe.BitCast<Generic4LongData, TSum>(Sum(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
4 => Unsafe.BitCast<Generic4LongData, TSum>(Sum(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
||||||
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
|
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user