Compare commits

...

10 Commits

27 changed files with 613 additions and 121 deletions

25
.github/workflows/pr_verify.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: PR-Verify
on:
pull_request:
branches: [ master ]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4.1.1
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release /p:Version=0.0.0
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release

7
Analyzers.globalconfig Normal file
View File

@ -0,0 +1,7 @@
is_global = true
# CA1000: Do not declare static members on generic types
dotnet_diagnostic.CA1000.severity = none
# CA1034: Nested types should not be visible
dotnet_diagnostic.CA1034.severity = none

View File

@ -1,4 +1,6 @@
using HPPH.System.Drawing; #pragma warning disable CA1416
using HPPH.System.Drawing;
namespace HPPH.Benchmark; namespace HPPH.Benchmark;

View File

@ -49,7 +49,7 @@ internal class Colors : IGeneratorFeature
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}): IColor public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}): IColor, IEquatable<{{colorFormat.TypeName}}>
{ {
#region Properties & Fields #region Properties & Fields
@ -74,11 +74,27 @@ internal class Colors : IGeneratorFeature
#endregion #endregion
#region Operators
public static bool operator ==({{colorFormat.TypeName}} left, {{colorFormat.TypeName}} right) => left.Equals(right);
public static bool operator !=({{colorFormat.TypeName}} left, {{colorFormat.TypeName}} right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals({{colorFormat.TypeName}} other) => (_{{colorFormat.FirstEntry}} == other._{{colorFormat.FirstEntry}}) && (_{{colorFormat.SecondEntry}} == other._{{colorFormat.SecondEntry}}) && (_{{colorFormat.ThirdEntry}} == other._{{colorFormat.ThirdEntry}});
/// <inheritdoc />
public override bool Equals(object? obj) => obj is {{colorFormat.TypeName}} other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_{{colorFormat.FirstEntry}}, _{{colorFormat.SecondEntry}}, _{{colorFormat.ThirdEntry}});
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
@ -116,7 +132,7 @@ internal class Colors : IGeneratorFeature
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}, byte {{colorFormat.FourthEntry}}) : IColor public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}, byte {{colorFormat.FourthEntry}}) : IColor, IEquatable<{{colorFormat.TypeName}}>
{ {
#region Properties & Fields #region Properties & Fields
@ -142,11 +158,27 @@ internal class Colors : IGeneratorFeature
#endregion #endregion
#region Operators
public static bool operator ==({{colorFormat.TypeName}} left, {{colorFormat.TypeName}} right) => left.Equals(right);
public static bool operator !=({{colorFormat.TypeName}} left, {{colorFormat.TypeName}} right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals({{colorFormat.TypeName}} other) => (_{{colorFormat.FirstEntry}} == other._{{colorFormat.FirstEntry}}) && (_{{colorFormat.SecondEntry}} == other._{{colorFormat.SecondEntry}}) && (_{{colorFormat.ThirdEntry}} == other._{{colorFormat.ThirdEntry}})&& (_{{colorFormat.FourthEntry}}== other._{{colorFormat.FourthEntry}});
/// <inheritdoc />
public override bool Equals(object? obj) => obj is {{colorFormat.TypeName}} other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_{{colorFormat.FirstEntry}}, _{{colorFormat.SecondEntry}}, _{{colorFormat.ThirdEntry}}, _{{colorFormat.FourthEntry}});
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -6,6 +6,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-all</AnalysisLevel>
<Authors>Darth Affe</Authors> <Authors>Darth Affe</Authors>
<Company>Wyrez</Company> <Company>Wyrez</Company>
<Language>en-US</Language> <Language>en-US</Language>
@ -41,6 +44,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> <PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants> <DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>

View File

@ -1,20 +1,28 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using SkiaSharp; using SkiaSharp;
namespace HPPH.SkiaSharp; namespace HPPH.SkiaSharp;
public static class ImageExtension public static class ImageExtension
{ {
public static unsafe SKImage ToSKImage(this IImage image) => SKImage.FromBitmap(image.ToSKBitmap()); public static unsafe SKImage ToSKImage(this IImage image)
{
ArgumentNullException.ThrowIfNull(image, nameof(image));
using SKBitmap bitmap = image.ToSKBitmap();
return SKImage.FromBitmap(bitmap);
}
public static unsafe SKBitmap ToSKBitmap(this IImage image) public static unsafe SKBitmap ToSKBitmap(this IImage image)
{ {
ArgumentNullException.ThrowIfNull(image, nameof(image));
SKBitmap bitmap = new(image.Width, image.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul); SKBitmap bitmap = new(image.Width, image.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul);
nint pixelPtr = bitmap.GetPixels(out nint length); nint pixelPtr = bitmap.GetPixels(out nint length);
image.ConvertTo<ColorBGRA>().CopyTo(new Span<byte>((void*)pixelPtr, (int)length));
(image as IImage<ColorBGRA> ?? image.ConvertTo<ColorBGRA>()).CopyTo(new Span<byte>((void*)pixelPtr, (int)length));
return bitmap; return bitmap;
} }
@ -25,8 +33,12 @@ public static class ImageExtension
return skImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); return skImage.Encode(SKEncodedImageFormat.Png, 100).ToArray();
} }
public static IImage ToImage(this SKImage skImage) => SKBitmap.FromImage(skImage).ToImage(); public static IImage<ColorBGRA> ToImage(this SKImage skImage) => SKBitmap.FromImage(skImage).ToImage();
public static IImage ToImage(this SKBitmap bitmap) public static IImage<ColorBGRA> ToImage(this SKBitmap bitmap)
=> Image<ColorBGRA>.Create(MemoryMarshal.Cast<SKColor, ColorBGRA>(bitmap.Pixels), bitmap.Width, bitmap.Height); {
ArgumentNullException.ThrowIfNull(bitmap, nameof(bitmap));
return Image<ColorBGRA>.Create(MemoryMarshal.Cast<SKColor, ColorBGRA>(bitmap.Pixels), bitmap.Width, bitmap.Height);
}
} }

