1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Added Quantize node

This commit is contained in:
Darth Affe 2022-08-20 18:03:37 +02:00
parent c7202bb94c
commit afd17b2661
10 changed files with 168 additions and 684 deletions

View File

@ -16,7 +16,6 @@
<PackageReference Include="NoStringEvaluating" Version="2.4.0" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="ScreenCapture.NET" Version="1.2.0" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" />
</ItemGroup>
@ -26,6 +25,9 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Nodes\Color\Screens\QuantizeNodeCustomView.axaml.cs">
<DependentUpon>QuantizeNodeCustomView.axaml</DependentUpon>
</Compile>
<Compile Update="Nodes\Easing\Screens\EasingTypeNodeEasingView.axaml.cs">
<DependentUpon>EasingTypeNodeEasingView.axaml</DependentUpon>
</Compile>

View File

@ -0,0 +1,68 @@
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.VisualScripting.Nodes.Color.Screens;
using SkiaSharp;
namespace Artemis.VisualScripting.Nodes.Color;
public class QuantizeNodeStorage
{
public int PaletteSize { get; set; } = 32;
public bool IgnoreLimits { get; set; }
};
[Node("Quantize", "Quantizes the image into key-colors", "Image", InputType = typeof(SKBitmap), OutputType = typeof(SKColor))]
public class QuantizeNode : Node<QuantizeNodeStorage, QuantizeNodeCustomViewModel>
{
#region Properties & Fields
public InputPin<SKBitmap> Image { get; set; }
public OutputPin<SKColor> Vibrant { get; set; }
public OutputPin<SKColor> Muted { get; set; }
public OutputPin<SKColor> DarkVibrant { get; set; }
public OutputPin<SKColor> DarkMuted { get; set; }
public OutputPin<SKColor> LightVibrant { get; set; }
public OutputPin<SKColor> LightMuted { get; set; }
#endregion
#region Constructors
public QuantizeNode()
: base("Quantize", "Quantizes the image into key-colors")
{
Image = CreateInputPin<SKBitmap>("Image");
Vibrant = CreateOutputPin<SKColor>("Vibrant");
Muted = CreateOutputPin<SKColor>("Muted");
DarkVibrant = CreateOutputPin<SKColor>("DarkVibrant");
DarkMuted = CreateOutputPin<SKColor>("DarkMuted");
LightVibrant = CreateOutputPin<SKColor>("LightVibrant");
LightMuted = CreateOutputPin<SKColor>("LightMuted");
Storage = new QuantizeNodeStorage();
}
#endregion
#region Methods
public override void Evaluate()
{
SKBitmap? image = Image.Value;
if (image == null) return;
SKColor[] colorPalette = ColorQuantizer.Quantize(image.Pixels, Storage?.PaletteSize ?? 32);
ColorSwatch swatch = ColorQuantizer.FindAllColorVariations(colorPalette, Storage?.IgnoreLimits ?? false);
Vibrant.Value = swatch.Vibrant;
Muted.Value = swatch.Muted;
DarkVibrant.Value = swatch.DarkVibrant;
DarkMuted.Value = swatch.DarkMuted;
LightVibrant.Value = swatch.LightVibrant;
LightMuted.Value = swatch.LightMuted;
}
#endregion
}

View File

