From db84f1dc7565dc8811ff0784c642203279a7bac0 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 22 Jul 2024 23:26:53 +0200 Subject: [PATCH 1/2] Major upgrade of RGB.NET to v3 --- src/Artemis.Core/Artemis.Core.csproj | 3 +- src/Artemis.Core/RGB.NET/SKTexture.cs | 80 ++++++++-------------- src/Artemis.Core/RGB.NET/SKTextureBrush.cs | 2 +- src/Directory.Packages.props | 7 +- 4 files changed, 34 insertions(+), 58 deletions(-) diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 42daefc59..71aeb0167 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -39,8 +39,9 @@ + - + diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs index 9664318ae..14aae47e2 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Artemis.Core.SkiaSharp; +using HPPH; +using HPPH.SkiaSharp; using RGB.NET.Core; -using RGB.NET.Presets.Textures.Sampler; +using RGB.NET.Presets.Extensions; using SkiaSharp; namespace Artemis.Core; @@ -12,35 +12,29 @@ namespace Artemis.Core; /// /// Represents a SkiaSharp-based RGB.NET PixelTexture /// -public sealed class SKTexture : PixelTexture, IDisposable +public sealed class SKTexture : ITexture, IDisposable { - private readonly Dictionary _ledRects; - private readonly SKPixmap _pixelData; - private readonly IntPtr _pixelDataPtr; - #region Constructors - internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection devices) : base(width, height, DATA_PER_PIXEL, - new AverageByteSampler()) + internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection devices) { + RenderScale = scale; + Size = new Size(width, height); + ImageInfo = new SKImageInfo(width, height); Surface = graphicsContext == null ? SKSurface.Create(ImageInfo) : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); - RenderScale = scale; - _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); - _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); - _ledRects = new Dictionary(); foreach (ArtemisDevice artemisDevice in devices) { foreach (ArtemisLed artemisLed in artemisDevice.Leds) { _ledRects[artemisLed.RgbLed] = SKRectI.Create( - (int) (artemisLed.AbsoluteRectangle.Left * RenderScale), - (int) (artemisLed.AbsoluteRectangle.Top * RenderScale), - (int) (artemisLed.AbsoluteRectangle.Width * RenderScale), - (int) (artemisLed.AbsoluteRectangle.Height * RenderScale) + (int)(artemisLed.AbsoluteRectangle.Left * RenderScale), + (int)(artemisLed.AbsoluteRectangle.Top * RenderScale), + (int)(artemisLed.AbsoluteRectangle.Width * RenderScale), + (int)(artemisLed.AbsoluteRectangle.Height * RenderScale) ); } } @@ -50,47 +44,29 @@ internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int heig internal Color GetColorAtRenderTarget(in RenderTarget renderTarget) { - if (Data.Length == 0) return Color.Transparent; + if (_image == null) return Color.Transparent; + SKRectI skRectI = _ledRects[renderTarget.Led]; if (skRectI.Width <= 0 || skRectI.Height <= 0) return Color.Transparent; - SamplerInfo samplerInfo = new(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, Stride, DataPerPixel, Data); - - Span pixelData = stackalloc byte[DATA_PER_PIXEL]; - Sampler.Sample(samplerInfo, pixelData); - - return GetColor(pixelData); - } - - private void ReleaseUnmanagedResources() - { - Marshal.FreeHGlobal(_pixelDataPtr); + return _image[skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height].Average().ToColor(); } /// ~SKTexture() { - ReleaseUnmanagedResources(); + Dispose(); } /// public void Dispose() { Surface.Dispose(); - _pixelData.Dispose(); - - ReleaseUnmanagedResources(); GC.SuppressFinalize(this); } - #region Constants - - private const int DATA_PER_PIXEL = 4; - - #endregion - #region Methods /// @@ -104,20 +80,23 @@ public void Invalidate() internal void CopyPixelData() { using SKImage skImage = Surface.Snapshot(); - skImage.ReadPixels(_pixelData); + _image = skImage.ToImage(); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override Color GetColor(in ReadOnlySpan pixel) => new(pixel[2], pixel[1], pixel[0]); - - /// - public override Color this[in Rectangle rectangle] => Color.Transparent; - #endregion #region Properties & Fields + private IImage? _image; + private readonly Dictionary _ledRects = []; + + /// + public Size Size { get; } + /// + public Color this[Point point] => Color.Transparent; + /// + public Color this[Rectangle rectangle] => Color.Transparent; + /// /// Gets the SKBitmap backing this texture /// @@ -128,11 +107,6 @@ internal void CopyPixelData() /// public SKImageInfo ImageInfo { get; } - /// - /// Gets the color data in RGB format - /// - protected override ReadOnlySpan Data => _pixelData.GetPixelSpan(); - /// /// Gets the render scale of the texture /// diff --git a/src/Artemis.Core/RGB.NET/SKTextureBrush.cs b/src/Artemis.Core/RGB.NET/SKTextureBrush.cs index 68f2308f1..d601dd905 100644 --- a/src/Artemis.Core/RGB.NET/SKTextureBrush.cs +++ b/src/Artemis.Core/RGB.NET/SKTextureBrush.cs @@ -20,7 +20,7 @@ public SKTextureBrush(SKTexture? texture) #region Methods /// - protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget) + protected override Color GetColorAtPoint(Rectangle rectangle, RenderTarget renderTarget) { return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent; } diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 9c07752b6..5d70e8ae5 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -14,6 +14,7 @@ + @@ -44,9 +45,9 @@ - - - + + + From 5d82c159e15d0269d0c1f2495eb4adf5e515458d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 22 Jul 2024 23:52:50 +0200 Subject: [PATCH 2/2] Replaced the quantization code and with the HPPH implementation --- .../ColorScience/Quantization/ColorCube.cs | 167 ------------------ .../Quantization/ColorQuantizer.cs | 31 +--- .../Quantization/QuantizerSort.cs | 121 ------------- .../ColorScience/Quantization/SortTarget.cs | 6 - 4 files changed, 7 insertions(+), 318 deletions(-) delete mode 100644 src/Artemis.Core/ColorScience/Quantization/ColorCube.cs delete mode 100644 src/Artemis.Core/ColorScience/Quantization/QuantizerSort.cs delete mode 100644 src/Artemis.Core/ColorScience/Quantization/SortTarget.cs diff --git a/src/Artemis.Core/ColorScience/Quantization/ColorCube.cs b/src/Artemis.Core/ColorScience/Quantization/ColorCube.cs deleted file mode 100644 index 5b7b353a0..000000000 --- a/src/Artemis.Core/ColorScience/Quantization/ColorCube.cs +++ /dev/null @@ -1,167 +0,0 @@ -using SkiaSharp; -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Artemis.Core.ColorScience; - -internal readonly struct ColorRanges -{ - public readonly byte RedRange; - public readonly byte GreenRange; - public readonly byte BlueRange; - - public ColorRanges(byte redRange, byte greenRange, byte blueRange) - { - this.RedRange = redRange; - this.GreenRange = greenRange; - this.BlueRange = blueRange; - } -} - -internal readonly struct ColorCube -{ - private const int BYTES_PER_COLOR = 4; - private static readonly int ELEMENTS_PER_VECTOR = Vector.Count / BYTES_PER_COLOR; - private static readonly int BYTES_PER_VECTOR = ELEMENTS_PER_VECTOR * BYTES_PER_COLOR; - - private readonly int _from; - private readonly int _length; - private readonly SortTarget _currentOrder = SortTarget.None; - - public ColorCube(in Span fullColorList, int from, int length, SortTarget preOrdered) - { - this._from = from; - this._length = length; - - if (length < 2) return; - - Span colors = fullColorList.Slice(from, length); - ColorRanges colorRanges = GetColorRanges(colors); - - if ((colorRanges.RedRange > colorRanges.GreenRange) && (colorRanges.RedRange > colorRanges.BlueRange)) - { - if (preOrdered != SortTarget.Red) - QuantizerSort.SortRed(colors); - - _currentOrder = SortTarget.Red; - } - else if (colorRanges.GreenRange > colorRanges.BlueRange) - { - if (preOrdered != SortTarget.Green) - QuantizerSort.SortGreen(colors); - - _currentOrder = SortTarget.Green; - } - else - { - if (preOrdered != SortTarget.Blue) - QuantizerSort.SortBlue(colors); - - _currentOrder = SortTarget.Blue; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ColorRanges GetColorRanges(in ReadOnlySpan colors) - { - if (Vector.IsHardwareAccelerated && (colors.Length >= Vector.Count)) - { - int chunks = colors.Length / ELEMENTS_PER_VECTOR; - int vectorElements = (chunks * ELEMENTS_PER_VECTOR); - int missingElements = colors.Length - vectorElements; - - Vector max = Vector.Zero; - Vector min = new(byte.MaxValue); - foreach (Vector currentVector in MemoryMarshal.Cast>(colors[..vectorElements])) - { - max = Vector.Max(max, currentVector); - min = Vector.Min(min, currentVector); - } - - byte redMin = byte.MaxValue; - byte redMax = byte.MinValue; - byte greenMin = byte.MaxValue; - byte greenMax = byte.MinValue; - byte blueMin = byte.MaxValue; - byte blueMax = byte.MinValue; - - for (int i = 0; i < BYTES_PER_VECTOR; i += BYTES_PER_COLOR) - { - if (min[i + 2] < redMin) redMin = min[i + 2]; - if (max[i + 2] > redMax) redMax = max[i + 2]; - if (min[i + 1] < greenMin) greenMin = min[i + 1]; - if (max[i + 1] > greenMax) greenMax = max[i + 1]; - if (min[i] < blueMin) blueMin = min[i]; - if (max[i] > blueMax) blueMax = max[i]; - } - - for (int i = 0; i < missingElements; i++) - { - SKColor color = colors[^(i + 1)]; - - if (color.Red < redMin) redMin = color.Red; - if (color.Red > redMax) redMax = color.Red; - if (color.Green < greenMin) greenMin = color.Green; - if (color.Green > greenMax) greenMax = color.Green; - if (color.Blue < blueMin) blueMin = color.Blue; - if (color.Blue > blueMax) blueMax = color.Blue; - } - - return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin)); - } - else - { - byte redMin = byte.MaxValue; - byte redMax = byte.MinValue; - byte greenMin = byte.MaxValue; - byte greenMax = byte.MinValue; - byte blueMin = byte.MaxValue; - byte blueMax = byte.MinValue; - - foreach (SKColor color in colors) - { - if (color.Red < redMin) redMin = color.Red; - if (color.Red > redMax) redMax = color.Red; - if (color.Green < greenMin) greenMin = color.Green; - if (color.Green > greenMax) greenMax = color.Green; - if (color.Blue < blueMin) blueMin = color.Blue; - if (color.Blue > blueMax) blueMax = color.Blue; - } - - return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin)); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Split(in Span fullColorList, out ColorCube a, out ColorCube b) - { - Span colors = fullColorList.Slice(_from, _length); - - int median = colors.Length / 2; - - a = new ColorCube(fullColorList, _from, median, _currentOrder); - b = new ColorCube(fullColorList, _from + median, colors.Length - median, _currentOrder); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal SKColor GetAverageColor(in ReadOnlySpan fullColorList) - { - ReadOnlySpan colors = fullColorList.Slice(_from, _length); - - int r = 0, g = 0, b = 0; - foreach (SKColor color in colors) - { - r += color.Red; - g += color.Green; - b += color.Blue; - } - - return new SKColor( - (byte)(r / colors.Length), - (byte)(g / colors.Length), - (byte)(b / colors.Length) - ); - } -} diff --git a/src/Artemis.Core/ColorScience/Quantization/ColorQuantizer.cs b/src/Artemis.Core/ColorScience/Quantization/ColorQuantizer.cs index 1afaa4f3c..1b6f6332d 100644 --- a/src/Artemis.Core/ColorScience/Quantization/ColorQuantizer.cs +++ b/src/Artemis.Core/ColorScience/Quantization/ColorQuantizer.cs @@ -1,7 +1,9 @@ -using SkiaSharp; +using HPPH; +using SkiaSharp; using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.InteropServices; namespace Artemis.Core.ColorScience; @@ -16,7 +18,7 @@ public static class ColorQuantizer /// The colors to quantize /// How many colors to return. Must be a power of two. /// colors. - public static SKColor[] Quantize(in Span colors, int amount) + public static SKColor[] Quantize(Span colors, int amount) { if (!BitOperations.IsPow2(amount)) throw new ArgumentException("Must be power of two", nameof(amount)); @@ -31,31 +33,12 @@ public static SKColor[] Quantize(in Span colors, int amount) /// The colors to quantize /// How many splits to execute. Each split doubles the number of colors returned. /// Up to (2 ^ ) number of colors. - public static SKColor[] QuantizeSplit(in Span colors, int splits) + public static SKColor[] QuantizeSplit(Span colors, int splits) { if (colors.Length < (1 << splits)) throw new ArgumentException($"The color array must at least contain ({(1 << splits)}) to perform {splits} splits."); - Span cubes = new ColorCube[1 << splits]; - cubes[0] = new ColorCube(colors, 0, colors.Length, SortTarget.None); - - int currentIndex = 0; - for (int i = 0; i < splits; i++) - { - int currentCubeCount = 1 << i; - Span currentCubes = cubes.Slice(0, currentCubeCount); - for (int j = 0; j < currentCubes.Length; j++) - { - currentCubes[j].Split(colors, out ColorCube a, out ColorCube b); - currentCubes[j] = a; - cubes[++currentIndex] = b; - } - } - - SKColor[] result = new SKColor[cubes.Length]; - for (int i = 0; i < cubes.Length; i++) - result[i] = cubes[i].GetAverageColor(colors); - - return result; + // DarthAffe 22.07.2024: This is not ideal as it allocates an additional array, but i don't see a way to get SKColors out here + return MemoryMarshal.Cast(MemoryMarshal.Cast(colors).CreateSimpleColorPalette(1 << splits)).ToArray(); } /// diff --git a/src/Artemis.Core/ColorScience/Quantization/QuantizerSort.cs b/src/Artemis.Core/ColorScience/Quantization/QuantizerSort.cs deleted file mode 100644 index 46b593ece..000000000 --- a/src/Artemis.Core/ColorScience/Quantization/QuantizerSort.cs +++ /dev/null @@ -1,121 +0,0 @@ -using SkiaSharp; -using System; -using System.Buffers; - -namespace Artemis.Core.ColorScience; - -//HACK DarthAffe 17.11.2022: Sorting is a really hot path in the quantizer, therefore abstracting this into cleaner code (one method with parameter or something like that) sadly has a well measurable performance impact. -internal static class QuantizerSort -{ - #region Methods - - public static void SortRed(in Span colors) - { - Span counts = stackalloc int[256]; - foreach (SKColor t in colors) - counts[t.Red]++; - - SKColor[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); - - try - { - Span buckets = bucketsArray.AsSpan().Slice(0, colors.Length); - Span currentBucketIndex = stackalloc int[256]; - - int offset = 0; - for (int i = 0; i < counts.Length; i++) - { - currentBucketIndex[i] = offset; - offset += counts[i]; - } - - foreach (SKColor color in colors) - { - int index = color.Red; - int bucketIndex = currentBucketIndex[index]; - currentBucketIndex[index]++; - buckets[bucketIndex] = color; - } - - buckets.CopyTo(colors); - } - finally - { - ArrayPool.Shared.Return(bucketsArray); - } - } - - public static void SortGreen(in Span colors) - { - Span counts = stackalloc int[256]; - foreach (SKColor t in colors) - counts[t.Green]++; - - SKColor[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); - - try - { - Span buckets = bucketsArray.AsSpan().Slice(0, colors.Length); - Span currentBucketIndex = stackalloc int[256]; - - int offset = 0; - for (int i = 0; i < counts.Length; i++) - { - currentBucketIndex[i] = offset; - offset += counts[i]; - } - - foreach (SKColor color in colors) - { - int index = color.Green; - int bucketIndex = currentBucketIndex[index]; - currentBucketIndex[index]++; - buckets[bucketIndex] = color; - } - - buckets.CopyTo(colors); - } - finally - { - ArrayPool.Shared.Return(bucketsArray); - } - } - - public static void SortBlue(in Span colors) - { - Span counts = stackalloc int[256]; - foreach (SKColor t in colors) - counts[t.Blue]++; - - SKColor[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); - - try - { - Span buckets = bucketsArray.AsSpan().Slice(0, colors.Length); - Span currentBucketIndex = stackalloc int[256]; - - int offset = 0; - for (int i = 0; i < counts.Length; i++) - { - currentBucketIndex[i] = offset; - offset += counts[i]; - } - - foreach (SKColor color in colors) - { - int index = color.Blue; - int bucketIndex = currentBucketIndex[index]; - currentBucketIndex[index]++; - buckets[bucketIndex] = color; - } - - buckets.CopyTo(colors); - } - finally - { - ArrayPool.Shared.Return(bucketsArray); - } - } - - #endregion -} diff --git a/src/Artemis.Core/ColorScience/Quantization/SortTarget.cs b/src/Artemis.Core/ColorScience/Quantization/SortTarget.cs deleted file mode 100644 index 72f58517d..000000000 --- a/src/Artemis.Core/ColorScience/Quantization/SortTarget.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.Core.ColorScience; - -internal enum SortTarget -{ - None, Red, Green, Blue -}