Reworked result image (WIP)

This commit is contained in:
Darth Affe 2023-09-05 00:39:04 +02:00
parent 8ce7db15e8
commit 9f9f153da5
9 changed files with 672 additions and 179 deletions

View File

@ -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;
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates readonly items from arbitrary memory locations.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
public readonly ref struct ReadOnlyRefEnumerable<T>
{
#region Properties & Fields
/// <summary>
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
private readonly ReadOnlySpan<T> _span;
/// <summary>
/// The distance between items in the sequence to enumerate.
/// </summary>
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
private readonly int _step;
/// <summary>
/// Gets the total available length for the sequence.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _span.Length;
}
/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
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;
}
}
/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
public ref readonly T this[Index index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this[index.GetOffset(Length)];
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="reference">A reference to the first item of the sequence.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[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
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[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>();
T[] array = new T[length];
CopyTo(array);
return array;
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyRefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(Span<T> 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);
}
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyRefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Span<T> 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
/// <summary>
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#region Properties & Fields
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}._span"/>
private readonly ReadOnlySpan<T> _span;
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}._step"/>
private readonly int _step;
/// <summary>
/// The current position in the sequence.
/// </summary>
private int _position;
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
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
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance with the info on the items to traverse.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(ReadOnlySpan<T> span, int step)
{
this._span = span;
this._step = step;
_position = -1;
}
#endregion
#region Methods
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => ++_position < _span.Length;
#endregion
}
}

View File

@ -133,7 +133,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture<ColorBGRA>
return result;
}
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone)
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
{
if (_context == null) return;
@ -157,25 +157,26 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture<ColorBGRA>
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<byte> source = mapSource.AsSpan(mapSource.RowPitch * textures.Height);
ReadOnlySpan<byte> 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<ColorBGRA>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRotate0(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
private static void CopyRotate0(in ReadOnlySpan<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
{
int height = captureZone.Image.Height;
int stride = captureZone.Image.Stride;
Span<byte> target = captureZone.Image.Raw;
int height = captureZone.Height;
int stride = captureZone.Stride;
Span<byte> target = buffer;
for (int y = 0; y < height; y++)
{
@ -201,51 +202,49 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture<ColorBGRA>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRotate90(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
private static void CopyRotate90(in ReadOnlySpan<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
{
int width = captureZone.Image.Width;
int height = captureZone.Image.Height;
int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
Span<ColorBGRA> target = captureZone.Image.Pixels;
int width = captureZone.Width;
int height = captureZone.Height;
int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel;
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(buffer);
for (int x = 0; x < width; x++)
{
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(x * sourceStride, usedBytesPerLine));
ReadOnlySpan<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(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<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
private static void CopyRotate180(in ReadOnlySpan<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone, in Span<byte> 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<ColorBGRA> target = captureZone.Image.Pixels;
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(buffer);
for (int y = 0; y < height; y++)
{
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(y * sourceStride, usedBytesPerLine));
ReadOnlySpan<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(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<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
private static void CopyRotate270(in ReadOnlySpan<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
{
int width = captureZone.Image.Width;
int height = captureZone.Image.Height;
int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
Span<ColorBGRA> target = captureZone.Image.Pixels;
int width = captureZone.Width;
int height = captureZone.Height;
int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel;
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(buffer);
for (int x = 0; x < width; x++)
{
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(x * sourceStride, usedBytesPerLine));
ReadOnlySpan<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(x * sourceStride, usedBytesPerLine));
for (int y = 0; y < src.Length; y++)
target[((height - y - 1) * width) + x] = src[y];
}

View File

@ -56,7 +56,7 @@ public abstract class AbstractScreenCapture<TColor> : IScreenCapture
{
try
{
PerformCaptureZoneUpdate(captureZone);
PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer);
captureZone.SetUpdated();
}
catch { /* */ }
@ -69,7 +69,7 @@ public abstract class AbstractScreenCapture<TColor> : IScreenCapture
protected abstract bool PerformScreenCapture();
protected abstract void PerformCaptureZoneUpdate(CaptureZone<TColor> captureZone);
protected abstract void PerformCaptureZoneUpdate(CaptureZone<TColor> captureZone, in Span<byte> buffer);
protected virtual void OnUpdated(bool result)
{
@ -93,11 +93,7 @@ public abstract class AbstractScreenCapture<TColor> : IScreenCapture
int unscaledHeight = height;
(width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel);
#if NET7_0_OR_GREATER
CaptureZone<TColor> captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage<TColor>(width, height, TColor.ColorFormat));
#else
CaptureZone<TColor> captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage<TColor>(width, height, IColor.GetColorFormat<TColor>()));
#endif
CaptureZone<TColor> captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight);
CaptureZones.Add(captureZone);
return captureZone;
@ -168,13 +164,7 @@ public abstract class AbstractScreenCapture<TColor> : 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);
}
}
}

View File

@ -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<TColor> : ICaptureZone
{
#region Properties & Fields
private readonly object _lock = new();
/// <summary>
/// Gets the unique id of this <see cref="CaptureZone{T}"/>.
/// </summary>
@ -19,6 +24,20 @@ public sealed class CaptureZone<TColor> : 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<TColor>();
}
#endif
/// <summary>
/// Gets the x-location of the region on the screen.
/// </summary>
@ -32,30 +51,53 @@ public sealed class CaptureZone<TColor> : ICaptureZone
/// <summary>
/// Gets the width of the captured region.
/// </summary>
public int Width { get; internal set; }
public int Width { get; private set; }
/// <summary>
/// Gets the height of the captured region.
/// </summary>
public int Height { get; internal set; }
public int Height { get; private set; }
public int Stride
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Width * ColorFormat.BytesPerPixel;
}
/// <summary>
/// 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.
/// </summary>
public int DownscaleLevel { get; internal set; }
public int DownscaleLevel { get; private set; }
/// <summary>
/// Gets the original width of the region (this equals <see cref="Width"/> if <see cref="DownscaleLevel"/> is 0).
/// </summary>
public int UnscaledWidth { get; internal set; }
public int UnscaledWidth { get; private set; }
/// <summary>
/// Gets the original height of the region (this equals <see cref="Height"/> if <see cref="DownscaleLevel"/> is 0).
/// </summary>
public int UnscaledHeight { get; internal set; }
public int UnscaledHeight { get; private set; }
IScreenImage ICaptureZone.Image => Image;
public ScreenImage<TColor> Image { get; }
internal byte[] InternalBuffer { get; set; }
public ReadOnlySpan<byte> RawBuffer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => InternalBuffer;
}
public ReadOnlySpan<TColor> Pixels
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => MemoryMarshal.Cast<byte, TColor>(RawBuffer);
}
public Image<TColor> Image
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(Pixels, 0, 0, Width, Height, Width);
}
/// <summary>
/// Gets or sets if the <see cref="CaptureZone{T}"/> should be automatically updated on every captured frame.
@ -66,7 +108,7 @@ public sealed class CaptureZone<TColor> : ICaptureZone
/// Gets if an update for the <see cref="CaptureZone{T}"/> is requested on the next captured frame.
/// </summary>
public bool IsUpdateRequested { get; private set; }
#endregion
#region Events
@ -93,7 +135,7 @@ public sealed class CaptureZone<TColor> : ICaptureZone
/// <param name="unscaledWidth">The original width of the region.</param>
/// <param name="unscaledHeight">The original height of the region</param>
/// <param name="buffer">The buffer containing the image data.</param>
internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, ScreenImage<TColor> 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<TColor> : 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);
}
/// <summary>
/// Requests to update this <see cref="CaptureZone{T}"/> when the next frame is captured.
/// Only necessary if <see cref="AutoUpdate"/> is set to <c>false</c>.
@ -127,6 +176,19 @@ public sealed class CaptureZone<TColor> : 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];
}
/// <summary>
/// Determines whether this <see cref="CaptureZone{T}"/> equals the given one.
/// </summary>
@ -141,4 +203,32 @@ public sealed class CaptureZone<TColor> : 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
}
}

