From f4c1eaddd6fe7a3ecfe62d4c9e9ff8ab7a5e170d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 19 Aug 2024 22:19:25 +0200 Subject: [PATCH 1/3] Improved performance of Sum on Images --- HPPH.Benchmark/BenchmarkHelper.cs | 14 +++ HPPH.Benchmark/SumBenchmarks.cs | 25 ++++++ HPPH.Generators/Features/Sum.cs | 7 +- .../ColorFormatABGR.Sum.g.cs | 4 +- .../ColorFormatARGB.Sum.g.cs | 4 +- .../ColorFormatBGR.Sum.g.cs | 4 +- .../ColorFormatBGRA.Sum.g.cs | 4 +- .../ColorFormatRGB.Sum.g.cs | 4 +- .../ColorFormatRGBA.Sum.g.cs | 4 +- .../IColorFormat.Sum.g.cs | 3 +- HPPH/Images/ImageRow.cs | 3 + HPPH/Images/Interfaces/IImageRow.cs | 2 + HPPH/PixelHelper.Quantize.cs | 4 +- HPPH/PixelHelper.Sum.cs | 87 +++++++------------ HPPH/PixelHelper.cs | 5 +- 15 files changed, 103 insertions(+), 71 deletions(-) diff --git a/HPPH.Benchmark/BenchmarkHelper.cs b/HPPH.Benchmark/BenchmarkHelper.cs index 4763793..085f558 100644 --- a/HPPH.Benchmark/BenchmarkHelper.cs +++ b/HPPH.Benchmark/BenchmarkHelper.cs @@ -21,4 +21,18 @@ internal static class BenchmarkHelper return colors; } + + public static List> GetSampleDataImages() + where T : struct, IColor + { + if (!Directory.Exists(SAMPLE_DATA_DIR)) throw new Exception("sample data not found!"); + + List> colors = []; + + IEnumerable files = Directory.EnumerateFiles(SAMPLE_DATA_DIR, "*.png", SearchOption.AllDirectories); + foreach (string file in files) + colors.Add(ImageHelper.LoadImage(file).ConvertTo()); + + return colors; + } } \ No newline at end of file diff --git a/HPPH.Benchmark/SumBenchmarks.cs b/HPPH.Benchmark/SumBenchmarks.cs index bc7d105..6b4d495 100644 --- a/HPPH.Benchmark/SumBenchmarks.cs +++ b/HPPH.Benchmark/SumBenchmarks.cs @@ -13,6 +13,8 @@ public class SumBenchmarks private readonly List _colors3bpp; private readonly List _colors4bpp; + private readonly List> _images3bpp; + private readonly List> _images4bpp; #endregion @@ -22,6 +24,9 @@ public class SumBenchmarks { _colors3bpp = BenchmarkHelper.GetSampleData(); _colors4bpp = BenchmarkHelper.GetSampleData(); + + _images3bpp = BenchmarkHelper.GetSampleDataImages(); + _images4bpp = BenchmarkHelper.GetSampleDataImages(); } #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() { diff --git a/HPPH.Generators/Features/Sum.cs b/HPPH.Generators/Features/Sum.cs index cd12f2d..f733286 100644 --- a/HPPH.Generators/Features/Sum.cs +++ b/HPPH.Generators/Features/Sum.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } @@ -130,7 +132,8 @@ internal class Sum : IGeneratorFeature public partial interface IColorFormat { - internal ISum Sum(ReadOnlySpan data); + internal Generic4LongData Sum(ReadOnlySpan data); + internal ISum ToSum(Generic4LongData data); } """; } diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs index 6f302dc..c4f92e2 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs index f9dd682..c527ebe 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs index 0655069..54fd3c8 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs index 61bcff4..67493c5 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs index 960115d..20e205a 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs index b9ea3ee..e5696b9 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs @@ -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 data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs index d924d82..cdfe18e 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs @@ -4,5 +4,6 @@ namespace HPPH; public partial interface IColorFormat { - internal ISum Sum(ReadOnlySpan data); + internal Generic4LongData Sum(ReadOnlySpan data); + internal ISum ToSum(Generic4LongData data); } \ No newline at end of file diff --git a/HPPH/Images/ImageRow.cs b/HPPH/Images/ImageRow.cs index 2bc42da..d48294f 100644 --- a/HPPH/Images/ImageRow.cs +++ b/HPPH/Images/ImageRow.cs @@ -156,6 +156,9 @@ internal sealed class IColorImageRow : IImageRow #region Methods + /// + public ReadOnlySpan AsByteSpan() => _buffer.AsSpan().Slice(_start, SizeInBytes); + /// public void CopyTo(Span destination) { diff --git a/HPPH/Images/Interfaces/IImageRow.cs b/HPPH/Images/Interfaces/IImageRow.cs index ae15708..1f1cb82 100644 --- a/HPPH/Images/Interfaces/IImageRow.cs +++ b/HPPH/Images/Interfaces/IImageRow.cs @@ -22,6 +22,8 @@ public interface IImageRow : IEnumerable /// The at the specified location. IColor this[int x] { get; } + ReadOnlySpan AsByteSpan(); + void CopyTo(Span destination); /// diff --git a/HPPH/PixelHelper.Quantize.cs b/HPPH/PixelHelper.Quantize.cs index 2908de5..5a23eca 100644 --- a/HPPH/PixelHelper.Quantize.cs +++ b/HPPH/PixelHelper.Quantize.cs @@ -245,8 +245,6 @@ public static partial class PixelHelper ColorCube[] cubes = new ColorCube[1 << splits]; cubes[0] = new ColorCube(0, colors.Length, SortTarget.None); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; - int colorsLength = colors.Length; fixed (T* colorsPtr = colors) { @@ -257,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) { diff --git a/HPPH/PixelHelper.Sum.cs b/HPPH/PixelHelper.Sum.cs index ead249b..2f41d8c 100644 --- a/HPPH/PixelHelper.Sum.cs +++ b/HPPH/PixelHelper.Sum.cs @@ -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,29 +13,20 @@ public static unsafe partial class PixelHelper { ArgumentNullException.ThrowIfNull(image); - int dataLength = image.SizeInBytes; + IColorFormat colorFormat = image.ColorFormat; - if (dataLength <= 1024) - { - Span 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 result = Vector256.Zero; + for (int y = 0; y < image.Height; y++) { - byte[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return image.ColorFormat.Sum(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan()); + Vector256 rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As(ref rowSum)); + result = Vector256.Add(result, rowSumVector); } + + return colorFormat.ToSum(Unsafe.BitCast, Generic4LongData>(result)); } public static ISum Sum(this IImage image) @@ -50,56 +40,37 @@ public static unsafe partial class PixelHelper public static ISum Sum(this RefImage 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 buffer = MemoryMarshal.Cast(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 result = Vector256.Zero; + for (int y = 0; y < image.Height; y++) { - T[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..(dataLength)]; - try - { - image.CopyTo(buffer); - return Sum(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan()); + Vector256 rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As(ref rowSum)); + result = Vector256.Add(result, rowSumVector); } + + return colorFormat.ToSum(Unsafe.BitCast, Generic4LongData>(result)); } public static ISum Sum(this ReadOnlySpan 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(this Span colors) where T : struct, IColor - => T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors)); - - internal static ISum Sum(ReadOnlySpan 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(Sum(MemoryMarshal.Cast(colors))), - 4 => Unsafe.BitCast(Sum(MemoryMarshal.Cast(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 data) + + internal static Generic4LongData Sum(ReadOnlySpan data) { long b1Sum = 0, b2Sum = 0, b3Sum = 0; @@ -215,7 +186,7 @@ public static unsafe partial class PixelHelper return new Generic4LongData(b1Sum, b2Sum, b3Sum, data.Length * 255); } - private static Generic4LongData Sum(ReadOnlySpan data) + internal static Generic4LongData Sum(ReadOnlySpan data) { long b1Sum, b2Sum, b3Sum, b4Sum; int i = 0; diff --git a/HPPH/PixelHelper.cs b/HPPH/PixelHelper.cs index a15aaca..2e13ae4 100644 --- a/HPPH/PixelHelper.cs +++ b/HPPH/PixelHelper.cs @@ -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 }; +} From e6b77ecfdb1d4d30f816a9bf66999b7b1786310f Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 19 Aug 2024 22:41:56 +0200 Subject: [PATCH 2/3] Improved performance of Average on Images --- HPPH.Benchmark/AverageBenchmarks.cs | 25 ++++++++ HPPH.Generators/ColorFormatSourceGenerator.cs | 1 - HPPH.Generators/Features/Average.cs | 51 ---------------- HPPH.Generators/Features/Colors.cs | 8 +++ .../ColorFormatABGR.g.cs | 6 ++ .../ColorFormatARGB.g.cs | 6 ++ .../ColorFormatBGR.g.cs | 6 ++ .../ColorFormatBGRA.g.cs | 6 ++ .../ColorFormatRGB.g.cs | 6 ++ .../ColorFormatRGBA.g.cs | 6 ++ .../IColorFormat.Instances.g.cs | 2 + HPPH/PixelHelper.Average.cs | 59 ++++--------------- 12 files changed, 84 insertions(+), 98 deletions(-) delete mode 100644 HPPH.Generators/Features/Average.cs diff --git a/HPPH.Benchmark/AverageBenchmarks.cs b/HPPH.Benchmark/AverageBenchmarks.cs index 9708606..5920a86 100644 --- a/HPPH.Benchmark/AverageBenchmarks.cs +++ b/HPPH.Benchmark/AverageBenchmarks.cs @@ -13,6 +13,8 @@ public class AverageBenchmarks private readonly List _colors3bpp; private readonly List _colors4bpp; + private readonly List> _images3bpp; + private readonly List> _images4bpp; #endregion @@ -22,6 +24,9 @@ public class AverageBenchmarks { _colors3bpp = BenchmarkHelper.GetSampleData(); _colors4bpp = BenchmarkHelper.GetSampleData(); + + _images3bpp = BenchmarkHelper.GetSampleDataImages(); + _images4bpp = BenchmarkHelper.GetSampleDataImages(); } #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() { diff --git a/HPPH.Generators/ColorFormatSourceGenerator.cs b/HPPH.Generators/ColorFormatSourceGenerator.cs index 4f631f1..f445c79 100644 --- a/HPPH.Generators/ColorFormatSourceGenerator.cs +++ b/HPPH.Generators/ColorFormatSourceGenerator.cs @@ -19,7 +19,6 @@ public class ColorSourceGenerator : IIncrementalGenerator private static readonly IGeneratorFeature[] FEATURES = [ new Colors(), - new Average(), new MinMax(), new Sum(), new Quantize() diff --git a/HPPH.Generators/Features/Average.cs b/HPPH.Generators/Features/Average.cs deleted file mode 100644 index 7cbd884..0000000 --- a/HPPH.Generators/Features/Average.cs +++ /dev/null @@ -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 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 data) => PixelHelper.Average(MemoryMarshal.Cast(data)); - - #endregion - } - """; - } - - private static string GenerateColorFormatInterfaceAverage() - { - return """ - #nullable enable - - namespace HPPH; - - public partial interface IColorFormat - { - internal IColor Average(ReadOnlySpan data); - } - """; - } -} \ No newline at end of file diff --git a/HPPH.Generators/Features/Colors.cs b/HPPH.Generators/Features/Colors.cs index 9e44537..32b3e54 100644 --- a/HPPH.Generators/Features/Colors.cs +++ b/HPPH.Generators/Features/Colors.cs @@ -218,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 } """; } @@ -243,6 +249,8 @@ internal class Colors : IGeneratorFeature sb.AppendLine(); sb.AppendLine(""" + IColor CreateColor(byte r, byte g, byte b, byte a); + #endregion } """); diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs index 3644f8c..247851c 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs index 19d9357..0448cb6 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs index e895511..92053f2 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs index a3c290e..6cd472b 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs index e6c1852..b694e70 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs index a3a44b0..b8c8e60 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs @@ -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 } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.g.cs index 4533389..1fd4a3b 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.g.cs @@ -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 } diff --git a/HPPH/PixelHelper.Average.cs b/HPPH/PixelHelper.Average.cs index 5ac2c62..85b70aa 100644 --- a/HPPH/PixelHelper.Average.cs +++ b/HPPH/PixelHelper.Average.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -12,29 +11,14 @@ public static partial class PixelHelper { ArgumentNullException.ThrowIfNull(image); - int dataLength = image.SizeInBytes; + float count = image.Width * image.Height; - if (dataLength <= 1024) - { - Span buffer = stackalloc byte[dataLength]; + ISum sum = Sum(image); - image.CopyTo(buffer); - return image.ColorFormat.Average(buffer); - } - else - { - byte[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return image.ColorFormat.Average(buffer); - } - finally - { - ArrayPool.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(this IImage image) @@ -48,30 +32,13 @@ public static partial class PixelHelper public static T Average(this RefImage 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 buffer = MemoryMarshal.Cast(stackalloc byte[sizeInBytes]); - - image.CopyTo(buffer); - return Average(buffer); - } - else - { - T[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return Average(buffer); - } - finally - { - ArrayPool.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(this Span colors) From f091aadd9231e288337465e499eab4cd7098607d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 20 Aug 2024 21:21:38 +0200 Subject: [PATCH 3/3] Improved performance of MinMax on Images --- HPPH.Benchmark/MinMaxBenchmarks.cs | 25 ++++ HPPH.Generators/Features/MinMax.cs | 7 +- .../ColorFormatABGR.MinMax.g.cs | 4 +- .../ColorFormatARGB.MinMax.g.cs | 4 +- .../ColorFormatBGR.MinMax.g.cs | 4 +- .../ColorFormatBGRA.MinMax.g.cs | 4 +- .../ColorFormatRGB.MinMax.g.cs | 4 +- .../ColorFormatRGBA.MinMax.g.cs | 4 +- .../IColorFormat.MinMax.g.cs | 3 +- HPPH/PixelHelper.MinMax.cs | 140 +++++++++++------- 10 files changed, 134 insertions(+), 65 deletions(-) diff --git a/HPPH.Benchmark/MinMaxBenchmarks.cs b/HPPH.Benchmark/MinMaxBenchmarks.cs index 1774677..41f3d49 100644 --- a/HPPH.Benchmark/MinMaxBenchmarks.cs +++ b/HPPH.Benchmark/MinMaxBenchmarks.cs @@ -13,6 +13,8 @@ public class MinMaxBenchmarks private readonly List _colors3bpp; private readonly List _colors4bpp; + private readonly List> _images3bpp; + private readonly List> _images4bpp; #endregion @@ -22,6 +24,9 @@ public class MinMaxBenchmarks { _colors3bpp = BenchmarkHelper.GetSampleData(); _colors4bpp = BenchmarkHelper.GetSampleData(); + + _images3bpp = BenchmarkHelper.GetSampleDataImages(); + _images4bpp = BenchmarkHelper.GetSampleDataImages(); } #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() { diff --git a/HPPH.Generators/Features/MinMax.cs b/HPPH.Generators/Features/MinMax.cs index cc1639c..cffaf42 100644 --- a/HPPH.Generators/Features/MinMax.cs +++ b/HPPH.Generators/Features/MinMax.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => {{(colorFormat.Bpp == 3 ? $"Unsafe.BitCast(data);" : "throw new NotSupportedException();")}} + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => {{(colorFormat.Bpp == 4 ? $"Unsafe.BitCast(data);" : "throw new NotSupportedException();")}} #endregion } @@ -163,7 +165,8 @@ internal class MinMax : IGeneratorFeature public partial interface IColorFormat { - internal IMinMax MinMax(ReadOnlySpan data); + internal IMinMax ToMinMax(Generic3ByteMinMax data); + internal IMinMax ToMinMax(Generic4ByteMinMax data); } """; } diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs index 7d073ea..666c565 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs index ca9d201..361786f 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs index a2857c1..f796204 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast(data); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException(); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs index 89a0845..9156e11 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs index ab55ee1..9df69f0 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast(data); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException(); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs index 7e07522..2134c56 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs @@ -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 data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs index 35116a0..8b9412e 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs @@ -4,5 +4,6 @@ namespace HPPH; public partial interface IColorFormat { - internal IMinMax MinMax(ReadOnlySpan data); + internal IMinMax ToMinMax(Generic3ByteMinMax data); + internal IMinMax ToMinMax(Generic4ByteMinMax data); } \ No newline at end of file diff --git a/HPPH/PixelHelper.MinMax.cs b/HPPH/PixelHelper.MinMax.cs index 86cd3ec..d644584 100644 --- a/HPPH/PixelHelper.MinMax.cs +++ b/HPPH/PixelHelper.MinMax.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -13,29 +12,40 @@ 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 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(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(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.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return image.ColorFormat.MinMax(buffer); - } - finally - { - ArrayPool.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(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(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(this IImage image) @@ -49,58 +59,75 @@ public static unsafe partial class PixelHelper public static IMinMax MinMax(this RefImage 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 buffer = MemoryMarshal.Cast(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(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(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.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..(dataLength)]; - try - { - image.CopyTo(buffer); - return MinMax(buffer); - } - finally - { - ArrayPool.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(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(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(this Span colors) where T : struct, IColor - => T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors)); - - public static IMinMax MinMax(this ReadOnlySpan colors) - where T : struct, IColor - => T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors)); - - internal static IMinMax MinMax(ReadOnlySpan 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(MinMax(MemoryMarshal.Cast(colors))), - 4 => Unsafe.BitCast(MinMax(MemoryMarshal.Cast(colors))), + 3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), + 4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), _ => throw new NotSupportedException("Data is not of a supported valid color-type.") }; } - private static Generic3ByteMinMax MinMax(ReadOnlySpan data) + public static IMinMax MinMax(this ReadOnlySpan 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(colors))), + 4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), + _ => throw new NotSupportedException("Data is not of a supported valid color-type.") + }; + } + + private static Generic3ByteMinMax MinMax(ReadOnlySpan data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Generic3ByteMinMax MinMax(ReadOnlySpan data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3) + { const int BYTES_PER_COLOR = 3; int elementsPerVector = Vector.Count / BYTES_PER_COLOR; @@ -166,11 +193,12 @@ public static unsafe partial class PixelHelper return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3); } - private static Generic4ByteMinMax MinMax(ReadOnlySpan 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 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 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.Count / BYTES_PER_COLOR;