View File

@ -6,6 +6,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-all</AnalysisLevel>
<Authors>Darth Affe</Authors> <Authors>Darth Affe</Authors>
<Company>Wyrez</Company> <Company>Wyrez</Company>
<Language>en-US</Language> <Language>en-US</Language>
@ -41,6 +44,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> <PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants> <DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>

View File

@ -9,6 +9,8 @@ public static class ImageExtension
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public static unsafe Bitmap ToBitmap(this IImage image) public static unsafe Bitmap ToBitmap(this IImage image)
{ {
ArgumentNullException.ThrowIfNull(image, nameof(image));
switch (image.ColorFormat.BytesPerPixel) switch (image.ColorFormat.BytesPerPixel)
{ {
case 3: case 3:
@ -16,10 +18,10 @@ public static class ImageExtension
Bitmap bitmap = new(image.Width, image.Height, PixelFormat.Format24bppRgb); Bitmap bitmap = new(image.Width, image.Height, PixelFormat.Format24bppRgb);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
IImage<ColorBGR> convertedImage = image.ConvertTo<ColorBGR>(); IImage<ColorBGR> img = image as IImage<ColorBGR> ?? image.ConvertTo<ColorBGR>();
nint ptr = bmpData.Scan0; nint ptr = bmpData.Scan0;
foreach (ImageRow<ColorBGR> row in convertedImage.Rows) foreach (ImageRow<ColorBGR> row in img.Rows)
{ {
row.CopyTo(new Span<byte>((void*)ptr, bmpData.Stride)); row.CopyTo(new Span<byte>((void*)ptr, bmpData.Stride));
ptr += bmpData.Stride; ptr += bmpData.Stride;
@ -35,10 +37,10 @@ public static class ImageExtension
Bitmap bitmap = new(image.Width, image.Height, PixelFormat.Format32bppArgb); Bitmap bitmap = new(image.Width, image.Height, PixelFormat.Format32bppArgb);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
IImage<ColorBGRA> convertedImage = image.ConvertTo<ColorBGRA>(); IImage<ColorBGRA> img = image as IImage<ColorBGRA> ?? image.ConvertTo<ColorBGRA>();
nint ptr = bmpData.Scan0; nint ptr = bmpData.Scan0;
foreach (ImageRow<ColorBGRA> row in convertedImage.Rows) foreach (ImageRow<ColorBGRA> row in img.Rows)
{ {
row.CopyTo(new Span<byte>((void*)ptr, bmpData.Stride)); row.CopyTo(new Span<byte>((void*)ptr, bmpData.Stride));
ptr += bmpData.Stride; ptr += bmpData.Stride;
@ -57,6 +59,8 @@ public static class ImageExtension
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public static byte[] ToPng(this IImage image) public static byte[] ToPng(this IImage image)
{ {
ArgumentNullException.ThrowIfNull(image, nameof(image));
using Bitmap bitmap = ToBitmap(image); using Bitmap bitmap = ToBitmap(image);
using MemoryStream ms = new(); using MemoryStream ms = new();
bitmap.Save(ms, ImageFormat.Png); bitmap.Save(ms, ImageFormat.Png);
@ -67,6 +71,8 @@ public static class ImageExtension
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public static unsafe IImage ToImage(this Bitmap bitmap) public static unsafe IImage ToImage(this Bitmap bitmap)
{ {
ArgumentNullException.ThrowIfNull(bitmap, nameof(bitmap));
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
ReadOnlySpan<byte> buffer = new(data.Scan0.ToPointer(), data.Stride * data.Height); ReadOnlySpan<byte> buffer = new(data.Scan0.ToPointer(), data.Stride * data.Height);

View File

@ -23,7 +23,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorABGR(byte a, byte b, byte g, byte r) : IColor public readonly partial struct ColorABGR(byte a, byte b, byte g, byte r) : IColor, IEquatable<ColorABGR>
{ {
#region Properties & Fields #region Properties & Fields
@ -49,11 +49,27 @@ public readonly partial struct ColorABGR(byte a, byte b, byte g, byte r) : IColo
#endregion #endregion
#region Operators
public static bool operator ==(ColorABGR left, ColorABGR right) => left.Equals(right);
public static bool operator !=(ColorABGR left, ColorABGR right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorABGR other) => (_a == other._a) && (_b == other._b) && (_g == other._g)&& (_r== other._r);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorABGR other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_a, _b, _g, _r);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -23,7 +23,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorARGB(byte a, byte r, byte g, byte b) : IColor public readonly partial struct ColorARGB(byte a, byte r, byte g, byte b) : IColor, IEquatable<ColorARGB>
{ {
#region Properties & Fields #region Properties & Fields
@ -49,11 +49,27 @@ public readonly partial struct ColorARGB(byte a, byte r, byte g, byte b) : IColo
#endregion #endregion
#region Operators
public static bool operator ==(ColorARGB left, ColorARGB right) => left.Equals(right);
public static bool operator !=(ColorARGB left, ColorARGB right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorARGB other) => (_a == other._a) && (_r == other._r) && (_g == other._g)&& (_b== other._b);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorARGB other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_a, _r, _g, _b);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -22,7 +22,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorBGR(byte b, byte g, byte r): IColor public readonly partial struct ColorBGR(byte b, byte g, byte r): IColor, IEquatable<ColorBGR>
{ {
#region Properties & Fields #region Properties & Fields
@ -47,11 +47,27 @@ public readonly partial struct ColorBGR(byte b, byte g, byte r): IColor
#endregion #endregion
#region Operators
public static bool operator ==(ColorBGR left, ColorBGR right) => left.Equals(right);
public static bool operator !=(ColorBGR left, ColorBGR right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorBGR other) => (_b == other._b) && (_g == other._g) && (_r == other._r);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorBGR other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_b, _g, _r);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -23,7 +23,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorBGRA(byte b, byte g, byte r, byte a) : IColor public readonly partial struct ColorBGRA(byte b, byte g, byte r, byte a) : IColor, IEquatable<ColorBGRA>
{ {
#region Properties & Fields #region Properties & Fields
@ -49,11 +49,27 @@ public readonly partial struct ColorBGRA(byte b, byte g, byte r, byte a) : IColo
#endregion #endregion
#region Operators
public static bool operator ==(ColorBGRA left, ColorBGRA right) => left.Equals(right);
public static bool operator !=(ColorBGRA left, ColorBGRA right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorBGRA other) => (_b == other._b) && (_g == other._g) && (_r == other._r)&& (_a== other._a);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorBGRA other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_b, _g, _r, _a);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -22,7 +22,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorRGB(byte r, byte g, byte b): IColor public readonly partial struct ColorRGB(byte r, byte g, byte b): IColor, IEquatable<ColorRGB>
{ {
#region Properties & Fields #region Properties & Fields
@ -47,11 +47,27 @@ public readonly partial struct ColorRGB(byte r, byte g, byte b): IColor
#endregion #endregion
#region Operators
public static bool operator ==(ColorRGB left, ColorRGB right) => left.Equals(right);
public static bool operator !=(ColorRGB left, ColorRGB right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorRGB other) => (_r == other._r) && (_g == other._g) && (_b == other._b);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorRGB other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_r, _g, _b);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -23,7 +23,7 @@ namespace HPPH;
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
[SkipLocalsInit] [SkipLocalsInit]
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly partial struct ColorRGBA(byte r, byte g, byte b, byte a) : IColor public readonly partial struct ColorRGBA(byte r, byte g, byte b, byte a) : IColor, IEquatable<ColorRGBA>
{ {
#region Properties & Fields #region Properties & Fields
@ -49,11 +49,27 @@ public readonly partial struct ColorRGBA(byte r, byte g, byte b, byte a) : IColo
#endregion #endregion
#region Operators
public static bool operator ==(ColorRGBA left, ColorRGBA right) => left.Equals(right);
public static bool operator !=(ColorRGBA left, ColorRGBA right) => !left.Equals(right);
#endregion
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A); public bool Equals(IColor? other) => (other != null) && (R == other.R) && (G == other.G) && (B == other.B) && (A == other.A);
/// <inheritdoc />
public bool Equals(ColorRGBA other) => (_r == other._r) && (_g == other._g) && (_b == other._b)&& (_a== other._a);
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ColorRGBA other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_r, _g, _b, _a);
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";

View File

@ -8,6 +8,9 @@
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath> <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-all</AnalysisLevel>
<Authors>Darth Affe</Authors> <Authors>Darth Affe</Authors>
<Company>Wyrez</Company> <Company>Wyrez</Company>
<Language>en-US</Language> <Language>en-US</Language>
@ -43,6 +46,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> <PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants> <DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>

View File

@ -40,13 +40,15 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
/// <inheritdoc /> /// <inheritdoc />
IColor IImage.this[int x, int y] => this[x, y]; IColor IImage.this[int x, int y] => this[x, y];
#pragma warning disable CA2208 // Not ideal, but splitting up all the checks introduces quite some overhead :(
/// <inheritdoc /> /// <inheritdoc />
public ref readonly T this[int x, int y] public ref readonly T this[int x, int y]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new ArgumentOutOfRangeException();
return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), (nint)(uint)((_y + y) * _stride))), (nint)(uint)(_x + x)); return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), (nint)(uint)((_y + y) * _stride))), (nint)(uint)(_x + x));
} }
@ -58,7 +60,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new ArgumentOutOfRangeException();
return new Image<T>(_buffer, _x + x, _y + y, width, height, _stride); return new Image<T>(_buffer, _x + x, _y + y, width, height, _stride);
} }
@ -70,12 +72,14 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new ArgumentOutOfRangeException();
return new RefImage<T>(_buffer, _x + x, _y + y, width, height, _stride); return new RefImage<T>(_buffer, _x + x, _y + y, width, height, _stride);
} }
} }
#pragma warning restore CA2208
/// <inheritdoc /> /// <inheritdoc />
IImageRows IImage.Rows => new IColorImageRows<T>(_buffer, _x, _y, Width, Height, _stride); IImageRows IImage.Rows => new IColorImageRows<T>(_buffer, _x, _y, Width, Height, _stride);
@ -127,6 +131,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
public static Image<T> Wrap(byte[] buffer, int width, int height, int stride) public static Image<T> Wrap(byte[] buffer, int width, int height, int stride)
{ {
ArgumentNullException.ThrowIfNull(buffer, nameof(buffer));
if (stride < width) throw new ArgumentException("Stride can't be smaller than width."); if (stride < width) throw new ArgumentException("Stride can't be smaller than width.");
if (buffer.Length < (height * stride)) throw new ArgumentException("Not enough data in the buffer."); if (buffer.Length < (height * stride)) throw new ArgumentException("Not enough data in the buffer.");

View File

@ -27,7 +27,7 @@ public readonly ref struct ImageColumn<T>
{ {
get get
{ {
if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); if ((y < 0) || (y >= _length)) throw new ArgumentOutOfRangeException(nameof(y));
return ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)(_start + (y * _step)))); return ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)(_start + (y * _step))));
} }
@ -114,7 +114,7 @@ public readonly ref struct ImageColumn<T>
//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) //HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md)
[SkipLocalsInit] [SkipLocalsInit]
internal class IColorImageColumn<T> : IImageColumn internal sealed class IColorImageColumn<T> : IImageColumn
where T : struct, IColor where T : struct, IColor
{ {
#region Properties & Fields #region Properties & Fields
@ -139,7 +139,7 @@ internal class IColorImageColumn<T> : IImageColumn
{ {
get get
{ {
if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); if ((y < 0) || (y >= _length)) throw new ArgumentOutOfRangeException(nameof(y));
return Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), _start + (y * _step))); return Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), _start + (y * _step)));
} }

