diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index de5d77946..ab3b4c4f5 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -160,7 +160,7 @@ - + @@ -194,6 +194,7 @@ + diff --git a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs index d88bb89b7..5267bd344 100644 --- a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs +++ b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs @@ -1,5 +1,4 @@ using System; -using System.Drawing; using Artemis.Core.RGB.NET; using RGB.NET.Core; @@ -7,13 +6,13 @@ namespace Artemis.Core.Events { public class FrameRenderedEventArgs : EventArgs { - public FrameRenderedEventArgs(GraphicsDecorator graphicsDecorator, RGBSurface rgbSurface) + public FrameRenderedEventArgs(BitmapBrush bitmapBrush, RGBSurface rgbSurface) { - GraphicsDecorator = graphicsDecorator; + BitmapBrush = bitmapBrush; RgbSurface = rgbSurface; } - - public GraphicsDecorator GraphicsDecorator { get; } + + public BitmapBrush BitmapBrush { get; } public RGBSurface RgbSurface { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/RgbColorExtensions.cs b/src/Artemis.Core/Extensions/RgbColorExtensions.cs deleted file mode 100644 index e3dcef4c9..000000000 --- a/src/Artemis.Core/Extensions/RgbColorExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using RGB.NET.Core; -using Color = System.Windows.Media.Color; - -namespace Artemis.Core.Extensions -{ - public static class RgbColorExtensions - { - public static Color ToMediaColor(this global::RGB.NET.Core.Color color) - { - var (_, r, g, b) = color.GetRGBBytes(); - return Color.FromRgb(r, g, b); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/SKColorExtensions.cs b/src/Artemis.Core/Extensions/SKColorExtensions.cs new file mode 100644 index 000000000..d7dfac717 --- /dev/null +++ b/src/Artemis.Core/Extensions/SKColorExtensions.cs @@ -0,0 +1,14 @@ +using RGB.NET.Core; +using SkiaSharp; + +namespace Artemis.Core.Extensions +{ + // ReSharper disable once InconsistentNaming - I didn't come up with SKColor + public static class SKColorExtensions + { + public static Color ToRgbColor(this SKColor color) + { + return new Color(color.Alpha, color.Red, color.Green, color.Blue); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs new file mode 100644 index 000000000..7f8f3945f --- /dev/null +++ b/src/Artemis.Core/RGB.NET/BitmapBrush.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Artemis.Core.Extensions; +using RGB.NET.Core; +using SkiaSharp; + +namespace Artemis.Core.RGB.NET +{ + public class BitmapBrush : AbstractDecoratable, IBrush, IDisposable + { + #region Constructors + + public BitmapBrush(Scale scale) + { + 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 Dictionary(); + + public Scale Scale { get; set; } + public SKBitmap Bitmap { get; private set; } + + #endregion + + #region Methods + + /// + public virtual void PerformRender(Rectangle rectangle, IEnumerable renderTargets) + { + if (RenderedRectangle != rectangle || RenderedScale != Scale) + Bitmap = null; + + if (renderTargets.Any()) + { + var test = RGBSurface.Instance.SurfaceRectangle; + var width = renderTargets.Max(l => l.Led.AbsoluteLedRectangle.Location.X + l.Led.AbsoluteLedRectangle.Size.Width); + var height = renderTargets.Max(l => l.Led.AbsoluteLedRectangle.Location.Y + l.Led.AbsoluteLedRectangle.Size.Height); + } + + RenderedRectangle = rectangle; + RenderedScale = Scale; + RenderedTargets.Clear(); + + if (Bitmap == null) + CreateBitmap(RenderedRectangle); + + foreach (var renderTarget in renderTargets) + { + 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(); + } + } + } + + public Scale RenderedScale { get; private set; } + + private void CreateBitmap(Rectangle rectangle) + { + 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) + { + return (int) Math.Round(number, MidpointRounding.AwayFromZero); + } + + /// + public virtual void PerformFinalize() + { + } + + public void Dispose() + { + Bitmap?.Dispose(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs b/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs index f93cecb82..3cf61959b 100644 --- a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs +++ b/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs @@ -22,7 +22,7 @@ namespace Artemis.Core.RGB.NET var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.Y + l.AbsoluteLedRectangle.Size.Height) * scale, 4096); Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt())); } - + public SKBitmap Bitmap { get; private set; } public Color ManipulateColor(Rectangle rectangle, BrushRenderTarget renderTarget, Color color) diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 606f5ccb3..9e0782079 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -89,14 +89,17 @@ namespace Artemis.Core.Services module.Update(args.DeltaTime); } - // If there is no ready graphics decorator, skip the frame - lock (_rgbService.GraphicsDecorator) + // If there is no ready bitmap brush, skip the frame + if (_rgbService.BitmapBrush == null) + return; + + lock (_rgbService.BitmapBrush) { - if (_rgbService.GraphicsDecorator?.Bitmap == null) + if (_rgbService.BitmapBrush.Bitmap == null) return; // Render all active modules - using (var canvas = new SKCanvas(_rgbService.GraphicsDecorator.Bitmap)) + using (var canvas = new SKCanvas(_rgbService.BitmapBrush.Bitmap)) { canvas.Clear(new SKColor(0, 0, 0)); lock (_modules) @@ -117,7 +120,7 @@ namespace Artemis.Core.Services private void SurfaceOnUpdated(UpdatedEventArgs args) { - OnFrameRendered(new FrameRenderedEventArgs(_rgbService.GraphicsDecorator, _rgbService.Surface)); + OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush, _rgbService.Surface)); } protected virtual void OnFrameRendering(FrameRenderingEventArgs e) diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index ebf7298c0..1148bbaa1 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -9,7 +9,7 @@ namespace Artemis.Core.Services.Interfaces public interface IRgbService : IArtemisService { RGBSurface Surface { get; set; } - GraphicsDecorator GraphicsDecorator { get; } + BitmapBrush BitmapBrush { get; } IReadOnlyCollection LoadedDevices { get; } void AddDeviceProvider(IRGBDeviceProvider deviceProvider); @@ -25,6 +25,6 @@ namespace Artemis.Core.Services.Interfaces /// event EventHandler DeviceReloaded; - void UpdateGraphicsDecorator(); + void UpdateSurfaceLedGroup(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index fc2445c71..82040faaa 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -21,7 +21,7 @@ namespace Artemis.Core.Services private readonly PluginSetting _renderScaleSetting; private readonly PluginSetting _targetFrameRateSetting; private readonly TimerUpdateTrigger _updateTrigger; - private ListLedGroup _background; + private ListLedGroup _surfaceLedGroup; internal RgbService(ILogger logger, ISettingsService settingsService) { @@ -30,7 +30,6 @@ namespace Artemis.Core.Services _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); Surface = RGBSurface.Instance; - GraphicsDecorator = new GraphicsDecorator(new ListLedGroup(), 1); // Let's throw these for now Surface.Exception += SurfaceOnException; @@ -44,7 +43,7 @@ namespace Artemis.Core.Services /// public RGBSurface Surface { get; set; } - public GraphicsDecorator GraphicsDecorator { get; private set; } + public BitmapBrush BitmapBrush { get; private set; } public IReadOnlyCollection LoadedDevices => _loadedDevices.AsReadOnly(); @@ -57,7 +56,7 @@ namespace Artemis.Core.Services _logger.Warning("RgbDevice provider {deviceProvider} has no devices", deviceProvider.GetType().Name); return; } - + foreach (var surfaceDevice in deviceProvider.Devices) { if (!_loadedDevices.Contains(surfaceDevice)) @@ -80,7 +79,7 @@ namespace Artemis.Core.Services private void RenderScaleSettingOnSettingChanged(object sender, EventArgs e) { - UpdateGraphicsDecorator(); + UpdateSurfaceLedGroup(); } private void TargetFrameRateSettingOnSettingChanged(object sender, EventArgs e) @@ -99,23 +98,28 @@ namespace Artemis.Core.Services public event EventHandler DeviceLoaded; public event EventHandler DeviceReloaded; - public void UpdateGraphicsDecorator() + public void UpdateSurfaceLedGroup() { - lock (GraphicsDecorator) + if (_surfaceLedGroup == null) { - // Clean up the old background if present - if (_background != null) - { - _background.Brush?.RemoveAllDecorators(); - _background.Detach(); - } + // Apply the application wide brush and decorator + BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value)); + _surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush }; + return; + } + + lock (_surfaceLedGroup) + { + // Clean up the old background + _surfaceLedGroup.Detach(); // Apply the application wide brush and decorator - _background = new ListLedGroup(Surface.Leds) {Brush = new SolidColorBrush(new Color(255, 255, 255, 255))}; - GraphicsDecorator = new GraphicsDecorator(_background, _renderScaleSetting.Value); - _background.Brush.RemoveAllDecorators(); - - _background.Brush.AddDecorator(GraphicsDecorator); + BitmapBrush.Scale = new Scale(_renderScaleSetting.Value); + _surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush }; + } + lock (BitmapBrush) + { + } } diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index f2a6e75b4..40704edf8 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -93,7 +93,7 @@ namespace Artemis.Core.Services.Storage } // Update the RGB service's graphics decorator to work with the new surface entity - _rgbService.UpdateGraphicsDecorator(); + _rgbService.UpdateSurfaceLedGroup(); OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurface)); } @@ -111,7 +111,7 @@ namespace Artemis.Core.Services.Storage } _surfaceRepository.Save(surface.SurfaceEntity); - _rgbService.UpdateGraphicsDecorator(); + _rgbService.UpdateSurfaceLedGroup(); OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surface)); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml index 5097631f3..0bd189dab 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml @@ -22,6 +22,9 @@ + + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs index 7746d7f43..7063d8888 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs @@ -40,16 +40,16 @@ namespace Artemis.UI.Screens.Settings.Debug { Execute.PostToUIThread(() => { - if (e.GraphicsDecorator.Bitmap == null) + if (e.BitmapBrush.Bitmap == null) return; if (!(CurrentFrame is WriteableBitmap writeableBitmap)) { - CurrentFrame = e.GraphicsDecorator.Bitmap.ToWriteableBitmap(); + CurrentFrame = e.BitmapBrush.Bitmap.ToWriteableBitmap(); return; } - using (var skiaImage = SKImage.FromPixels(e.GraphicsDecorator.Bitmap.PeekPixels())) + using (var skiaImage = SKImage.FromPixels(e.BitmapBrush.Bitmap.PeekPixels())) { var info = new SKImageInfo(skiaImage.Width, skiaImage.Height); writeableBitmap.Lock(); diff --git a/src/Artemis.UI/Screens/Settings/SettingsView.xaml b/src/Artemis.UI/Screens/Settings/SettingsView.xaml index 5c6de26a3..55d956ea7 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/SettingsView.xaml @@ -5,8 +5,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:xaml="https://github.com/canton7/Stylet" + xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" - d:DataContext="{d:DesignInstance screens:SettingsViewModel}" + d:DataContext="{d:DesignInstance settings:SettingsViewModel}" d:DesignHeight="600" d:DesignWidth="600"> @@ -16,71 +18,206 @@ - - - General - - General settings like start up with Windows etc. - - - - - - - - - + + + + + + General + + + + + + + + + + + + + Start up with Windows + + + + + + - Render scale - + + + + + + + + + + + Start up with Windows minimized + + + + + + - Target framerate - - + + + + + + + + + + + Debugger + + Use the debugger to see the raw image Artemis is rendering on the surface. + + + + + + + + - - + + Rendering + + + + + + + + + + + + + Render scale + + Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces. + + + + + + + - Devices - - A list of devices and options to disable them - - - - - - - - - - - - - + + + + + + + + + + + Target framerate + + Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations. + + + + + + + + + - - Plugins - - A list of plugins and options to disable them - - - - + + + + Below you view and manage your plugins. To find and install new plugins use the workshop (TODO). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Below you view and manage the devices that were detected by Artemis + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index 86e82a38e..83695cebf 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -1,4 +1,7 @@ -using Artemis.Core.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Services; using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Settings.Debug; @@ -34,10 +37,32 @@ namespace Artemis.UI.Screens.Settings _deviceSettingsViewModelFactory = deviceSettingsViewModelFactory; DeviceSettingsViewModels = new BindableCollection(); + + RenderScales = new List> {new Tuple("10%", 0.1)}; + for (var i = 25; i <= 100; i += 25) + RenderScales.Add(new Tuple(i + "%", i / 100.0)); + + TargetFrameRates = new List>(); + for (var i = 10; i <= 30; i += 5) + TargetFrameRates.Add(new Tuple(i + " FPS", i)); } public BindableCollection DeviceSettingsViewModels { get; set; } + public List> RenderScales { get; set; } + + public Tuple SelectedRenderScale + { + get => RenderScales.FirstOrDefault(s => Math.Abs(s.Item2 - RenderScale) < 0.01); + set => RenderScale = value.Item2; + } + + public Tuple SelectedTargetFrameRate + { + get => TargetFrameRates.FirstOrDefault(t => Math.Abs(t.Item2 - TargetFrameRate) < 0.01); + set => TargetFrameRate = value.Item2; + } + public double RenderScale { get => _settingsService.GetSetting("Core.RenderScale", 1.0).Value; @@ -48,6 +73,8 @@ namespace Artemis.UI.Screens.Settings } } + public List> TargetFrameRates { get; set; } + public int TargetFrameRate { get => _settingsService.GetSetting("Core.TargetFrameRate", 25).Value; diff --git a/src/Artemis.UI/Screens/Splash/SplashView.xaml b/src/Artemis.UI/Screens/Splash/SplashView.xaml index e26961e6f..4b41f103f 100644 --- a/src/Artemis.UI/Screens/Splash/SplashView.xaml +++ b/src/Artemis.UI/Screens/Splash/SplashView.xaml @@ -20,7 +20,7 @@ - + Artemis is initializing...