diff --git a/ScreenCapture.NET.sln.DotSettings b/ScreenCapture.NET.sln.DotSettings
index 8e3f720..4097b57 100644
--- a/ScreenCapture.NET.sln.DotSettings
+++ b/ScreenCapture.NET.sln.DotSettings
@@ -1,3 +1,4 @@
+ BGRA
DPI
DX
\ No newline at end of file
diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs
index 8444acc..da40620 100644
--- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs
+++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Threading;
using SharpGen.Runtime;
using Vortice.Direct3D;
@@ -18,7 +18,7 @@ namespace ScreenCapture.NET;
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
///
// ReSharper disable once InconsistentNaming
-public sealed class DX11ScreenCapture : IScreenCapture
+public sealed class DX11ScreenCapture : AbstractScreenCapture
{
#region Constants
@@ -30,8 +30,6 @@ public sealed class DX11ScreenCapture : IScreenCapture
FeatureLevel.Level_10_0
};
- private const int BPP = 4;
-
#endregion
#region Properties & Fields
@@ -39,14 +37,10 @@ public sealed class DX11ScreenCapture : IScreenCapture
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
+ /// 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;
@@ -59,14 +53,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
private ID3D11DeviceContext? _context;
private ID3D11Texture2D? _captureTexture;
- private readonly Dictionary _captureZones = new();
-
- #endregion
-
- #region Events
-
- ///
- public event EventHandler? Updated;
+ private readonly Dictionary, ZoneTextures> _textures = new();
#endregion
@@ -82,9 +69,9 @@ public sealed class DX11ScreenCapture : IScreenCapture
/// 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)
+ : base(display)
{
this._factory = factory;
- this.Display = display;
this._useNewDuplicationAdapter = useNewDuplicationAdapter;
Restart();
@@ -94,8 +81,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
#region Methods
- ///
- public bool CaptureScreen()
+ protected override bool PerformScreenCapture()
{
bool result = false;
lock (_captureLock)
@@ -142,83 +128,68 @@ public sealed class DX11ScreenCapture : IScreenCapture
catch { Thread.Sleep(100); }
}
}
- catch { /**/ }
-
- try
- {
- UpdateZones();
- }
- catch { /**/ }
-
- try
- {
- Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
- }
- catch { /**/ }
-
- return result;
}
+
+ return result;
}
- private void UpdateZones()
+ protected override void PerformCaptureZoneUpdate(CaptureZone captureZone)
{
if (_context == null) return;
- lock (_captureZones)
+ lock (_textures)
{
- foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested))
+ if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
+
+ if (textures.ScalingTexture != null)
{
- if (scalingTexture != null)
- {
- _context.CopySubresourceRegion(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);
- lock (captureZone.Buffer)
- {
- Span source = mapSource.AsSpan(mapSource.RowPitch * captureZone.Height);
- switch (Display.Rotation)
- {
- case Rotation.Rotation90:
- CopyRotate90(source, mapSource.RowPitch, captureZone);
- break;
-
- case Rotation.Rotation180:
- CopyRotate180(source, mapSource.RowPitch, captureZone);
- break;
-
- case Rotation.Rotation270:
- CopyRotate270(source, mapSource.RowPitch, captureZone);
- break;
-
- default:
- CopyRotate0(source, mapSource.RowPitch, captureZone);
- break;
- }
- }
-
- _context.Unmap(stagingTexture, 0);
- captureZone.SetUpdated();
+ _context.CopySubresourceRegion(textures.ScalingTexture, 0, 0, 0, 0, _captureTexture, 0,
+ new Box(textures.X, textures.Y, 0,
+ textures.X + textures.UnscaledWidth,
+ textures.Y + textures.UnscaledHeight, 1));
+ _context.GenerateMips(textures.ScalingTextureView);
+ _context.CopySubresourceRegion(textures.StagingTexture, 0, 0, 0, 0, textures.ScalingTexture, captureZone.DownscaleLevel);
}
+ else
+ _context.CopySubresourceRegion(textures.StagingTexture, 0, 0, 0, 0, _captureTexture, 0,
+ new Box(textures.X, textures.Y, 0,
+ textures.X + textures.UnscaledWidth,
+ textures.Y + textures.UnscaledHeight, 1));
+
+ MappedSubresource mapSource = _context.Map(textures.StagingTexture, 0, MapMode.Read, MapFlags.None);
+ using IDisposable @lock = captureZone.Image.Lock();
+ {
+ Span source = mapSource.AsSpan(mapSource.RowPitch * textures.Height);
+ switch (Display.Rotation)
+ {
+ case Rotation.Rotation90:
+ CopyRotate90(source, mapSource.RowPitch, captureZone);
+ break;
+
+ case Rotation.Rotation180:
+ CopyRotate180(source, mapSource.RowPitch, captureZone);
+ break;
+
+ case Rotation.Rotation270:
+ CopyRotate270(source, mapSource.RowPitch, captureZone);
+ break;
+
+ default:
+ CopyRotate0(source, mapSource.RowPitch, captureZone);
+ break;
+ }
+ }
+
+ _context.Unmap(textures.StagingTexture, 0);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRotate0(in Span source, int sourceStride, in CaptureZone captureZone)
+ private static void CopyRotate0(in Span source, int sourceStride, in CaptureZone captureZone)
{
- int height = captureZone.Height;
- int stride = captureZone.Stride;
- Span target = captureZone.Buffer.AsSpan();
+ int height = captureZone.Image.Height;
+ int stride = captureZone.Image.Stride;
+ Span target = captureZone.Image.Raw;
for (int y = 0; y < height; y++)
{
@@ -230,98 +201,78 @@ public sealed class DX11ScreenCapture : IScreenCapture
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRotate90(in Span source, int sourceStride, in CaptureZone captureZone)
+ private static void CopyRotate90(in Span source, int sourceStride, in CaptureZone captureZone)
{
- int width = captureZone.Width;
- int height = captureZone.Height;
- Span target = captureZone.Buffer.AsSpan();
+ int width = captureZone.Image.Width;
+ int height = captureZone.Image.Height;
+ int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
+ Span target = captureZone.Image.Pixels;
- for (int y = 0; y < height; y++)
- for (int x = 0; x < width; x++)
- {
- int sourceOffset = ((y * sourceStride) + (x * BPP));
- int targetOffset = ((x * height) + ((height - 1) - y)) * BPP;
-
- target[targetOffset] = source[sourceOffset];
- target[targetOffset + 1] = source[sourceOffset + 1];
- target[targetOffset + 2] = source[sourceOffset + 2];
- target[targetOffset + 3] = source[sourceOffset + 3];
- }
+ for (int x = 0; x < width; x++)
+ {
+ Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine));
+ for (int y = 0; y < src.Length; y++)
+ target[(y * width) + (width - x - 1)] = src[y];
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRotate180(in Span source, int sourceStride, in CaptureZone captureZone)
+ private static void CopyRotate180(in Span source, int sourceStride, in CaptureZone captureZone)
{
- int width = captureZone.Width;
- int height = captureZone.Height;
- int stride = captureZone.Stride;
- Span target = captureZone.Buffer.AsSpan();
+ int width = captureZone.Image.Width;
+ int height = captureZone.Image.Height;
+ int bpp = captureZone.Image.ColorFormat.BytesPerPixel;
+ int usedBytesPerLine = width * bpp;
+ Span target = captureZone.Image.Pixels;
for (int y = 0; y < height; y++)
- for (int x = 0; x < width; x++)
+ {
+ Span src = MemoryMarshal.Cast(source.Slice(y * sourceStride, usedBytesPerLine));
+ for (int x = 0; x < src.Length; x++)
{
- int sourceOffset = ((y * sourceStride) + (x * BPP));
- int targetOffset = target.Length - ((y * stride) + (x * BPP)) - 1;
-
- target[targetOffset - 3] = source[sourceOffset];
- target[targetOffset - 2] = source[sourceOffset + 1];
- target[targetOffset - 1] = source[sourceOffset + 2];
- target[targetOffset] = source[sourceOffset + 3];
+ target[((height - y - 1) * width) + (width - x - 1)] = src[x];
}
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRotate270(in Span source, int sourceStride, in CaptureZone captureZone)
+ private static void CopyRotate270(in Span source, int sourceStride, in CaptureZone captureZone)
{
- int width = captureZone.Width;
- int height = captureZone.Height;
- Span target = captureZone.Buffer.AsSpan();
+ int width = captureZone.Image.Width;
+ int height = captureZone.Image.Height;
+ int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel;
+ Span target = captureZone.Image.Pixels;
- for (int y = 0; y < height; y++)
- for (int x = 0; x < width; x++)
- {
- int sourceOffset = ((y * sourceStride) + (x * BPP));
- int targetOffset = ((((width - 1) - x) * height) + y) * BPP;
-
- target[targetOffset] = source[sourceOffset];
- target[targetOffset + 1] = source[sourceOffset + 1];
- target[targetOffset + 2] = source[sourceOffset + 2];
- target[targetOffset + 3] = source[sourceOffset + 3];
- }
+ for (int x = 0; x < width; x++)
+ {
+ Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine));
+ for (int y = 0; y < src.Length; y++)
+ target[((height - y - 1) * width) + x] = src[y];
+ }
}
///
- public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
+ public override CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
{
- ValidateCaptureZoneAndThrow(x, y, width, height);
+ CaptureZone captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel);
- if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
- (x, y, width, height) = (y, x, height, width);
-
- int unscaledWidth = width;
- int unscaledHeight = height;
- (width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel);
-
- byte[] buffer = new byte[width * height * BPP];
-
- CaptureZone captureZone = new(_indexCounter++, x, y, width, height, BPP, downscaleLevel, unscaledWidth, unscaledHeight, buffer);
- lock (_captureZones)
+ lock (_textures)
InitializeCaptureZone(captureZone);
return captureZone;
}
///
- public bool UnregisterCaptureZone(CaptureZone captureZone)
+ public override bool UnregisterCaptureZone(CaptureZone captureZone)
{
- lock (_captureZones)
+ if (!base.UnregisterCaptureZone(captureZone)) return false;
+
+ lock (_textures)
{
- if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data))
+ if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
{
- _captureZones.Remove(captureZone);
- data.stagingTexture.Dispose();
- data.scalingTexture?.Dispose();
- data._scalingTextureView?.Dispose();
+ textures.Dispose();
+ _textures.Remove(captureZone);
return true;
}
@@ -331,88 +282,66 @@ public sealed class DX11ScreenCapture : IScreenCapture
}
///
- public void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
+ public override void UpdateCaptureZone(CaptureZone 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);
-
- if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
- (newX, newY, newUnscaledWidth, newUnscaledHeight) = (newY, newX, newUnscaledHeight, newUnscaledWidth);
-
- captureZone.X = newX;
- captureZone.Y = newY;
+ base.UpdateCaptureZone(captureZone, x, y, width, height, downscaleLevel);
//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, newDownscaleLevel) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
- lock (_captureZones)
+ lock (_textures)
{
- 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);
+ if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
+ {
+ textures.Dispose();
+ InitializeCaptureZone(captureZone);
+ }
}
}
}
- private (int width, int height, int downscaleLevel) CalculateScaledSize(int width, int height, int downscaleLevel)
- {
- if (downscaleLevel > 0)
- for (int i = 0; i < downscaleLevel; i++)
- {
- if ((width <= 1) && (height <= 1))
- {
- downscaleLevel = i;
- break;
- }
-
- width /= 2;
- height /= 2;
- }
-
- if (width < 1) width = 1;
- if (height < 1) height = 1;
-
- return (width, height, downscaleLevel);
- }
-
- private void ValidateCaptureZoneAndThrow(int x, int y, int width, int height)
+ protected override void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel)
{
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");
+ base.ValidateCaptureZoneAndThrow(x, y, width, height, downscaleLevel);
}
- private void InitializeCaptureZone(in CaptureZone captureZone)
+ private void InitializeCaptureZone(in CaptureZone captureZone)
{
+ int x;
+ int y;
+ int width;
+ int height;
+ int unscaledWidth;
+ int unscaledHeight;
+
+ if (captureZone.Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
+ {
+ x = captureZone.Y;
+ y = captureZone.X;
+ width = captureZone.Height;
+ height = captureZone.Width;
+ unscaledWidth = captureZone.UnscaledHeight;
+ unscaledHeight = captureZone.UnscaledWidth;
+ }
+ else
+ {
+ x = captureZone.X;
+ y = captureZone.Y;
+ width = captureZone.Width;
+ height = captureZone.Height;
+ unscaledWidth = captureZone.UnscaledWidth;
+ unscaledHeight = captureZone.UnscaledHeight;
+ }
+
Texture2DDescription stagingTextureDesc = new()
{
CPUAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
- Width = captureZone.Width,
- Height = captureZone.Height,
+ Width = width,
+ Height = height,
MiscFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
@@ -430,8 +359,8 @@ public sealed class DX11ScreenCapture : IScreenCapture
CPUAccessFlags = CpuAccessFlags.None,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm,
- Width = captureZone.UnscaledWidth,
- Height = captureZone.UnscaledHeight,
+ Width = unscaledWidth,
+ Height = unscaledHeight,
MiscFlags = ResourceOptionFlags.GenerateMips,
MipLevels = captureZone.DownscaleLevel + 1,
ArraySize = 1,
@@ -442,95 +371,134 @@ public sealed class DX11ScreenCapture : IScreenCapture
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
}
- _captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView);
+ _textures[captureZone] = new ZoneTextures(x, y, width, height, unscaledWidth, unscaledHeight, stagingTexture, scalingTexture, scalingTextureView);
}
///
- public void Restart()
+ public override void Restart()
{
+ base.Restart();
+
lock (_captureLock)
- {
- try
+ lock (_textures)
{
- 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();
-
- int width = Display.Width;
- int height = Display.Height;
- if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
- (width, height) = (height, width);
-
- Texture2DDescription captureTextureDesc = new()
+ try
{
- CPUAccessFlags = CpuAccessFlags.None,
- BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
- Format = Format.B8G8R8A8_UNorm,
- Width = width,
- Height = height,
- MiscFlags = ResourceOptionFlags.None,
- MipLevels = 1,
- ArraySize = 1,
- SampleDescription = { Count = 1, Quality = 0 },
- Usage = ResourceUsage.Default
- };
- _captureTexture = _device.CreateTexture2D(captureTextureDesc);
+ foreach (ZoneTextures textures in _textures.Values)
+ textures.Dispose();
+ _textures.Clear();
- lock (_captureZones)
- {
- foreach (CaptureZone captureZone in captureZones)
+ DisposeDX();
+
+ 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();
+
+ int width = Display.Width;
+ int height = Display.Height;
+ if (Display.Rotation is Rotation.Rotation90 or Rotation.Rotation270)
+ (width, height) = (height, width);
+
+ Texture2DDescription captureTextureDesc = new()
+ {
+ CPUAccessFlags = CpuAccessFlags.None,
+ BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
+ Format = Format.B8G8R8A8_UNorm,
+ Width = width,
+ Height = height,
+ MiscFlags = ResourceOptionFlags.None,
+ MipLevels = 1,
+ ArraySize = 1,
+ SampleDescription = { Count = 1, Quality = 0 },
+ Usage = ResourceUsage.Default
+ };
+ _captureTexture = _device.CreateTexture2D(captureTextureDesc);
+
+ foreach (CaptureZone captureZone in CaptureZones)
InitializeCaptureZone(captureZone);
- }
- if (_useNewDuplicationAdapter)
- _duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
- else
- _duplicatedOutput = output.DuplicateOutput(_device);
+ if (_useNewDuplicationAdapter)
+ _duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
+ else
+ _duplicatedOutput = output.DuplicateOutput(_device);
+ }
+ catch { DisposeDX(); }
}
- catch { Dispose(false); }
- }
}
- ///
- public void Dispose() => Dispose(true);
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ DisposeDX();
+ }
- private void Dispose(bool removeCaptureZones)
+ private void DisposeDX()
{
try
{
- lock (_captureLock)
- {
- try { _duplicatedOutput?.Dispose(); } catch { /**/ }
- _duplicatedOutput = null;
+ try { _duplicatedOutput?.Dispose(); } catch { /**/ }
+ try { _output?.Dispose(); } catch { /**/ }
+ try { _context?.Dispose(); } catch { /**/ }
+ try { _device?.Dispose(); } catch { /**/ }
+ try { _captureTexture?.Dispose(); } catch { /**/ }
- 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;
- }
+ _duplicatedOutput = null;
+ _context = null;
+ _captureTexture = null;
}
catch { /**/ }
}
#endregion
+
+ private sealed class ZoneTextures : IDisposable
+ {
+ #region Properties & Fields
+
+ public int X { get; }
+ public int Y { get; }
+ public int Width { get; }
+ public int Height { get; }
+ public int UnscaledWidth { get; }
+ public int UnscaledHeight { get; }
+
+ public ID3D11Texture2D StagingTexture { get; }
+ public ID3D11Texture2D? ScalingTexture { get; }
+ public ID3D11ShaderResourceView? ScalingTextureView { get; }
+
+ #endregion
+
+ #region Constructors
+
+ public ZoneTextures(int x, int y, int width, int height, int unscaledWidth, int unscaledHeight,
+ ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)
+ {
+ this.X = x;
+ this.Y = y;
+ this.Width = width;
+ this.Height = height;
+ this.UnscaledWidth = unscaledWidth;
+ this.UnscaledHeight = unscaledHeight;
+ this.StagingTexture = stagingTexture;
+ this.ScalingTexture = scalingTexture;
+ this.ScalingTextureView = scalingTextureView;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void Dispose()
+ {
+ StagingTexture.Dispose();
+ ScalingTexture?.Dispose();
+ ScalingTextureView?.Dispose();
+ }
+
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs b/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs
index 79a023e..28d0fce 100644
--- a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs
+++ b/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs
@@ -59,7 +59,7 @@ public class DX11ScreenCaptureService : IScreenCaptureService
}
}
- private Rotation GetRotation(ModeRotation rotation) => rotation switch
+ private static Rotation GetRotation(ModeRotation rotation) => rotation switch
{
ModeRotation.Rotate90 => Rotation.Rotation90,
ModeRotation.Rotate180 => Rotation.Rotation180,
@@ -68,7 +68,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService
};
///
- public IScreenCapture GetScreenCapture(Display display)
+ IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
+ public DX11ScreenCapture GetScreenCapture(Display display)
{
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
new file mode 100644
index 0000000..61af70a
--- /dev/null
+++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ScreenCapture.NET;
+
+public abstract class AbstractScreenCapture : IScreenCapture
+ where TColor : struct, IColor
+{
+ #region Properties & Fields
+
+ private bool _isDisposed;
+ private int _indexCounter = 0;
+
+ protected HashSet> CaptureZones { get; } = new();
+
+ public Display Display { get; }
+
+ #endregion
+
+ #region Events
+
+ public event EventHandler? Updated;
+
+ #endregion
+
+ #region Constructors
+
+ protected AbstractScreenCapture(Display display)
+ {
+ this.Display = display;
+ }
+
+ ~AbstractScreenCapture() => Dispose(false);
+
+ #endregion
+
+ #region Methods
+
+ public virtual bool CaptureScreen()
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ bool result;
+
+ try
+ {
+ result = PerformScreenCapture();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ foreach (CaptureZone captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested))
+ {
+ try
+ {
+ PerformCaptureZoneUpdate(captureZone);
+ captureZone.SetUpdated();
+ }
+ catch { /* */ }
+ }
+
+ OnUpdated(result);
+
+ return result;
+ }
+
+ protected abstract bool PerformScreenCapture();
+
+ protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone);
+
+ protected virtual void OnUpdated(bool result)
+ {
+ try
+ {
+ Updated?.Invoke(this, new ScreenCaptureUpdatedEventArgs(result));
+ }
+ catch { /**/ }
+ }
+
+ ICaptureZone IScreenCapture.RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel) => RegisterCaptureZone(x, y, width, height, downscaleLevel);
+ public virtual CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ lock (CaptureZones)
+ {
+ ValidateCaptureZoneAndThrow(x, y, width, height, downscaleLevel);
+
+ int unscaledWidth = width;
+ int unscaledHeight = height;
+ (width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel);
+
+#if NET7_0_OR_GREATER
+ CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage(width, height, TColor.ColorFormat));
+#else
+ CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, new ScreenImage(width, height, IColor.GetColorFormat()));
+#endif
+ CaptureZones.Add(captureZone);
+
+ return captureZone;
+ }
+ }
+
+ protected virtual void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel)
+ {
+ if (x < 0) throw new ArgumentException("x < 0");
+ if (y < 0) throw new ArgumentException("y < 0");
+ if (width <= 0) throw new ArgumentException("with <= 0");
+ if (height <= 0) throw new ArgumentException("height <= 0");
+ if ((x + width) > Display.Width) throw new ArgumentException("x + width > Display width");
+ if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height");
+ }
+
+ protected virtual (int width, int height, int downscaleLevel) CalculateScaledSize(int width, int height, int downscaleLevel)
+ {
+ if (downscaleLevel > 0)
+ for (int i = 0; i < downscaleLevel; i++)
+ {
+ if ((width <= 1) && (height <= 1))
+ {
+ downscaleLevel = i;
+ break;
+ }
+
+ width /= 2;
+ height /= 2;
+ }
+
+ if (width < 1) width = 1;
+ if (height < 1) height = 1;
+
+ return (width, height, downscaleLevel);
+ }
+
+ bool IScreenCapture.UnregisterCaptureZone(ICaptureZone captureZone) => UnregisterCaptureZone(captureZone as CaptureZone ?? throw new ArgumentException("Invalid capture-zone."));
+ public virtual bool UnregisterCaptureZone(CaptureZone captureZone)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ return CaptureZones.Remove(captureZone);
+ }
+
+ void IScreenCapture.UpdateCaptureZone(ICaptureZone captureZone, int? x, int? y, int? width, int? height, int? downscaleLevel)
+ => UpdateCaptureZone(captureZone as CaptureZone ?? throw new ArgumentException("Invalid capture-zone."), x, y, width, height, downscaleLevel);
+ public virtual void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ lock (CaptureZones)
+ {
+ if (!CaptureZones.Contains(captureZone))
+ throw new ArgumentException("The capture zone is not registered to this ScreenCapture", nameof(captureZone));
+
+ int newX = x ?? captureZone.X;
+ int newY = y ?? captureZone.Y;
+ int newUnscaledWidth = width ?? captureZone.UnscaledWidth;
+ int newUnscaledHeight = height ?? captureZone.UnscaledHeight;
+ int newDownscaleLevel = downscaleLevel ?? captureZone.DownscaleLevel;
+
+ ValidateCaptureZoneAndThrow(newX, newY, newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
+
+ captureZone.X = newX;
+ captureZone.Y = newY;
+
+ if ((width != null) || (height != null) || (downscaleLevel != null))
+ {
+ (int newWidth, int newHeight, newDownscaleLevel) = CalculateScaledSize(newUnscaledWidth, newUnscaledHeight, newDownscaleLevel);
+
+ captureZone.UnscaledWidth = newUnscaledWidth;
+ captureZone.UnscaledHeight = newUnscaledHeight;
+ captureZone.Width = newWidth;
+ captureZone.Height = newHeight;
+ captureZone.DownscaleLevel = newDownscaleLevel;
+ captureZone.Image.Resize(newWidth, newHeight);
+ }
+ }
+ }
+
+ public virtual void Restart()
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ Dispose(true);
+ }
+ catch { /* don't throw in dispose! */ }
+
+ GC.SuppressFinalize(this);
+
+ _isDisposed = true;
+ }
+
+ protected virtual void Dispose(bool disposing) { }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/IScreenCapture.cs b/ScreenCapture.NET/Generic/IScreenCapture.cs
similarity index 90%
rename from ScreenCapture.NET/IScreenCapture.cs
rename to ScreenCapture.NET/Generic/IScreenCapture.cs
index 362836f..cc423bd 100644
--- a/ScreenCapture.NET/IScreenCapture.cs
+++ b/ScreenCapture.NET/Generic/IScreenCapture.cs
@@ -32,14 +32,14 @@ public interface IScreenCapture : IDisposable
/// 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);
+ ICaptureZone 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);
+ bool UnregisterCaptureZone(ICaptureZone captureZone);
///
/// Updates the the given .
@@ -53,7 +53,7 @@ public interface IScreenCapture : IDisposable
/// 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);
+ void UpdateCaptureZone(ICaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null);
///
/// Restarts the .
diff --git a/ScreenCapture.NET/IScreenCaptureService.cs b/ScreenCapture.NET/Generic/IScreenCaptureService.cs
similarity index 100%
rename from ScreenCapture.NET/IScreenCaptureService.cs
rename to ScreenCapture.NET/Generic/IScreenCaptureService.cs
diff --git a/ScreenCapture.NET/Model/BlackBarDetection.cs b/ScreenCapture.NET/Model/BlackBarDetection.cs
index 254a57c..1e93712 100644
--- a/ScreenCapture.NET/Model/BlackBarDetection.cs
+++ b/ScreenCapture.NET/Model/BlackBarDetection.cs
@@ -11,7 +11,7 @@ public sealed class BlackBarDetection
{
#region Properties & Fields
- private readonly CaptureZone _captureZone;
+ private readonly ICaptureZone _captureZone;
private int? _top;
///
@@ -55,7 +55,7 @@ public sealed class BlackBarDetection
#region Constructors
- internal BlackBarDetection(CaptureZone captureZone)
+ internal BlackBarDetection(ICaptureZone captureZone)
{
this._captureZone = captureZone;
}
@@ -77,62 +77,62 @@ public sealed class BlackBarDetection
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;
- }
+ //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;
+ //}
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;
- }
+ //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;
- }
+ //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;
- }
+ //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;
}
diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs
index e33d59e..5be69a0 100644
--- a/ScreenCapture.NET/Model/CaptureZone.cs
+++ b/ScreenCapture.NET/Model/CaptureZone.cs
@@ -7,15 +7,18 @@ namespace ScreenCapture.NET;
///
/// Represents a duplicated region on the screen.
///
-public sealed class CaptureZone
+public sealed class CaptureZone : ICaptureZone
+ where TColor : struct, IColor
{
#region Properties & Fields
///
- /// Gets the unique id of this .
+ /// Gets the unique id of this .
///
public int Id { get; }
+ public Display Display { get; }
+
///
/// Gets the x-location of the region on the screen.
///
@@ -51,42 +54,25 @@ public sealed class CaptureZone
///
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; }
+ IScreenImage ICaptureZone.Image => Image;
+ public ScreenImage Image { 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.
+ /// 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.
+ /// 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.
+ /// Occurs when the is updated.
///
public event EventHandler? Updated;
@@ -95,9 +81,9 @@ public sealed class CaptureZone
#region Constructors
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The unique id of this .
+ /// 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.
@@ -107,20 +93,18 @@ public sealed class CaptureZone
/// 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)
+ internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, ScreenImage image)
{
this.Id = id;
+ this.Display = display;
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);
+ this.Image = image;
}
#endregion
@@ -128,32 +112,30 @@ public sealed class CaptureZone
#region Methods
///
- /// Requests to update this when the next frame is captured.
+ /// 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 !
+ /// Marks the as updated.
///
- public void SetUpdated()
+ internal void SetUpdated()
{
IsUpdateRequested = false;
- BlackBars.InvalidateCache();
Updated?.Invoke(this, EventArgs.Empty);
}
///
- /// Determines whether this equals the given one.
+ /// Determines whether this equals the given one.
///
- /// The to compare.
+ /// 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 bool Equals(CaptureZone other) => Id == other.Id;
///
- public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
+ public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
///
public override int GetHashCode() => Id;
diff --git a/ScreenCapture.NET/Model/ColorFormat.cs b/ScreenCapture.NET/Model/ColorFormat.cs
new file mode 100644
index 0000000..c376580
--- /dev/null
+++ b/ScreenCapture.NET/Model/ColorFormat.cs
@@ -0,0 +1,43 @@
+namespace ScreenCapture.NET;
+
+public readonly struct ColorFormat
+{
+ #region Instances
+
+ public static readonly ColorFormat BGRA = new(1, 4);
+
+ #endregion
+
+ #region Properties & Fields
+
+ public readonly int Id;
+ public readonly int BytesPerPixel;
+
+ #endregion
+
+ #region Constructors
+
+ private ColorFormat(int id, int bytesPerPixel)
+ {
+ this.Id = id;
+ this.BytesPerPixel = bytesPerPixel;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public bool Equals(ColorFormat other) => Id == other.Id;
+ public override bool Equals(object? obj) => obj is ColorFormat other && Equals(other);
+
+ public override int GetHashCode() => Id;
+
+ #endregion
+
+ #region Operators
+
+ public static bool operator ==(ColorFormat left, ColorFormat right) => left.Equals(right);
+ public static bool operator !=(ColorFormat left, ColorFormat right) => !(left == right);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs
new file mode 100644
index 0000000..dabd01c
--- /dev/null
+++ b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs
@@ -0,0 +1,39 @@
+// ReSharper disable ConvertToAutoProperty
+
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture.NET;
+
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct ColorBGRA : IColor
+{
+ #region Properties & Fields
+
+#if NET7_0_OR_GREATER
+ public static ColorFormat ColorFormat => ColorFormat.BGRA;
+#endif
+
+ private readonly byte _b;
+ private readonly byte _g;
+ private readonly byte _r;
+ private readonly byte _a;
+
+ public byte B => _b;
+ public byte G => _g;
+ public byte R => _r;
+ public byte A => _a;
+
+ #endregion
+
+ #region Constructors
+
+ public ColorBGRA(byte b, byte g, byte r, byte a)
+ {
+ this._b = b;
+ this._g = g;
+ this._r = r;
+ this._a = a;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/Model/Colors/IColor.cs b/ScreenCapture.NET/Model/Colors/IColor.cs
new file mode 100644
index 0000000..43386ff
--- /dev/null
+++ b/ScreenCapture.NET/Model/Colors/IColor.cs
@@ -0,0 +1,22 @@
+namespace ScreenCapture.NET;
+
+public interface IColor
+{
+ byte R { get; }
+ byte G { get; }
+ byte B { get; }
+ byte A { get; }
+
+#if NET7_0_OR_GREATER
+ public static abstract ColorFormat ColorFormat { get; }
+#else
+ public static ColorFormat GetColorFormat()
+ where TColor : IColor
+ {
+ System.Type colorType = typeof(TColor);
+ if (colorType == typeof(ColorBGRA)) return ColorFormat.BGRA;
+
+ throw new System.ArgumentException($"Not ColorFormat registered for '{typeof(TColor).Name}'");
+ }
+#endif
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs
new file mode 100644
index 0000000..7f83100
--- /dev/null
+++ b/ScreenCapture.NET/Model/ICaptureZone.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace ScreenCapture.NET;
+
+public interface ICaptureZone
+{
+ ///
+ /// Gets the unique id of this .
+ ///
+ int Id { get; }
+ Display Display { get; }
+ ///
+ /// Gets the x-location of the region on the screen.
+ ///
+ int X { get; }
+ ///
+ /// Gets the y-location of the region on the screen.
+ ///
+ int Y { get; }
+ ///
+ /// Gets the width of the captured region.
+ ///
+ int Width { get; }
+ ///
+ /// Gets the height of the captured region.
+ ///
+ int Height { get; }
+ ///
+ /// 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.
+ ///
+ int DownscaleLevel { get; }
+ ///
+ /// Gets the original width of the region (this equals if is 0).
+ ///
+ int UnscaledWidth { get; }
+ ///
+ /// Gets the original height of the region (this equals if is 0).
+ ///
+ int UnscaledHeight { get; }
+
+ IScreenImage Image { get; }
+ ///
+ /// Gets or sets if the should be automatically updated on every captured frame.
+ ///
+ bool AutoUpdate { get; set; }
+ ///
+ /// Gets if an update for the is requested on the next captured frame.
+ ///
+ bool IsUpdateRequested { get; }
+ ///
+ /// Occurs when the is updated.
+ ///
+ event EventHandler? Updated;
+
+ ///
+ /// Requests to update this when the next frame is captured.
+ /// Only necessary if is set to false.
+ ///
+ void RequestUpdate();
+}
diff --git a/ScreenCapture.NET/Model/IScreenImage.cs b/ScreenCapture.NET/Model/IScreenImage.cs
new file mode 100644
index 0000000..4b87105
--- /dev/null
+++ b/ScreenCapture.NET/Model/IScreenImage.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ScreenCapture.NET;
+
+public interface IScreenImage
+{
+ Span Raw { get; }
+ int Width { get; }
+ int Height { get; }
+ int Stride { get; }
+ ColorFormat ColorFormat { get; }
+
+ IDisposable Lock();
+
+ IColor this[int x, int y] { get; }
+}
diff --git a/ScreenCapture.NET/Model/ScreenImage.cs b/ScreenCapture.NET/Model/ScreenImage.cs
new file mode 100644
index 0000000..9b6c47e
--- /dev/null
+++ b/ScreenCapture.NET/Model/ScreenImage.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace ScreenCapture.NET;
+
+public sealed class ScreenImage : IScreenImage
+ where TColor : struct, IColor
+{
+ #region Properties & Fields
+
+ private readonly object _lock = new();
+ private byte[] _buffer;
+
+ public Span Raw => _buffer;
+ public Span Pixels => MemoryMarshal.Cast(_buffer);
+
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public int Stride => Width * ColorFormat.BytesPerPixel;
+ public ColorFormat ColorFormat { get; }
+
+ #endregion
+
+ #region Indexer
+
+ IColor IScreenImage.this[int x, int y] => this[x, y];
+ public TColor this[int x, int y] => Pixels[(y * Width) + x];
+
+ #endregion
+
+ #region Constructors
+
+ internal ScreenImage(int width, int height, ColorFormat colorFormat)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.ColorFormat = colorFormat;
+
+ _buffer = new byte[width * height * colorFormat.BytesPerPixel];
+ }
+
+ #endregion
+
+ #region Methods
+
+ internal void Resize(int width, int height)
+ {
+ Width = width;
+ Height = height;
+
+ _buffer = new byte[width * height * ColorFormat.BytesPerPixel];
+ }
+
+ public IDisposable Lock()
+ {
+ Monitor.Enter(_lock);
+ return new UnlockDisposable(_lock);
+ }
+
+ #endregion
+
+ private class UnlockDisposable : IDisposable
+ {
+ #region Properties & Fields
+
+ private bool _disposed = false;
+ private readonly object _lock;
+
+ #endregion
+
+ #region Constructors
+
+ public UnlockDisposable(object @lock) => this._lock = @lock;
+
+ #endregion
+
+ #region Methods
+
+ public void Dispose()
+ {
+ if (_disposed) throw new ObjectDisposedException("The lock is already released");
+
+ Monitor.Exit(_lock);
+ _disposed = true;
+ }
+
+ #endregion
+ }
+
+ public readonly ref struct ScreemImageRow
+ {
+ private readonly Span _pixels;
+
+ public IColor this[int x] => _pixels[x];
+
+ public ScreemImageRow(Span pixels)
+ {
+ this._pixels = pixels;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj
index 49dfad2..8396d8a 100644
--- a/ScreenCapture.NET/ScreenCapture.NET.csproj
+++ b/ScreenCapture.NET/ScreenCapture.NET.csproj
@@ -41,6 +41,10 @@
snupkg
+
+ true
+
+
$(DefineConstants);TRACE;DEBUG
true
diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings
index 5cd2a56..3504048 100644
--- a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings
+++ b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings
@@ -1,5 +1,8 @@
+ True
True
True
+ True
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file