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