diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index b13eae70e..cb65d532a 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -64,15 +64,9 @@ - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Brushes.dll - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Groups.dll - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Layout.dll diff --git a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs index 5911591d5..110f7279c 100644 --- a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs +++ b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs @@ -8,16 +8,16 @@ namespace Artemis.Core /// public class FrameRenderedEventArgs : EventArgs { - internal FrameRenderedEventArgs(BitmapBrush bitmapBrush, RGBSurface rgbSurface) + internal FrameRenderedEventArgs(SKTexture texture, RGBSurface rgbSurface) { - BitmapBrush = bitmapBrush; + Texture = texture; RgbSurface = rgbSurface; } /// /// Gets the bitmap brush used to render this frame /// - public BitmapBrush BitmapBrush { get; } + public SKTexture Texture { get; } /// /// Gets the RGB surface used to render this frame diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 90aee62a2..7db6d2ff1 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -106,7 +106,7 @@ namespace Artemis.Core /// /// Gets or sets the X-position of the device /// - public double X + public float X { get => DeviceEntity.X; set @@ -119,7 +119,7 @@ namespace Artemis.Core /// /// Gets or sets the Y-position of the device /// - public double Y + public float Y { get => DeviceEntity.Y; set @@ -132,7 +132,7 @@ namespace Artemis.Core /// /// Gets or sets the rotation of the device /// - public double Rotation + public float Rotation { get => DeviceEntity.Rotation; set @@ -145,7 +145,7 @@ namespace Artemis.Core /// /// Gets or sets the scale of the device /// - public double Scale + public float Scale { get => DeviceEntity.Scale; set @@ -171,7 +171,7 @@ namespace Artemis.Core /// /// Gets or sets the scale of the red color component used for calibration /// - public double RedScale + public float RedScale { get => DeviceEntity.RedScale; set @@ -184,7 +184,7 @@ namespace Artemis.Core /// /// Gets or sets the scale of the green color component used for calibration /// - public double GreenScale + public float GreenScale { get => DeviceEntity.GreenScale; set @@ -197,7 +197,7 @@ namespace Artemis.Core /// /// Gets or sets the scale of the blue color component used for calibration /// - public double BlueScale + public float BlueScale { get => DeviceEntity.BlueScale; set diff --git a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs index 6424b4f06..e52e06c13 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs @@ -4,7 +4,6 @@ using System.Linq; using Artemis.Core.Services; using Ninject; using RGB.NET.Core; -using RGB.NET.Groups; using SkiaSharp; namespace Artemis.Core.LayerBrushes diff --git a/src/Artemis.Core/RGB.NET/ArtemisSampler.cs b/src/Artemis.Core/RGB.NET/ArtemisSampler.cs new file mode 100644 index 000000000..307dcc252 --- /dev/null +++ b/src/Artemis.Core/RGB.NET/ArtemisSampler.cs @@ -0,0 +1,32 @@ +using System; +using RGB.NET.Core; + +namespace Artemis.Core +{ + public class ArtemisSampler : ISampler + { + #region Methods + + /// + public Color SampleColor(SamplerInfo info) + { + int count = info.Width * info.Height; + if (count == 0) return Color.Transparent; + + ReadOnlySpan data = info.Data; + + uint r = 0, g = 0, b = 0; + for (int i = 0; i < data.Length; i += 4) + { + r += data[i]; + g += data[i + 1]; + b += data[i + 2]; + } + + float divisor = count * byte.MaxValue; + return new Color(r / divisor, g / divisor, b / divisor); + } + + #endregion + } +} diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs deleted file mode 100644 index 11021f317..000000000 --- a/src/Artemis.Core/RGB.NET/BitmapBrush.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Services; -using RGB.NET.Core; -using SkiaSharp; - -namespace Artemis.Core -{ - /// - /// The RGB.NET brush Artemis uses to map the SkiaSharp bitmap to LEDs - /// - public sealed class BitmapBrush : AbstractDecoratable, IBrush, IDisposable - { - private readonly object _disposeLock; - private readonly PluginSetting _sampleSizeSetting; - private readonly IRgbService _rgbService; - - #region Constructors - - internal BitmapBrush(Scale scale, PluginSetting sampleSizeSetting, IRgbService rgbService) - { - _disposeLock = new object(); - _sampleSizeSetting = sampleSizeSetting; - _rgbService = rgbService; - Scale = scale; - } - - #endregion - - #region Properties & Fields - - /// - public bool IsEnabled { get; set; } = true; - - /// - public BrushCalculationMode BrushCalculationMode { get; set; } = BrushCalculationMode.Absolute; - - /// - public double Brightness { get; set; } - - /// - public double Opacity { get; set; } - - /// - public IList ColorCorrections { get; } = new List(); - - /// - public Rectangle RenderedRectangle { get; private set; } - - /// - public Dictionary RenderedTargets { get; } = new(); - - /// - /// Gets or sets the desired scale of the bitmap brush - /// - public Scale Scale { get; set; } - - /// - /// Gets the last rendered scale of the bitmap brush - /// - public Scale RenderedScale { get; private set; } - - /// - /// Gets the bitmap used to sample the brush - /// - public SKBitmap? Bitmap { get; private set; } - - #endregion - - #region Methods - - /// - public void PerformRender(Rectangle rectangle, IEnumerable renderTargets) - { - lock (_disposeLock) - { - // Can happen during surface change - if (IsDisposed) - return; - - if (RenderedRectangle != rectangle || RenderedScale != Scale) - Bitmap = null; - - RenderedRectangle = rectangle; - RenderedScale = Scale; - RenderedTargets.Clear(); - - if (Bitmap == null) - CreateBitmap(RenderedRectangle); - - if (_sampleSizeSetting.Value == 1) - TakeCenter(renderTargets); - else - TakeSamples(renderTargets); - } - } - - private void TakeCenter(IEnumerable renderTargets) - { - if (Bitmap == null) - return; - - foreach (BrushRenderTarget renderTarget in renderTargets) - { - Point scaledLocation = renderTarget.Point * Scale; - if (scaledLocation.X < Bitmap.Width && scaledLocation.Y < Bitmap.Height) - { - Color pixel = Bitmap.GetPixel(scaledLocation.X.RoundToInt(), scaledLocation.Y.RoundToInt()).ToRgbColor(); - ArtemisDevice? artemisDevice = _rgbService.GetLed(renderTarget.Led)?.Device; - if (artemisDevice != null) - pixel = pixel.MultiplyRGB(artemisDevice.RedScale, artemisDevice.GreenScale, artemisDevice.BlueScale); - RenderedTargets[renderTarget] = pixel; - } - } - } - - private void TakeSamples(IEnumerable renderTargets) - { - if (Bitmap == null) - return; - - int sampleSize = _sampleSizeSetting.Value; - int sampleDepth = Math.Sqrt(sampleSize).RoundToInt(); - - int bitmapWidth = Bitmap.Width; - int bitmapHeight = Bitmap.Height; - - using SKPixmap pixmap = Bitmap.PeekPixels(); - foreach (BrushRenderTarget renderTarget in renderTargets) - { - // SKRect has all the good stuff we need - int left = (int) ((renderTarget.Rectangle.Location.X + 4) * Scale.Horizontal); - int top = (int) ((renderTarget.Rectangle.Location.Y + 4) * Scale.Vertical); - int width = (int) ((renderTarget.Rectangle.Size.Width - 8) * Scale.Horizontal); - int height = (int) ((renderTarget.Rectangle.Size.Height - 8) * Scale.Vertical); - - int verticalSteps = height / (sampleDepth - 1); - int horizontalSteps = width / (sampleDepth - 1); - - int a = 0, r = 0, g = 0, b = 0; - for (int horizontalStep = 0; horizontalStep < sampleDepth; horizontalStep++) - { - for (int verticalStep = 0; verticalStep < sampleDepth; verticalStep++) - { - int x = left + horizontalSteps * horizontalStep; - int y = top + verticalSteps * verticalStep; - if (x < 0 || x >= bitmapWidth || y < 0 || y >= bitmapHeight) - continue; - - SKColor color = pixmap.GetPixelColor(x, y); - a += color.Alpha; - r += color.Red; - g += color.Green; - b += color.Blue; - - // 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)); - } - } - - Color pixel = new(a / sampleSize, r / sampleSize, g / sampleSize, b / sampleSize); - - ArtemisDevice? artemisDevice = _rgbService.GetLed(renderTarget.Led)?.Device; - if (artemisDevice is not null) - pixel = pixel.MultiplyRGB(artemisDevice.RedScale, artemisDevice.GreenScale, artemisDevice.BlueScale); - - RenderedTargets[renderTarget] = pixel; - } - } - - private void CreateBitmap(Rectangle rectangle) - { - double width = Math.Min((rectangle.Location.X + rectangle.Size.Width) * Scale.Horizontal, 4096); - double height = Math.Min((rectangle.Location.Y + rectangle.Size.Height) * Scale.Vertical, 4096); - Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt(), SKColorType.Rgb888x)); - } - - /// - public void PerformFinalize() - { - } - - /// - public void Dispose() - { - lock (_disposeLock) - { - Bitmap?.Dispose(); - IsDisposed = true; - } - } - - internal bool IsDisposed { get; set; } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs new file mode 100644 index 000000000..3be5905a2 --- /dev/null +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -0,0 +1,96 @@ +using System; +using System.Buffers; +using RGB.NET.Core; +using SkiaSharp; + +namespace Artemis.Core +{ + public sealed class SKTexture : ITexture + { + #region Constants + + private const int STACK_ALLOC_LIMIT = 1024; + + #endregion + + #region Properties & Fields + + private readonly SKBitmap _bitmap; + private readonly int _stride; + + public SKBitmap Bitmap => _bitmap; + + public Size Size { get; } + + public ISampler Sampler { get; set; } = new ArtemisSampler(); + + public Color this[in Point point] + { + get + { + int x = (Size.Width * point.X.Clamp(0, 1)).RoundToInt(); + int y = (Size.Height * point.Y.Clamp(0, 1)).RoundToInt(); + return _bitmap.GetPixel(x, y).ToRgbColor(); + } + } + + public Color this[in Rectangle rectangle] + { + get + { + int x = (Size.Width * rectangle.Location.X.Clamp(0, 1)).RoundToInt(); + int y = (Size.Height * rectangle.Location.Y.Clamp(0, 1)).RoundToInt(); + int width = (Size.Width * rectangle.Size.Width.Clamp(0, 1)).RoundToInt(); + int height = (Size.Height * rectangle.Size.Height.Clamp(0, 1)).RoundToInt(); + + int bufferSize = width * height * 4; + if (bufferSize <= STACK_ALLOC_LIMIT) + { + Span buffer = stackalloc byte[bufferSize]; + GetRegionData(x, y, width, height, buffer); + return Sampler.SampleColor(new SamplerInfo(width, height, buffer)); + } + else + { + byte[] rent = ArrayPool.Shared.Rent(bufferSize); + Span buffer = new Span(rent).Slice(0, bufferSize); + GetRegionData(x, y, width, height, buffer); + Color color = Sampler.SampleColor(new SamplerInfo(width, height, buffer)); + ArrayPool.Shared.Return(rent); + + return color; + } + } + } + + #endregion + + #region Constructors + + public SKTexture(SKBitmap bitmap) + { + this._bitmap = bitmap; + + Size = new Size(bitmap.Width, bitmap.Height); + _stride = bitmap.Width; + } + + #endregion + + #region Methods + + private void GetRegionData(int x, int y, int width, int height, in Span buffer) + { + int width4 = width * 4; + ReadOnlySpan data = _bitmap.GetPixelSpan(); + for (int i = 0; i < height; i++) + { + ReadOnlySpan dataSlice = data.Slice((((y + i) * _stride) + x) * 4, width4); + Span destination = buffer.Slice(i * width4, width4); + dataSlice.CopyTo(destination); + } + } + + #endregion + } +} diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index ff820f4cb..e4e0a460d 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Artemis.Core.DataModelExpansions; using Artemis.Core.Ninject; -using Artemis.Core.Services.Core; using Artemis.Storage; using HidSharp; -using Newtonsoft.Json; using Ninject; using RGB.NET.Core; using Serilog; @@ -38,6 +35,8 @@ namespace Artemis.Core.Services private List _dataModelExpansions = new(); private DateTime _lastExceptionLog; private List _modules = new(); + private SKBitmap? _bitmap; + private readonly object _bitmapLock = new(); // ReSharper disable UnusedParameter.Local public CoreService(IKernel kernel, @@ -66,11 +65,14 @@ namespace Artemis.Core.Services _rgbService.Surface.Updating += SurfaceOnUpdating; _rgbService.Surface.Updated += SurfaceOnUpdated; + _rgbService.Surface.SurfaceLayoutChanged += SurfaceOnSurfaceLayoutChanged; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); + _renderScale.SettingChanged += RenderScaleSettingChanged; _pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache(); _pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache(); } + // ReSharper restore UnusedParameter.Local public TimeSpan FrameTime { get; private set; } @@ -169,8 +171,8 @@ namespace Artemis.Core.Services string[] parts = argument.Split('='); if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument)) { - _logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!); - LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument; + _logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel)logLevelArgument!); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel)logLevelArgument; } else { @@ -214,23 +216,22 @@ namespace Artemis.Core.Services foreach (Module module in modules) module.InternalUpdate(args.DeltaTime); - // If there is no ready bitmap brush, skip the frame - if (_rgbService.BitmapBrush == null) - return; - - lock (_rgbService.BitmapBrush) + lock (_bitmapLock) { - if (_rgbService.BitmapBrush.Bitmap == null) - return; + if (_bitmap == null) + { + _bitmap = CreateBitmap(); + _rgbService.UpdateTexture(_bitmap); + } // Render all active modules - using SKCanvas canvas = new(_rgbService.BitmapBrush.Bitmap); - canvas.Scale((float) _renderScale.Value); + using SKCanvas canvas = new(_bitmap); + canvas.Scale((float)_renderScale.Value); canvas.Clear(new SKColor(0, 0, 0)); if (!ModuleRenderingDisabled) // While non-activated modules may be updated above if they expand the main data model, they may never render foreach (Module module in modules.Where(m => m.IsActivated)) - module.InternalRender(args.DeltaTime, canvas, _rgbService.BitmapBrush.Bitmap.Info); + module.InternalRender(args.DeltaTime, canvas, _bitmap.Info); OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); } @@ -248,6 +249,26 @@ namespace Artemis.Core.Services } } + private SKBitmap CreateBitmap() + { + float width = MathF.Min(_rgbService.Surface.Boundary.Size.Width * (float)_renderScale.Value, 4096); + float height = MathF.Min(_rgbService.Surface.Boundary.Size.Height * (float)_renderScale.Value, 4096); + return new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt(), SKColorType.Rgb888x)); + } + + private void InvalidateBitmap() + { + + lock (_bitmapLock) + { + _bitmap?.Dispose(); + _bitmap = null; + } + } + + private void SurfaceOnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs args) => InvalidateBitmap(); + private void RenderScaleSettingChanged(object? sender, EventArgs e) => InvalidateBitmap(); + private void LogUpdateExceptions() { // Only log update exceptions every 10 seconds to avoid spamming the logs @@ -271,7 +292,7 @@ namespace Artemis.Core.Services if (_rgbService.IsRenderPaused) return; - OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush!, _rgbService.Surface)); + OnFrameRendered(new FrameRenderedEventArgs(_rgbService.Texture!, _rgbService.Surface)); } #region Events diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index 032c0db53..46f8bdc52 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -1,8 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using RGB.NET.Brushes; using RGB.NET.Core; -using RGB.NET.Groups; namespace Artemis.Core.Services { diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 838f15ff8..d478d8a00 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using RGB.NET.Core; +using SkiaSharp; namespace Artemis.Core.Services { @@ -31,9 +32,14 @@ namespace Artemis.Core.Services RGBSurface Surface { get; set; } /// - /// Gets the bitmap brush used to convert the rendered frame to LED-colors + /// Gets the texture brush used to convert the rendered frame to LED-colors /// - BitmapBrush? BitmapBrush { get; } + TextureBrush TextureBrush { get; } + + /// + /// Gets the texture used to convert the rendered frame to LED-colors + /// + SKTexture? Texture { get; } /// /// Gets the update trigger that drives the render loop @@ -45,6 +51,12 @@ namespace Artemis.Core.Services /// bool IsRenderPaused { get; set; } + /// + /// Recreates the Texture to use the given + /// + /// + void UpdateTexture(SKBitmap bitmap); + /// /// Adds the given device provider to the /// diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 3cec517d6..5ca291db6 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -7,8 +7,8 @@ using Artemis.Core.Services.Models; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Repositories.Interfaces; using RGB.NET.Core; -using RGB.NET.Groups; using Serilog; +using SkiaSharp; namespace Artemis.Core.Services { @@ -24,7 +24,6 @@ namespace Artemis.Core.Services private readonly ILogger _logger; private readonly IPluginManagementService _pluginManagementService; private readonly IDeviceRepository _deviceRepository; - private readonly PluginSetting _renderScaleSetting; private readonly PluginSetting _targetFrameRateSetting; private readonly PluginSetting _sampleSizeSetting; private ListLedGroup? _surfaceLedGroup; @@ -35,7 +34,6 @@ namespace Artemis.Core.Services _logger = logger; _pluginManagementService = pluginManagementService; _deviceRepository = deviceRepository; - _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); _sampleSizeSetting = settingsService.GetSetting("Core.SampleSize", 1); @@ -43,13 +41,13 @@ namespace Artemis.Core.Services // Let's throw these for now Surface.Exception += SurfaceOnException; - _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; + Surface.SurfaceLayoutChanged += SurfaceOnLayoutChanged; _targetFrameRateSetting.SettingChanged += TargetFrameRateSettingOnSettingChanged; _enabledDevices = new List(); _devices = new List(); _ledMap = new Dictionary(); - UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; + UpdateTrigger = new TimerUpdateTrigger { UpdateFrequency = 1.0 / _targetFrameRateSetting.Value }; Surface.RegisterUpdateTrigger(UpdateTrigger); } @@ -61,7 +59,8 @@ namespace Artemis.Core.Services public RGBSurface Surface { get; set; } public TimerUpdateTrigger UpdateTrigger { get; } - public BitmapBrush? BitmapBrush { get; private set; } + public TextureBrush TextureBrush { get; private set; } = new(ITexture.Empty) { CalculationMode = RenderMode.Absolute }; + public SKTexture? Texture { get; private set; } public bool IsRenderPaused { get; set; } @@ -104,7 +103,7 @@ namespace Artemis.Core.Services finally { _modifyingProviders = false; - UpdateBitmapBrush(); + UpdateLedGroup(); } } } @@ -132,12 +131,20 @@ namespace Artemis.Core.Services finally { _modifyingProviders = false; - UpdateBitmapBrush(); + UpdateLedGroup(); } } } - private void UpdateBitmapBrush() + public void UpdateTexture(SKBitmap bitmap) + { + Texture = new SKTexture(bitmap); + TextureBrush.Texture = Texture; + } + + private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) => UpdateLedGroup(); + + private void UpdateLedGroup() { lock (_devices) { @@ -146,11 +153,9 @@ namespace Artemis.Core.Services _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); - if (_surfaceLedGroup == null || BitmapBrush == null) + if (_surfaceLedGroup == null) { - // Apply the application wide brush and decorator - BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value), _sampleSizeSetting, this); - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = TextureBrush }; OnLedsChanged(); return; } @@ -161,8 +166,7 @@ namespace Artemis.Core.Services _surfaceLedGroup.Detach(Surface); // Apply the application wide brush and decorator - BitmapBrush.Scale = new Scale(_renderScaleSetting.Value); - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = TextureBrush }; OnLedsChanged(); } } @@ -234,10 +238,8 @@ namespace Artemis.Core.Services public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) { device.ApplyLayout(layout, createMissingLeds, removeExessiveLeds); - // Applying layouts can affect LEDs, update LED group - UpdateBitmapBrush(); } - + public ArtemisDevice? GetDevice(IRGBDevice rgbDevice) { return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice); @@ -259,7 +261,6 @@ namespace Artemis.Core.Services device.ApplyToEntity(); _deviceRepository.Save(device.DeviceEntity); - UpdateBitmapBrush(); OnDeviceAdded(new DeviceEventArgs(device)); } @@ -273,7 +274,6 @@ namespace Artemis.Core.Services device.ApplyToEntity(); _deviceRepository.Save(device.DeviceEntity); - UpdateBitmapBrush(); OnDeviceRemoved(new DeviceEventArgs(device)); } @@ -298,7 +298,6 @@ namespace Artemis.Core.Services if (device.IsEnabled) _enabledDevices.Remove(device); - UpdateBitmapBrush(); OnDeviceRemoved(new DeviceEventArgs(device)); } @@ -349,12 +348,6 @@ namespace Artemis.Core.Services #region Event handlers - private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) - { - // The surface hasn't changed so we can safely reuse it - UpdateBitmapBrush(); - } - private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) { UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value; diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs index ca0d1ce60..245bd7621 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs @@ -90,8 +90,8 @@ namespace Artemis.Core.Services.Models surfaceArrangementType.Arrange(devices); // See if we need to move the surface to keep X and Y values positive - double x = devices.Min(d => d.RgbDevice.Location.X); - double y = devices.Min(d => d.RgbDevice.Location.Y); + float x = devices.Min(d => d.RgbDevice.Location.X); + float y = devices.Min(d => d.RgbDevice.Location.Y); if (x < 0) { foreach (ArtemisDevice surfaceDevice in devices) diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs index a3ada512d..d21e4378a 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs @@ -52,11 +52,11 @@ namespace Artemis.Core.Services.Models if (stackVertically) { artemisDevice.X = previous.X; - artemisDevice.Y = previous.RgbDevice.Location.Y + previous.RgbDevice.Size.Height + MarginTop / 2.0; + artemisDevice.Y = previous.RgbDevice.Location.Y + previous.RgbDevice.Size.Height + MarginTop / 2f; } else { - artemisDevice.X = previous.RgbDevice.Location.X + previous.RgbDevice.Size.Width + MarginLeft / 2.0; + artemisDevice.X = previous.RgbDevice.Location.X + previous.RgbDevice.Size.Width + MarginLeft / 2f; artemisDevice.Y = previous.Y; } } @@ -66,7 +66,7 @@ namespace Artemis.Core.Services.Models { HorizontalArrangementPosition.Left => startPoint.X - artemisDevice.RgbDevice.Size.Width - MarginRight, HorizontalArrangementPosition.Right => startPoint.X + MarginLeft, - HorizontalArrangementPosition.Center => startPoint.X - artemisDevice.RgbDevice.Size.Width / 2, + HorizontalArrangementPosition.Center => startPoint.X - artemisDevice.RgbDevice.Size.Width / 2f, HorizontalArrangementPosition.Equal => startPoint.X, _ => artemisDevice.X }; @@ -74,7 +74,7 @@ namespace Artemis.Core.Services.Models { VerticalArrangementPosition.Top => startPoint.Y - artemisDevice.RgbDevice.Size.Height - MarginBottom, VerticalArrangementPosition.Bottom => startPoint.Y + MarginTop, - VerticalArrangementPosition.Center => startPoint.Y - artemisDevice.RgbDevice.Size.Height / 2, + VerticalArrangementPosition.Center => startPoint.Y - artemisDevice.RgbDevice.Size.Height / 2f, VerticalArrangementPosition.Equal => startPoint.Y, _ => artemisDevice.X }; diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs index 6a2c4f185..5f25fe5f2 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs @@ -62,18 +62,18 @@ namespace Artemis.Core.Services.Models if (!devices.Any()) return new Point(); - double x = horizontalPosition switch + float x = horizontalPosition switch { - HorizontalArrangementPosition.Left => devices.Min(d => d.RgbDevice.Location.X) - (AppliedConfiguration?.MarginLeft ?? 0.0), - HorizontalArrangementPosition.Right => devices.Max(d => d.RgbDevice.Location.X + d.RgbDevice.Size.Width) + (AppliedConfiguration?.MarginRight ?? 0.0), + HorizontalArrangementPosition.Left => devices.Min(d => d.RgbDevice.Location.X) - (AppliedConfiguration?.MarginLeft ?? 0.0f), + HorizontalArrangementPosition.Right => devices.Max(d => d.RgbDevice.Location.X + d.RgbDevice.Size.Width) + (AppliedConfiguration?.MarginRight ?? 0.0f), HorizontalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.X, HorizontalArrangementPosition.Equal => devices.First().RgbDevice.Location.X, _ => throw new ArgumentOutOfRangeException(nameof(horizontalPosition), horizontalPosition, null) }; - double y = verticalPosition switch + float y = verticalPosition switch { - VerticalArrangementPosition.Top => devices.Min(d => d.RgbDevice.Location.Y) - (AppliedConfiguration?.MarginTop ?? 0.0), - VerticalArrangementPosition.Bottom => devices.Max(d => d.RgbDevice.Location.Y + d.RgbDevice.Size.Height) + (AppliedConfiguration?.MarginBottom ?? 0.0), + VerticalArrangementPosition.Top => devices.Min(d => d.RgbDevice.Location.Y) - (AppliedConfiguration?.MarginTop ?? 0.0f), + VerticalArrangementPosition.Bottom => devices.Max(d => d.RgbDevice.Location.Y + d.RgbDevice.Size.Height) + (AppliedConfiguration?.MarginBottom ?? 0.0f), VerticalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.Y, VerticalArrangementPosition.Equal => devices.First().RgbDevice.Location.Y, _ => throw new ArgumentOutOfRangeException(nameof(verticalPosition), verticalPosition, null) diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 670cec969..258902d5e 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -10,14 +10,14 @@ namespace Artemis.Storage.Entities.Surface } public string Id { get; set; } - public double X { get; set; } - public double Y { get; set; } - public double Rotation { get; set; } - public double Scale { get; set; } + public float X { get; set; } + public float Y { get; set; } + public float Rotation { get; set; } + public float Scale { get; set; } public int ZIndex { get; set; } - public double RedScale { get; set; } - public double GreenScale { get; set; } - public double BlueScale { get; set; } + public float RedScale { get; set; } + public float GreenScale { get; set; } + public float BlueScale { get; set; } public bool IsEnabled { get; set; } public int PhysicalLayout { get; set; } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index c3ba1fd6a..498932b35 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -6,6 +6,7 @@ using System.Windows.Media.Imaging; using Artemis.Core; using RGB.NET.Core; using Color = System.Windows.Media.Color; +using SolidColorBrush = System.Windows.Media.SolidColorBrush; namespace Artemis.UI.Shared { diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index a78e9658a..7ec6d7c41 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -1,364 +1,358 @@  - - WinExe - net5.0-windows - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - Artemis - Artemis - en-US - Provides advanced unified lighting across many different brands RGB peripherals - Copyright © Robert Beekman - 2021 - 2.0.0.0 - bin\$(Platform)\$(Configuration)\ - true - x64 - windows - - - x64 - + + WinExe + net5.0-windows + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + Artemis + Artemis + en-US + Provides advanced unified lighting across many different brands RGB peripherals + Copyright © Robert Beekman - 2021 + 2.0.0.0 + bin\$(Platform)\$(Configuration)\ + true + x64 + windows + + + x64 + - - Resources\Images\Logo\logo-512.ico - - - - 2.0.0.0 - 2.0.0 - - - 2.0-{chash:6} - true - true - true - v[0-9]* - true - git - true - - - - - - - false - - - - - - - - - - - - - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Brushes.dll - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Groups.dll - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Layout.dll - - - - - - ResXFileCodeGenerator - Designer - Resources.Designer.cs - - - - - true - - - true - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - True - True - Settings.settings - - - - - PreserveNewest - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - $(DefaultXamlRuntime) - - + + Resources\Images\Logo\logo-512.ico + + + + 2.0.0.0 + 2.0.0 + + + 2.0-{chash:6} + true + true + true + v[0-9]* + true + git + true + + + + + + + false + + + + + + + + + + + + + + + ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll + + + ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Layout.dll + + + + + + ResXFileCodeGenerator + Designer + Resources.Designer.cs + + + + + true + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + PreserveNewest + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + $(DefaultXamlRuntime) + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index 437284ed4..2d5fc3572 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -52,22 +52,22 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { Execute.PostToUIThread(() => { - if (e.BitmapBrush?.Bitmap == null || e.BitmapBrush.Bitmap.Pixels.Length == 0) + if (e.Texture.Bitmap.Pixels.Length == 0) return; - SKImageInfo bitmapInfo = e.BitmapBrush.Bitmap.Info; + SKImageInfo bitmapInfo = e.Texture.Bitmap.Info; if (!(CurrentFrame is WriteableBitmap writeableBitmap) || writeableBitmap.Width != bitmapInfo.Width || writeableBitmap.Height != bitmapInfo.Height) { - CurrentFrame = e.BitmapBrush.Bitmap.ToWriteableBitmap(); + CurrentFrame = e.Texture.Bitmap.ToWriteableBitmap(); return; } try { - using (SKImage skiaImage = SKImage.FromPixels(e.BitmapBrush.Bitmap.PeekPixels())) + using (SKImage skiaImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels())) { SKImageInfo info = new(skiaImage.Width, skiaImage.Height); writeableBitmap.Lock(); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs index ad06f0ca0..ea52a5867 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs @@ -17,16 +17,16 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs private readonly ICoreService _coreService; private readonly IRgbService _rgbService; private readonly IMessageService _messageService; - private readonly double _initialRedScale; - private readonly double _initialGreenScale; - private readonly double _initialBlueScale; + private readonly float _initialRedScale; + private readonly float _initialGreenScale; + private readonly float _initialBlueScale; private int _rotation; - private double _scale; + private float _scale; private int _x; private int _y; - private double _redScale; - private double _greenScale; - private double _blueScale; + private float _redScale; + private float _greenScale; + private float _blueScale; private SKColor _currentColor; private bool _displayOnDevices; @@ -46,9 +46,9 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs Y = (int) Device.Y; Scale = Device.Scale; Rotation = (int) Device.Rotation; - RedScale = Device.RedScale * 100d; - GreenScale = Device.GreenScale * 100d; - BlueScale = Device.BlueScale * 100d; + RedScale = Device.RedScale * 100f; + GreenScale = Device.GreenScale * 100f; + BlueScale = Device.BlueScale * 100f; //we need to store the initial values to be able to restore them when the user clicks "Cancel" _initialRedScale = Device.RedScale; _initialGreenScale = Device.GreenScale; @@ -79,7 +79,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _y, value); } - public double Scale + public float Scale { get => _scale; set => SetAndNotify(ref _scale, value); @@ -91,19 +91,19 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _rotation, value); } - public double RedScale + public float RedScale { get => _redScale; set => SetAndNotify(ref _redScale, value); } - public double GreenScale + public float GreenScale { get => _greenScale; set => SetAndNotify(ref _greenScale, value); } - public double BlueScale + public float BlueScale { get => _blueScale; set => SetAndNotify(ref _blueScale, value); @@ -134,9 +134,9 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs Device.Y = Y; Device.Scale = Scale; Device.Rotation = Rotation; - Device.RedScale = RedScale / 100d; - Device.GreenScale = GreenScale / 100d; - Device.BlueScale = BlueScale / 100d; + Device.RedScale = RedScale / 100f; + Device.GreenScale = GreenScale / 100f; + Device.BlueScale = BlueScale / 100f; _coreService.ModuleRenderingDisabled = false; Session.Close(true); @@ -144,9 +144,9 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs public void ApplyScaling() { - Device.RedScale = RedScale / 100d; - Device.GreenScale = GreenScale / 100d; - Device.BlueScale = BlueScale / 100d; + Device.RedScale = RedScale / 100f; + Device.GreenScale = GreenScale / 100f; + Device.BlueScale = BlueScale / 100f; } public void BrowseCustomLayout(object sender, MouseEventArgs e) diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs index 764bb36db..188ace180 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs @@ -10,7 +10,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs RuleFor(m => m.Y).GreaterThanOrEqualTo(0).WithMessage("Y-coordinate must be 0 or greater"); - RuleFor(m => m.Scale).GreaterThanOrEqualTo(0.2).WithMessage("Scale must be 0.2 or greater"); + RuleFor(m => m.Scale).GreaterThanOrEqualTo(0.2f).WithMessage("Scale must be 0.2 or greater"); RuleFor(m => m.Rotation).GreaterThanOrEqualTo(0).WithMessage("Rotation must be 0 or greater"); RuleFor(m => m.Rotation).LessThanOrEqualTo(359).WithMessage("Rotation must be 359 or less"); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs index 07f04fce9..4168c5b00 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs @@ -4,9 +4,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services; using MaterialDesignThemes.Wpf; -using RGB.NET.Brushes; using RGB.NET.Core; -using RGB.NET.Groups; namespace Artemis.UI.Screens.SurfaceEditor.Dialogs {