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