From b1870e9e64c0f2edb04eca58f349105b54223011 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 19 Dec 2019 23:18:09 +0100 Subject: [PATCH] Added configurable LED sample size --- .../Extensions/DoubleExtensions.cs | 2 + src/Artemis.Core/Models/Profile/Layer.cs | 4 + .../Models/Profile/LayerShapes/Ellipse.cs | 1 + .../Models/Profile/LayerShapes/Rectangle.cs | 1 + src/Artemis.Core/RGB.NET/BitmapBrush.cs | 77 +++++++++++++++++-- src/Artemis.Core/Services/PluginService.cs | 2 +- src/Artemis.Core/Services/RgbService.cs | 9 +-- .../Visualization/ProfileViewModel.cs | 14 +++- .../Screens/Settings/SettingsView.xaml | 22 ++++++ .../Screens/Settings/SettingsViewModel.cs | 17 +++- 10 files changed, 132 insertions(+), 17 deletions(-) diff --git a/src/Artemis.Core/Extensions/DoubleExtensions.cs b/src/Artemis.Core/Extensions/DoubleExtensions.cs index 7eda9aa26..62ec1117e 100644 --- a/src/Artemis.Core/Extensions/DoubleExtensions.cs +++ b/src/Artemis.Core/Extensions/DoubleExtensions.cs @@ -1,9 +1,11 @@ using System; +using System.Runtime.CompilerServices; namespace Artemis.Core.Extensions { public static class DoubleExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RoundToInt(this double number) { return (int) Math.Round(number, MidpointRounding.AwayFromZero); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 77d179b2f..32a8c5542 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -118,6 +118,10 @@ namespace Artemis.Core.Models.Profile return; canvas.Save(); + canvas.ClipPath(Path); + // Placeholder + if (LayerShape?.RenderPath != null) + canvas.DrawPath(LayerShape.RenderPath, new SKPaint(){Color = new SKColor(255,0,0)}); LayerBrush?.Render(canvas); canvas.Restore(); } diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs index 9cb1095e9..32f0e082f 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs @@ -21,6 +21,7 @@ namespace Artemis.Core.Models.Profile.LayerShapes var path = new SKPath(); path.AddOval(rect); + path.Transform(SKMatrix.MakeTranslation(Layer.Rectangle.Left, Layer.Rectangle.Top)); RenderPath = path; RenderRectangle = path.GetRect(); diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs index 1e911dc0f..7c74e8879 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs @@ -20,6 +20,7 @@ namespace Artemis.Core.Models.Profile.LayerShapes var rect = SKRect.Create(Position.X * width, Position.Y * height, Size.Width * width, Size.Height * height); var path = new SKPath(); path.AddRect(rect); + path.Transform(SKMatrix.MakeTranslation(Layer.Rectangle.Left, Layer.Rectangle.Top)); RenderPath = path; RenderRectangle = path.GetRect(); diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs index 720533da0..46a11e38f 100644 --- a/src/Artemis.Core/RGB.NET/BitmapBrush.cs +++ b/src/Artemis.Core/RGB.NET/BitmapBrush.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Artemis.Core.Extensions; +using Artemis.Core.Plugins.Models; using RGB.NET.Core; using SkiaSharp; @@ -9,10 +10,13 @@ namespace Artemis.Core.RGB.NET { public class BitmapBrush : AbstractDecoratable, IBrush, IDisposable { + private readonly PluginSetting _sampleSizeSetting; + #region Constructors - public BitmapBrush(Scale scale) + public BitmapBrush(Scale scale, PluginSetting sampleSizeSetting) { + _sampleSizeSetting = sampleSizeSetting; Scale = scale; } @@ -61,12 +65,65 @@ namespace Artemis.Core.RGB.NET if (Bitmap == null) CreateBitmap(RenderedRectangle); + if (_sampleSizeSetting.Value == 1) + TakeCenter(renderTargets); + else + TakeSamples(renderTargets); + } + + private void TakeCenter(IEnumerable renderTargets) + { foreach (var renderTarget in renderTargets) { - // TODO: Right now the sample size is 1, make this configurable to something higher and average the samples out var scaledLocation = renderTarget.Point * Scale; if (scaledLocation.X < Bitmap.Width && scaledLocation.Y < Bitmap.Height) - RenderedTargets[renderTarget] = Bitmap.GetPixel(RoundToInt(scaledLocation.X), RoundToInt(scaledLocation.Y)).ToRgbColor(); + RenderedTargets[renderTarget] = Bitmap.GetPixel(scaledLocation.X.RoundToInt(), scaledLocation.Y.RoundToInt()).ToRgbColor(); + } + } + + private void TakeSamples(IEnumerable renderTargets) + { + var sampleSize = _sampleSizeSetting.Value; + var sampleDepth = Math.Sqrt(sampleSize).RoundToInt(); + var pixelSpan = Bitmap.GetPixelSpan(); + + foreach (var renderTarget in renderTargets) + { + // SKRect has all the good stuff we need + var rect = SKRect.Create( + (float) ((renderTarget.Rectangle.Location.X + 4) * Scale.Horizontal), + (float) ((renderTarget.Rectangle.Location.Y + 4) * Scale.Vertical), + (float) ((renderTarget.Rectangle.Size.Width - 8) * Scale.Horizontal), + (float) ((renderTarget.Rectangle.Size.Height - 8) * Scale.Vertical)); + + var verticalSteps = rect.Height / (sampleDepth - 1); + var horizontalSteps = rect.Width / (sampleDepth - 1); + + var a = 0; + var r = 0; + var g = 0; + var b = 0; + for (var horizontalStep = 0; horizontalStep < sampleDepth; horizontalStep++) + { + for (var verticalStep = 0; verticalStep < sampleDepth; verticalStep++) + { + var x = (rect.Left + horizontalSteps * horizontalStep).RoundToInt(); + var y = (rect.Top + verticalSteps * verticalStep).RoundToInt(); + if (x < 0 || x > Bitmap.Width || y < 0 || y > Bitmap.Height) + continue; + + var (pixelA, pixelR, pixelG, pixelB) = GetRgbColor(pixelSpan, x, y); + a += pixelA; + r += pixelR; + g += pixelG; + b += pixelB; + + // Uncomment to view the sample pixels in the debugger, need a checkbox in the actual debugger but this was a quickie + // Bitmap.SetPixel(x, y, new SKColor(0, 255, 0)); + } + } + + RenderedTargets[renderTarget] = new Color(a / sampleSize, r / sampleSize, g / sampleSize, b / sampleSize); } } @@ -74,16 +131,24 @@ namespace Artemis.Core.RGB.NET private void CreateBitmap(Rectangle rectangle) { - // TODO: Test this max size, it applied to System.Drawing.Bitmap but SKBitmap might scale better or worse var width = Math.Min((rectangle.Location.X + rectangle.Size.Width) * Scale.Horizontal, 4096); var height = Math.Min((rectangle.Location.Y + rectangle.Size.Height) * Scale.Vertical, 4096); Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt())); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int RoundToInt(double number) + private Tuple GetRgbColor(in ReadOnlySpan pixelSpan, float x, float y) { - return (int) Math.Round(number, MidpointRounding.AwayFromZero); + var index = ((int) y * Bitmap.Width + (int) x) * 4; + if (index + 3 > pixelSpan.Length) + return new Tuple(0, 0, 0, 0); + + var b = pixelSpan[index]; + var g = pixelSpan[index + 1]; + var r = pixelSpan[index + 2]; + var a = pixelSpan[index + 3]; + + return new Tuple(a, r, g, b); } /// diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 35c6c8bea..691c26674 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -308,7 +308,7 @@ namespace Artemis.Core.Services public Plugin GetDevicePlugin(IRGBDevice rgbDevice) { - return GetPluginsOfType().First(d => d.RgbDeviceProvider.Devices.Contains(rgbDevice)); + return GetPluginsOfType().First(d => d.RgbDeviceProvider.Devices != null && d.RgbDeviceProvider.Devices.Contains(rgbDevice)); } public void Dispose() diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 85527b199..2eb5e0f16 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; using Artemis.Core.Events; using Artemis.Core.Plugins.Models; using Artemis.Core.RGB.NET; using Artemis.Core.Services.Interfaces; -using RGB.NET.Brushes; -using RGB.NET.Brushes.Gradients; using RGB.NET.Core; using RGB.NET.Groups; using Serilog; @@ -21,6 +18,7 @@ namespace Artemis.Core.Services private readonly List _loadedDevices; private readonly ILogger _logger; private readonly PluginSetting _renderScaleSetting; + private readonly PluginSetting _sampleSizeSetting; private readonly PluginSetting _targetFrameRateSetting; private readonly TimerUpdateTrigger _updateTrigger; private ListLedGroup _surfaceLedGroup; @@ -30,9 +28,10 @@ namespace Artemis.Core.Services _logger = logger; _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 1.0); _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); + _sampleSizeSetting = settingsService.GetSetting("Core.SampleSize", 1); Surface = RGBSurface.Instance; - + // Let's throw these for now Surface.Exception += SurfaceOnException; _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; @@ -105,7 +104,7 @@ namespace Artemis.Core.Services if (_surfaceLedGroup == null) { // Apply the application wide brush and decorator - BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value)); + BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value), _sampleSizeSetting); _surfaceLedGroup = new ListLedGroup(Surface.Leds) {Brush = BitmapBrush}; return; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index e4840d593..a56da3ccc 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -80,8 +80,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization // Remove the tool from the canvas if (_activeToolViewModel != null) { - CanvasViewModels.Remove(_activeToolViewModel); - NotifyOfPropertyChange(() => CanvasViewModels); + lock (CanvasViewModels) + { + CanvasViewModels.Remove(_activeToolViewModel); + NotifyOfPropertyChange(() => CanvasViewModels); + } } // Set the new tool @@ -89,8 +92,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization // Add the new tool to the canvas if (_activeToolViewModel != null) { - CanvasViewModels.Add(_activeToolViewModel); - NotifyOfPropertyChange(() => CanvasViewModels); + lock (CanvasViewModels) + { + CanvasViewModels.Add(_activeToolViewModel); + NotifyOfPropertyChange(() => CanvasViewModels); + } } } } diff --git a/src/Artemis.UI/Screens/Settings/SettingsView.xaml b/src/Artemis.UI/Screens/Settings/SettingsView.xaml index 55d956ea7..c0897814b 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/SettingsView.xaml @@ -130,6 +130,28 @@ + + + + + + + + + + + + + LED sample size + + Sets the amount of samples that is taken to determine each LEDs color. + Best you leave it on 1 but included for completeness, a higher value increases CPU-usage quite rapidly. + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index 83695cebf..9a8235b68 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -45,8 +45,13 @@ namespace Artemis.UI.Screens.Settings TargetFrameRates = new List>(); for (var i = 10; i <= 30; i += 5) TargetFrameRates.Add(new Tuple(i + " FPS", i)); + + // Anything else is kinda broken right now + SampleSizes = new List {1, 9}; } + public List SampleSizes { get; set; } + public BindableCollection DeviceSettingsViewModels { get; set; } public List> RenderScales { get; set; } @@ -61,7 +66,7 @@ namespace Artemis.UI.Screens.Settings { get => TargetFrameRates.FirstOrDefault(t => Math.Abs(t.Item2 - TargetFrameRate) < 0.01); set => TargetFrameRate = value.Item2; - } + } public double RenderScale { @@ -85,6 +90,16 @@ namespace Artemis.UI.Screens.Settings } } + public int SampleSize + { + get => _settingsService.GetSetting("Core.SampleSize", 1).Value; + set + { + _settingsService.GetSetting("Core.SampleSize", 1).Value = value; + _settingsService.GetSetting("Core.SampleSize", 1).Save(); + } + } + public string Title => "Settings"; protected override void OnActivate()