@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.Color.Screens"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.Color.Screens.QuantizeNodeCustomView"
x:DataType="screens:QuantizeNodeCustomViewModel">
<StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="16,4,0,4">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Palette Size" VerticalAlignment="Center" Margin="0,0,0,4" Width="70" FontSize="11" />
<ComboBox Classes="condensed" Width="75" Items="{CompiledBinding PaletteSizes}" SelectedItem="{CompiledBinding PaletteSize}"
ToolTip.Tip="The number of colors the image is compressed to before selecting the final colors." />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
<TextBlock Text="Ignore Limits" VerticalAlignment="Center" Margin="0,0,0,4" Width="70" FontSize="11" />
<CheckBox Classes="condensed" IsChecked="{CompiledBinding IgnoreLimits}"
ToolTip.Tip="Ignore hard limits on whether a color is considered for each category.&#10;&#13;If disabled some of the results may be empty." />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.Color.Screens;
public partial class QuantizeNodeCustomView : ReactiveUserControl<QuantizeNodeCustomViewModel>
{
public QuantizeNodeCustomView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,58 @@
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.VisualScripting;
using ReactiveUI;
namespace Artemis.VisualScripting.Nodes.Color.Screens;
public class QuantizeNodeCustomViewModel : CustomNodeViewModel
{
#region Properties & Fields
private readonly QuantizeNode _node;
private readonly INodeEditorService _nodeEditorService;
public ObservableCollection<int> PaletteSizes { get; } = new() { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };
public int PaletteSize
{
get => _node.Storage?.PaletteSize ?? 32;
set
{
if ((_node.Storage != null) && (_node.Storage.PaletteSize != value))
{
_node.Storage.PaletteSize = value;
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<QuantizeNodeStorage>(_node, _node.Storage));
}
}
}
public bool IgnoreLimits
{
get => _node.Storage?.IgnoreLimits ?? false;
set
{
if ((_node.Storage != null) && (_node.Storage.IgnoreLimits != value))
{
_node.Storage.IgnoreLimits = value;
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<QuantizeNodeStorage>(_node, _node.Storage));
}
}
}
#endregion
#region Constructors
public QuantizeNodeCustomViewModel(QuantizeNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
{
this._node = node;
this._nodeEditorService = nodeEditorService;
NodeModified += (_, _) => this.RaisePropertyChanged(nameof(PaletteSize));
}
#endregion
}

View File

@ -1,66 +0,0 @@
using Artemis.Core;
using Artemis.VisualScripting.Nodes.Image.Screens;
using ScreenCapture.NET;
using SkiaSharp;
namespace Artemis.VisualScripting.Nodes.Image;
[Node("Capture Screen", "Captures a region of the screen", "Image", OutputType = typeof(SKBitmap))]
public class CaptureScreenNode : Node<object, CaptureScreenNodeCustomViewModel>
{
#region Properties & Fields
private static readonly Thread _thread;
private static readonly IScreenCaptureService _screenCaptureService = new DX11ScreenCaptureService();
private static readonly IScreenCapture _screenCapture;
static CaptureScreenNode()
{
IEnumerable<GraphicsCard> graphicsCards = _screenCaptureService.GetGraphicsCards();
IEnumerable<Display> displays = _screenCaptureService.GetDisplays(graphicsCards.First());
_screenCapture = _screenCaptureService.GetScreenCapture(displays.First());
_thread = new Thread(() =>
{
while (true)
_screenCapture.CaptureScreen();
});
_thread.Start();
}
private CaptureZone _captureZone;
public OutputPin<SKBitmap> Output { get; set; }
#endregion
#region Constructors
public CaptureScreenNode()
: base("Capture Screen", "Captures a region of the screen")
{
Output = CreateOutputPin<SKBitmap>("Image");
_captureZone = _screenCapture.RegisterCaptureZone(20, 20, 256, 256, 1);
}
#endregion
#region Methods
public override unsafe void Evaluate()
{
lock (_captureZone.Buffer)
{
ReadOnlySpan<byte> capture = _captureZone.Buffer;
if (capture.IsEmpty) return;
fixed (byte* ptr = capture)
Output.Value = SKBitmap.FromImage(SKImage.FromPixels(new SKImageInfo(_captureZone.Width, _captureZone.Height, SKColorType.Bgra8888, SKAlphaType.Opaque), new IntPtr(ptr), _captureZone.Stride));
//TODO DarthAffe 18.08.2022: Dispose Output or better reuse it
}
}
#endregion
}

View File

