diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 8c0e0f9..d38e76c 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -23,20 +23,12 @@ public sealed class CaptureZone : ICaptureZone public int Id { get; } public Display Display { get; } - -#if NET7_0_OR_GREATER - public ColorFormat ColorFormat + + 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. @@ -93,12 +85,18 @@ public sealed class CaptureZone : ICaptureZone get => MemoryMarshal.Cast(RawBuffer); } - public Image Image + public RefImage Image { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Pixels, 0, 0, Width, Height, Width); } + IImage ICaptureZone.Image + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Image(InternalBuffer, 0, 0, Width, Height, Width); + } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// @@ -154,6 +152,14 @@ public sealed class CaptureZone : ICaptureZone #region Methods + public RefImage GetRefImage() + where T : struct, IColor + { + if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested Color-Format does not match the data.", nameof(T)); + + return new RefImage(MemoryMarshal.Cast(RawBuffer), 0, 0, Width, Height, Width); + } + public IDisposable Lock() { Monitor.Enter(_lock); @@ -216,6 +222,7 @@ public sealed class CaptureZone : ICaptureZone #region Constructors public UnlockDisposable(object @lock) => this._lock = @lock; + ~UnlockDisposable() => Dispose(); #endregion @@ -227,6 +234,8 @@ public sealed class CaptureZone : ICaptureZone Monitor.Exit(_lock); _disposed = true; + + GC.SuppressFinalize(this); } #endregion diff --git a/ScreenCapture.NET/Model/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs similarity index 100% rename from ScreenCapture.NET/Model/ColorFormat.cs rename to ScreenCapture.NET/Model/Colors/ColorFormat.cs diff --git a/ScreenCapture.NET/Model/Colors/IColor.cs b/ScreenCapture.NET/Model/Colors/IColor.cs index 43386ff..f3ea888 100644 --- a/ScreenCapture.NET/Model/Colors/IColor.cs +++ b/ScreenCapture.NET/Model/Colors/IColor.cs @@ -1,4 +1,6 @@ -namespace ScreenCapture.NET; +using System; + +namespace ScreenCapture.NET; public interface IColor { @@ -7,16 +9,5 @@ public interface IColor byte B { get; } byte A { get; } -#if NET7_0_OR_GREATER - public static abstract ColorFormat ColorFormat { get; } -#else - public static ColorFormat GetColorFormat() - where TColor : IColor - { - System.Type colorType = typeof(TColor); - if (colorType == typeof(ColorBGRA)) return ColorFormat.BGRA; - - throw new System.ArgumentException($"Not ColorFormat registered for '{typeof(TColor).Name}'"); - } -#endif + public static virtual ColorFormat ColorFormat => throw new NotSupportedException(); } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 150f382..70cc58c 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -8,6 +8,7 @@ public interface ICaptureZone /// Gets the unique id of this . /// int Id { get; } + Display Display { get; } /// /// Gets the x-location of the region on the screen. @@ -40,6 +41,8 @@ public interface ICaptureZone ReadOnlySpan RawBuffer { get; } + IImage Image { get; } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// @@ -58,4 +61,7 @@ public interface ICaptureZone /// Only necessary if is set to false. /// void RequestUpdate(); + + RefImage GetRefImage() + where TColor : struct, IColor; } diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs new file mode 100644 index 0000000..bbe18e7 --- /dev/null +++ b/ScreenCapture.NET/Model/IImage.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace ScreenCapture.NET; + +public interface IImage : IEnumerable +{ + int Width { get; } + int Height { get; } + + IColor this[int x, int y] { get; } + IImage this[int x, int y, int width, int height] { get; } + + IImageRows Rows { get; } + IImageColumns Columns { get; } + + public interface IImageRows : IEnumerable + { + IImageRow this[int column] { get; } + } + + public interface IImageColumns : IEnumerable + { + IImageColumn this[int column] { get; } + } + + public interface IImageRow : IEnumerable + { + IColor this[int x] { get; } + } + + public interface IImageColumn : IEnumerable + { + IColor this[int y] { get; } + } +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index f0d7d8f..87a3760 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -1,15 +1,17 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace ScreenCapture.NET; -public readonly ref struct Image +public sealed class Image : IImage where TColor : struct, IColor { #region Properties & Fields - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; @@ -22,46 +24,47 @@ public readonly ref struct Image #region Indexer - public TColor this[int x, int y] + public IColor this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (x > Width) || (y > Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); - return _pixels[((_y + y) * _stride) + (_x + x)]; + return MemoryMarshal.Cast(_buffer)[((_y + y) * _stride) + (_x + x)]; } } - public Image this[int x, int y, int width, int height] + public IImage this[int x, int y, int width, int height] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); - return new Image(_pixels, _x + x, _y + y, width, height, _stride); + return new Image(_buffer, _x + x, _y + y, width, height, _stride); } } - public ImageRows Rows + public IImage.IImageRows Rows { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(_pixels, _x, _y, Width, Height, _stride); + get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); } - public ImageColumns Columns + + public IImage.IImageColumns Columns { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(_pixels, _x, _y, Width, Height, _stride); + get => new ImageColumns(_buffer, _x, _y, Width, Height, _stride); } #endregion #region Constructors - internal Image(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal Image(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this.Width = width; @@ -73,78 +76,24 @@ public readonly ref struct Image #region Methods - public void CopyTo(in Span dest) + public IEnumerator GetEnumerator() { - 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..]; - } + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + yield return this[x, y]; } - public TColor[] ToArray() - { - TColor[] array = new TColor[Width * Height]; - CopyTo(array); - return array; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageEnumerator GetEnumerator() => new(_pixels); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion - public ref struct ImageEnumerator + #region Indexer-Classes + + private sealed class ImageRows : IImage.IImageRows { #region Properties & Fields - private readonly ReadOnlySpan _pixels; - private int _position; - - /// - public TColor Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _pixels[_position]; - } - - #endregion - - #region Constructors - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageEnumerator(ReadOnlySpan pixels) - { - this._pixels = pixels; - - _position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _pixels.Length; - - #endregion - } - - #region Indexer-Structs - - public readonly ref struct ImageRows - { - #region Properties & Fields - - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; private readonly int _width; @@ -155,17 +104,13 @@ public readonly ref struct Image #region Indexer - public readonly ReadOnlyRefEnumerable this[int row] + public IImage.IImageRow this[int row] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + 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); + return new ImageRow(_buffer, (((row + _y) * _stride) + _x), _width); } } @@ -173,9 +118,9 @@ public readonly ref struct Image #region Constructors - public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal ImageRows(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this._width = width; @@ -187,56 +132,71 @@ public readonly ref struct Image #region Methods - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageRowsEnumerator GetEnumerator() => new(this); + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion - - public ref struct ImageRowsEnumerator - { - #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 ImageRowsEnumerator(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 + private sealed class ImageRow : IImage.IImageRow { #region Properties & Fields - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + + #endregion + + #region Indexer + + public IColor this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[x]; + } + } + + #endregion + + #region Constructors + + internal ImageRow(byte[] buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int x = 0; x < _length; x++) + yield return this[x]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + private sealed class ImageColumns : IImage.IImageColumns + { + #region Properties & Fields + + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; private readonly int _width; @@ -247,17 +207,13 @@ public readonly ref struct Image #region Indexer - public ReadOnlyRefEnumerable this[int column] + public IImage.IImageColumn this[int column] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + 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); + return new ImageColumn(_buffer, (_y * _stride) + _x + column, _height, _stride); } } @@ -265,9 +221,9 @@ public readonly ref struct Image #region Constructors - public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal ImageColumns(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this._width = width; @@ -279,47 +235,66 @@ public readonly ref struct Image #region Methods - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageColumnsEnumerator GetEnumerator() => new(this); + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + private sealed class ImageColumn : IImage.IImageColumn + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; #endregion - public ref struct ImageColumnsEnumerator + #region Indexer + + public IColor this[int y] { - #region Properties & Fields - - private readonly ImageColumns _columns; - private int _position; - - /// - public ReadOnlyRefEnumerable Current + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _columns[_position]; + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[y * _step]; } - - #endregion - - #region Constructors - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageColumnsEnumerator(ImageColumns columns) - { - this._columns = columns; - this._position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _columns._width; - - #endregion } + + #endregion + + #region Constructors + + internal ImageColumn(byte[] buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _length; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion } #endregion diff --git a/ScreenCapture.NET/Model/RefImage.cs b/ScreenCapture.NET/Model/RefImage.cs new file mode 100644 index 0000000..2fe734d --- /dev/null +++ b/ScreenCapture.NET/Model/RefImage.cs @@ -0,0 +1,326 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +public readonly ref struct RefImage + 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 RefImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + + return new RefImage(_pixels, _x + x, _y + y, width, height, _stride); + } + } + + public ImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } + + #endregion + + #region Constructors + + internal RefImage(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..]; + } + } + + public TColor[] ToArray() + { + TColor[] array = new TColor[Width * Height]; + CopyTo(array); + return array; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageEnumerator GetEnumerator() => new(_pixels); + + #endregion + + public ref struct ImageEnumerator + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private int _position; + + /// + public TColor Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pixels[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageEnumerator(ReadOnlySpan pixels) + { + this._pixels = pixels; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _pixels.Length; + + #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 ImageRowsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowsEnumerator + { + #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 ImageRowsEnumerator(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 ImageColumnsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnsEnumerator + { + #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 ImageColumnsEnumerator(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