From 427d3d252170679b84e8179fb6ec8a5cb6f31b86 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 8 Nov 2020 17:28:22 +0100 Subject: [PATCH] Profiles - Reworked render pipeline Profiles - Added two transform modes, normal and clip Intro animation - Disable with debugger attached Profile editor - Added layer copy --- src/Artemis.Core/Models/Profile/Folder.cs | 145 ++++--- src/Artemis.Core/Models/Profile/Layer.cs | 361 ++++++------------ .../Models/Profile/LayerGeneralProperties.cs | 7 +- .../Models/Profile/LayerPropertyGroup.cs | 1 + src/Artemis.Core/Models/Profile/Profile.cs | 5 +- .../Models/Profile/ProfileElement.cs | 4 +- src/Artemis.Core/Models/Profile/Renderer.cs | 83 ++++ .../LayerBrushes/Internal/BaseLayerBrush.cs | 2 +- .../Plugins/LayerBrushes/LayerBrush.cs | 7 +- .../Plugins/LayerBrushes/PerLedLayerBrush.cs | 20 +- .../Plugins/LayerBrushes/RgbNetLayerBrush.cs | 2 +- .../LayerEffects/Internal/BaseLayerEffect.cs | 6 +- .../Placeholder/PlaceholderLayerEffect.cs | 4 +- .../Plugins/Modules/ProfileModule.cs | 2 +- src/Artemis.Core/Resources/intro-profile.json | 74 +++- src/Artemis.Core/Services/CoreService.cs | 25 +- src/Artemis.Core/Utilities/IntroAnimation.cs | 4 +- .../LayerPropertiesViewModel.cs | 1 - .../ProfileTree/ProfileTreeViewModel.cs | 10 +- .../ProfileTree/TreeItem/LayerView.xaml | 5 + .../ProfileTree/TreeItem/LayerViewModel.cs | 15 +- 21 files changed, 394 insertions(+), 389 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/Renderer.cs diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 6c28c72a1..724244b6f 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -4,6 +4,7 @@ using System.Linq; using Artemis.Core.LayerEffects; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; +using Newtonsoft.Json; using SkiaSharp; namespace Artemis.Core @@ -13,8 +14,6 @@ namespace Artemis.Core /// public sealed class Folder : RenderProfileElement { - private SKBitmap _folderBitmap; - /// /// Creates a new instance of the class and adds itself to the child collection of the provided /// @@ -30,6 +29,7 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; Enabled = true; + Renderer = new Renderer(); _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -47,6 +47,7 @@ namespace Artemis.Core Name = folderEntity.Name; Enabled = folderEntity.Enabled; Order = folderEntity.Order; + Renderer = new Renderer(); _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -68,6 +69,8 @@ namespace Artemis.Core internal override RenderElementEntity RenderElementEntity => FolderEntity; + internal Renderer Renderer { get; } + /// public override List GetAllLayerProperties() { @@ -124,6 +127,18 @@ namespace Artemis.Core CalculateRenderProperties(); } + /// + /// Creates a deep copy of the layer + /// + /// The newly created copy + public Folder CreateCopy() + { + FolderEntity entityCopy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(FolderEntity)); + entityCopy.Id = Guid.NewGuid(); + + return new Folder(Profile, Parent, entityCopy); + } + public override string ToString() { return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; @@ -154,8 +169,8 @@ namespace Artemis.Core foreach (ProfileElement profileElement in Children) profileElement.Dispose(); + Renderer.Dispose(); - _folderBitmap?.Dispose(); base.Dispose(disposing); } @@ -199,16 +214,13 @@ namespace Artemis.Core #region Rendering - public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(SKCanvas canvas) { if (Disposed) throw new ObjectDisposedException("Folder"); - if (!Enabled || !Children.Any(c => c.Enabled)) - return; - // Ensure the folder is ready - if (Path == null) + if (!Enabled || !Children.Any(c => c.Enabled) || Path == null) return; // No point rendering if none of the children are going to render @@ -217,81 +229,56 @@ namespace Artemis.Core lock (Timeline) { - RenderFolder(Timeline, canvas, canvasInfo); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + { + baseLayerEffect.BaseProperties?.Update(Timeline); + baseLayerEffect.Update(Timeline.Delta.TotalSeconds); + } + + try + { + canvas.Save(); + Renderer.Open(Path, Parent as Folder); + + if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null) + throw new ArtemisCoreException("Failed to open folder render context"); + + // Renderer.ApplyClip(canvas); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PreProcess(Renderer.Canvas, Renderer.Path, Renderer.Paint); + + // If required, apply the opacity override of the module to the root folder + if (IsRootFolder && Profile.Module.OpacityOverride < 1) + { + double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); + Renderer.Paint.Color = Renderer.Paint.Color.WithAlpha((byte)(Renderer.Paint.Color.Alpha * multiplier)); + } + + // No point rendering if the alpha was set to zero by one of the effects + if (Renderer.Paint.Color.Alpha == 0) + return; + + // Iterate the children in reverse because the first layer must be rendered last to end up on top + for (int index = Children.Count - 1; index > -1; index--) + Children[index].Render(Renderer.Canvas); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PostProcess(Renderer.Canvas, Renderer.Path, Renderer.Paint); + + canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint); + } + finally + { + canvas.Restore(); + Renderer.Close(); + } + Timeline.ClearDelta(); } } - private void PrepareForRender(Timeline timeline) - { - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - { - baseLayerEffect.BaseProperties?.Update(timeline); - baseLayerEffect.Update(timeline.Delta.TotalSeconds); - } - } - - private void RenderFolder(Timeline timeline, SKCanvas canvas, SKImageInfo canvasInfo) - { - PrepareForRender(timeline); - - if (_folderBitmap == null) - { - _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); - } - else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height) - { - _folderBitmap.Dispose(); - _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); - } - - using SKPath folderPath = new SKPath(Path); - using SKCanvas folderCanvas = new SKCanvas(_folderBitmap); - using SKPaint folderPaint = new SKPaint(); - folderCanvas.Clear(); - - folderPath.Transform(SKMatrix.MakeTranslation(folderPath.Bounds.Left * -1, folderPath.Bounds.Top * -1)); - - SKPoint targetLocation = Path.Bounds.Location; - if (Parent is Folder parentFolder) - targetLocation -= parentFolder.Path.Bounds.Location; - - canvas.Save(); - - using SKPath clipPath = new SKPath(folderPath); - clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); - canvas.ClipPath(clipPath); - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(folderCanvas, _folderBitmap.Info, folderPath, folderPaint); - - // No point rendering if the alpha was set to zero by one of the effects - if (folderPaint.Color.Alpha == 0) - return; - - // Iterate the children in reverse because the first layer must be rendered last to end up on top - for (int index = Children.Count - 1; index > -1; index--) - { - folderCanvas.Save(); - ProfileElement profileElement = Children[index]; - profileElement.Render(folderCanvas, _folderBitmap.Info); - folderCanvas.Restore(); - } - - // If required, apply the opacity override of the module to the root folder - if (IsRootFolder && Profile.Module.OpacityOverride < 1) - { - double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); - folderPaint.Color = folderPaint.Color.WithAlpha((byte) (folderPaint.Color.Alpha * multiplier)); - } - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(canvas, canvasInfo, folderPath, folderPaint); - canvas.DrawBitmap(_folderBitmap, targetLocation, folderPaint); - - canvas.Restore(); - } - #endregion #region Events diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 70da32cd7..a0be455f1 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -6,6 +6,7 @@ using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; +using Newtonsoft.Json; using SkiaSharp; namespace Artemis.Core @@ -16,7 +17,6 @@ namespace Artemis.Core public sealed class Layer : RenderProfileElement { private LayerGeneralProperties _general; - private SKBitmap _layerBitmap; private BaseLayerBrush _layerBrush; private LayerShape _layerShape; private List _leds; @@ -39,6 +39,7 @@ namespace Artemis.Core Enabled = true; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); + Renderer = new Renderer(); _layerEffects = new List(); _leds = new List(); @@ -57,6 +58,7 @@ namespace Artemis.Core Parent = parent; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); + Renderer = new Renderer(); _layerEffects = new List(); _leds = new List(); @@ -112,6 +114,27 @@ namespace Artemis.Core internal override RenderElementEntity RenderElementEntity => LayerEntity; + internal Renderer Renderer { get; } + + /// + /// Creates a deep copy of the layer + /// + /// The newly created copy + public Layer CreateCopy() + { + LayerEntity entityCopy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(LayerEntity)); + entityCopy.Id = Guid.NewGuid(); + entityCopy.Name = entityCopy.Name + " - Copy"; + + Layer copy = new Layer(Profile, Parent, entityCopy); + copy.ChangeLayerBrush(LayerBrush.Descriptor); + copy.AddLeds(Leds); + + Parent.AddChild(copy, Order + 1); + + return copy; + } + /// public override List GetAllLayerProperties() { @@ -142,10 +165,9 @@ namespace Artemis.Core // Brush first in case it depends on any of the other disposables during it's own disposal _layerBrush?.Dispose(); - _general?.Dispose(); - _layerBitmap?.Dispose(); _transform?.Dispose(); + Renderer.Dispose(); base.Dispose(disposing); } @@ -272,16 +294,13 @@ namespace Artemis.Core } /// - public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(SKCanvas canvas) { if (Disposed) throw new ObjectDisposedException("Layer"); - if (!Enabled) - return; - // Ensure the layer is ready - if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) + if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) return; // Ensure the brush is ready if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular) @@ -289,14 +308,14 @@ namespace Artemis.Core lock (Timeline) { - RenderLayer(Timeline, canvas); + RenderTimeline(Timeline, canvas); foreach (Timeline extraTimeline in Timeline.ExtraTimelines) - RenderLayer(extraTimeline, canvas); + RenderTimeline(extraTimeline, canvas); Timeline.ClearDelta(); } } - private void PrepareForRender(Timeline timeline) + private void ApplyTimeline(Timeline timeline) { General.Update(timeline); Transform.Update(timeline); @@ -310,127 +329,79 @@ namespace Artemis.Core } } - private void RenderLayer(Timeline timeline, SKCanvas canvas) + private void RenderTimeline(Timeline timeline, SKCanvas canvas) { if (timeline.IsFinished) return; - PrepareForRender(timeline); + ApplyTimeline(timeline); - if (_layerBitmap == null) + try { - _layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + canvas.Save(); + Renderer.Open(Path, Parent as Folder); + + if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null) + throw new ArtemisCoreException("Failed to open layer render context"); + + // Apply blend mode and color + Renderer.Paint.BlendMode = General.BlendMode.CurrentValue; + Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); + + // Clip anything outside the LED selection bounds + canvas.ClipPath(Renderer.ClipPath); + + using SKPath renderPath = new SKPath(); + renderPath.AddRect(Renderer.Path.Bounds); + + if (General.TransformMode.CurrentValue == LayerTransformMode.Normal) + { + // Apply transformation except rotation to the render path + if (LayerBrush.SupportsTransformation) + { + SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, false); + renderPath.Transform(renderPathMatrix); + } + + // Apply rotation to the canvas + if (LayerBrush.SupportsTransformation) + { + SKMatrix rotationMatrix = GetTransformMatrix(true, false, false, true); + Renderer.Canvas.SetMatrix(Renderer.Canvas.TotalMatrix.PreConcat(rotationMatrix)); + } + + // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off + Renderer.Canvas.ClipPath(renderPath); + DelegateRendering(renderPath); + } + else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip) + { + SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, true); + renderPath.Transform(renderPathMatrix); + + // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off + Renderer.Canvas.ClipPath(renderPath); + DelegateRendering(Renderer.Path); + } + + canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint); } - else if (_layerBitmap.Info.Width != (int) Path.Bounds.Width || _layerBitmap.Info.Height != (int) Path.Bounds.Height) + finally { - _layerBitmap.Dispose(); - _layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + canvas.Restore(); + Renderer.Close(); } + } - using SKPath layerPath = new SKPath(Path); - using SKCanvas layerCanvas = new SKCanvas(_layerBitmap); - using SKPaint layerPaint = new SKPaint {FilterQuality = SKFilterQuality.Low}; - layerCanvas.Clear(); + private void DelegateRendering(SKPath path) + { + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PreProcess(Renderer.Canvas, path, Renderer.Paint); - layerPath.Transform(SKMatrix.MakeTranslation(layerPath.Bounds.Left * -1, layerPath.Bounds.Top * -1)); + LayerBrush.InternalRender(Renderer.Canvas, path, Renderer.Paint); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(layerCanvas, _layerBitmap.Info, layerPath, layerPaint); - - // No point rendering if the alpha was set to zero by one of the effects - if (layerPaint.Color.Alpha == 0) - return; - - if (!LayerBrush.SupportsTransformation) - SimpleRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath); - else if (General.ResizeMode.CurrentValue == LayerResizeMode.Normal) - StretchRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath); - else if (General.ResizeMode.CurrentValue == LayerResizeMode.Clip) - ClipRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath); - - using SKPaint canvasPaint = new SKPaint - { - BlendMode = General.BlendMode.CurrentValue, - Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)) - }; - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(layerCanvas, _layerBitmap.Info, layerPath, canvasPaint); - - SKPoint targetLocation = new SKPoint(0, 0); - if (Parent is Folder parentFolder) - targetLocation = Path.Bounds.Location - parentFolder.Path.Bounds.Location; - - using SKPath canvasPath = new SKPath(Path); - canvasPath.Transform(SKMatrix.MakeTranslation( - (canvasPath.Bounds.Left - targetLocation.X) * -1, - (canvasPath.Bounds.Top - targetLocation.Y) * -1) - ); - // canvas.ClipPath(canvasPath); - canvas.DrawBitmap(_layerBitmap, targetLocation, canvasPaint); - } - - private void SimpleRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) - { - using SKPath renderPath = new SKPath(LayerShape.Path); - LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); - } - - private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) - { - // Apply transformations - SKSize sizeProperty = Transform.Scale.CurrentValue; - float rotationProperty = Transform.Rotation.CurrentValue; - - SKPoint anchorPosition = GetLayerAnchorPosition(layerPath); - SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; - - // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - layerPath.Bounds.MidX - anchorProperty.X * layerPath.Bounds.Width; - float y = anchorPosition.Y - layerPath.Bounds.MidY - anchorProperty.Y * layerPath.Bounds.Height; - - // Apply these before translation because anchorPosition takes translation into account - canvas.RotateDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y); - canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); - canvas.Translate(x, y); - - using SKPath renderPath = new SKPath(LayerShape.Path); - LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); - } - - private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) - { - // Apply transformation - SKSize sizeProperty = Transform.Scale.CurrentValue; - float rotationProperty = Transform.Rotation.CurrentValue; - - SKPoint anchorPosition = GetLayerAnchorPosition(layerPath); - SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; - - // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - layerPath.Bounds.MidX - anchorProperty.X * layerPath.Bounds.Width; - float y = anchorPosition.Y - layerPath.Bounds.MidY - anchorProperty.Y * layerPath.Bounds.Height; - - using SKPath clipPath = new SKPath(LayerShape.Path); - clipPath.Transform(SKMatrix.MakeTranslation(x, y)); - clipPath.Transform(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y)); - clipPath.Transform(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); - canvas.ClipPath(clipPath); - - canvas.RotateDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y); - canvas.Translate(x, y); - - // Render the layer in the largest required bounds, this still creates stretching in some situations - // but the only alternative I see right now is always forcing brushes to render on the entire canvas - SKRect boundsRect = new SKRect( - Math.Min(clipPath.Bounds.Left - x, Bounds.Left - x), - Math.Min(clipPath.Bounds.Top - y, Bounds.Top - y), - Math.Max(clipPath.Bounds.Right - x, Bounds.Right - x), - Math.Max(clipPath.Bounds.Bottom - y, Bounds.Bottom - y) - ); - using SKPath renderPath = new SKPath(); - renderPath.AddRect(boundsRect); - - LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); + baseLayerEffect.PostProcess(Renderer.Canvas, path, Renderer.Paint); } internal void CalculateRenderProperties() @@ -462,7 +433,7 @@ namespace Artemis.Core OnRenderPropertiesUpdated(); } - internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false) + internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool applyTranslation, bool zeroBased) { if (Disposed) throw new ObjectDisposedException("Layer"); @@ -475,45 +446,15 @@ namespace Artemis.Core : new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY); // Apply translation - position.X += positionProperty.X * layerPath.Bounds.Width; - position.Y += positionProperty.Y * layerPath.Bounds.Height; + if (applyTranslation) + { + position.X += positionProperty.X * layerPath.Bounds.Width; + position.Y += positionProperty.Y * layerPath.Bounds.Height; + } return position; } - /// - /// Excludes the provided path from the translations applied to the layer by applying translations that cancel the - /// layer translations out - /// - /// - public void IncludePathInTranslation(SKPath path, bool zeroBased) - { - if (Disposed) - throw new ObjectDisposedException("Layer"); - - SKSize sizeProperty = Transform.Scale.CurrentValue; - float rotationProperty = Transform.Rotation.CurrentValue; - - SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased); - SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; - - // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; - float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; - - if (General.ResizeMode == LayerResizeMode.Normal) - { - 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)); - } - } - /// /// Creates a transformation matrix that applies the current transformation settings /// @@ -522,7 +463,7 @@ namespace Artemis.Core /// surface /// /// The transformation matrix containing the current transformation settings - public SKMatrix GetTransformMatrix(bool zeroBased) + public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation) { if (Disposed) throw new ObjectDisposedException("Layer"); @@ -530,98 +471,38 @@ namespace Artemis.Core SKSize sizeProperty = Transform.Scale.CurrentValue; float rotationProperty = Transform.Rotation.CurrentValue; - SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased); + SKPoint anchorPosition = GetLayerAnchorPosition(Path, true, zeroBased); SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; - if (General.ResizeMode == LayerResizeMode.Normal) + SKMatrix transform = SKMatrix.Empty; + + if (includeTranslation) { - SKMatrix transform = SKMatrix.MakeTranslation(x, y); - transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); - transform = transform.PostConcat(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y)); - return transform; - } - else - { - SKMatrix transform = SKMatrix.MakeTranslation(x, y); - transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); - return transform; - } - } - - /// - /// 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) - { - if (Disposed) - throw new ObjectDisposedException("Layer"); - - SKSize sizeProperty = Transform.Scale.CurrentValue; - float rotationProperty = Transform.Rotation.CurrentValue; - - SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased); - SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; - - // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; - float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; - - float reversedXScale = 1f / (sizeProperty.Width / 100f); - float reversedYScale = 1f / (sizeProperty.Height / 100f); - - if (General.ResizeMode == LayerResizeMode.Normal) - { - path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); - path.Transform(SKMatrix.MakeScale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y)); - path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); - } - else - { - path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); - path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); - } - } - - /// - /// 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) - { - if (Disposed) - throw new ObjectDisposedException("Layer"); - - SKSize sizeProperty = Transform.Scale.CurrentValue; - float rotationProperty = Transform.Rotation.CurrentValue; - - SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased); - SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; - - // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; - float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; - - float reversedXScale = 1f / (sizeProperty.Width / 100f); - float reversedYScale = 1f / (sizeProperty.Height / 100f); - - if (General.ResizeMode == LayerResizeMode.Normal) - { - canvas.Translate(x * -1, y * -1); - canvas.Scale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y); - canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y); - - return 3; + // transform is always SKMatrix.Empty here... + transform = SKMatrix.MakeTranslation(x, y); } - canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y); - canvas.Translate(x * -1, y * -1); - return 2; + if (includeScale) + { + if (transform == SKMatrix.Empty) + transform = SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); + else + transform = transform.PostConcat(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y)); + } + + if (includeRotation) + { + if (transform == SKMatrix.Empty) + transform = SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y); + else + transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); + } + + return transform; } #endregion @@ -811,7 +692,7 @@ namespace Artemis.Core Rectangle } - public enum LayerResizeMode + public enum LayerTransformMode { Normal, Clip diff --git a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs index c5e6d0c2c..4345c4be7 100644 --- a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs @@ -8,19 +8,18 @@ namespace Artemis.Core [PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")] public EnumLayerProperty ShapeType { get; set; } - [PropertyDescription(Name = "Resize mode", Description = "How to make the shape adjust to scale changes")] - public EnumLayerProperty ResizeMode { get; set; } - [PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")] public EnumLayerProperty BlendMode { get; set; } + [PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")] + public EnumLayerProperty TransformMode { get; set; } + [PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")] public LayerBrushReferenceLayerProperty BrushReference { get; set; } protected override void PopulateDefaults() { ShapeType.DefaultValue = LayerShapeType.Rectangle; - ResizeMode.DefaultValue = LayerResizeMode.Normal; BlendMode.DefaultValue = SKBlendMode.SrcOver; } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 48afecab9..f27827c74 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -290,6 +290,7 @@ namespace Artemis.Core internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e) { + Parent?.OnLayerPropertyOnCurrentValueSet(e); LayerPropertyOnCurrentValueSet?.Invoke(this, e); } diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 786ed8369..e92a80027 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; +using Newtonsoft.Json; using SkiaSharp; namespace Artemis.Core @@ -66,7 +67,7 @@ namespace Artemis.Core } } - public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(SKCanvas canvas) { lock (this) { @@ -76,7 +77,7 @@ namespace Artemis.Core throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (ProfileElement profileElement in Children) - profileElement.Render(canvas, canvasInfo); + profileElement.Render(canvas); } } diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 56b86b240..85cd09919 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -15,8 +15,8 @@ namespace Artemis.Core private int _order; private ProfileElement _parent; private Profile _profile; - protected bool Disposed; protected List ChildrenList; + protected bool Disposed; protected ProfileElement() { @@ -91,7 +91,7 @@ namespace Artemis.Core /// /// Renders the element /// - public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo); + public abstract void Render(SKCanvas canvas); /// /// Resets the internal state of the element diff --git a/src/Artemis.Core/Models/Profile/Renderer.cs b/src/Artemis.Core/Models/Profile/Renderer.cs new file mode 100644 index 000000000..a6494c1c8 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Renderer.cs @@ -0,0 +1,83 @@ +using System; +using SkiaSharp; + +namespace Artemis.Core +{ + internal class Renderer : IDisposable + { + private bool _disposed; + public SKBitmap? Bitmap { get; private set; } + public SKCanvas? Canvas { get; private set; } + public SKPaint? Paint { get; private set; } + public SKPath? Path { get; private set; } + public SKPath? ClipPath { get; private set; } + public SKPoint TargetLocation { get; private set; } + + public bool IsOpen { get; private set; } + + /// + /// Opens the render context using the dimensions of the provided path + /// + public void Open(SKPath path, Folder? parent) + { + if (_disposed) + throw new ObjectDisposedException("Renderer"); + + if (IsOpen) + throw new ArtemisCoreException("Cannot open render context because it is already open"); + + int width = (int) path.Bounds.Width; + int height = (int) path.Bounds.Height; + + if (Bitmap == null) + Bitmap = new SKBitmap(width, height); + else if (Bitmap.Info.Width != width || Bitmap.Info.Height != height) + Bitmap = new SKBitmap(width, height); + + Path = new SKPath(path); + Canvas = new SKCanvas(Bitmap); + Paint = new SKPaint(); + + Path.Transform(SKMatrix.MakeTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1)); + Canvas.Clear(); + + TargetLocation = new SKPoint(path.Bounds.Location.X, path.Bounds.Location.Y); + if (parent != null) + TargetLocation -= parent.Path.Bounds.Location; + + ClipPath = new SKPath(Path); + ClipPath.Transform(SKMatrix.MakeTranslation(TargetLocation.X, TargetLocation.Y)); + + IsOpen = true; + } + + public void Close() + { + if (_disposed) + throw new ObjectDisposedException("Renderer"); + + Canvas?.Dispose(); + Paint?.Dispose(); + Path?.Dispose(); + ClipPath?.Dispose(); + + Canvas = null; + Paint = null; + Path = null; + ClipPath = null; + + IsOpen = false; + } + + public void Dispose() + { + if (IsOpen) + Close(); + + Bitmap?.Dispose(); + Bitmap = null; + + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs index 910432692..847d5cd66 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs @@ -107,7 +107,7 @@ namespace Artemis.Core.LayerBrushes // but LayerBrush and RgbNetLayerBrush outside the core internal abstract void Initialize(); - internal abstract void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); + internal abstract void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint); internal virtual void Dispose(bool disposing) { diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs index 0ad17ffb2..8ab2bd9cd 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs @@ -22,14 +22,13 @@ namespace Artemis.Core.LayerBrushes /// Called during rendering or layer preview, in the order configured on the layer /// /// The layer canvas - /// /// The path to be filled, represents the shape /// The paint to be used to fill the shape - public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); + public abstract void Render(SKCanvas canvas, SKPath path, SKPaint paint); - internal override void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) { - Render(canvas, canvasInfo, path, paint); + Render(canvas, path, paint); } internal override void Initialize() diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs index c76ea80b2..368492cc3 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs @@ -28,18 +28,11 @@ namespace Artemis.Core.LayerBrushes /// 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) + internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) { - // We don't want translations on this canvas because that'll displace the LEDs, translations are applied to the points of each LED instead - Layer.ExcludeCanvasFromTranslation(canvas, true); - - if (Layer.General.ResizeMode == LayerResizeMode.Normal) - { - // Apply a translated version of the shape as the clipping mask - SKPath shapePath = new SKPath(Layer.LayerShape.Path); - Layer.IncludePathInTranslation(shapePath, true); - canvas.ClipPath(shapePath); - } + // We don't want rotation on this canvas because that'll displace the LEDs, translations are applied to the points of each LED instead + if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation) + canvas.SetMatrix(canvas.TotalMatrix.PreConcat(Layer.GetTransformMatrix(true, false, false, true).Invert())); using SKPath pointsPath = new SKPath(); using SKPaint ledPaint = new SKPaint(); @@ -52,7 +45,10 @@ namespace Artemis.Core.LayerBrushes }); } - Layer.ExcludePathFromTranslation(pointsPath, true); + // Apply the translation to the points of each LED instead + if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation) + pointsPath.Transform(Layer.GetTransformMatrix(true, true, true, true).Invert()); + SKPoint[] points = pointsPath.Points; for (int index = 0; index < Layer.Leds.Count; index++) { diff --git a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs index 38acdb123..0856af7cf 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs @@ -68,7 +68,7 @@ namespace Artemis.Core.LayerBrushes } // Not used in this effect type - internal override void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) { throw new NotImplementedException("RGB.NET layer effectes do not implement InternalRender"); } diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index e4ec96c9e..7e5778301 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -131,19 +131,17 @@ namespace Artemis.Core.LayerEffects /// Called before the layer or folder will be rendered /// /// The canvas used to render the frame - /// Info on the canvas size and pixel type /// The bounds this layer/folder will render in /// The paint this layer/folder will use to render - public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint); + public abstract void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint); /// /// Called after the layer of folder has been rendered /// /// The canvas used to render the frame - /// Info on the canvas size and pixel type /// The bounds this layer/folder rendered in /// The paint this layer/folder used to render - public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint); + public abstract void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint); // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // but LayerEffect outside the core diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs index 995b244e9..6447679d0 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -40,12 +40,12 @@ namespace Artemis.Core.LayerEffects.Placeholder } /// - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) { } /// - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) { } diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index d72015308..6b5b452e8 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -175,7 +175,7 @@ namespace Artemis.Core.Modules lock (this) { // Render the profile - ActiveProfile?.Render(canvas, canvasInfo); + ActiveProfile?.Render(canvas); } ProfileRendered(deltaTime, surface, canvas, canvasInfo); diff --git a/src/Artemis.Core/Resources/intro-profile.json b/src/Artemis.Core/Resources/intro-profile.json index 972e89c7c..25fd1d594 100644 --- a/src/Artemis.Core/Resources/intro-profile.json +++ b/src/Artemis.Core/Resources/intro-profile.json @@ -1,8 +1,8 @@ { "$type": "Artemis.Storage.Entities.Profile.ProfileEntity, Artemis.Storage", - "Id": "824a235d-da46-4c82-a16b-13efe347f492", + "Id": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "PluginGuid": "0de2991a-d7b8-4f61-ae4e-6623849215b5", - "Name": "Intro animation - Imported", + "Name": "Intro animation", "IsActive": true, "Folders": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib", @@ -10,12 +10,12 @@ { "$type": "Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage", "Id": "cc21b67c-3485-4dc6-b2af-105fda42a915", - "ParentId": "824a235d-da46-4c82-a16b-13efe347f492", + "ParentId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "Order": 1, "Name": "Root folder", "Enabled": true, "Profile": null, - "ProfileId": "824a235d-da46-4c82-a16b-13efe347f492", + "ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "LayerEffects": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] @@ -63,7 +63,7 @@ "$values": [] }, "Profile": null, - "ProfileId": "824a235d-da46-4c82-a16b-13efe347f492", + "ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "LayerEffects": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] @@ -307,7 +307,7 @@ "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9", "Path": "LayerBrush.Scale", - "Value": "{\"IsEmpty\":false,\"Width\":44.9,\"Height\":31.0}", + "Value": "{\"IsEmpty\":false,\"Width\":31.5,\"Height\":31.9}", "KeyframesEnabled": false, "KeyframeEntities": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", @@ -362,6 +362,21 @@ "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] } + }, + { + "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", + "PluginGuid": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "Path": "General.TransformMode", + "Value": "1", + "KeyframesEnabled": false, + "KeyframeEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + }, + "DataBindingEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + } } ] }, @@ -401,7 +416,7 @@ "$values": [] }, "Profile": null, - "ProfileId": "824a235d-da46-4c82-a16b-13efe347f492", + "ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "LayerEffects": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] @@ -608,7 +623,7 @@ "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a", "Path": "LayerBrush.Colors", - "Value": "{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.492},{\"Color\":\"#ffedff00\",\"Position\":0.905},{\"Color\":\"#00ff0000\",\"Position\":1.0}]}", + "Value": "{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffffdf00\",\"Position\":0.916},{\"Color\":\"#00ffcd00\",\"Position\":1.0},{\"Color\":\"#fff31900\",\"Position\":0.636},{\"Color\":\"#dbf41500\",\"Position\":0.5461956},{\"Color\":\"#00f60f00\",\"Position\":0.462},{\"Color\":\"#93f60d00\",\"Position\":0.3668478}]}", "KeyframesEnabled": false, "KeyframeEntities": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", @@ -678,12 +693,29 @@ "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] } + }, + { + "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", + "PluginGuid": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "Path": "General.TransformMode", + "Value": "0", + "KeyframesEnabled": false, + "KeyframeEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + }, + "DataBindingEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + } } ] }, "ExpandedPropertyGroups": { "$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib", - "$values": [] + "$values": [ + "LayerBrush" + ] }, "DisplayCondition": { "$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage", @@ -715,7 +747,7 @@ "$values": [] }, "Profile": null, - "ProfileId": "824a235d-da46-4c82-a16b-13efe347f492", + "ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad", "LayerEffects": { "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] @@ -977,13 +1009,29 @@ "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", "$values": [] } + }, + { + "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", + "PluginGuid": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "Path": "General.TransformMode", + "Value": "0", + "KeyframesEnabled": false, + "KeyframeEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + }, + "DataBindingEntities": { + "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib", + "$values": [] + } } ] }, "ExpandedPropertyGroups": { "$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib", "$values": [ - "LayerBrush" + "LayerBrush", + "General" ] }, "DisplayCondition": { @@ -997,9 +1045,9 @@ "Timeline": { "$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage", "StartSegmentLength": "00:00:00", - "MainSegmentLength": "00:00:05", + "MainSegmentLength": "00:00:03", "EndSegmentLength": "00:00:00", - "PlayMode": 0, + "PlayMode": 1, "StopMode": 0, "EventOverlapMode": 0 } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 7901cb0a0..7cb158c7e 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -33,7 +33,6 @@ namespace Artemis.Core.Services private readonly IRgbService _rgbService; private readonly ISurfaceService _surfaceService; private List _dataModelExpansions; - private IntroAnimation _introAnimation; private List _modules; // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else @@ -111,28 +110,24 @@ namespace Artemis.Core.Services private void PlayIntroAnimation() { - FrameRendering += DrawOverlay; - _introAnimation = new IntroAnimation(_logger, _profileService, _surfaceService); + // The intro is cool and all, but sometimes you just wanna see what you're working on straight away ^^ + if (Debugger.IsAttached) + return; + + IntroAnimation intro = new IntroAnimation(_logger, _profileService, _surfaceService); // Draw a white overlay over the device - void DrawOverlay(object sender, FrameRenderingEventArgs args) - { - if (_introAnimation == null) - args.Canvas.Clear(new SKColor(0, 0, 0)); - else - _introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info); - } - - TimeSpan introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.Timeline.Length); + void DrawOverlay(object? sender, FrameRenderingEventArgs args) => intro.Render(args.DeltaTime, args.Canvas); + FrameRendering += DrawOverlay; // Stop rendering after the profile finishes (take 1 second extra in case of slow updates) + TimeSpan introLength = intro.AnimationProfile.GetAllLayers().Max(l => l.Timeline.Length); Task.Run(async () => { await Task.Delay(introLength.Add(TimeSpan.FromSeconds(1))); FrameRendering -= DrawOverlay; - _introAnimation.AnimationProfile?.Dispose(); - _introAnimation = null; + intro.AnimationProfile.Dispose(); }); } @@ -167,7 +162,7 @@ namespace Artemis.Core.Services lock (_dataModelExpansions) { // Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated - foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.Enabled)) + foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.Enabled)) dataModelExpansion.Update(args.DeltaTime); } diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 3e4b90eb8..0529bfb90 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -26,13 +26,13 @@ namespace Artemis.Core public Profile AnimationProfile { get; set; } - public void Render(double deltaTime, SKCanvas canvas, SKImageInfo bitmapInfo) + public void Render(double deltaTime, SKCanvas canvas) { if (AnimationProfile == null) return; AnimationProfile.Update(deltaTime); - AnimationProfile.Render(canvas, bitmapInfo); + AnimationProfile.Render(canvas); } private void CreateIntroProfile() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 41401ab7e..f687f8eb9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -289,7 +289,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties bool hideRenderRelatedProperties = SelectedLayer?.LayerBrush != null && !SelectedLayer.LayerBrush.SupportsTransformation; SelectedLayer.General.ShapeType.IsHidden = hideRenderRelatedProperties; - SelectedLayer.General.ResizeMode.IsHidden = hideRenderRelatedProperties; SelectedLayer.General.BlendMode.IsHidden = hideRenderRelatedProperties; SelectedLayer.Transform.IsHidden = hideRenderRelatedProperties; diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 4c1fbc54b..72db66f48 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -122,11 +122,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree _updatingTree = false; // Auto-select the first layer - if (_profileEditorService.SelectedProfile != null && SelectedTreeItem == null) - { - if (_profileEditorService.SelectedProfile.GetRootFolder().Children.FirstOrDefault() is RenderProfileElement firstElement) - Execute.PostToUIThread(() => _profileEditorService.ChangeSelectedProfileElement(firstElement)); - } + // if (_profileEditorService.SelectedProfile != null && SelectedTreeItem == null) + // { + // if (_profileEditorService.SelectedProfile.GetRootFolder().Children.FirstOrDefault() is RenderProfileElement firstElement) + // Execute.PostToUIThread(() => _profileEditorService.ChangeSelectedProfileElement(firstElement)); + // } } private static DragDropType GetDragDropType(IDropInfo dropInfo) diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml index b8036acc8..846634132 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml @@ -17,6 +17,11 @@ + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index b338ddd13..cfa32b93e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System.Threading.Tasks; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -7,6 +8,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { public class LayerViewModel : TreeItemViewModel { + private readonly IProfileEditorService _profileEditorService; + public LayerViewModel(ProfileElement layer, IProfileEditorService profileEditorService, IDialogService dialogService, @@ -15,6 +18,16 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem ISurfaceService surfaceService) : base(layer, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, surfaceService) { + _profileEditorService = profileEditorService; + } + + public async void CopyElement() + { + Layer layer = Layer.CreateCopy(); + + _profileEditorService.UpdateSelectedProfile(); + // await Task.Delay(200); + _profileEditorService.ChangeSelectedProfileElement(layer); } public Layer Layer => ProfileElement as Layer;