mirror of
https://github.com/DarthAffe/ScreenCapture.NET.git
synced 2025-12-12 13:28:35 +00:00
Reworked result image (WIP)
This commit is contained in:
parent
8ce7db15e8
commit
9f9f153da5
@ -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
|
||||
}
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
269
ScreenCapture.NET/Model/Image.cs
Normal file
269
ScreenCapture.NET/Model/Image.cs
Normal 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
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user