diff --git a/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs b/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs new file mode 100644 index 0000000..6dda976 --- /dev/null +++ b/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs @@ -0,0 +1,266 @@ +// DarthAffe 05.09.2023: Based on https://github.com/CommunityToolkit/dotnet/blob/b0d6c4f9c0cfb5d860400abb00b0ca1b3e94dfa4/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable%7BT%7D.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +/// +/// A that iterates readonly items from arbitrary memory locations. +/// +/// The type of items to enumerate. +public readonly ref struct ReadOnlyRefEnumerable +{ + #region Properties & Fields + + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly ReadOnlySpan _span; + + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int _step; + + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span.Length; + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) throw new IndexOutOfRangeException(); + + ref T r0 = ref MemoryMarshal.GetReference(_span); + nint offset = (nint)(uint)index * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(in T reference, int length, int step) + { + this._step = step; + + _span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(_span, _step); + + public T[] ToArray() + { + int length = _span.Length; + + // Empty array if no data is mapped + if (length == 0) + return Array.Empty(); + + T[] array = new T[length]; + CopyTo(array); + + return array; + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (_step == 1) + { + _span.CopyTo(destination); + return; + } + + ref T sourceRef = ref MemoryMarshal.GetReference(_span); + int length = _span.Length; + if ((uint)destination.Length < (uint)length) + throw new ArgumentException("The target span is too short to copy all the current items to."); + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + + CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)_step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= _span.Length) + { + CopyTo(destination); + return true; + } + + return false; + } + + private static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) + { + nint sourceOffset = 0; + nint destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 8; + sourceOffset += sourceStep; + destinationOffset += 8; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 4; + sourceOffset += sourceStep; + destinationOffset += 4; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += sourceStep; + destinationOffset += 1; + } + } + + #endregion + + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { + #region Properties & Fields + + /// + private readonly ReadOnlySpan _span; + + /// + private readonly int _step; + + /// + /// The current position in the sequence. + /// + private int _position; + + /// + public readonly ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T r0 = ref MemoryMarshal.GetReference(_span); + + nint offset = (nint)(uint)_position * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan span, int step) + { + this._span = span; + this._step = step; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _span.Length; + + #endregion + } +} diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index da40620..d64d8ee 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -133,7 +133,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture return result; } - protected override void PerformCaptureZoneUpdate(CaptureZone captureZone) + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { if (_context == null) return; @@ -157,25 +157,26 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture textures.Y + textures.UnscaledHeight, 1)); MappedSubresource mapSource = _context.Map(textures.StagingTexture, 0, MapMode.Read, MapFlags.None); - using IDisposable @lock = captureZone.Image.Lock(); + + using IDisposable @lock = captureZone.Lock(); { - Span source = mapSource.AsSpan(mapSource.RowPitch * textures.Height); + ReadOnlySpan source = mapSource.AsSpan(mapSource.RowPitch * textures.Height); switch (Display.Rotation) { case Rotation.Rotation90: - CopyRotate90(source, mapSource.RowPitch, captureZone); + CopyRotate90(source, mapSource.RowPitch, captureZone, buffer); break; case Rotation.Rotation180: - CopyRotate180(source, mapSource.RowPitch, captureZone); + CopyRotate180(source, mapSource.RowPitch, captureZone, buffer); break; case Rotation.Rotation270: - CopyRotate270(source, mapSource.RowPitch, captureZone); + CopyRotate270(source, mapSource.RowPitch, captureZone, buffer); break; default: - CopyRotate0(source, mapSource.RowPitch, captureZone); + CopyRotate0(source, mapSource.RowPitch, captureZone, buffer); break; } } @@ -185,11 +186,11 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate0(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate0(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int height = captureZone.Image.Height; - int stride = captureZone.Image.Stride; - Span target = captureZone.Image.Raw; + int height = captureZone.Height; + int stride = captureZone.Stride; + Span target = buffer; for (int y = 0; y < height; y++) { @@ -201,51 +202,49 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate90(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate90(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel; - Span target = captureZone.Image.Pixels; + int width = captureZone.Width; + int height = captureZone.Height; + int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel; + Span target = MemoryMarshal.Cast(buffer); for (int x = 0; x < width; x++) { - Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); + ReadOnlySpan src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); for (int y = 0; y < src.Length; y++) target[(y * width) + (width - x - 1)] = src[y]; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate180(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate180(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int bpp = captureZone.Image.ColorFormat.BytesPerPixel; + int width = captureZone.Width; + int height = captureZone.Height; + int bpp = captureZone.ColorFormat.BytesPerPixel; int usedBytesPerLine = width * bpp; - Span target = captureZone.Image.Pixels; + Span target = MemoryMarshal.Cast(buffer); for (int y = 0; y < height; y++) { - Span src = MemoryMarshal.Cast(source.Slice(y * sourceStride, usedBytesPerLine)); + ReadOnlySpan src = MemoryMarshal.Cast(source.Slice(y * sourceStride, usedBytesPerLine)); for (int x = 0; x < src.Length; x++) - { target[((height - y - 1) * width) + (width - x - 1)] = src[x]; - } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate270(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate270(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel; - Span target = captureZone.Image.Pixels; + int width = captureZone.Width; + int height = captureZone.Height; + int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel; + Span target = MemoryMarshal.Cast(buffer); for (int x = 0; x < width; x++) { - Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); + ReadOnlySpan src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); for (int y = 0; y < src.Length; y++) target[((height - y - 1) * width) + x] = src[y]; } diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs index 61af70a..3eb8c0d 100644 --- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs +++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs @@ -56,7 +56,7 @@ public abstract class AbstractScreenCapture : IScreenCapture { try { - PerformCaptureZoneUpdate(captureZone); + PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer); captureZone.SetUpdated(); } catch { /* */ } @@ -69,7 +69,7 @@ public abstract class AbstractScreenCapture : IScreenCapture protected abstract bool PerformScreenCapture(); - protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone); + protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer); protected virtual void OnUpdated(bool result) { @@ -93,11 +93,7 @@ public abstract class AbstractScreenCapture : IScreenCapture int unscaledHeight = height; (width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel); -#if NET7_0_OR_GREATER - CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage(width, height, TColor.ColorFormat)); -#else - CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage(width, height, IColor.GetColorFormat())); -#endif + CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight); CaptureZones.Add(captureZone); return captureZone; @@ -168,13 +164,7 @@ public abstract class AbstractScreenCapture : IScreenCapture if ((width != null) || (height != null) || (downscaleLevel != null)) { (int newWidth, int newHeight, newDownscaleLevel) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel); - - captureZone.UnscaledWidth = newUnscaledWidth; - captureZone.UnscaledHeight = newUnscaledHeight; - captureZone.Width = newWidth; - captureZone.Height = newHeight; - captureZone.DownscaleLevel = newDownscaleLevel; - captureZone.Image.Resize(newWidth, newHeight); + captureZone.Resize(newWidth, newHeight, newDownscaleLevel, newUnscaledWidth, newUnscaledHeight); } } } diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 5be69a0..8c0e0f9 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -1,6 +1,9 @@ // ReSharper disable MemberCanBePrivate.Global using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; namespace ScreenCapture.NET; @@ -12,6 +15,8 @@ public sealed class CaptureZone : ICaptureZone { #region Properties & Fields + private readonly object _lock = new(); + /// /// Gets the unique id of this . /// @@ -19,6 +24,20 @@ public sealed class CaptureZone : ICaptureZone public Display Display { get; } +#if NET7_0_OR_GREATER + public ColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => TColor.ColorFormat; + } +#else + public ColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => IColor.GetColorFormat(); + } +#endif + /// /// Gets the x-location of the region on the screen. /// @@ -32,30 +51,53 @@ public sealed class CaptureZone : ICaptureZone /// /// Gets the width of the captured region. /// - public int Width { get; internal set; } + public int Width { get; private set; } /// /// Gets the height of the captured region. /// - public int Height { get; internal set; } + public int Height { get; private set; } + + public int Stride + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Width * ColorFormat.BytesPerPixel; + } /// /// Gets the level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. /// - public int DownscaleLevel { get; internal set; } + public int DownscaleLevel { get; private set; } /// /// Gets the original width of the region (this equals if is 0). /// - public int UnscaledWidth { get; internal set; } + public int UnscaledWidth { get; private set; } /// /// Gets the original height of the region (this equals if is 0). /// - public int UnscaledHeight { get; internal set; } + public int UnscaledHeight { get; private set; } - IScreenImage ICaptureZone.Image => Image; - public ScreenImage Image { get; } + internal byte[] InternalBuffer { get; set; } + + public ReadOnlySpan RawBuffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => InternalBuffer; + } + + public ReadOnlySpan Pixels + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MemoryMarshal.Cast(RawBuffer); + } + + public Image Image + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(Pixels, 0, 0, Width, Height, Width); + } /// /// Gets or sets if the should be automatically updated on every captured frame. @@ -66,7 +108,7 @@ public sealed class CaptureZone : ICaptureZone /// Gets if an update for the is requested on the next captured frame. /// public bool IsUpdateRequested { get; private set; } - + #endregion #region Events @@ -93,7 +135,7 @@ public sealed class CaptureZone : ICaptureZone /// The original width of the region. /// The original height of the region /// The buffer containing the image data. - internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, ScreenImage image) + internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) { this.Id = id; this.Display = display; @@ -101,16 +143,23 @@ public sealed class CaptureZone : ICaptureZone this.Y = y; this.Width = width; this.Height = height; + this.DownscaleLevel = downscaleLevel; this.UnscaledWidth = unscaledWidth; this.UnscaledHeight = unscaledHeight; - this.DownscaleLevel = downscaleLevel; - this.Image = image; + + InternalBuffer = new byte[Stride * Height]; } #endregion #region Methods + public IDisposable Lock() + { + Monitor.Enter(_lock); + return new UnlockDisposable(_lock); + } + /// /// Requests to update this when the next frame is captured. /// Only necessary if is set to false. @@ -127,6 +176,19 @@ public sealed class CaptureZone : ICaptureZone Updated?.Invoke(this, EventArgs.Empty); } + internal void Resize(int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) + { + Width = width; + Height = height; + DownscaleLevel = downscaleLevel; + UnscaledWidth = unscaledWidth; + UnscaledHeight = unscaledHeight; + + int newBufferSize = Stride * Height; + if (newBufferSize != InternalBuffer.Length) + InternalBuffer = new byte[newBufferSize]; + } + /// /// Determines whether this equals the given one. /// @@ -141,4 +203,32 @@ public sealed class CaptureZone : ICaptureZone public override int GetHashCode() => Id; #endregion + + private class UnlockDisposable : IDisposable + { + #region Properties & Fields + + private bool _disposed = false; + private readonly object _lock; + + #endregion + + #region Constructors + + public UnlockDisposable(object @lock) => this._lock = @lock; + + #endregion + + #region Methods + + public void Dispose() + { + if (_disposed) throw new ObjectDisposedException("The lock is already released"); + + Monitor.Exit(_lock); + _disposed = true; + } + + #endregion + } } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 7f83100..150f382 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -38,7 +38,8 @@ public interface ICaptureZone /// int UnscaledHeight { get; } - IScreenImage Image { get; } + ReadOnlySpan RawBuffer { get; } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// diff --git a/ScreenCapture.NET/Model/IScreenImage.cs b/ScreenCapture.NET/Model/IScreenImage.cs deleted file mode 100644 index 4b87105..0000000 --- a/ScreenCapture.NET/Model/IScreenImage.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace ScreenCapture.NET; - -public interface IScreenImage -{ - Span Raw { get; } - int Width { get; } - int Height { get; } - int Stride { get; } - ColorFormat ColorFormat { get; } - - IDisposable Lock(); - - IColor this[int x, int y] { get; } -} diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs new file mode 100644 index 0000000..6eae374 --- /dev/null +++ b/ScreenCapture.NET/Model/Image.cs @@ -0,0 +1,269 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +public readonly ref struct Image + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + + private readonly int _x; + private readonly int _y; + private readonly int _stride; + + public int Width { get; } + public int Height { get; } + + #endregion + + #region Indexer + + public TColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x > Width) || (y > Height)) throw new IndexOutOfRangeException(); + + return _pixels[((_y + y) * _stride) + (_x + x)]; + } + } + + public Image this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new Image(_pixels, _x + x, _y + y, width, height, _stride); + } + } + + public ImageRows Rows => new(_pixels, _x, _y, Width, Height, _stride); + public ImageColumns Columns => new(_pixels, _x, _y, Width, Height, _stride); + + #endregion + + #region Constructors + + internal Image(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + public void CopyTo(in Span dest) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest)); + + ImageRows rows = Rows; + Span target = dest; + foreach (ReadOnlyRefEnumerable row in rows) + { + row.CopyTo(target); + target = target[Width..]; + } + } + + #endregion + + #region Indexer-Structs + + public readonly ref struct ImageRows + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public readonly ReadOnlyRefEnumerable this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); + + return new ReadOnlyRefEnumerable(rr, _width, 1); + } + } + + #endregion + + #region Constructors + + public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(this); + + #endregion + + public ref struct Enumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } + } + + public readonly ref struct ImageColumns + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public ReadOnlyRefEnumerable this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); + + return new ReadOnlyRefEnumerable(rc, _height, _stride); + } + } + + #endregion + + #region Constructors + + public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(this); + + #endregion + + public ref struct Enumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ScreenImage.cs b/ScreenCapture.NET/Model/ScreenImage.cs deleted file mode 100644 index 9b6c47e..0000000 --- a/ScreenCapture.NET/Model/ScreenImage.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; - -namespace ScreenCapture.NET; - -public sealed class ScreenImage : IScreenImage - where TColor : struct, IColor -{ - #region Properties & Fields - - private readonly object _lock = new(); - private byte[] _buffer; - - public Span Raw => _buffer; - public Span Pixels => MemoryMarshal.Cast(_buffer); - - public int Width { get; private set; } - public int Height { get; private set; } - public int Stride => Width * ColorFormat.BytesPerPixel; - public ColorFormat ColorFormat { get; } - - #endregion - - #region Indexer - - IColor IScreenImage.this[int x, int y] => this[x, y]; - public TColor this[int x, int y] => Pixels[(y * Width) + x]; - - #endregion - - #region Constructors - - internal ScreenImage(int width, int height, ColorFormat colorFormat) - { - this.Width = width; - this.Height = height; - this.ColorFormat = colorFormat; - - _buffer = new byte[width * height * colorFormat.BytesPerPixel]; - } - - #endregion - - #region Methods - - internal void Resize(int width, int height) - { - Width = width; - Height = height; - - _buffer = new byte[width * height * ColorFormat.BytesPerPixel]; - } - - public IDisposable Lock() - { - Monitor.Enter(_lock); - return new UnlockDisposable(_lock); - } - - #endregion - - private class UnlockDisposable : IDisposable - { - #region Properties & Fields - - private bool _disposed = false; - private readonly object _lock; - - #endregion - - #region Constructors - - public UnlockDisposable(object @lock) => this._lock = @lock; - - #endregion - - #region Methods - - public void Dispose() - { - if (_disposed) throw new ObjectDisposedException("The lock is already released"); - - Monitor.Exit(_lock); - _disposed = true; - } - - #endregion - } - - public readonly ref struct ScreemImageRow - { - private readonly Span _pixels; - - public IColor this[int x] => _pixels[x]; - - public ScreemImageRow(Span pixels) - { - this._pixels = pixels; - } - } -} \ No newline at end of file diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj index 8396d8a..49dfad2 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -41,10 +41,6 @@ snupkg - - true - - $(DefineConstants);TRACE;DEBUG true