View File

@ -28,7 +28,7 @@ public readonly ref struct ImageColumns<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); if ((column < 0) || (column >= _width)) throw new ArgumentOutOfRangeException(nameof(column));
return new ImageColumn<T>(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride); return new ImageColumn<T>(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride);
} }
@ -100,7 +100,7 @@ public readonly ref struct ImageColumns<T>
//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) //HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md)
[SkipLocalsInit] [SkipLocalsInit]
internal class IColorImageColumns<T> : IImageColumns internal sealed class IColorImageColumns<T> : IImageColumns
where T : struct, IColor where T : struct, IColor
{ {
#region Properties & Fields #region Properties & Fields
@ -126,7 +126,7 @@ internal class IColorImageColumns<T> : IImageColumns
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); if ((column < 0) || (column >= _width)) throw new ArgumentOutOfRangeException(nameof(column));
return new IColorImageColumn<T>(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride); return new IColorImageColumn<T>(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride);
} }

View File

@ -26,7 +26,7 @@ public readonly ref struct ImageRow<T>
{ {
get get
{ {
if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); if ((x < 0) || (x >= _length)) throw new ArgumentOutOfRangeException(nameof(x));
return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)_start)), (nint)(uint)x); return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)_start)), (nint)(uint)x);
} }
@ -108,7 +108,7 @@ public readonly ref struct ImageRow<T>
//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) //HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md)
[SkipLocalsInit] [SkipLocalsInit]
internal class IColorImageRow<T> : IImageRow internal sealed class IColorImageRow<T> : IImageRow
where T : struct, IColor where T : struct, IColor
{ {
#region Properties & Fields #region Properties & Fields
@ -132,7 +132,7 @@ internal class IColorImageRow<T> : IImageRow
{ {
get get
{ {
if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); if ((x < 0) || (x >= _length)) throw new ArgumentOutOfRangeException(nameof(x));
return Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), (nint)(uint)_start)), (nint)(uint)x); return Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), (nint)(uint)_start)), (nint)(uint)x);
} }

