using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Artemis.Core.SkiaSharp; using RGB.NET.Core; using RGB.NET.Presets.Textures.Sampler; using SkiaSharp; namespace Artemis.Core; /// /// Represents a SkiaSharp-based RGB.NET PixelTexture /// public sealed class SKTexture : PixelTexture, IDisposable { private readonly Dictionary _ledRects; private readonly SKPixmap _pixelData; private readonly IntPtr _pixelDataPtr; #region Constructors internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection 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; _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); _ledRects = new Dictionary(); 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 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; SamplerInfo samplerInfo = new(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, Stride, DataPerPixel, Data); Span pixelData = stackalloc byte[DATA_PER_PIXEL]; Sampler.Sample(samplerInfo, pixelData); return GetColor(pixelData); } private void ReleaseUnmanagedResources() { Marshal.FreeHGlobal(_pixelDataPtr); } /// ~SKTexture() { ReleaseUnmanagedResources(); } /// public void Dispose() { Surface.Dispose(); _pixelData.Dispose(); ReleaseUnmanagedResources(); GC.SuppressFinalize(this); } #region Constants private const int DATA_PER_PIXEL = 4; #endregion #region Methods /// /// Invalidates the texture /// public void Invalidate() { IsInvalid = true; } internal void CopyPixelData() { using SKImage skImage = Surface.Snapshot(); skImage.ReadPixels(_pixelData); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Color GetColor(in ReadOnlySpan pixel) => new(pixel[2], pixel[1], pixel[0]); /// public override Color this[in Rectangle rectangle] => Color.Transparent; #endregion #region Properties & Fields /// /// Gets the SKBitmap backing this texture /// public SKSurface Surface { get; } /// /// Gets the image info used to create the /// public SKImageInfo ImageInfo { get; } /// /// Gets the color data in RGB format /// protected override ReadOnlySpan Data => _pixelData.GetPixelSpan(); /// /// Gets the render scale of the texture /// public float RenderScale { get; } /// /// Gets a boolean indicating whether has been called on this texture, indicating it should /// be replaced /// public bool IsInvalid { get; private set; } #endregion }