mirror of
https://github.com/DarthAffe/HPPH.git
synced 2025-12-12 13:28:37 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da26ca248f | |||
| 94f3b8cd16 | |||
| 94695ab9cb | |||
| f091aadd92 | |||
| e6b77ecfdb | |||
| f4c1eaddd6 | |||
| 723649053c | |||
| d6d126d88d | |||
| 32664a2971 | |||
| 61035cb376 | |||
| 2c2250d9d7 | |||
| 3f4c7abdfe | |||
| 4ac5dd8e94 | |||
| ca4e7e7af5 | |||
| b66fc3c9e9 | |||
| cddb82e07e | |||
| b9aa24aeec | |||
| c93463fc7a | |||
| 862f1eb1f8 | |||
| 47b7dc7bcf | |||
| 1d8106c565 | |||
| 817b4bcb28 |
25
.github/workflows/pr_verify.yml
vendored
Normal file
25
.github/workflows/pr_verify.yml
vendored
Normal 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
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -24,13 +24,13 @@ jobs:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore HPPH
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build HPPH --no-restore --configuration Release /p:Version=${{ github.event.inputs.version }}
|
||||
run: dotnet build --no-restore --configuration Release /p:Version=${{ github.event.inputs.version }}
|
||||
|
||||
- name: Test
|
||||
run: dotnet test HPPH --no-build --verbosity normal --configuration Release
|
||||
run: dotnet test --no-build --verbosity normal --configuration Release
|
||||
|
||||
- name: List files
|
||||
run: tree
|
||||
|
||||
7
Analyzers.globalconfig
Normal file
7
Analyzers.globalconfig
Normal 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
|
||||
@ -13,6 +13,8 @@ public class AverageBenchmarks
|
||||
|
||||
private readonly List<ColorRGB[]> _colors3bpp;
|
||||
private readonly List<ColorRGBA[]> _colors4bpp;
|
||||
private readonly List<IImage<ColorRGB>> _images3bpp;
|
||||
private readonly List<IImage<ColorRGBA>> _images4bpp;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -22,6 +24,9 @@ public class AverageBenchmarks
|
||||
{
|
||||
_colors3bpp = BenchmarkHelper.GetSampleData<ColorRGB>();
|
||||
_colors4bpp = BenchmarkHelper.GetSampleData<ColorRGBA>();
|
||||
|
||||
_images3bpp = BenchmarkHelper.GetSampleDataImages<ColorRGB>();
|
||||
_images4bpp = BenchmarkHelper.GetSampleDataImages<ColorRGBA>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -48,6 +53,26 @@ public class AverageBenchmarks
|
||||
return averages;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ColorRGB[] PixelHelper_3BPP_Image()
|
||||
{
|
||||
ColorRGB[] averages = new ColorRGB[_images3bpp.Count];
|
||||
for (int i = 0; i < _images3bpp.Count; i++)
|
||||
averages[i] = _images3bpp[i].Average();
|
||||
|
||||
return averages;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ColorRGBA[] PixelHelper_4BPP_Image()
|
||||
{
|
||||
ColorRGBA[] averages = new ColorRGBA[_images4bpp.Count];
|
||||
for (int i = 0; i < _images4bpp.Count; i++)
|
||||
averages[i] = _images4bpp[i].Average();
|
||||
|
||||
return averages;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ColorRGB[] Reference_3BPP()
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using HPPH.System.Drawing;
|
||||
#pragma warning disable CA1416
|
||||
|
||||
using HPPH.System.Drawing;
|
||||
|
||||
namespace HPPH.Benchmark;
|
||||
|
||||
@ -19,4 +21,18 @@ internal static class BenchmarkHelper
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
public static List<IImage<T>> GetSampleDataImages<T>()
|
||||
where T : struct, IColor
|
||||
{
|
||||
if (!Directory.Exists(SAMPLE_DATA_DIR)) throw new Exception("sample data not found!");
|
||||
|
||||
List<IImage<T>> colors = [];
|
||||
|
||||
IEnumerable<string> files = Directory.EnumerateFiles(SAMPLE_DATA_DIR, "*.png", SearchOption.AllDirectories);
|
||||
foreach (string file in files)
|
||||
colors.Add(ImageHelper.LoadImage(file).ConvertTo<T>());
|
||||
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@ public class MinMaxBenchmarks
|
||||
|
||||
private readonly List<ColorRGB[]> _colors3bpp;
|
||||
private readonly List<ColorRGBA[]> _colors4bpp;
|
||||
private readonly List<IImage<ColorRGB>> _images3bpp;
|
||||
private readonly List<IImage<ColorRGBA>> _images4bpp;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -22,6 +24,9 @@ public class MinMaxBenchmarks
|
||||
{
|
||||
_colors3bpp = BenchmarkHelper.GetSampleData<ColorRGB>();
|
||||
_colors4bpp = BenchmarkHelper.GetSampleData<ColorRGBA>();
|
||||
|
||||
_images3bpp = BenchmarkHelper.GetSampleDataImages<ColorRGB>();
|
||||
_images4bpp = BenchmarkHelper.GetSampleDataImages<ColorRGBA>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -48,6 +53,26 @@ public class MinMaxBenchmarks
|
||||
return minMax;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public IMinMax[] PixelHelper_3BPP_Image()
|
||||
{
|
||||
IMinMax[] minMax = new IMinMax[_images3bpp.Count];
|
||||
for (int i = 0; i < _images3bpp.Count; i++)
|
||||
minMax[i] = _images3bpp[i].MinMax();
|
||||
|
||||
return minMax;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public IMinMax[] PixelHelper_4BPP_Image()
|
||||
{
|
||||
IMinMax[] minMax = new IMinMax[_images4bpp.Count];
|
||||
for (int i = 0; i < _images4bpp.Count; i++)
|
||||
minMax[i] = _images4bpp[i].MinMax();
|
||||
|
||||
return minMax;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public IMinMax[] Reference_3BPP()
|
||||
{
|
||||
|
||||
@ -13,6 +13,8 @@ public class SumBenchmarks
|
||||
|
||||
private readonly List<ColorRGB[]> _colors3bpp;
|
||||
private readonly List<ColorRGBA[]> _colors4bpp;
|
||||
private readonly List<IImage<ColorRGB>> _images3bpp;
|
||||
private readonly List<IImage<ColorRGBA>> _images4bpp;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -22,6 +24,9 @@ public class SumBenchmarks
|
||||
{
|
||||
_colors3bpp = BenchmarkHelper.GetSampleData<ColorRGB>();
|
||||
_colors4bpp = BenchmarkHelper.GetSampleData<ColorRGBA>();
|
||||
|
||||
_images3bpp = BenchmarkHelper.GetSampleDataImages<ColorRGB>();
|
||||
_images4bpp = BenchmarkHelper.GetSampleDataImages<ColorRGBA>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -48,6 +53,26 @@ public class SumBenchmarks
|
||||
return sums;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISum[] PixelHelper_3BPP_Image()
|
||||
{
|
||||
ISum[] sums = new ISum[_colors3bpp.Count];
|
||||
for (int i = 0; i < _images3bpp.Count; i++)
|
||||
sums[i] = _images3bpp[i].Sum();
|
||||
|
||||
return sums;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISum[] PixelHelper_4BPP_Image()
|
||||
{
|
||||
ISum[] sums = new ISum[_images4bpp.Count];
|
||||
for (int i = 0; i < _images4bpp.Count; i++)
|
||||
sums[i] = _images4bpp[i].Sum();
|
||||
|
||||
return sums;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISum[] Reference_3BPP()
|
||||
{
|
||||
|
||||
@ -19,7 +19,6 @@ public class ColorSourceGenerator : IIncrementalGenerator
|
||||
private static readonly IGeneratorFeature[] FEATURES =
|
||||
[
|
||||
new Colors(),
|
||||
new Average(),
|
||||
new MinMax(),
|
||||
new Sum(),
|
||||
new Quantize()
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace HPPH.Generators;
|
||||
|
||||
internal class Average : IGeneratorFeature
|
||||
{
|
||||
public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat)
|
||||
{
|
||||
yield return ($"ColorFormat{colorFormat.Format}.Average", GenerateColorFormatAverage(colorFormat));
|
||||
}
|
||||
|
||||
public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray<ColorFormatData> colorFormats)
|
||||
{
|
||||
yield return ("IColorFormat.Average", GenerateColorFormatInterfaceAverage());
|
||||
}
|
||||
|
||||
private static string GenerateColorFormatAverage(ColorFormatData colorFormat)
|
||||
{
|
||||
return $$"""
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
|
||||
public sealed partial class ColorFormat{{colorFormat.Format}}
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IColor IColorFormat.Average(ReadOnlySpan<byte> data) => PixelHelper.Average(MemoryMarshal.Cast<byte, Color{{colorFormat.Format}}>(data));
|
||||
|
||||
#endregion
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static string GenerateColorFormatInterfaceAverage()
|
||||
{
|
||||
return """
|
||||
#nullable enable
|
||||
|
||||
namespace HPPH;
|
||||
|
||||
public partial interface IColorFormat
|
||||
{
|
||||
internal IColor Average(ReadOnlySpan<byte> data);
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@ internal class Colors : IGeneratorFeature
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -74,11 +74,27 @@ internal class Colors : IGeneratorFeature
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
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}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -142,11 +158,27 @@ internal class Colors : IGeneratorFeature
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
@ -186,6 +218,12 @@ internal class Colors : IGeneratorFeature
|
||||
private ColorFormat{{colorFormat.Format}}() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => Color{{colorFormat.Format}}.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
""";
|
||||
}
|
||||
@ -211,6 +249,8 @@ internal class Colors : IGeneratorFeature
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("""
|
||||
IColor CreateColor(byte r, byte g, byte b, byte a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
""");
|
||||
|
||||
@ -139,6 +139,7 @@ internal class MinMax : IGeneratorFeature
|
||||
return $$"""
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -147,7 +148,8 @@ internal class MinMax : IGeneratorFeature
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<Color{{colorFormat.Format}}, MinMax{{colorFormat.Format}}>(MemoryMarshal.Cast<byte, Color{{colorFormat.Format}}>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => {{(colorFormat.Bpp == 3 ? $"Unsafe.BitCast<Generic3ByteMinMax, MinMax{colorFormat.Format}>(data);" : "throw new NotSupportedException();")}}
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => {{(colorFormat.Bpp == 4 ? $"Unsafe.BitCast<Generic4ByteMinMax, MinMax{colorFormat.Format}>(data);" : "throw new NotSupportedException();")}}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -163,7 +165,8 @@ internal class MinMax : IGeneratorFeature
|
||||
|
||||
public partial interface IColorFormat
|
||||
{
|
||||
internal IMinMax MinMax(ReadOnlySpan<byte> data);
|
||||
internal IMinMax ToMinMax(Generic3ByteMinMax data);
|
||||
internal IMinMax ToMinMax(Generic4ByteMinMax data);
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
@ -106,6 +106,7 @@ internal class Sum : IGeneratorFeature
|
||||
return $$"""
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -114,7 +115,8 @@ internal class Sum : IGeneratorFeature
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<Color{{colorFormat.Format}}, Sum{{colorFormat.Format}}>(MemoryMarshal.Cast<byte, Color{{colorFormat.Format}}>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic{{colorFormat.Bpp}}ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, Sum{{colorFormat.Format}}>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -130,7 +132,8 @@ internal class Sum : IGeneratorFeature
|
||||
|
||||
public partial interface IColorFormat
|
||||
{
|
||||
internal ISum Sum(ReadOnlySpan<byte> data);
|
||||
internal Generic4LongData Sum(ReadOnlySpan<byte> data);
|
||||
internal ISum ToSum(Generic4LongData data);
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
|
||||
<Authors>Darth Affe</Authors>
|
||||
<Company>Wyrez</Company>
|
||||
<Language>en-US</Language>
|
||||
@ -41,6 +44,10 @@
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@ -1,20 +1,28 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace HPPH.SkiaSharp;
|
||||
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image, nameof(image));
|
||||
|
||||
SKBitmap bitmap = new(image.Width, image.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul);
|
||||
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;
|
||||
}
|
||||
@ -25,8 +33,12 @@ public static class ImageExtension
|
||||
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)
|
||||
=> Image<ColorBGRA>.Create(MemoryMarshal.Cast<SKColor, ColorBGRA>(bitmap.Pixels), bitmap.Width, bitmap.Height);
|
||||
public static IImage<ColorBGRA> ToImage(this SKBitmap bitmap)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bitmap, nameof(bitmap));
|
||||
|
||||
return Image<ColorBGRA>.Create(MemoryMarshal.Cast<SKColor, ColorBGRA>(bitmap.Pixels), bitmap.Width, bitmap.Height);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
|
||||
<Authors>Darth Affe</Authors>
|
||||
<Company>Wyrez</Company>
|
||||
<Language>en-US</Language>
|
||||
@ -41,6 +44,10 @@
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@ -9,6 +9,8 @@ public static class ImageExtension
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static unsafe Bitmap ToBitmap(this IImage image)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image, nameof(image));
|
||||
|
||||
switch (image.ColorFormat.BytesPerPixel)
|
||||
{
|
||||
case 3:
|
||||
@ -16,10 +18,10 @@ public static class ImageExtension
|
||||
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);
|
||||
|
||||
IImage<ColorBGR> convertedImage = image.ConvertTo<ColorBGR>();
|
||||
IImage<ColorBGR> img = image as IImage<ColorBGR> ?? image.ConvertTo<ColorBGR>();
|
||||
|
||||
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));
|
||||
ptr += bmpData.Stride;
|
||||
@ -35,10 +37,10 @@ public static class ImageExtension
|
||||
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);
|
||||
|
||||
IImage<ColorBGRA> convertedImage = image.ConvertTo<ColorBGRA>();
|
||||
IImage<ColorBGRA> img = image as IImage<ColorBGRA> ?? image.ConvertTo<ColorBGRA>();
|
||||
|
||||
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));
|
||||
ptr += bmpData.Stride;
|
||||
@ -57,6 +59,8 @@ public static class ImageExtension
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static byte[] ToPng(this IImage image)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image, nameof(image));
|
||||
|
||||
using Bitmap bitmap = ToBitmap(image);
|
||||
using MemoryStream ms = new();
|
||||
bitmap.Save(ms, ImageFormat.Png);
|
||||
@ -67,6 +71,8 @@ public static class ImageExtension
|
||||
[SupportedOSPlatform("windows")]
|
||||
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);
|
||||
ReadOnlySpan<byte> buffer = new(data.Scan0.ToPointer(), data.Stride * data.Height);
|
||||
|
||||
|
||||
@ -748,7 +748,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongXBig()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -756,7 +756,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongYBig()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -764,7 +764,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongXSmall()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -772,7 +772,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongYSmall()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -780,7 +780,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongX()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -788,7 +788,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongY()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -796,7 +796,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongWidth()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -804,7 +804,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongHeight()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -812,7 +812,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInvalidSizeWidth()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -820,7 +820,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInvalidSizeHeight()
|
||||
{
|
||||
Image<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -828,7 +828,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceWrongX()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -836,7 +836,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceWrongY()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -844,7 +844,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceWrongWidth()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -852,7 +852,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceWrongHeight()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -860,7 +860,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceInvalidSizeWidth()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -868,7 +868,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInterfaceInvalidSizeHeight()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -876,7 +876,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnsIndexerToBig()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -884,7 +884,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnsIndexerToSmall()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -892,7 +892,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowsIndexerToBig()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -900,7 +900,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowsIndexerToSmall()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -908,7 +908,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnIndexerToBig()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -916,7 +916,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnIndexerToSmall()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -924,7 +924,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowIndexerToBig()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
@ -932,7 +932,7 @@ public class ImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowIndexerToSmall()
|
||||
{
|
||||
IImage image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20);
|
||||
|
||||
@ -296,7 +296,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongXBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -304,7 +304,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongYBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -312,7 +312,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongXSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -320,7 +320,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void IndexerWrongYSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -328,7 +328,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongX()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -336,7 +336,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongY()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -344,7 +344,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongWidth()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -352,7 +352,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageWrongHeight()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -360,7 +360,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInvalidSizeWidth()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -368,7 +368,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SubImageInvalidSizeHeight()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -376,7 +376,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnsIndexerToBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -384,7 +384,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnsIndexerToSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -392,7 +392,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowsIndexerToBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -400,7 +400,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowsIndexerToSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -408,7 +408,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnIndexerToBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -416,7 +416,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ColumnIndexerToSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -424,7 +424,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowIndexerToBig()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
@ -432,7 +432,7 @@ public class RefImageTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(IndexOutOfRangeException))]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void RowIndexerToSmall()
|
||||
{
|
||||
RefImage<ColorRGB> image = TestDataHelper.CreateTestImage<ColorRGB>(10, 20).AsRefImage();
|
||||
|
||||
@ -33,6 +33,12 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorBGR> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorBGR>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorBGR>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -63,6 +69,12 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorARGB> converted = TestDataHelper.CreateTestImage<ColorRGBA>(1920, 1080).ConvertTo<ColorARGB>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorARGB>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -93,6 +105,12 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorBGRA> converted = TestDataHelper.CreateTestImage<ColorRGBA>(1920, 1080).ConvertTo<ColorBGRA>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorBGRA>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -123,6 +141,12 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorRGB> converted = TestDataHelper.CreateTestImage<ColorRGBA>(1920, 1080).ConvertTo<ColorRGB>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorRGB>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -153,6 +177,12 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorBGR> converted = TestDataHelper.CreateTestImage<ColorRGBA>(1920, 1080).ConvertTo<ColorBGR>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorBGR>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -183,6 +213,20 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorRGBA> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorRGBA>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
{
|
||||
ColorABGR refColor = TestDataHelper.GetColorFromLocation<ColorABGR>(x, y);
|
||||
ColorRGBA color = converted[x, y];
|
||||
|
||||
Assert.AreEqual(255, color.A, $"Wrong A at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.R, color.R, $"Wrong R at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.G, color.G, $"Wrong G at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.B, color.B, $"Wrong B at x: {x}, y: {y}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -213,6 +257,20 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorARGB> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorARGB>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
{
|
||||
ColorABGR refColor = TestDataHelper.GetColorFromLocation<ColorABGR>(x, y);
|
||||
ColorARGB color = converted[x, y];
|
||||
|
||||
Assert.AreEqual(255, color.A, $"Wrong A at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.R, color.R, $"Wrong R at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.G, color.G, $"Wrong G at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.B, color.B, $"Wrong B at x: {x}, y: {y}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -243,6 +301,20 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorBGRA> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorBGRA>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
{
|
||||
ColorABGR refColor = TestDataHelper.GetColorFromLocation<ColorABGR>(x, y);
|
||||
ColorBGRA color = converted[x, y];
|
||||
|
||||
Assert.AreEqual(255, color.A, $"Wrong A at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.R, color.R, $"Wrong R at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.G, color.G, $"Wrong G at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.B, color.B, $"Wrong B at x: {x}, y: {y}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -273,6 +345,20 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorABGR> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorABGR>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
{
|
||||
ColorABGR refColor = TestDataHelper.GetColorFromLocation<ColorABGR>(x, y);
|
||||
ColorABGR color = converted[x, y];
|
||||
|
||||
Assert.AreEqual(255, color.A, $"Wrong A at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.R, color.R, $"Wrong R at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.G, color.G, $"Wrong G at x: {x}, y: {y}");
|
||||
Assert.AreEqual(refColor.B, color.B, $"Wrong B at x: {x}, y: {y}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -303,5 +389,11 @@ public class ConvertTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IImage<ColorBGR> converted = TestDataHelper.CreateTestImage<ColorRGB>(1920, 1080).ConvertTo<ColorBGR>();
|
||||
|
||||
for (int y = 0; y < converted.Height; y++)
|
||||
for (int x = 0; x < converted.Width; x++)
|
||||
Assert.AreEqual(TestDataHelper.GetColorFromLocation<ColorBGR>(x, y), converted[x, y], $"Wrong color at x: {x}, y: {y}");
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,14 @@ public class SystemDrawingTests
|
||||
IImage image2 = bitmap.ToImage();
|
||||
|
||||
Assert.AreEqual(IColorFormat.BGR, image2.ColorFormat);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
Assert.AreEqual(image[x, y].A, image2[x, y].A, $"{x}-{y}");
|
||||
Assert.AreEqual(image[x, y].R, image2[x, y].R, $"{x}-{y}");
|
||||
Assert.AreEqual(image[x, y].G, image2[x, y].G, $"{x}-{y}");
|
||||
Assert.AreEqual(image[x, y].B, image2[x, y].B, $"{x}-{y}");
|
||||
}
|
||||
|
||||
image2 = image2.ConvertTo<ColorRGB>();
|
||||
|
||||
@ -30,6 +38,14 @@ public class SystemDrawingTests
|
||||
IImage image2 = bitmap.ToImage();
|
||||
|
||||
Assert.AreEqual(IColorFormat.BGRA, image2.ColorFormat);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
Assert.AreEqual(image[x, y].A, image2[x, y].A);
|
||||
Assert.AreEqual(image[x, y].R, image2[x, y].R);
|
||||
Assert.AreEqual(image[x, y].G, image2[x, y].G);
|
||||
Assert.AreEqual(image[x, y].B, image2[x, y].B);
|
||||
}
|
||||
|
||||
image2 = image2.ConvertTo<ColorRGBA>();
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -49,11 +49,27 @@ public readonly partial struct ColorABGR(byte a, byte b, byte g, byte r) : IColo
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -49,11 +49,27 @@ public readonly partial struct ColorARGB(byte a, byte r, byte g, byte b) : IColo
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -47,11 +47,27 @@ public readonly partial struct ColorBGR(byte b, byte g, byte r): IColor
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -49,11 +49,27 @@ public readonly partial struct ColorBGRA(byte b, byte g, byte r, byte a) : IColo
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatABGR
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorABGR, MinMaxABGR>(MemoryMarshal.Cast<byte, ColorABGR>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxABGR>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatABGR
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorABGR, SumABGR>(MemoryMarshal.Cast<byte, ColorABGR>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic4ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumABGR>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatABGR : IColorFormat
|
||||
private ColorFormatABGR() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorABGR.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatARGB
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorARGB, MinMaxARGB>(MemoryMarshal.Cast<byte, ColorARGB>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxARGB>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatARGB
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorARGB, SumARGB>(MemoryMarshal.Cast<byte, ColorARGB>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic4ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumARGB>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatARGB : IColorFormat
|
||||
private ColorFormatARGB() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorARGB.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGR
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorBGR, MinMaxBGR>(MemoryMarshal.Cast<byte, ColorBGR>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast<Generic3ByteMinMax, MinMaxBGR>(data);
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException();
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGR
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorBGR, SumBGR>(MemoryMarshal.Cast<byte, ColorBGR>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic3ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumBGR>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatBGR : IColorFormat
|
||||
private ColorFormatBGR() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorBGR.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGRA
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorBGRA, MinMaxBGRA>(MemoryMarshal.Cast<byte, ColorBGRA>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxBGRA>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGRA
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorBGRA, SumBGRA>(MemoryMarshal.Cast<byte, ColorBGRA>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic4ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumBGRA>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatBGRA : IColorFormat
|
||||
private ColorFormatBGRA() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorBGRA.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGB
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorRGB, MinMaxRGB>(MemoryMarshal.Cast<byte, ColorRGB>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast<Generic3ByteMinMax, MinMaxRGB>(data);
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException();
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGB
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorRGB, SumRGB>(MemoryMarshal.Cast<byte, ColorRGB>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic3ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumRGB>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatRGB : IColorFormat
|
||||
private ColorFormatRGB() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorRGB.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGBA
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorRGBA, MinMaxRGBA>(MemoryMarshal.Cast<byte, ColorRGBA>(data));
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
|
||||
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxRGBA>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGBA
|
||||
{
|
||||
#region Methods
|
||||
|
||||
unsafe ISum IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum<ColorRGBA, SumRGBA>(MemoryMarshal.Cast<byte, ColorRGBA>(data));
|
||||
unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan<byte> data) => PixelHelper.Sum(MemoryMarshal.Cast<byte, Generic4ByteData>(data));
|
||||
unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast<Generic4LongData, SumRGBA>(data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -21,4 +21,10 @@ public sealed partial class ColorFormatRGBA : IColorFormat
|
||||
private ColorFormatRGBA() {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IColor CreateColor(byte r, byte g, byte b, byte a) => ColorRGBA.Create(r, g, b, a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -47,11 +47,27 @@ public readonly partial struct ColorRGB(byte r, byte g, byte b): IColor
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ namespace HPPH;
|
||||
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
|
||||
[SkipLocalsInit]
|
||||
[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
|
||||
|
||||
@ -49,11 +49,27 @@ public readonly partial struct ColorRGBA(byte r, byte g, byte b, byte a) : IColo
|
||||
|
||||
#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
|
||||
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]";
|
||||
|
||||
|
||||
@ -13,5 +13,7 @@ public partial interface IColorFormat
|
||||
public static ColorFormatRGBA RGBA => ColorFormatRGBA.Instance;
|
||||
public static ColorFormatBGRA BGRA => ColorFormatBGRA.Instance;
|
||||
|
||||
IColor CreateColor(byte r, byte g, byte b, byte a);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -4,5 +4,6 @@ namespace HPPH;
|
||||
|
||||
public partial interface IColorFormat
|
||||
{
|
||||
internal IMinMax MinMax(ReadOnlySpan<byte> data);
|
||||
internal IMinMax ToMinMax(Generic3ByteMinMax data);
|
||||
internal IMinMax ToMinMax(Generic4ByteMinMax data);
|
||||
}
|
||||
@ -4,5 +4,6 @@ namespace HPPH;
|
||||
|
||||
public partial interface IColorFormat
|
||||
{
|
||||
internal ISum Sum(ReadOnlySpan<byte> data);
|
||||
internal Generic4LongData Sum(ReadOnlySpan<byte> data);
|
||||
internal ISum ToSum(Generic4LongData data);
|
||||
}
|
||||
@ -8,6 +8,9 @@
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
|
||||
<Authors>Darth Affe</Authors>
|
||||
<Company>Wyrez</Company>
|
||||
<Language>en-US</Language>
|
||||
@ -43,6 +46,10 @@
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<GlobalAnalyzerConfigFiles Include="../Analyzers.globalconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@ -40,13 +40,15 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public ref readonly T this[int x, int y]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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));
|
||||
}
|
||||
@ -58,7 +60,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
@ -70,12 +72,14 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CA2208
|
||||
|
||||
/// <inheritdoc />
|
||||
IImageRows IImage.Rows => new IColorImageRows<T>(_buffer, _x, _y, Width, Height, _stride);
|
||||
|
||||
@ -120,13 +124,14 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
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.");
|
||||
|
||||
byte[] data = new byte[buffer.Length];
|
||||
byte[] data = GC.AllocateUninitializedArray<byte>(buffer.Length);
|
||||
buffer.CopyTo(data);
|
||||
return new Image<T>(data, 0, 0, width, height, 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 (buffer.Length < (height * stride)) throw new ArgumentException("Not enough data in the buffer.");
|
||||
|
||||
@ -147,7 +152,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
else
|
||||
{
|
||||
byte[] data = ToRawArray();
|
||||
byte[] target = new byte[Width * Height * targetBpp];
|
||||
byte[] target = GC.AllocateUninitializedArray<byte>(Width * Height * targetBpp);
|
||||
MemoryMarshal.Cast<byte, T>(data.AsSpan()).Convert(MemoryMarshal.Cast<byte, TColor>(target));
|
||||
return new Image<TColor>(target, 0, 0, Width, Height, Width * targetBpp);
|
||||
}
|
||||
@ -175,7 +180,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
/// <inheritdoc />
|
||||
public byte[] ToRawArray()
|
||||
{
|
||||
byte[] array = new byte[SizeInBytes];
|
||||
byte[] array = GC.AllocateUninitializedArray<byte>(SizeInBytes);
|
||||
CopyTo(array);
|
||||
return array;
|
||||
}
|
||||
@ -183,7 +188,7 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
/// <inheritdoc />
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] colors = new T[Width * Height];
|
||||
T[] colors = GC.AllocateUninitializedArray<T>(Width * Height);
|
||||
CopyTo(colors);
|
||||
return colors;
|
||||
}
|
||||
@ -244,49 +249,41 @@ public sealed class Image<T> : IImage<T>, IEquatable<Image<T>>
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
//TODO DarthAffe 20.07.2024: All of those equals can be optimized
|
||||
/// <inheritdoc />
|
||||
public bool Equals(IImage? other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
if (other.ColorFormat != ColorFormat) return false;
|
||||
if (other.Width != Width) return false;
|
||||
if (other.Height != Height) return false;
|
||||
|
||||
for (int y = 0; y < Height; y++)
|
||||
for (int x = 0; x < Width; x++)
|
||||
if (!this[x, y].Equals(other[x, y]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return Equals(other.AsRefImage<T>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(IImage<T>? other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
if (other.Width != Width) return false;
|
||||
if (other.Height != Height) return false;
|
||||
|
||||
for (int y = 0; y < Height; y++)
|
||||
for (int x = 0; x < Width; x++)
|
||||
if (!this[x, y].Equals(other[x, y]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return Equals(other.AsRefImage());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(Image<T>? other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return Equals(other.AsRefImage());
|
||||
}
|
||||
|
||||
public bool Equals(RefImage<T> other)
|
||||
{
|
||||
if (other.Width != Width) return false;
|
||||
if (other.Height != Height) return false;
|
||||
|
||||
RefImage<T> thisRef = AsRefImage();
|
||||
|
||||
for (int y = 0; y < Height; y++)
|
||||
for (int x = 0; x < Width; x++)
|
||||
if (!this[x, y].Equals(other[x, y]))
|
||||
return false;
|
||||
if (!thisRef.Rows[y].AsByteSpan().SequenceEqual(other.Rows[y].AsByteSpan()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ public readonly ref struct ImageColumn<T>
|
||||
{
|
||||
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))));
|
||||
}
|
||||
@ -64,7 +64,7 @@ public readonly ref struct ImageColumn<T>
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] array = new T[Length];
|
||||
T[] array = GC.AllocateUninitializedArray<T>(Length);
|
||||
CopyTo(array);
|
||||
return array;
|
||||
}
|
||||
@ -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)
|
||||
[SkipLocalsInit]
|
||||
internal class IColorImageColumn<T> : IImageColumn
|
||||
internal sealed class IColorImageColumn<T> : IImageColumn
|
||||
where T : struct, IColor
|
||||
{
|
||||
#region Properties & Fields
|
||||
@ -139,7 +139,7 @@ internal class IColorImageColumn<T> : IImageColumn
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ public readonly ref struct ImageColumns<T>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
@ -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)
|
||||
[SkipLocalsInit]
|
||||
internal class IColorImageColumns<T> : IImageColumns
|
||||
internal sealed class IColorImageColumns<T> : IImageColumns
|
||||
where T : struct, IColor
|
||||
{
|
||||
#region Properties & Fields
|
||||
@ -126,7 +126,7 @@ internal class IColorImageColumns<T> : IImageColumns
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ public readonly ref struct ImageRow<T>
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -47,6 +47,9 @@ public readonly ref struct ImageRow<T>
|
||||
|
||||
#region Methods
|
||||
|
||||
public ReadOnlySpan<T> AsSpan() => MemoryMarshal.Cast<byte, T>(AsByteSpan());
|
||||
public ReadOnlySpan<byte> AsByteSpan() => _buffer.Slice(_start, SizeInBytes);
|
||||
|
||||
public void CopyTo(Span<T> destination) => CopyTo(MemoryMarshal.AsBytes(destination));
|
||||
|
||||
public void CopyTo(Span<byte> destination)
|
||||
@ -54,15 +57,16 @@ public readonly ref struct ImageRow<T>
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));
|
||||
|
||||
_buffer.Slice(_start, SizeInBytes).CopyTo(destination);
|
||||
AsByteSpan().CopyTo(destination);
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] array = new T[Length];
|
||||
T[] array = GC.AllocateUninitializedArray<T>(Length);
|
||||
CopyTo(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ImageRowEnumerator GetEnumerator() => new(this);
|
||||
@ -108,7 +112,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)
|
||||
[SkipLocalsInit]
|
||||
internal class IColorImageRow<T> : IImageRow
|
||||
internal sealed class IColorImageRow<T> : IImageRow
|
||||
where T : struct, IColor
|
||||
{
|
||||
#region Properties & Fields
|
||||
@ -132,7 +136,7 @@ internal class IColorImageRow<T> : IImageRow
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -153,6 +157,9 @@ internal class IColorImageRow<T> : IImageRow
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlySpan<byte> AsByteSpan() => _buffer.AsSpan().Slice(_start, SizeInBytes);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Span<IColor> destination)
|
||||
{
|
||||
|
||||
@ -28,7 +28,7 @@ public readonly ref struct ImageRows<T>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
@ -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)
|
||||
[SkipLocalsInit]
|
||||
internal class IColorImageRows<T> : IImageRows
|
||||
internal sealed class IColorImageRows<T> : IImageRows
|
||||
where T : struct, IColor
|
||||
{
|
||||
#region Properties & Fields
|
||||
@ -128,7 +128,7 @@ internal class IColorImageRows<T> : IImageRows
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ public interface IImageRow : IEnumerable<IColor>
|
||||
/// <returns>The <see cref="IColor"/> at the specified location.</returns>
|
||||
IColor this[int x] { get; }
|
||||
|
||||
ReadOnlySpan<byte> AsByteSpan();
|
||||
|
||||
void CopyTo(Span<IColor> destination);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -34,12 +34,14 @@ public readonly ref struct RefImage<T>
|
||||
|
||||
#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]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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));
|
||||
}
|
||||
@ -50,12 +52,14 @@ public readonly ref struct RefImage<T>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CA2208
|
||||
|
||||
public ImageRows<T> Rows
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -124,7 +128,7 @@ public readonly ref struct RefImage<T>
|
||||
/// <returns>The new array containing the data of this <see cref="RefImage{T}"/>.</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] array = new T[Width * Height];
|
||||
T[] array = GC.AllocateUninitializedArray<T>(Width * Height);
|
||||
CopyTo(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -12,62 +11,34 @@ public static partial class PixelHelper
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image);
|
||||
|
||||
int dataLength = image.SizeInBytes;
|
||||
float count = image.Width * image.Height;
|
||||
|
||||
if (dataLength <= 1024)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[dataLength];
|
||||
ISum sum = Sum(image);
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.Average(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] array = ArrayPool<byte>.Shared.Rent(dataLength);
|
||||
Span<byte> buffer = array.AsSpan()[..dataLength];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.Average(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
return image.ColorFormat.CreateColor((byte)MathF.Round(sum.R / count),
|
||||
(byte)MathF.Round(sum.G / count),
|
||||
(byte)MathF.Round(sum.B / count),
|
||||
(byte)MathF.Round(sum.A / count));
|
||||
}
|
||||
|
||||
public static T Average<T>(this IImage<T> image)
|
||||
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)
|
||||
where T : struct, IColor
|
||||
{
|
||||
int dataLength = image.Width * image.Height;
|
||||
int sizeInBytes = dataLength * T.ColorFormat.BytesPerPixel;
|
||||
float count = image.Width * image.Height;
|
||||
|
||||
if (sizeInBytes <= 1024)
|
||||
{
|
||||
Span<T> buffer = MemoryMarshal.Cast<byte, T>(stackalloc byte[sizeInBytes]);
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return Average(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
T[] array = ArrayPool<T>.Shared.Rent(dataLength);
|
||||
Span<T> buffer = array.AsSpan()[..dataLength];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return Average(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
ISum sum = Sum(image);
|
||||
return (T)T.Create((byte)MathF.Round(sum.R / count),
|
||||
(byte)MathF.Round(sum.G / count),
|
||||
(byte)MathF.Round(sum.B / count),
|
||||
(byte)MathF.Round(sum.A / count));
|
||||
}
|
||||
|
||||
public static T Average<T>(this Span<T> colors)
|
||||
|
||||
@ -5,6 +5,12 @@ namespace HPPH;
|
||||
|
||||
public static unsafe partial class PixelHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int MIN_BATCH_SIZE = 8;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public static Span<TTarget> ConvertInPlace<TSource, TTarget>(this Span<TSource> colors)
|
||||
@ -32,7 +38,7 @@ public static unsafe partial class PixelHelper
|
||||
{
|
||||
if (colors == null) throw new ArgumentNullException(nameof(colors));
|
||||
|
||||
TTarget[] buffer = new TTarget[colors.Length];
|
||||
TTarget[] buffer = GC.AllocateUninitializedArray<TTarget>(colors.Length);
|
||||
Convert(colors, buffer.AsSpan());
|
||||
return buffer;
|
||||
}
|
||||
@ -43,7 +49,7 @@ public static unsafe partial class PixelHelper
|
||||
{
|
||||
if (colors == null) throw new ArgumentNullException(nameof(colors));
|
||||
|
||||
TTarget[] buffer = new TTarget[colors.Length];
|
||||
TTarget[] buffer = GC.AllocateUninitializedArray<TTarget>(colors.Length);
|
||||
Convert(colors, buffer.AsSpan());
|
||||
return buffer;
|
||||
}
|
||||
@ -105,6 +111,8 @@ public static unsafe partial class PixelHelper
|
||||
|
||||
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> targetMapping = targetFormat.ByteMapping;
|
||||
|
||||
@ -133,12 +141,103 @@ public static unsafe partial class PixelHelper
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[missingElements * BPP];
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(tar, buffer.Length));
|
||||
}
|
||||
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);
|
||||
|
||||
Span<byte> buffer = stackalloc byte[missing * BPP];
|
||||
for (int i = 0; i < missing; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = missingSrc[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = missingSrc[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = missingSrc[(i * BPP) + maskVector[2]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(missingTar, buffer.Length));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[missingElements * BPP];
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(tar, buffer.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> targetMapping = targetFormat.ByteMapping;
|
||||
|
||||
@ -166,49 +265,112 @@ public static unsafe partial class PixelHelper
|
||||
(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));
|
||||
|
||||
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)
|
||||
fixed (byte* targetPtr = target)
|
||||
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* src = sourcePtr;
|
||||
byte* tar = targetPtr;
|
||||
byte* sourcePtr = fixedSourcePtr;
|
||||
byte* targetPtr = fixedTargetPtr;
|
||||
|
||||
for (int i = 0; i < chunks; i++)
|
||||
if (batches == 1)
|
||||
{
|
||||
Vector128<byte> vector = Vector128.Load(src);
|
||||
Vector128.Shuffle(vector, maskVector).Store(tar);
|
||||
byte* src = sourcePtr;
|
||||
byte* tar = targetPtr;
|
||||
|
||||
src += bytesPerVector;
|
||||
tar += bytesPerVector;
|
||||
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;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[missingElements * BPP];
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
|
||||
buffer[(i * BPP) + 3] = src[(i * BPP) + maskVector[3]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(tar, buffer.Length));
|
||||
}
|
||||
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
|
||||
for (int j = 0; j < buffer.Length; j++)
|
||||
buffer[j] = src[mask[j]];
|
||||
int missing = elements - (batchSize * batches);
|
||||
if (missing > 0)
|
||||
{
|
||||
byte* missingSrc = sourcePtr + (batches * batchSize * BPP);
|
||||
byte* missingTar = targetPtr + (batches * batchSize * BPP);
|
||||
|
||||
buffer.CopyTo(new Span<byte>(tar, buffer.Length));
|
||||
Span<byte> buffer = stackalloc byte[missing * BPP];
|
||||
for (int i = 0; i < missing; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = missingSrc[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = missingSrc[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = missingSrc[(i * BPP) + maskVector[2]];
|
||||
buffer[(i * BPP) + 3] = missingSrc[(i * BPP) + maskVector[3]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(missingTar, buffer.Length));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[missingElements * BPP];
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
{
|
||||
buffer[(i * BPP) + 0] = src[(i * BPP) + maskVector[0]];
|
||||
buffer[(i * BPP) + 1] = src[(i * BPP) + maskVector[1]];
|
||||
buffer[(i * BPP) + 2] = src[(i * BPP) + maskVector[2]];
|
||||
buffer[(i * BPP) + 3] = src[(i * BPP) + maskVector[3]];
|
||||
}
|
||||
buffer.CopyTo(new Span<byte>(tar, buffer.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> targetMapping = targetFormat.ByteMapping;
|
||||
|
||||
// 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[1] == Color.A ? byte.MaxValue : (byte)0,
|
||||
@ -270,46 +432,105 @@ public static unsafe partial class PixelHelper
|
||||
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> 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)
|
||||
fixed (byte* targetPtr = target)
|
||||
int chunks = elements / targetElementsPerVector;
|
||||
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* tar = targetPtr;
|
||||
byte* sourcePtr = fixedSourcePtr;
|
||||
byte* targetPtr = fixedTargetPtr;
|
||||
|
||||
for (int i = 0; i < chunks; i++)
|
||||
if (batches == 1)
|
||||
{
|
||||
Vector128<byte> vector = Vector128.Load(src);
|
||||
Vector128<byte> shuffled = Vector128.Shuffle(vector, maskVector);
|
||||
Vector128.BitwiseOr(shuffled, alphaMaskVector).Store(tar);
|
||||
byte* src = sourcePtr;
|
||||
byte* tar = targetPtr;
|
||||
|
||||
src += sourceBytesPerVector;
|
||||
tar += targetBytesPerVector;
|
||||
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]]);
|
||||
}
|
||||
}
|
||||
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
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
for (int j = 0; j < targetBpp; j++)
|
||||
buffer[(i * targetBpp) + j] = Math.Max(isAlpha[j], src[(i * sourceBpp) + mask[j]]);
|
||||
int missing = elements - (batchSize * batches);
|
||||
if (missing > 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
const int SOURCE_BPP = 4;
|
||||
const int TARGET_BPP = 3;
|
||||
|
||||
ReadOnlySpan<byte> sourceMapping = sourceFormat.ByteMapping;
|
||||
ReadOnlySpan<byte> targetMapping = targetFormat.ByteMapping;
|
||||
|
||||
@ -340,39 +561,91 @@ public static unsafe partial class PixelHelper
|
||||
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));
|
||||
|
||||
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)
|
||||
fixed (byte* targetPtr = target)
|
||||
int chunks = elements / sourceElementsPerVector;
|
||||
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* tar = targetPtr;
|
||||
byte* sourcePtr = fixedSourcePtr;
|
||||
byte* targetPtr = fixedTargetPtr;
|
||||
|
||||
for (int i = 0; i < chunks; i++)
|
||||
if (batches == 1)
|
||||
{
|
||||
Vector128<byte> vector = Vector128.Load(src);
|
||||
Vector128.Shuffle(vector, maskVector).Store(tar);
|
||||
byte* src = sourcePtr;
|
||||
byte* tar = targetPtr;
|
||||
|
||||
src += sourceBytesPerVector;
|
||||
tar += targetBytesPerVector;
|
||||
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) + 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
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
for (int j = 0; j < targetBpp; j++)
|
||||
buffer[(i * targetBpp) + j] = src[(i * sourceBpp) + mask[j]];
|
||||
int missing = elements - (batchSize * batches);
|
||||
if (missing > 0)
|
||||
{
|
||||
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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -13,90 +12,122 @@ public static unsafe partial class PixelHelper
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image);
|
||||
|
||||
int dataLength = image.SizeInBytes;
|
||||
IColorFormat colorFormat = image.ColorFormat;
|
||||
|
||||
if (dataLength <= 1024)
|
||||
if (colorFormat.BytesPerPixel == 3)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[dataLength];
|
||||
if (image.Height == 0) return colorFormat.ToMinMax(new Generic3ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue));
|
||||
if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(image.Rows[0].AsByteSpan())));
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.MinMax(buffer);
|
||||
Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
result = MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(image.Rows[y].AsByteSpan()),
|
||||
result.B1Min, result.B1Max,
|
||||
result.B2Min, result.B2Max,
|
||||
result.B3Min, result.B3Max);
|
||||
|
||||
return colorFormat.ToMinMax(result);
|
||||
}
|
||||
else
|
||||
|
||||
if (colorFormat.BytesPerPixel == 4)
|
||||
{
|
||||
byte[] array = ArrayPool<byte>.Shared.Rent(dataLength);
|
||||
Span<byte> buffer = array.AsSpan()[..dataLength];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.MinMax(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
if (image.Height == 0) return colorFormat.ToMinMax(new Generic4ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue));
|
||||
if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[0].AsByteSpan())));
|
||||
|
||||
Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
result = MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[y].AsByteSpan()),
|
||||
result.B1Min, result.B1Max,
|
||||
result.B2Min, result.B2Max,
|
||||
result.B3Min, result.B3Max,
|
||||
result.B4Min, result.B4Max);
|
||||
|
||||
return colorFormat.ToMinMax(result);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Data is not of a supported valid color-type.");
|
||||
}
|
||||
|
||||
public static IMinMax MinMax<T>(this IImage<T> image)
|
||||
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)
|
||||
where T : struct, IColor
|
||||
{
|
||||
int dataLength = image.Width * image.Height;
|
||||
int sizeInBytes = dataLength * T.ColorFormat.BytesPerPixel;
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
|
||||
if (sizeInBytes <= 1024)
|
||||
if (colorFormat.BytesPerPixel == 3)
|
||||
{
|
||||
Span<T> buffer = MemoryMarshal.Cast<byte, T>(stackalloc byte[sizeInBytes]);
|
||||
if (image.Height == 0) return colorFormat.ToMinMax(new Generic3ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue));
|
||||
if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(image.Rows[0].AsByteSpan())));
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return MinMax(buffer);
|
||||
Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
result = MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(image.Rows[y].AsByteSpan()),
|
||||
result.B1Min, result.B1Max,
|
||||
result.B2Min, result.B2Max,
|
||||
result.B3Min, result.B3Max);
|
||||
|
||||
return colorFormat.ToMinMax(result);
|
||||
}
|
||||
else
|
||||
|
||||
if (colorFormat.BytesPerPixel == 4)
|
||||
{
|
||||
T[] array = ArrayPool<T>.Shared.Rent(dataLength);
|
||||
Span<T> buffer = array.AsSpan()[..(dataLength)];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return MinMax(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
}
|
||||
if (image.Height == 0) return colorFormat.ToMinMax(new Generic4ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue));
|
||||
if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[0].AsByteSpan())));
|
||||
|
||||
Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
result = MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[y].AsByteSpan()),
|
||||
result.B1Min, result.B1Max,
|
||||
result.B2Min, result.B2Max,
|
||||
result.B3Min, result.B3Max,
|
||||
result.B4Min, result.B4Max);
|
||||
|
||||
return colorFormat.ToMinMax(result);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Data is not of a supported valid color-type.");
|
||||
}
|
||||
|
||||
public static IMinMax MinMax<T>(this Span<T> colors)
|
||||
where T : struct, IColor
|
||||
=> T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors));
|
||||
|
||||
public static IMinMax MinMax<T>(this ReadOnlySpan<T> colors)
|
||||
where T : struct, IColor
|
||||
=> T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors));
|
||||
|
||||
internal static IMinMax MinMax<T, TMinMax>(ReadOnlySpan<T> colors)
|
||||
where T : struct, IColor
|
||||
where TMinMax : struct, IMinMax
|
||||
{
|
||||
if (colors == null) throw new ArgumentNullException(nameof(colors));
|
||||
|
||||
return T.ColorFormat.BytesPerPixel switch
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
return colorFormat.BytesPerPixel switch
|
||||
{
|
||||
3 => Unsafe.BitCast<Generic3ByteMinMax, TMinMax>(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
|
||||
4 => Unsafe.BitCast<Generic4ByteMinMax, TMinMax>(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
||||
3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
|
||||
4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
||||
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
|
||||
};
|
||||
}
|
||||
|
||||
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data)
|
||||
public static IMinMax MinMax<T>(this ReadOnlySpan<T> colors)
|
||||
where T : struct, IColor
|
||||
{
|
||||
byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue;
|
||||
byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue;
|
||||
if (colors == null) throw new ArgumentNullException(nameof(colors));
|
||||
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
return colorFormat.BytesPerPixel switch
|
||||
{
|
||||
3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
|
||||
4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
||||
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
|
||||
};
|
||||
}
|
||||
|
||||
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3)
|
||||
{
|
||||
const int BYTES_PER_COLOR = 3;
|
||||
int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR;
|
||||
|
||||
@ -162,11 +193,12 @@ public static unsafe partial class PixelHelper
|
||||
return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3);
|
||||
}
|
||||
|
||||
private static Generic4ByteMinMax MinMax(ReadOnlySpan<Generic4ByteData> data)
|
||||
{
|
||||
byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue, minB4 = byte.MaxValue;
|
||||
byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue, maxB4 = byte.MinValue;
|
||||
|
||||
private static Generic4ByteMinMax MinMax(ReadOnlySpan<Generic4ByteData> data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Generic4ByteMinMax MinMax(ReadOnlySpan<Generic4ByteData> data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3, byte minB4, byte maxB4)
|
||||
{
|
||||
const int BYTES_PER_COLOR = 4;
|
||||
int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR;
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace HPPH;
|
||||
|
||||
@ -40,7 +39,11 @@ public static partial class PixelHelper
|
||||
|
||||
public static T[] CreateColorPalette<T>(this IImage<T> image, int paletteSize)
|
||||
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)
|
||||
where T : unmanaged, IColor
|
||||
@ -165,7 +168,11 @@ public static partial class PixelHelper
|
||||
|
||||
public static T[] CreateSimpleColorPalette<T>(this IImage<T> image, int paletteSize)
|
||||
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)
|
||||
where T : unmanaged, IColor
|
||||
@ -238,8 +245,6 @@ public static partial class PixelHelper
|
||||
ColorCube<T>[] cubes = new ColorCube<T>[1 << splits];
|
||||
cubes[0] = new ColorCube<T>(0, colors.Length, SortTarget.None);
|
||||
|
||||
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
|
||||
|
||||
int colorsLength = colors.Length;
|
||||
fixed (T* colorsPtr = colors)
|
||||
{
|
||||
@ -250,7 +255,7 @@ public static partial class PixelHelper
|
||||
{
|
||||
int currentCubeCount = 1 << i;
|
||||
|
||||
Parallel.For(0, currentCubeCount, parallelOptions, CreateCubes);
|
||||
Parallel.For(0, currentCubeCount, PARALLEL_OPTIONS, CreateCubes);
|
||||
|
||||
void CreateCubes(int index)
|
||||
{
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace HPPH;
|
||||
@ -14,88 +13,64 @@ public static unsafe partial class PixelHelper
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image);
|
||||
|
||||
int dataLength = image.SizeInBytes;
|
||||
IColorFormat colorFormat = image.ColorFormat;
|
||||
|
||||
if (dataLength <= 1024)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[dataLength];
|
||||
if (image.Height == 0) return colorFormat.ToSum(new Generic4LongData(0, 0, 0, 0));
|
||||
if (image.Height == 1) return colorFormat.ToSum(colorFormat.Sum(image.Rows[0].AsByteSpan()));
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.Sum(buffer);
|
||||
}
|
||||
else
|
||||
Vector256<long> result = Vector256<long>.Zero;
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
byte[] array = ArrayPool<byte>.Shared.Rent(dataLength);
|
||||
Span<byte> buffer = array.AsSpan()[..dataLength];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return image.ColorFormat.Sum(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan());
|
||||
Vector256<long> rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As<Generic4LongData, long>(ref rowSum));
|
||||
result = Vector256.Add(result, rowSumVector);
|
||||
}
|
||||
|
||||
return colorFormat.ToSum(Unsafe.BitCast<Vector256<long>, Generic4LongData>(result));
|
||||
}
|
||||
|
||||
public static ISum Sum<T>(this IImage<T> image)
|
||||
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)
|
||||
where T : struct, IColor
|
||||
{
|
||||
int dataLength = image.Width * image.Height;
|
||||
int sizeInBytes = dataLength * T.ColorFormat.BytesPerPixel;
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
|
||||
if (sizeInBytes <= 1024)
|
||||
{
|
||||
Span<T> buffer = MemoryMarshal.Cast<byte, T>(stackalloc byte[sizeInBytes]);
|
||||
if (image.Height == 0) return colorFormat.ToSum(new Generic4LongData(0, 0, 0, 0));
|
||||
if (image.Height == 1) return colorFormat.ToSum(colorFormat.Sum(image.Rows[0].AsByteSpan()));
|
||||
|
||||
image.CopyTo(buffer);
|
||||
return Sum(buffer);
|
||||
}
|
||||
else
|
||||
Vector256<long> result = Vector256<long>.Zero;
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
T[] array = ArrayPool<T>.Shared.Rent(dataLength);
|
||||
Span<T> buffer = array.AsSpan()[..(dataLength)];
|
||||
try
|
||||
{
|
||||
image.CopyTo(buffer);
|
||||
return Sum(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
}
|
||||
Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan());
|
||||
Vector256<long> rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As<Generic4LongData, long>(ref rowSum));
|
||||
result = Vector256.Add(result, rowSumVector);
|
||||
}
|
||||
|
||||
return colorFormat.ToSum(Unsafe.BitCast<Vector256<long>, Generic4LongData>(result));
|
||||
}
|
||||
|
||||
public static ISum Sum<T>(this ReadOnlySpan<T> colors)
|
||||
where T : struct, IColor
|
||||
=> T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors));
|
||||
{
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
return colorFormat.ToSum(colorFormat.Sum(MemoryMarshal.AsBytes(colors)));
|
||||
}
|
||||
|
||||
public static ISum Sum<T>(this Span<T> colors)
|
||||
where T : struct, IColor
|
||||
=> T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors));
|
||||
|
||||
internal static ISum Sum<T, TSum>(ReadOnlySpan<T> colors)
|
||||
where T : struct, IColor
|
||||
where TSum : struct, ISum
|
||||
{
|
||||
if (colors == null) throw new ArgumentNullException(nameof(colors));
|
||||
|
||||
return T.ColorFormat.BytesPerPixel switch
|
||||
{
|
||||
// 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))),
|
||||
4 => Unsafe.BitCast<Generic4LongData, TSum>(Sum(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
|
||||
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
|
||||
};
|
||||
IColorFormat colorFormat = T.ColorFormat;
|
||||
return colorFormat.ToSum(colorFormat.Sum(MemoryMarshal.AsBytes(colors)));
|
||||
}
|
||||
|
||||
private static Generic4LongData Sum(ReadOnlySpan<Generic3ByteData> data)
|
||||
|
||||
internal static Generic4LongData Sum(ReadOnlySpan<Generic3ByteData> data)
|
||||
{
|
||||
long b1Sum = 0, b2Sum = 0, b3Sum = 0;
|
||||
|
||||
@ -211,7 +186,7 @@ public static unsafe partial class PixelHelper
|
||||
return new Generic4LongData(b1Sum, b2Sum, b3Sum, data.Length * 255);
|
||||
}
|
||||
|
||||
private static Generic4LongData Sum(ReadOnlySpan<Generic4ByteData> data)
|
||||
internal static Generic4LongData Sum(ReadOnlySpan<Generic4ByteData> data)
|
||||
{
|
||||
long b1Sum, b2Sum, b3Sum, b4Sum;
|
||||
int i = 0;
|
||||
|
||||
@ -3,4 +3,7 @@
|
||||
namespace HPPH;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public static partial class PixelHelper;
|
||||
public static partial class PixelHelper
|
||||
{
|
||||
private static readonly ParallelOptions PARALLEL_OPTIONS = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
|
||||
}
|
||||
|
||||
14
README.md
14
README.md
@ -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).
|
||||
|
||||
| Method | Mean | Error | StdDev | Allocated |
|
||||
|----------- |---------:|----------:|----------:|----------:|
|
||||
| RGBToBGR | 6.272 ms | 0.0288 ms | 0.0240 ms | 8.81 MB |
|
||||
| RGBToBGRA | 8.534 ms | 0.0684 ms | 0.0640 ms | 11.75 MB |
|
||||
| RGBAToABGR | 8.128 ms | 0.0927 ms | 0.0867 ms | 11.75 MB |
|
||||
| ARGBToBGR | 8.004 ms | 0.0353 ms | 0.0313 ms | 8.81 MB |
|
||||
| Method | Mean | Error | StdDev | Allocated |
|
||||
|------------------- |---------:|----------:|----------:|------------:|
|
||||
| RGBToBGR | 1.487 ms | 0.0221 ms | 0.0196 ms | 9073.58 KB |
|
||||
| RGBToBGRA | 1.676 ms | 0.0330 ms | 0.0353 ms | 12064.76 KB |
|
||||
| RGBAToABGR | 1.766 ms | 0.0348 ms | 0.0476 ms | 12084.93 KB |
|
||||
| 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 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user