mirror of
https://github.com/DarthAffe/ScreenCapture.NET.git
synced 2025-12-13 05:48:39 +00:00
Reworked capturing to fully detach the capture zone from the capturing itself and allow better handling of result-data (WIP)
This commit is contained in:
parent
9066366ed0
commit
8ce7db15e8
@ -1,3 +1,4 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BGRA/@EntryIndexedValue">BGRA</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DPI/@EntryIndexedValue">DPI</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DPI/@EntryIndexedValue">DPI</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DX/@EntryIndexedValue">DX</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DX/@EntryIndexedValue">DX</s:String></wpf:ResourceDictionary>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using SharpGen.Runtime;
|
using SharpGen.Runtime;
|
||||||
using Vortice.Direct3D;
|
using Vortice.Direct3D;
|
||||||
@ -18,7 +18,7 @@ namespace ScreenCapture.NET;
|
|||||||
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
|
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
public sealed class DX11ScreenCapture : IScreenCapture
|
public sealed class DX11ScreenCapture : AbstractScreenCapture<ColorBGRA>
|
||||||
{
|
{
|
||||||
#region Constants
|
#region Constants
|
||||||
|
|
||||||
@ -30,8 +30,6 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
FeatureLevel.Level_10_0
|
FeatureLevel.Level_10_0
|
||||||
};
|
};
|
||||||
|
|
||||||
private const int BPP = 4;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties & Fields
|
#region Properties & Fields
|
||||||
@ -39,14 +37,10 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
private readonly object _captureLock = new();
|
private readonly object _captureLock = new();
|
||||||
|
|
||||||
private readonly bool _useNewDuplicationAdapter;
|
private readonly bool _useNewDuplicationAdapter;
|
||||||
private int _indexCounter = 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Display Display { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the timeout in ms used for screen-capturing. (default 1000ms)
|
/// Gets or sets the timeout in ms used for screen-capturing. (default 1000ms)
|
||||||
/// This is used in <see cref="CaptureScreen"/> https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-acquirenextframe
|
/// This is used in <see cref="PerformScreenCapture"/> https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-acquirenextframe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
public int Timeout { get; set; } = 1000;
|
public int Timeout { get; set; } = 1000;
|
||||||
@ -59,14 +53,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
private ID3D11DeviceContext? _context;
|
private ID3D11DeviceContext? _context;
|
||||||
private ID3D11Texture2D? _captureTexture;
|
private ID3D11Texture2D? _captureTexture;
|
||||||
|
|
||||||
private readonly Dictionary<CaptureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView)> _captureZones = new();
|
private readonly Dictionary<CaptureZone<ColorBGRA>, ZoneTextures> _textures = new();
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -82,9 +69,9 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
/// <param name="display">The <see cref="Display"/> to duplicate.</param>
|
/// <param name="display">The <see cref="Display"/> to duplicate.</param>
|
||||||
/// <param name="useNewDuplicationAdapter">Indicates if the DuplicateOutput1 interface should be used instead of the older DuplicateOutput. Currently there's no real use in setting this to true.</param>
|
/// <param name="useNewDuplicationAdapter">Indicates if the DuplicateOutput1 interface should be used instead of the older DuplicateOutput. Currently there's no real use in setting this to true.</param>
|
||||||
public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false)
|
public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false)
|
||||||
|
: base(display)
|
||||||
{
|
{
|
||||||
this._factory = factory;
|
this._factory = factory;
|
||||||
this.Display = display;
|
|
||||||
this._useNewDuplicationAdapter = useNewDuplicationAdapter;
|
this._useNewDuplicationAdapter = useNewDuplicationAdapter;
|
||||||
|
|
||||||
Restart();
|
Restart();
|
||||||
@ -94,8 +81,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
|
|
||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
/// <inheritdoc />
|
protected override bool PerformScreenCapture()
|
||||||
public bool CaptureScreen()
|
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
lock (_captureLock)
|
lock (_captureLock)
|
||||||
@ -142,83 +128,68 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
catch { Thread.Sleep(100); }
|
catch { Thread.Sleep(100); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
UpdateZones();
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateZones()
|
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
if (_context == null) return;
|
if (_context == null) return;
|
||||||
|
|
||||||
lock (_captureZones)
|
lock (_textures)
|
||||||
{
|
{
|
||||||
foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested))
|
if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
|
||||||
|
|
||||||
|
if (textures.ScalingTexture != null)
|
||||||
{
|
{
|
||||||
if (scalingTexture != null)
|
_context.CopySubresourceRegion(textures.ScalingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
||||||
{
|
new Box(textures.X, textures.Y, 0,
|
||||||
_context.CopySubresourceRegion(scalingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
textures.X + textures.UnscaledWidth,
|
||||||
new Box(captureZone.X, captureZone.Y, 0,
|
textures.Y + textures.UnscaledHeight, 1));
|
||||||
captureZone.X + captureZone.UnscaledWidth,
|
_context.GenerateMips(textures.ScalingTextureView);
|
||||||
captureZone.Y + captureZone.UnscaledHeight, 1));
|
_context.CopySubresourceRegion(textures.StagingTexture, 0, 0, 0, 0, textures.ScalingTexture, captureZone.DownscaleLevel);
|
||||||
_context.GenerateMips(scalingTextureView);
|
|
||||||
_context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, scalingTexture, captureZone.DownscaleLevel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
_context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
|
||||||
new Box(captureZone.X, captureZone.Y, 0,
|
|
||||||
captureZone.X + captureZone.UnscaledWidth,
|
|
||||||
captureZone.Y + captureZone.UnscaledHeight, 1));
|
|
||||||
|
|
||||||
MappedSubresource mapSource = _context.Map(stagingTexture, 0, MapMode.Read, MapFlags.None);
|
|
||||||
lock (captureZone.Buffer)
|
|
||||||
{
|
|
||||||
Span<byte> source = mapSource.AsSpan(mapSource.RowPitch * captureZone.Height);
|
|
||||||
switch (Display.Rotation)
|
|
||||||
{
|
|
||||||
case Rotation.Rotation90:
|
|
||||||
CopyRotate90(source, mapSource.RowPitch, captureZone);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Rotation.Rotation180:
|
|
||||||
CopyRotate180(source, mapSource.RowPitch, captureZone);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Rotation.Rotation270:
|
|
||||||
CopyRotate270(source, mapSource.RowPitch, captureZone);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
CopyRotate0(source, mapSource.RowPitch, captureZone);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Unmap(stagingTexture, 0);
|
|
||||||
captureZone.SetUpdated();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
_context.CopySubresourceRegion(textures.StagingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
||||||
|
new Box(textures.X, textures.Y, 0,
|
||||||
|
textures.X + textures.UnscaledWidth,
|
||||||
|
textures.Y + textures.UnscaledHeight, 1));
|
||||||
|
|
||||||
|
MappedSubresource mapSource = _context.Map(textures.StagingTexture, 0, MapMode.Read, MapFlags.None);
|
||||||
|
using IDisposable @lock = captureZone.Image.Lock();
|
||||||
|
{
|
||||||
|
Span<byte> source = mapSource.AsSpan(mapSource.RowPitch * textures.Height);
|
||||||
|
switch (Display.Rotation)
|
||||||
|
{
|
||||||
|
case Rotation.Rotation90:
|
||||||
|
CopyRotate90(source, mapSource.RowPitch, captureZone);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Rotation.Rotation180:
|
||||||
|
CopyRotate180(source, mapSource.RowPitch, captureZone);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Rotation.Rotation270:
|
||||||
|
CopyRotate270(source, mapSource.RowPitch, captureZone);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
CopyRotate0(source, mapSource.RowPitch, captureZone);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Unmap(textures.StagingTexture, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CopyRotate0(in Span<byte> source, int sourceStride, in CaptureZone captureZone)
|
private static void CopyRotate0(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
int height = captureZone.Height;
|
int height = captureZone.Image.Height;
|
||||||
int stride = captureZone.Stride;
|
int stride = captureZone.Image.Stride;
|
||||||
Span<byte> target = captureZone.Buffer.AsSpan();
|
Span<byte> target = captureZone.Image.Raw;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++)
|
for (int y = 0; y < height; y++)
|
||||||
{
|
{
|
||||||
@ -230,98 +201,78 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CopyRotate90(in Span<byte> source, int sourceStride, in CaptureZone captureZone)
|
private static void CopyRotate90(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
int width = captureZone.Width;
|
int width = captureZone.Image.Width;
|
||||||
int height = captureZone.Height;
|
int height = captureZone.Image.Height;
|
||||||
Span<byte> target = captureZone.Buffer.AsSpan();
|
int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
|
||||||
|
Span<ColorBGRA> target = captureZone.Image.Pixels;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++)
|
for (int x = 0; x < width; x++)
|
||||||
for (int x = 0; x < width; x++)
|
{
|
||||||
{
|
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(x * sourceStride, usedBytesPerLine));
|
||||||
int sourceOffset = ((y * sourceStride) + (x * BPP));
|
for (int y = 0; y < src.Length; y++)
|
||||||
int targetOffset = ((x * height) + ((height - 1) - y)) * BPP;
|
target[(y * width) + (width - x - 1)] = src[y];
|
||||||
|
}
|
||||||
target[targetOffset] = source[sourceOffset];
|
|
||||||
target[targetOffset + 1] = source[sourceOffset + 1];
|
|
||||||
target[targetOffset + 2] = source[sourceOffset + 2];
|
|
||||||
target[targetOffset + 3] = source[sourceOffset + 3];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CopyRotate180(in Span<byte> source, int sourceStride, in CaptureZone captureZone)
|
private static void CopyRotate180(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
int width = captureZone.Width;
|
int width = captureZone.Image.Width;
|
||||||
int height = captureZone.Height;
|
int height = captureZone.Image.Height;
|
||||||
int stride = captureZone.Stride;
|
int bpp = captureZone.Image.ColorFormat.BytesPerPixel;
|
||||||
Span<byte> target = captureZone.Buffer.AsSpan();
|
int usedBytesPerLine = width * bpp;
|
||||||
|
Span<ColorBGRA> target = captureZone.Image.Pixels;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++)
|
for (int y = 0; y < height; y++)
|
||||||
for (int x = 0; x < width; x++)
|
{
|
||||||
|
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(y * sourceStride, usedBytesPerLine));
|
||||||
|
for (int x = 0; x < src.Length; x++)
|
||||||
{
|
{
|
||||||
int sourceOffset = ((y * sourceStride) + (x * BPP));
|
target[((height - y - 1) * width) + (width - x - 1)] = src[x];
|
||||||
int targetOffset = target.Length - ((y * stride) + (x * BPP)) - 1;
|
|
||||||
|
|
||||||
target[targetOffset - 3] = source[sourceOffset];
|
|
||||||
target[targetOffset - 2] = source[sourceOffset + 1];
|
|
||||||
target[targetOffset - 1] = source[sourceOffset + 2];
|
|
||||||
target[targetOffset] = source[sourceOffset + 3];
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CopyRotate270(in Span<byte> source, int sourceStride, in CaptureZone captureZone)
|
private static void CopyRotate270(in Span<byte> source, int sourceStride, in CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
int width = captureZone.Width;
|
int width = captureZone.Image.Width;
|
||||||
int height = captureZone.Height;
|
int height = captureZone.Image.Height;
|
||||||
Span<byte> target = captureZone.Buffer.AsSpan();
|
int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
|
||||||
|
Span<ColorBGRA> target = captureZone.Image.Pixels;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++)
|
for (int x = 0; x < width; x++)
|
||||||
for (int x = 0; x < width; x++)
|
{
|
||||||
{
|
Span<ColorBGRA> src = MemoryMarshal.Cast<byte, ColorBGRA>(source.Slice(x * sourceStride, usedBytesPerLine));
|
||||||
int sourceOffset = ((y * sourceStride) + (x * BPP));
|
for (int y = 0; y < src.Length; y++)
|
||||||
int targetOffset = ((((width - 1) - x) * height) + y) * BPP;
|
target[((height - y - 1) * width) + x] = src[y];
|
||||||
|
}
|
||||||
target[targetOffset] = source[sourceOffset];
|
|
||||||
target[targetOffset + 1] = source[sourceOffset + 1];
|
|
||||||
target[targetOffset + 2] = source[sourceOffset + 2];
|
|
||||||
target[targetOffset + 3] = source[sourceOffset + 3];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
public override CaptureZone<ColorBGRA> RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
||||||
{
|
{
|
||||||
ValidateCaptureZoneAndThrow(x, y, width, height);
|
CaptureZone<ColorBGRA> captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel);
|
||||||
|
|
||||||
if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
|
lock (_textures)
|
||||||
(x, y, width, height) = (y, x, height, width);
|
|
||||||
|
|
||||||
int unscaledWidth = width;
|
|
||||||
int unscaledHeight = height;
|
|
||||||
(width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel);
|
|
||||||
|
|
||||||
byte[] buffer = new byte[width * height * BPP];
|
|
||||||
|
|
||||||
CaptureZone captureZone = new(_indexCounter++, x, y, width, height, BPP, downscaleLevel, unscaledWidth, unscaledHeight, buffer);
|
|
||||||
lock (_captureZones)
|
|
||||||
InitializeCaptureZone(captureZone);
|
InitializeCaptureZone(captureZone);
|
||||||
|
|
||||||
return captureZone;
|
return captureZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool UnregisterCaptureZone(CaptureZone captureZone)
|
public override bool UnregisterCaptureZone(CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
lock (_captureZones)
|
if (!base.UnregisterCaptureZone(captureZone)) return false;
|
||||||
|
|
||||||
|
lock (_textures)
|
||||||
{
|
{
|
||||||
if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data))
|
if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
|
||||||
{
|
{
|
||||||
_captureZones.Remove(captureZone);
|
textures.Dispose();
|
||||||
data.stagingTexture.Dispose();
|
_textures.Remove(captureZone);
|
||||||
data.scalingTexture?.Dispose();
|
|
||||||
data._scalingTextureView?.Dispose();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -331,88 +282,66 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
|
public override void UpdateCaptureZone(CaptureZone<ColorBGRA> captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
|
||||||
{
|
{
|
||||||
lock (_captureZones)
|
base.UpdateCaptureZone(captureZone, x, y, width, height, downscaleLevel);
|
||||||
if (!_captureZones.ContainsKey(captureZone))
|
|
||||||
throw new ArgumentException("The capture zone is not registered to this ScreenCapture", nameof(captureZone));
|
|
||||||
|
|
||||||
int newX = x ?? captureZone.X;
|
|
||||||
int newY = y ?? captureZone.Y;
|
|
||||||
int newUnscaledWidth = width ?? captureZone.UnscaledWidth;
|
|
||||||
int newUnscaledHeight = height ?? captureZone.UnscaledHeight;
|
|
||||||
int newDownscaleLevel = downscaleLevel ?? captureZone.DownscaleLevel;
|
|
||||||
|
|
||||||
ValidateCaptureZoneAndThrow(newX, newY, newUnscaledWidth, newUnscaledHeight);
|
|
||||||
|
|
||||||
if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
|
|
||||||
(newX, newY, newUnscaledWidth, newUnscaledHeight) = (newY, newX, newUnscaledHeight, newUnscaledWidth);
|
|
||||||
|
|
||||||
captureZone.X = newX;
|
|
||||||
captureZone.Y = newY;
|
|
||||||
|
|
||||||
//TODO DarthAffe 01.05.2022: For now just reinitialize the zone in that case, but this could be optimized to only recreate the textures needed.
|
//TODO DarthAffe 01.05.2022: For now just reinitialize the zone in that case, but this could be optimized to only recreate the textures needed.
|
||||||
if ((width != null) || (height != null) || (downscaleLevel != null))
|
if ((width != null) || (height != null) || (downscaleLevel != null))
|
||||||
{
|
{
|
||||||
(int newWidth, int newHeight, newDownscaleLevel) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
|
lock (_textures)
|
||||||
lock (_captureZones)
|
|
||||||
{
|
{
|
||||||
UnregisterCaptureZone(captureZone);
|
if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
|
||||||
|
{
|
||||||
captureZone.UnscaledWidth = newUnscaledWidth;
|
textures.Dispose();
|
||||||
captureZone.UnscaledHeight = newUnscaledHeight;
|
InitializeCaptureZone(captureZone);
|
||||||
captureZone.Width = newWidth;
|
}
|
||||||
captureZone.Height = newHeight;
|
|
||||||
captureZone.DownscaleLevel = newDownscaleLevel;
|
|
||||||
captureZone.Buffer = new byte[newWidth * newHeight * BPP];
|
|
||||||
|
|
||||||
InitializeCaptureZone(captureZone);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (int width, int height, int downscaleLevel) CalculateScaledSize(int width, int height, int downscaleLevel)
|
protected override void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel)
|
||||||
{
|
|
||||||
if (downscaleLevel > 0)
|
|
||||||
for (int i = 0; i < downscaleLevel; i++)
|
|
||||||
{
|
|
||||||
if ((width <= 1) && (height <= 1))
|
|
||||||
{
|
|
||||||
downscaleLevel = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
width /= 2;
|
|
||||||
height /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < 1) width = 1;
|
|
||||||
if (height < 1) height = 1;
|
|
||||||
|
|
||||||
return (width, height, downscaleLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateCaptureZoneAndThrow(int x, int y, int width, int height)
|
|
||||||
{
|
{
|
||||||
if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized.");
|
if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized.");
|
||||||
|
|
||||||
if (x < 0) throw new ArgumentException("x < 0");
|
base.ValidateCaptureZoneAndThrow(x, y, width, height, downscaleLevel);
|
||||||
if (y < 0) throw new ArgumentException("y < 0");
|
|
||||||
if (width <= 0) throw new ArgumentException("with <= 0");
|
|
||||||
if (height <= 0) throw new ArgumentException("height <= 0");
|
|
||||||
if ((x + width) > Display.Width) throw new ArgumentException("x + width > Display width");
|
|
||||||
if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeCaptureZone(in CaptureZone captureZone)
|
private void InitializeCaptureZone(in CaptureZone<ColorBGRA> captureZone)
|
||||||
{
|
{
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int unscaledWidth;
|
||||||
|
int unscaledHeight;
|
||||||
|
|
||||||
|
if (captureZone.Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
|
||||||
|
{
|
||||||
|
x = captureZone.Y;
|
||||||
|
y = captureZone.X;
|
||||||
|
width = captureZone.Height;
|
||||||
|
height = captureZone.Width;
|
||||||
|
unscaledWidth = captureZone.UnscaledHeight;
|
||||||
|
unscaledHeight = captureZone.UnscaledWidth;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = captureZone.X;
|
||||||
|
y = captureZone.Y;
|
||||||
|
width = captureZone.Width;
|
||||||
|
height = captureZone.Height;
|
||||||
|
unscaledWidth = captureZone.UnscaledWidth;
|
||||||
|
unscaledHeight = captureZone.UnscaledHeight;
|
||||||
|
}
|
||||||
|
|
||||||
Texture2DDescription stagingTextureDesc = new()
|
Texture2DDescription stagingTextureDesc = new()
|
||||||
{
|
{
|
||||||
CPUAccessFlags = CpuAccessFlags.Read,
|
CPUAccessFlags = CpuAccessFlags.Read,
|
||||||
BindFlags = BindFlags.None,
|
BindFlags = BindFlags.None,
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
Width = captureZone.Width,
|
Width = width,
|
||||||
Height = captureZone.Height,
|
Height = height,
|
||||||
MiscFlags = ResourceOptionFlags.None,
|
MiscFlags = ResourceOptionFlags.None,
|
||||||
MipLevels = 1,
|
MipLevels = 1,
|
||||||
ArraySize = 1,
|
ArraySize = 1,
|
||||||
@ -430,8 +359,8 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
CPUAccessFlags = CpuAccessFlags.None,
|
CPUAccessFlags = CpuAccessFlags.None,
|
||||||
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
Width = captureZone.UnscaledWidth,
|
Width = unscaledWidth,
|
||||||
Height = captureZone.UnscaledHeight,
|
Height = unscaledHeight,
|
||||||
MiscFlags = ResourceOptionFlags.GenerateMips,
|
MiscFlags = ResourceOptionFlags.GenerateMips,
|
||||||
MipLevels = captureZone.DownscaleLevel + 1,
|
MipLevels = captureZone.DownscaleLevel + 1,
|
||||||
ArraySize = 1,
|
ArraySize = 1,
|
||||||
@ -442,95 +371,134 @@ public sealed class DX11ScreenCapture : IScreenCapture
|
|||||||
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
|
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
_captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView);
|
_textures[captureZone] = new ZoneTextures(x, y, width, height, unscaledWidth, unscaledHeight, stagingTexture, scalingTexture, scalingTextureView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Restart()
|
public override void Restart()
|
||||||
{
|
{
|
||||||
|
base.Restart();
|
||||||
|
|
||||||
lock (_captureLock)
|
lock (_captureLock)
|
||||||
{
|
lock (_textures)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
try
|
||||||
Dispose();
|
|
||||||
|
|
||||||
using IDXGIAdapter1 adapter = _factory.GetAdapter1(Display.GraphicsCard.Index) ?? throw new ApplicationException("Couldn't create DirectX-Adapter.");
|
|
||||||
|
|
||||||
D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, FEATURE_LEVELS, out _device).CheckError();
|
|
||||||
_context = _device!.ImmediateContext;
|
|
||||||
|
|
||||||
_output = adapter.GetOutput(Display.Index) ?? throw new ApplicationException("Couldn't get DirectX-Output.");
|
|
||||||
using IDXGIOutput5 output = _output.QueryInterface<IDXGIOutput5>();
|
|
||||||
|
|
||||||
int width = Display.Width;
|
|
||||||
int height = Display.Height;
|
|
||||||
if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
|
|
||||||
(width, height) = (height, width);
|
|
||||||
|
|
||||||
Texture2DDescription captureTextureDesc = new()
|
|
||||||
{
|
{
|
||||||
CPUAccessFlags = CpuAccessFlags.None,
|
foreach (ZoneTextures textures in _textures.Values)
|
||||||
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
textures.Dispose();
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
_textures.Clear();
|
||||||
Width = width,
|
|
||||||
Height = height,
|
|
||||||
MiscFlags = ResourceOptionFlags.None,
|
|
||||||
MipLevels = 1,
|
|
||||||
ArraySize = 1,
|
|
||||||
SampleDescription = { Count = 1, Quality = 0 },
|
|
||||||
Usage = ResourceUsage.Default
|
|
||||||
};
|
|
||||||
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
|
|
||||||
|
|
||||||
lock (_captureZones)
|
DisposeDX();
|
||||||
{
|
|
||||||
foreach (CaptureZone captureZone in captureZones)
|
using IDXGIAdapter1 adapter = _factory.GetAdapter1(Display.GraphicsCard.Index) ?? throw new ApplicationException("Couldn't create DirectX-Adapter.");
|
||||||
|
|
||||||
|
D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, FEATURE_LEVELS, out _device).CheckError();
|
||||||
|
_context = _device!.ImmediateContext;
|
||||||
|
|
||||||
|
_output = adapter.GetOutput(Display.Index) ?? throw new ApplicationException("Couldn't get DirectX-Output.");
|
||||||
|
using IDXGIOutput5 output = _output.QueryInterface<IDXGIOutput5>();
|
||||||
|
|
||||||
|
int width = Display.Width;
|
||||||
|
int height = Display.Height;
|
||||||
|
if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
|
||||||
|
(width, height) = (height, width);
|
||||||
|
|
||||||
|
Texture2DDescription captureTextureDesc = new()
|
||||||
|
{
|
||||||
|
CPUAccessFlags = CpuAccessFlags.None,
|
||||||
|
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
||||||
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
MiscFlags = ResourceOptionFlags.None,
|
||||||
|
MipLevels = 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
SampleDescription = { Count = 1, Quality = 0 },
|
||||||
|
Usage = ResourceUsage.Default
|
||||||
|
};
|
||||||
|
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
|
||||||
|
|
||||||
|
foreach (CaptureZone<ColorBGRA> captureZone in CaptureZones)
|
||||||
InitializeCaptureZone(captureZone);
|
InitializeCaptureZone(captureZone);
|
||||||
}
|
|
||||||
|
|
||||||
if (_useNewDuplicationAdapter)
|
if (_useNewDuplicationAdapter)
|
||||||
_duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
|
_duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
|
||||||
else
|
else
|
||||||
_duplicatedOutput = output.DuplicateOutput(_device);
|
_duplicatedOutput = output.DuplicateOutput(_device);
|
||||||
|
}
|
||||||
|
catch { DisposeDX(); }
|
||||||
}
|
}
|
||||||
catch { Dispose(false); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
protected override void Dispose(bool disposing)
|
||||||
public void Dispose() => Dispose(true);
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
DisposeDX();
|
||||||
|
}
|
||||||
|
|
||||||
private void Dispose(bool removeCaptureZones)
|
private void DisposeDX()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_captureLock)
|
try { _duplicatedOutput?.Dispose(); } catch { /**/ }
|
||||||
{
|
try { _output?.Dispose(); } catch { /**/ }
|
||||||
try { _duplicatedOutput?.Dispose(); } catch { /**/ }
|
try { _context?.Dispose(); } catch { /**/ }
|
||||||
_duplicatedOutput = null;
|
try { _device?.Dispose(); } catch { /**/ }
|
||||||
|
try { _captureTexture?.Dispose(); } catch { /**/ }
|
||||||
|
|
||||||
try
|
_duplicatedOutput = null;
|
||||||
{
|
_context = null;
|
||||||
if (removeCaptureZones)
|
_captureTexture = null;
|
||||||
{
|
|
||||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
|
||||||
foreach (CaptureZone captureZone in captureZones)
|
|
||||||
UnregisterCaptureZone(captureZone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
try { _output?.Dispose(); } catch { /**/ }
|
|
||||||
try { _context?.Dispose(); } catch { /**/ }
|
|
||||||
try { _device?.Dispose(); } catch { /**/ }
|
|
||||||
try { _captureTexture?.Dispose(); } catch { /**/ }
|
|
||||||
_context = null;
|
|
||||||
_captureTexture = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { /**/ }
|
catch { /**/ }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private sealed class ZoneTextures : IDisposable
|
||||||
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public int X { get; }
|
||||||
|
public int Y { get; }
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
public int UnscaledWidth { get; }
|
||||||
|
public int UnscaledHeight { get; }
|
||||||
|
|
||||||
|
public ID3D11Texture2D StagingTexture { get; }
|
||||||
|
public ID3D11Texture2D? ScalingTexture { get; }
|
||||||
|
public ID3D11ShaderResourceView? ScalingTextureView { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public ZoneTextures(int x, int y, int width, int height, int unscaledWidth, int unscaledHeight,
|
||||||
|
ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)
|
||||||
|
{
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
this.Width = width;
|
||||||
|
this.Height = height;
|
||||||
|
this.UnscaledWidth = unscaledWidth;
|
||||||
|
this.UnscaledHeight = unscaledHeight;
|
||||||
|
this.StagingTexture = stagingTexture;
|
||||||
|
this.ScalingTexture = scalingTexture;
|
||||||
|
this.ScalingTextureView = scalingTextureView;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StagingTexture.Dispose();
|
||||||
|
ScalingTexture?.Dispose();
|
||||||
|
ScalingTextureView?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ public class DX11ScreenCaptureService : IScreenCaptureService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rotation GetRotation(ModeRotation rotation) => rotation switch
|
private static Rotation GetRotation(ModeRotation rotation) => rotation switch
|
||||||
{
|
{
|
||||||
ModeRotation.Rotate90 => Rotation.Rotation90,
|
ModeRotation.Rotate90 => Rotation.Rotation90,
|
||||||
ModeRotation.Rotate180 => Rotation.Rotation180,
|
ModeRotation.Rotate180 => Rotation.Rotation180,
|
||||||
@ -68,7 +68,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IScreenCapture GetScreenCapture(Display display)
|
IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
|
||||||
|
public DX11ScreenCapture GetScreenCapture(Display display)
|
||||||
{
|
{
|
||||||
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
|
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
|
||||||
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
|
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
|
||||||
|
|||||||
206
ScreenCapture.NET/Generic/AbstractScreenCapture.cs
Normal file
206
ScreenCapture.NET/Generic/AbstractScreenCapture.cs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
public abstract class AbstractScreenCapture<TColor> : IScreenCapture
|
||||||
|
where TColor : struct, IColor
|
||||||
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
private int _indexCounter = 0;
|
||||||
|
|
||||||
|
protected HashSet<CaptureZone<TColor>> CaptureZones { get; } = new();
|
||||||
|
|
||||||
|
public Display Display { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
public event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
protected AbstractScreenCapture(Display display)
|
||||||
|
{
|
||||||
|
this.Display = display;
|
||||||
|
}
|
||||||
|
|
||||||
|
~AbstractScreenCapture() => Dispose(false);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public virtual bool CaptureScreen()
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = PerformScreenCapture();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CaptureZone<TColor> captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PerformCaptureZoneUpdate(captureZone);
|
||||||
|
captureZone.SetUpdated();
|
||||||
|
}
|
||||||
|
catch { /* */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
OnUpdated(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool PerformScreenCapture();
|
||||||
|
|
||||||
|
protected abstract void PerformCaptureZoneUpdate(CaptureZone<TColor> captureZone);
|
||||||
|
|
||||||
|
protected virtual void OnUpdated(bool result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
|
||||||
|
}
|
||||||
|
catch { /**/ }
|
||||||
|
}
|
||||||
|
|
||||||
|
ICaptureZone IScreenCapture.RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel) => RegisterCaptureZone(x, y, width, height, downscaleLevel);
|
||||||
|
public virtual CaptureZone<TColor> RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
|
||||||
|
lock (CaptureZones)
|
||||||
|
{
|
||||||
|
ValidateCaptureZoneAndThrow(x, y, width, height, downscaleLevel);
|
||||||
|
|
||||||
|
int unscaledWidth = width;
|
||||||
|
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
|
||||||
|
CaptureZones.Add(captureZone);
|
||||||
|
|
||||||
|
return captureZone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel)
|
||||||
|
{
|
||||||
|
if (x < 0) throw new ArgumentException("x < 0");
|
||||||
|
if (y < 0) throw new ArgumentException("y < 0");
|
||||||
|
if (width <= 0) throw new ArgumentException("with <= 0");
|
||||||
|
if (height <= 0) throw new ArgumentException("height <= 0");
|
||||||
|
if ((x + width) > Display.Width) throw new ArgumentException("x + width > Display width");
|
||||||
|
if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual (int width, int height, int downscaleLevel) CalculateScaledSize(int width, int height, int downscaleLevel)
|
||||||
|
{
|
||||||
|
if (downscaleLevel > 0)
|
||||||
|
for (int i = 0; i < downscaleLevel; i++)
|
||||||
|
{
|
||||||
|
if ((width <= 1) && (height <= 1))
|
||||||
|
{
|
||||||
|
downscaleLevel = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
width /= 2;
|
||||||
|
height /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width < 1) width = 1;
|
||||||
|
if (height < 1) height = 1;
|
||||||
|
|
||||||
|
return (width, height, downscaleLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IScreenCapture.UnregisterCaptureZone(ICaptureZone captureZone) => UnregisterCaptureZone(captureZone as CaptureZone<TColor> ?? throw new ArgumentException("Invalid capture-zone."));
|
||||||
|
public virtual bool UnregisterCaptureZone(CaptureZone<TColor> captureZone)
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
|
||||||
|
return CaptureZones.Remove(captureZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IScreenCapture.UpdateCaptureZone(ICaptureZone captureZone, int? x, int? y, int? width, int? height, int? downscaleLevel)
|
||||||
|
=> UpdateCaptureZone(captureZone as CaptureZone<TColor> ?? throw new ArgumentException("Invalid capture-zone."), x, y, width, height, downscaleLevel);
|
||||||
|
public virtual void UpdateCaptureZone(CaptureZone<TColor> captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
|
||||||
|
lock (CaptureZones)
|
||||||
|
{
|
||||||
|
if (!CaptureZones.Contains(captureZone))
|
||||||
|
throw new ArgumentException("The capture zone is not registered to this ScreenCapture", nameof(captureZone));
|
||||||
|
|
||||||
|
int newX = x ?? captureZone.X;
|
||||||
|
int newY = y ?? captureZone.Y;
|
||||||
|
int newUnscaledWidth = width ?? captureZone.UnscaledWidth;
|
||||||
|
int newUnscaledHeight = height ?? captureZone.UnscaledHeight;
|
||||||
|
int newDownscaleLevel = downscaleLevel ?? captureZone.DownscaleLevel;
|
||||||
|
|
||||||
|
ValidateCaptureZoneAndThrow(newX, newY, newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
|
||||||
|
|
||||||
|
captureZone.X = newX;
|
||||||
|
captureZone.Y = newY;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Restart()
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
catch { /* don't throw in dispose! */ }
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing) { }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -32,14 +32,14 @@ public interface IScreenCapture : IDisposable
|
|||||||
/// <param name="height">The height of the region to capture (must be >= 0 and this + y must be <= screen-height).</param>
|
/// <param name="height">The height of the region to capture (must be >= 0 and this + y must be <= screen-height).</param>
|
||||||
/// <param name="downscaleLevel">The level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel.</param>
|
/// <param name="downscaleLevel">The level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel.</param>
|
||||||
/// <returns>The new <see cref="CaptureScreen"/>.</returns>
|
/// <returns>The new <see cref="CaptureScreen"/>.</returns>
|
||||||
CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0);
|
ICaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the given <see cref="CaptureScreen"/> from the <see cref="IScreenCapture"/>.
|
/// Removes the given <see cref="CaptureScreen"/> from the <see cref="IScreenCapture"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="captureZone">The previously registered <see cref="CaptureScreen"/>.</param>
|
/// <param name="captureZone">The previously registered <see cref="CaptureScreen"/>.</param>
|
||||||
/// <returns><c>true</c> if the <see cref="CaptureScreen"/> was successfully removed; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if the <see cref="CaptureScreen"/> was successfully removed; otherwise, <c>false</c>.</returns>
|
||||||
bool UnregisterCaptureZone(CaptureZone captureZone);
|
bool UnregisterCaptureZone(ICaptureZone captureZone);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the the given <see cref="CaptureScreen"/>.
|
/// Updates the the given <see cref="CaptureScreen"/>.
|
||||||
@ -53,7 +53,7 @@ public interface IScreenCapture : IDisposable
|
|||||||
/// <param name="width">The width of the region to capture (must be >= 0 and this + x must be <= screen-width).</param>
|
/// <param name="width">The width of the region to capture (must be >= 0 and this + x must be <= screen-width).</param>
|
||||||
/// <param name="height">The new height of the region to capture (must be >= 0 and this + y must be <= screen-height).</param>
|
/// <param name="height">The new height of the region to capture (must be >= 0 and this + y must be <= screen-height).</param>
|
||||||
/// <param name="downscaleLevel">The new level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel.</param>
|
/// <param name="downscaleLevel">The new level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel.</param>
|
||||||
void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null);
|
void UpdateCaptureZone(ICaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restarts the <see cref="IScreenCapture"/>.
|
/// Restarts the <see cref="IScreenCapture"/>.
|
||||||
@ -11,7 +11,7 @@ public sealed class BlackBarDetection
|
|||||||
{
|
{
|
||||||
#region Properties & Fields
|
#region Properties & Fields
|
||||||
|
|
||||||
private readonly CaptureZone _captureZone;
|
private readonly ICaptureZone _captureZone;
|
||||||
|
|
||||||
private int? _top;
|
private int? _top;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,7 +55,7 @@ public sealed class BlackBarDetection
|
|||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
internal BlackBarDetection(CaptureZone captureZone)
|
internal BlackBarDetection(ICaptureZone captureZone)
|
||||||
{
|
{
|
||||||
this._captureZone = captureZone;
|
this._captureZone = captureZone;
|
||||||
}
|
}
|
||||||
@ -77,62 +77,62 @@ public sealed class BlackBarDetection
|
|||||||
|
|
||||||
private int CalculateTop()
|
private int CalculateTop()
|
||||||
{
|
{
|
||||||
int threshold = Threshold;
|
//int threshold = Threshold;
|
||||||
int stride = _captureZone.Stride;
|
//int stride = _captureZone.Stride;
|
||||||
for (int row = 0; row < _captureZone.Height; row++)
|
//for (int row = 0; row < _captureZone.Height; row++)
|
||||||
{
|
//{
|
||||||
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
// Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
||||||
for (int i = 0; i < data.Length; i += 4)
|
// for (int i = 0; i < data.Length; i += 4)
|
||||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
// if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||||
return row;
|
// return row;
|
||||||
}
|
//}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CalculateBottom()
|
private int CalculateBottom()
|
||||||
{
|
{
|
||||||
int threshold = Threshold;
|
//int threshold = Threshold;
|
||||||
int stride = _captureZone.Stride;
|
//int stride = _captureZone.Stride;
|
||||||
for (int row = _captureZone.Height - 1; row >= 0; row--)
|
//for (int row = _captureZone.Height - 1; row >= 0; row--)
|
||||||
{
|
//{
|
||||||
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
// Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
||||||
for (int i = 0; i < data.Length; i += 4)
|
// for (int i = 0; i < data.Length; i += 4)
|
||||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
// if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||||
return (_captureZone.Height - 1) - row;
|
// return (_captureZone.Height - 1) - row;
|
||||||
}
|
//}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CalculateLeft()
|
private int CalculateLeft()
|
||||||
{
|
{
|
||||||
int threshold = Threshold;
|
//int threshold = Threshold;
|
||||||
int stride = _captureZone.Stride;
|
//int stride = _captureZone.Stride;
|
||||||
byte[] buffer = _captureZone.Buffer;
|
//byte[] buffer = _captureZone.Buffer;
|
||||||
for (int column = 0; column < _captureZone.Width; column++)
|
//for (int column = 0; column < _captureZone.Width; column++)
|
||||||
for (int row = 0; row < _captureZone.Height; row++)
|
// for (int row = 0; row < _captureZone.Height; row++)
|
||||||
{
|
// {
|
||||||
int offset = (stride * row) + (column * 4);
|
// int offset = (stride * row) + (column * 4);
|
||||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
// if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
||||||
return column;
|
// return column;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CalculateRight()
|
private int CalculateRight()
|
||||||
{
|
{
|
||||||
int threshold = Threshold;
|
//int threshold = Threshold;
|
||||||
int stride = _captureZone.Stride;
|
//int stride = _captureZone.Stride;
|
||||||
byte[] buffer = _captureZone.Buffer;
|
//byte[] buffer = _captureZone.Buffer;
|
||||||
for (int column = _captureZone.Width - 1; column >= 0; column--)
|
//for (int column = _captureZone.Width - 1; column >= 0; column--)
|
||||||
for (int row = 0; row < _captureZone.Height; row++)
|
// for (int row = 0; row < _captureZone.Height; row++)
|
||||||
{
|
// {
|
||||||
int offset = (stride * row) + (column * 4);
|
// int offset = (stride * row) + (column * 4);
|
||||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
// if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
||||||
return (_captureZone.Width - 1) - column;
|
// return (_captureZone.Width - 1) - column;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,15 +7,18 @@ namespace ScreenCapture.NET;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a duplicated region on the screen.
|
/// Represents a duplicated region on the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CaptureZone
|
public sealed class CaptureZone<TColor> : ICaptureZone
|
||||||
|
where TColor : struct, IColor
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
#region Properties & Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the unique id of this <see cref="CaptureZone"/>.
|
/// Gets the unique id of this <see cref="CaptureZone{T}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
|
||||||
|
public Display Display { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the x-location of the region on the screen.
|
/// Gets the x-location of the region on the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -51,42 +54,25 @@ public sealed class CaptureZone
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int UnscaledHeight { get; internal set; }
|
public int UnscaledHeight { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
IScreenImage ICaptureZone.Image => Image;
|
||||||
/// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]).
|
public ScreenImage<TColor> Image { get; }
|
||||||
/// </summary>
|
|
||||||
public int BytesPerPixel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the size in bytes of a row in the region (<see cref="Width"/> * <see cref="BytesPerPixel"/>).
|
/// Gets or sets if the <see cref="CaptureZone{T}"/> should be automatically updated on every captured frame.
|
||||||
/// </summary>
|
|
||||||
public int Stride => Width * BytesPerPixel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Buffer { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the config for black-bar detection.
|
|
||||||
/// </summary>
|
|
||||||
public BlackBarDetection BlackBars { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets if the <see cref="CaptureZone"/> should be automatically updated on every captured frame.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoUpdate { get; set; } = true;
|
public bool AutoUpdate { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets if an update for the <see cref="CaptureZone"/> is requested on the next captured frame.
|
/// Gets if an update for the <see cref="CaptureZone{T}"/> is requested on the next captured frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsUpdateRequested { get; private set; }
|
public bool IsUpdateRequested { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the <see cref="CaptureZone"/> is updated.
|
/// Occurs when the <see cref="CaptureZone{T}"/> is updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler? Updated;
|
public event EventHandler? Updated;
|
||||||
|
|
||||||
@ -95,9 +81,9 @@ public sealed class CaptureZone
|
|||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CaptureZone"/> class.
|
/// Initializes a new instance of the <see cref="CaptureZone{T}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The unique id of this <see cref="CaptureZone"/>.</param>
|
/// <param name="id">The unique id of this <see cref="CaptureZone{T}"/>.</param>
|
||||||
/// <param name="x">The x-location of the region on the screen.</param>
|
/// <param name="x">The x-location of the region on the screen.</param>
|
||||||
/// <param name="y">The y-location of the region on the screen.</param>
|
/// <param name="y">The y-location of the region on the screen.</param>
|
||||||
/// <param name="width">The width of the region on the screen.</param>
|
/// <param name="width">The width of the region on the screen.</param>
|
||||||
@ -107,20 +93,18 @@ public sealed class CaptureZone
|
|||||||
/// <param name="unscaledWidth">The original width of the region.</param>
|
/// <param name="unscaledWidth">The original width of the region.</param>
|
||||||
/// <param name="unscaledHeight">The original height of the region</param>
|
/// <param name="unscaledHeight">The original height of the region</param>
|
||||||
/// <param name="buffer">The buffer containing the image data.</param>
|
/// <param name="buffer">The buffer containing the image data.</param>
|
||||||
internal CaptureZone(int id, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, byte[] buffer)
|
internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, ScreenImage<TColor> image)
|
||||||
{
|
{
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
|
this.Display = display;
|
||||||
this.X = x;
|
this.X = x;
|
||||||
this.Y = y;
|
this.Y = y;
|
||||||
this.Width = width;
|
this.Width = width;
|
||||||
this.Height = height;
|
this.Height = height;
|
||||||
this.BytesPerPixel = bytesPerPixel;
|
|
||||||
this.UnscaledWidth = unscaledWidth;
|
this.UnscaledWidth = unscaledWidth;
|
||||||
this.UnscaledHeight = unscaledHeight;
|
this.UnscaledHeight = unscaledHeight;
|
||||||
this.DownscaleLevel = downscaleLevel;
|
this.DownscaleLevel = downscaleLevel;
|
||||||
this.Buffer = buffer;
|
this.Image = image;
|
||||||
|
|
||||||
BlackBars = new BlackBarDetection(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -128,32 +112,30 @@ public sealed class CaptureZone
|
|||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Requests to update this <see cref="CaptureZone"/> when the next frame is captured.
|
/// 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>.
|
/// Only necessary if <see cref="AutoUpdate"/> is set to <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RequestUpdate() => IsUpdateRequested = true;
|
public void RequestUpdate() => IsUpdateRequested = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the <see cref="CaptureZone"/> as updated.
|
/// Marks the <see cref="CaptureZone{T}"/> as updated.
|
||||||
/// WARNING: This should not be called outside of an <see cref="IScreenCapture"/>!
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetUpdated()
|
internal void SetUpdated()
|
||||||
{
|
{
|
||||||
IsUpdateRequested = false;
|
IsUpdateRequested = false;
|
||||||
BlackBars.InvalidateCache();
|
|
||||||
|
|
||||||
Updated?.Invoke(this, EventArgs.Empty);
|
Updated?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether this <see cref="CaptureZone"/> equals the given one.
|
/// Determines whether this <see cref="CaptureZone{T}"/> equals the given one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">The <see cref="CaptureZone"/> to compare.</param>
|
/// <param name="other">The <see cref="CaptureZone{T}"/> to compare.</param>
|
||||||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
||||||
public bool Equals(CaptureZone other) => Id == other.Id;
|
public bool Equals(CaptureZone<TColor> other) => Id == other.Id;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
|
public override bool Equals(object? obj) => obj is CaptureZone<TColor> other && Equals(other);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override int GetHashCode() => Id;
|
public override int GetHashCode() => Id;
|
||||||
|
|||||||
43
ScreenCapture.NET/Model/ColorFormat.cs
Normal file
43
ScreenCapture.NET/Model/ColorFormat.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
public readonly struct ColorFormat
|
||||||
|
{
|
||||||
|
#region Instances
|
||||||
|
|
||||||
|
public static readonly ColorFormat BGRA = new(1, 4);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public readonly int Id;
|
||||||
|
public readonly int BytesPerPixel;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
private ColorFormat(int id, int bytesPerPixel)
|
||||||
|
{
|
||||||
|
this.Id = id;
|
||||||
|
this.BytesPerPixel = bytesPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public bool Equals(ColorFormat other) => Id == other.Id;
|
||||||
|
public override bool Equals(object? obj) => obj is ColorFormat other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode() => Id;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Operators
|
||||||
|
|
||||||
|
public static bool operator ==(ColorFormat left, ColorFormat right) => left.Equals(right);
|
||||||
|
public static bool operator !=(ColorFormat left, ColorFormat right) => !(left == right);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
39
ScreenCapture.NET/Model/Colors/ColorBGRA.cs
Normal file
39
ScreenCapture.NET/Model/Colors/ColorBGRA.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// ReSharper disable ConvertToAutoProperty
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct ColorBGRA : IColor
|
||||||
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
#if NET7_0_OR_GREATER
|
||||||
|
public static ColorFormat ColorFormat => ColorFormat.BGRA;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly byte _b;
|
||||||
|
private readonly byte _g;
|
||||||
|
private readonly byte _r;
|
||||||
|
private readonly byte _a;
|
||||||
|
|
||||||
|
public byte B => _b;
|
||||||
|
public byte G => _g;
|
||||||
|
public byte R => _r;
|
||||||
|
public byte A => _a;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public ColorBGRA(byte b, byte g, byte r, byte a)
|
||||||
|
{
|
||||||
|
this._b = b;
|
||||||
|
this._g = g;
|
||||||
|
this._r = r;
|
||||||
|
this._a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
22
ScreenCapture.NET/Model/Colors/IColor.cs
Normal file
22
ScreenCapture.NET/Model/Colors/IColor.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
public interface IColor
|
||||||
|
{
|
||||||
|
byte R { get; }
|
||||||
|
byte G { get; }
|
||||||
|
byte B { get; }
|
||||||
|
byte A { get; }
|
||||||
|
|
||||||
|
#if NET7_0_OR_GREATER
|
||||||
|
public static abstract ColorFormat ColorFormat { get; }
|
||||||
|
#else
|
||||||
|
public static ColorFormat GetColorFormat<TColor>()
|
||||||
|
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
|
||||||
|
}
|
||||||
60
ScreenCapture.NET/Model/ICaptureZone.cs
Normal file
60
ScreenCapture.NET/Model/ICaptureZone.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
public interface ICaptureZone
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique id of this <see cref="ICaptureZone"/>.
|
||||||
|
/// </summary>
|
||||||
|
int Id { get; }
|
||||||
|
Display Display { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the x-location of the region on the screen.
|
||||||
|
/// </summary>
|
||||||
|
int X { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the y-location of the region on the screen.
|
||||||
|
/// </summary>
|
||||||
|
int Y { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the width of the captured region.
|
||||||
|
/// </summary>
|
||||||
|
int Width { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the height of the captured region.
|
||||||
|
/// </summary>
|
||||||
|
int Height { get; }
|
||||||
|
/// <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>
|
||||||
|
int DownscaleLevel { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original width of the region (this equals <see cref="Width"/> if <see cref="DownscaleLevel"/> is 0).
|
||||||
|
/// </summary>
|
||||||
|
int UnscaledWidth { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original height of the region (this equals <see cref="Height"/> if <see cref="DownscaleLevel"/> is 0).
|
||||||
|
/// </summary>
|
||||||
|
int UnscaledHeight { get; }
|
||||||
|
|
||||||
|
IScreenImage Image { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if the <see cref="ICaptureZone"/> should be automatically updated on every captured frame.
|
||||||
|
/// </summary>
|
||||||
|
bool AutoUpdate { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if an update for the <see cref="ICaptureZone"/> is requested on the next captured frame.
|
||||||
|
/// </summary>
|
||||||
|
bool IsUpdateRequested { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the <see cref="ICaptureZone"/> is updated.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler? Updated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests to update this <see cref="ICaptureZone"/> when the next frame is captured.
|
||||||
|
/// Only necessary if <see cref="AutoUpdate"/> is set to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
void RequestUpdate();
|
||||||
|
}
|
||||||
16
ScreenCapture.NET/Model/IScreenImage.cs
Normal file
16
ScreenCapture.NET/Model/IScreenImage.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
102
ScreenCapture.NET/Model/ScreenImage.cs
Normal file
102
ScreenCapture.NET/Model/ScreenImage.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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,6 +41,10 @@
|
|||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)'=='net6.0'">
|
||||||
|
<EnablePreviewFeatures>true</EnablePreviewFeatures>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
|
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=communitytoolkit_002Ehighperformance/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=directx/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=directx/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generic/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model_005Ccolors/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
Loading…
x
Reference in New Issue
Block a user