From aa7c914b92fe19cf1a1cc8084208559a3aa45de9 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 2 Aug 2020 00:20:25 +0200 Subject: [PATCH] UI - Many misc fixes I can't remember Profile editor - Removed layer outline, increasing performance by a lot Device visualizer - Streamlined custom Device providers - Added debug device provider ASUS - Added Maximus X Hero thanks @copystring Plugin core - Added PerLedLayerBrush Noise layer - Converted to PerLedLayerBrush, increasing performance and quality --- .../Conditions/DisplayConditionPredicate.cs | 3 +- src/Artemis.Core/Models/Profile/Folder.cs | 2 + src/Artemis.Core/Models/Profile/Layer.cs | 77 +++++++- .../Plugins/Abstract/DeviceProvider.cs | 2 +- .../LayerBrush/Abstract/PerLedLayerBrush.cs | 74 ++++++++ .../Controls/DeviceVisualizer.cs | 35 +++- .../Controls/DeviceVisualizerLed.cs | 4 +- .../Services/ProfileEditorService.cs | 3 +- .../Ninject/Factories/IVMFactory.cs | 2 +- .../DisplayConditionPredicateView.xaml | 76 ++++---- .../LayerPropertiesViewModel.cs | 3 +- .../ProfileEditor/ProfileEditorViewModel.cs | 29 ++- .../Visualization/ProfileLayerView.xaml | 21 +-- .../Visualization/ProfileLayerViewModel.cs | 141 +++++---------- .../Visualization/ProfileViewModel.cs | 36 ++-- .../Settings/Debug/DeviceDebugView.xaml | 5 +- .../Plugins/PluginSettingsWindowViewModel.cs | 8 + src/Artemis.sln | 12 ++ .../Images/Asus/Mainboards/MAXIMUS-X-HERO.png | Bin 0 -> 11594204 bytes .../Asus/Mainboards/MAXIMUS-X-HERO.xml | 76 ++++++++ .../Artemis.Plugins.Devices.Debug.csproj | 53 ++++++ .../DebugDeviceProvider.cs | 67 +++++++ .../Properties/AssemblyInfo.cs | 9 + .../Settings/DeviceDefinition.cs | 8 + .../ViewModels/DebugConfigurationViewModel.cs | 39 ++++ .../Views/DebugConfigurationView.xaml | 49 +++++ .../Artemis.Plugins.Devices.Debug/app.config | 36 ++++ .../Artemis.Plugins.Devices.Debug/plugin.json | 7 + .../NoiseBrush.cs | 167 ++++++++++-------- 29 files changed, 767 insertions(+), 277 deletions(-) create mode 100644 src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.Asus/Images/Asus/Mainboards/MAXIMUS-X-HERO.png create mode 100644 src/Plugins/Artemis.Plugins.Devices.Asus/Layouts/Asus/Mainboards/MAXIMUS-X-HERO.xml create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/Artemis.Plugins.Devices.Debug.csproj create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/DebugDeviceProvider.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/Properties/AssemblyInfo.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/Settings/DeviceDefinition.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/ViewModels/DebugConfigurationViewModel.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/Views/DebugConfigurationView.xaml create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/app.config create mode 100644 src/Plugins/Artemis.Plugins.Devices.Debug/plugin.json diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index eeb31bf6c..fd8485857 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -124,7 +124,8 @@ namespace Artemis.Core.Models.Profile.Conditions StaticConditionLambda = null; CompiledStaticConditionLambda = null; - if (PredicateType == PredicateType.Dynamic) + // If the operator does not support a right side, create a static expression because the right side will simply be null + if (PredicateType == PredicateType.Dynamic && Operator.SupportsRightSide) CreateDynamicExpression(); CreateStaticExpression(); diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 315ecac2d..c95d37598 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -156,8 +156,10 @@ namespace Artemis.Core.Models.Profile // Iterate the children in reverse because the first layer must be rendered last to end up on top for (var index = Children.Count - 1; index > -1; index--) { + folderCanvas.Save(); var profileElement = Children[index]; profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info); + folderCanvas.Restore(); } var targetLocation = Path.Bounds.Location; diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 99265b4bc..99b21c3b3 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -425,12 +425,14 @@ namespace Artemis.Core.Models.Profile OnRenderPropertiesUpdated(); } - internal SKPoint GetLayerAnchorPosition(SKPath layerPath) + internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false) { var positionProperty = Transform.Position.CurrentValue; // Start at the center of the shape - var position = new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY); + var position = zeroBased + ? new SKPoint(layerPath.Bounds.MidX - layerPath.Bounds.Left, layerPath.Bounds.MidY - layerPath.Bounds.Top) + : new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY); // Apply translation position.X += positionProperty.X * layerPath.Bounds.Width; @@ -444,17 +446,46 @@ namespace Artemis.Core.Models.Profile /// layer translations out /// /// - public void ExcludePathFromTranslation(SKPath path) + public void IncludePathInTranslation(SKPath path, bool zeroBased) { var sizeProperty = Transform.Scale.CurrentValue; var rotationProperty = Transform.Rotation.CurrentValue; - var anchorPosition = GetLayerAnchorPosition(Path); + var anchorPosition = GetLayerAnchorPosition(Path, zeroBased); var anchorProperty = Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width; - var y = anchorPosition.Y - Bounds.MidY - anchorProperty.Y * Bounds.Height; + var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; + var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; + + if (General.FillType == LayerFillType.Stretch) + { + path.Transform(SKMatrix.MakeTranslation(x, y)); + path.Transform(SKMatrix.MakeScale((sizeProperty.Width / 100f), (sizeProperty.Height / 100f), anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); + } + else + { + path.Transform(SKMatrix.MakeTranslation(x, y)); + path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); + } + } + + /// + /// Excludes the provided path from the translations applied to the layer by applying translations that cancel the + /// layer translations out + /// + public void ExcludePathFromTranslation(SKPath path, bool zeroBased) + { + var sizeProperty = Transform.Scale.CurrentValue; + var rotationProperty = Transform.Rotation.CurrentValue; + + var anchorPosition = GetLayerAnchorPosition(Path, zeroBased); + var anchorProperty = Transform.AnchorPoint.CurrentValue; + + // Translation originates from the unscaled center of the shape and is tied to the anchor + var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; + var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; var reversedXScale = 1f / (sizeProperty.Width / 100f); var reversedYScale = 1f / (sizeProperty.Height / 100f); @@ -472,6 +503,40 @@ namespace Artemis.Core.Models.Profile } } + /// + /// Excludes the provided canvas from the translations applied to the layer by applying translations that cancel the + /// layer translations out + /// + /// The number of transformations applied + public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased) + { + var sizeProperty = Transform.Scale.CurrentValue; + var rotationProperty = Transform.Rotation.CurrentValue; + + var anchorPosition = GetLayerAnchorPosition(Path, zeroBased); + var anchorProperty = Transform.AnchorPoint.CurrentValue; + + // Translation originates from the unscaled center of the shape and is tied to the anchor + var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; + var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; + + var reversedXScale = 1f / (sizeProperty.Width / 100f); + var reversedYScale = 1f / (sizeProperty.Height / 100f); + + if (General.FillType == LayerFillType.Stretch) + { + canvas.Translate(x * -1, y * -1); + canvas.Scale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y); + canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y); + + return 3; + } + + canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y); + canvas.Translate(x * -1, y * -1); + return 2; + } + #endregion #region LED management diff --git a/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs b/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs index 7a6c5d58b..d9bde1593 100644 --- a/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs @@ -30,7 +30,7 @@ namespace Artemis.Core.Plugins.Abstract protected void ResolveAbsolutePath(Type type, object sender, ResolvePathEventArgs e) { - if (sender.GetType().IsGenericType(type)) + if (sender.GetType() == type || sender.GetType().IsGenericType(type)) { // Start from the plugin directory if (e.RelativePart != null && e.FileName != null) diff --git a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs new file mode 100644 index 000000000..65c4614f4 --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs @@ -0,0 +1,74 @@ +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Surface; +using Artemis.Core.Services.Interfaces; +using SkiaSharp; + +namespace Artemis.Core.Plugins.LayerBrush.Abstract +{ + public abstract class PerLedLayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup + { + protected PerLedLayerBrush() + { + BrushType = LayerBrushType.Regular; + } + + + /// + /// The main method of rendering for this type of brush. Called once per frame for each LED in the layer + /// + /// Note: Due to transformations, the render point may not match the position of the LED, always use the render + /// point to determine where the color will go. + /// + /// + /// The LED that will receive the color + /// The point at which the color is located + /// The color the LED will receive + public abstract SKColor GetColor(ArtemisLed led, SKPoint renderPoint); + + internal override void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + { + // This lil' snippet renders per LED, it's neater but doesn't support translations + Layer.ExcludeCanvasFromTranslation(canvas, true); + + var shapePath = new SKPath(Layer.LayerShape.Path); + Layer.IncludePathInTranslation(shapePath, true); + canvas.ClipPath(shapePath); + + using var pointsPath = new SKPath(); + using var ledPaint = new SKPaint(); + foreach (var artemisLed in Layer.Leds) + { + pointsPath.AddPoly(new[] + { + new SKPoint(0, 0), + new SKPoint(artemisLed.AbsoluteRenderRectangle.Left - Layer.Bounds.Left, artemisLed.AbsoluteRenderRectangle.Top - Layer.Bounds.Top) + }); + } + + Layer.ExcludePathFromTranslation(pointsPath, true); + var points = pointsPath.Points; + for (var index = 0; index < Layer.Leds.Count; index++) + { + var artemisLed = Layer.Leds[index]; + var renderPoint = points[index * 2 + 1]; + + // Let the brush determine the color + ledPaint.Color = GetColor(artemisLed, renderPoint); + + var ledRectangle = SKRect.Create( + artemisLed.AbsoluteRenderRectangle.Left - Layer.Bounds.Left, + artemisLed.AbsoluteRenderRectangle.Top - Layer.Bounds.Top, + artemisLed.AbsoluteRenderRectangle.Width, + artemisLed.AbsoluteRenderRectangle.Height + ); + + canvas.DrawRect(ledRectangle, ledPaint); + } + } + + internal override void Initialize(IRenderElementService renderElementService) + { + InitializeProperties(renderElementService); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index 7fd7f4956..a61dd5561 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -65,13 +65,28 @@ namespace Artemis.UI.Shared.Controls _timer.Stop(); } + public static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight) + { + double scale; + if (maxWidth == double.PositiveInfinity && maxHeight != double.PositiveInfinity) + scale = maxHeight / src.Height; + else if (maxWidth != double.PositiveInfinity && maxHeight == double.PositiveInfinity) + scale = maxWidth / src.Width; + else if (maxWidth == double.PositiveInfinity && maxHeight == double.PositiveInfinity) + return src; + + scale = Math.Min(maxWidth / src.Width, maxHeight / src.Height); + + return new Size(src.Width * scale, src.Height * scale); + } + protected override void OnRender(DrawingContext drawingContext) { if (Device == null) return; // Determine the scale required to fit the desired size of the control - var measureSize = MeasureOverride(Size.Empty); + var measureSize = MeasureDevice(); var scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height); var scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale); @@ -103,16 +118,24 @@ namespace Artemis.UI.Shared.Controls drawingContext.DrawDrawing(_backingStore); } + private Size MeasureDevice() + { + if (Device == null) + return Size.Empty; + + var rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height); + rotationRect.Transform(new RotateTransform(Device.Rotation).Value); + + return rotationRect.Size; + } + protected override Size MeasureOverride(Size availableSize) { if (Device == null) return Size.Empty; - - var rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height); - rotationRect.Transform(new RotateTransform(Device.Rotation).Value); - // TODO: If availableSize exceeds what we need, scale up - return rotationRect.Size; + var deviceSize = MeasureDevice(); + return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height); } private void OnUnloaded(object sender, RoutedEventArgs e) diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index ca637bef0..231d81237 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -69,7 +69,7 @@ namespace Artemis.UI.Shared.Controls // Create transparent pixels covering the entire LedRect so the image size matched the LedRect size drawingContext.DrawRectangle(new SolidColorBrush(Colors.Transparent), null, LedRect); // Translate to the top-left of the LedRect - drawingContext.PushTransform(new TranslateTransform(LedRect.X, LedRect.Y)); + drawingContext.PushTransform(new TranslateTransform(LedRect.X , LedRect.Y)); // Render the LED geometry drawingContext.DrawGeometry(fillBrush, new Pen(penBrush, 1) {LineJoin = PenLineJoin.Round}, DisplayGeometry.GetOutlinedPathGeometry()); // Restore the drawing context @@ -88,7 +88,7 @@ namespace Artemis.UI.Shared.Controls if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) CreateCustomGeometry(2.0); else - CreateCustomGeometry(0); + CreateCustomGeometry(1.0); break; case Shape.Rectangle: if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 4c4d05455..f9d651199 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Abstract; @@ -125,7 +126,7 @@ namespace Artemis.UI.Shared.Services { if (SelectedProfile == null) return; - + // Stick to the main segment for any element that is not currently selected foreach (var folder in SelectedProfile.GetAllFolders()) folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index e9770db83..01f869ce1 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -65,7 +65,7 @@ namespace Artemis.UI.Ninject.Factories public interface IProfileLayerVmFactory : IVmFactory { - ProfileLayerViewModel Create(Layer layer); + ProfileLayerViewModel Create(Layer layer, ProfileViewModel profileViewModel); } public interface IVisualizationToolVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index c83604a27..96e3c2a4c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -104,43 +104,45 @@ - - - - + Visibility="{Binding SelectedOperator.SupportsRightSide, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"> + + + + + + + + + \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Debug/app.config b/src/Plugins/Artemis.Plugins.Devices.Debug/app.config new file mode 100644 index 000000000..aed5d2184 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Devices.Debug/app.config @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Debug/plugin.json b/src/Plugins/Artemis.Plugins.Devices.Debug/plugin.json new file mode 100644 index 000000000..7426fc0f2 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Devices.Debug/plugin.json @@ -0,0 +1,7 @@ +{ + "Guid": "cad475d3-c621-4ec7-bbfc-784e3b4723ce", + "Name": "Debug Devices", + "Description": "Provides configurable debug devices to among other things easily test layouts", + "Version": "1.0.0.0", + "Main": "Artemis.Plugins.Devices.Debug.dll" +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index 043460c5c..ce3d29804 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using Artemis.Core.Extensions; +using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerBrush.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Plugins.LayerBrushes.Noise.Utilities; @@ -8,7 +9,7 @@ using SkiaSharp; namespace Artemis.Plugins.LayerBrushes.Noise { - public class NoiseBrush : LayerBrush + public class NoiseBrush : PerLedLayerBrush { private static readonly Random Rand = new Random(); private readonly IRgbService _rgbService; @@ -60,75 +61,69 @@ namespace Artemis.Plugins.LayerBrushes.Noise DetermineRenderScale(); } - public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) - { - var mainColor = Properties.MainColor.CurrentValue; - var secondColor = Properties.SecondaryColor.CurrentValue; - var gradientColor = Properties.GradientColor.CurrentValue; - var scale = Properties.Scale.CurrentValue; - var hardness = Properties.Hardness.CurrentValue / 100f; - - // Scale down the render path to avoid computing a value for every pixel - var width = (int) Math.Floor(path.Bounds.Width * _renderScale); - var height = (int) Math.Floor(path.Bounds.Height * _renderScale); - - CreateBitmap(width, height); - - // Copy the layer path to use for the clip path - using var layerPath = new SKPath(Layer.Path); - // Undo any transformations - Layer.ExcludePathFromTranslation(layerPath); - // Apply the transformations so new ones may be applied, not sure if there is a better way to do this - using var clipPath = new SKPath(layerPath); - // Zero out the position of the clip path - clipPath.Transform(SKMatrix.MakeTranslation(Layer.Path.Bounds.Left * -1, Layer.Path.Bounds.Top * -1)); - - // Fill a canvas matching the final area that will be rendered - using var bitmapCanvas = new SKCanvas(_bitmap); - using var clipPaint = new SKPaint {Color = new SKColor(0, 0, 0, 255)}; - bitmapCanvas.Clear(); - bitmapCanvas.Scale(_renderScale); - bitmapCanvas.DrawPath(clipPath, clipPaint); - - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var scrolledX = x + _x; - var scrolledY = y + _y; - var evalX = 0.1 * scale.Width * scrolledX / width; - var evalY = 0.1 * scale.Height * scrolledY / height; - if (double.IsInfinity(evalX) || double.IsNaN(evalX) || double.IsNaN(evalY) || double.IsInfinity(evalY)) - continue; - - var pixel = _bitmap.GetPixel(x, y); - if (pixel.Alpha != 255) - continue; - - var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness; - var amount = Math.Max(0f, Math.Min(1f, v)); - if (Properties.ColorType.BaseValue == ColorMappingType.Simple) - _bitmap.SetPixel(x, y, mainColor.Interpolate(secondColor, amount)); - else if (gradientColor != null && _colorMap.Length == 101) - { - var color = _colorMap[(int) Math.Round(amount * 100, MidpointRounding.AwayFromZero)]; - _bitmap.SetPixel(x, y, color); - } - } - } - - var bitmapTransform = SKMatrix.Concat( - SKMatrix.MakeTranslation(path.Bounds.Left, path.Bounds.Top), - SKMatrix.MakeScale(1f / _renderScale, 1f / _renderScale) - ); - - if (Properties.ColorType.BaseValue == ColorMappingType.Simple) - paint.Color = Properties.SecondaryColor.CurrentValue; - - using var foregroundShader = SKShader.CreateBitmap(_bitmap, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, bitmapTransform); - paint.Shader = foregroundShader; - canvas.DrawPath(path, paint); - } + // public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + // { + // var mainColor = Properties.MainColor.CurrentValue; + // var secondColor = Properties.SecondaryColor.CurrentValue; + // var gradientColor = Properties.GradientColor.CurrentValue; + // var scale = Properties.Scale.CurrentValue; + // var hardness = Properties.Hardness.CurrentValue / 100f; + // + // // Scale down the render path to avoid computing a value for every pixel + // var width = (int) Math.Floor(path.Bounds.Width * _renderScale); + // var height = (int) Math.Floor(path.Bounds.Height * _renderScale); + // + // // This lil' snippet renders per LED, it's neater but doesn't support translations + // Layer.ExcludeCanvasFromTranslation(canvas); + // + // var shapePath = new SKPath(Layer.LayerShape.Path); + // Layer.IncludePathInTranslation(shapePath); + // canvas.ClipPath(shapePath); + // + // using var ledPaint = new SKPaint(); + // foreach (var artemisLed in Layer.Leds) + // { + // var ledRectangle = SKRect.Create( + // artemisLed.AbsoluteRenderRectangle.Left - Layer.Bounds.Left, + // artemisLed.AbsoluteRenderRectangle.Top - Layer.Bounds.Top, + // artemisLed.AbsoluteRenderRectangle.Width, + // artemisLed.AbsoluteRenderRectangle.Height + // ); + // + // var scrolledX = ledRectangle.MidX + _x; + // if (float.IsNaN(scrolledX)) + // scrolledX = 0; + // var scrolledY = ledRectangle.MidY + _y; + // if (float.IsNaN(scrolledY)) + // scrolledY = 0; + // + // + // var evalPath = new SKPath(); + // evalPath.AddPoly(new[] {new SKPoint(0, 0), new SKPoint(scrolledX, scrolledY)}); + // Layer.ExcludePathFromTranslation(evalPath); + // + // if (evalPath.Bounds.IsEmpty) + // continue; + // + // scrolledX = evalPath.Points[1].X; + // scrolledY = evalPath.Points[1].Y; + // + // var evalX = 0.1 * scale.Width * scrolledX / width; + // var evalY = 0.1 * scale.Height * scrolledY / height; + // + // var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness; + // var amount = Math.Max(0f, Math.Min(1f, v)); + // + // if (Properties.ColorType.BaseValue == ColorMappingType.Simple) + // ledPaint.Color = mainColor.Interpolate(secondColor, amount); + // else if (gradientColor != null && _colorMap.Length == 101) + // ledPaint.Color = _colorMap[(int) Math.Round(amount * 100, MidpointRounding.AwayFromZero)]; + // else + // ledPaint.Color = SKColor.Empty; + // + // canvas.DrawRect(ledRectangle, ledPaint); + // } + // } private void GradientColorChanged(object sender, PropertyChangedEventArgs e) { @@ -138,7 +133,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise private void DetermineRenderScale() { - _renderScale = (float) (0.125f / _rgbService.RenderScale); + _renderScale = (float) (0.25f / _rgbService.RenderScale); } private void CreateBitmap(int width, int height) @@ -160,6 +155,36 @@ namespace Artemis.Plugins.LayerBrushes.Noise _colorMap = colorMap; } + + public override SKColor GetColor(ArtemisLed led, SKPoint renderPoint) + { + var mainColor = Properties.MainColor.CurrentValue; + var secondColor = Properties.SecondaryColor.CurrentValue; + var gradientColor = Properties.GradientColor.CurrentValue; + var scale = Properties.Scale.CurrentValue; + var hardness = Properties.Hardness.CurrentValue / 100f; + + var scrolledX = renderPoint.X + _x; + if (float.IsNaN(scrolledX)) + scrolledX = 0; + var scrolledY = renderPoint.Y + _y; + if (float.IsNaN(scrolledY)) + scrolledY = 0; + + + var evalX = scrolledX * (scale.Width *-1) / 1000f; + var evalY = scrolledY * (scale.Height*-1) / 1000f; + + var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness; + var amount = Math.Max(0f, Math.Min(1f, v)); + + if (Properties.ColorType.BaseValue == ColorMappingType.Simple) + return mainColor.Interpolate(secondColor, amount); + else if (gradientColor != null && _colorMap.Length == 101) + return _colorMap[(int) Math.Round(amount * 100, MidpointRounding.AwayFromZero)]; + else + return SKColor.Empty; + } } public enum ColorMappingType