diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 8283d2827..3389ff82e 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -55,11 +55,11 @@ namespace Artemis.Core.Models.Profile
profileElement.Update(deltaTime);
}
- public override void Render(double deltaTime, SKCanvas canvas)
+ public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
// Folders don't render but their children do
foreach (var profileElement in Children)
- profileElement.Render(deltaTime, canvas);
+ profileElement.Render(deltaTime, canvas, canvasInfo);
}
public Folder AddFolder(string name)
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index d10a149ca..361e32d95 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -72,7 +72,7 @@ namespace Artemis.Core.Models.Profile
///
public SKPath Path
{
- get => _path;
+ get => _path != null ? new SKPath(_path) : null;
private set
{
_path = value;
@@ -231,7 +231,7 @@ namespace Artemis.Core.Models.Profile
LayerBrush?.Update(deltaTime);
}
- public override void Render(double deltaTime, SKCanvas canvas)
+ public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
if (Path == null || LayerShape?.Path == null)
return;
@@ -247,10 +247,10 @@ namespace Artemis.Core.Models.Profile
switch (FillTypeProperty.CurrentValue)
{
case LayerFillType.Stretch:
- StretchRender(canvas, paint);
+ StretchRender(canvas, canvasInfo, paint);
break;
case LayerFillType.Clip:
- ClipRender(canvas, paint, true);
+ ClipRender(canvas, canvasInfo, paint);
break;
default:
throw new ArgumentOutOfRangeException();
@@ -260,7 +260,7 @@ namespace Artemis.Core.Models.Profile
canvas.Restore();
}
- private void StretchRender(SKCanvas canvas, SKPaint paint)
+ private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint)
{
// Apply transformations
var sizeProperty = ScaleProperty.CurrentValue;
@@ -278,10 +278,10 @@ namespace Artemis.Core.Models.Profile
canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y);
canvas.Translate(x, y);
- LayerBrush?.Render(canvas, new SKPath(LayerShape.Path), paint);
+ LayerBrush?.Render(canvas, canvasInfo, new SKPath(LayerShape.Path), paint);
}
- private void ClipRender(SKCanvas canvas, SKPaint paint, bool rotatePath)
+ private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint)
{
// Apply transformations
var sizeProperty = ScaleProperty.CurrentValue;
@@ -303,10 +303,17 @@ namespace Artemis.Core.Models.Profile
canvas.RotateDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y);
canvas.Translate(x, y);
- // Render the entire layer, the clip will ensure the shape is matched
+ // 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
+ var 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)
+ );
var renderPath = new SKPath();
- renderPath.AddRect(Path.Bounds);
- LayerBrush?.Render(canvas, renderPath, paint);
+ renderPath.AddRect(boundsRect);
+ LayerBrush?.Render(canvas, canvasInfo, renderPath, paint);
}
internal void CalculateRenderProperties()
@@ -332,7 +339,7 @@ namespace Artemis.Core.Models.Profile
OnRenderPropertiesUpdated();
}
- private SKPoint GetLayerAnchorPosition()
+ internal SKPoint GetLayerAnchorPosition()
{
var positionProperty = PositionProperty.CurrentValue;
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index 6cb9da5e2..2302fc436 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -57,7 +57,7 @@ namespace Artemis.Core.Models.Profile
}
}
- public override void Render(double deltaTime, SKCanvas canvas)
+ public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
lock (this)
{
@@ -65,7 +65,7 @@ namespace Artemis.Core.Models.Profile
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
foreach (var profileElement in Children)
- profileElement.Render(deltaTime, canvas);
+ profileElement.Render(deltaTime, canvas, canvasInfo);
}
}
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 0718aa2f7..89d74aa99 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -44,7 +44,7 @@ namespace Artemis.Core.Models.Profile
///
/// Renders the element
///
- public abstract void Render(double deltaTime, SKCanvas canvas);
+ public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
public List GetAllFolders()
{
diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs
index 9050f9646..67b8e5f97 100644
--- a/src/Artemis.Core/Plugins/Abstract/Module.cs
+++ b/src/Artemis.Core/Plugins/Abstract/Module.cs
@@ -44,7 +44,8 @@ namespace Artemis.Core.Plugins.Abstract
/// Time since the last render
/// The RGB Surface to render to
///
- public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas);
+ ///
+ public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo);
///
/// Called when the module's view model is being show, return view models here to create tabs for them
diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs
index d7a0ab15f..72c07af99 100644
--- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs
+++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs
@@ -31,12 +31,12 @@ namespace Artemis.Core.Plugins.Abstract
}
///
- public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas)
+ public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo)
{
lock (this)
{
// Render the profile
- ActiveProfile?.Render(deltaTime, canvas);
+ ActiveProfile?.Render(deltaTime, canvas, canvasInfo);
}
}
diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs
index 43305b4a8..4c9e66790 100644
--- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs
@@ -37,9 +37,10 @@ namespace Artemis.Core.Plugins.LayerBrush
/// Called during rendering, in the order configured on the layer
///
/// The layer canvas
+ ///
/// The path to be filled, represents the shape
/// The paint to be used to fill the shape
- public virtual void Render(SKCanvas canvas, SKPath path, SKPaint paint)
+ public virtual void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
{
}
diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs
index 3e37bba20..bae50acb3 100644
--- a/src/Artemis.Core/RGB.NET/BitmapBrush.cs
+++ b/src/Artemis.Core/RGB.NET/BitmapBrush.cs
@@ -86,32 +86,28 @@ namespace Artemis.Core.RGB.NET
var sampleSize = _sampleSizeSetting.Value;
var sampleDepth = Math.Sqrt(sampleSize).RoundToInt();
+ var bitmapWidth = Bitmap.Width;
+ var bitmapHeight = Bitmap.Height;
+
foreach (var renderTarget in renderTargets)
{
// SKRect has all the good stuff we need
- var rect = SKRect.Create(
- (float) ((renderTarget.Rectangle.Location.X + 4) * Scale.Horizontal),
- (float) ((renderTarget.Rectangle.Location.Y + 4) * Scale.Vertical),
- (float) ((renderTarget.Rectangle.Size.Width - 8) * Scale.Horizontal),
- (float) ((renderTarget.Rectangle.Size.Height - 8) * Scale.Vertical)
- );
+ var left = (int) ((renderTarget.Rectangle.Location.X + 4) * Scale.Horizontal);
+ var top = (int) ((renderTarget.Rectangle.Location.Y + 4) * Scale.Vertical);
+ var width = (int) ((renderTarget.Rectangle.Size.Width - 8) * Scale.Horizontal);
+ var height = (int) ((renderTarget.Rectangle.Size.Height - 8) * Scale.Vertical);
- var verticalSteps = rect.Height / (sampleDepth - 1);
- var horizontalSteps = rect.Width / (sampleDepth - 1);
+ var verticalSteps = height / (sampleDepth - 1);
+ var horizontalSteps = width / (sampleDepth - 1);
- var a = 0;
- var r = 0;
- var g = 0;
- var b = 0;
-
- // TODO: Compare this with LINQ, might be quicker and cleaner
+ int a = 0, r = 0, g = 0, b = 0;
for (var horizontalStep = 0; horizontalStep < sampleDepth; horizontalStep++)
{
for (var verticalStep = 0; verticalStep < sampleDepth; verticalStep++)
{
- var x = (rect.Left + horizontalSteps * horizontalStep).RoundToInt();
- var y = (rect.Top + verticalSteps * verticalStep).RoundToInt();
- if (x < 0 || x > Bitmap.Width || y < 0 || y > Bitmap.Height)
+ var x = left + horizontalSteps * horizontalStep;
+ var y = top + verticalSteps * verticalStep;
+ if (x < 0 || x > bitmapWidth || y < 0 || y > bitmapHeight)
continue;
var color = Bitmap.GetPixel(x, y);
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 4ca70e968..80203346f 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -127,7 +127,7 @@ namespace Artemis.Core.Services
lock (_modules)
{
foreach (var module in _modules)
- module.Render(args.DeltaTime, _surfaceService.ActiveSurface, canvas);
+ module.Render(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info);
}
}
diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs
index 1148bbaa1..19ca009a7 100644
--- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs
@@ -8,8 +8,24 @@ namespace Artemis.Core.Services.Interfaces
{
public interface IRgbService : IArtemisService
{
+ ///
+ /// Gets or sets the RGB surface rendering is performed on
+ ///
RGBSurface Surface { get; set; }
+
+ ///
+ /// Gets the bitmap brush used to convert the rendered frame to LED-colors
+ ///
BitmapBrush BitmapBrush { get; }
+
+ ///
+ /// Gets the scale the frames are rendered on, a scale of 1.0 means 1 pixel = 1mm
+ ///
+ double RenderScale { get; }
+
+ ///
+ /// Gets all loaded RGB devices
+ ///
IReadOnlyCollection LoadedDevices { get; }
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs
index 2eb5e0f16..10fa534fe 100644
--- a/src/Artemis.Core/Services/RgbService.cs
+++ b/src/Artemis.Core/Services/RgbService.cs
@@ -48,6 +48,8 @@ namespace Artemis.Core.Services
public IReadOnlyCollection LoadedDevices => _loadedDevices.AsReadOnly();
+ public double RenderScale => _renderScaleSetting.Value;
+
public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
{
Surface.LoadDevices(deviceProvider);
diff --git a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs
index da15fd3a5..7206f1c79 100644
--- a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs
+++ b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs
@@ -14,6 +14,7 @@ namespace Artemis.Plugins.LayerBrushes.Color
private SKColor _color;
private SKPaint _paint;
private SKShader _shader;
+ private SKRect _shaderBounds;
public ColorBrush(Layer layer, LayerBrushDescriptor descriptor) : base(layer, descriptor)
{
@@ -30,9 +31,9 @@ namespace Artemis.Plugins.LayerBrushes.Color
_testColors.Add(SKColor.FromHsv(0, 100, 100));
}
- CreateShader();
- Layer.RenderPropertiesUpdated += (sender, args) => CreateShader();
- GradientTypeProperty.ValueChanged += (sender, args) => CreateShader();
+ CreateShader(_shaderBounds);
+ Layer.RenderPropertiesUpdated += (sender, args) => CreateShader(_shaderBounds);
+ GradientTypeProperty.ValueChanged += (sender, args) => CreateShader(_shaderBounds);
}
public LayerProperty ColorProperty { get; set; }
@@ -44,19 +45,22 @@ namespace Artemis.Plugins.LayerBrushes.Color
if (_color != ColorProperty.CurrentValue)
{
_color = ColorProperty.CurrentValue;
- CreateShader();
+ CreateShader(_shaderBounds);
}
base.Update(deltaTime);
}
- public override void Render(SKCanvas canvas, SKPath path, SKPaint paint)
+ public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
{
+ if (path.Bounds != _shaderBounds)
+ CreateShader(path.Bounds);
+
paint.Shader = _shader;
canvas.DrawPath(path, paint);
}
- private void CreateShader()
+ private void CreateShader(SKRect pathBounds)
{
var center = new SKPoint(Layer.Bounds.MidX, Layer.Bounds.MidY);
SKShader shader;
@@ -66,10 +70,10 @@ namespace Artemis.Plugins.LayerBrushes.Color
shader = SKShader.CreateColor(_color);
break;
case GradientType.LinearGradient:
- shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.Bounds.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat);
+ shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(pathBounds.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat);
break;
case GradientType.RadialGradient:
- shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.Bounds.Width, Layer.Bounds.Height), _testColors.ToArray(), SKShaderTileMode.Repeat);
+ shader = SKShader.CreateRadialGradient(center, Math.Min(pathBounds.Width, pathBounds.Height), _testColors.ToArray(), SKShaderTileMode.Repeat);
break;
case GradientType.SweepGradient:
shader = SKShader.CreateSweepGradient(center, _testColors.ToArray(), null, SKShaderTileMode.Clamp, 0, 360);
@@ -82,6 +86,7 @@ namespace Artemis.Plugins.LayerBrushes.Color
var oldPaint = _paint;
_shader = shader;
_paint = new SKPaint {Shader = _shader, FilterQuality = SKFilterQuality.Low};
+ _shaderBounds = pathBounds;
oldShader?.Dispose();
oldPaint?.Dispose();
}
diff --git a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs
index 2cd3c93e8..a19d12079 100644
--- a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs
+++ b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs
@@ -2,6 +2,7 @@
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerBrush;
+using Artemis.Core.Services.Interfaces;
using Artemis.Plugins.LayerBrushes.Noise.Utilities;
using SkiaSharp;
@@ -10,16 +11,23 @@ namespace Artemis.Plugins.LayerBrushes.Noise
public class NoiseBrush : LayerBrush
{
private static readonly Random Rand = new Random();
+ private readonly OpenSimplexNoise _noise;
+ private readonly IRgbService _rgbService;
+ private SKBitmap _bitmap;
private float _renderScale;
- private readonly OpenSimplexNoise _noise;
private float _x;
private float _y;
private float _z;
- private SKBitmap _bitmap;
- public NoiseBrush(Layer layer, LayerBrushDescriptor descriptor) : base(layer, descriptor)
+ public NoiseBrush(Layer layer, LayerBrushDescriptor descriptor, IRgbService rgbService) : base(layer, descriptor)
{
+ _rgbService = rgbService;
+ _x = Rand.Next(0, 4096);
+ _y = Rand.Next(0, 4096);
+ _z = Rand.Next(0, 4096);
+ _noise = new OpenSimplexNoise(Rand.Next(0, 4096));
+
MainColorProperty = RegisterLayerProperty("Brush.MainColor", "Main color", "The main color of the noise.");
SecondaryColorProperty = RegisterLayerProperty("Brush.SecondaryColor", "Secondary color", "The secondary color of the noise.");
ScaleProperty = RegisterLayerProperty("Brush.Scale", "Scale", "The scale of the noise.");
@@ -27,10 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise
AnimationSpeedProperty = RegisterLayerProperty("Brush.AnimationSpeed", "Animation speed", "The speed at which the noise moves.");
ScaleProperty.InputAffix = "%";
- _x = Rand.Next(0, 4096);
- _y = Rand.Next(0, 4096);
- _z = Rand.Next(0, 4096);
- _noise = new OpenSimplexNoise(Rand.Next(0, 4096));
+ DetermineRenderScale();
}
public LayerProperty MainColorProperty { get; set; }
@@ -52,28 +57,35 @@ namespace Artemis.Plugins.LayerBrushes.Noise
_y = 0;
if (float.IsPositiveInfinity(_z) || float.IsNegativeInfinity(_z) || float.IsNaN(_z))
_z = 0;
+
+ DetermineRenderScale();
base.Update(deltaTime);
}
- public override void Render(SKCanvas canvas, SKPath path, SKPaint paint)
+ public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
{
var mainColor = MainColorProperty.CurrentValue;
var scale = ScaleProperty.CurrentValue;
- // Scale down the render path to avoid computing a value for every pixel
- var width = Math.Floor(path.Bounds.Width * RenderScale);
- var height = Math.Floor(path.Bounds.Height * RenderScale);
-
- CreateBitmap((int) width, (int) height);
var opacity = (float) Math.Round(mainColor.Alpha / 255.0, 2, MidpointRounding.AwayFromZero);
- _bitmap.Erase(SKColor.Empty);
+ // Scale down the render path to avoid computing a value for every pixel
+ var width = Math.Floor(path.Bounds.Width * _renderScale);
+ var height = Math.Floor(path.Bounds.Height * _renderScale);
+
+ CreateBitmap((int) width, (int) height);
+
for (var x = 0; x < width; x++)
{
var scrolledX = x + _x;
for (var y = 0; y < height; y++)
{
var scrolledY = y + _y;
- var v = _noise.Evaluate(0.1f * scale.Width * scrolledX / width, 0.1f * scale.Height * scrolledY / height, _z);
+ var evalX = 0.1 * scale.Width * scrolledX / width;
+ var evalY = 0.1 * scale.Height * scrolledY / height;
+ if (double.IsNaN(evalX) || double.IsNaN(evalY))
+ continue;
+
+ var v = _noise.Evaluate(evalX, evalY, _z);
var alpha = (byte) Math.Max(0, Math.Min(255, v * 1024));
_bitmap.SetPixel(x, y, new SKColor(mainColor.Red, mainColor.Green, mainColor.Blue, (byte) (alpha * opacity)));
}
@@ -81,7 +93,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise
var bitmapTransform = SKMatrix.Concat(
SKMatrix.MakeTranslation(path.Bounds.Left, path.Bounds.Top),
- SKMatrix.MakeScale(1f / RenderScale, 1f / RenderScale)
+ SKMatrix.MakeScale(1f / _renderScale, 1f / _renderScale)
);
using (var backgroundShader = SKShader.CreateColor(SecondaryColorProperty.CurrentValue))
using (var foregroundShader = SKShader.CreateBitmap(_bitmap, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, bitmapTransform))
@@ -94,12 +106,15 @@ namespace Artemis.Plugins.LayerBrushes.Noise
}
}
+ private void DetermineRenderScale()
+ {
+ _renderScale = (float) (0.125f / _rgbService.RenderScale);
+ }
+
private void CreateBitmap(int width, int height)
{
if (_bitmap == null)
- {
_bitmap = new SKBitmap(new SKImageInfo(width, height));
- }
else if (_bitmap.Width != width || _bitmap.Height != height)
{
_bitmap.Dispose();