mirror of
https://github.com/DarthAffe/ScreenCapture.NET.git
synced 2025-12-13 05:48:39 +00:00
Small refactorings
This commit is contained in:
parent
dee0c096a6
commit
19080e6ec7
@ -11,420 +11,419 @@ using Vortice.Mathematics;
|
|||||||
using MapFlags = Vortice.Direct3D11.MapFlags;
|
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||||
using ResultCode = Vortice.DXGI.ResultCode;
|
using ResultCode = Vortice.DXGI.ResultCode;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a ScreenCapture using DirectX 11 desktop duplicaton.
|
||||||
|
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
|
||||||
|
/// </summary>
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public sealed class DX11ScreenCapture : IScreenCapture
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Constants
|
||||||
/// Represents a ScreenCapture using DirectX 11 desktop duplicaton.
|
|
||||||
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
|
private static readonly FeatureLevel[] FEATURE_LEVELS =
|
||||||
/// </summary>
|
|
||||||
// ReSharper disable once InconsistentNaming
|
|
||||||
public sealed class DX11ScreenCapture : IScreenCapture
|
|
||||||
{
|
{
|
||||||
#region Constants
|
FeatureLevel.Level_11_1,
|
||||||
|
FeatureLevel.Level_11_0,
|
||||||
|
FeatureLevel.Level_10_1,
|
||||||
|
FeatureLevel.Level_10_0
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly FeatureLevel[] FEATURE_LEVELS =
|
private const int BPP = 4;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
private readonly object _captureLock = new();
|
||||||
|
|
||||||
|
private readonly bool _useNewDuplicationAdapter;
|
||||||
|
private int _indexCounter = 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Display Display { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// </summary>
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
public int Timeout { get; set; } = 1000;
|
||||||
|
|
||||||
|
private readonly IDXGIFactory1 _factory;
|
||||||
|
|
||||||
|
private IDXGIOutput? _output;
|
||||||
|
private IDXGIOutputDuplication? _duplicatedOutput;
|
||||||
|
private ID3D11Device? _device;
|
||||||
|
private ID3D11DeviceContext? _context;
|
||||||
|
private ID3D11Texture2D? _captureTexture;
|
||||||
|
|
||||||
|
private readonly Dictionary<CaptureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView)> _captureZones = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DX11ScreenCapture"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that setting useNewDuplicationAdapter to true requires to call <c>DPIAwareness.Initalize();</c> and prevents the capture from running in a WPF-thread.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="factory">The <see cref="IDXGIFactory1"/> used to create underlying objects.</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>
|
||||||
|
public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false)
|
||||||
|
{
|
||||||
|
this._factory = factory;
|
||||||
|
this.Display = display;
|
||||||
|
this._useNewDuplicationAdapter = useNewDuplicationAdapter;
|
||||||
|
|
||||||
|
Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool CaptureScreen()
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
lock (_captureLock)
|
||||||
{
|
{
|
||||||
FeatureLevel.Level_11_1,
|
if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null))
|
||||||
FeatureLevel.Level_11_0,
|
|
||||||
FeatureLevel.Level_10_1,
|
|
||||||
FeatureLevel.Level_10_0
|
|
||||||
};
|
|
||||||
|
|
||||||
private const int BPP = 4;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties & Fields
|
|
||||||
|
|
||||||
private readonly object _captureLock = new();
|
|
||||||
|
|
||||||
private readonly bool _useNewDuplicationAdapter;
|
|
||||||
private int _indexCounter = 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Display Display { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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
|
|
||||||
/// </summary>
|
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public int Timeout { get; set; } = 1000;
|
|
||||||
|
|
||||||
private readonly IDXGIFactory1 _factory;
|
|
||||||
|
|
||||||
private IDXGIOutput? _output;
|
|
||||||
private IDXGIOutputDuplication? _duplicatedOutput;
|
|
||||||
private ID3D11Device? _device;
|
|
||||||
private ID3D11DeviceContext? _context;
|
|
||||||
private ID3D11Texture2D? _captureTexture;
|
|
||||||
|
|
||||||
private readonly Dictionary<CaptureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView)> _captureZones = new();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DX11ScreenCapture"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Note that setting useNewDuplicationAdapter to true requires to call <c>DPIAwareness.Initalize();</c> and prevents the capture from running in a WPF-thread.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="factory">The <see cref="IDXGIFactory1"/> used to create underlying objects.</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>
|
|
||||||
public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false)
|
|
||||||
{
|
|
||||||
this._factory = factory;
|
|
||||||
this.Display = display;
|
|
||||||
this._useNewDuplicationAdapter = useNewDuplicationAdapter;
|
|
||||||
|
|
||||||
Restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool CaptureScreen()
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
lock (_captureLock)
|
|
||||||
{
|
{
|
||||||
if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null))
|
Restart();
|
||||||
{
|
|
||||||
Restart();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IDXGIResource? screenResource = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_duplicatedOutput.AcquireNextFrame(Timeout, out OutduplFrameInfo duplicateFrameInformation, out screenResource);
|
|
||||||
if ((screenResource == null) || (duplicateFrameInformation.LastPresentTime == 0)) return false;
|
|
||||||
|
|
||||||
using ID3D11Texture2D screenTexture = screenResource.QueryInterface<ID3D11Texture2D>();
|
|
||||||
_context.CopySubresourceRegion(_captureTexture, 0, 0, 0, 0, screenTexture, 0);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
screenResource?.Dispose();
|
|
||||||
_duplicatedOutput?.ReleaseFrame();
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
}
|
|
||||||
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
catch (SharpGenException dxException)
|
|
||||||
{
|
|
||||||
if ((dxException.ResultCode == ResultCode.AccessLost)
|
|
||||||
|| (dxException.ResultCode == ResultCode.AccessDenied)
|
|
||||||
|| (dxException.ResultCode == ResultCode.InvalidCall))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Restart();
|
|
||||||
}
|
|
||||||
catch { Thread.Sleep(100); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
UpdateZones();
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
|
|
||||||
}
|
|
||||||
catch { /**/ }
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateZones()
|
|
||||||
{
|
|
||||||
if (_context == null) return;
|
|
||||||
|
|
||||||
lock (_captureZones)
|
|
||||||
{
|
|
||||||
foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested))
|
|
||||||
{
|
|
||||||
if (scalingTexture != null)
|
|
||||||
{
|
|
||||||
_context.CopySubresourceRegion(scalingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
|
||||||
new Box(captureZone.X, captureZone.Y, 0,
|
|
||||||
captureZone.X + captureZone.UnscaledWidth,
|
|
||||||
captureZone.Y + captureZone.UnscaledHeight, 1));
|
|
||||||
_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);
|
|
||||||
IntPtr sourcePtr = mapSource.DataPointer;
|
|
||||||
lock (captureZone.Buffer)
|
|
||||||
{
|
|
||||||
for (int y = 0; y < captureZone.Height; y++)
|
|
||||||
{
|
|
||||||
Marshal.Copy(sourcePtr, captureZone.Buffer, y * captureZone.Stride, captureZone.Stride);
|
|
||||||
sourcePtr += mapSource.RowPitch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Unmap(stagingTexture, 0);
|
|
||||||
captureZone.SetUpdated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
|
||||||
{
|
|
||||||
ValidateCaptureZoneAndThrow(x, y, width, height);
|
|
||||||
|
|
||||||
int unscaledWidth = width;
|
|
||||||
int unscaledHeight = height;
|
|
||||||
(width, height) = 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);
|
|
||||||
|
|
||||||
return captureZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool UnregisterCaptureZone(CaptureZone captureZone)
|
|
||||||
{
|
|
||||||
lock (_captureZones)
|
|
||||||
{
|
|
||||||
if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data))
|
|
||||||
{
|
|
||||||
_captureZones.Remove(captureZone);
|
|
||||||
data.stagingTexture.Dispose();
|
|
||||||
data.scalingTexture?.Dispose();
|
|
||||||
data._scalingTextureView?.Dispose();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
|
|
||||||
{
|
|
||||||
lock (_captureZones)
|
|
||||||
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);
|
|
||||||
|
|
||||||
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.
|
|
||||||
if ((width != null) || (height != null) || (downscaleLevel != null))
|
|
||||||
{
|
|
||||||
(int newWidth, int newHeight) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
|
|
||||||
lock (_captureZones)
|
|
||||||
{
|
|
||||||
UnregisterCaptureZone(captureZone);
|
|
||||||
|
|
||||||
captureZone.UnscaledWidth = newUnscaledWidth;
|
|
||||||
captureZone.UnscaledHeight = newUnscaledHeight;
|
|
||||||
captureZone.Width = newWidth;
|
|
||||||
captureZone.Height = newHeight;
|
|
||||||
captureZone.DownscaleLevel = newDownscaleLevel;
|
|
||||||
captureZone.Buffer = new byte[newWidth * newHeight * BPP];
|
|
||||||
|
|
||||||
InitializeCaptureZone(captureZone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private (int width, int height) CalculateScaledSize(int width, int height, int downscaleLevel)
|
|
||||||
{
|
|
||||||
if (downscaleLevel > 0)
|
|
||||||
for (int i = 0; i < downscaleLevel; i++)
|
|
||||||
{
|
|
||||||
width /= 2;
|
|
||||||
height /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < 1) width = 1;
|
|
||||||
if (height < 1) height = 1;
|
|
||||||
|
|
||||||
return (width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateCaptureZoneAndThrow(int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized.");
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeCaptureZone(in CaptureZone captureZone)
|
|
||||||
{
|
|
||||||
Texture2DDescription stagingTextureDesc = new()
|
|
||||||
{
|
|
||||||
CpuAccessFlags = CpuAccessFlags.Read,
|
|
||||||
BindFlags = BindFlags.None,
|
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
|
||||||
Width = captureZone.Width,
|
|
||||||
Height = captureZone.Height,
|
|
||||||
OptionFlags = ResourceOptionFlags.None,
|
|
||||||
MipLevels = 1,
|
|
||||||
ArraySize = 1,
|
|
||||||
SampleDescription = { Count = 1, Quality = 0 },
|
|
||||||
Usage = ResourceUsage.Staging
|
|
||||||
};
|
|
||||||
ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc);
|
|
||||||
|
|
||||||
ID3D11Texture2D? scalingTexture = null;
|
|
||||||
ID3D11ShaderResourceView? scalingTextureView = null;
|
|
||||||
if (captureZone.DownscaleLevel > 0)
|
|
||||||
{
|
|
||||||
Texture2DDescription scalingTextureDesc = new()
|
|
||||||
{
|
|
||||||
CpuAccessFlags = CpuAccessFlags.None,
|
|
||||||
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
|
||||||
Width = captureZone.UnscaledWidth,
|
|
||||||
Height = captureZone.UnscaledHeight,
|
|
||||||
OptionFlags = ResourceOptionFlags.GenerateMips,
|
|
||||||
MipLevels = captureZone.DownscaleLevel + 1,
|
|
||||||
ArraySize = 1,
|
|
||||||
SampleDescription = { Count = 1, Quality = 0 },
|
|
||||||
Usage = ResourceUsage.Default
|
|
||||||
};
|
|
||||||
scalingTexture = _device!.CreateTexture2D(scalingTextureDesc);
|
|
||||||
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
_captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Restart()
|
|
||||||
{
|
|
||||||
lock (_captureLock)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
|
||||||
Dispose();
|
|
||||||
|
|
||||||
using IDXGIAdapter1 adapter = _factory.GetAdapter1(Display.GraphicsCard.Index);
|
|
||||||
|
|
||||||
D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, FEATURE_LEVELS, out _device).CheckError();
|
|
||||||
_context = _device.ImmediateContext;
|
|
||||||
|
|
||||||
_output = adapter.GetOutput(Display.Index);
|
|
||||||
using IDXGIOutput5 output = _output.QueryInterface<IDXGIOutput5>();
|
|
||||||
|
|
||||||
Texture2DDescription captureTextureDesc = new()
|
|
||||||
{
|
|
||||||
CpuAccessFlags = CpuAccessFlags.None,
|
|
||||||
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
|
||||||
Format = Format.B8G8R8A8_UNorm,
|
|
||||||
Width = Display.Width,
|
|
||||||
Height = Display.Height,
|
|
||||||
OptionFlags = ResourceOptionFlags.None,
|
|
||||||
MipLevels = 1,
|
|
||||||
ArraySize = 1,
|
|
||||||
SampleDescription = { Count = 1, Quality = 0 },
|
|
||||||
Usage = ResourceUsage.Default
|
|
||||||
};
|
|
||||||
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
|
|
||||||
|
|
||||||
lock (_captureZones)
|
|
||||||
{
|
|
||||||
foreach (CaptureZone captureZone in captureZones)
|
|
||||||
InitializeCaptureZone(captureZone);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_useNewDuplicationAdapter)
|
|
||||||
_duplicatedOutput = output.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
|
|
||||||
else
|
|
||||||
_duplicatedOutput = output.DuplicateOutput(_device);
|
|
||||||
}
|
|
||||||
catch { Dispose(false); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose() => Dispose(true);
|
|
||||||
|
|
||||||
private void Dispose(bool removeCaptureZones)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_captureLock)
|
IDXGIResource? screenResource = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try { _duplicatedOutput?.Dispose(); } catch { /**/ }
|
_duplicatedOutput.AcquireNextFrame(Timeout, out OutduplFrameInfo duplicateFrameInformation, out screenResource);
|
||||||
_duplicatedOutput = null;
|
if ((screenResource == null) || (duplicateFrameInformation.LastPresentTime == 0)) return false;
|
||||||
|
|
||||||
|
using ID3D11Texture2D screenTexture = screenResource.QueryInterface<ID3D11Texture2D>();
|
||||||
|
_context.CopySubresourceRegion(_captureTexture, 0, 0, 0, 0, screenTexture, 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (removeCaptureZones)
|
screenResource?.Dispose();
|
||||||
{
|
_duplicatedOutput?.ReleaseFrame();
|
||||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
|
||||||
foreach (CaptureZone captureZone in captureZones)
|
|
||||||
UnregisterCaptureZone(captureZone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { /**/ }
|
catch { /**/ }
|
||||||
|
}
|
||||||
|
|
||||||
try { _output?.Dispose(); } catch { /**/ }
|
result = true;
|
||||||
try { _context?.Dispose(); } catch { /**/ }
|
}
|
||||||
try { _device?.Dispose(); } catch { /**/ }
|
catch (SharpGenException dxException)
|
||||||
try { _captureTexture?.Dispose(); } catch { /**/ }
|
{
|
||||||
_context = null;
|
if ((dxException.ResultCode == ResultCode.AccessLost)
|
||||||
_captureTexture = null;
|
|| (dxException.ResultCode == ResultCode.AccessDenied)
|
||||||
|
|| (dxException.ResultCode == ResultCode.InvalidCall))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Restart();
|
||||||
|
}
|
||||||
|
catch { Thread.Sleep(100); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { /**/ }
|
catch { /**/ }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdateZones();
|
||||||
|
}
|
||||||
|
catch { /**/ }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
|
||||||
|
}
|
||||||
|
catch { /**/ }
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateZones()
|
||||||
|
{
|
||||||
|
if (_context == null) return;
|
||||||
|
|
||||||
|
lock (_captureZones)
|
||||||
|
{
|
||||||
|
foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested))
|
||||||
|
{
|
||||||
|
if (scalingTexture != null)
|
||||||
|
{
|
||||||
|
_context.CopySubresourceRegion(scalingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
||||||
|
new Box(captureZone.X, captureZone.Y, 0,
|
||||||
|
captureZone.X + captureZone.UnscaledWidth,
|
||||||
|
captureZone.Y + captureZone.UnscaledHeight, 1));
|
||||||
|
_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);
|
||||||
|
IntPtr sourcePtr = mapSource.DataPointer;
|
||||||
|
lock (captureZone.Buffer)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < captureZone.Height; y++)
|
||||||
|
{
|
||||||
|
Marshal.Copy(sourcePtr, captureZone.Buffer, y * captureZone.Stride, captureZone.Stride);
|
||||||
|
sourcePtr += mapSource.RowPitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Unmap(stagingTexture, 0);
|
||||||
|
captureZone.SetUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
||||||
|
{
|
||||||
|
ValidateCaptureZoneAndThrow(x, y, width, height);
|
||||||
|
|
||||||
|
int unscaledWidth = width;
|
||||||
|
int unscaledHeight = height;
|
||||||
|
(width, height) = 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);
|
||||||
|
|
||||||
|
return captureZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool UnregisterCaptureZone(CaptureZone captureZone)
|
||||||
|
{
|
||||||
|
lock (_captureZones)
|
||||||
|
{
|
||||||
|
if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data))
|
||||||
|
{
|
||||||
|
_captureZones.Remove(captureZone);
|
||||||
|
data.stagingTexture.Dispose();
|
||||||
|
data.scalingTexture?.Dispose();
|
||||||
|
data._scalingTextureView?.Dispose();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
|
||||||
|
{
|
||||||
|
lock (_captureZones)
|
||||||
|
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);
|
||||||
|
|
||||||
|
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.
|
||||||
|
if ((width != null) || (height != null) || (downscaleLevel != null))
|
||||||
|
{
|
||||||
|
(int newWidth, int newHeight) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
|
||||||
|
lock (_captureZones)
|
||||||
|
{
|
||||||
|
UnregisterCaptureZone(captureZone);
|
||||||
|
|
||||||
|
captureZone.UnscaledWidth = newUnscaledWidth;
|
||||||
|
captureZone.UnscaledHeight = newUnscaledHeight;
|
||||||
|
captureZone.Width = newWidth;
|
||||||
|
captureZone.Height = newHeight;
|
||||||
|
captureZone.DownscaleLevel = newDownscaleLevel;
|
||||||
|
captureZone.Buffer = new byte[newWidth * newHeight * BPP];
|
||||||
|
|
||||||
|
InitializeCaptureZone(captureZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int width, int height) CalculateScaledSize(int width, int height, int downscaleLevel)
|
||||||
|
{
|
||||||
|
if (downscaleLevel > 0)
|
||||||
|
for (int i = 0; i < downscaleLevel; i++)
|
||||||
|
{
|
||||||
|
width /= 2;
|
||||||
|
height /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width < 1) width = 1;
|
||||||
|
if (height < 1) height = 1;
|
||||||
|
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateCaptureZoneAndThrow(int x, int y, int width, int height)
|
||||||
|
{
|
||||||
|
if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized.");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeCaptureZone(in CaptureZone captureZone)
|
||||||
|
{
|
||||||
|
Texture2DDescription stagingTextureDesc = new()
|
||||||
|
{
|
||||||
|
CpuAccessFlags = CpuAccessFlags.Read,
|
||||||
|
BindFlags = BindFlags.None,
|
||||||
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
|
Width = captureZone.Width,
|
||||||
|
Height = captureZone.Height,
|
||||||
|
OptionFlags = ResourceOptionFlags.None,
|
||||||
|
MipLevels = 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
SampleDescription = { Count = 1, Quality = 0 },
|
||||||
|
Usage = ResourceUsage.Staging
|
||||||
|
};
|
||||||
|
ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc);
|
||||||
|
|
||||||
|
ID3D11Texture2D? scalingTexture = null;
|
||||||
|
ID3D11ShaderResourceView? scalingTextureView = null;
|
||||||
|
if (captureZone.DownscaleLevel > 0)
|
||||||
|
{
|
||||||
|
Texture2DDescription scalingTextureDesc = new()
|
||||||
|
{
|
||||||
|
CpuAccessFlags = CpuAccessFlags.None,
|
||||||
|
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
||||||
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
|
Width = captureZone.UnscaledWidth,
|
||||||
|
Height = captureZone.UnscaledHeight,
|
||||||
|
OptionFlags = ResourceOptionFlags.GenerateMips,
|
||||||
|
MipLevels = captureZone.DownscaleLevel + 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
SampleDescription = { Count = 1, Quality = 0 },
|
||||||
|
Usage = ResourceUsage.Default
|
||||||
|
};
|
||||||
|
scalingTexture = _device!.CreateTexture2D(scalingTextureDesc);
|
||||||
|
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
_captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Restart()
|
||||||
|
{
|
||||||
|
lock (_captureLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
||||||
|
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>();
|
||||||
|
|
||||||
|
Texture2DDescription captureTextureDesc = new()
|
||||||
|
{
|
||||||
|
CpuAccessFlags = CpuAccessFlags.None,
|
||||||
|
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
||||||
|
Format = Format.B8G8R8A8_UNorm,
|
||||||
|
Width = Display.Width,
|
||||||
|
Height = Display.Height,
|
||||||
|
OptionFlags = ResourceOptionFlags.None,
|
||||||
|
MipLevels = 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
SampleDescription = { Count = 1, Quality = 0 },
|
||||||
|
Usage = ResourceUsage.Default
|
||||||
|
};
|
||||||
|
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
|
||||||
|
|
||||||
|
lock (_captureZones)
|
||||||
|
{
|
||||||
|
foreach (CaptureZone captureZone in captureZones)
|
||||||
|
InitializeCaptureZone(captureZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useNewDuplicationAdapter)
|
||||||
|
_duplicatedOutput = output.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
|
||||||
|
else
|
||||||
|
_duplicatedOutput = output.DuplicateOutput(_device);
|
||||||
|
}
|
||||||
|
catch { Dispose(false); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() => Dispose(true);
|
||||||
|
|
||||||
|
private void Dispose(bool removeCaptureZones)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_captureLock)
|
||||||
|
{
|
||||||
|
try { _duplicatedOutput?.Dispose(); } catch { /**/ }
|
||||||
|
_duplicatedOutput = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (removeCaptureZones)
|
||||||
|
{
|
||||||
|
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 { /**/ }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -2,83 +2,82 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Vortice.DXGI;
|
using Vortice.DXGI;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="IScreenCaptureService"/> using the <see cref="DX11ScreenCapture"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class DX11ScreenCaptureService : IScreenCaptureService
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
private readonly IDXGIFactory1 _factory;
|
||||||
|
|
||||||
|
private readonly Dictionary<Display, DX11ScreenCapture> _screenCaptures = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Initializes a new instance of the <see cref="DX11ScreenCaptureService"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DX11ScreenCaptureService : IScreenCaptureService
|
public DX11ScreenCaptureService()
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
DXGI.CreateDXGIFactory1(out _factory!).CheckError();
|
||||||
|
|
||||||
private readonly IDXGIFactory1 _factory;
|
|
||||||
|
|
||||||
private readonly Dictionary<Display, DX11ScreenCapture> _screenCaptures = new();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public DX11ScreenCaptureService()
|
|
||||||
{
|
|
||||||
DXGI.CreateDXGIFactory1(out _factory).CheckError();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<GraphicsCard> GetGraphicsCards()
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
while (_factory.EnumAdapters1(i, out IDXGIAdapter1 adapter).Success)
|
|
||||||
{
|
|
||||||
yield return new GraphicsCard(i, adapter.Description1.Description, adapter.Description1.VendorId, adapter.Description1.DeviceId);
|
|
||||||
adapter.Dispose();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard)
|
|
||||||
{
|
|
||||||
using IDXGIAdapter1? adapter = _factory.GetAdapter1(graphicsCard.Index);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (adapter.EnumOutputs(i, out IDXGIOutput output).Success)
|
|
||||||
{
|
|
||||||
int width = output.Description.DesktopCoordinates.Right - output.Description.DesktopCoordinates.Left;
|
|
||||||
int height = output.Description.DesktopCoordinates.Bottom - output.Description.DesktopCoordinates.Top;
|
|
||||||
yield return new Display(i, output.Description.DeviceName, width, height, graphicsCard);
|
|
||||||
output.Dispose();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IScreenCapture GetScreenCapture(Display display)
|
|
||||||
{
|
|
||||||
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
|
|
||||||
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
|
|
||||||
return screenCapture;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values)
|
|
||||||
screenCapture.Dispose();
|
|
||||||
_screenCaptures.Clear();
|
|
||||||
|
|
||||||
_factory.Dispose();
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<GraphicsCard> GetGraphicsCards()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (_factory.EnumAdapters1(i, out IDXGIAdapter1 adapter).Success)
|
||||||
|
{
|
||||||
|
yield return new GraphicsCard(i, adapter.Description1.Description, adapter.Description1.VendorId, adapter.Description1.DeviceId);
|
||||||
|
adapter.Dispose();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard)
|
||||||
|
{
|
||||||
|
using IDXGIAdapter1? adapter = _factory.GetAdapter1(graphicsCard.Index);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (adapter.EnumOutputs(i, out IDXGIOutput output).Success)
|
||||||
|
{
|
||||||
|
int width = output.Description.DesktopCoordinates.Right - output.Description.DesktopCoordinates.Left;
|
||||||
|
int height = output.Description.DesktopCoordinates.Bottom - output.Description.DesktopCoordinates.Top;
|
||||||
|
yield return new Display(i, output.Description.DeviceName, width, height, graphicsCard);
|
||||||
|
output.Dispose();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IScreenCapture GetScreenCapture(Display display)
|
||||||
|
{
|
||||||
|
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
|
||||||
|
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
|
||||||
|
return screenCapture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values)
|
||||||
|
screenCapture.Dispose();
|
||||||
|
_screenCaptures.Clear();
|
||||||
|
|
||||||
|
_factory.Dispose();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -2,34 +2,33 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the information supplied with an <see cref="E:ScreenCapture.IDX11ScreenCapture.Updated" />-event.
|
||||||
|
/// </summary>
|
||||||
|
public class ScreenCaptureUpdatedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
#region Properties & Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the information supplied with an <see cref="E:ScreenCapture.IDX11ScreenCapture.Updated" />-event.
|
/// <c>true</c> if the update was successful; otherwise, <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ScreenCaptureUpdatedEventArgs : EventArgs
|
public bool IsSuccessful { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ScreenCaptureUpdatedEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isSuccessful">Indicates if the last update was successful.</param>
|
||||||
|
public ScreenCaptureUpdatedEventArgs(bool isSuccessful)
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
this.IsSuccessful = isSuccessful;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <c>true</c> if the update was successful; otherwise, <c>false</c>.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSuccessful { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ScreenCaptureUpdatedEventArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isSuccessful">Indicates if the last update was successful.</param>
|
|
||||||
public ScreenCaptureUpdatedEventArgs(bool isSuccessful)
|
|
||||||
{
|
|
||||||
this.IsSuccessful = isSuccessful;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -3,40 +3,39 @@
|
|||||||
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper-class for DPI-related WIN-API calls.
|
||||||
|
/// </summary>
|
||||||
|
public static class DPIAwareness
|
||||||
{
|
{
|
||||||
/// <summary>
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
/// Helper-class for DPI-related WIN-API calls.
|
internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag);
|
||||||
/// </summary>
|
|
||||||
public static class DPIAwareness
|
[DllImport("SHCore.dll", SetLastError = true)]
|
||||||
|
internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
internal static extern bool SetProcessDPIAware();
|
||||||
|
|
||||||
|
internal enum PROCESS_DPI_AWARENESS
|
||||||
{
|
{
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
Process_DPI_Unaware = 0,
|
||||||
internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag);
|
Process_System_DPI_Aware = 1,
|
||||||
|
Process_Per_Monitor_DPI_Aware = 2
|
||||||
[DllImport("SHCore.dll", SetLastError = true)]
|
|
||||||
internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
internal static extern bool SetProcessDPIAware();
|
|
||||||
|
|
||||||
internal enum PROCESS_DPI_AWARENESS
|
|
||||||
{
|
|
||||||
Process_DPI_Unaware = 0,
|
|
||||||
Process_System_DPI_Aware = 1,
|
|
||||||
Process_Per_Monitor_DPI_Aware = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum DPI_AWARENESS_CONTEXT
|
|
||||||
{
|
|
||||||
DPI_AWARENESS_CONTEXT_UNAWARE = 16,
|
|
||||||
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
|
|
||||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
|
|
||||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the DPI-Awareness-Context to V2. This is needed to prevent issues when using desktop duplication.
|
|
||||||
/// </summary>
|
|
||||||
public static void Initalize() => SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
internal enum DPI_AWARENESS_CONTEXT
|
||||||
|
{
|
||||||
|
DPI_AWARENESS_CONTEXT_UNAWARE = 16,
|
||||||
|
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
|
||||||
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
|
||||||
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the DPI-Awareness-Context to V2. This is needed to prevent issues when using desktop duplication.
|
||||||
|
/// </summary>
|
||||||
|
public static void Initalize() => SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
}
|
||||||
@ -1,63 +1,62 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the duplication of a single display.
|
||||||
|
/// </summary>
|
||||||
|
public interface IScreenCapture : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the duplication of a single display.
|
/// Gets the <see cref="Display"/> this capture is duplicating.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IScreenCapture : IDisposable
|
Display Display { get; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="Display"/> this capture is duplicating.
|
|
||||||
/// </summary>
|
|
||||||
Display Display { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the <see cref="IScreenCapture"/> is updated.
|
/// Occurs when the <see cref="IScreenCapture"/> is updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
event EventHandler<ScreenCaptureUpdatedEventArgs>? Updated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attemts to capture the current frame showed on the <see cref="Display"/>.
|
/// Attemts to capture the current frame showed on the <see cref="Display"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><c>true</c> if the current frame was captures successfully; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if the current frame was captures successfully; otherwise, <c>false</c>.</returns>
|
||||||
bool CaptureScreen();
|
bool CaptureScreen();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="CaptureScreen"/> for this <see cref="IScreenCapture"/>.
|
/// Creates a new <see cref="CaptureScreen"/> for this <see cref="IScreenCapture"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="x">The x-location of the region to capture (must be >= 0 and < screen-width).</param>
|
/// <param name="x">The x-location of the region to capture (must be >= 0 and < screen-width).</param>
|
||||||
/// <param name="y">The y-location of the region to capture (must be >= 0 and < screen-height).</param>
|
/// <param name="y">The y-location of the region to capture (must be >= 0 and < screen-height).</param>
|
||||||
/// <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 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);
|
CaptureZone 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(CaptureZone captureZone);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the the given <see cref="CaptureScreen"/>.
|
/// Updates the the given <see cref="CaptureScreen"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <c>null</c>-parameters are ignored and not changed.
|
/// <c>null</c>-parameters are ignored and not changed.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="captureZone">The previously registered <see cref="CaptureScreen"/>.</param>
|
/// <param name="captureZone">The previously registered <see cref="CaptureScreen"/>.</param>
|
||||||
/// <param name="x">The new x-location of the region to capture (must be >= 0 and < screen-width).</param>
|
/// <param name="x">The new x-location of the region to capture (must be >= 0 and < screen-width).</param>
|
||||||
/// <param name="y">The new y-location of the region to capture (must be >= 0 and < screen-height).</param>
|
/// <param name="y">The new y-location of the region to capture (must be >= 0 and < screen-height).</param>
|
||||||
/// <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(CaptureZone 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"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Restart();
|
void Restart();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@ -1,31 +1,30 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public interface IScreenCaptureService : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets a enumerable of all available graphics-cards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IScreenCaptureService : IDisposable
|
/// <returns>A enumerable of all available graphics-cards.</returns>
|
||||||
{
|
IEnumerable<GraphicsCard> GetGraphicsCards();
|
||||||
/// <summary>
|
|
||||||
/// Gets a enumerable of all available graphics-cards.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A enumerable of all available graphics-cards.</returns>
|
|
||||||
IEnumerable<GraphicsCard> GetGraphicsCards();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a enumerable of all display connected to the given graphics-cards.
|
/// Gets a enumerable of all display connected to the given graphics-cards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="graphicsCard">The graphics-card to get the displays from.</param>
|
/// <param name="graphicsCard">The graphics-card to get the displays from.</param>
|
||||||
/// <returns>A enumerable of all display connected to the given graphics-cards.</returns>
|
/// <returns>A enumerable of all display connected to the given graphics-cards.</returns>
|
||||||
IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard);
|
IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="IScreenCapture"/> for the given display.
|
/// Creates a <see cref="IScreenCapture"/> for the given display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="display">The display to duplicate.</param>
|
/// <param name="display">The display to duplicate.</param>
|
||||||
/// <returns>The <see cref="IScreenCapture"/> for the give display.</returns>
|
/// <returns>The <see cref="IScreenCapture"/> for the give display.</returns>
|
||||||
IScreenCapture GetScreenCapture(Display display);
|
IScreenCapture GetScreenCapture(Display display);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@ -2,141 +2,140 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the configuration for the detection and removal of black bars around the screen image.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BlackBarDetection
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
private readonly CaptureZone _captureZone;
|
||||||
|
|
||||||
|
private int? _top;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the configuration for the detection and removal of black bars around the screen image.
|
/// Gets the size of the detected black bar at the top of the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class BlackBarDetection
|
public int Top => _top ??= CalculateTop();
|
||||||
|
|
||||||
|
private int? _bottom;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the detected black bar at the bottom of the image.
|
||||||
|
/// </summary>
|
||||||
|
public int Bottom => _bottom ??= CalculateBottom();
|
||||||
|
|
||||||
|
private int? _left;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the detected black bar at the left of the image.
|
||||||
|
/// </summary>
|
||||||
|
public int Left => _left ??= CalculateLeft();
|
||||||
|
|
||||||
|
private int? _right;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the detected black bar at the right of the image.
|
||||||
|
/// </summary>
|
||||||
|
public int Right => _right ??= CalculateRight();
|
||||||
|
|
||||||
|
private int _theshold = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) (default 0)
|
||||||
|
/// </summary>
|
||||||
|
public int Threshold
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
get => _theshold;
|
||||||
|
set
|
||||||
private readonly CaptureZone _captureZone;
|
|
||||||
|
|
||||||
private int? _top;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the detected black bar at the top of the image.
|
|
||||||
/// </summary>
|
|
||||||
public int Top => _top ??= CalculateTop();
|
|
||||||
|
|
||||||
private int? _bottom;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the detected black bar at the bottom of the image.
|
|
||||||
/// </summary>
|
|
||||||
public int Bottom => _bottom ??= CalculateBottom();
|
|
||||||
|
|
||||||
private int? _left;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the detected black bar at the left of the image.
|
|
||||||
/// </summary>
|
|
||||||
public int Left => _left ??= CalculateLeft();
|
|
||||||
|
|
||||||
private int? _right;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the detected black bar at the right of the image.
|
|
||||||
/// </summary>
|
|
||||||
public int Right => _right ??= CalculateRight();
|
|
||||||
|
|
||||||
private int _theshold = 0;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) (default 0)
|
|
||||||
/// </summary>
|
|
||||||
public int Threshold
|
|
||||||
{
|
{
|
||||||
get => _theshold;
|
_theshold = value;
|
||||||
set
|
InvalidateCache();
|
||||||
{
|
}
|
||||||
_theshold = value;
|
}
|
||||||
InvalidateCache();
|
|
||||||
}
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
internal BlackBarDetection(CaptureZone captureZone)
|
||||||
|
{
|
||||||
|
this._captureZone = captureZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalidates the cached values and recalculates <see cref="Top"/>, <see cref="Bottom"/>, <see cref="Left"/> and <see cref="Right"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void InvalidateCache()
|
||||||
|
{
|
||||||
|
_top = null;
|
||||||
|
_bottom = null;
|
||||||
|
_left = null;
|
||||||
|
_right = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CalculateTop()
|
||||||
|
{
|
||||||
|
int threshold = Threshold;
|
||||||
|
int stride = _captureZone.Stride;
|
||||||
|
for (int row = 0; row < _captureZone.Height; row++)
|
||||||
|
{
|
||||||
|
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
||||||
|
for (int i = 0; i < data.Length; i += 4)
|
||||||
|
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||||
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#region Constructors
|
private int CalculateBottom()
|
||||||
|
{
|
||||||
internal BlackBarDetection(CaptureZone captureZone)
|
int threshold = Threshold;
|
||||||
|
int stride = _captureZone.Stride;
|
||||||
|
for (int row = _captureZone.Height - 1; row >= 0; row--)
|
||||||
{
|
{
|
||||||
this._captureZone = captureZone;
|
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
||||||
|
for (int i = 0; i < data.Length; i += 4)
|
||||||
|
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||||
|
return (_captureZone.Height - 1) - row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#region Methods
|
private int CalculateLeft()
|
||||||
|
{
|
||||||
/// <summary>
|
int threshold = Threshold;
|
||||||
/// Invalidates the cached values and recalculates <see cref="Top"/>, <see cref="Bottom"/>, <see cref="Left"/> and <see cref="Right"/>.
|
int stride = _captureZone.Stride;
|
||||||
/// </summary>
|
byte[] buffer = _captureZone.Buffer;
|
||||||
public void InvalidateCache()
|
for (int column = 0; column < _captureZone.Width; column++)
|
||||||
{
|
|
||||||
_top = null;
|
|
||||||
_bottom = null;
|
|
||||||
_left = null;
|
|
||||||
_right = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateTop()
|
|
||||||
{
|
|
||||||
int threshold = Threshold;
|
|
||||||
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);
|
int offset = (stride * row) + (column * 4);
|
||||||
for (int i = 0; i < data.Length; i += 4)
|
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
||||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
return column;
|
||||||
return row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateBottom()
|
|
||||||
{
|
|
||||||
int threshold = Threshold;
|
|
||||||
int stride = _captureZone.Stride;
|
|
||||||
for (int row = _captureZone.Height - 1; row >= 0; row--)
|
|
||||||
{
|
|
||||||
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
|
|
||||||
for (int i = 0; i < data.Length; i += 4)
|
|
||||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
|
||||||
return (_captureZone.Height - 1) - row;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateLeft()
|
|
||||||
{
|
|
||||||
int threshold = Threshold;
|
|
||||||
int stride = _captureZone.Stride;
|
|
||||||
byte[] buffer = _captureZone.Buffer;
|
|
||||||
for (int column = 0; column < _captureZone.Width; column++)
|
|
||||||
for (int row = 0; row < _captureZone.Height; row++)
|
|
||||||
{
|
|
||||||
int offset = (stride * row) + (column * 4);
|
|
||||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
|
||||||
return column;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateRight()
|
|
||||||
{
|
|
||||||
int threshold = Threshold;
|
|
||||||
int stride = _captureZone.Stride;
|
|
||||||
byte[] buffer = _captureZone.Buffer;
|
|
||||||
for (int column = _captureZone.Width - 1; column >= 0; column--)
|
|
||||||
for (int row = 0; row < _captureZone.Height; row++)
|
|
||||||
{
|
|
||||||
int offset = (stride * row) + (column * 4);
|
|
||||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
|
||||||
return (_captureZone.Width - 1) - column;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private int CalculateRight()
|
||||||
|
{
|
||||||
|
int threshold = Threshold;
|
||||||
|
int stride = _captureZone.Stride;
|
||||||
|
byte[] buffer = _captureZone.Buffer;
|
||||||
|
for (int column = _captureZone.Width - 1; column >= 0; column--)
|
||||||
|
for (int row = 0; row < _captureZone.Height; row++)
|
||||||
|
{
|
||||||
|
int offset = (stride * row) + (column * 4);
|
||||||
|
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
|
||||||
|
return (_captureZone.Width - 1) - column;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -2,161 +2,160 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a duplicated region on the screen.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CaptureZone
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a duplicated region on the screen.
|
/// Gets the unique id of this <see cref="CaptureZone"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CaptureZone
|
public int Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the x-location of the region on the screen.
|
||||||
|
/// </summary>
|
||||||
|
public int X { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the y-location of the region on the screen.
|
||||||
|
/// </summary>
|
||||||
|
public int Y { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the width of the captured region.
|
||||||
|
/// </summary>
|
||||||
|
public int Width { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the height of the captured region.
|
||||||
|
/// </summary>
|
||||||
|
public int Height { get; internal set; }
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]).
|
||||||
|
/// </summary>
|
||||||
|
public int BytesPerPixel { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size in bytes of a row in the region (<see cref="Width"/> * <see cref="BytesPerPixel"/>).
|
||||||
|
/// </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>
|
||||||
|
public bool AutoUpdate { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if an update for the <see cref="CaptureZone"/> is requested on the next captured frame.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsUpdateRequested { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the <see cref="CaptureZone"/> is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? Updated;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CaptureZone"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique id of this <see cref="CaptureZone"/>.</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="width">The width of the region on the screen.</param>
|
||||||
|
/// <param name="height">The height of the region on the screen.</param>
|
||||||
|
/// <param name="downscaleLevel">The level of downscaling applied to the image of this region before copying to local memory.</param>
|
||||||
|
/// <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, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, byte[] buffer)
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
this.Id = id;
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
this.Width = width;
|
||||||
|
this.Height = height;
|
||||||
|
this.BytesPerPixel = bytesPerPixel;
|
||||||
|
this.UnscaledWidth = unscaledWidth;
|
||||||
|
this.UnscaledHeight = unscaledHeight;
|
||||||
|
this.DownscaleLevel = downscaleLevel;
|
||||||
|
this.Buffer = buffer;
|
||||||
|
|
||||||
/// <summary>
|
BlackBars = new BlackBarDetection(this);
|
||||||
/// Gets the unique id of this <see cref="CaptureZone"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the x-location of the region on the screen.
|
|
||||||
/// </summary>
|
|
||||||
public int X { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the y-location of the region on the screen.
|
|
||||||
/// </summary>
|
|
||||||
public int Y { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the width of the captured region.
|
|
||||||
/// </summary>
|
|
||||||
public int Width { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the height of the captured region.
|
|
||||||
/// </summary>
|
|
||||||
public int Height { get; internal set; }
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]).
|
|
||||||
/// </summary>
|
|
||||||
public int BytesPerPixel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size in bytes of a row in the region (<see cref="Width"/> * <see cref="BytesPerPixel"/>).
|
|
||||||
/// </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>
|
|
||||||
public bool AutoUpdate { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets if an update for the <see cref="CaptureZone"/> is requested on the next captured frame.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsUpdateRequested { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the <see cref="CaptureZone"/> is updated.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? Updated;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CaptureZone"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The unique id of this <see cref="CaptureZone"/>.</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="width">The width of the region on the screen.</param>
|
|
||||||
/// <param name="height">The height of the region on the screen.</param>
|
|
||||||
/// <param name="downscaleLevel">The level of downscaling applied to the image of this region before copying to local memory.</param>
|
|
||||||
/// <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, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, byte[] buffer)
|
|
||||||
{
|
|
||||||
this.Id = id;
|
|
||||||
this.X = x;
|
|
||||||
this.Y = y;
|
|
||||||
this.Width = width;
|
|
||||||
this.Height = height;
|
|
||||||
this.BytesPerPixel = bytesPerPixel;
|
|
||||||
this.UnscaledWidth = unscaledWidth;
|
|
||||||
this.UnscaledHeight = unscaledHeight;
|
|
||||||
this.DownscaleLevel = downscaleLevel;
|
|
||||||
this.Buffer = buffer;
|
|
||||||
|
|
||||||
BlackBars = new BlackBarDetection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Requests to update this <see cref="CaptureZone"/> when the next frame is captured.
|
|
||||||
/// Only necessary if <see cref="AutoUpdate"/> is set to <c>false</c>.
|
|
||||||
/// </summary>
|
|
||||||
public void RequestUpdate() => IsUpdateRequested = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks the <see cref="CaptureZone"/> as updated.
|
|
||||||
/// WARNING: This should not be called outside of an <see cref="IScreenCapture"/>!
|
|
||||||
/// </summary>
|
|
||||||
public void SetUpdated()
|
|
||||||
{
|
|
||||||
IsUpdateRequested = false;
|
|
||||||
BlackBars.InvalidateCache();
|
|
||||||
|
|
||||||
Updated?.Invoke(this, new EventArgs());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this <see cref="CaptureZone"/> equals the given one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The <see cref="CaptureZone"/> to compare.</param>
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode() => Id;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests to update this <see cref="CaptureZone"/> when the next frame is captured.
|
||||||
|
/// Only necessary if <see cref="AutoUpdate"/> is set to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
public void RequestUpdate() => IsUpdateRequested = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks the <see cref="CaptureZone"/> as updated.
|
||||||
|
/// WARNING: This should not be called outside of an <see cref="IScreenCapture"/>!
|
||||||
|
/// </summary>
|
||||||
|
public void SetUpdated()
|
||||||
|
{
|
||||||
|
IsUpdateRequested = false;
|
||||||
|
BlackBars.InvalidateCache();
|
||||||
|
|
||||||
|
Updated?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this <see cref="CaptureZone"/> equals the given one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The <see cref="CaptureZone"/> to compare.</param>
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode() => Id;
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -1,93 +1,92 @@
|
|||||||
// ReSharper disable MemberCanBePrivate.Global
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a display connected to graphics-card.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct Display
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a display connected to graphics-card.
|
/// Gets the index of the <see cref="Display"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct Display
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the <see cref="Display"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string DeviceName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the with of the <see cref="Display"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int Width { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the height of the <see cref="Display"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="GraphicsCard"/> this <see cref="Display"/> is connected to.
|
||||||
|
/// </summary>
|
||||||
|
public GraphicsCard GraphicsCard { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Display"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the <see cref="Display"/>.</param>
|
||||||
|
/// <param name="deviceName">The name of the <see cref="Display"/>.</param>
|
||||||
|
/// <param name="width">The with of the <see cref="Display"/>.</param>
|
||||||
|
/// <param name="height">The height of the <see cref="Display"/>.</param>
|
||||||
|
/// <param name="graphicsCard">The <see cref="GraphicsCard"/> this <see cref="Display"/> is connected to.</param>
|
||||||
|
public Display(int index, string deviceName, int width, int height, GraphicsCard graphicsCard)
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
this.Index = index;
|
||||||
|
this.DeviceName = deviceName;
|
||||||
/// <summary>
|
this.Width = width;
|
||||||
/// Gets the index of the <see cref="Display"/>.
|
this.Height = height;
|
||||||
/// </summary>
|
this.GraphicsCard = graphicsCard;
|
||||||
public int Index { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the <see cref="Display"/>.
|
|
||||||
/// </summary>
|
|
||||||
public string DeviceName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the with of the <see cref="Display"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int Width { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the height of the <see cref="Display"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int Height { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="GraphicsCard"/> this <see cref="Display"/> is connected to.
|
|
||||||
/// </summary>
|
|
||||||
public GraphicsCard GraphicsCard { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Display"/> struct.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the <see cref="Display"/>.</param>
|
|
||||||
/// <param name="deviceName">The name of the <see cref="Display"/>.</param>
|
|
||||||
/// <param name="width">The with of the <see cref="Display"/>.</param>
|
|
||||||
/// <param name="height">The height of the <see cref="Display"/>.</param>
|
|
||||||
/// <param name="graphicsCard">The <see cref="GraphicsCard"/> this <see cref="Display"/> is connected to.</param>
|
|
||||||
public Display(int index, string deviceName, int width, int height, GraphicsCard graphicsCard)
|
|
||||||
{
|
|
||||||
this.Index = index;
|
|
||||||
this.DeviceName = deviceName;
|
|
||||||
this.Width = width;
|
|
||||||
this.Height = height;
|
|
||||||
this.GraphicsCard = graphicsCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this <see cref="Display"/> equals the given one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The <see cref="Display"/> to compare.</param>
|
|
||||||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
|
||||||
public bool Equals(Display other) => Index == other.Index;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Equals(object? obj) => obj is Display other && Equals(other);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode() => Index;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="Display"/> are equal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="left">The first value.</param>
|
|
||||||
/// <param name="right">The second value.</param>
|
|
||||||
/// <returns><c>true</c> if the two specified displays are equal; otherwise, <c>false</c>.</returns>
|
|
||||||
public static bool operator ==(Display left, Display right) => left.Equals(right);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="Display"/> are not equal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="left">The first value.</param>
|
|
||||||
/// <param name="right">The second value.</param>
|
|
||||||
/// <returns><c>true</c> if the two specified displays are not equal; otherwise, <c>false</c>.</returns>
|
|
||||||
public static bool operator !=(Display left, Display right) => !(left == right);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this <see cref="Display"/> equals the given one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The <see cref="Display"/> to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool Equals(Display other) => Index == other.Index;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj) => obj is Display other && Equals(other);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode() => Index;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether two <see cref="Display"/> are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first value.</param>
|
||||||
|
/// <param name="right">The second value.</param>
|
||||||
|
/// <returns><c>true</c> if the two specified displays are equal; otherwise, <c>false</c>.</returns>
|
||||||
|
public static bool operator ==(Display left, Display right) => left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether two <see cref="Display"/> are not equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first value.</param>
|
||||||
|
/// <param name="right">The second value.</param>
|
||||||
|
/// <returns><c>true</c> if the two specified displays are not equal; otherwise, <c>false</c>.</returns>
|
||||||
|
public static bool operator !=(Display left, Display right) => !(left == right);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -1,86 +1,85 @@
|
|||||||
// ReSharper disable MemberCanBePrivate.Global
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
|
||||||
namespace ScreenCapture.NET
|
namespace ScreenCapture.NET;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a graphics-card.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct GraphicsCard
|
||||||
{
|
{
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a graphics-card.
|
/// Gets the index of the <see cref="GraphicsCard"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct GraphicsCard
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the <see cref="GraphicsCard"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vendor-id of the <see cref="GraphicsCard"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int VendorId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the device-id of the <see cref="GraphicsCard"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int DeviceId { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GraphicsCard"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the <see cref="GraphicsCard"/>.</param>
|
||||||
|
/// <param name="name">The name of the <see cref="GraphicsCard"/>.</param>
|
||||||
|
/// <param name="vendorId">The vendor-id of the <see cref="GraphicsCard"/>.</param>
|
||||||
|
/// <param name="deviceId">The device-id of the <see cref="GraphicsCard"/>.</param>
|
||||||
|
public GraphicsCard(int index, string name, int vendorId, int deviceId)
|
||||||
{
|
{
|
||||||
#region Properties & Fields
|
this.Index = index;
|
||||||
|
this.Name = name;
|
||||||
/// <summary>
|
this.VendorId = vendorId;
|
||||||
/// Gets the index of the <see cref="GraphicsCard"/>.
|
this.DeviceId = deviceId;
|
||||||
/// </summary>
|
|
||||||
public int Index { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the <see cref="GraphicsCard"/>.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the vendor-id of the <see cref="GraphicsCard"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int VendorId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the device-id of the <see cref="GraphicsCard"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int DeviceId { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GraphicsCard"/> struct.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the <see cref="GraphicsCard"/>.</param>
|
|
||||||
/// <param name="name">The name of the <see cref="GraphicsCard"/>.</param>
|
|
||||||
/// <param name="vendorId">The vendor-id of the <see cref="GraphicsCard"/>.</param>
|
|
||||||
/// <param name="deviceId">The device-id of the <see cref="GraphicsCard"/>.</param>
|
|
||||||
public GraphicsCard(int index, string name, int vendorId, int deviceId)
|
|
||||||
{
|
|
||||||
this.Index = index;
|
|
||||||
this.Name = name;
|
|
||||||
this.VendorId = vendorId;
|
|
||||||
this.DeviceId = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this <see cref="Display"/> equals the given one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The <see cref="Display"/> to compare.</param>
|
|
||||||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
|
||||||
public bool Equals(GraphicsCard other) => Index == other.Index;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode() => Index;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="GraphicsCard"/> are equal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="left">The first value.</param>
|
|
||||||
/// <param name="right">The second value.</param>
|
|
||||||
/// <returns><c>true</c> if the two specified graphics-cards are equal; otherwise, <c>false</c>.</returns>
|
|
||||||
public static bool operator ==(GraphicsCard left, GraphicsCard right) => left.Equals(right);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="GraphicsCard"/> are not equal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="left">The first value.</param>
|
|
||||||
/// <param name="right">The second value.</param>
|
|
||||||
/// <returns><c>true</c> if the two specified graphics-cards are not equal; otherwise, <c>false</c>.</returns>
|
|
||||||
public static bool operator !=(GraphicsCard left, GraphicsCard right) => !(left == right);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this <see cref="Display"/> equals the given one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The <see cref="Display"/> to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool Equals(GraphicsCard other) => Index == other.Index;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode() => Index;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether two <see cref="GraphicsCard"/> are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first value.</param>
|
||||||
|
/// <param name="right">The second value.</param>
|
||||||
|
/// <returns><c>true</c> if the two specified graphics-cards are equal; otherwise, <c>false</c>.</returns>
|
||||||
|
public static bool operator ==(GraphicsCard left, GraphicsCard right) => left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether two <see cref="GraphicsCard"/> are not equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first value.</param>
|
||||||
|
/// <param name="right">The second value.</param>
|
||||||
|
/// <returns><c>true</c> if the two specified graphics-cards are not equal; otherwise, <c>false</c>.</returns>
|
||||||
|
public static bool operator !=(GraphicsCard left, GraphicsCard right) => !(left == right);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user