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:
parent
9475f7cc0c
commit
55cfa65bb5
@ -59,8 +59,18 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void CalculateRectangles()
|
internal void CalculateRectangles()
|
||||||
{
|
{
|
||||||
Rectangle = RgbLed.Boundary.ToSKRect();
|
Rectangle = Utilities.CreateScaleCompatibleRect(
|
||||||
AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect();
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Artemis.Core.SkiaSharp;
|
using Artemis.Core.SkiaSharp;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
@ -13,22 +14,36 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SKTexture : PixelTexture<byte>, IDisposable
|
public sealed class SKTexture : PixelTexture<byte>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly bool _isScaledDown;
|
|
||||||
private readonly SKPixmap _pixelData;
|
private readonly SKPixmap _pixelData;
|
||||||
private readonly IntPtr _pixelDataPtr;
|
private readonly IntPtr _pixelDataPtr;
|
||||||
|
private readonly Dictionary<Led, SKRectI> _ledRects;
|
||||||
|
|
||||||
#region Constructors
|
#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);
|
ImageInfo = new SKImageInfo(width, height);
|
||||||
Surface = graphicsContext == null
|
Surface = graphicsContext == null
|
||||||
? SKSurface.Create(ImageInfo)
|
? SKSurface.Create(ImageInfo)
|
||||||
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
|
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
|
||||||
RenderScale = scale;
|
RenderScale = scale;
|
||||||
_isScaledDown = Math.Abs(scale - 1) > 0.001;
|
|
||||||
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
|
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
|
||||||
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes);
|
_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
|
#endregion
|
||||||
@ -80,63 +95,11 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Color GetColor(in ReadOnlySpan<byte> pixel)
|
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 />
|
/// <inheritdoc />
|
||||||
public override Color this[in Rectangle rectangle]
|
public override Color this[in Rectangle rectangle] => Color.Transparent;
|
||||||
{
|
|
||||||
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))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -169,5 +132,40 @@ namespace Artemis.Core
|
|||||||
public bool IsInvalid { get; private set; }
|
public bool IsInvalid { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
44
src/Artemis.Core/RGB.NET/SKTextureBrush.cs
Normal file
44
src/Artemis.Core/RGB.NET/SKTextureBrush.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,7 +25,7 @@ namespace Artemis.Core.Services
|
|||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly PluginSetting<double> _renderScaleSetting;
|
private readonly PluginSetting<double> _renderScaleSetting;
|
||||||
private readonly PluginSetting<int> _targetFrameRateSetting;
|
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 Dictionary<Led, ArtemisLed> _ledMap;
|
||||||
private ListLedGroup? _surfaceLedGroup;
|
private ListLedGroup? _surfaceLedGroup;
|
||||||
private SKTexture? _texture;
|
private SKTexture? _texture;
|
||||||
@ -39,6 +39,7 @@ namespace Artemis.Core.Services
|
|||||||
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5);
|
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5);
|
||||||
|
|
||||||
Surface = new RGBSurface();
|
Surface = new RGBSurface();
|
||||||
|
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
|
||||||
|
|
||||||
// Let's throw these for now
|
// Let's throw these for now
|
||||||
Surface.Exception += SurfaceOnException;
|
Surface.Exception += SurfaceOnException;
|
||||||
@ -113,7 +114,6 @@ namespace Artemis.Core.Services
|
|||||||
if (changedRenderPaused)
|
if (changedRenderPaused)
|
||||||
SetRenderPaused(false);
|
SetRenderPaused(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
|
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
|
||||||
@ -134,7 +134,12 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
|
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
|
||||||
|
|
||||||
_texture?.Invalidate();
|
_texture?.Invalidate();
|
||||||
|
foreach (ArtemisDevice artemisDevice in Devices)
|
||||||
|
artemisDevice.CalculateRenderProperties();
|
||||||
|
OnLedsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
|
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
|
||||||
@ -315,7 +320,7 @@ namespace Artemis.Core.Services
|
|||||||
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
|
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
|
||||||
|
|
||||||
_texture?.Dispose();
|
_texture?.Dispose();
|
||||||
_texture = new SKTexture(graphicsContext, width, height, renderScale);
|
_texture = new SKTexture(graphicsContext, width, height, renderScale, Devices);
|
||||||
_textureBrush.Texture = _texture;
|
_textureBrush.Texture = _texture;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
|
using Artemis.Core.Properties;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -42,8 +43,7 @@ namespace Artemis.Core.Services
|
|||||||
_profileCategoryRepository = profileCategoryRepository;
|
_profileCategoryRepository = profileCategoryRepository;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_profileRepository = profileRepository;
|
_profileRepository = profileRepository;
|
||||||
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll()
|
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
|
||||||
.Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
|
|
||||||
|
|
||||||
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
|
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
|
||||||
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
@ -102,6 +103,30 @@ namespace Artemis.Core
|
|||||||
RestartRequested?.Invoke(null, e);
|
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
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user