View File

@ -28,7 +28,7 @@ public readonly ref struct ImageRows<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); if ((row < 0) || (row >= _height)) throw new ArgumentOutOfRangeException(nameof(row));
return new ImageRow<T>(_data, ((row + _y) * _stride) + (_x * _bpp), _width); return new ImageRow<T>(_data, ((row + _y) * _stride) + (_x * _bpp), _width);
} }
@ -102,7 +102,7 @@ public readonly ref struct ImageRows<T>
//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) //HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md)
[SkipLocalsInit] [SkipLocalsInit]
internal class IColorImageRows<T> : IImageRows internal sealed class IColorImageRows<T> : IImageRows
where T : struct, IColor where T : struct, IColor
{ {
#region Properties & Fields #region Properties & Fields
@ -128,7 +128,7 @@ internal class IColorImageRows<T> : IImageRows
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); if ((row < 0) || (row >= _height)) throw new ArgumentOutOfRangeException(nameof(row));
return new IColorImageRow<T>(_data, ((row + _y) * _stride) + (_x * _bpp), _width); return new IColorImageRow<T>(_data, ((row + _y) * _stride) + (_x * _bpp), _width);
} }

View File

@ -34,12 +34,14 @@ public readonly ref struct RefImage<T>
#region Indexer #region Indexer
#pragma warning disable CA2208 // Not ideal, but splitting up all the checks introduces quite some overhead :(
public ref readonly T this[int x, int y] public ref readonly T this[int x, int y]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new ArgumentOutOfRangeException();
return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_data), (nint)(uint)((_y + y) * RawStride))), (nint)(uint)(_x + x)); return ref Unsafe.Add(ref Unsafe.As<byte, T>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_data), (nint)(uint)((_y + y) * RawStride))), (nint)(uint)(_x + x));
} }
@ -50,12 +52,14 @@ public readonly ref struct RefImage<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new ArgumentOutOfRangeException();
return new RefImage<T>(_data, _x + x, _y + y, width, height, RawStride); return new RefImage<T>(_data, _x + x, _y + y, width, height, RawStride);
} }
} }
#pragma warning restore CA2208
public ImageRows<T> Rows public ImageRows<T> Rows
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -39,7 +39,11 @@ public static partial class PixelHelper
public static T Average<T>(this IImage<T> image) public static T Average<T>(this IImage<T> image)
where T : struct, IColor where T : struct, IColor
=> image.AsRefImage().Average(); {
ArgumentNullException.ThrowIfNull(image, nameof(image));
return image.AsRefImage().Average();
}
public static T Average<T>(this RefImage<T> image) public static T Average<T>(this RefImage<T> image)
where T : struct, IColor where T : struct, IColor

