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:
SpoinkyNL committed 2020-11-08 17:28:22 +01:00
1 parent d37c70371c
commit 427d3d2521
21 files changed
+394 -389

No files matched your search

+66 -79
View File
@@ -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
/// </summary>
public sealed class Folder : RenderProfileElement
{
private SKBitmap _folderBitmap;
/// <summary>
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
/// <paramref name="parent" />
@@ -30,6 +29,7 @@ namespace Artemis.Core
Profile = Parent.Profile;
Name = name;
Enabled = true;
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
@@ -47,6 +47,7 @@ namespace Artemis.Core
Name = folderEntity.Name;
Enabled = folderEntity.Enabled;
Order = folderEntity.Order;
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
@@ -68,6 +69,8 @@ namespace Artemis.Core
internal override RenderElementEntity RenderElementEntity => FolderEntity;
internal Renderer Renderer { get; }
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
@@ -124,6 +127,18 @@ namespace Artemis.Core
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()
{
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
+121 -240
View File
@@ -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<ArtemisLed> _leds;
@@ -39,6 +39,7 @@ namespace Artemis.Core
Enabled = true;
General = new LayerGeneralProperties();
Transform = new LayerTransformProperties();
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>();
@@ -57,6 +58,7 @@ namespace Artemis.Core
Parent = parent;
General = new LayerGeneralProperties();
Transform = new LayerTransformProperties();
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>();
@@ -112,6 +114,27 @@ namespace Artemis.Core
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 />
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
_layerBrush?.Dispose();
_general?.Dispose();
_layerBitmap?.Dispose();
_transform?.Dispose();
Renderer.Dispose();
base.Dispose(disposing);
}
@@ -272,16 +294,13 @@ namespace Artemis.Core
}
/// <inheritdoc />
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;
}
/// <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>
/// Creates a transformation matrix that applies the current transformation settings
/// </summary>
@@ -522,7 +463,7 @@ namespace Artemis.Core
/// surface
/// </param>
/// <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)
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;
}
}
/// <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;
// 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
@@ -8,19 +8,18 @@ namespace Artemis.Core
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
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")]
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")]
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
protected override void PopulateDefaults()
{
ShapeType.DefaultValue = LayerShapeType.Rectangle;
ResizeMode.DefaultValue = LayerResizeMode.Normal;
BlendMode.DefaultValue = SKBlendMode.SrcOver;
}
@@ -290,6 +290,7 @@ namespace Artemis.Core
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
{
Parent?.OnLayerPropertyOnCurrentValueSet(e);
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
}
+3 -2
View File
@@ -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);
}
}
@@ -15,8 +15,8 @@ namespace Artemis.Core
private int _order;
private ProfileElement _parent;
private Profile _profile;
protected bool Disposed;
protected List<ProfileElement> ChildrenList;
protected bool Disposed;
protected ProfileElement()
{
@@ -91,7 +91,7 @@ namespace Artemis.Core
/// <summary>
/// Renders the element
/// </summary>
public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
public abstract void Render(SKCanvas canvas);
/// <summary>
/// Resets the internal state of the element
@@ -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;
}
}
}