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/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 class ColorQuantizer
/// 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
-}
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 @@ public sealed class SKTexture : PixelTexture, IDisposable
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 sealed class SKTexture : PixelTexture, IDisposable
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 @@ public sealed class SKTexture : PixelTexture, IDisposable
///
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 @@ internal class SKTextureBrush : AbstractBrush
#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 b85c70808..63ecc90f5 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -14,6 +14,7 @@
+
@@ -44,9 +45,9 @@
-
-
-
+
+
+