View File

@ -5,6 +5,12 @@ namespace HPPH;
public static unsafe partial class PixelHelper public static unsafe partial class PixelHelper
{ {
#region Constants
private const int MIN_BATCH_SIZE = 8;
#endregion
#region Methods #region Methods
public static Span<TTarget> ConvertInPlace<TSource, TTarget>(this Span<TSource> colors) public static Span<TTarget> ConvertInPlace<TSource, TTarget>(this Span<TSource> colors)
@ -105,6 +111,8 @@ public static unsafe partial class PixelHelper
private static void Convert3Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat) private static void Convert3Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat)
{ {
const int BPP = 3;
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping; ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping; ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
@ -133,12 +141,97 @@ public static unsafe partial class PixelHelper
15 15
]; ];
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask));
ConvertSameBpp(source, target, mask, 3); int elements = source.Length / BPP;
int elementsPerVector = Vector128<byte>.Count / BPP;
int bytesPerVector = elementsPerVector * BPP;
int chunks = elements / elementsPerVector;
int batches = Math.Max(1, Math.Min(chunks / MIN_BATCH_SIZE, Environment.ProcessorCount));
int batchSize = elements / batches;
fixed (byte* fixedSourcePtr = source)
fixed (byte* fixedTargetPtr = target)
{
byte* sourcePtr = fixedSourcePtr;
byte* targetPtr = fixedTargetPtr;
if (batches == 1)
{
byte* src = sourcePtr;
byte* tar = targetPtr;
int chunkCount = Math.Max(0, (batchSize / elementsPerVector) - 1);
int missingElements = batchSize - (chunkCount * elementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += bytesPerVector;
tar += bytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
tar[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
tar[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
}
}
else
{
Parallel.For(0, batches, Process);
int missing = elements - (batchSize * batches);
if (missing > 0)
{
byte* missingSrc = sourcePtr + (batches * batchSize * BPP);
byte* missingTar = targetPtr + (batches * batchSize * BPP);
for (int i = 0; i < missing; i++)
{
missingTar[(i * BPP) + 0] = missingSrc[(i * BPP) + maskVector[0]];
missingTar[(i * BPP) + 1] = missingSrc[(i * BPP) + maskVector[1]];
missingTar[(i * BPP) + 2] = missingSrc[(i * BPP) + maskVector[2]];
}
}
void Process(int index)
{
int offset = index * batchSize;
byte* src = sourcePtr + (offset * BPP);
byte* tar = targetPtr + (offset * BPP);
int chunkCount = Math.Max(0, (batchSize / elementsPerVector) - 1);
int missingElements = batchSize - (chunkCount * elementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += bytesPerVector;
tar += bytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
tar[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
tar[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
}
}
}
}
} }
private static void Convert4Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat) private static void Convert4Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat)
{ {
const int BPP = 4;
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping; ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping; ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
@ -166,49 +259,106 @@ public static unsafe partial class PixelHelper
(byte)(mapping[3] + 12), (byte)(mapping[3] + 12),
]; ];
ConvertSameBpp(source, target, mask, 4);
}
private static void ConvertSameBpp(ReadOnlySpan<byte> source, Span<byte> target, ReadOnlySpan<byte> mask, int bpp)
{
int elementsPerVector = Vector128<byte>.Count / bpp;
int bytesPerVector = elementsPerVector * bpp;
int chunks = source.Length / bytesPerVector;
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask)); Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask));
int missingElements = (source.Length - (chunks * bytesPerVector)) / bpp; int elements = source.Length / BPP;
int elementsPerVector = Vector128<byte>.Count / BPP;
int bytesPerVector = elementsPerVector * BPP;
fixed (byte* sourcePtr = source) int chunks = elements / elementsPerVector;
fixed (byte* targetPtr = target) int batches = Math.Max(1, Math.Min(chunks / MIN_BATCH_SIZE, Environment.ProcessorCount));
int batchSize = elements / batches;
fixed (byte* fixedSourcePtr = source)
fixed (byte* fixedTargetPtr = target)
{ {
byte* src = sourcePtr; byte* sourcePtr = fixedSourcePtr;
byte* tar = targetPtr; byte* targetPtr = fixedTargetPtr;
for (int i = 0; i < chunks; i++) if (batches == 1)
{ {
Vector128<byte> vector = Vector128.Load(src); byte* src = sourcePtr;
Vector128.Shuffle(vector, maskVector).Store(tar); byte* tar = targetPtr;
src += bytesPerVector; int chunkCount = batchSize / elementsPerVector;
tar += bytesPerVector; int missingElements = batchSize - (chunkCount * elementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += bytesPerVector;
tar += bytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
tar[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
tar[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
tar[(i * BPP) + 3] = src[(i * BPP) + maskVector[3]];
}
} }
else
{
Parallel.For(0, batches, Process);
Span<byte> buffer = stackalloc byte[missingElements * bpp]; // DarthAffe 08.07.2024: This is fine as it's always < 16 bytes int missing = elements - (batchSize * batches);
for (int j = 0; j < buffer.Length; j++) if (missing > 0)
buffer[j] = src[mask[j]]; {
byte* missingSrc = sourcePtr + (batches * batchSize * BPP);
byte* missingTar = targetPtr + (batches * batchSize * BPP);
buffer.CopyTo(new Span<byte>(tar, buffer.Length)); for (int i = 0; i < missing; i++)
{
missingTar[(i * BPP) + 0] = missingSrc[(i * BPP) + maskVector[0]];
missingTar[(i * BPP) + 1] = missingSrc[(i * BPP) + maskVector[1]];
missingTar[(i * BPP) + 2] = missingSrc[(i * BPP) + maskVector[2]];
missingTar[(i * BPP) + 3] = missingSrc[(i * BPP) + maskVector[3]];
}
}
void Process(int index)
{
int offset = index * batchSize;
byte* src = sourcePtr + (offset * BPP);
byte* tar = targetPtr + (offset * BPP);
int chunkCount = batchSize / elementsPerVector;
int missingElements = batchSize - (chunkCount * elementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += bytesPerVector;
tar += bytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
tar[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
tar[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
tar[(i * BPP) + 3] = src[(i * BPP) + maskVector[3]];
}
}
}
} }
} }
private static void ConvertWiden3To4Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat) private static void ConvertWiden3To4Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat)
{ {
const int SOURCE_BPP = 3;
const int TARGET_BPP = 4;
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping; ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping; ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
// DarthAffe 08.07.2024: For now alpha is the only thing to be added // DarthAffe 08.07.2024: For now alpha is the only thing to be added
Span<byte> isAlpha = byte[] isAlpha =
[ [
targetMapping[0] == Color.A ? byte.MaxValue : (byte)0, targetMapping[0] == Color.A ? byte.MaxValue : (byte)0,
targetMapping[1] == Color.A ? byte.MaxValue : (byte)0, targetMapping[1] == Color.A ? byte.MaxValue : (byte)0,
@ -270,46 +420,105 @@ public static unsafe partial class PixelHelper
isAlpha[3], isAlpha[3],
]; ];
int sourceBpp = sourceFormat.BytesPerPixel;
int targetBpp = targetFormat.BytesPerPixel;
int targetElementsPerVector = Vector128<byte>.Count / targetBpp;
int targetBytesPerVector = targetElementsPerVector * targetBpp;
int sourceBytesPerVector = targetElementsPerVector * sourceBpp;
int chunks = (source.Length / sourceBytesPerVector);
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask)); Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask));
Vector128<byte> alphaMaskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(alphaMask)); Vector128<byte> alphaMaskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(alphaMask));
int missingElements = (source.Length - (chunks * sourceBytesPerVector)) / sourceBpp; int elements = source.Length / SOURCE_BPP;
int targetElementsPerVector = Vector128<byte>.Count / TARGET_BPP;
int sourceBytesPerVector = targetElementsPerVector * SOURCE_BPP;
int targetBytesPerVector = targetElementsPerVector * TARGET_BPP;
fixed (byte* sourcePtr = source) int chunks = elements / targetElementsPerVector;
fixed (byte* targetPtr = target) int batches = Math.Max(1, Math.Min(chunks / MIN_BATCH_SIZE, Environment.ProcessorCount));
int batchSize = elements / batches;
fixed (byte* fixedSourcePtr = source)
fixed (byte* fixedTargetPtr = target)
{ {
byte* src = sourcePtr; byte* sourcePtr = fixedSourcePtr;
byte* tar = targetPtr; byte* targetPtr = fixedTargetPtr;
for (int i = 0; i < chunks; i++) if (batches == 1)
{ {
Vector128<byte> vector = Vector128.Load(src); byte* src = sourcePtr;
Vector128<byte> shuffled = Vector128.Shuffle(vector, maskVector); byte* tar = targetPtr;
Vector128.BitwiseOr(shuffled, alphaMaskVector).Store(tar);
src += sourceBytesPerVector; int chunkCount = batchSize / targetElementsPerVector;
tar += targetBytesPerVector; int missingElements = batchSize - (chunkCount * targetElementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128<byte> shuffled = Vector128.Shuffle(vector, maskVector);
Vector128.BitwiseOr(shuffled, alphaMaskVector).Store(tar);
src += sourceBytesPerVector;
tar += targetBytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * TARGET_BPP) + 0] = Math.Max(isAlpha[0], src[(i * SOURCE_BPP) + maskVector[0]]);
tar[(i * TARGET_BPP) + 1] = Math.Max(isAlpha[1], src[(i * SOURCE_BPP) + maskVector[1]]);
tar[(i * TARGET_BPP) + 2] = Math.Max(isAlpha[2], src[(i * SOURCE_BPP) + maskVector[2]]);
tar[(i * TARGET_BPP) + 3] = Math.Max(isAlpha[3], src[(i * SOURCE_BPP) + maskVector[3]]);
}
} }
else
{
Parallel.For(0, batches, Process);
Span<byte> buffer = stackalloc byte[missingElements * targetBpp]; // DarthAffe 08.07.2024: This is fine as it's always < 16 bytes int missing = elements - (batchSize * batches);
for (int i = 0; i < missingElements; i++) if (missing > 0)
for (int j = 0; j < targetBpp; j++) {
buffer[(i * targetBpp) + j] = Math.Max(isAlpha[j], src[(i * sourceBpp) + mask[j]]); byte* missingSrc = sourcePtr + (batches * batchSize * SOURCE_BPP);
byte* missingTar = targetPtr + (batches * batchSize * TARGET_BPP);
buffer.CopyTo(new Span<byte>(tar, buffer.Length)); for (int i = 0; i < missing; i++)
{
missingTar[(i * TARGET_BPP) + 0] = Math.Max(isAlpha[0], missingSrc[(i * SOURCE_BPP) + maskVector[0]]);
missingTar[(i * TARGET_BPP) + 1] = Math.Max(isAlpha[1], missingSrc[(i * SOURCE_BPP) + maskVector[1]]);
missingTar[(i * TARGET_BPP) + 2] = Math.Max(isAlpha[2], missingSrc[(i * SOURCE_BPP) + maskVector[2]]);
missingTar[(i * TARGET_BPP) + 3] = Math.Max(isAlpha[3], missingSrc[(i * SOURCE_BPP) + maskVector[3]]);
}
}
void Process(int index)
{
int offset = index * batchSize;
byte* src = sourcePtr + (offset * SOURCE_BPP);
byte* tar = targetPtr + (offset * TARGET_BPP);
int chunkCount = batchSize / targetElementsPerVector;
int missingElements = batchSize - (chunkCount * targetElementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128<byte> shuffled = Vector128.Shuffle(vector, maskVector);
Vector128.BitwiseOr(shuffled, alphaMaskVector).Store(tar);
src += sourceBytesPerVector;
tar += targetBytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * TARGET_BPP) + 0] = Math.Max(isAlpha[0], src[(i * SOURCE_BPP) + maskVector[0]]);
tar[(i * TARGET_BPP) + 1] = Math.Max(isAlpha[1], src[(i * SOURCE_BPP) + maskVector[1]]);
tar[(i * TARGET_BPP) + 2] = Math.Max(isAlpha[2], src[(i * SOURCE_BPP) + maskVector[2]]);
tar[(i * TARGET_BPP) + 3] = Math.Max(isAlpha[3], src[(i * SOURCE_BPP) + maskVector[3]]);
}
}
}
} }
} }
private static void ConvertNarrow4To3Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat) private static void ConvertNarrow4To3Bytes(ReadOnlySpan<byte> source, Span<byte> target, IColorFormat sourceFormat, IColorFormat targetFormat)
{ {
const int SOURCE_BPP = 4;
const int TARGET_BPP = 3;
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping; ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping; ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
@ -340,39 +549,91 @@ public static unsafe partial class PixelHelper
15 15
]; ];
int sourceBpp = sourceFormat.BytesPerPixel;
int targetBpp = targetFormat.BytesPerPixel;
int sourceElementsPerVector = Vector128<byte>.Count / sourceBpp;
int sourceBytesPerVector = sourceElementsPerVector * sourceBpp;
int targetBytesPerVector = sourceElementsPerVector * targetBpp;
int chunks = (source.Length / sourceBytesPerVector) - 1; // DarthAffe 08.07.2024: -1 since we don't have enough space to copy a full target vector for the last set
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask)); Vector128<byte> maskVector = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(mask));
int missingElements = (source.Length - (chunks * sourceBytesPerVector)) / sourceBpp; int elements = source.Length / SOURCE_BPP;
int sourceElementsPerVector = Vector128<byte>.Count / SOURCE_BPP;
int sourceBytesPerVector = sourceElementsPerVector * SOURCE_BPP;
int targetBytesPerVector = sourceElementsPerVector * TARGET_BPP;
fixed (byte* sourcePtr = source) int chunks = elements / sourceElementsPerVector;
fixed (byte* targetPtr = target) int batches = Math.Max(1, Math.Min(chunks / MIN_BATCH_SIZE, Environment.ProcessorCount));
int batchSize = elements / batches;
fixed (byte* fixedSourcePtr = source)
fixed (byte* fixedTargetPtr = target)
{ {
byte* src = sourcePtr; byte* sourcePtr = fixedSourcePtr;
byte* tar = targetPtr; byte* targetPtr = fixedTargetPtr;
for (int i = 0; i < chunks; i++) if (batches == 1)
{ {
Vector128<byte> vector = Vector128.Load(src); byte* src = sourcePtr;
Vector128.Shuffle(vector, maskVector).Store(tar); byte* tar = targetPtr;
src += sourceBytesPerVector; int chunkCount = Math.Max(0, (batchSize / sourceElementsPerVector) - 1); // DarthAffe 08.07.2024: -1 since we don't have enough space to copy a full target vector for the last set
tar += targetBytesPerVector; int missingElements = batchSize - (chunkCount * sourceElementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += sourceBytesPerVector;
tar += targetBytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * TARGET_BPP) + 0] = src[(i * SOURCE_BPP) + mapping[0]];
tar[(i * TARGET_BPP) + 1] = src[(i * SOURCE_BPP) + mapping[1]];
tar[(i * TARGET_BPP) + 2] = src[(i * SOURCE_BPP) + mapping[2]];
}
} }
else
{
Parallel.For(0, batches, Process);
Span<byte> buffer = stackalloc byte[missingElements * targetBpp]; // DarthAffe 08.07.2024: This is fine as it's always < 24 bytes int missing = elements - (batchSize * batches);
for (int i = 0; i < missingElements; i++) if (missing > 0)
for (int j = 0; j < targetBpp; j++) {
buffer[(i * targetBpp) + j] = src[(i * sourceBpp) + mask[j]]; byte* missingSrc = sourcePtr + (batches * batchSize * SOURCE_BPP);
byte* missingTar = targetPtr + (batches * batchSize * TARGET_BPP);
buffer.CopyTo(new Span<byte>(tar, buffer.Length)); for (int i = 0; i < missing; i++)
{
missingTar[(i * TARGET_BPP) + 0] = missingSrc[(i * SOURCE_BPP) + maskVector[0]];
missingTar[(i * TARGET_BPP) + 1] = missingSrc[(i * SOURCE_BPP) + maskVector[1]];
missingTar[(i * TARGET_BPP) + 2] = missingSrc[(i * SOURCE_BPP) + maskVector[2]];
}
}
void Process(int index)
{
int offset = index * batchSize;
byte* src = sourcePtr + (offset * SOURCE_BPP);
byte* tar = targetPtr + (offset * TARGET_BPP);
int chunkCount = Math.Max(0, (batchSize / sourceElementsPerVector) - 1); // DarthAffe 08.07.2024: -1 since we don't have enough space to copy a full target vector for the last set
int missingElements = batchSize - (chunkCount * sourceElementsPerVector);
for (int i = 0; i < chunkCount; i++)
{
Vector128<byte> vector = Vector128.Load(src);
Vector128.Shuffle(vector, maskVector).Store(tar);
src += sourceBytesPerVector;
tar += targetBytesPerVector;
}
for (int i = 0; i < missingElements; i++)
{
tar[(i * TARGET_BPP) + 0] = src[(i * SOURCE_BPP) + maskVector[0]];
tar[(i * TARGET_BPP) + 1] = src[(i * SOURCE_BPP) + maskVector[1]];
tar[(i * TARGET_BPP) + 2] = src[(i * SOURCE_BPP) + maskVector[2]];
}
}
}
} }
} }

