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

Added ScreenCapture and QuantizeNode (WIP)

This commit is contained in:
Darth Affe 2022-08-18 20:27:21 +02:00
parent 84b394fc51
commit 082531a682
7 changed files with 766 additions and 74 deletions

View File

@ -13,78 +13,81 @@
x:DataType="visualScripting:CableViewModel"
ClipToBounds="False"
IsVisible="{CompiledBinding Connected}">
<UserControl.Resources>
<converters:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
<shared:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<Canvas PointerEnter="OnPointerEnter"
PointerLeave="OnPointerLeave">
<Path Name="CablePath"
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
StrokeThickness="4"
StrokeLineCap="Round">
<Path.Transitions>
<Transitions>
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
</Transitions>
</Path.Transitions>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="False">
<PathFigure.Segments>
<BezierSegment />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Border Name="ValueBorder"
Background="{DynamicResource ContentDialogBackground}"
BorderBrush="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
BorderThickness="2"
CornerRadius="3"
Padding="4"
Canvas.Left="{CompiledBinding ValuePoint.X}"
Canvas.Top="{CompiledBinding ValuePoint.Y}"
IsVisible="{CompiledBinding DisplayValue}">
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
<ContentControl.DataTemplates>
<DataTemplate DataType="skiaSharp:SKColor">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="HexDisplay"
Text="{CompiledBinding Converter={StaticResource SKColorToStringConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
FontFamily="Consolas"/>
<Border Margin="5 0 0 0"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
BorderThickness="1"
MinWidth="18"
MinHeight="18"
Background="{DynamicResource CheckerboardBrush}"
BorderBrush="{DynamicResource ColorPickerButtonOutline}"
CornerRadius="4"
ClipToBounds="True">
<Border CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding Converter={StaticResource SKColorToColorConverter}}" />
</Border.Background>
</Border>
</Border>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Border>
</Canvas>
<UserControl.Resources>
<converters:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
<shared:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<Canvas PointerEnter="OnPointerEnter"
PointerLeave="OnPointerLeave">
<Path Name="CablePath"
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
StrokeThickness="4"
StrokeLineCap="Round">
<Path.Transitions>
<Transitions>
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
</Transitions>
</Path.Transitions>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="False">
<PathFigure.Segments>
<BezierSegment />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Border Name="ValueBorder"
Background="{DynamicResource ContentDialogBackground}"
BorderBrush="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
BorderThickness="2"
CornerRadius="3"
Padding="4"
Canvas.Left="{CompiledBinding ValuePoint.X}"
Canvas.Top="{CompiledBinding ValuePoint.Y}"
IsVisible="{CompiledBinding DisplayValue}">
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
<ContentControl.DataTemplates>
<DataTemplate DataType="skiaSharp:SKImage">
</DataTemplate>
<DataTemplate DataType="skiaSharp:SKColor">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="HexDisplay"
Text="{CompiledBinding Converter={StaticResource SKColorToStringConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
FontFamily="Consolas"/>
<Border Margin="5 0 0 0"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
BorderThickness="1"
MinWidth="18"
MinHeight="18"
Background="{DynamicResource CheckerboardBrush}"
BorderBrush="{DynamicResource ColorPickerButtonOutline}"
CornerRadius="4"
ClipToBounds="True">
<Border CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding Converter={StaticResource SKColorToColorConverter}}" />
</Border.Background>
</Border>
</Border>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Border>
</Canvas>
</UserControl>

View File

@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -15,6 +16,7 @@
<PackageReference Include="NoStringEvaluating" Version="2.2.2" />
<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.1" />
</ItemGroup>
@ -27,6 +29,9 @@
<Compile Update="Nodes\Easing\Screens\EasingTypeNodeEasingView.axaml.cs">
<DependentUpon>EasingTypeNodeEasingView.axaml</DependentUpon>
</Compile>
<Compile Update="Nodes\Image\Screens\CaptureScreenNodeCustomView.axaml.cs">
<DependentUpon>CaptureScreenNodeCustomView.axaml</DependentUpon>
</Compile>
<Compile Update="Nodes\Static\Screens\StaticStringValueNodeCustomView.axaml.cs">
<DependentUpon>StaticStringValueNodeCustomView.axaml</DependentUpon>
</Compile>

View File

@ -0,0 +1,66 @@
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(SKImage))]
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<SKImage> Output { get; set; }
#endregion
#region Constructors
public CaptureScreenNode()
: base("Capture Screen", "Captures a region of the screen")
{
Output = CreateOutputPin<SKImage>("Image");
_captureZone = _screenCapture.RegisterCaptureZone(4500, 700, 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 = 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

@ -0,0 +1,560 @@
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(SKImage), OutputType = typeof(SKColor))]
public class QuantizeNode : Node
{
#region Properties & Fields
public InputPin<SKImage> 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<SKImage>("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;
using SKBitmap bitmap = SKBitmap.FromImage(Image.Value);
SKColor[] colorPalette = ColorQuantizer.Quantize(bitmap.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

@ -0,0 +1,10 @@
<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

@ -0,0 +1,17 @@
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

@ -0,0 +1,31 @@
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
}