From 520c4a98fcdc5b760eabc36e82d52d72ef0ee184 Mon Sep 17 00:00:00 2001 From: Igneom Date: Thu, 28 Apr 2022 00:42:23 -0400 Subject: [PATCH 1/6] Added functionality to reposition CaptureZones, without having to recreate the CaptureZone from scratch. --- .../DirectX/DX11ScreenCapture.cs | 36 ++++++++++++++----- ScreenCapture.NET/IScreenCapture.cs | 11 +++++- ScreenCapture.NET/Model/CaptureZone.cs | 4 +-- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index d0434b8..e7e39ff 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -203,14 +203,7 @@ namespace ScreenCapture.NET /// public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0) { - 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"); + CaptureZoneValidityCheck(x, y, width, height); int unscaledWidth = width; int unscaledHeight = height; @@ -252,6 +245,33 @@ namespace ScreenCapture.NET } } + /// + public void RepositionCaptureZone(CaptureZone captureZone, int x, int y) + { + CaptureZoneValidityCheck(x, y, captureZone.UnscaledWidth, captureZone.UnscaledHeight); + + lock (_captureZones) + { + if (!_captureZones.ContainsKey(captureZone)) + throw new ArgumentException("Non registered CaptureZone", nameof(captureZone)); + } + + captureZone.X = x; + captureZone.Y = y; + } + + private void CaptureZoneValidityCheck(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() diff --git a/ScreenCapture.NET/IScreenCapture.cs b/ScreenCapture.NET/IScreenCapture.cs index 1fc80f7..7107c35 100644 --- a/ScreenCapture.NET/IScreenCapture.cs +++ b/ScreenCapture.NET/IScreenCapture.cs @@ -37,10 +37,19 @@ namespace ScreenCapture.NET /// /// Removes the given from the . /// - /// The previosly registered . + /// The previously registered . /// true if the was successfully removed; otherwise, false. bool UnregisterCaptureZone(CaptureZone captureZone); + /// + /// Updates the position of the given . + /// + /// The previously registered . + /// The new x-location of the region on the screen. + /// The new y-location of the region on the screen + /// true if the was successfully repositioned; otherwise, false. + void RepositionCaptureZone(CaptureZone captureZone, int x, int y); + /// /// Restarts the . /// diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index dbbc48d..83ff803 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -19,12 +19,12 @@ namespace ScreenCapture.NET /// /// Gets the x-location of the region on the screen. /// - public int X { get; } + public int X { get; internal set; } /// /// Gets the y-location of the region on the screen. /// - public int Y { get; } + public int Y { get; internal set; } /// /// Gets the width of the region on the screen. From dee0c096a65a6c3ba41d04487a4fc314ea1b983f Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 1 May 2022 14:29:55 +0200 Subject: [PATCH 2/6] Changed RepositionCaptureZone to UpdateCaptureZone and handled all kinds of changes --- .../DirectX/DX11ScreenCapture.cs | 82 +++++++++++++------ ScreenCapture.NET/IScreenCapture.cs | 17 ++-- ScreenCapture.NET/Model/CaptureZone.cs | 16 ++-- 3 files changed, 75 insertions(+), 40 deletions(-) diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index e7e39ff..77d0b2d 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -171,17 +171,17 @@ namespace ScreenCapture.NET 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)); + 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)); + 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; @@ -203,21 +203,13 @@ namespace ScreenCapture.NET /// public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0) { - CaptureZoneValidityCheck(x, y, width, height); + ValidateCaptureZoneAndThrow(x, y, width, height); int unscaledWidth = width; int unscaledHeight = height; - if (downscaleLevel > 0) - for (int i = 0; i < downscaleLevel; i++) - { - width /= 2; - height /= 2; - } + (width, height) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel); - if (width < 1) width = 1; - if (height < 1) height = 1; - - byte[] buffer = new byte[width * height * 4]; + byte[] buffer = new byte[width * height * BPP]; CaptureZone captureZone = new(_indexCounter++, x, y, width, height, BPP, downscaleLevel, unscaledWidth, unscaledHeight, buffer); lock (_captureZones) @@ -246,21 +238,59 @@ namespace ScreenCapture.NET } /// - public void RepositionCaptureZone(CaptureZone captureZone, int x, int y) + public void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null) { - CaptureZoneValidityCheck(x, y, captureZone.UnscaledWidth, captureZone.UnscaledHeight); - lock (_captureZones) - { if (!_captureZones.ContainsKey(captureZone)) - throw new ArgumentException("Non registered CaptureZone", nameof(captureZone)); - } + throw new ArgumentException("The capture zone is not registered to this ScreenCapture", nameof(captureZone)); - captureZone.X = x; - captureZone.Y = y; + 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 void CaptureZoneValidityCheck(int x, int y, int width, int height) + 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."); diff --git a/ScreenCapture.NET/IScreenCapture.cs b/ScreenCapture.NET/IScreenCapture.cs index 7107c35..e2b47e9 100644 --- a/ScreenCapture.NET/IScreenCapture.cs +++ b/ScreenCapture.NET/IScreenCapture.cs @@ -27,7 +27,7 @@ namespace ScreenCapture.NET /// Creates a new for this . /// /// The x-location of the region to capture (must be >= 0 and < screen-width). - /// The x-location of the region to capture (must be >= 0 and < screen-height). + /// The y-location of the region to capture (must be >= 0 and < screen-height). /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). /// The height of the region to capture (must be >= 0 and this + y must be <= screen-height). /// The level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. @@ -42,13 +42,18 @@ namespace ScreenCapture.NET bool UnregisterCaptureZone(CaptureZone captureZone); /// - /// Updates the position of the given . + /// Updates the the given . /// + /// + /// null-parameters are ignored and not changed. + /// /// The previously registered . - /// The new x-location of the region on the screen. - /// The new y-location of the region on the screen - /// true if the was successfully repositioned; otherwise, false. - void RepositionCaptureZone(CaptureZone captureZone, int x, int y); + /// The new x-location of the region to capture (must be >= 0 and < screen-width). + /// The new y-location of the region to capture (must be >= 0 and < screen-height). + /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). + /// The new height of the region to capture (must be >= 0 and this + y must be <= screen-height). + /// 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. + void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null); /// /// Restarts the . diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 83ff803..460a0a6 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -27,29 +27,29 @@ namespace ScreenCapture.NET public int Y { get; internal set; } /// - /// Gets the width of the region on the screen. + /// Gets the width of the captured region. /// - public int Width { get; } + public int Width { get; internal set; } /// - /// Gets the height of the region on the screen. + /// Gets the height of the captured region. /// - public int Height { get; } + public int Height { get; internal set; } /// /// 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. /// - public int DownscaleLevel { get; } + public int DownscaleLevel { get; internal set; } /// /// Gets the original width of the region (this equals if is 0). /// - public int UnscaledWidth { get; } + public int UnscaledWidth { get; internal set; } /// /// Gets the original height of the region (this equals if is 0). /// - public int UnscaledHeight { get; } + public int UnscaledHeight { get; internal set; } /// /// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]). @@ -64,7 +64,7 @@ namespace ScreenCapture.NET /// /// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32. /// - public byte[] Buffer { get; } + public byte[] Buffer { get; internal set; } /// /// Gets the config for black-bar detection. From 19080e6ec7d85620e920da35008a2079a30c7f5c Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 1 May 2022 14:34:12 +0200 Subject: [PATCH 3/6] Small refactorings --- .../DirectX/DX11ScreenCapture.cs | 787 +++++++++--------- .../DirectX/DX11ScreenCaptureService.cs | 149 ++-- .../Events/ScreenCaptureUpdatedEventArgs.cs | 51 +- ScreenCapture.NET/Helper/DPIAwareness.cs | 67 +- ScreenCapture.NET/IScreenCapture.cs | 103 ++- ScreenCapture.NET/IScreenCaptureService.cs | 45 +- ScreenCapture.NET/Model/BlackBarDetection.cs | 241 +++--- ScreenCapture.NET/Model/CaptureZone.cs | 301 ++++--- ScreenCapture.NET/Model/Display.cs | 171 ++-- ScreenCapture.NET/Model/GraphicsCard.cs | 157 ++-- 10 files changed, 1031 insertions(+), 1041 deletions(-) diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index 77d0b2d..7afc9fb 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -11,420 +11,419 @@ using Vortice.Mathematics; using MapFlags = Vortice.Direct3D11.MapFlags; using ResultCode = Vortice.DXGI.ResultCode; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents a ScreenCapture using DirectX 11 desktop duplicaton. +/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api +/// +// ReSharper disable once InconsistentNaming +public sealed class DX11ScreenCapture : IScreenCapture { - /// - /// Represents a ScreenCapture using DirectX 11 desktop duplicaton. - /// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api - /// - // ReSharper disable once InconsistentNaming - public sealed class DX11ScreenCapture : IScreenCapture + #region Constants + + private static readonly FeatureLevel[] FEATURE_LEVELS = { - #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; + + /// + public Display Display { get; } + + /// + /// Gets or sets the timeout in ms used for screen-capturing. (default 1000ms) + /// This is used in https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-acquirenextframe + /// + // 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 _captureZones = new(); + + #endregion + + #region Events + + /// + public event EventHandler? Updated; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// Note that setting useNewDuplicationAdapter to true requires to call DPIAwareness.Initalize(); and prevents the capture from running in a WPF-thread. + /// + /// The used to create underlying objects. + /// The to duplicate. + /// Indicates if the DuplicateOutput1 interface should be used instead of the older DuplicateOutput. Currently there's no real use in setting this to true. + public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false) + { + this._factory = factory; + this.Display = display; + this._useNewDuplicationAdapter = useNewDuplicationAdapter; + + Restart(); + } + + #endregion + + #region Methods + + /// + public bool CaptureScreen() + { + bool result = false; + lock (_captureLock) { - FeatureLevel.Level_11_1, - 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; - - /// - public Display Display { get; } - - /// - /// Gets or sets the timeout in ms used for screen-capturing. (default 1000ms) - /// This is used in https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-acquirenextframe - /// - // 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 _captureZones = new(); - - #endregion - - #region Events - - /// - public event EventHandler? Updated; - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// - /// Note that setting useNewDuplicationAdapter to true requires to call DPIAwareness.Initalize(); and prevents the capture from running in a WPF-thread. - /// - /// The used to create underlying objects. - /// The to duplicate. - /// Indicates if the DuplicateOutput1 interface should be used instead of the older DuplicateOutput. Currently there's no real use in setting this to true. - public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false) - { - this._factory = factory; - this.Display = display; - this._useNewDuplicationAdapter = useNewDuplicationAdapter; - - Restart(); - } - - #endregion - - #region Methods - - /// - public bool CaptureScreen() - { - bool result = false; - lock (_captureLock) + if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null)) { - if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null)) - { - 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(); - _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(); - } - } - } - - /// - 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; - } - - /// - 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; - } - + Restart(); return false; } - } - /// - 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); - } - - /// - public void Restart() - { - lock (_captureLock) - { - try - { - List 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(); - - 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); } - } - } - - /// - public void Dispose() => Dispose(true); - - private void Dispose(bool removeCaptureZones) - { try { - lock (_captureLock) + IDXGIResource? screenResource = null; + try { - try { _duplicatedOutput?.Dispose(); } catch { /**/ } - _duplicatedOutput = null; + _duplicatedOutput.AcquireNextFrame(Timeout, out OutduplFrameInfo duplicateFrameInformation, out screenResource); + if ((screenResource == null) || (duplicateFrameInformation.LastPresentTime == 0)) return false; + using ID3D11Texture2D screenTexture = screenResource.QueryInterface(); + _context.CopySubresourceRegion(_captureTexture, 0, 0, 0, 0, screenTexture, 0); + } + finally + { try { - if (removeCaptureZones) - { - List captureZones = _captureZones.Keys.ToList(); - foreach (CaptureZone captureZone in captureZones) - UnregisterCaptureZone(captureZone); - } + screenResource?.Dispose(); + _duplicatedOutput?.ReleaseFrame(); } catch { /**/ } + } - try { _output?.Dispose(); } catch { /**/ } - try { _context?.Dispose(); } catch { /**/ } - try { _device?.Dispose(); } catch { /**/ } - try { _captureTexture?.Dispose(); } catch { /**/ } - _context = null; - _captureTexture = null; + 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(); + } + } + } + + /// + 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; + } + + /// + 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; + } + } + + /// + 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); } -} + + /// + public void Restart() + { + lock (_captureLock) + { + try + { + List 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(); + + 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); } + } + } + + /// + public void Dispose() => Dispose(true); + + private void Dispose(bool removeCaptureZones) + { + try + { + lock (_captureLock) + { + try { _duplicatedOutput?.Dispose(); } catch { /**/ } + _duplicatedOutput = null; + + try + { + if (removeCaptureZones) + { + List 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 +} \ No newline at end of file diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs b/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs index 5a9a80b..da70944 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs @@ -2,83 +2,82 @@ using System.Collections.Generic; using Vortice.DXGI; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents a using the . +/// +public class DX11ScreenCaptureService : IScreenCaptureService { + #region Properties & Fields + + private readonly IDXGIFactory1 _factory; + + private readonly Dictionary _screenCaptures = new(); + + #endregion + + #region Constructors + /// - /// + /// Initializes a new instance of the class. /// - public class DX11ScreenCaptureService : IScreenCaptureService + public DX11ScreenCaptureService() { - #region Properties & Fields - - private readonly IDXGIFactory1 _factory; - - private readonly Dictionary _screenCaptures = new(); - - #endregion - - #region Constructors - - /// - /// - /// - public DX11ScreenCaptureService() - { - DXGI.CreateDXGIFactory1(out _factory).CheckError(); - } - - #endregion - - #region Methods - - /// - public IEnumerable 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++; - } - } - - /// - public IEnumerable 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++; - } - } - - /// - public IScreenCapture GetScreenCapture(Display display) - { - if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture)) - _screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display)); - return screenCapture; - } - - /// - public void Dispose() - { - foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values) - screenCapture.Dispose(); - _screenCaptures.Clear(); - - _factory.Dispose(); - - GC.SuppressFinalize(this); - } - - #endregion + DXGI.CreateDXGIFactory1(out _factory!).CheckError(); } -} + + #endregion + + #region Methods + + /// + public IEnumerable 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++; + } + } + + /// + public IEnumerable 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++; + } + } + + /// + public IScreenCapture GetScreenCapture(Display display) + { + if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture)) + _screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display)); + return screenCapture; + } + + /// + public void Dispose() + { + foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values) + screenCapture.Dispose(); + _screenCaptures.Clear(); + + _factory.Dispose(); + + GC.SuppressFinalize(this); + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Events/ScreenCaptureUpdatedEventArgs.cs b/ScreenCapture.NET/Events/ScreenCaptureUpdatedEventArgs.cs index 2decab1..7fc89d2 100644 --- a/ScreenCapture.NET/Events/ScreenCaptureUpdatedEventArgs.cs +++ b/ScreenCapture.NET/Events/ScreenCaptureUpdatedEventArgs.cs @@ -2,34 +2,33 @@ using System; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// +/// Represents the information supplied with an -event. +/// +public class ScreenCaptureUpdatedEventArgs : EventArgs { - /// + #region Properties & Fields + /// - /// Represents the information supplied with an -event. + /// true if the update was successful; otherwise, false. /// - public class ScreenCaptureUpdatedEventArgs : EventArgs + public bool IsSuccessful { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// Indicates if the last update was successful. + public ScreenCaptureUpdatedEventArgs(bool isSuccessful) { - #region Properties & Fields - - /// - /// true if the update was successful; otherwise, false. - /// - public bool IsSuccessful { get; set; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// Indicates if the last update was successful. - public ScreenCaptureUpdatedEventArgs(bool isSuccessful) - { - this.IsSuccessful = isSuccessful; - } - - #endregion + this.IsSuccessful = isSuccessful; } -} + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Helper/DPIAwareness.cs b/ScreenCapture.NET/Helper/DPIAwareness.cs index de55dd6..07bf69f 100644 --- a/ScreenCapture.NET/Helper/DPIAwareness.cs +++ b/ScreenCapture.NET/Helper/DPIAwareness.cs @@ -3,40 +3,39 @@ using System.Runtime.InteropServices; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Helper-class for DPI-related WIN-API calls. +/// +public static class DPIAwareness { - /// - /// Helper-class for DPI-related WIN-API calls. - /// - public static class DPIAwareness + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag); + + [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)] - internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag); - - [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 - } - - /// - /// Sets the DPI-Awareness-Context to V2. This is needed to prevent issues when using desktop duplication. - /// - public static void Initalize() => SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + 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 + } + + /// + /// Sets the DPI-Awareness-Context to V2. This is needed to prevent issues when using desktop duplication. + /// + public static void Initalize() => SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); +} \ No newline at end of file diff --git a/ScreenCapture.NET/IScreenCapture.cs b/ScreenCapture.NET/IScreenCapture.cs index e2b47e9..362836f 100644 --- a/ScreenCapture.NET/IScreenCapture.cs +++ b/ScreenCapture.NET/IScreenCapture.cs @@ -1,63 +1,62 @@ using System; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents the duplication of a single display. +/// +public interface IScreenCapture : IDisposable { /// - /// Represents the duplication of a single display. + /// Gets the this capture is duplicating. /// - public interface IScreenCapture : IDisposable - { - /// - /// Gets the this capture is duplicating. - /// - Display Display { get; } + Display Display { get; } - /// - /// Occurs when the is updated. - /// - event EventHandler? Updated; + /// + /// Occurs when the is updated. + /// + event EventHandler? Updated; - /// - /// Attemts to capture the current frame showed on the . - /// - /// true if the current frame was captures successfully; otherwise, false. - bool CaptureScreen(); + /// + /// Attemts to capture the current frame showed on the . + /// + /// true if the current frame was captures successfully; otherwise, false. + bool CaptureScreen(); - /// - /// Creates a new for this . - /// - /// The x-location of the region to capture (must be >= 0 and < screen-width). - /// The y-location of the region to capture (must be >= 0 and < screen-height). - /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). - /// The height of the region to capture (must be >= 0 and this + y must be <= screen-height). - /// The level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. - /// The new . - CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0); + /// + /// Creates a new for this . + /// + /// The x-location of the region to capture (must be >= 0 and < screen-width). + /// The y-location of the region to capture (must be >= 0 and < screen-height). + /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). + /// The height of the region to capture (must be >= 0 and this + y must be <= screen-height). + /// The level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. + /// The new . + CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0); - /// - /// Removes the given from the . - /// - /// The previously registered . - /// true if the was successfully removed; otherwise, false. - bool UnregisterCaptureZone(CaptureZone captureZone); + /// + /// Removes the given from the . + /// + /// The previously registered . + /// true if the was successfully removed; otherwise, false. + bool UnregisterCaptureZone(CaptureZone captureZone); - /// - /// Updates the the given . - /// - /// - /// null-parameters are ignored and not changed. - /// - /// The previously registered . - /// The new x-location of the region to capture (must be >= 0 and < screen-width). - /// The new y-location of the region to capture (must be >= 0 and < screen-height). - /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). - /// The new height of the region to capture (must be >= 0 and this + y must be <= screen-height). - /// 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. - void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null); + /// + /// Updates the the given . + /// + /// + /// null-parameters are ignored and not changed. + /// + /// The previously registered . + /// The new x-location of the region to capture (must be >= 0 and < screen-width). + /// The new y-location of the region to capture (must be >= 0 and < screen-height). + /// The width of the region to capture (must be >= 0 and this + x must be <= screen-width). + /// The new height of the region to capture (must be >= 0 and this + y must be <= screen-height). + /// 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. + void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null); - /// - /// Restarts the . - /// - void Restart(); - } -} + /// + /// Restarts the . + /// + void Restart(); +} \ No newline at end of file diff --git a/ScreenCapture.NET/IScreenCaptureService.cs b/ScreenCapture.NET/IScreenCaptureService.cs index b4eb2fe..f98e28b 100644 --- a/ScreenCapture.NET/IScreenCaptureService.cs +++ b/ScreenCapture.NET/IScreenCaptureService.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// +/// +public interface IScreenCaptureService : IDisposable { /// - /// + /// Gets a enumerable of all available graphics-cards. /// - public interface IScreenCaptureService : IDisposable - { - /// - /// Gets a enumerable of all available graphics-cards. - /// - /// A enumerable of all available graphics-cards. - IEnumerable GetGraphicsCards(); + /// A enumerable of all available graphics-cards. + IEnumerable GetGraphicsCards(); - /// - /// Gets a enumerable of all display connected to the given graphics-cards. - /// - /// The graphics-card to get the displays from. - /// A enumerable of all display connected to the given graphics-cards. - IEnumerable GetDisplays(GraphicsCard graphicsCard); + /// + /// Gets a enumerable of all display connected to the given graphics-cards. + /// + /// The graphics-card to get the displays from. + /// A enumerable of all display connected to the given graphics-cards. + IEnumerable GetDisplays(GraphicsCard graphicsCard); - /// - /// Creates a for the given display. - /// - /// The display to duplicate. - /// The for the give display. - IScreenCapture GetScreenCapture(Display display); - } -} + /// + /// Creates a for the given display. + /// + /// The display to duplicate. + /// The for the give display. + IScreenCapture GetScreenCapture(Display display); +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/BlackBarDetection.cs b/ScreenCapture.NET/Model/BlackBarDetection.cs index 7d58e5b..254a57c 100644 --- a/ScreenCapture.NET/Model/BlackBarDetection.cs +++ b/ScreenCapture.NET/Model/BlackBarDetection.cs @@ -2,141 +2,140 @@ using System; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents the configuration for the detection and removal of black bars around the screen image. +/// +public sealed class BlackBarDetection { + #region Properties & Fields + + private readonly CaptureZone _captureZone; + + private int? _top; /// - /// 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. /// - public sealed class BlackBarDetection + public int Top => _top ??= CalculateTop(); + + private int? _bottom; + /// + /// Gets the size of the detected black bar at the bottom of the image. + /// + public int Bottom => _bottom ??= CalculateBottom(); + + private int? _left; + /// + /// Gets the size of the detected black bar at the left of the image. + /// + public int Left => _left ??= CalculateLeft(); + + private int? _right; + /// + /// Gets the size of the detected black bar at the right of the image. + /// + public int Right => _right ??= CalculateRight(); + + private int _theshold = 0; + /// + /// 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) + /// + public int Threshold { - #region Properties & Fields - - private readonly CaptureZone _captureZone; - - private int? _top; - /// - /// Gets the size of the detected black bar at the top of the image. - /// - public int Top => _top ??= CalculateTop(); - - private int? _bottom; - /// - /// Gets the size of the detected black bar at the bottom of the image. - /// - public int Bottom => _bottom ??= CalculateBottom(); - - private int? _left; - /// - /// Gets the size of the detected black bar at the left of the image. - /// - public int Left => _left ??= CalculateLeft(); - - private int? _right; - /// - /// Gets the size of the detected black bar at the right of the image. - /// - public int Right => _right ??= CalculateRight(); - - private int _theshold = 0; - /// - /// 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) - /// - public int Threshold + get => _theshold; + set { - get => _theshold; - set - { - _theshold = value; - InvalidateCache(); - } + _theshold = value; + InvalidateCache(); + } + } + + #endregion + + #region Constructors + + internal BlackBarDetection(CaptureZone captureZone) + { + this._captureZone = captureZone; + } + + #endregion + + #region Methods + + /// + /// Invalidates the cached values and recalculates , , and . + /// + 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 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 - - internal BlackBarDetection(CaptureZone captureZone) + private int CalculateBottom() + { + int threshold = Threshold; + int stride = _captureZone.Stride; + for (int row = _captureZone.Height - 1; row >= 0; row--) { - this._captureZone = captureZone; + Span 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 - - /// - /// Invalidates the cached values and recalculates , , and . - /// - public void InvalidateCache() - { - _top = null; - _bottom = null; - _left = null; - _right = null; - } - - private int CalculateTop() - { - int threshold = Threshold; - int stride = _captureZone.Stride; + 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++) { - Span 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; + int offset = (stride * row) + (column * 4); + if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) + return column; } - return 0; - } - - private int CalculateBottom() - { - int threshold = Threshold; - int stride = _captureZone.Stride; - for (int row = _captureZone.Height - 1; row >= 0; row--) - { - Span 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 + 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 +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 460a0a6..100263d 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -2,161 +2,160 @@ using System; -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents a duplicated region on the screen. +/// +public sealed class CaptureZone { + #region Properties & Fields + /// - /// Represents a duplicated region on the screen. + /// Gets the unique id of this . /// - public sealed class CaptureZone + public int Id { get; } + + /// + /// Gets the x-location of the region on the screen. + /// + public int X { get; internal set; } + + /// + /// Gets the y-location of the region on the screen. + /// + public int Y { get; internal set; } + + /// + /// Gets the width of the captured region. + /// + public int Width { get; internal set; } + + /// + /// Gets the height of the captured region. + /// + public int Height { get; internal set; } + + /// + /// 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. + /// + public int DownscaleLevel { get; internal set; } + + /// + /// Gets the original width of the region (this equals if is 0). + /// + public int UnscaledWidth { get; internal set; } + + /// + /// Gets the original height of the region (this equals if is 0). + /// + public int UnscaledHeight { get; internal set; } + + /// + /// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]). + /// + public int BytesPerPixel { get; } + + /// + /// Gets the size in bytes of a row in the region ( * ). + /// + public int Stride => Width * BytesPerPixel; + + /// + /// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32. + /// + public byte[] Buffer { get; internal set; } + + /// + /// Gets the config for black-bar detection. + /// + public BlackBarDetection BlackBars { get; } + + /// + /// Gets or sets if the should be automatically updated on every captured frame. + /// + public bool AutoUpdate { get; set; } = true; + + /// + /// Gets if an update for the is requested on the next captured frame. + /// + public bool IsUpdateRequested { get; private set; } + + #endregion + + #region Events + + /// + /// Occurs when the is updated. + /// + public event EventHandler? Updated; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The unique id of this . + /// The x-location of the region on the screen. + /// The y-location of the region on the screen. + /// The width of the region on the screen. + /// The height of the region on the screen. + /// The level of downscaling applied to the image of this region before copying to local memory. + /// The original width of the region. + /// The original height of the region + /// The buffer containing the image data. + 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; - /// - /// Gets the unique id of this . - /// - public int Id { get; } - - /// - /// Gets the x-location of the region on the screen. - /// - public int X { get; internal set; } - - /// - /// Gets the y-location of the region on the screen. - /// - public int Y { get; internal set; } - - /// - /// Gets the width of the captured region. - /// - public int Width { get; internal set; } - - /// - /// Gets the height of the captured region. - /// - public int Height { get; internal set; } - - /// - /// 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. - /// - public int DownscaleLevel { get; internal set; } - - /// - /// Gets the original width of the region (this equals if is 0). - /// - public int UnscaledWidth { get; internal set; } - - /// - /// Gets the original height of the region (this equals if is 0). - /// - public int UnscaledHeight { get; internal set; } - - /// - /// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]). - /// - public int BytesPerPixel { get; } - - /// - /// Gets the size in bytes of a row in the region ( * ). - /// - public int Stride => Width * BytesPerPixel; - - /// - /// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32. - /// - public byte[] Buffer { get; internal set; } - - /// - /// Gets the config for black-bar detection. - /// - public BlackBarDetection BlackBars { get; } - - /// - /// Gets or sets if the should be automatically updated on every captured frame. - /// - public bool AutoUpdate { get; set; } = true; - - /// - /// Gets if an update for the is requested on the next captured frame. - /// - public bool IsUpdateRequested { get; private set; } - - #endregion - - #region Events - - /// - /// Occurs when the is updated. - /// - public event EventHandler? Updated; - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The unique id of this . - /// The x-location of the region on the screen. - /// The y-location of the region on the screen. - /// The width of the region on the screen. - /// The height of the region on the screen. - /// The level of downscaling applied to the image of this region before copying to local memory. - /// The original width of the region. - /// The original height of the region - /// The buffer containing the image data. - 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 - - /// - /// Requests to update this when the next frame is captured. - /// Only necessary if is set to false. - /// - public void RequestUpdate() => IsUpdateRequested = true; - - /// - /// Marks the as updated. - /// WARNING: This should not be called outside of an ! - /// - public void SetUpdated() - { - IsUpdateRequested = false; - BlackBars.InvalidateCache(); - - Updated?.Invoke(this, new EventArgs()); - } - - /// - /// Determines whether this equals the given one. - /// - /// The to compare. - /// true if the specified object is equal to the current object; otherwise, false. - public bool Equals(CaptureZone other) => Id == other.Id; - - /// - public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other); - - /// - public override int GetHashCode() => Id; - - #endregion + BlackBars = new BlackBarDetection(this); } + + #endregion + + #region Methods + + /// + /// Requests to update this when the next frame is captured. + /// Only necessary if is set to false. + /// + public void RequestUpdate() => IsUpdateRequested = true; + + /// + /// Marks the as updated. + /// WARNING: This should not be called outside of an ! + /// + public void SetUpdated() + { + IsUpdateRequested = false; + BlackBars.InvalidateCache(); + + Updated?.Invoke(this, new EventArgs()); + } + + /// + /// Determines whether this equals the given one. + /// + /// The to compare. + /// true if the specified object is equal to the current object; otherwise, false. + public bool Equals(CaptureZone other) => Id == other.Id; + + /// + public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other); + + /// + public override int GetHashCode() => Id; + + #endregion } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Display.cs b/ScreenCapture.NET/Model/Display.cs index e2b5ed0..7c749aa 100644 --- a/ScreenCapture.NET/Model/Display.cs +++ b/ScreenCapture.NET/Model/Display.cs @@ -1,93 +1,92 @@ // ReSharper disable MemberCanBePrivate.Global -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents a display connected to graphics-card. +/// +public readonly struct Display { + #region Properties & Fields + /// - /// Represents a display connected to graphics-card. + /// Gets the index of the . /// - public readonly struct Display + public int Index { get; } + + /// + /// Gets the name of the . + /// + public string DeviceName { get; } + + /// + /// Gets the with of the . + /// + public int Width { get; } + + /// + /// Gets the height of the . + /// + public int Height { get; } + + /// + /// Gets the this is connected to. + /// + public GraphicsCard GraphicsCard { get; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The index of the . + /// The name of the . + /// The with of the . + /// The height of the . + /// The this is connected to. + public Display(int index, string deviceName, int width, int height, GraphicsCard graphicsCard) { - #region Properties & Fields - - /// - /// Gets the index of the . - /// - public int Index { get; } - - /// - /// Gets the name of the . - /// - public string DeviceName { get; } - - /// - /// Gets the with of the . - /// - public int Width { get; } - - /// - /// Gets the height of the . - /// - public int Height { get; } - - /// - /// Gets the this is connected to. - /// - public GraphicsCard GraphicsCard { get; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the struct. - /// - /// The index of the . - /// The name of the . - /// The with of the . - /// The height of the . - /// The this is connected to. - 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 - - /// - /// Determines whether this equals the given one. - /// - /// The to compare. - /// true if the specified object is equal to the current object; otherwise, false. - public bool Equals(Display other) => Index == other.Index; - - /// - public override bool Equals(object? obj) => obj is Display other && Equals(other); - - /// - public override int GetHashCode() => Index; - - /// - /// Determines whether two are equal. - /// - /// The first value. - /// The second value. - /// true if the two specified displays are equal; otherwise, false. - public static bool operator ==(Display left, Display right) => left.Equals(right); - - /// - /// Determines whether two are not equal. - /// - /// The first value. - /// The second value. - /// true if the two specified displays are not equal; otherwise, false. - public static bool operator !=(Display left, Display right) => !(left == right); - - #endregion + this.Index = index; + this.DeviceName = deviceName; + this.Width = width; + this.Height = height; + this.GraphicsCard = graphicsCard; } -} + + #endregion + + #region Methods + + /// + /// Determines whether this equals the given one. + /// + /// The to compare. + /// true if the specified object is equal to the current object; otherwise, false. + public bool Equals(Display other) => Index == other.Index; + + /// + public override bool Equals(object? obj) => obj is Display other && Equals(other); + + /// + public override int GetHashCode() => Index; + + /// + /// Determines whether two are equal. + /// + /// The first value. + /// The second value. + /// true if the two specified displays are equal; otherwise, false. + public static bool operator ==(Display left, Display right) => left.Equals(right); + + /// + /// Determines whether two are not equal. + /// + /// The first value. + /// The second value. + /// true if the two specified displays are not equal; otherwise, false. + public static bool operator !=(Display left, Display right) => !(left == right); + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/GraphicsCard.cs b/ScreenCapture.NET/Model/GraphicsCard.cs index 2c4d27d..5b7ba3c 100644 --- a/ScreenCapture.NET/Model/GraphicsCard.cs +++ b/ScreenCapture.NET/Model/GraphicsCard.cs @@ -1,86 +1,85 @@ // ReSharper disable MemberCanBePrivate.Global -namespace ScreenCapture.NET +namespace ScreenCapture.NET; + +/// +/// Represents a graphics-card. +/// +public readonly struct GraphicsCard { + #region Properties & Fields + /// - /// Represents a graphics-card. + /// Gets the index of the . /// - public readonly struct GraphicsCard + public int Index { get; } + + /// + /// Gets the name of the . + /// + public string Name { get; } + + /// + /// Gets the vendor-id of the . + /// + public int VendorId { get; } + + /// + /// Gets the device-id of the . + /// + public int DeviceId { get; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The index of the . + /// The name of the . + /// The vendor-id of the . + /// The device-id of the . + public GraphicsCard(int index, string name, int vendorId, int deviceId) { - #region Properties & Fields - - /// - /// Gets the index of the . - /// - public int Index { get; } - - /// - /// Gets the name of the . - /// - public string Name { get; } - - /// - /// Gets the vendor-id of the . - /// - public int VendorId { get; } - - /// - /// Gets the device-id of the . - /// - public int DeviceId { get; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the struct. - /// - /// The index of the . - /// The name of the . - /// The vendor-id of the . - /// The device-id of the . - 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 - - /// - /// Determines whether this equals the given one. - /// - /// The to compare. - /// true if the specified object is equal to the current object; otherwise, false. - public bool Equals(GraphicsCard other) => Index == other.Index; - - /// - public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other); - - /// - public override int GetHashCode() => Index; - - /// - /// Determines whether two are equal. - /// - /// The first value. - /// The second value. - /// true if the two specified graphics-cards are equal; otherwise, false. - public static bool operator ==(GraphicsCard left, GraphicsCard right) => left.Equals(right); - - /// - /// Determines whether two are not equal. - /// - /// The first value. - /// The second value. - /// true if the two specified graphics-cards are not equal; otherwise, false. - public static bool operator !=(GraphicsCard left, GraphicsCard right) => !(left == right); - - #endregion + this.Index = index; + this.Name = name; + this.VendorId = vendorId; + this.DeviceId = deviceId; } -} + + #endregion + + #region Methods + + /// + /// Determines whether this equals the given one. + /// + /// The to compare. + /// true if the specified object is equal to the current object; otherwise, false. + public bool Equals(GraphicsCard other) => Index == other.Index; + + /// + public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other); + + /// + public override int GetHashCode() => Index; + + /// + /// Determines whether two are equal. + /// + /// The first value. + /// The second value. + /// true if the two specified graphics-cards are equal; otherwise, false. + public static bool operator ==(GraphicsCard left, GraphicsCard right) => left.Equals(right); + + /// + /// Determines whether two are not equal. + /// + /// The first value. + /// The second value. + /// true if the two specified graphics-cards are not equal; otherwise, false. + public static bool operator !=(GraphicsCard left, GraphicsCard right) => !(left == right); + + #endregion +} \ No newline at end of file From 2614596807e0e96bd3813642bba7c71c555d445e Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 1 May 2022 14:37:35 +0200 Subject: [PATCH 4/6] Bumped version to 1.2.0 --- ScreenCapture.NET/ScreenCapture.NET.csproj | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj index a7cb920..2baac8f 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -16,8 +16,8 @@ ScreenCapture.NET Vortice based Screen-Capturing Vortice based Screen-Capturing using Desktop Duplication - Copyright © Darth Affe 2021 - Copyright © Darth Affe 2021 + Copyright © Darth Affe 2022 + Copyright © Darth Affe 2022 icon.png https://github.com/DarthAffe/ScreenCapture.NET LGPL-2.1-only @@ -26,15 +26,13 @@ True - - Fixed problems with capturing inside WPF-threads. - - Removed the need to call 'DPIAwareness.Initalize()' in default configuration - - Added .NET 6 support - - Updated Vortice to 1.9.143 + - Added IScreenCapture.UpdateCaptureZone to modify existing CaptureZones without having to remove and add them again. + (This has performance benefits when only X and/or Y is changed, changes to width, height and downscale are internally still reinitializing the zone.) - 1.1.0 - 1.1.0 - 1.1.0 + 1.2.0 + 1.2.0 + 1.2.0 ..\bin\ true From 2a4dbd8fe56e9c0bf4a408f02192c6eea48202af Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 2 May 2022 22:21:10 +0200 Subject: [PATCH 5/6] Fixed ambiguous equals in Display --- ScreenCapture.NET/Model/Display.cs | 10 ++++++---- ScreenCapture.NET/ScreenCapture.NET.csproj | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ScreenCapture.NET/Model/Display.cs b/ScreenCapture.NET/Model/Display.cs index 7c749aa..bbf67a0 100644 --- a/ScreenCapture.NET/Model/Display.cs +++ b/ScreenCapture.NET/Model/Display.cs @@ -1,5 +1,7 @@ // ReSharper disable MemberCanBePrivate.Global +using System; + namespace ScreenCapture.NET; /// @@ -58,20 +60,20 @@ public readonly struct Display #endregion #region Methods - + /// /// Determines whether this equals the given one. /// /// The to compare. /// true if the specified object is equal to the current object; otherwise, false. - public bool Equals(Display other) => Index == other.Index; + public bool Equals(Display other) => (Index == other.Index) && GraphicsCard.Equals(other.GraphicsCard); /// public override bool Equals(object? obj) => obj is Display other && Equals(other); /// - public override int GetHashCode() => Index; - + public override int GetHashCode() => HashCode.Combine(Index, GraphicsCard); + /// /// Determines whether two are equal. /// diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj index 2baac8f..6923e83 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -26,8 +26,9 @@ True - - Added IScreenCapture.UpdateCaptureZone to modify existing CaptureZones without having to remove and add them again. + - Added IScreenCapture.UpdateCaptureZone to modify existing CaptureZones without having to remove and add them again. (This has performance benefits when only X and/or Y is changed, changes to width, height and downscale are internally still reinitializing the zone.) + - Fixed ambiguous equals in Display 1.2.0 From 8aed47d42fc33b21172edb96610ce84121fc219c Mon Sep 17 00:00:00 2001 From: DarthAffe Date: Tue, 3 May 2022 21:17:03 +0200 Subject: [PATCH 6/6] Added UpdateCaptureZone to readme --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69eb0d6..b01077f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ IEnumerable displays = screenCaptureService.GetDisplays(graphicsCards.F // Create a screen-capture for all screens you want to capture IScreenCapture screenCapture = screenCaptureService.GetScreenCapture(displays.First()); -// Register the regions you want to capture om the screen +// Register the regions you want to capture on the screen // Capture the whole screen CaptureZone fullscreen = screenCapture.RegisterCaptureZone(0, 0, screenCapture.Display.Width, screenCapture.Display.Height); // Capture a 100x100 region at the top left and scale it down to 50x50 @@ -54,4 +54,13 @@ lock (fullscreen.Buffer) } } } -``` \ No newline at end of file + +// Move the top left zone more towards the center +// Using the Update-method allows to move the zone without having to allocate +// new buffers and textures which yields a good performance gain if done at high framerates. +screenCapture.UpdateCaptureZone(topLeft, x: 100, y: 200); + +// Note that resizing the zone is also possible but currently reinitializes the zone +// -> no performance gain compared to removing and readding the zone. +screenCapture.UpdateCaptureZone(topLeft, width: 20, height: 20); +```