diff --git a/ScreenCapture.sln b/ScreenCapture.sln
new file mode 100644
index 0000000..eab2945
--- /dev/null
+++ b/ScreenCapture.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31025.194
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture", "ScreenCapture\ScreenCapture.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B5111031-6E65-4331-9E6E-A07165289860}
+ EndGlobalSection
+EndGlobal
diff --git a/ScreenCapture.sln.DotSettings b/ScreenCapture.sln.DotSettings
new file mode 100644
index 0000000..8e3f720
--- /dev/null
+++ b/ScreenCapture.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ DPI
+ DX
\ No newline at end of file
diff --git a/ScreenCapture/DPIAwareness.cs b/ScreenCapture/DPIAwareness.cs
new file mode 100644
index 0000000..4d8622d
--- /dev/null
+++ b/ScreenCapture/DPIAwareness.cs
@@ -0,0 +1,37 @@
+// ReSharper disable InconsistentNaming
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture
+{
+ 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
+ {
+ 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
+ }
+
+ public static void Initalize()
+ {
+ SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+ }
+ }
+}
diff --git a/ScreenCapture/DX11ScreenCapture.cs b/ScreenCapture/DX11ScreenCapture.cs
new file mode 100644
index 0000000..b60887a
--- /dev/null
+++ b/ScreenCapture/DX11ScreenCapture.cs
@@ -0,0 +1,337 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using SharpGen.Runtime;
+using Vortice.Direct3D;
+using Vortice.Direct3D11;
+using Vortice.DXGI;
+using Vortice.Mathematics;
+using MapFlags = Vortice.Direct3D11.MapFlags;
+using ResultCode = Vortice.DXGI.ResultCode;
+using Usage = Vortice.Direct3D11.Usage;
+
+namespace ScreenCapture
+{
+ // ReSharper disable once InconsistentNaming
+ public class DX11ScreenCapture : IScreenCapture
+ {
+ #region Constants
+
+ private static readonly FeatureLevel[] FEATURE_LEVELS =
+ {
+ FeatureLevel.Level_11_1,
+ FeatureLevel.Level_11_0,
+ FeatureLevel.Level_10_1,
+ FeatureLevel.Level_10_0
+ };
+
+ #endregion
+
+ #region Properties & Fields
+
+ private readonly object _captureLock = new();
+
+ private int _indexCounter = 0;
+
+ public Display Display { get; }
+
+ 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 Constructors
+
+ public DX11ScreenCapture(IDXGIFactory1 factory, Display display)
+ {
+ this._factory = factory;
+ this.Display = display;
+
+ Restart();
+ }
+
+ #endregion
+
+ #region Methods
+
+ public bool CaptureScreen()
+ {
+ bool result = false;
+ lock (_captureLock)
+ {
+ 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 { /**/ }
+
+ 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)
+ Marshal.Copy(sourcePtr, captureZone.Buffer, 0, captureZone.Buffer.Length);
+
+ _context.Unmap(stagingTexture, 0);
+ captureZone.SetUpdated();
+ }
+ }
+ }
+
+ 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");
+
+ int textureWidth = (int)Math.Ceiling(width / 32.0) * 32;
+ int textureHeight = (int)Math.Ceiling(height / 32.0) * 32;
+
+ int unscaledWidth = width;
+ int unscaledHeight = height;
+ if (downscaleLevel > 0)
+ for (int i = 0; i < downscaleLevel; i++)
+ {
+ width /= 2;
+ height /= 2;
+ }
+
+ if (width < 1) width = 1;
+ if (height < 1) height = 1;
+
+ int bufferWidth = (int)Math.Ceiling(width / 32.0) * 32;
+ int bufferHeight = (int)Math.Ceiling(height / 32.0) * 32;
+
+ byte[] buffer = new byte[bufferWidth * bufferHeight * 4];
+
+ CaptureZone captureZone = new(_indexCounter++, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, textureWidth, textureHeight, bufferWidth, bufferHeight, 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;
+ }
+ }
+
+ private void InitializeCaptureZone(in CaptureZone captureZone)
+ {
+ Texture2DDescription stagingTextureDesc = new()
+ {
+ CpuAccessFlags = CpuAccessFlags.Read,
+ BindFlags = BindFlags.None,
+ Format = Format.B8G8R8A8_UNorm,
+ Width = captureZone.BufferWidth,
+ Height = captureZone.BufferHeight,
+ OptionFlags = ResourceOptionFlags.None,
+ MipLevels = 1,
+ ArraySize = 1,
+ SampleDescription = { Count = 1, Quality = 0 },
+ Usage = Usage.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.CaptureWidth,
+ Height = captureZone.CaptureHeight,
+ OptionFlags = ResourceOptionFlags.GenerateMips,
+ MipLevels = captureZone.DownscaleLevel + 1,
+ ArraySize = 1,
+ SampleDescription = { Count = 1, Quality = 0 },
+ Usage = Usage.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 output1 = _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 = Usage.Default
+ };
+ _captureTexture = _device.CreateTexture2D(captureTextureDesc);
+
+ lock (_captureZones)
+ {
+ foreach (CaptureZone captureZone in captureZones)
+ InitializeCaptureZone(captureZone);
+ }
+
+ _duplicatedOutput = output1.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
+ }
+ 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
+ }
+}
diff --git a/ScreenCapture/DX11ScreenCaptureService.cs b/ScreenCapture/DX11ScreenCaptureService.cs
new file mode 100644
index 0000000..c24f82c
--- /dev/null
+++ b/ScreenCapture/DX11ScreenCaptureService.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using Vortice.DXGI;
+
+namespace ScreenCapture
+{
+ public class DX11ScreenCaptureService : IScreenCaptureService
+ {
+ #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();
+ }
+
+ #endregion
+ }
+}
diff --git a/ScreenCapture/IScreenCapture.cs b/ScreenCapture/IScreenCapture.cs
new file mode 100644
index 0000000..d8f7b32
--- /dev/null
+++ b/ScreenCapture/IScreenCapture.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace ScreenCapture
+{
+ public interface IScreenCapture : IDisposable
+ {
+ Display Display { get; }
+
+ bool CaptureScreen();
+ CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0);
+ bool UnregisterCaptureZone(CaptureZone captureZone);
+ void Restart();
+ }
+}
diff --git a/ScreenCapture/IScreenCaptureService.cs b/ScreenCapture/IScreenCaptureService.cs
new file mode 100644
index 0000000..8e686ff
--- /dev/null
+++ b/ScreenCapture/IScreenCaptureService.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace ScreenCapture
+{
+ public interface IScreenCaptureService : IDisposable
+ {
+ IEnumerable GetGraphicsCards();
+ IEnumerable GetDisplays(GraphicsCard graphicsCard);
+ IScreenCapture GetScreenCapture(Display display);
+ }
+}
diff --git a/ScreenCapture/Model/BlackBarDetection.cs b/ScreenCapture/Model/BlackBarDetection.cs
new file mode 100644
index 0000000..58053ae
--- /dev/null
+++ b/ScreenCapture/Model/BlackBarDetection.cs
@@ -0,0 +1,119 @@
+using System;
+
+namespace ScreenCapture
+{
+ public sealed class BlackBarDetection
+ {
+ #region Properties & Fields
+
+ private readonly CaptureZone _captureZone;
+
+ private int? _top;
+ public int Top => _top ??= CalculateTop();
+
+ private int? _bottom;
+ public int Bottom => _bottom ??= CalculateBottom();
+
+ private int? _left;
+ public int Left => _left ??= CalculateLeft();
+
+ private int? _right;
+ public int Right => _right ??= CalculateRight();
+
+ private int _theshold = 0;
+ public int Threshold
+ {
+ get => _theshold;
+ set
+ {
+ _theshold = value;
+ InvalidateCache();
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public BlackBarDetection(CaptureZone captureZone)
+ {
+ this._captureZone = captureZone;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void InvalidateCache()
+ {
+ _top = null;
+ _bottom = null;
+ _left = null;
+ _right = null;
+ }
+
+ private int CalculateTop()
+ {
+ int threshold = Threshold;
+ int stride = _captureZone.BufferWidth * 4;
+ int bytesPerRow = _captureZone.Width * 4;
+ for (int row = 0; row < _captureZone.Height; row++)
+ {
+ Span data = new(_captureZone.Buffer, row * stride, bytesPerRow);
+ for (int i = 0; i < data.Length; i += 4)
+ if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
+ return row;
+ }
+
+ return 0;
+ }
+
+ private int CalculateBottom()
+ {
+ int threshold = Threshold;
+ int stride = _captureZone.BufferWidth * 4;
+ int bytesPerRow = _captureZone.Width * 4;
+ for (int row = _captureZone.Height; row >= 0; row--)
+ {
+ Span data = new(_captureZone.Buffer, row * stride, bytesPerRow);
+ for (int i = 0; i < data.Length; i += 4)
+ if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
+ return _captureZone.Height - row;
+ }
+
+ return 0;
+ }
+
+ private int CalculateLeft()
+ {
+ int threshold = Threshold;
+ int stride = _captureZone.BufferWidth * 4;
+ 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.BufferWidth * 4;
+ byte[] buffer = _captureZone.Buffer;
+ for (int column = _captureZone.Width; 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 - column;
+ }
+
+ return 0;
+ }
+
+ #endregion
+ }
+}
diff --git a/ScreenCapture/Model/CaptureZone.cs b/ScreenCapture/Model/CaptureZone.cs
new file mode 100644
index 0000000..a57d8c1
--- /dev/null
+++ b/ScreenCapture/Model/CaptureZone.cs
@@ -0,0 +1,72 @@
+namespace ScreenCapture
+{
+ public sealed class CaptureZone
+ {
+ #region Properties & Fields
+
+ public int Id { get; }
+
+ public int X { get; }
+ public int Y { get; }
+ public int Width { get; }
+ public int Height { get; }
+
+ public int DownscaleLevel { get; }
+
+ public int UnscaledWidth { get; }
+ public int UnscaledHeight { get; }
+
+ public int CaptureWidth { get; }
+ public int CaptureHeight { get; }
+
+ public int BufferWidth { get; }
+ public int BufferHeight { get; }
+ public byte[] Buffer { get; }
+
+ public BlackBarDetection BlackBars { get; }
+
+ public bool AutoUpdate { get; set; } = true;
+ public bool IsUpdateRequested { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ public CaptureZone(int id, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, int captureWidth, int captureHeight, int bufferWidth, int bufferHeight, byte[] buffer)
+ {
+ this.Id = id;
+ this.X = x;
+ this.Y = y;
+ this.Width = width;
+ this.Height = height;
+ this.UnscaledWidth = unscaledWidth;
+ this.UnscaledHeight = unscaledHeight;
+ this.DownscaleLevel = downscaleLevel;
+ this.CaptureWidth = captureWidth;
+ this.CaptureHeight = captureHeight;
+ this.BufferWidth = bufferWidth;
+ this.BufferHeight = bufferHeight;
+ this.Buffer = buffer;
+
+ BlackBars = new BlackBarDetection(this);
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void RequestUpdate() => IsUpdateRequested = true;
+
+ public void SetUpdated()
+ {
+ IsUpdateRequested = false;
+ BlackBars.InvalidateCache();
+ }
+
+ public override int GetHashCode() => Id;
+ public bool Equals(CaptureZone other) => Id == other.Id;
+ public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ScreenCapture/Model/Display.cs b/ScreenCapture/Model/Display.cs
new file mode 100644
index 0000000..4783081
--- /dev/null
+++ b/ScreenCapture/Model/Display.cs
@@ -0,0 +1,38 @@
+namespace ScreenCapture
+{
+ public readonly struct Display
+ {
+ #region Properties & Fields
+
+ public int Index { get; }
+ public string DeviceName { get; }
+
+ public int Width { get; }
+ public int Height { get; }
+
+ public GraphicsCard GraphicsCard { get; }
+
+ #endregion
+
+ #region Constructors
+
+ 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
+
+ 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;
+
+ #endregion
+ }
+}
diff --git a/ScreenCapture/Model/GraphicsCard.cs b/ScreenCapture/Model/GraphicsCard.cs
new file mode 100644
index 0000000..7b3fbad
--- /dev/null
+++ b/ScreenCapture/Model/GraphicsCard.cs
@@ -0,0 +1,34 @@
+namespace ScreenCapture
+{
+ public readonly struct GraphicsCard
+ {
+ #region Properties & Fields
+
+ public int Index { get; }
+ public string Name { get; }
+ public int VendorId { get; }
+ public int DeviceId { get; }
+
+ #endregion
+
+ #region Constructors
+
+ 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
+
+ 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;
+
+ #endregion
+ }
+}
diff --git a/ScreenCapture/ScreenCapture.csproj b/ScreenCapture/ScreenCapture.csproj
new file mode 100644
index 0000000..e87033e
--- /dev/null
+++ b/ScreenCapture/ScreenCapture.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net5.0
+ enable
+ true
+
+
+
+ $(DefineConstants);TRACE;DEBUG
+ true
+ full
+ false
+
+
+
+ pdbonly
+ true
+ $(NoWarn);CS1591;CS1572;CS1573
+ $(DefineConstants);RELEASE
+
+
+
+
+
+
+
diff --git a/ScreenCapture/ScreenCapture.csproj.DotSettings b/ScreenCapture/ScreenCapture.csproj.DotSettings
new file mode 100644
index 0000000..dd7e081
--- /dev/null
+++ b/ScreenCapture/ScreenCapture.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file