@ -1,559 +0,0 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Artemis.Core;
using Artemis.Core.Services;
using SkiaSharp;
namespace Artemis.VisualScripting.Nodes.Image;
[Node("Quantize", "Quantizes the image into key-colors", "Image", InputType = typeof(SKBitmap), OutputType = typeof(SKColor))]
public class QuantizeNode : Node
{
#region Properties & Fields
public InputPin<SKBitmap> Image { get; set; }
public OutputPin<SKColor> Vibrant { get; set; }
public OutputPin<SKColor> Muted { get; set; }
public OutputPin<SKColor> DarkVibrant { get; set; }
public OutputPin<SKColor> DarkMuted { get; set; }
public OutputPin<SKColor> LightVibrant { get; set; }
public OutputPin<SKColor> LightMuted { get; set; }
#endregion
#region Constructors
public QuantizeNode()
: base("Quantize", "Quantizes the image into key-colors")
{
Image = CreateInputPin<SKBitmap>("Image");
Vibrant = CreateOutputPin<SKColor>("Vibrant");
Muted = CreateOutputPin<SKColor>("Muted");
DarkVibrant = CreateOutputPin<SKColor>("DarkVibrant");
DarkMuted = CreateOutputPin<SKColor>("DarkMuted");
LightVibrant = CreateOutputPin<SKColor>("LightVibrant");
LightMuted = CreateOutputPin<SKColor>("LightMuted");
}
#endregion
#region Methods
public override void Evaluate()
{
if (Image.Value == null) return;
SKColor[] colorPalette = ColorQuantizer.Quantize(Image.Value.Pixels, 32); //TODO DarthAffe 18.08.2022: Palette-Size as input
ColorSwatch swatch = ColorQuantizer.FindAllColorVariations(colorPalette, true);
Vibrant.Value = swatch.Vibrant;
Muted.Value = swatch.Muted;
DarkVibrant.Value = swatch.DarkVibrant;
DarkMuted.Value = swatch.DarkMuted;
LightVibrant.Value = swatch.LightVibrant;
LightMuted.Value = swatch.LightMuted;
}
#endregion
}
#region Quantizer //TODO DarthAffe 18.08.2022: external project?
public static class ColorQuantizer
{
public static SKColor[] Quantize(in Span<SKColor> colors, int amount)
{
if ((amount & (amount - 1)) != 0)
throw new ArgumentException("Must be power of two", nameof(amount));
Queue<ColorCube> cubes = new(amount);
cubes.Enqueue(new ColorCube(colors, 0, colors.Length, SortTarget.None));
while (cubes.Count < amount)
{
ColorCube cube = cubes.Dequeue();
if (cube.TrySplit(colors, out ColorCube? a, out ColorCube? b))
{
cubes.Enqueue(a);
cubes.Enqueue(b);
}
}
SKColor[] result = new SKColor[cubes.Count];
int i = 0;
foreach (ColorCube colorCube in cubes)
result[i++] = colorCube.GetAverageColor(colors);
return result;
}
public static ColorSwatch FindAllColorVariations(IEnumerable<SKColor> colors, bool ignoreLimits = false)
{
SKColor bestVibrantColor = SKColor.Empty;
SKColor bestLightVibrantColor = SKColor.Empty;
SKColor bestDarkVibrantColor = SKColor.Empty;
SKColor bestMutedColor = SKColor.Empty;
SKColor bestLightMutedColor = SKColor.Empty;
SKColor bestDarkMutedColor = SKColor.Empty;
float bestVibrantScore = float.MinValue;
float bestLightVibrantScore = float.MinValue;
float bestDarkVibrantScore = float.MinValue;
float bestMutedScore = float.MinValue;
float bestLightMutedScore = float.MinValue;
float bestDarkMutedScore = float.MinValue;
//ugly but at least we only loop through the enumerable once ¯\_(ツ)_/¯
foreach (SKColor color in colors)
{
static void SetIfBetterScore(ref float bestScore, ref SKColor bestColor, SKColor newColor, ColorType type, bool ignoreLimits)
{
float newScore = GetScore(newColor, type, ignoreLimits);
if (newScore > bestScore)
{
bestScore = newScore;
bestColor = newColor;
}
}
SetIfBetterScore(ref bestVibrantScore, ref bestVibrantColor, color, ColorType.Vibrant, ignoreLimits);
SetIfBetterScore(ref bestLightVibrantScore, ref bestLightVibrantColor, color, ColorType.LightVibrant, ignoreLimits);
SetIfBetterScore(ref bestDarkVibrantScore, ref bestDarkVibrantColor, color, ColorType.DarkVibrant, ignoreLimits);
SetIfBetterScore(ref bestMutedScore, ref bestMutedColor, color, ColorType.Muted, ignoreLimits);
SetIfBetterScore(ref bestLightMutedScore, ref bestLightMutedColor, color, ColorType.LightMuted, ignoreLimits);
SetIfBetterScore(ref bestDarkMutedScore, ref bestDarkMutedColor, color, ColorType.DarkMuted, ignoreLimits);
}
return new ColorSwatch
{
Vibrant = bestVibrantColor,
LightVibrant = bestLightVibrantColor,
DarkVibrant = bestDarkVibrantColor,
Muted = bestMutedColor,
LightMuted = bestLightMutedColor,
DarkMuted = bestDarkMutedColor,
};
}
private static float GetScore(SKColor color, ColorType type, bool ignoreLimits = false)
{
static float InvertDiff(float value, float target)
{
return 1 - Math.Abs(value - target);
}
color.ToHsl(out float _, out float saturation, out float luma);
saturation /= 100f;
luma /= 100f;
if (!ignoreLimits && ((saturation <= GetMinSaturation(type)) || (saturation >= GetMaxSaturation(type)) || (luma <= GetMinLuma(type)) || (luma >= GetMaxLuma(type))))
{
//if either saturation or luma falls outside the min-max, return the
//lowest score possible unless we're ignoring these limits.
return float.MinValue;
}
float totalValue = (InvertDiff(saturation, GetTargetSaturation(type)) * WEIGHT_SATURATION) + (InvertDiff(luma, GetTargetLuma(type)) * WEIGHT_LUMA);
const float TOTAL_WEIGHT = WEIGHT_SATURATION + WEIGHT_LUMA;
return totalValue / TOTAL_WEIGHT;
}
#region Constants
private const float TARGET_DARK_LUMA = 0.26f;
private const float MAX_DARK_LUMA = 0.45f;
private const float MIN_LIGHT_LUMA = 0.55f;
private const float TARGET_LIGHT_LUMA = 0.74f;
private const float MIN_NORMAL_LUMA = 0.3f;
private const float TARGET_NORMAL_LUMA = 0.5f;
private const float MAX_NORMAL_LUMA = 0.7f;
private const float TARGET_MUTES_SATURATION = 0.3f;
private const float MAX_MUTES_SATURATION = 0.3f;
private const float TARGET_VIBRANT_SATURATION = 1.0f;
private const float MIN_VIBRANT_SATURATION = 0.35f;
private const float WEIGHT_SATURATION = 3f;
private const float WEIGHT_LUMA = 5f;
private static float GetTargetLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => TARGET_NORMAL_LUMA,
ColorType.LightVibrant => TARGET_LIGHT_LUMA,
ColorType.DarkVibrant => TARGET_DARK_LUMA,
ColorType.Muted => TARGET_NORMAL_LUMA,
ColorType.LightMuted => TARGET_LIGHT_LUMA,
ColorType.DarkMuted => TARGET_DARK_LUMA,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMinLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => MIN_NORMAL_LUMA,
ColorType.LightVibrant => MIN_LIGHT_LUMA,
ColorType.DarkVibrant => 0f,
ColorType.Muted => MIN_NORMAL_LUMA,
ColorType.LightMuted => MIN_LIGHT_LUMA,
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMaxLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => MAX_NORMAL_LUMA,
ColorType.LightVibrant => 1f,
ColorType.DarkVibrant => MAX_DARK_LUMA,
ColorType.Muted => MAX_NORMAL_LUMA,
ColorType.LightMuted => 1f,
ColorType.DarkMuted => MAX_DARK_LUMA,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetTargetSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => TARGET_VIBRANT_SATURATION,
ColorType.LightVibrant => TARGET_VIBRANT_SATURATION,
ColorType.DarkVibrant => TARGET_VIBRANT_SATURATION,
ColorType.Muted => TARGET_MUTES_SATURATION,
ColorType.LightMuted => TARGET_MUTES_SATURATION,
ColorType.DarkMuted => TARGET_MUTES_SATURATION,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMinSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => MIN_VIBRANT_SATURATION,
ColorType.LightVibrant => MIN_VIBRANT_SATURATION,
ColorType.DarkVibrant => MIN_VIBRANT_SATURATION,
ColorType.Muted => 0,
ColorType.LightMuted => 0,
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMaxSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => 1f,
ColorType.LightVibrant => 1f,
ColorType.DarkVibrant => 1f,
ColorType.Muted => MAX_MUTES_SATURATION,
ColorType.LightMuted => MAX_MUTES_SATURATION,
ColorType.DarkMuted => MAX_MUTES_SATURATION,
_ => throw new ArgumentException(nameof(colorType))
};
#endregion
}
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 class ColorCube
{
private readonly int _from;
private readonly int _length;
private SortTarget _currentOrder = SortTarget.None;
public ColorCube(in Span<SKColor> fullColorList, int from, int length, SortTarget preOrdered)
{
this._from = from;
this._length = length;
OrderColors(fullColorList.Slice(from, length), preOrdered);
}
private void OrderColors(in Span<SKColor> colors, SortTarget preOrdered)
{
if (colors.Length < 2) return;
ColorRanges colorRanges = GetColorRanges(colors);
if ((colorRanges.RedRange > colorRanges.GreenRange) && (colorRanges.RedRange > colorRanges.BlueRange))
{
if (preOrdered != SortTarget.Red)
RadixLikeSortRed.Sort(colors);
_currentOrder = SortTarget.Red;
}
else if (colorRanges.GreenRange > colorRanges.BlueRange)
{
if (preOrdered != SortTarget.Green)
RadixLikeSortGreen.Sort(colors);
_currentOrder = SortTarget.Green;
}
else
{
if (preOrdered != SortTarget.Blue)
RadixLikeSortBlue.Sort(colors);
_currentOrder = SortTarget.Blue;
}
}
private ColorRanges GetColorRanges(in Span<SKColor> colors)
{
if (colors.Length < 512)
{
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 < colors.Length; i++)
{
SKColor color = colors[i];
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
{
Span<bool> redBuckets = stackalloc bool[256];
Span<bool> greenBuckets = stackalloc bool[256];
Span<bool> blueBuckets = stackalloc bool[256];
for (int i = 0; i < colors.Length; i++)
{
SKColor color = colors[i];
redBuckets[color.Red] = true;
greenBuckets[color.Green] = true;
blueBuckets[color.Blue] = true;
}
byte redMin = 0;
byte redMax = 0;
byte greenMin = 0;
byte greenMax = 0;
byte blueMin = 0;
byte blueMax = 0;
for (byte i = 0; i < redBuckets.Length; i++)
if (redBuckets[i])
{
redMin = i;
break;
}
for (int i = redBuckets.Length - 1; i >= 0; i--)
if (redBuckets[i])
{
redMax = (byte)i;
break;
}
for (byte i = 0; i < greenBuckets.Length; i++)
if (greenBuckets[i])
{
greenMin = i;
break;
}
for (int i = greenBuckets.Length - 1; i >= 0; i--)
if (greenBuckets[i])
{
greenMax = (byte)i;
break;
}
for (byte i = 0; i < blueBuckets.Length; i++)
if (blueBuckets[i])
{
blueMin = i;
break;
}
for (int i = blueBuckets.Length - 1; i >= 0; i--)
if (blueBuckets[i])
{
blueMax = (byte)i;
break;
}
return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin));
}
}
internal bool TrySplit(in Span<SKColor> fullColorList, [NotNullWhen(returnValue: true)] out ColorCube? a, [NotNullWhen(returnValue: true)] out ColorCube? b)
{
Span<SKColor> colors = fullColorList.Slice(_from, _length);
if (colors.Length < 2)
{
a = null;
b = null;
return false;
}
int median = colors.Length / 2;
a = new ColorCube(fullColorList, _from, median, _currentOrder);
b = new ColorCube(fullColorList, _from + median, colors.Length - median, _currentOrder);
return true;
}
internal SKColor GetAverageColor(in Span<SKColor> fullColorList)
{
Span<SKColor> colors = fullColorList.Slice(_from, _length);
int r = 0, g = 0, b = 0;
for (int i = 0; i < colors.Length; i++)
{
SKColor color = colors[i];
r += color.Red;
g += color.Green;
b += color.Blue;
}
return new SKColor(
(byte)(r / colors.Length),
(byte)(g / colors.Length),
(byte)(b / colors.Length)
);
}
}
internal static class RadixLikeSortRed
{
#region Methods
public static void Sort(in Span<SKColor> span)
{
Span<int> counts = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
counts[span[i].Red]++;
Span<SKColor[]> buckets = ArrayPool<SKColor[]>.Shared.Rent(256).AsSpan(0, 256);
for (int i = 0; i < counts.Length; i++)
buckets[i] = ArrayPool<SKColor>.Shared.Rent(counts[i]);
Span<int> currentBucketIndex = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
{
SKColor color = span[i];
int index = color.Red;
SKColor[] bucket = buckets[index];
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
bucket[bucketIndex] = color;
}
int newIndex = 0;
for (int i = 0; i < buckets.Length; i++)
{
Span<SKColor> bucket = buckets[i].AsSpan(0, counts[i]);
for (int j = 0; j < bucket.Length; j++)
span[newIndex++] = bucket[j];
ArrayPool<SKColor>.Shared.Return(buckets[i]);
}
}
#endregion
}
internal static class RadixLikeSortGreen
{
#region Methods
public static void Sort(in Span<SKColor> span)
{
Span<int> counts = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
counts[span[i].Green]++;
Span<SKColor[]> buckets = ArrayPool<SKColor[]>.Shared.Rent(256).AsSpan(0, 256);
for (int i = 0; i < counts.Length; i++)
buckets[i] = ArrayPool<SKColor>.Shared.Rent(counts[i]);
Span<int> currentBucketIndex = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
{
SKColor color = span[i];
int index = color.Green;
SKColor[] bucket = buckets[index];
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
bucket[bucketIndex] = color;
}
int newIndex = 0;
for (int i = 0; i < buckets.Length; i++)
{
Span<SKColor> bucket = buckets[i].AsSpan(0, counts[i]);
for (int j = 0; j < bucket.Length; j++)
span[newIndex++] = bucket[j];
ArrayPool<SKColor>.Shared.Return(buckets[i]);
}
}
#endregion
}
internal static class RadixLikeSortBlue
{
#region Methods
public static void Sort(in Span<SKColor> span)
{
Span<int> counts = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
counts[span[i].Blue]++;
Span<SKColor[]> buckets = ArrayPool<SKColor[]>.Shared.Rent(256).AsSpan(0, 256);
for (int i = 0; i < counts.Length; i++)
buckets[i] = ArrayPool<SKColor>.Shared.Rent(counts[i]);
Span<int> currentBucketIndex = stackalloc int[256];
for (int i = 0; i < span.Length; i++)
{
SKColor color = span[i];
int index = color.Blue;
SKColor[] bucket = buckets[index];
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
bucket[bucketIndex] = color;
}
int newIndex = 0;
for (int i = 0; i < buckets.Length; i++)
{
Span<SKColor> bucket = buckets[i].AsSpan(0, counts[i]);
for (int j = 0; j < bucket.Length; j++)
span[newIndex++] = bucket[j];
ArrayPool<SKColor>.Shared.Return(buckets[i]);
}
}
#endregion
}
public enum SortTarget
{
None, Red, Green, Blue
}
#endregion

View File

@ -1,10 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.Image.Screens"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.Image.Screens.CaptureScreenNodeCustomView"
x:DataType="screens:CaptureScreenNodeCustomViewModel">
<TextBlock Text="Test" />
</UserControl>

View File

@ -1,17 +0,0 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.Image.Screens;
public partial class CaptureScreenNodeCustomView : ReactiveUserControl<CaptureScreenNodeCustomViewModel>
{
public CaptureScreenNodeCustomView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -1,31 +0,0 @@
using Artemis.Core;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.VisualScripting;
namespace Artemis.VisualScripting.Nodes.Image.Screens;
public class CaptureScreenNodeCustomViewModel : CustomNodeViewModel
{
#region Properties & Fields
private readonly CaptureScreenNode _node;
private readonly INodeEditorService _nodeEditorService;
#endregion
#region Constructors
/// <inheritdoc />
public CaptureScreenNodeCustomViewModel(CaptureScreenNode node, INodeScript script, INodeEditorService nodeEditorService)
: base(node, script)
{
this._node = node;
this._nodeEditorService = nodeEditorService;
}
#endregion
#region Methods
#endregion
}