diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index c5242f460..61acb02d0 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -144,6 +144,7 @@ + diff --git a/src/Artemis.Core/Events/DeviceEventArgs.cs b/src/Artemis.Core/Events/DeviceEventArgs.cs index dc9f6dff9..1401cee36 100644 --- a/src/Artemis.Core/Events/DeviceEventArgs.cs +++ b/src/Artemis.Core/Events/DeviceEventArgs.cs @@ -5,10 +5,6 @@ namespace Artemis.Core.Events { public class DeviceEventArgs : EventArgs { - public DeviceEventArgs() - { - } - public DeviceEventArgs(IRGBDevice device) { Device = device; diff --git a/src/Artemis.Core/Events/FrameEventArgs.cs b/src/Artemis.Core/Events/FrameEventArgs.cs new file mode 100644 index 000000000..b21cd9e91 --- /dev/null +++ b/src/Artemis.Core/Events/FrameEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using Artemis.Core.Plugins.Abstract; +using RGB.NET.Core; + +namespace Artemis.Core.Events +{ + public class FrameEventArgs : EventArgs + { + public FrameEventArgs(List modules, Bitmap bitmap, double deltaTime, RGBSurface rgbSurface) + { + Modules = modules; + Bitmap = bitmap; + DeltaTime = deltaTime; + RgbSurface = rgbSurface; + } + + public List Modules { get; } + public Bitmap Bitmap { get; } + public double DeltaTime { get; } + public RGBSurface RgbSurface { get; } + } +} \ 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 de5ee5225..f1f3187a6 100644 --- a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs +++ b/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs @@ -2,27 +2,24 @@ using System.Drawing; using System.Linq; using RGB.NET.Core; -using RGB.NET.Groups; using Color = RGB.NET.Core.Color; using Rectangle = RGB.NET.Core.Rectangle; namespace Artemis.Core.RGB.NET { - public class GraphicsDecorator : AbstractDecorator, IBrushDecorator + public class GraphicsDecorator : AbstractDecorator, IBrushDecorator, IDisposable { - private readonly DirectBitmap _bitmap; + private DirectBitmap _bitmap; - public GraphicsDecorator(ListLedGroup ledGroup) + public GraphicsDecorator(ILedGroup ledGroup) { var leds = ledGroup.GetLeds().ToList(); if (!leds.Any()) - { _bitmap = null; - } else { - var width = leds.Max(l => l.AbsoluteLedRectangle.X + l.AbsoluteLedRectangle.Width); - var height = leds.Max(l => l.AbsoluteLedRectangle.Y + l.AbsoluteLedRectangle.Height); + var width = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.X + l.AbsoluteLedRectangle.Width), 2000); + var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Y + l.AbsoluteLedRectangle.Height), 2000); _bitmap = new DirectBitmap((int) width, (int) height); } @@ -40,9 +37,25 @@ namespace Artemis.Core.RGB.NET return new Color(0, 0, 0); } + public override void OnDetached(IDecoratable decoratable) + { + Dispose(); + } + + public void Dispose() + { + _bitmap?.Dispose(); + _bitmap = null; + } + public Graphics GetGraphics() { return _bitmap == null ? null : Graphics.FromImage(_bitmap.Bitmap); } + + public Bitmap GetBitmap() + { + return _bitmap?.Bitmap; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index be72126ef..7be6e9fe2 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -1,7 +1,8 @@ using System; +using System.Linq; using System.Threading.Tasks; +using Artemis.Core.Events; using Artemis.Core.Exceptions; -using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage; @@ -46,11 +47,17 @@ namespace Artemis.Core.Services throw new ArtemisCoreException("Cannot initialize the core as it is already initialized."); _logger.Information("Initializing Artemis Core version {version}", typeof(CoreService).Assembly.GetName().Version); - + // Initialize the services await Task.Run(() => _pluginService.CopyBuiltInPlugins()); await Task.Run(() => _pluginService.LoadPlugins()); + var surfaceConfig = _surfaceService.ActiveSurfaceConfiguration; + if (surfaceConfig != null) + _logger.Information("Initialized with active surface configuration {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.Guid); + else + _logger.Information("Initialized without an active surface configuration"); + OnInitialized(); } @@ -79,6 +86,8 @@ namespace Artemis.Core.Services foreach (var module in modules) module.Render(args.DeltaTime, _rgbService.Surface, g); } + + OnFrameRendered(new FrameEventArgs(modules, _rgbService.GraphicsDecorator.GetBitmap(), args.DeltaTime, _rgbService.Surface)); } catch (Exception e) { @@ -89,6 +98,7 @@ namespace Artemis.Core.Services #region Events public event EventHandler Initialized; + public event EventHandler FrameRendered; private void OnInitialized() { @@ -97,5 +107,10 @@ namespace Artemis.Core.Services } #endregion + + protected virtual void OnFrameRendered(FrameEventArgs e) + { + FrameRendered?.Invoke(this, e); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index aba2228cd..c82b1eb6c 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -1,4 +1,5 @@ using System; +using Artemis.Core.Events; namespace Artemis.Core.Services.Interfaces { @@ -13,5 +14,10 @@ namespace Artemis.Core.Services.Interfaces /// Occurs the core has finished initializing /// event EventHandler Initialized; + + /// + /// Occurs whenever a frame has finished rendering + /// + event EventHandler FrameRendered; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 2cbb51c79..8544aaea1 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -30,7 +30,7 @@ namespace Artemis.Core.Services Surface.Exception += SurfaceOnException; _loadedDevices = new List(); - _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / 30}; + _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / 25}; Surface.RegisterUpdateTrigger(_updateTrigger); } @@ -97,10 +97,12 @@ namespace Artemis.Core.Services public void UpdateGraphicsDecorator() { + // TODO: Create new one first, then clean up the old one for a smoother transition + // Clean up the old background if present if (_background != null) { - _background.RemoveAllDecorators(); + _background.Brush?.RemoveAllDecorators(); _background.Detach(); } diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index fc78c4c2e..900359b9c 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -140,8 +140,7 @@ namespace Artemis.Core.Services.Storage MatchDeviceConfiguration(e.Device, surfaceConfiguration); } - foreach (var deviceConfiguration in ActiveSurfaceConfiguration.DeviceConfigurations) - deviceConfiguration.ApplyToDevice(); + UpdateSurfaceConfiguration(ActiveSurfaceConfiguration, true); } #endregion @@ -169,7 +168,7 @@ namespace Artemis.Core.Services.Storage // When all surface configs are loaded, apply the active surface config var active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive); if (active != null) - ActiveSurfaceConfiguration = active; + SetActiveSurfaceConfiguration(active); } #endregion diff --git a/src/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Artemis.Plugins.Modules.General/GeneralModule.cs index 31652b650..a9293a0c4 100644 --- a/src/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -3,32 +3,30 @@ using System.Drawing; using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; -using Artemis.Core.Services.Interfaces; using Artemis.Plugins.Modules.General.ViewModels; using RGB.NET.Core; using Stylet; using Color = System.Drawing.Color; +using Rectangle = System.Drawing.Rectangle; namespace Artemis.Plugins.Modules.General { public class GeneralModule : Module { private readonly PluginSettings _settings; - private readonly RGBSurface _surface; - private Color _color; - public GeneralModule(PluginInfo pluginInfo, IRgbService rgbService, PluginSettings settings) : base(pluginInfo) + public GeneralModule(PluginInfo pluginInfo, PluginSettings settings) : base(pluginInfo) { _settings = settings; DisplayName = "General"; ExpandsMainDataModel = true; - _surface = rgbService.Surface; - var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); - _color = ColorHelpers.GetRandomRainbowColor(); + Colors = new Color[1000]; } + public Color[] Colors { get; set; } + public override void EnablePlugin() { } @@ -39,16 +37,59 @@ namespace Artemis.Plugins.Modules.General public override void Update(double deltaTime) { - _color = ColorHelpers.ShiftColor(_color, (int) (deltaTime * 200)); + for (var i = 0; i < Colors.Length; i++) + { + var color = Colors[i]; + Colors[i] = ColorHelpers.ShiftColor(color, (int) (deltaTime * 200)); + } } public override void Render(double deltaTime, RGBSurface surface, Graphics graphics) { - // Lets do this in the least performant way possible - foreach (var surfaceLed in _surface.Leds) + // Per-device coloring, slower + // RenderPerDevice(surface, graphics); + + // Per-LED coloring, slowest + RenderPerLed(surface, graphics); + } + + public void RenderFullSurface(RGBSurface surface, Graphics graphics) + { + } + + public void RenderPerDevice(RGBSurface surface, Graphics graphics) + { + var index = 0; + foreach (var device in surface.Devices) { - var rectangle = surfaceLed.AbsoluteLedRectangle.ToDrawingRectangle(); - graphics.FillRectangle(new SolidBrush(_color), rectangle); + var color = Colors[index]; + if (color.A == 0) + { + color = ColorHelpers.GetRandomRainbowColor(); + Colors[index] = color; + } + + var rectangle = new Rectangle((int) device.Location.X, (int) device.Location.Y, (int) device.Size.Width, (int) device.Size.Height); + graphics.FillRectangle(new SolidBrush(color), rectangle); + index++; + } + } + + public void RenderPerLed(RGBSurface surface, Graphics graphics) + { + var index = 0; + foreach (var led in surface.Leds) + { + var color = Colors[index]; + if (color.A == 0) + { + color = ColorHelpers.GetRandomRainbowColor(); + Colors[index] = color; + } + + var rectangle = led.AbsoluteLedRectangle.ToDrawingRectangle(); + graphics.FillRectangle(new SolidBrush(color), rectangle); + index++; } } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index c98b1afcc..6938f4f6d 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -165,6 +165,7 @@ + @@ -186,6 +187,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs index c591f7176..8d5b5bc86 100644 --- a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs +++ b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs @@ -51,7 +51,9 @@ namespace Artemis.UI.ViewModels.Controls.SurfaceEditor public IReadOnlyCollection Leds => _leds.AsReadOnly(); - public Rect DeviceRectangle => new Rect(X, Y, DeviceConfiguration.Device.Size.Width, DeviceConfiguration.Device.Size.Height); + public Rect DeviceRectangle => DeviceConfiguration.Device == null + ? new Rect() + : new Rect(X, Y, DeviceConfiguration.Device.Size.Width, DeviceConfiguration.Device.Size.Height); public void StartMouseDrag(Point mouseStartPosition) { diff --git a/src/Artemis.UI/ViewModels/Screens/DebugViewModel.cs b/src/Artemis.UI/ViewModels/Screens/DebugViewModel.cs new file mode 100644 index 000000000..5e07755b0 --- /dev/null +++ b/src/Artemis.UI/ViewModels/Screens/DebugViewModel.cs @@ -0,0 +1,68 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Artemis.Core.Events; +using Artemis.Core.Services.Interfaces; +using Stylet; + +namespace Artemis.UI.ViewModels.Screens +{ + public class DebugViewModel : Screen + { + private readonly ICoreService _coreService; + private readonly IRgbService _rgbService; + + public DebugViewModel(ICoreService coreService, IRgbService rgbService) + { + _coreService = coreService; + _rgbService = rgbService; + + _coreService.FrameRendered += CoreServiceOnFrameRendered; + } + + public ImageSource CurrentFrame { get; set; } + public double CurrentFps { get; set; } + + public void ForceGarbageCollection() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + private void CoreServiceOnFrameRendered(object sender, FrameEventArgs e) + { + var imageSource = ImageSourceFromBitmap(e.Bitmap); + imageSource.Freeze(); + CurrentFps = Math.Round(1.0 / e.DeltaTime, 2); + Execute.OnUIThread(() => CurrentFrame = imageSource); + } + + protected override void OnClose() + { + _coreService.FrameRendered -= CoreServiceOnFrameRendered; + base.OnClose(); + } + + // This is much quicker than saving the bitmap into a memory stream and converting it + private static ImageSource ImageSourceFromBitmap(Bitmap bmp) + { + var handle = bmp.GetHbitmap(); + try + { + return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); + } + finally + { + DeleteObject(handle); + } + } + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteObject([In] IntPtr hObject); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/ViewModels/Screens/SettingsViewModel.cs b/src/Artemis.UI/ViewModels/Screens/SettingsViewModel.cs index 905102932..432fb0e8e 100644 --- a/src/Artemis.UI/ViewModels/Screens/SettingsViewModel.cs +++ b/src/Artemis.UI/ViewModels/Screens/SettingsViewModel.cs @@ -2,14 +2,20 @@ using Artemis.Core.Services.Interfaces; using Artemis.UI.ViewModels.Controls.Settings; using Artemis.UI.ViewModels.Interfaces; +using Ninject; using Stylet; namespace Artemis.UI.ViewModels.Screens { public class SettingsViewModel : Screen, ISettingsViewModel { - public SettingsViewModel(IRgbService rgbService) + private readonly IKernel _kernel; + private readonly IWindowManager _windowManager; + + public SettingsViewModel(IKernel kernel, IRgbService rgbService, IWindowManager windowManager) { + _kernel = kernel; + _windowManager = windowManager; DeviceSettingsViewModels = new BindableCollection(); foreach (var device in rgbService.LoadedDevices) DeviceSettingsViewModels.Add(new RgbDeviceSettingsViewModel(device)); @@ -20,6 +26,11 @@ namespace Artemis.UI.ViewModels.Screens public BindableCollection DeviceSettingsViewModels { get; set; } public string Title => "Settings"; + public void ShowDebugger() + { + _windowManager.ShowWindow(_kernel.Get()); + } + private void UpdateDevices(object sender, DeviceEventArgs deviceEventArgs) { DeviceSettingsViewModels.Add(new RgbDeviceSettingsViewModel(deviceEventArgs.Device)); diff --git a/src/Artemis.UI/Views/Screens/DebugView.xaml b/src/Artemis.UI/Views/Screens/DebugView.xaml new file mode 100644 index 000000000..145a67e84 --- /dev/null +++ b/src/Artemis.UI/Views/Screens/DebugView.xaml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + In this window you can view the inner workings of Artemis. + Please not that having this window open can have a performance impact on your system. + + + + + + + + + + This image shows what is being rendered and dispatched to RGB.NET + + + FPS: + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Views/Screens/SettingsView.xaml b/src/Artemis.UI/Views/Screens/SettingsView.xaml index f80da523b..e2bf289bd 100644 --- a/src/Artemis.UI/Views/Screens/SettingsView.xaml +++ b/src/Artemis.UI/Views/Screens/SettingsView.xaml @@ -1,6 +1,7 @@  General - + General settings like start up with Windows etc. - + + + Devices