1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Core - Reworking color sampling to always use the same rect as used in rendering

This commit is contained in:
Robert 2021-09-10 22:59:14 +02:00
parent 9475f7cc0c
commit 55cfa65bb5
6 changed files with 151 additions and 69 deletions

View File

@ -59,8 +59,18 @@ namespace Artemis.Core
internal void CalculateRectangles()
{
Rectangle = RgbLed.Boundary.ToSKRect();
AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect();
Rectangle = Utilities.CreateScaleCompatibleRect(
RgbLed.Boundary.Location.X,
RgbLed.Boundary.Location.Y,
RgbLed.Boundary.Size.Width,
RgbLed.Boundary.Size.Height
);
AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
RgbLed.AbsoluteBoundary.Location.X,
RgbLed.AbsoluteBoundary.Location.Y,
RgbLed.AbsoluteBoundary.Size.Width,
RgbLed.AbsoluteBoundary.Size.Height
);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
@ -13,22 +14,36 @@ namespace Artemis.Core
/// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable
{
private readonly bool _isScaledDown;
private readonly SKPixmap _pixelData;
private readonly IntPtr _pixelDataPtr;
private readonly Dictionary<Led, SKRectI> _ledRects;
#region Constructors
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler())
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices) : base(width, height, DATA_PER_PIXEL,
new AverageByteSampler())
{
ImageInfo = new SKImageInfo(width, height);
Surface = graphicsContext == null
? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = scale;
_isScaledDown = Math.Abs(scale - 1) > 0.001;
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes);
_ledRects = new Dictionary<Led, SKRectI>();
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)
);
}
}
}
#endregion
@ -80,63 +95,11 @@ namespace Artemis.Core
/// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<byte> pixel)
{
return new(pixel[2], pixel[1], pixel[0]);
return new Color(pixel[2], pixel[1], pixel[0]);
}
/// <inheritdoc />
public override Color this[in Rectangle rectangle]
{
get
{
if (Data.Length == 0) return Color.Transparent;
SKRectI skRectI = CreatedFlooredRectI(
Size.Width * rectangle.Location.X.Clamp(0, 1),
Size.Height * rectangle.Location.Y.Clamp(0, 1),
Size.Width * rectangle.Size.Width.Clamp(0, 1),
Size.Height * rectangle.Size.Height.Clamp(0, 1)
);
if (skRectI.Width == 0 || skRectI.Height == 0) return Color.Transparent;
if (skRectI.Width == 1 && skRectI.Height == 1) return GetColor(GetPixelData(skRectI.Left, skRectI.Top));
int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<byte> buffer = stackalloc byte[bufferSize];
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
byte[] rent = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(rent).Slice(0, bufferSize);
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
ArrayPool<byte>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
private static SKRectI CreatedFlooredRectI(float x, float y, float width, float height)
{
return new(
width <= 0.0 ? checked((int) Math.Floor(x)) : checked((int) Math.Ceiling(x)),
height <= 0.0 ? checked((int) Math.Floor(y)) : checked((int) Math.Ceiling(y)),
width >= 0.0 ? checked((int) Math.Floor(x + width)) : checked((int) Math.Ceiling(x + width)),
height >= 0.0 ? checked((int) Math.Floor(y + height)) : checked((int) Math.Ceiling(y + height))
);
}
public override Color this[in Rectangle rectangle] => Color.Transparent;
#endregion
@ -169,5 +132,40 @@ namespace Artemis.Core
public bool IsInvalid { get; private set; }
#endregion
internal Color GetColorAtRenderTarget(in RenderTarget renderTarget)
{
if (Data.Length == 0) return Color.Transparent;
SKRectI skRectI = _ledRects[renderTarget.Led];
if (skRectI.Width <= 0 || skRectI.Height <= 0)
return Color.Transparent;
int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<byte> buffer = stackalloc byte[bufferSize];
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
byte[] rent = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(rent).Slice(0, bufferSize);
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
ArrayPool<byte>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
}

View File

@ -0,0 +1,44 @@
using RGB.NET.Core;
namespace Artemis.Core
{
internal class SKTextureBrush : AbstractBrush
{
#region Properties & Fields
private SKTexture? _texture;
/// <summary>
/// Gets or sets the texture drawn by this brush.
/// </summary>
public SKTexture? Texture
{
get => _texture;
set => SetProperty(ref _texture, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TextureBrush" /> class.
/// </summary>
/// <param name="texture">The texture drawn by this brush.</param>
public SKTextureBrush(SKTexture? texture)
{
this.Texture = texture;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget)
{
return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent;
}
#endregion
}
}

View File

@ -25,7 +25,7 @@ namespace Artemis.Core.Services
private readonly IPluginManagementService _pluginManagementService;
private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute};
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap;
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
@ -39,6 +39,7 @@ namespace Artemis.Core.Services
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5);
Surface = new RGBSurface();
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
// Let's throw these for now
Surface.Exception += SurfaceOnException;
@ -56,7 +57,7 @@ namespace Artemis.Core.Services
UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
SetRenderPaused(true);
Surface.RegisterUpdateTrigger(UpdateTrigger);
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
}
@ -93,7 +94,7 @@ namespace Artemis.Core.Services
if (_surfaceLedGroup == null)
{
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush };
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
OnLedsChanged();
return;
}
@ -104,7 +105,7 @@ namespace Artemis.Core.Services
_surfaceLedGroup.Detach();
// Apply the application wide brush and decorator
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush };
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
OnLedsChanged();
}
}
@ -113,7 +114,6 @@ namespace Artemis.Core.Services
if (changedRenderPaused)
SetRenderPaused(false);
}
}
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
@ -134,7 +134,12 @@ namespace Artemis.Core.Services
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
{
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
_texture?.Invalidate();
foreach (ArtemisDevice artemisDevice in Devices)
artemisDevice.CalculateRenderProperties();
OnLedsChanged();
}
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
@ -315,7 +320,7 @@ namespace Artemis.Core.Services
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
_texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale);
_texture = new SKTexture(graphicsContext, width, height, renderScale, Devices);
_textureBrush.Texture = _texture;

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Artemis.Core.Modules;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
@ -42,8 +43,7 @@ namespace Artemis.Core.Services
_profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService;
_profileRepository = profileRepository;
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll()
.Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using SkiaSharp;
namespace Artemis.Core
{
@ -18,8 +19,8 @@ namespace Artemis.Core
public static void PrepareFirstLaunch()
{
CreateAccessibleDirectory(Constants.DataFolder);
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"plugins"));
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"user layouts"));
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "plugins"));
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "user layouts"));
}
/// <summary>
@ -102,6 +103,30 @@ namespace Artemis.Core
RestartRequested?.Invoke(null, e);
}
#region Scaling
internal static int RenderScaleMultiplier { get; set; } = 2;
internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height)
{
int roundX = (int) MathF.Floor(x);
int roundY = (int) MathF.Floor(y);
int roundWidth = (int) MathF.Ceiling(width);
int roundHeight = (int) MathF.Ceiling(height);
if (RenderScaleMultiplier == 1)
return SKRectI.Create(roundX, roundY, roundWidth, roundHeight);
return SKRectI.Create(
roundX - (roundX % RenderScaleMultiplier),
roundY - (roundY % RenderScaleMultiplier),
roundWidth - (roundWidth % RenderScaleMultiplier),
roundHeight - (roundHeight % RenderScaleMultiplier)
);
}
#endregion
#region Events
/// <summary>