View File

@ -40,7 +40,11 @@ public static unsafe partial class PixelHelper
public static IMinMax MinMax<T>(this IImage<T> image) public static IMinMax MinMax<T>(this IImage<T> image)
where T : struct, IColor where T : struct, IColor
=> image.AsRefImage().MinMax(); {
ArgumentNullException.ThrowIfNull(image, nameof(image));
return image.AsRefImage().MinMax();
}
public static IMinMax MinMax<T>(this RefImage<T> image) public static IMinMax MinMax<T>(this RefImage<T> image)
where T : struct, IColor where T : struct, IColor

View File

@ -1,7 +1,6 @@
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static System.Net.Mime.MediaTypeNames;
namespace HPPH; namespace HPPH;
@ -40,7 +39,11 @@ public static partial class PixelHelper
public static T[] CreateColorPalette<T>(this IImage<T> image, int paletteSize) public static T[] CreateColorPalette<T>(this IImage<T> image, int paletteSize)
where T : unmanaged, IColor where T : unmanaged, IColor
=> image.AsRefImage().CreateColorPalette(paletteSize); {
ArgumentNullException.ThrowIfNull(image, nameof(image));
return image.AsRefImage().CreateColorPalette(paletteSize);
}
public static T[] CreateColorPalette<T>(this RefImage<T> image, int paletteSize) public static T[] CreateColorPalette<T>(this RefImage<T> image, int paletteSize)
where T : unmanaged, IColor where T : unmanaged, IColor
@ -165,7 +168,11 @@ public static partial class PixelHelper
public static T[] CreateSimpleColorPalette<T>(this IImage<T> image, int paletteSize) public static T[] CreateSimpleColorPalette<T>(this IImage<T> image, int paletteSize)
where T : unmanaged, IColor where T : unmanaged, IColor
=> image.AsRefImage().CreateSimpleColorPalette(paletteSize); {
ArgumentNullException.ThrowIfNull(image, nameof(image));
return image.AsRefImage().CreateSimpleColorPalette(paletteSize);
}
public static T[] CreateSimpleColorPalette<T>(this RefImage<T> image, int paletteSize) public static T[] CreateSimpleColorPalette<T>(this RefImage<T> image, int paletteSize)
where T : unmanaged, IColor where T : unmanaged, IColor

