mirror of
https://github.com/DarthAffe/HPPH.git
synced 2025-12-13 05:48:57 +00:00
Implemented a first draft of a ColorFormat-conversion
This commit is contained in:
parent
9f37a41f55
commit
ccd4a8d5c8
@ -1,9 +1,12 @@
|
|||||||
using System.Text;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace HPPH.Generators;
|
namespace HPPH.Generators;
|
||||||
|
|
||||||
internal readonly struct ColorFormatData(string typeName, int bpp, char firstEntry, char secondEntry, char thirdEntry, char fourthEntry)
|
internal readonly struct ColorFormatData(string typeName, int bpp, char firstEntry, char secondEntry, char thirdEntry, char fourthEntry)
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
public readonly string TypeName = typeName;
|
public readonly string TypeName = typeName;
|
||||||
public readonly int Bpp = bpp;
|
public readonly int Bpp = bpp;
|
||||||
public readonly string FirstEntry = firstEntry.ToString().ToLowerInvariant();
|
public readonly string FirstEntry = firstEntry.ToString().ToLowerInvariant();
|
||||||
@ -16,6 +19,8 @@ internal readonly struct ColorFormatData(string typeName, int bpp, char firstEnt
|
|||||||
public string ThirdEntryName => GetEntryName(ThirdEntry);
|
public string ThirdEntryName => GetEntryName(ThirdEntry);
|
||||||
public string FourthEntryName => GetEntryName(FourthEntry);
|
public string FourthEntryName => GetEntryName(FourthEntry);
|
||||||
|
|
||||||
|
public string ByteMapping => CreateByteMapping();
|
||||||
|
|
||||||
public string Format
|
public string Format
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -38,13 +43,55 @@ internal readonly struct ColorFormatData(string typeName, int bpp, char firstEnt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
private string CreateByteMapping()
|
||||||
|
{
|
||||||
|
string[] mapping = new string[Bpp];
|
||||||
|
if (Bpp > 0)
|
||||||
|
{
|
||||||
|
mapping[0] = GetByteMappingIndex(FirstEntry).ToString();
|
||||||
|
|
||||||
|
if (Bpp > 1)
|
||||||
|
{
|
||||||
|
mapping[1] = GetByteMappingIndex(SecondEntry).ToString();
|
||||||
|
|
||||||
|
if (Bpp > 2)
|
||||||
|
{
|
||||||
|
mapping[2] = GetByteMappingIndex(ThirdEntry).ToString();
|
||||||
|
|
||||||
|
if (Bpp > 3)
|
||||||
|
{
|
||||||
|
mapping[3] = GetByteMappingIndex(FourthEntry).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(", ", mapping);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetEntryName(string entry)
|
private static string GetEntryName(string entry)
|
||||||
=> entry switch
|
=> entry switch
|
||||||
{
|
{
|
||||||
"a" => "Alpha",
|
|
||||||
"r" => "Red",
|
"r" => "Red",
|
||||||
"g" => "Green",
|
"g" => "Green",
|
||||||
"b" => "Blue",
|
"b" => "Blue",
|
||||||
|
"a" => "Alpha",
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static int GetByteMappingIndex(string entry)
|
||||||
|
=> entry switch
|
||||||
|
{
|
||||||
|
"r" => 0,
|
||||||
|
"g" => 1,
|
||||||
|
"b" => 2,
|
||||||
|
"a" => 3,
|
||||||
|
_ => throw new IndexOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ internal class Colors : IGeneratorFeature
|
|||||||
|
|
||||||
public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray<ColorFormatData> colorFormats)
|
public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray<ColorFormatData> colorFormats)
|
||||||
{
|
{
|
||||||
yield return ("IColorFormat.Instances.cs", GenerateColorFormats(colorFormats));
|
yield return ("IColorFormat.Instances", GenerateColorFormats(colorFormats));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateColorStructCode(ColorFormatData colorFormat)
|
private static string GenerateColorStructCode(ColorFormatData colorFormat)
|
||||||
@ -165,6 +165,8 @@ internal class Colors : IGeneratorFeature
|
|||||||
|
|
||||||
public string Name => "{{colorFormat.Format}}";
|
public string Name => "{{colorFormat.Format}}";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [{{colorFormat.ByteMapping}}];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
88
HPPH.Test/ConvertTests.cs
Normal file
88
HPPH.Test/ConvertTests.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
namespace HPPH.Test;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ConvertTests
|
||||||
|
{
|
||||||
|
private static IEnumerable<string> GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert3ByteSameBppRGBToBGR()
|
||||||
|
{
|
||||||
|
foreach (string image in GetTestImages())
|
||||||
|
{
|
||||||
|
ColorRGB[] data = ImageHelper.Get3ByteColorsFromImage(image);
|
||||||
|
ReadOnlySpan<ColorRGB> referenceData = data;
|
||||||
|
|
||||||
|
Span<ColorRGB> sourceData = new ColorRGB[referenceData.Length];
|
||||||
|
referenceData.CopyTo(sourceData);
|
||||||
|
|
||||||
|
Span<ColorBGR> result = PixelHelper.Convert<ColorRGB, ColorBGR>(sourceData);
|
||||||
|
|
||||||
|
Assert.AreEqual(referenceData.Length, result.Length);
|
||||||
|
for (int i = 0; i < referenceData.Length; i++)
|
||||||
|
{
|
||||||
|
ColorRGB reference = referenceData[i];
|
||||||
|
ColorBGR test = result[i];
|
||||||
|
|
||||||
|
Assert.AreEqual(reference.R, test.R, "R differs");
|
||||||
|
Assert.AreEqual(reference.G, test.G, "G differs");
|
||||||
|
Assert.AreEqual(reference.B, test.B, "B differs");
|
||||||
|
Assert.AreEqual(reference.A, test.A, "A differs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert4ByteSameBppRGBAToARGB()
|
||||||
|
{
|
||||||
|
foreach (string image in GetTestImages())
|
||||||
|
{
|
||||||
|
ColorRGBA[] data = ImageHelper.Get4ByteColorsFromImage(image);
|
||||||
|
ReadOnlySpan<ColorRGBA> referenceData = data;
|
||||||
|
|
||||||
|
Span<ColorRGBA> sourceData = new ColorRGBA[referenceData.Length];
|
||||||
|
referenceData.CopyTo(sourceData);
|
||||||
|
|
||||||
|
Span<ColorARGB> result = PixelHelper.Convert<ColorRGBA, ColorARGB>(sourceData);
|
||||||
|
|
||||||
|
Assert.AreEqual(referenceData.Length, result.Length);
|
||||||
|
for (int i = 0; i < referenceData.Length; i++)
|
||||||
|
{
|
||||||
|
ColorRGBA reference = referenceData[i];
|
||||||
|
ColorARGB test = result[i];
|
||||||
|
|
||||||
|
Assert.AreEqual(reference.R, test.R, "R differs");
|
||||||
|
Assert.AreEqual(reference.G, test.G, "G differs");
|
||||||
|
Assert.AreEqual(reference.B, test.B, "B differs");
|
||||||
|
Assert.AreEqual(reference.A, test.A, "A differs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert4ByteSameBppRGBAToBGRA()
|
||||||
|
{
|
||||||
|
foreach (string image in GetTestImages())
|
||||||
|
{
|
||||||
|
ColorRGBA[] data = ImageHelper.Get4ByteColorsFromImage(image);
|
||||||
|
ReadOnlySpan<ColorRGBA> referenceData = data;
|
||||||
|
|
||||||
|
Span<ColorRGBA> sourceData = new ColorRGBA[referenceData.Length];
|
||||||
|
referenceData.CopyTo(sourceData);
|
||||||
|
|
||||||
|
Span<ColorBGRA> result = PixelHelper.Convert<ColorRGBA, ColorBGRA>(sourceData);
|
||||||
|
|
||||||
|
Assert.AreEqual(referenceData.Length, result.Length);
|
||||||
|
for (int i = 0; i < referenceData.Length; i++)
|
||||||
|
{
|
||||||
|
ColorRGBA reference = referenceData[i];
|
||||||
|
ColorBGRA test = result[i];
|
||||||
|
|
||||||
|
Assert.AreEqual(reference.R, test.R, "R differs");
|
||||||
|
Assert.AreEqual(reference.G, test.G, "G differs");
|
||||||
|
Assert.AreEqual(reference.B, test.B, "B differs");
|
||||||
|
Assert.AreEqual(reference.A, test.A, "A differs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,4 +12,6 @@ public partial interface IColorFormat
|
|||||||
int BytesPerPixel { get; }
|
int BytesPerPixel { get; }
|
||||||
|
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
|
||||||
|
internal ReadOnlySpan<byte> ByteMapping { get; }
|
||||||
}
|
}
|
||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatABGR : IColorFormat
|
|||||||
|
|
||||||
public string Name => "ABGR";
|
public string Name => "ABGR";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [3, 2, 1, 0];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatARGB : IColorFormat
|
|||||||
|
|
||||||
public string Name => "ARGB";
|
public string Name => "ARGB";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [3, 0, 1, 2];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatBGR : IColorFormat
|
|||||||
|
|
||||||
public string Name => "BGR";
|
public string Name => "BGR";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [2, 1, 0];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatBGRA : IColorFormat
|
|||||||
|
|
||||||
public string Name => "BGRA";
|
public string Name => "BGRA";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [2, 1, 0, 3];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatRGB : IColorFormat
|
|||||||
|
|
||||||
public string Name => "RGB";
|
public string Name => "RGB";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [0, 1, 2];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@ -10,6 +10,8 @@ public sealed partial class ColorFormatRGBA : IColorFormat
|
|||||||
|
|
||||||
public string Name => "RGBA";
|
public string Name => "RGBA";
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> IColorFormat.ByteMapping => [0, 1, 2, 3];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
155
HPPH/PixelHelper.Convert.cs
Normal file
155
HPPH/PixelHelper.Convert.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
|
namespace HPPH;
|
||||||
|
|
||||||
|
public static unsafe partial class PixelHelper
|
||||||
|
{
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public static Span<TTarget> Convert<TSource, TTarget>(Span<TSource> data)
|
||||||
|
where TSource : struct, IColor
|
||||||
|
where TTarget : struct, IColor
|
||||||
|
{
|
||||||
|
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||||
|
|
||||||
|
Convert(MemoryMarshal.AsBytes(data), TSource.ColorFormat, TTarget.ColorFormat);
|
||||||
|
|
||||||
|
return MemoryMarshal.Cast<TSource, TTarget>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Convert(Span<byte> data, IColorFormat sourceFormat, IColorFormat targetFormat)
|
||||||
|
{
|
||||||
|
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||||
|
ArgumentNullException.ThrowIfNull(sourceFormat);
|
||||||
|
ArgumentNullException.ThrowIfNull(targetFormat);
|
||||||
|
|
||||||
|
if (sourceFormat == targetFormat) return;
|
||||||
|
|
||||||
|
if (sourceFormat.BytesPerPixel == targetFormat.BytesPerPixel)
|
||||||
|
ConvertEqualBpp(data, sourceFormat, targetFormat);
|
||||||
|
else if ((sourceFormat.BytesPerPixel == 3) && (targetFormat.BytesPerPixel == 4))
|
||||||
|
ConvertWiden3To4Bytes(data, sourceFormat, targetFormat);
|
||||||
|
else if ((sourceFormat.BytesPerPixel == 4) && (targetFormat.BytesPerPixel == 3))
|
||||||
|
ConvertNarrow4To3Bytes(data, sourceFormat, targetFormat);
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("Data is not of a supported valid color-type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertEqualBpp(Span<byte> data, IColorFormat sourceFormat, IColorFormat targetFormat)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
|
||||||
|
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
|
||||||
|
|
||||||
|
switch (sourceFormat.BytesPerPixel)
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
ReadOnlySpan<byte> mapping3 = [targetMapping[sourceMapping[0]], targetMapping[sourceMapping[1]], targetMapping[sourceMapping[2]]];
|
||||||
|
ReadOnlySpan<byte> mask3 =
|
||||||
|
[
|
||||||
|
mapping3[0],
|
||||||
|
mapping3[1],
|
||||||
|
mapping3[2],
|
||||||
|
|
||||||
|
(byte)(mapping3[0] + 3),
|
||||||
|
(byte)(mapping3[1] + 3),
|
||||||
|
(byte)(mapping3[2] + 3),
|
||||||
|
|
||||||
|
(byte)(mapping3[0] + 6),
|
||||||
|
(byte)(mapping3[1] + 6),
|
||||||
|
(byte)(mapping3[2] + 6),
|
||||||
|
|
||||||
|
(byte)(mapping3[0] + 9),
|
||||||
|
(byte)(mapping3[1] + 9),
|
||||||
|
(byte)(mapping3[2] + 9),
|
||||||
|
|
||||||
|
(byte)(mapping3[0] + 12),
|
||||||
|
(byte)(mapping3[1] + 12),
|
||||||
|
(byte)(mapping3[2] + 12),
|
||||||
|
|
||||||
|
15
|
||||||
|
];
|
||||||
|
|
||||||
|
ConvertEqualBpp(data, mask3, 3);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
ReadOnlySpan<byte> mapping4 = [targetMapping[sourceMapping[0]], targetMapping[sourceMapping[1]], targetMapping[sourceMapping[2]], targetMapping[sourceMapping[3]]];
|
||||||
|
ReadOnlySpan<byte> mask4 =
|
||||||
|
[
|
||||||
|
mapping4[0],
|
||||||
|
mapping4[1],
|
||||||
|
mapping4[2],
|
||||||
|
mapping4[3],
|
||||||
|
|
||||||
|
(byte)(mapping4[0] + 4),
|
||||||
|
(byte)(mapping4[1] + 4),
|
||||||
|
(byte)(mapping4[2] + 4),
|
||||||
|
(byte)(mapping4[3] + 4),
|
||||||
|
|
||||||
|
(byte)(mapping4[0] + 8),
|
||||||
|
(byte)(mapping4[1] + 8),
|
||||||
|
(byte)(mapping4[2] + 8),
|
||||||
|
(byte)(mapping4[3] + 8),
|
||||||
|
|
||||||
|
(byte)(mapping4[0] + 12),
|
||||||
|
(byte)(mapping4[1] + 12),
|
||||||
|
(byte)(mapping4[2] + 12),
|
||||||
|
(byte)(mapping4[3] + 12),
|
||||||
|
];
|
||||||
|
|
||||||
|
ConvertEqualBpp(data, mask4, 4);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException("Data is not of a supported valid color-type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DarthAffe 07.07.2024: No fallback-implementation here. Shuffle Requires only Seee3 which should be supported nearly anywhere and if not the fallback of Vector128.Shuffle is perfectly fine.
|
||||||
|
private static void ConvertEqualBpp(Span<byte> data, ReadOnlySpan<byte> mask, int bpp)
|
||||||
|
{
|
||||||
|
int elementsPerVector = Vector128<byte>.Count / bpp;
|
||||||
|
int bytesPerVector = elementsPerVector * bpp;
|
||||||
|
|
||||||
|
int chunks = data.Length / bytesPerVector;
|
||||||
|
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask));
|
||||||
|
|
||||||
|
int missingElements = (data.Length - (chunks * bytesPerVector)) / bpp;
|
||||||
|
|
||||||
|
fixed (byte* dataPtr = data)
|
||||||
|
{
|
||||||
|
byte* ptr = dataPtr;
|
||||||
|
|
||||||
|
for (int i = 0; i < chunks; i++)
|
||||||
|
{
|
||||||
|
Vector128<byte> vector = Vector128.Load(ptr);
|
||||||
|
Vector128.Shuffle(vector, maskVector).Store(ptr);
|
||||||
|
|
||||||
|
ptr += bytesPerVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> buffer = stackalloc byte[missingElements * bpp]; // DarthAffe 07.07.2024: This is fine as it's always < 16 bytes
|
||||||
|
for (int i = 0; i < missingElements; i++)
|
||||||
|
{
|
||||||
|
int elementIndex = i * buffer.Length;
|
||||||
|
for (int j = 0; j < buffer.Length; j++)
|
||||||
|
buffer[elementIndex + j] = ptr[elementIndex + mask[j]];
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.CopyTo(new Span<byte>(ptr, buffer.Length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertWiden3To4Bytes(Span<byte> data, IColorFormat sourceFormat, IColorFormat targetFormat)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertNarrow4To3Bytes(Span<byte> data, IColorFormat sourceFormat, IColorFormat targetFormat)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user