mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profiles - Reworked render pipeline
Profiles - Added two transform modes, normal and clip Intro animation - Disable with debugger attached Profile editor - Added layer copy
This commit is contained in:
parent
d37c70371c
commit
427d3d2521
@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -13,8 +14,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Folder : RenderProfileElement
|
public sealed class Folder : RenderProfileElement
|
||||||
{
|
{
|
||||||
private SKBitmap _folderBitmap;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
|
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
|
||||||
/// <paramref name="parent" />
|
/// <paramref name="parent" />
|
||||||
@ -30,6 +29,7 @@ namespace Artemis.Core
|
|||||||
Profile = Parent.Profile;
|
Profile = Parent.Profile;
|
||||||
Name = name;
|
Name = name;
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
|
Renderer = new Renderer();
|
||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
@ -47,6 +47,7 @@ namespace Artemis.Core
|
|||||||
Name = folderEntity.Name;
|
Name = folderEntity.Name;
|
||||||
Enabled = folderEntity.Enabled;
|
Enabled = folderEntity.Enabled;
|
||||||
Order = folderEntity.Order;
|
Order = folderEntity.Order;
|
||||||
|
Renderer = new Renderer();
|
||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
@ -68,6 +69,8 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
||||||
|
|
||||||
|
internal Renderer Renderer { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override List<ILayerProperty> GetAllLayerProperties()
|
public override List<ILayerProperty> GetAllLayerProperties()
|
||||||
{
|
{
|
||||||
@ -124,6 +127,18 @@ namespace Artemis.Core
|
|||||||
CalculateRenderProperties();
|
CalculateRenderProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a deep copy of the layer
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The newly created copy</returns>
|
||||||
|
public Folder CreateCopy()
|
||||||
|
{
|
||||||
|
FolderEntity entityCopy = JsonConvert.DeserializeObject<FolderEntity>(JsonConvert.SerializeObject(FolderEntity));
|
||||||
|
entityCopy.Id = Guid.NewGuid();
|
||||||
|
|
||||||
|
return new Folder(Profile, Parent, entityCopy);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||||
@ -154,8 +169,8 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Dispose();
|
profileElement.Dispose();
|
||||||
|
Renderer.Dispose();
|
||||||
|
|
||||||
_folderBitmap?.Dispose();
|
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,16 +214,13 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
#region Rendering
|
#region Rendering
|
||||||
|
|
||||||
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
|
public override void Render(SKCanvas canvas)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Folder");
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
if (!Enabled || !Children.Any(c => c.Enabled))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ensure the folder is ready
|
// Ensure the folder is ready
|
||||||
if (Path == null)
|
if (!Enabled || !Children.Any(c => c.Enabled) || Path == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// No point rendering if none of the children are going to render
|
// No point rendering if none of the children are going to render
|
||||||
@ -217,81 +229,56 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
lock (Timeline)
|
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();
|
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
|
#endregion
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using Artemis.Core.LayerBrushes;
|
|||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -16,7 +17,6 @@ namespace Artemis.Core
|
|||||||
public sealed class Layer : RenderProfileElement
|
public sealed class Layer : RenderProfileElement
|
||||||
{
|
{
|
||||||
private LayerGeneralProperties _general;
|
private LayerGeneralProperties _general;
|
||||||
private SKBitmap _layerBitmap;
|
|
||||||
private BaseLayerBrush _layerBrush;
|
private BaseLayerBrush _layerBrush;
|
||||||
private LayerShape _layerShape;
|
private LayerShape _layerShape;
|
||||||
private List<ArtemisLed> _leds;
|
private List<ArtemisLed> _leds;
|
||||||
@ -39,6 +39,7 @@ namespace Artemis.Core
|
|||||||
Enabled = true;
|
Enabled = true;
|
||||||
General = new LayerGeneralProperties();
|
General = new LayerGeneralProperties();
|
||||||
Transform = new LayerTransformProperties();
|
Transform = new LayerTransformProperties();
|
||||||
|
Renderer = new Renderer();
|
||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_leds = new List<ArtemisLed>();
|
_leds = new List<ArtemisLed>();
|
||||||
@ -57,6 +58,7 @@ namespace Artemis.Core
|
|||||||
Parent = parent;
|
Parent = parent;
|
||||||
General = new LayerGeneralProperties();
|
General = new LayerGeneralProperties();
|
||||||
Transform = new LayerTransformProperties();
|
Transform = new LayerTransformProperties();
|
||||||
|
Renderer = new Renderer();
|
||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_leds = new List<ArtemisLed>();
|
_leds = new List<ArtemisLed>();
|
||||||
@ -112,6 +114,27 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
||||||
|
|
||||||
|
internal Renderer Renderer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a deep copy of the layer
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The newly created copy</returns>
|
||||||
|
public Layer CreateCopy()
|
||||||
|
{
|
||||||
|
LayerEntity entityCopy = JsonConvert.DeserializeObject<LayerEntity>(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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override List<ILayerProperty> GetAllLayerProperties()
|
public override List<ILayerProperty> 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
|
// Brush first in case it depends on any of the other disposables during it's own disposal
|
||||||
_layerBrush?.Dispose();
|
_layerBrush?.Dispose();
|
||||||
|
|
||||||
_general?.Dispose();
|
_general?.Dispose();
|
||||||
_layerBitmap?.Dispose();
|
|
||||||
_transform?.Dispose();
|
_transform?.Dispose();
|
||||||
|
Renderer.Dispose();
|
||||||
|
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
@ -272,16 +294,13 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
|
public override void Render(SKCanvas canvas)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
if (!Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ensure the layer is ready
|
// 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;
|
return;
|
||||||
// Ensure the brush is ready
|
// Ensure the brush is ready
|
||||||
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
|
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
|
||||||
@ -289,14 +308,14 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
lock (Timeline)
|
lock (Timeline)
|
||||||
{
|
{
|
||||||
RenderLayer(Timeline, canvas);
|
RenderTimeline(Timeline, canvas);
|
||||||
foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
|
foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
|
||||||
RenderLayer(extraTimeline, canvas);
|
RenderTimeline(extraTimeline, canvas);
|
||||||
Timeline.ClearDelta();
|
Timeline.ClearDelta();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareForRender(Timeline timeline)
|
private void ApplyTimeline(Timeline timeline)
|
||||||
{
|
{
|
||||||
General.Update(timeline);
|
General.Update(timeline);
|
||||||
Transform.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)
|
if (timeline.IsFinished)
|
||||||
return;
|
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();
|
canvas.Restore();
|
||||||
_layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
|
Renderer.Close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using SKPath layerPath = new SKPath(Path);
|
private void DelegateRendering(SKPath path)
|
||||||
using SKCanvas layerCanvas = new SKCanvas(_layerBitmap);
|
{
|
||||||
using SKPaint layerPaint = new SKPaint {FilterQuality = SKFilterQuality.Low};
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
layerCanvas.Clear();
|
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))
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
baseLayerEffect.PreProcess(layerCanvas, _layerBitmap.Info, layerPath, layerPaint);
|
baseLayerEffect.PostProcess(Renderer.Canvas, path, Renderer.Paint);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CalculateRenderProperties()
|
internal void CalculateRenderProperties()
|
||||||
@ -462,7 +433,7 @@ namespace Artemis.Core
|
|||||||
OnRenderPropertiesUpdated();
|
OnRenderPropertiesUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
|
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool applyTranslation, bool zeroBased)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
@ -475,45 +446,15 @@ namespace Artemis.Core
|
|||||||
: new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
|
: new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
|
||||||
|
|
||||||
// Apply translation
|
// Apply translation
|
||||||
position.X += positionProperty.X * layerPath.Bounds.Width;
|
if (applyTranslation)
|
||||||
position.Y += positionProperty.Y * layerPath.Bounds.Height;
|
{
|
||||||
|
position.X += positionProperty.X * layerPath.Bounds.Width;
|
||||||
|
position.Y += positionProperty.Y * layerPath.Bounds.Height;
|
||||||
|
}
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
|
|
||||||
/// layer translations out
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a transformation matrix that applies the current transformation settings
|
/// Creates a transformation matrix that applies the current transformation settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -522,7 +463,7 @@ namespace Artemis.Core
|
|||||||
/// surface
|
/// surface
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>The transformation matrix containing the current transformation settings</returns>
|
/// <returns>The transformation matrix containing the current transformation settings</returns>
|
||||||
public SKMatrix GetTransformMatrix(bool zeroBased)
|
public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
@ -530,98 +471,38 @@ namespace Artemis.Core
|
|||||||
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
||||||
float rotationProperty = Transform.Rotation.CurrentValue;
|
float rotationProperty = Transform.Rotation.CurrentValue;
|
||||||
|
|
||||||
SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
|
SKPoint anchorPosition = GetLayerAnchorPosition(Path, true, zeroBased);
|
||||||
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
|
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
|
||||||
|
|
||||||
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
// 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 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 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 is always SKMatrix.Empty here...
|
||||||
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
|
transform = SKMatrix.MakeTranslation(x, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
|
|
||||||
/// layer translations out
|
|
||||||
/// </summary>
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Excludes the provided canvas from the translations applied to the layer by applying translations that cancel the
|
|
||||||
/// layer translations out
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The number of transformations applied</returns>
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y);
|
if (includeScale)
|
||||||
canvas.Translate(x * -1, y * -1);
|
{
|
||||||
return 2;
|
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
|
#endregion
|
||||||
@ -811,7 +692,7 @@ namespace Artemis.Core
|
|||||||
Rectangle
|
Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LayerResizeMode
|
public enum LayerTransformMode
|
||||||
{
|
{
|
||||||
Normal,
|
Normal,
|
||||||
Clip
|
Clip
|
||||||
|
|||||||
@ -8,19 +8,18 @@ namespace Artemis.Core
|
|||||||
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
|
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
|
||||||
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
|
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
|
||||||
|
|
||||||
[PropertyDescription(Name = "Resize mode", Description = "How to make the shape adjust to scale changes")]
|
|
||||||
public EnumLayerProperty<LayerResizeMode> ResizeMode { get; set; }
|
|
||||||
|
|
||||||
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
|
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
|
||||||
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
|
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
|
||||||
|
|
||||||
|
[PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")]
|
||||||
|
public EnumLayerProperty<LayerTransformMode> TransformMode { get; set; }
|
||||||
|
|
||||||
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
|
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
|
||||||
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
|
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
|
||||||
|
|
||||||
protected override void PopulateDefaults()
|
protected override void PopulateDefaults()
|
||||||
{
|
{
|
||||||
ShapeType.DefaultValue = LayerShapeType.Rectangle;
|
ShapeType.DefaultValue = LayerShapeType.Rectangle;
|
||||||
ResizeMode.DefaultValue = LayerResizeMode.Normal;
|
|
||||||
BlendMode.DefaultValue = SKBlendMode.SrcOver;
|
BlendMode.DefaultValue = SKBlendMode.SrcOver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -290,6 +290,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
|
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
|
||||||
{
|
{
|
||||||
|
Parent?.OnLayerPropertyOnCurrentValueSet(e);
|
||||||
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
|
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
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)
|
lock (this)
|
||||||
{
|
{
|
||||||
@ -76,7 +77,7 @@ namespace Artemis.Core
|
|||||||
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
|
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Render(canvas, canvasInfo);
|
profileElement.Render(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ namespace Artemis.Core
|
|||||||
private int _order;
|
private int _order;
|
||||||
private ProfileElement _parent;
|
private ProfileElement _parent;
|
||||||
private Profile _profile;
|
private Profile _profile;
|
||||||
protected bool Disposed;
|
|
||||||
protected List<ProfileElement> ChildrenList;
|
protected List<ProfileElement> ChildrenList;
|
||||||
|
protected bool Disposed;
|
||||||
|
|
||||||
protected ProfileElement()
|
protected ProfileElement()
|
||||||
{
|
{
|
||||||
@ -91,7 +91,7 @@ namespace Artemis.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Renders the element
|
/// Renders the element
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
|
public abstract void Render(SKCanvas canvas);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the internal state of the element
|
/// Resets the internal state of the element
|
||||||
|
|||||||
83
src/Artemis.Core/Models/Profile/Renderer.cs
Normal file
83
src/Artemis.Core/Models/Profile/Renderer.cs
Normal file
@ -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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the render context using the dimensions of the provided path
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -107,7 +107,7 @@ namespace Artemis.Core.LayerBrushes
|
|||||||
// but LayerBrush<T> and RgbNetLayerBrush<T> outside the core
|
// but LayerBrush<T> and RgbNetLayerBrush<T> outside the core
|
||||||
internal abstract void Initialize();
|
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)
|
internal virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -22,14 +22,13 @@ namespace Artemis.Core.LayerBrushes
|
|||||||
/// <para>Called during rendering or layer preview, in the order configured on the layer</para>
|
/// <para>Called during rendering or layer preview, in the order configured on the layer</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="canvas">The layer canvas</param>
|
/// <param name="canvas">The layer canvas</param>
|
||||||
/// <param name="canvasInfo"></param>
|
|
||||||
/// <param name="path">The path to be filled, represents the shape</param>
|
/// <param name="path">The path to be filled, represents the shape</param>
|
||||||
/// <param name="paint">The paint to be used to fill the shape</param>
|
/// <param name="paint">The paint to be used to fill the shape</param>
|
||||||
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()
|
internal override void Initialize()
|
||||||
|
|||||||
@ -28,18 +28,11 @@ namespace Artemis.Core.LayerBrushes
|
|||||||
/// <returns>The color the LED will receive</returns>
|
/// <returns>The color the LED will receive</returns>
|
||||||
public abstract SKColor GetColor(ArtemisLed led, SKPoint renderPoint);
|
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
|
// We don't want rotation 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.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)
|
||||||
|
canvas.SetMatrix(canvas.TotalMatrix.PreConcat(Layer.GetTransformMatrix(true, false, false, true).Invert()));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
using SKPath pointsPath = new SKPath();
|
using SKPath pointsPath = new SKPath();
|
||||||
using SKPaint ledPaint = new SKPaint();
|
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;
|
SKPoint[] points = pointsPath.Points;
|
||||||
for (int index = 0; index < Layer.Leds.Count; index++)
|
for (int index = 0; index < Layer.Leds.Count; index++)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -68,7 +68,7 @@ namespace Artemis.Core.LayerBrushes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not used in this effect type
|
// 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");
|
throw new NotImplementedException("RGB.NET layer effectes do not implement InternalRender");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,19 +131,17 @@ namespace Artemis.Core.LayerEffects
|
|||||||
/// Called before the layer or folder will be rendered
|
/// Called before the layer or folder will be rendered
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="canvas">The canvas used to render the frame</param>
|
/// <param name="canvas">The canvas used to render the frame</param>
|
||||||
/// <param name="canvasInfo">Info on the canvas size and pixel type</param>
|
|
||||||
/// <param name="renderBounds">The bounds this layer/folder will render in</param>
|
/// <param name="renderBounds">The bounds this layer/folder will render in</param>
|
||||||
/// <param name="paint">The paint this layer/folder will use to render</param>
|
/// <param name="paint">The paint this layer/folder will use to render</param>
|
||||||
public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint);
|
public abstract void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after the layer of folder has been rendered
|
/// Called after the layer of folder has been rendered
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="canvas">The canvas used to render the frame</param>
|
/// <param name="canvas">The canvas used to render the frame</param>
|
||||||
/// <param name="canvasInfo">Info on the canvas size and pixel type</param>
|
|
||||||
/// <param name="renderBounds">The bounds this layer/folder rendered in</param>
|
/// <param name="renderBounds">The bounds this layer/folder rendered in</param>
|
||||||
/// <param name="paint">The paint this layer/folder used to render</param>
|
/// <param name="paint">The paint this layer/folder used to render</param>
|
||||||
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
|
// Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything
|
||||||
// but LayerEffect<T> outside the core
|
// but LayerEffect<T> outside the core
|
||||||
|
|||||||
@ -40,12 +40,12 @@ namespace Artemis.Core.LayerEffects.Placeholder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint)
|
public override void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint)
|
public override void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -175,7 +175,7 @@ namespace Artemis.Core.Modules
|
|||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
// Render the profile
|
// Render the profile
|
||||||
ActiveProfile?.Render(canvas, canvasInfo);
|
ActiveProfile?.Render(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileRendered(deltaTime, surface, canvas, canvasInfo);
|
ProfileRendered(deltaTime, surface, canvas, canvasInfo);
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.ProfileEntity, Artemis.Storage",
|
"$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",
|
"PluginGuid": "0de2991a-d7b8-4f61-ae4e-6623849215b5",
|
||||||
"Name": "Intro animation - Imported",
|
"Name": "Intro animation",
|
||||||
"IsActive": true,
|
"IsActive": true,
|
||||||
"Folders": {
|
"Folders": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$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",
|
"$type": "Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage",
|
||||||
"Id": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
"Id": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
||||||
"ParentId": "824a235d-da46-4c82-a16b-13efe347f492",
|
"ParentId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad",
|
||||||
"Order": 1,
|
"Order": 1,
|
||||||
"Name": "Root folder",
|
"Name": "Root folder",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
"ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad",
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$values": []
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
"ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad",
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$values": []
|
||||||
@ -307,7 +307,7 @@
|
|||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
"PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9",
|
"PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9",
|
||||||
"Path": "LayerBrush.Scale",
|
"Path": "LayerBrush.Scale",
|
||||||
"Value": "{\"IsEmpty\":false,\"Width\":44.9,\"Height\":31.0}",
|
"Value": "{\"IsEmpty\":false,\"Width\":31.5,\"Height\":31.9}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$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",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$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": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
"ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad",
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$values": []
|
||||||
@ -608,7 +623,7 @@
|
|||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
||||||
"Path": "LayerBrush.Colors",
|
"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,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$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",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$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": {
|
"ExpandedPropertyGroups": {
|
||||||
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$values": [
|
||||||
|
"LayerBrush"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"DisplayCondition": {
|
"DisplayCondition": {
|
||||||
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
|
||||||
@ -715,7 +747,7 @@
|
|||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
"ProfileId": "ebf09eb7-77c2-4af5-9695-4db0615a42ad",
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$values": []
|
||||||
@ -977,13 +1009,29 @@
|
|||||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$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": {
|
"ExpandedPropertyGroups": {
|
||||||
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
||||||
"$values": [
|
"$values": [
|
||||||
"LayerBrush"
|
"LayerBrush",
|
||||||
|
"General"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DisplayCondition": {
|
"DisplayCondition": {
|
||||||
@ -997,9 +1045,9 @@
|
|||||||
"Timeline": {
|
"Timeline": {
|
||||||
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
|
||||||
"StartSegmentLength": "00:00:00",
|
"StartSegmentLength": "00:00:00",
|
||||||
"MainSegmentLength": "00:00:05",
|
"MainSegmentLength": "00:00:03",
|
||||||
"EndSegmentLength": "00:00:00",
|
"EndSegmentLength": "00:00:00",
|
||||||
"PlayMode": 0,
|
"PlayMode": 1,
|
||||||
"StopMode": 0,
|
"StopMode": 0,
|
||||||
"EventOverlapMode": 0
|
"EventOverlapMode": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,6 @@ namespace Artemis.Core.Services
|
|||||||
private readonly IRgbService _rgbService;
|
private readonly IRgbService _rgbService;
|
||||||
private readonly ISurfaceService _surfaceService;
|
private readonly ISurfaceService _surfaceService;
|
||||||
private List<BaseDataModelExpansion> _dataModelExpansions;
|
private List<BaseDataModelExpansion> _dataModelExpansions;
|
||||||
private IntroAnimation _introAnimation;
|
|
||||||
private List<Module> _modules;
|
private List<Module> _modules;
|
||||||
|
|
||||||
// ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else
|
// 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()
|
private void PlayIntroAnimation()
|
||||||
{
|
{
|
||||||
FrameRendering += DrawOverlay;
|
// The intro is cool and all, but sometimes you just wanna see what you're working on straight away ^^
|
||||||
_introAnimation = new IntroAnimation(_logger, _profileService, _surfaceService);
|
if (Debugger.IsAttached)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IntroAnimation intro = new IntroAnimation(_logger, _profileService, _surfaceService);
|
||||||
|
|
||||||
// Draw a white overlay over the device
|
// Draw a white overlay over the device
|
||||||
void DrawOverlay(object sender, FrameRenderingEventArgs args)
|
void DrawOverlay(object? sender, FrameRenderingEventArgs args) => intro.Render(args.DeltaTime, args.Canvas);
|
||||||
{
|
FrameRendering += DrawOverlay;
|
||||||
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);
|
|
||||||
|
|
||||||
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
|
// 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 () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(introLength.Add(TimeSpan.FromSeconds(1)));
|
await Task.Delay(introLength.Add(TimeSpan.FromSeconds(1)));
|
||||||
FrameRendering -= DrawOverlay;
|
FrameRendering -= DrawOverlay;
|
||||||
|
|
||||||
_introAnimation.AnimationProfile?.Dispose();
|
intro.AnimationProfile.Dispose();
|
||||||
_introAnimation = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +162,7 @@ namespace Artemis.Core.Services
|
|||||||
lock (_dataModelExpansions)
|
lock (_dataModelExpansions)
|
||||||
{
|
{
|
||||||
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
|
// 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);
|
dataModelExpansion.Update(args.DeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,13 +26,13 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
public Profile AnimationProfile { get; set; }
|
public Profile AnimationProfile { get; set; }
|
||||||
|
|
||||||
public void Render(double deltaTime, SKCanvas canvas, SKImageInfo bitmapInfo)
|
public void Render(double deltaTime, SKCanvas canvas)
|
||||||
{
|
{
|
||||||
if (AnimationProfile == null)
|
if (AnimationProfile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AnimationProfile.Update(deltaTime);
|
AnimationProfile.Update(deltaTime);
|
||||||
AnimationProfile.Render(canvas, bitmapInfo);
|
AnimationProfile.Render(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateIntroProfile()
|
private void CreateIntroProfile()
|
||||||
|
|||||||
@ -289,7 +289,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
|||||||
bool hideRenderRelatedProperties = SelectedLayer?.LayerBrush != null && !SelectedLayer.LayerBrush.SupportsTransformation;
|
bool hideRenderRelatedProperties = SelectedLayer?.LayerBrush != null && !SelectedLayer.LayerBrush.SupportsTransformation;
|
||||||
|
|
||||||
SelectedLayer.General.ShapeType.IsHidden = hideRenderRelatedProperties;
|
SelectedLayer.General.ShapeType.IsHidden = hideRenderRelatedProperties;
|
||||||
SelectedLayer.General.ResizeMode.IsHidden = hideRenderRelatedProperties;
|
|
||||||
SelectedLayer.General.BlendMode.IsHidden = hideRenderRelatedProperties;
|
SelectedLayer.General.BlendMode.IsHidden = hideRenderRelatedProperties;
|
||||||
SelectedLayer.Transform.IsHidden = hideRenderRelatedProperties;
|
SelectedLayer.Transform.IsHidden = hideRenderRelatedProperties;
|
||||||
|
|
||||||
|
|||||||
@ -122,11 +122,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
_updatingTree = false;
|
_updatingTree = false;
|
||||||
|
|
||||||
// Auto-select the first layer
|
// Auto-select the first layer
|
||||||
if (_profileEditorService.SelectedProfile != null && SelectedTreeItem == null)
|
// if (_profileEditorService.SelectedProfile != null && SelectedTreeItem == null)
|
||||||
{
|
// {
|
||||||
if (_profileEditorService.SelectedProfile.GetRootFolder().Children.FirstOrDefault() is RenderProfileElement firstElement)
|
// if (_profileEditorService.SelectedProfile.GetRootFolder().Children.FirstOrDefault() is RenderProfileElement firstElement)
|
||||||
Execute.PostToUIThread(() => _profileEditorService.ChangeSelectedProfileElement(firstElement));
|
// Execute.PostToUIThread(() => _profileEditorService.ChangeSelectedProfileElement(firstElement));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
||||||
|
|||||||
@ -17,6 +17,11 @@
|
|||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="RenameBox" />
|
<materialDesign:PackIcon Kind="RenameBox" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action CopyElement}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Delete" Command="{s:Action DeleteElement}">
|
<MenuItem Header="Delete" Command="{s:Action DeleteElement}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Artemis.Core;
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
@ -7,6 +8,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
{
|
{
|
||||||
public class LayerViewModel : TreeItemViewModel
|
public class LayerViewModel : TreeItemViewModel
|
||||||
{
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
public LayerViewModel(ProfileElement layer,
|
public LayerViewModel(ProfileElement layer,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDialogService dialogService,
|
IDialogService dialogService,
|
||||||
@ -15,6 +18,16 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
ISurfaceService surfaceService) :
|
ISurfaceService surfaceService) :
|
||||||
base(layer, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, 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;
|
public Layer Layer => ProfileElement as Layer;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user