diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs
index 14aae47e2..0337d2b3c 100644
--- a/src/Artemis.Core/RGB.NET/SKTexture.cs
+++ b/src/Artemis.Core/RGB.NET/SKTexture.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
using HPPH;
-using HPPH.SkiaSharp;
using RGB.NET.Core;
using RGB.NET.Presets.Extensions;
using SkiaSharp;
@@ -25,33 +24,85 @@ public sealed class SKTexture : ITexture, IDisposable
Surface = graphicsContext == null
? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
+ _readback = new SKBitmap(new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Unpremul));
foreach (ArtemisDevice artemisDevice in devices)
{
foreach (ArtemisLed artemisLed in artemisDevice.Leds)
- {
- _ledRects[artemisLed.RgbLed] = SKRectI.Create(
- (int)(artemisLed.AbsoluteRectangle.Left * RenderScale),
- (int)(artemisLed.AbsoluteRectangle.Top * RenderScale),
- (int)(artemisLed.AbsoluteRectangle.Width * RenderScale),
- (int)(artemisLed.AbsoluteRectangle.Height * RenderScale)
- );
- }
+ _ledRects[artemisLed.RgbLed] = CreateSampleRect(artemisLed.RgbLed.AbsoluteBoundary, scale, width, height);
}
}
#endregion
+ private static SKRectI CreateSampleRect(Rectangle boundary, float scale, int width, int height)
+ {
+ (int left, int right) = ScaleSpan(boundary.Location.X, boundary.Size.Width, scale, width);
+ (int top, int bottom) = ScaleSpan(boundary.Location.Y, boundary.Size.Height, scale, height);
+ return new SKRectI(left, top, right, bottom);
+ }
+
+ private static (int Start, int End) ScaleSpan(float location, float size, float scale, int max)
+ {
+ float start = location * scale;
+ float end = (location + size) * scale;
+ if (end <= 0 || start >= max)
+ return (0, 0);
+
+ // Always sample at least one pixel
+ int startPixel = Math.Clamp(start.RoundToInt(), 0, max - 1);
+ int endPixel = Math.Clamp(end.RoundToInt(), startPixel + 1, max);
+ return (startPixel, endPixel);
+ }
+
internal Color GetColorAtRenderTarget(in RenderTarget renderTarget)
{
- if (_image == null) return Color.Transparent;
+ if (!_hasPixels) return Color.Transparent;
SKRectI skRectI = _ledRects[renderTarget.Led];
if (skRectI.Width <= 0 || skRectI.Height <= 0)
return Color.Transparent;
- return _image[skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height].Average().ToColor();
+ return AveragePixels(_readback, skRectI);
+ }
+
+ internal static unsafe Color AveragePixels(SKBitmap bitmap, SKRectI rectangle)
+ {
+ int left = Math.Max(0, rectangle.Left);
+ int top = Math.Max(0, rectangle.Top);
+ int right = Math.Min(bitmap.Width, rectangle.Right);
+ int bottom = Math.Min(bitmap.Height, rectangle.Bottom);
+ if (left >= right || top >= bottom)
+ return Color.Transparent;
+
+ byte* pixels = (byte*)bitmap.GetPixels().ToPointer();
+ if (pixels == null)
+ return Color.Transparent;
+
+ long blue = 0;
+ long green = 0;
+ long red = 0;
+ long alpha = 0;
+ int rowBytes = bitmap.RowBytes;
+ for (int y = top; y < bottom; y++)
+ {
+ byte* pixel = pixels + y * rowBytes + left * 4;
+ for (int x = left; x < right; x++, pixel += 4)
+ {
+ blue += pixel[0];
+ green += pixel[1];
+ red += pixel[2];
+ alpha += pixel[3];
+ }
+ }
+
+ float count = (right - left) * (bottom - top);
+ return new ColorBGRA(
+ (byte)MathF.Round(blue / count),
+ (byte)MathF.Round(green / count),
+ (byte)MathF.Round(red / count),
+ (byte)MathF.Round(alpha / count)).ToColor();
}
///
@@ -63,6 +114,8 @@ public sealed class SKTexture : ITexture, IDisposable
///
public void Dispose()
{
+ _hasPixels = false;
+ _readback.Dispose();
Surface.Dispose();
GC.SuppressFinalize(this);
}
@@ -79,15 +132,15 @@ public sealed class SKTexture : ITexture, IDisposable
internal void CopyPixelData()
{
- using SKImage skImage = Surface.Snapshot();
- _image = skImage.ToImage();
+ _hasPixels = Surface.ReadPixels(_readback.Info, _readback.GetPixels(), _readback.RowBytes, 0, 0);
}
#endregion
#region Properties & Fields
- private IImage? _image;
+ private readonly SKBitmap _readback;
+ private bool _hasPixels;
private readonly Dictionary _ledRects = [];
///
@@ -119,4 +172,4 @@ public sealed class SKTexture : ITexture, IDisposable
public bool IsInvalid { get; private set; }
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs
index bf2138dcb..3d28859b7 100644
--- a/src/Artemis.Core/Services/RenderService.cs
+++ b/src/Artemis.Core/Services/RenderService.cs
@@ -47,6 +47,7 @@ internal class RenderService : IRenderService, IRenderer, IDisposable
_preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged;
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
+ ApplyRenderScaleMultiplier();
_surfaceManager = new SurfaceManager(this, GraphicsContext, _targetFrameRateSetting.Value, (float) _renderScaleSetting.Value);
}
@@ -189,9 +190,14 @@ internal class RenderService : IRenderService, IRenderer, IDisposable
_surfaceManager.UpdateTargetFrameRate(_targetFrameRateSetting.Value);
}
- private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
+ private void ApplyRenderScaleMultiplier()
{
RenderScale.SetRenderScaleMultiplier((int) (1 / _renderScaleSetting.Value));
+ }
+
+ private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
+ {
+ ApplyRenderScaleMultiplier();
_surfaceManager.UpdateRenderScale((float) _renderScaleSetting.Value);
}