View File

@ -41,7 +41,11 @@ public static unsafe partial class PixelHelper
public static ISum Sum<T>(this IImage<T> image) public static ISum Sum<T>(this IImage<T> image)
where T : struct, IColor where T : struct, IColor
=> image.AsRefImage().Sum(); {
ArgumentNullException.ThrowIfNull(image, nameof(image));
return image.AsRefImage().Sum();
}
public static ISum Sum<T>(this RefImage<T> image) public static ISum Sum<T>(this RefImage<T> image)
where T : struct, IColor where T : struct, IColor

View File

@ -97,9 +97,11 @@ All of the included formats can freely be converted between each other.
Allocation-free in-place conversion is only supported for formats of same size (both 24 or 32 bit). Allocation-free in-place conversion is only supported for formats of same size (both 24 or 32 bit).
| Method | Mean | Error | StdDev | Allocated | | Method | Mean | Error | StdDev | Allocated |
|----------- |---------:|----------:|----------:|----------:| |------------------- |---------:|----------:|----------:|------------:|
| RGBToBGR | 6.272 ms | 0.0288 ms | 0.0240 ms | 8.81 MB | | RGBToBGR | 1.487 ms | 0.0221 ms | 0.0196 ms | 9073.58 KB |
| RGBToBGRA | 8.534 ms | 0.0684 ms | 0.0640 ms | 11.75 MB | | RGBToBGRA | 1.676 ms | 0.0330 ms | 0.0353 ms | 12064.76 KB |
| RGBAToABGR | 8.128 ms | 0.0927 ms | 0.0867 ms | 11.75 MB | | RGBAToABGR | 1.766 ms | 0.0348 ms | 0.0476 ms | 12084.93 KB |
| ARGBToBGR | 8.004 ms | 0.0353 ms | 0.0313 ms | 8.81 MB | | ARGBToBGR | 1.533 ms | 0.0072 ms | 0.0064 ms | 9085.36 KB |
| RGBToBGR_InPlace | 1.025 ms | 0.0021 ms | 0.0017 ms | 34.47 KB |
| RGBAToABGR_InPlace | 1.054 ms | 0.0023 ms | 0.0020 ms | 34.16 KB |