diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index e1b091e70..4a90f4988 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -110,8 +110,11 @@ ..\packages\Serilog.Sinks.File.4.0.0\lib\net45\Serilog.Sinks.File.dll - - ..\packages\Stylet.1.2.0\lib\net45\Stylet.dll + + ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll + + + ..\packages\Stylet.1.3.0\lib\net45\Stylet.dll @@ -125,20 +128,19 @@ ..\packages\System.Diagnostics.DiagnosticSource.4.5.1\lib\net46\System.Diagnostics.DiagnosticSource.dll - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - ..\packages\System.Numerics.Vectors.4.6.0-preview5.19224.8\lib\net46\System.Numerics.Vectors.dll + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll ..\packages\System.Reflection.Metadata.1.7.0-preview8.19405.3\lib\netstandard2.0\System.Reflection.Metadata.dll - ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0-preview8.19405.3\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll @@ -156,6 +158,7 @@ + @@ -189,7 +192,6 @@ - @@ -230,5 +232,7 @@ + + \ No newline at end of file diff --git a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs index 144482c9c..d88bb89b7 100644 --- a/src/Artemis.Core/Events/FrameRenderedEventArgs.cs +++ b/src/Artemis.Core/Events/FrameRenderedEventArgs.cs @@ -1,18 +1,19 @@ using System; using System.Drawing; +using Artemis.Core.RGB.NET; using RGB.NET.Core; namespace Artemis.Core.Events { public class FrameRenderedEventArgs : EventArgs { - public FrameRenderedEventArgs(Bitmap bitmap, RGBSurface rgbSurface) + public FrameRenderedEventArgs(GraphicsDecorator graphicsDecorator, RGBSurface rgbSurface) { - Bitmap = bitmap; + GraphicsDecorator = graphicsDecorator; RgbSurface = rgbSurface; } - - public Bitmap Bitmap { get; } + + public GraphicsDecorator GraphicsDecorator { get; } public RGBSurface RgbSurface { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Events/FrameRenderingEventArgs.cs b/src/Artemis.Core/Events/FrameRenderingEventArgs.cs index 70ed191ac..05d05eff0 100644 --- a/src/Artemis.Core/Events/FrameRenderingEventArgs.cs +++ b/src/Artemis.Core/Events/FrameRenderingEventArgs.cs @@ -1,23 +1,23 @@ using System; using System.Collections.Generic; -using System.Drawing; using Artemis.Core.Plugins.Abstract; using RGB.NET.Core; +using SkiaSharp; namespace Artemis.Core.Events { public class FrameRenderingEventArgs : EventArgs { - public FrameRenderingEventArgs(List modules, Bitmap bitmap, double deltaTime, RGBSurface rgbSurface) + public FrameRenderingEventArgs(List modules, SKCanvas canvas, double deltaTime, RGBSurface rgbSurface) { Modules = modules; - Bitmap = bitmap; + Canvas = canvas; DeltaTime = deltaTime; RgbSurface = rgbSurface; } public List Modules { get; } - public Bitmap Bitmap { get; } + public SKCanvas Canvas { get; } public double DeltaTime { get; } public RGBSurface RgbSurface { get; } } diff --git a/src/Artemis.Core/Extensions/DoubleExtensions.cs b/src/Artemis.Core/Extensions/DoubleExtensions.cs new file mode 100644 index 000000000..7eda9aa26 --- /dev/null +++ b/src/Artemis.Core/Extensions/DoubleExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Artemis.Core.Extensions +{ + public static class DoubleExtensions + { + public static int RoundToInt(this double number) + { + return (int) Math.Round(number, MidpointRounding.AwayFromZero); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/RgbRectangleExtensions.cs b/src/Artemis.Core/Extensions/RgbRectangleExtensions.cs index bbc3c21e1..3910dc9a0 100644 --- a/src/Artemis.Core/Extensions/RgbRectangleExtensions.cs +++ b/src/Artemis.Core/Extensions/RgbRectangleExtensions.cs @@ -1,16 +1,17 @@ -using System.Drawing; +using RGB.NET.Core; +using SkiaSharp; namespace Artemis.Core.Extensions { public static class RgbRectangleExtensions { - public static Rectangle ToDrawingRectangle(this global::RGB.NET.Core.Rectangle rectangle, double scale) + public static SKRect ToSKRect(this Rectangle rectangle, double scale) { - return new Rectangle( - (int) (rectangle.Location.X * scale), - (int) (rectangle.Location.Y * scale), - (int) (rectangle.Size.Width * scale), - (int) (rectangle.Size.Height * scale) + return SKRect.Create( + (rectangle.Location.X * scale).RoundToInt(), + (rectangle.Location.Y * scale).RoundToInt(), + (rectangle.Size.Width * scale).RoundToInt(), + (rectangle.Size.Height * scale).RoundToInt() ); } } diff --git a/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs b/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs index 8fec27918..ca259edd1 100644 --- a/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Drawing; using System.Linq; using Artemis.Core.Models.Surface; +using SkiaSharp; using Stylet; namespace Artemis.Core.Models.Profile.Abstract @@ -45,7 +45,7 @@ namespace Artemis.Core.Models.Profile.Abstract /// /// Renders the element /// - public abstract void Render(double deltaTime, ArtemisSurface surface, Graphics graphics); + public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas); /// /// Applies the profile element's properties to the underlying storage entity diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 41fde07ae..5d72238ee 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -1,9 +1,9 @@ using System; -using System.Drawing; using System.Linq; using Artemis.Core.Models.Profile.Abstract; using Artemis.Core.Models.Surface; using Artemis.Storage.Entities.Profile; +using SkiaSharp; namespace Artemis.Core.Models.Profile { @@ -56,11 +56,11 @@ namespace Artemis.Core.Models.Profile profileElement.Update(deltaTime); } - public override void Render(double deltaTime, ArtemisSurface surface, Graphics graphics) + public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) { // Folders don't render but their children do foreach (var profileElement in Children) - profileElement.Render(deltaTime, surface, graphics); + profileElement.Render(deltaTime, surface, canvas); } public Folder AddFolder(string name) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index aee7eab8b..15ec80610 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Drawing; -using System.Drawing.Drawing2D; using System.Linq; using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.Abstract; @@ -10,6 +8,7 @@ using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; +using SkiaSharp; namespace Artemis.Core.Models.Profile { @@ -50,8 +49,9 @@ namespace Artemis.Core.Models.Profile public ReadOnlyCollection Leds => _leds.AsReadOnly(); public ReadOnlyCollection LayerElements => _layerElements.AsReadOnly(); - public Rectangle RenderRectangle { get; set; } - public GraphicsPath RenderPath { get; set; } + public SKRect RenderRectangle { get; set; } + public SKRect AbsoluteRenderRectangle { get; set; } + public SKPath RenderPath { get; set; } public override void Update(double deltaTime) { @@ -59,20 +59,44 @@ namespace Artemis.Core.Models.Profile layerElement.Update(deltaTime); } - public override void Render(double deltaTime, ArtemisSurface surface, Graphics graphics) + public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) { - graphics.SetClip(RenderPath); + if (RenderRectangle == null || AbsoluteRenderRectangle == null || RenderPath == null) + return; + + canvas.Save(); foreach (var layerElement in LayerElements) - layerElement.RenderPreProcess(surface, graphics); + layerElement.RenderPreProcess(surface, canvas); - foreach (var layerElement in LayerElements) - layerElement.Render(surface, graphics); + using (var bitmap = new SKBitmap(new SKImageInfo((int) RenderRectangle.Width, (int) RenderRectangle.Height))) + using (var layerCanvas = new SKCanvas(bitmap)) + { + layerCanvas.Clear(); - foreach (var layerElement in LayerElements) - layerElement.RenderPostProcess(surface, graphics); + foreach (var layerElement in LayerElements) + layerElement.Render(surface, layerCanvas); - graphics.ResetClip(); + var baseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat, SKMatrix.MakeTranslation(RenderRectangle.Left, RenderRectangle.Top)); + foreach (var layerElement in LayerElements) + { + var newBaseShader = layerElement.RenderPostProcess(surface, bitmap, baseShader); + if (newBaseShader == null) + continue; + + // Dispose the old base shader if the layer element provided a new one + if (!ReferenceEquals(baseShader, newBaseShader)) + baseShader.Dispose(); + + baseShader = newBaseShader; + } + + //canvas.ClipPath(RenderPath); + canvas.DrawRect(RenderRectangle, new SKPaint {Shader = baseShader, FilterQuality = SKFilterQuality.Low}); + baseShader.Dispose(); + } + + canvas.Restore(); } internal override void ApplyToEntity() @@ -167,21 +191,36 @@ namespace Artemis.Core.Models.Profile } // Determine to top-left and bottom-right - var minX = Leds.Min(l => l.AbsoluteRenderRectangle.X); - var minY = Leds.Min(l => l.AbsoluteRenderRectangle.Y); - var maxX = Leds.Max(l => l.AbsoluteRenderRectangle.X + l.AbsoluteRenderRectangle.Width); - var maxY = Leds.Max(l => l.AbsoluteRenderRectangle.Y + l.AbsoluteRenderRectangle.Height); + var minX = Leds.Min(l => l.AbsoluteRenderRectangle.Left); + var minY = Leds.Min(l => l.AbsoluteRenderRectangle.Top); + var maxX = Leds.Max(l => l.AbsoluteRenderRectangle.Right); + var maxY = Leds.Max(l => l.AbsoluteRenderRectangle.Bottom); - RenderRectangle = new Rectangle(minX, minY, maxX - minX, maxY - minY); + RenderRectangle = SKRect.Create(minX, minY, maxX - minX, maxY - minY); + AbsoluteRenderRectangle = SKRect.Create(0, 0, maxX - minX, maxY - minY); + + var path = new SKPath {FillType = SKPathFillType.Winding}; + foreach (var artemisLed in Leds) + path.AddRect(artemisLed.AbsoluteRenderRectangle); - var path = new GraphicsPath(); - path.AddRectangles(Leds.Select(l => l.AbsoluteRenderRectangle).ToArray()); RenderPath = path; + OnRenderPropertiesUpdated(); } public override string ToString() { return $"Layer - {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } + + #region Events + + public event EventHandler RenderPropertiesUpdated; + + private void OnRenderPropertiesUpdated() + { + RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 2f6f17395..beea80582 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -1,11 +1,11 @@ using System; -using System.Drawing; using System.Linq; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.Abstract; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Models; using Artemis.Storage.Entities.Profile; +using SkiaSharp; namespace Artemis.Core.Models.Profile { @@ -56,7 +56,7 @@ namespace Artemis.Core.Models.Profile } } - public override void Render(double deltaTime, ArtemisSurface surface, Graphics graphics) + public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) { lock (this) { @@ -64,7 +64,7 @@ namespace Artemis.Core.Models.Profile throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (var profileElement in Children) - profileElement.Render(deltaTime, surface, graphics); + profileElement.Render(deltaTime, surface, canvas); } } diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index d276921a6..12fdc8269 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -1,13 +1,12 @@ using System; using System.Collections.ObjectModel; -using System.Drawing.Drawing2D; using System.Linq; using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; +using SkiaSharp; using Stylet; -using Rectangle = System.Drawing.Rectangle; namespace Artemis.Core.Models.Surface { @@ -38,8 +37,8 @@ namespace Artemis.Core.Models.Surface Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); } - public Rectangle RenderRectangle { get; private set; } - public GraphicsPath RenderPath { get; private set; } + public SKRect RenderRectangle { get; private set; } + public SKPath RenderPath { get; private set; } public IRGBDevice RgbDevice { get; } public Plugin Plugin { get; } @@ -95,11 +94,11 @@ namespace Artemis.Core.Models.Surface internal void CalculateRenderProperties() { - RenderRectangle = new Rectangle( - (int) Math.Round(RgbDevice.Location.X * Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbDevice.Location.Y * Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbDevice.DeviceRectangle.Size.Width * Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbDevice.DeviceRectangle.Size.Height * Surface.Scale, MidpointRounding.AwayFromZero) + RenderRectangle = SKRect.Create( + (RgbDevice.Location.X * Surface.Scale).RoundToInt(), + (RgbDevice.Location.Y * Surface.Scale).RoundToInt(), + (RgbDevice.DeviceRectangle.Size.Width * Surface.Scale).RoundToInt(), + (RgbDevice.DeviceRectangle.Size.Height * Surface.Scale).RoundToInt() ); if (!Leds.Any()) @@ -108,9 +107,10 @@ namespace Artemis.Core.Models.Surface foreach (var led in Leds) led.CalculateRenderRectangle(); - var path = new GraphicsPath(); - path.AddRectangles(Leds.Select(l => l.AbsoluteRenderRectangle).ToArray()); - path.FillMode = FillMode.Winding; + var path = new SKPath {FillType = SKPathFillType.Winding}; + foreach (var artemisLed in Leds) + path.AddRect(artemisLed.AbsoluteRenderRectangle); + RenderPath = path; } diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index fdc6f1162..5b5cb0910 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -1,7 +1,7 @@ -using System; +using Artemis.Core.Extensions; using RGB.NET.Core; +using SkiaSharp; using Stylet; -using Rectangle = System.Drawing.Rectangle; namespace Artemis.Core.Models.Surface { @@ -17,22 +17,22 @@ namespace Artemis.Core.Models.Surface public Led RgbLed { get; } public ArtemisDevice Device { get; } - public Rectangle RenderRectangle { get; private set; } - public Rectangle AbsoluteRenderRectangle { get; private set; } + public SKRect RenderRectangle { get; private set; } + public SKRect AbsoluteRenderRectangle { get; private set; } public void CalculateRenderRectangle() { - RenderRectangle = new Rectangle( - (int) Math.Round(RgbLed.LedRectangle.Location.X * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.LedRectangle.Location.Y * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.LedRectangle.Size.Width * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.LedRectangle.Size.Height * Device.Surface.Scale, MidpointRounding.AwayFromZero) + RenderRectangle = SKRect.Create( + (RgbLed.LedRectangle.Location.X * Device.Surface.Scale).RoundToInt(), + (RgbLed.LedRectangle.Location.Y * Device.Surface.Scale).RoundToInt(), + (RgbLed.LedRectangle.Size.Width * Device.Surface.Scale).RoundToInt(), + (RgbLed.LedRectangle.Size.Height * Device.Surface.Scale).RoundToInt() ); - AbsoluteRenderRectangle = new Rectangle( - (int) Math.Round(RgbLed.AbsoluteLedRectangle.Location.X * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.AbsoluteLedRectangle.Location.Y * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.AbsoluteLedRectangle.Size.Width * Device.Surface.Scale, MidpointRounding.AwayFromZero), - (int) Math.Round(RgbLed.AbsoluteLedRectangle.Size.Height * Device.Surface.Scale, MidpointRounding.AwayFromZero) + AbsoluteRenderRectangle = SKRect.Create( + (RgbLed.AbsoluteLedRectangle.Location.X * Device.Surface.Scale).RoundToInt(), + (RgbLed.AbsoluteLedRectangle.Location.Y * Device.Surface.Scale).RoundToInt(), + (RgbLed.AbsoluteLedRectangle.Size.Width * Device.Surface.Scale).RoundToInt(), + (RgbLed.AbsoluteLedRectangle.Size.Height * Device.Surface.Scale).RoundToInt() ); } } diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs index 3f085ed03..244e83ce6 100644 --- a/src/Artemis.Core/Plugins/Abstract/Module.cs +++ b/src/Artemis.Core/Plugins/Abstract/Module.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Drawing; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Models; +using SkiaSharp; namespace Artemis.Core.Plugins.Abstract { @@ -37,8 +37,8 @@ namespace Artemis.Core.Plugins.Abstract /// /// Time since the last render /// The RGB Surface to render to - /// - public abstract void Render(double deltaTime, ArtemisSurface surface, Graphics graphics); + /// + public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas); /// /// Called when the module's view model is being show, return view models here to create tabs for them diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 69d4f52ed..10954ed4f 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -1,9 +1,9 @@ using System; -using System.Drawing; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Models; +using SkiaSharp; namespace Artemis.Core.Plugins.Abstract { @@ -26,12 +26,12 @@ namespace Artemis.Core.Plugins.Abstract } /// - public override void Render(double deltaTime, ArtemisSurface surface, Graphics graphics) + public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) { lock (this) { // Render the profile - ActiveProfile?.Render(deltaTime, surface, graphics); + ActiveProfile?.Render(deltaTime, surface, canvas); } } diff --git a/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs b/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs index b4df0ab4e..2ec7a5d41 100644 --- a/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs +++ b/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs @@ -1,6 +1,6 @@ -using System.Drawing; -using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; +using SkiaSharp; namespace Artemis.Core.Plugins.LayerElement { @@ -27,21 +27,43 @@ namespace Artemis.Core.Plugins.LayerElement /// Called before rendering every frame, write your update logic here /// /// - public abstract void Update(double deltaTime); + public virtual void Update(double deltaTime) + { + } /// - /// Called before rendering, in the order configured on the layer + /// Allows you to perform rendering on the surface before any layer-clipping is applied + /// Called before rendering, in the order configured on the layer /// - public abstract void RenderPreProcess(ArtemisSurface surface, Graphics graphics); + /// The surface the layer is rendered on + /// The entire surface canvas + public virtual void RenderPreProcess(ArtemisSurface surface, SKCanvas canvas) + { + } /// - /// Called during rendering, in the order configured on the layer + /// The main method of rendering anything to the layer. The provided is specific to the layer + /// and matches it's width and height. + /// Called during rendering, in the order configured on the layer /// - public abstract void Render(ArtemisSurface surface, Graphics graphics); + /// The surface the layer is rendered on + /// The layer canvas + public virtual void Render(ArtemisSurface surface, SKCanvas canvas) + { + } /// - /// Called after rendering, in the order configured on the layer + /// Allows you to modify the used to draw the layer's on the + /// . + /// Called after rendering, in the order configured on the layer. /// - public abstract void RenderPostProcess(ArtemisSurface surface, Graphics graphics); + /// The surface the layer is rendered on + /// The bitmap created from the layer canvas + /// The current shader used to draw the bitmap on the surface canvas + /// The resulting shader used to draw the bitmap on the surface canvas + public virtual SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) + { + return shader; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/DirectBitmap.cs b/src/Artemis.Core/RGB.NET/DirectBitmap.cs deleted file mode 100644 index 9bc7ddce4..000000000 --- a/src/Artemis.Core/RGB.NET/DirectBitmap.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.InteropServices; - -namespace Artemis.Core.RGB.NET -{ - public class DirectBitmap : IDisposable - { - public DirectBitmap(int width, int height) - { - Width = width; - Height = height; - Bits = new int[width * height]; - BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); - Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); - } - - public Bitmap Bitmap { get; } - public int[] Bits { get; } - public bool Disposed { get; private set; } - public int Height { get; } - public int Width { get; } - - protected GCHandle BitsHandle { get; } - - public void Dispose() - { - if (Disposed) return; - Disposed = true; - Bitmap.Dispose(); - BitsHandle.Free(); - } - - public void SetPixel(int x, int y, Color colour) - { - var index = x + y * Width; - var col = colour.ToArgb(); - - Bits[index] = col; - } - - public Color GetPixel(int x, int y) - { - var index = x + y * Width; - if (index >= 0 && index - 1 <= Bits.Length) - { - var col = Bits[index]; - var result = Color.FromArgb(col); - - return result; - } - - return Color.Black; - } - } -} \ 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 25c126c96..f93cecb82 100644 --- a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs +++ b/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs @@ -1,16 +1,14 @@ using System; -using System.Drawing; using System.Linq; +using Artemis.Core.Extensions; using RGB.NET.Core; -using Color = RGB.NET.Core.Color; -using Rectangle = RGB.NET.Core.Rectangle; +using SkiaSharp; namespace Artemis.Core.RGB.NET { public class GraphicsDecorator : AbstractDecorator, IBrushDecorator, IDisposable { private readonly double _scale; - private DirectBitmap _bitmap; public GraphicsDecorator(ILedGroup ledGroup, double scale) { @@ -18,27 +16,25 @@ namespace Artemis.Core.RGB.NET var leds = ledGroup.GetLeds().ToList(); if (!leds.Any()) - _bitmap = null; - else - { - var width = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.X + l.AbsoluteLedRectangle.Size.Width) * scale, 4096); - var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.Y + l.AbsoluteLedRectangle.Size.Height) * scale, 4096); + return; - _bitmap = new DirectBitmap((int) width, (int) height); - } + var width = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.X + l.AbsoluteLedRectangle.Size.Width) * scale, 4096); + 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) { + if (Bitmap == null) + return new Color(0, 0, 0); + var x = renderTarget.Led.AbsoluteLedRectangle.Center.X * _scale; var y = renderTarget.Led.AbsoluteLedRectangle.Center.Y * _scale; - if (_bitmap != null && _bitmap.Width - 1 >= x && _bitmap.Height - 1 >= y) - { - var pixel = _bitmap.GetPixel((int) x, (int) y); - return new Color(pixel.A, pixel.R, pixel.G, pixel.B); - } - return new Color(0, 0, 0); + var pixel = Bitmap.GetPixel(RoundToInt(x), RoundToInt(y)); + return new Color(pixel.Alpha, pixel.Red, pixel.Green, pixel.Blue); } public override void OnDetached(IDecoratable decoratable) @@ -48,27 +44,13 @@ namespace Artemis.Core.RGB.NET public void Dispose() { - _bitmap?.Dispose(); - _bitmap = null; + Bitmap?.Dispose(); + Bitmap = null; } - public Graphics GetGraphics() + private int RoundToInt(double number) { - try - { - return _bitmap == null ? null : Graphics.FromImage(_bitmap.Bitmap); - } - catch (AccessViolationException) - { - // ignored - } - - return null; - } - - public Bitmap GetBitmap() - { - return _bitmap?.Bitmap; + return (int) Math.Round(number, MidpointRounding.AwayFromZero); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 5f099afd9..f784b2c65 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -8,7 +8,7 @@ using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; using RGB.NET.Core; using Serilog; -using Color = System.Drawing.Color; +using SkiaSharp; namespace Artemis.Core.Services { @@ -78,26 +78,25 @@ namespace Artemis.Core.Services module.Update(args.DeltaTime); } - // If there is no graphics decorator, skip the frame - if (_rgbService.GraphicsDecorator == null) - return; - - // Render all active modules - using (var g = _rgbService.GraphicsDecorator.GetGraphics()) + // If there is no ready graphics decorator, skip the frame + lock (_rgbService.GraphicsDecorator) { - // If there are no graphics, skip the frame - if (g == null) + if (_rgbService.GraphicsDecorator?.Bitmap == null) return; - g.Clear(Color.Black); - lock (_modules) + // Render all active modules + using (var canvas = new SKCanvas(_rgbService.GraphicsDecorator.Bitmap)) { - foreach (var module in _modules) - module.Render(args.DeltaTime, _surfaceService.ActiveSurface, g); + canvas.Clear(new SKColor(0, 0, 0)); + lock (_modules) + { + foreach (var module in _modules) + module.Render(args.DeltaTime, _surfaceService.ActiveSurface, canvas); + } + + OnFrameRendering(new FrameRenderingEventArgs(_modules, canvas, args.DeltaTime, _rgbService.Surface)); } } - - OnFrameRendering(new FrameRenderingEventArgs(_modules, _rgbService.GraphicsDecorator.GetBitmap(), args.DeltaTime, _rgbService.Surface)); } catch (Exception e) { @@ -107,7 +106,7 @@ namespace Artemis.Core.Services private void SurfaceOnUpdated(UpdatedEventArgs args) { - OnFrameRendered(new FrameRenderedEventArgs(_rgbService.GraphicsDecorator.GetBitmap(), _rgbService.Surface)); + OnFrameRendered(new FrameRenderedEventArgs(_rgbService.GraphicsDecorator, _rgbService.Surface)); } protected virtual void OnFrameRendering(FrameRenderingEventArgs e) diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index c511a1593..fab36dec0 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -1,8 +1,8 @@ -using System.Drawing; -using System.Threading.Tasks; +using System.Threading.Tasks; using Artemis.Core.Events; using Artemis.Core.Models.Surface; using Artemis.Core.Services.Interfaces; +using SkiaSharp; namespace Artemis.Core.Services { @@ -25,10 +25,7 @@ namespace Artemis.Core.Services // Draw a white overlay over the device void DrawOverlay(object sender, FrameRenderingEventArgs args) { - using (var g = Graphics.FromImage(args.Bitmap)) - { - g.FillPath(new SolidBrush(Color.White), device.RenderPath); - } + args.Canvas.DrawPath(device.RenderPath, new SKPaint {Color = new SKColor(255, 255, 255)}); } _coreService.FrameRendering += DrawOverlay; diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 34ef7ff62..5f6719938 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -30,6 +30,7 @@ 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; @@ -100,21 +101,22 @@ 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) + lock (GraphicsDecorator) { - _background.Brush?.RemoveAllDecorators(); - _background.Detach(); + // Clean up the old background if present + if (_background != null) + { + _background.Brush?.RemoveAllDecorators(); + _background.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); } - - // 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); } private void OnDeviceLoaded(DeviceEventArgs e) diff --git a/src/Artemis.Core/app.config b/src/Artemis.Core/app.config index 32dcf9823..29342a3c3 100644 --- a/src/Artemis.Core/app.config +++ b/src/Artemis.Core/app.config @@ -97,7 +97,7 @@ - + diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config index 2733bfdeb..b00ab366b 100644 --- a/src/Artemis.Core/packages.config +++ b/src/Artemis.Core/packages.config @@ -1,5 +1,4 @@  - @@ -15,13 +14,14 @@ - + + - + - + \ No newline at end of file diff --git a/src/Artemis.Plugins.Devices.Corsair/app.config b/src/Artemis.Plugins.Devices.Corsair/app.config index 9b3e8ff8d..7567ab9a5 100644 --- a/src/Artemis.Plugins.Devices.Corsair/app.config +++ b/src/Artemis.Plugins.Devices.Corsair/app.config @@ -13,7 +13,7 @@ - + diff --git a/src/Artemis.Plugins.Devices.Logitech/app.config b/src/Artemis.Plugins.Devices.Logitech/app.config index c8bc187aa..be637eaeb 100644 --- a/src/Artemis.Plugins.Devices.Logitech/app.config +++ b/src/Artemis.Plugins.Devices.Logitech/app.config @@ -21,7 +21,7 @@ - + diff --git a/src/Artemis.Plugins.LayerElements.Animations/AnimationLayerElementProvider.cs b/src/Artemis.Plugins.LayerElements.Animations/AnimationLayerElementProvider.cs new file mode 100644 index 000000000..11ed3bf09 --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/AnimationLayerElementProvider.cs @@ -0,0 +1,26 @@ +using Artemis.Core.Plugins.LayerElement; +using Artemis.Core.Plugins.Models; + +namespace Artemis.Plugins.LayerElements.Animations +{ + public class AnimationLayerElementProvider : LayerElementProvider + { + public AnimationLayerElementProvider(PluginInfo pluginInfo) : base(pluginInfo) + { + AddLayerElementDescriptor("Slide animation", "A sliding animation", "ArrowAll"); + AddLayerElementDescriptor("Rotation animation", "A rotation animation", "CropRotate"); + } + + public override void Dispose() + { + } + + public override void EnablePlugin() + { + } + + public override void DisablePlugin() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/Artemis.Plugins.LayerElements.Animations.csproj b/src/Artemis.Plugins.LayerElements.Animations/Artemis.Plugins.LayerElements.Animations.csproj new file mode 100644 index 000000000..d8e14bc49 --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/Artemis.Plugins.LayerElements.Animations.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE} + Library + Properties + Artemis.Plugins.LayerElements.Animations + Artemis.Plugins.LayerElements.Animations + v4.7.2 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll + + + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + + + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + + + + + + + + + {9b811f9b-86b9-4771-87af-72bae7078a36} + Artemis.Core + + + + + + + PreserveNewest + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + echo Copying plugin to Artemis.UI output directory +XCOPY "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)\Artemis.UI\$(OutDir)Plugins\$(ProjectName)" /s /q /i /y + + \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/Properties/AssemblyInfo.cs b/src/Artemis.Plugins.LayerElements.Animations/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3bd9cdaa0 --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Artemis.Plugins.LayerElements.Animations")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Artemis.Plugins.LayerElements.Animations")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6fe5ded5-d62e-4811-985f-644124fceefe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs b/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs new file mode 100644 index 000000000..ade9daa5f --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs @@ -0,0 +1,36 @@ +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Surface; +using Artemis.Core.Plugins.LayerElement; +using SkiaSharp; + +namespace Artemis.Plugins.LayerElements.Animations +{ + public class RotationLayerElement : LayerElement + { + public RotationLayerElement(Layer layer, LayerElementSettings settings, LayerElementDescriptor descriptor) : base(layer, settings, descriptor) + { + } + + public float Rotation { get; set; } + + public override LayerElementViewModel GetViewModel() + { + return null; + } + + public override void Update(double deltaTime) + { + Rotation += (float)(deltaTime * 100); + if (Rotation > 360) + Rotation = 0; + } + + public override SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) + { + var center = new SKPoint(Layer.AbsoluteRenderRectangle.MidX, Layer.AbsoluteRenderRectangle.MidY); + + // TODO Scale so that the rectangle is covered in every rotation, instead of just putting it at 2 + return SKShader.CreateLocalMatrix(SKShader.CreateLocalMatrix(shader, SKMatrix.MakeScale(2, 2, center.X, center.Y)), SKMatrix.MakeRotationDegrees(Rotation, center.X, center.Y)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs b/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs new file mode 100644 index 000000000..17f308e93 --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs @@ -0,0 +1,33 @@ +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Surface; +using Artemis.Core.Plugins.LayerElement; +using SkiaSharp; + +namespace Artemis.Plugins.LayerElements.Animations +{ + public class SlideLayerElement : LayerElement + { + public SlideLayerElement(Layer layer, LayerElementSettings settings, LayerElementDescriptor descriptor) : base(layer, settings, descriptor) + { + } + + public int MovePercentage { get; set; } + + public override LayerElementViewModel GetViewModel() + { + return null; + } + + public override void Update(double deltaTime) + { + MovePercentage++; + if (MovePercentage > 100) + MovePercentage = 0; + } + + public override SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) + { + return SKShader.CreateLocalMatrix(shader, SKMatrix.MakeTranslation(Layer.RenderRectangle.Width / 100 * MovePercentage * -1, 0)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/app.config b/src/Artemis.Plugins.LayerElements.Animations/app.config new file mode 100644 index 000000000..03a8d9e5d --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/app.config @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/packages.config b/src/Artemis.Plugins.LayerElements.Animations/packages.config new file mode 100644 index 000000000..97d48701a --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/plugin.json b/src/Artemis.Plugins.LayerElements.Animations/plugin.json new file mode 100644 index 000000000..a5e4bbb6f --- /dev/null +++ b/src/Artemis.Plugins.LayerElements.Animations/plugin.json @@ -0,0 +1,10 @@ +{ + "Guid": "a3b94380-6592-44f9-8717-3053ca9b4799", + "Name": "Animation layer elements", + "Version": { + "Major": 1, + "Minor": 0, + "Build": 0 + }, + "Main": "Artemis.Plugins.LayerElements.Animations.dll" +} \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerElements.Brush.csproj b/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerElements.Brush.csproj index f39c66274..7ef80e783 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerElements.Brush.csproj +++ b/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerElements.Brush.csproj @@ -13,6 +13,8 @@ 512 true + + true @@ -48,12 +50,28 @@ ..\..\..\RGB.NET\bin\net45\RGB.NET.Core.dll False - - ..\packages\Stylet.1.2.0\lib\net45\Stylet.dll + + ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll + False + + + ..\packages\Stylet.1.3.0\lib\net45\Stylet.dll + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + - + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll False @@ -100,4 +118,11 @@ echo Copying plugin to Artemis.UI output directory XCOPY "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)\Artemis.UI\$(OutDir)Plugins\$(ProjectName)" /s /q /i /y + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs index 37153dd13..607ed2f86 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs +++ b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs @@ -1,15 +1,42 @@ -using System.Drawing; +using System.Collections.Generic; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; +using SkiaSharp; namespace Artemis.Plugins.LayerElements.Brush { public class BrushLayerElement : LayerElement { + private SKShader _shader; + private List _testColors; + public BrushLayerElement(Layer layer, BrushLayerElementSettings settings, LayerElementDescriptor descriptor) : base(layer, settings, descriptor) { - Settings = settings ?? new BrushLayerElementSettings {Brush = new SolidBrush(Color.Red)}; + Settings = settings ?? new BrushLayerElementSettings(); + + _testColors = new List(); + for (var i = 0; i < 9; i++) + { + if (i != 8) + _testColors.Add(SKColor.FromHsv(i * 32, 100, 100)); + else + _testColors.Add(SKColor.FromHsv(0, 100, 100)); + } + + CreateShader(); + Layer.RenderPropertiesUpdated += (sender, args) => CreateShader(); + } + + private void CreateShader() + { + var center = new SKPoint(Layer.AbsoluteRenderRectangle.MidX, Layer.AbsoluteRenderRectangle.MidY); + var shader = SKShader.CreateSweepGradient(center, _testColors.ToArray(), null, SKShaderTileMode.Clamp, 0, 360); + + var oldShader = _shader; + _shader = shader; + + oldShader?.Dispose(); } public new BrushLayerElementSettings Settings { get; } @@ -19,22 +46,12 @@ namespace Artemis.Plugins.LayerElements.Brush return new BrushLayerElementViewModel(this); } - public override void Update(double deltaTime) - { - } - - public override void RenderPreProcess(ArtemisSurface surface, Graphics graphics) - { - } - - public override void Render(ArtemisSurface surface, Graphics graphics) - { - if (Settings?.Brush != null) - graphics.FillRectangle(Settings.Brush, Layer.RenderRectangle); - } - - public override void RenderPostProcess(ArtemisSurface surface, Graphics graphics) + public override void Render(ArtemisSurface surface, SKCanvas canvas) { + using (var paint = new SKPaint {Shader = _shader, FilterQuality = SKFilterQuality.Low}) + { + canvas.DrawRect(Layer.AbsoluteRenderRectangle, paint); + } } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElementSettings.cs b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElementSettings.cs index b274810e8..e2d40a752 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElementSettings.cs +++ b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElementSettings.cs @@ -1,9 +1,16 @@ -using Artemis.Core.Plugins.LayerElement; +using System.Collections.Generic; +using Artemis.Core.Plugins.LayerElement; +using SkiaSharp; namespace Artemis.Plugins.LayerElements.Brush { public class BrushLayerElementSettings : LayerElementSettings { - public System.Drawing.Brush Brush { get; set; } + public BrushLayerElementSettings() + { + Colors = new List(); + } + + public List Colors { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerTypes.Brush/app.config b/src/Artemis.Plugins.LayerTypes.Brush/app.config index 0edb16ded..983b4f4d9 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/app.config +++ b/src/Artemis.Plugins.LayerTypes.Brush/app.config @@ -13,7 +13,7 @@ - + diff --git a/src/Artemis.Plugins.LayerTypes.Brush/packages.config b/src/Artemis.Plugins.LayerTypes.Brush/packages.config index ca0c2406c..66df08a60 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/packages.config +++ b/src/Artemis.Plugins.LayerTypes.Brush/packages.config @@ -1,7 +1,11 @@  - - + + + + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj b/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj index bef9f7b45..af8d5a9fa 100644 --- a/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj +++ b/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj @@ -13,6 +13,8 @@ 512 true + + true @@ -42,15 +44,27 @@ False ..\..\..\RGB.NET\bin\net45\RGB.NET.Core.dll - - ..\packages\Stylet.1.2.0\lib\net45\Stylet.dll + + ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll + False + + + ..\packages\Stylet.1.3.0\lib\net45\Stylet.dll + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + - - - ..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll - False + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll @@ -97,4 +111,11 @@ echo Copying plugin to Artemis.UI output directory XCOPY "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)\Artemis.UI\$(OutDir)Plugins\$(ProjectName)" /s /q /i /y + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.General/ColorHelpers.cs b/src/Artemis.Plugins.Modules.General/ColorHelpers.cs index 61e0d063b..df07d6012 100644 --- a/src/Artemis.Plugins.Modules.General/ColorHelpers.cs +++ b/src/Artemis.Plugins.Modules.General/ColorHelpers.cs @@ -1,5 +1,4 @@ using System; -using System.Drawing; namespace Artemis.Plugins.Modules.General { @@ -11,29 +10,5 @@ namespace Artemis.Plugins.Modules.General { return Rand.Next(0, 360); } - - public static Color ColorFromHSV(double hue, double saturation, double value) - { - var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6; - var f = hue / 60 - Math.Floor(hue / 60); - - value = value * 255; - var v = Convert.ToInt32(value); - var p = Convert.ToInt32(value * (1 - saturation)); - var q = Convert.ToInt32(value * (1 - f * saturation)); - var t = Convert.ToInt32(value * (1 - (1 - f) * saturation)); - - if (hi == 0) - return Color.FromArgb(255, v, t, p); - if (hi == 1) - return Color.FromArgb(255, q, v, p); - if (hi == 2) - return Color.FromArgb(255, p, v, t); - if (hi == 3) - return Color.FromArgb(255, p, q, v); - if (hi == 4) - return Color.FromArgb(255, t, p, v); - return Color.FromArgb(255, v, p, q); - } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Artemis.Plugins.Modules.General/GeneralModule.cs index 775ac0455..c732dfd12 100644 --- a/src/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -1,21 +1,17 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Linq; +using System.Diagnostics; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Storage.Interfaces; using Artemis.Plugins.Modules.General.ViewModels; -using RGB.NET.Core; -using Color = System.Drawing.Color; +using SkiaSharp; namespace Artemis.Plugins.Modules.General { public class GeneralModule : ProfileModule { - private readonly ColorBlend _rainbowColorBlend; private readonly PluginSettings _settings; public GeneralModule(PluginInfo pluginInfo, PluginSettings settings, ISurfaceService surfaceService) : base(pluginInfo) @@ -23,31 +19,25 @@ namespace Artemis.Plugins.Modules.General _settings = settings; DisplayName = "General"; ExpandsMainDataModel = true; - DeviceBrushes = new Dictionary(); + DeviceShaders = new Dictionary(); + RainbowColors = new List(); - var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); - - Hues = new int[1000]; - for (var i = 0; i < 1000; i++) - Hues[i] = ColorHelpers.GetRandomHue(); - - _rainbowColorBlend = new ColorBlend(9); for (var i = 0; i < 9; i++) { - _rainbowColorBlend.Positions[i] = i / 8f; if (i != 8) - _rainbowColorBlend.Colors[i] = ColorHelpers.ColorFromHSV(i * 32, 1, 1); + RainbowColors.Add(SKColor.FromHsv(i * 32, 100, 100)); else - _rainbowColorBlend.Colors[i] = ColorHelpers.ColorFromHSV(0, 1, 1); + RainbowColors.Add(SKColor.FromHsv(0, 100, 100)); } - surfaceService.SurfaceConfigurationUpdated += (sender, args) => DeviceBrushes.Clear(); + surfaceService.SurfaceConfigurationUpdated += (sender, args) => DeviceShaders.Clear(); + var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); } - public int[] Hues { get; set; } public int MovePercentage { get; set; } - public Dictionary DeviceBrushes { get; set; } + public Dictionary DeviceShaders { get; set; } + public List RainbowColors { get; set; } public override void EnablePlugin() { @@ -59,13 +49,6 @@ namespace Artemis.Plugins.Modules.General public override void Update(double deltaTime) { - for (var i = 0; i < Hues.Length; i++) - { - Hues[i]++; - if (Hues[i] > 360) - Hues[i] = 0; - } - MovePercentage++; if (MovePercentage > 100) MovePercentage = 0; @@ -74,62 +57,46 @@ namespace Artemis.Plugins.Modules.General } - public override void Render(double deltaTime, ArtemisSurface surface, Graphics graphics) + public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) { - // Per-device coloring, slower - // RenderPerDevice(surface, graphics); + base.Render(deltaTime, surface, canvas); + return; - // Per-LED coloring, slowest - // RenderPerLed(surface, graphics); - - base.Render(deltaTime, surface, graphics); - } - - public void RenderFullSurface(ArtemisSurface surface, Graphics graphics) - { - } - - public void RenderPerDevice(ArtemisSurface surface, Graphics graphics) - { foreach (var device in surface.Devices) { - if (!DeviceBrushes.ContainsKey(device)) - DeviceBrushes.Add(device, new TextureBrush(RenderGradientForDevice(device), WrapMode.Tile)); + using (var bitmap = new SKBitmap(new SKImageInfo((int) device.RenderRectangle.Width, (int) device.RenderRectangle.Height))) + { + using (var layerCanvas = new SKCanvas(bitmap)) + { + layerCanvas.Clear(); + SKShader shader; + if (DeviceShaders.ContainsKey(device)) + shader = DeviceShaders[device]; + else + { + shader = SKShader.CreateLinearGradient( + new SKPoint(0, 0), + new SKPoint(device.RenderRectangle.Width, 0), + RainbowColors.ToArray(), + null, + SKShaderTileMode.Clamp); + DeviceShaders.Add(device, shader); + } - var brush = DeviceBrushes[device]; - brush.TranslateTransform((int) Math.Round(device.RenderRectangle.Width / 100.0 * MovePercentage), 0); - graphics.FillPath(brush, device.RenderPath); - brush.TranslateTransform((int) Math.Round(device.RenderRectangle.Width / 100.0 * MovePercentage) * -1, 0); + using (var paint = new SKPaint {Shader = shader, FilterQuality = SKFilterQuality.Low}) + { + layerCanvas.DrawRect(0, 0, device.RenderRectangle.Width, device.RenderRectangle.Height, paint); + } + } - graphics.DrawRectangle(new Pen(Color.Red), device.RenderRectangle); - } - } - - private Image RenderGradientForDevice(ArtemisDevice device) - { - var brush = new LinearGradientBrush(device.RenderRectangle, Color.Black, Color.Black, 0, false) - { - InterpolationColors = _rainbowColorBlend - }; - var bitmap = new Bitmap(device.RenderRectangle.Width, device.RenderRectangle.Height); - using (var g = Graphics.FromImage(bitmap)) - { - g.FillRectangle(brush, 0, 0, device.RenderRectangle.Width, device.RenderRectangle.Height); - } - - return bitmap; - } - - public void RenderPerLed(ArtemisSurface surface, Graphics graphics) - { - var index = 0; - foreach (var led in surface.Devices.SelectMany(d => d.Leds)) - { - if (led.RgbLed.Id == LedId.HeadsetStand1) - graphics.FillRectangle(new SolidBrush(Color.Red), led.AbsoluteRenderRectangle); - else - graphics.FillRectangle(new SolidBrush(ColorHelpers.ColorFromHSV(Hues[index], 1, 1)), led.AbsoluteRenderRectangle); - index++; + using (var bitmapShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat)) + using (var translated = SKShader.CreateLocalMatrix(bitmapShader, SKMatrix.MakeTranslation(device.RenderRectangle.Width / 100 * MovePercentage * -1, 0))) + using (var translatedPaint = new SKPaint {Shader = translated, FilterQuality = SKFilterQuality.Low}) + { + // Here we can let each module modify the shader as needed + canvas.DrawRect(device.RenderRectangle, translatedPaint); + } + } } } diff --git a/src/Artemis.Plugins.Modules.General/app.config b/src/Artemis.Plugins.Modules.General/app.config index 0edb16ded..983b4f4d9 100644 --- a/src/Artemis.Plugins.Modules.General/app.config +++ b/src/Artemis.Plugins.Modules.General/app.config @@ -13,7 +13,7 @@ - + diff --git a/src/Artemis.Plugins.Modules.General/packages.config b/src/Artemis.Plugins.Modules.General/packages.config index c5ec6307a..865736c9f 100644 --- a/src/Artemis.Plugins.Modules.General/packages.config +++ b/src/Artemis.Plugins.Modules.General/packages.config @@ -1,8 +1,12 @@  - - + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/App.config b/src/Artemis.UI/App.config index 056019794..15f01e053 100644 --- a/src/Artemis.UI/App.config +++ b/src/Artemis.UI/App.config @@ -100,7 +100,7 @@ - + diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index d29501b6e..d9a6e68e6 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -117,16 +117,38 @@ ..\packages\Serilog.2.8.0\lib\net46\Serilog.dll + + ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll + + + ..\packages\SkiaSharp.Views.Desktop.Common.1.68.1\lib\net45\SkiaSharp.Views.Desktop.Common.dll + + + ..\packages\SkiaSharp.Views.WPF.1.68.1\lib\net45\SkiaSharp.Views.WPF.dll + ..\packages\Stylet.1.3.0\lib\net45\Stylet.dll + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + ..\packages\System.ComponentModel.Annotations.4.6.0\lib\net461\System.ComponentModel.Annotations.dll + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll @@ -420,5 +442,7 @@ + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs index 72d6b088b..7179b8a65 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs @@ -1,12 +1,11 @@ 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 SkiaSharp; +using SkiaSharp.Views.WPF; using Stylet; namespace Artemis.UI.Screens.Settings.Debug @@ -35,12 +34,27 @@ namespace Artemis.UI.Screens.Settings.Debug private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) { - if (e.Bitmap == null) - return; + Execute.PostToUIThread(() => + { + if (!(CurrentFrame is WriteableBitmap writeableBitmap)) + { + CurrentFrame = e.GraphicsDecorator.Bitmap.ToWriteableBitmap(); + return; + } - var imageSource = ImageSourceFromBitmap(e.Bitmap); - imageSource.Freeze(); - Execute.PostToUIThread(() => { CurrentFrame = imageSource; }); + using (var skiaImage = SKImage.FromPixels(e.GraphicsDecorator.Bitmap.PeekPixels())) + { + var info = new SKImageInfo(skiaImage.Width, skiaImage.Height); + writeableBitmap.Lock(); + using (var pixmap = new SKPixmap(info, writeableBitmap.BackBuffer, writeableBitmap.BackBufferStride)) + { + skiaImage.ReadPixels(pixmap, 0, 0); + } + + writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, info.Width, info.Height)); + writeableBitmap.Unlock(); + } + }); } private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -61,23 +75,5 @@ namespace Artemis.UI.Screens.Settings.Debug _coreService.FrameRendering -= CoreServiceOnFrameRendering; base.OnDeactivate(); } - - // 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/packages.config b/src/Artemis.UI/packages.config index 73fe590d2..799e332f9 100644 --- a/src/Artemis.UI/packages.config +++ b/src/Artemis.UI/packages.config @@ -1,5 +1,4 @@  - @@ -17,7 +16,14 @@ + + + + + + + \ No newline at end of file diff --git a/src/Artemis.sln b/src/Artemis.sln index 71bccf436..437dd44f4 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -8,6 +8,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.UI", "Artemis.UI\Ar {E592F239-FAA0-4840-9C85-46E5867D06D5} = {E592F239-FAA0-4840-9C85-46E5867D06D5} {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} = {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} {235A45C7-24AD-4F47-B9D4-CD67E610A04D} = {235A45C7-24AD-4F47-B9D4-CD67E610A04D} + {6FE5DED5-D62E-4811-985F-644124FCEEFE} = {6FE5DED5-D62E-4811-985F-644124FCEEFE} {A779B2F8-C253-4C4B-8634-6EB8F594E96D} = {A779B2F8-C253-4C4B-8634-6EB8F594E96D} EndProjectSection EndProject @@ -25,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.Devices.Cor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.Devices.Logitech", "Artemis.Plugins.Devices.Logitech\Artemis.Plugins.Devices.Logitech.csproj", "{235A45C7-24AD-4F47-B9D4-CD67E610A04D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.LayerElements.Animations", "Artemis.Plugins.LayerElements.Animations\Artemis.Plugins.LayerElements.Animations.csproj", "{6FE5DED5-D62E-4811-985F-644124FCEEFE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +92,14 @@ Global {235A45C7-24AD-4F47-B9D4-CD67E610A04D}.Release|Any CPU.Build.0 = Release|Any CPU {235A45C7-24AD-4F47-B9D4-CD67E610A04D}.Release|x64.ActiveCfg = Release|Any CPU {235A45C7-24AD-4F47-B9D4-CD67E610A04D}.Release|x64.Build.0 = Release|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Debug|x64.Build.0 = Debug|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Release|Any CPU.Build.0 = Release|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Release|x64.ActiveCfg = Release|Any CPU + {6FE5DED5-D62E-4811-985F-644124FCEEFE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,6 +109,7 @@ Global {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} {A779B2F8-C253-4C4B-8634-6EB8F594E96D} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} {235A45C7-24AD-4F47-B9D4-CD67E610A04D} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} + {6FE5DED5-D62E-4811-985F-644124FCEEFE} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A}