View File

@ -38,7 +38,8 @@ public interface ICaptureZone
/// </summary>
int UnscaledHeight { get; }
IScreenImage Image { get; }
ReadOnlySpan<byte> RawBuffer { get; }
/// <summary>
/// Gets or sets if the <see cref="ICaptureZone"/> should be automatically updated on every captured frame.
/// </summary>

View File

@ -1,16 +0,0 @@
using System;
namespace ScreenCapture.NET;
public interface IScreenImage
{
Span<byte> Raw { get; }
int Width { get; }
int Height { get; }
int Stride { get; }
ColorFormat ColorFormat { get; }
IDisposable Lock();
IColor this[int x, int y] { get; }
}

View File

@ -0,0 +1,269 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ScreenCapture.NET;
public readonly ref struct Image<TColor>
where TColor : struct, IColor
{
#region Properties & Fields
private readonly ReadOnlySpan<TColor> _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<TColor> 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<TColor>(_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<TColor> 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<TColor> 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<TColor> target = dest;
foreach (ReadOnlyRefEnumerable<TColor> row in rows)
{
row.CopyTo(target);
target = target[Width..];
}
}
#endregion
#region Indexer-Structs
public readonly ref struct ImageRows
{
#region Properties & Fields
private readonly ReadOnlySpan<TColor> _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<TColor> 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<TColor>(rr, _width, 1);
}
}
#endregion
#region Constructors
public ImageRows(ReadOnlySpan<TColor> 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
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(this);
#endregion
public ref struct Enumerator
{
#region Properties & Fields
private readonly ImageRows _rows;
private int _position;
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
public ReadOnlyRefEnumerable<TColor> 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
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => ++_position < _rows._height;
#endregion
}
}
public readonly ref struct ImageColumns
{
#region Properties & Fields
private readonly ReadOnlySpan<TColor> _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<TColor> 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<TColor>(rc, _height, _stride);
}
}
#endregion
#region Constructors
public ImageColumns(ReadOnlySpan<TColor> 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
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(this);
#endregion
public ref struct Enumerator
{
#region Properties & Fields
private readonly ImageColumns _columns;
private int _position;
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
public ReadOnlyRefEnumerable<TColor> 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
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => ++_position < _columns._width;
#endregion
}
}
#endregion
}

View File

@ -1,102 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ScreenCapture.NET;
public sealed class ScreenImage<TColor> : IScreenImage
where TColor : struct, IColor
{
#region Properties & Fields
private readonly object _lock = new();
private byte[] _buffer;
public Span<byte> Raw => _buffer;
public Span<TColor> Pixels => MemoryMarshal.Cast<byte, TColor>(_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<TColor> _pixels;
public IColor this[int x] => _pixels[x];
public ScreemImageRow(Span<TColor> pixels)
{
this._pixels = pixels;
}
}
}

View File

@ -41,10 +41,6 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net6.0'">
<EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>