From 8ce7db15e8d57bc8980ed03dadf9e56ae3f512fb Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 4 Sep 2023 19:55:57 +0200 Subject: [PATCH 01/18] Reworked capturing to fully detach the capture zone from the capturing itself and allow better handling of result-data (WIP) --- ScreenCapture.NET.sln.DotSettings | 1 + .../DirectX/DX11ScreenCapture.cs | 522 ++++++++---------- .../DirectX/DX11ScreenCaptureService.cs | 5 +- .../Generic/AbstractScreenCapture.cs | 206 +++++++ .../{ => Generic}/IScreenCapture.cs | 6 +- .../{ => Generic}/IScreenCaptureService.cs | 0 ScreenCapture.NET/Model/BlackBarDetection.cs | 80 +-- ScreenCapture.NET/Model/CaptureZone.cs | 64 +-- ScreenCapture.NET/Model/ColorFormat.cs | 43 ++ ScreenCapture.NET/Model/Colors/ColorBGRA.cs | 39 ++ ScreenCapture.NET/Model/Colors/IColor.cs | 22 + ScreenCapture.NET/Model/ICaptureZone.cs | 60 ++ ScreenCapture.NET/Model/IScreenImage.cs | 16 + ScreenCapture.NET/Model/ScreenImage.cs | 102 ++++ ScreenCapture.NET/ScreenCapture.NET.csproj | 4 + .../ScreenCapture.NET.csproj.DotSettings | 5 +- 16 files changed, 811 insertions(+), 364 deletions(-) create mode 100644 ScreenCapture.NET/Generic/AbstractScreenCapture.cs rename ScreenCapture.NET/{ => Generic}/IScreenCapture.cs (90%) rename ScreenCapture.NET/{ => Generic}/IScreenCaptureService.cs (100%) create mode 100644 ScreenCapture.NET/Model/ColorFormat.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorBGRA.cs create mode 100644 ScreenCapture.NET/Model/Colors/IColor.cs create mode 100644 ScreenCapture.NET/Model/ICaptureZone.cs create mode 100644 ScreenCapture.NET/Model/IScreenImage.cs create mode 100644 ScreenCapture.NET/Model/ScreenImage.cs 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 From 9f9f153da54c745c8f9111f77db2ba318b8b0978 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 00:39:04 +0200 Subject: [PATCH 02/18] Reworked result image (WIP) --- .../ReadOnlyRefEnumerable.cs | 266 +++++++++++++++++ .../DirectX/DX11ScreenCapture.cs | 61 ++-- .../Generic/AbstractScreenCapture.cs | 18 +- ScreenCapture.NET/Model/CaptureZone.cs | 112 +++++++- ScreenCapture.NET/Model/ICaptureZone.cs | 3 +- ScreenCapture.NET/Model/IScreenImage.cs | 16 -- ScreenCapture.NET/Model/Image.cs | 269 ++++++++++++++++++ ScreenCapture.NET/Model/ScreenImage.cs | 102 ------- ScreenCapture.NET/ScreenCapture.NET.csproj | 4 - 9 files changed, 672 insertions(+), 179 deletions(-) create mode 100644 ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs delete mode 100644 ScreenCapture.NET/Model/IScreenImage.cs create mode 100644 ScreenCapture.NET/Model/Image.cs delete mode 100644 ScreenCapture.NET/Model/ScreenImage.cs diff --git a/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs b/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs new file mode 100644 index 0000000..6dda976 --- /dev/null +++ b/ScreenCapture.NET/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs @@ -0,0 +1,266 @@ +// DarthAffe 05.09.2023: Based on https://github.com/CommunityToolkit/dotnet/blob/b0d6c4f9c0cfb5d860400abb00b0ca1b3e94dfa4/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable%7BT%7D.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +/// +/// A that iterates readonly items from arbitrary memory locations. +/// +/// The type of items to enumerate. +public readonly ref struct ReadOnlyRefEnumerable +{ + #region Properties & Fields + + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly ReadOnlySpan _span; + + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int _step; + + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span.Length; + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) throw new IndexOutOfRangeException(); + + ref T r0 = ref MemoryMarshal.GetReference(_span); + nint offset = (nint)(uint)index * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(in T reference, int length, int step) + { + this._step = step; + + _span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(_span, _step); + + public T[] ToArray() + { + int length = _span.Length; + + // Empty array if no data is mapped + if (length == 0) + return Array.Empty(); + + T[] array = new T[length]; + CopyTo(array); + + return array; + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (_step == 1) + { + _span.CopyTo(destination); + return; + } + + ref T sourceRef = ref MemoryMarshal.GetReference(_span); + int length = _span.Length; + if ((uint)destination.Length < (uint)length) + throw new ArgumentException("The target span is too short to copy all the current items to."); + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + + CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)_step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= _span.Length) + { + CopyTo(destination); + return true; + } + + return false; + } + + private static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) + { + nint sourceOffset = 0; + nint destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 8; + sourceOffset += sourceStep; + destinationOffset += 8; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 4; + sourceOffset += sourceStep; + destinationOffset += 4; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += sourceStep; + destinationOffset += 1; + } + } + + #endregion + + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { + #region Properties & Fields + + /// + private readonly ReadOnlySpan _span; + + /// + private readonly int _step; + + /// + /// The current position in the sequence. + /// + private int _position; + + /// + public readonly ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T r0 = ref MemoryMarshal.GetReference(_span); + + nint offset = (nint)(uint)_position * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan span, int step) + { + this._span = span; + this._step = step; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _span.Length; + + #endregion + } +} diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index da40620..d64d8ee 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -133,7 +133,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture return result; } - protected override void PerformCaptureZoneUpdate(CaptureZone captureZone) + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { if (_context == null) return; @@ -157,25 +157,26 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture textures.Y + textures.UnscaledHeight, 1)); MappedSubresource mapSource = _context.Map(textures.StagingTexture, 0, MapMode.Read, MapFlags.None); - using IDisposable @lock = captureZone.Image.Lock(); + + using IDisposable @lock = captureZone.Lock(); { - Span source = mapSource.AsSpan(mapSource.RowPitch * textures.Height); + ReadOnlySpan source = mapSource.AsSpan(mapSource.RowPitch * textures.Height); switch (Display.Rotation) { case Rotation.Rotation90: - CopyRotate90(source, mapSource.RowPitch, captureZone); + CopyRotate90(source, mapSource.RowPitch, captureZone, buffer); break; case Rotation.Rotation180: - CopyRotate180(source, mapSource.RowPitch, captureZone); + CopyRotate180(source, mapSource.RowPitch, captureZone, buffer); break; case Rotation.Rotation270: - CopyRotate270(source, mapSource.RowPitch, captureZone); + CopyRotate270(source, mapSource.RowPitch, captureZone, buffer); break; default: - CopyRotate0(source, mapSource.RowPitch, captureZone); + CopyRotate0(source, mapSource.RowPitch, captureZone, buffer); break; } } @@ -185,11 +186,11 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate0(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate0(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int height = captureZone.Image.Height; - int stride = captureZone.Image.Stride; - Span target = captureZone.Image.Raw; + int height = captureZone.Height; + int stride = captureZone.Stride; + Span target = buffer; for (int y = 0; y < height; y++) { @@ -201,51 +202,49 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRotate90(in Span source, int sourceStride, in CaptureZone captureZone) + private static void CopyRotate90(in ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel; - Span target = captureZone.Image.Pixels; + int width = captureZone.Width; + int height = captureZone.Height; + int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel; + Span target = MemoryMarshal.Cast(buffer); for (int x = 0; x < width; x++) { - Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); + ReadOnlySpan 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 ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int bpp = captureZone.Image.ColorFormat.BytesPerPixel; + int width = captureZone.Width; + int height = captureZone.Height; + int bpp = captureZone.ColorFormat.BytesPerPixel; int usedBytesPerLine = width * bpp; - Span target = captureZone.Image.Pixels; + Span target = MemoryMarshal.Cast(buffer); for (int y = 0; y < height; y++) { - Span src = MemoryMarshal.Cast(source.Slice(y * sourceStride, usedBytesPerLine)); + ReadOnlySpan src = MemoryMarshal.Cast(source.Slice(y * sourceStride, usedBytesPerLine)); for (int x = 0; x < src.Length; x++) - { 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 ReadOnlySpan source, int sourceStride, in CaptureZone captureZone, in Span buffer) { - int width = captureZone.Image.Width; - int height = captureZone.Image.Height; - int usedBytesPerLine = height * captureZone.Image.ColorFormat.BytesPerPixel; - Span target = captureZone.Image.Pixels; + int width = captureZone.Width; + int height = captureZone.Height; + int usedBytesPerLine = height * captureZone.ColorFormat.BytesPerPixel; + Span target = MemoryMarshal.Cast(buffer); for (int x = 0; x < width; x++) { - Span src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); + ReadOnlySpan src = MemoryMarshal.Cast(source.Slice(x * sourceStride, usedBytesPerLine)); for (int y = 0; y < src.Length; y++) target[((height - y - 1) * width) + x] = src[y]; } diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs index 61af70a..3eb8c0d 100644 --- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs +++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs @@ -56,7 +56,7 @@ public abstract class AbstractScreenCapture : IScreenCapture { try { - PerformCaptureZoneUpdate(captureZone); + PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer); captureZone.SetUpdated(); } catch { /* */ } @@ -69,7 +69,7 @@ public abstract class AbstractScreenCapture : IScreenCapture protected abstract bool PerformScreenCapture(); - protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone); + protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer); protected virtual void OnUpdated(bool result) { @@ -93,11 +93,7 @@ public abstract class AbstractScreenCapture : IScreenCapture 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 + CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight); CaptureZones.Add(captureZone); return captureZone; @@ -168,13 +164,7 @@ public abstract class AbstractScreenCapture : IScreenCapture 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); + captureZone.Resize(newWidth, newHeight, newDownscaleLevel, newUnscaledWidth, newUnscaledHeight); } } } diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 5be69a0..8c0e0f9 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -1,6 +1,9 @@ // ReSharper disable MemberCanBePrivate.Global using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; namespace ScreenCapture.NET; @@ -12,6 +15,8 @@ public sealed class CaptureZone : ICaptureZone { #region Properties & Fields + private readonly object _lock = new(); + /// /// Gets the unique id of this . /// @@ -19,6 +24,20 @@ public sealed class CaptureZone : ICaptureZone public Display Display { get; } +#if NET7_0_OR_GREATER + public ColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => TColor.ColorFormat; + } +#else + public ColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => IColor.GetColorFormat(); + } +#endif + /// /// Gets the x-location of the region on the screen. /// @@ -32,30 +51,53 @@ public sealed class CaptureZone : ICaptureZone /// /// Gets the width of the captured region. /// - public int Width { get; internal set; } + public int Width { get; private set; } /// /// Gets the height of the captured region. /// - public int Height { get; internal set; } + public int Height { get; private set; } + + public int Stride + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Width * ColorFormat.BytesPerPixel; + } /// /// Gets the level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. /// - public int DownscaleLevel { get; internal set; } + public int DownscaleLevel { get; private set; } /// /// Gets the original width of the region (this equals if is 0). /// - public int UnscaledWidth { get; internal set; } + public int UnscaledWidth { get; private set; } /// /// Gets the original height of the region (this equals if is 0). /// - public int UnscaledHeight { get; internal set; } + public int UnscaledHeight { get; private set; } - IScreenImage ICaptureZone.Image => Image; - public ScreenImage Image { get; } + internal byte[] InternalBuffer { get; set; } + + public ReadOnlySpan RawBuffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => InternalBuffer; + } + + public ReadOnlySpan Pixels + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MemoryMarshal.Cast(RawBuffer); + } + + public Image Image + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(Pixels, 0, 0, Width, Height, Width); + } /// /// Gets or sets if the should be automatically updated on every captured frame. @@ -66,7 +108,7 @@ public sealed class CaptureZone : ICaptureZone /// Gets if an update for the is requested on the next captured frame. /// public bool IsUpdateRequested { get; private set; } - + #endregion #region Events @@ -93,7 +135,7 @@ public sealed class CaptureZone : ICaptureZone /// The original width of the region. /// The original height of the region /// The buffer containing the image data. - internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, ScreenImage image) + internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) { this.Id = id; this.Display = display; @@ -101,16 +143,23 @@ public sealed class CaptureZone : ICaptureZone this.Y = y; this.Width = width; this.Height = height; + this.DownscaleLevel = downscaleLevel; this.UnscaledWidth = unscaledWidth; this.UnscaledHeight = unscaledHeight; - this.DownscaleLevel = downscaleLevel; - this.Image = image; + + InternalBuffer = new byte[Stride * Height]; } #endregion #region Methods + public IDisposable Lock() + { + Monitor.Enter(_lock); + return new UnlockDisposable(_lock); + } + /// /// Requests to update this when the next frame is captured. /// Only necessary if is set to false. @@ -127,6 +176,19 @@ public sealed class CaptureZone : ICaptureZone Updated?.Invoke(this, EventArgs.Empty); } + internal void Resize(int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) + { + Width = width; + Height = height; + DownscaleLevel = downscaleLevel; + UnscaledWidth = unscaledWidth; + UnscaledHeight = unscaledHeight; + + int newBufferSize = Stride * Height; + if (newBufferSize != InternalBuffer.Length) + InternalBuffer = new byte[newBufferSize]; + } + /// /// Determines whether this equals the given one. /// @@ -141,4 +203,32 @@ public sealed class CaptureZone : ICaptureZone public override int GetHashCode() => Id; #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 + } } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 7f83100..150f382 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -38,7 +38,8 @@ public interface ICaptureZone /// int UnscaledHeight { get; } - IScreenImage Image { get; } + ReadOnlySpan RawBuffer { get; } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// diff --git a/ScreenCapture.NET/Model/IScreenImage.cs b/ScreenCapture.NET/Model/IScreenImage.cs deleted file mode 100644 index 4b87105..0000000 --- a/ScreenCapture.NET/Model/IScreenImage.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/Image.cs b/ScreenCapture.NET/Model/Image.cs new file mode 100644 index 0000000..6eae374 --- /dev/null +++ b/ScreenCapture.NET/Model/Image.cs @@ -0,0 +1,269 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +public readonly ref struct Image + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + + private readonly int _x; + private readonly int _y; + private readonly int _stride; + + public int Width { get; } + public int Height { get; } + + #endregion + + #region Indexer + + public TColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x > Width) || (y > Height)) throw new IndexOutOfRangeException(); + + return _pixels[((_y + y) * _stride) + (_x + x)]; + } + } + + public Image this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new Image(_pixels, _x + x, _y + y, width, height, _stride); + } + } + + public ImageRows Rows => new(_pixels, _x, _y, Width, Height, _stride); + public ImageColumns Columns => new(_pixels, _x, _y, Width, Height, _stride); + + #endregion + + #region Constructors + + internal Image(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + public void CopyTo(in Span dest) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest)); + + ImageRows rows = Rows; + Span target = dest; + foreach (ReadOnlyRefEnumerable row in rows) + { + row.CopyTo(target); + target = target[Width..]; + } + } + + #endregion + + #region Indexer-Structs + + public readonly ref struct ImageRows + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public readonly ReadOnlyRefEnumerable this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); + + return new ReadOnlyRefEnumerable(rr, _width, 1); + } + } + + #endregion + + #region Constructors + + public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(this); + + #endregion + + public ref struct Enumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } + } + + public readonly ref struct ImageColumns + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public ReadOnlyRefEnumerable this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); + + return new ReadOnlyRefEnumerable(rc, _height, _stride); + } + } + + #endregion + + #region Constructors + + public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(this); + + #endregion + + public ref struct Enumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ScreenImage.cs b/ScreenCapture.NET/Model/ScreenImage.cs deleted file mode 100644 index 9b6c47e..0000000 --- a/ScreenCapture.NET/Model/ScreenImage.cs +++ /dev/null @@ -1,102 +0,0 @@ -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 8396d8a..49dfad2 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -41,10 +41,6 @@ snupkg - - true - - $(DefineConstants);TRACE;DEBUG true From 1782c8341502443b458beda1dd109f4965dcecae Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 20:11:39 +0200 Subject: [PATCH 03/18] Small fixes --- ScreenCapture.NET/Model/Colors/ColorBGRA.cs | 2 -- ScreenCapture.NET/Model/Image.cs | 12 ++++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs index dabd01c..1742899 100644 --- a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs +++ b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs @@ -9,9 +9,7 @@ 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; diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index 6eae374..a67ba53 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -44,8 +44,16 @@ public readonly ref struct Image } } - public ImageRows Rows => new(_pixels, _x, _y, Width, Height, _stride); - public ImageColumns Columns => new(_pixels, _x, _y, Width, Height, _stride); + public ImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } #endregion From ba5233be6f9250deb90dd7a6ab2fc8e2d9a666c6 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 20:20:25 +0200 Subject: [PATCH 04/18] Added ToArray to Image --- ScreenCapture.NET/Model/Image.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index a67ba53..f7ca9c8 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -38,7 +38,7 @@ public readonly ref struct Image [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); return new Image(_pixels, _x + x, _y + y, width, height, _stride); } @@ -87,6 +87,13 @@ public readonly ref struct Image } } + public TColor[] ToArray() + { + TColor[] array = new TColor[Width * Height]; + CopyTo(array); + return array; + } + #endregion #region Indexer-Structs From db1e37f1d98563d3f3eb5e70fee753cbc9d9243a Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 20:23:35 +0200 Subject: [PATCH 05/18] Added Enumerator to Image --- ScreenCapture.NET/Model/Image.cs | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index f7ca9c8..6ba2b09 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -94,8 +94,50 @@ public readonly ref struct Image return array; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(_pixels); + #endregion + public ref struct Enumerator + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private int _position; + + /// + public TColor Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pixels[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan pixels) + { + this._pixels = pixels; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _pixels.Length; + + #endregion + } + #region Indexer-Structs public readonly ref struct ImageRows From 52d37b996edc28ce68a57095ad305e155adec3b1 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 20:32:14 +0200 Subject: [PATCH 06/18] Small fixes --- ScreenCapture.NET/Model/Colors/ColorBGRA.cs | 10 ++++++++++ ScreenCapture.NET/Model/Image.cs | 18 +++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs index 1742899..4029709 100644 --- a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs +++ b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs @@ -1,9 +1,11 @@ // ReSharper disable ConvertToAutoProperty +using System.Diagnostics; using System.Runtime.InteropServices; namespace ScreenCapture.NET; +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorBGRA : IColor { @@ -16,10 +18,12 @@ public readonly struct ColorBGRA : IColor private readonly byte _r; private readonly byte _a; + // ReSharper disable ConvertToAutoPropertyWhenPossible public byte B => _b; public byte G => _g; public byte R => _r; public byte A => _a; + // ReSharper restore ConvertToAutoPropertyWhenPossible #endregion @@ -34,4 +38,10 @@ public readonly struct ColorBGRA : IColor } #endregion + + #region Methods + + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index 6ba2b09..f0d7d8f 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -96,11 +96,11 @@ public readonly ref struct Image /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new(_pixels); + public ImageEnumerator GetEnumerator() => new(_pixels); #endregion - public ref struct Enumerator + public ref struct ImageEnumerator { #region Properties & Fields @@ -120,7 +120,7 @@ public readonly ref struct Image [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(ReadOnlySpan pixels) + internal ImageEnumerator(ReadOnlySpan pixels) { this._pixels = pixels; @@ -189,11 +189,11 @@ public readonly ref struct Image /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new(this); + public ImageRowsEnumerator GetEnumerator() => new(this); #endregion - public ref struct Enumerator + public ref struct ImageRowsEnumerator { #region Properties & Fields @@ -213,7 +213,7 @@ public readonly ref struct Image [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(ImageRows rows) + internal ImageRowsEnumerator(ImageRows rows) { this._rows = rows; @@ -281,11 +281,11 @@ public readonly ref struct Image /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new(this); + public ImageColumnsEnumerator GetEnumerator() => new(this); #endregion - public ref struct Enumerator + public ref struct ImageColumnsEnumerator { #region Properties & Fields @@ -304,7 +304,7 @@ public readonly ref struct Image #region Constructors [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(ImageColumns columns) + internal ImageColumnsEnumerator(ImageColumns columns) { this._columns = columns; this._position = -1; From 946dafe64933d4439c14fb8554acc77bfde6a287 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 21:55:42 +0200 Subject: [PATCH 07/18] Implemented generic image --- ScreenCapture.NET/Model/CaptureZone.cs | 31 +- .../Model/{ => Colors}/ColorFormat.cs | 0 ScreenCapture.NET/Model/Colors/IColor.cs | 17 +- ScreenCapture.NET/Model/ICaptureZone.cs | 6 + ScreenCapture.NET/Model/IImage.cs | 35 ++ ScreenCapture.NET/Model/Image.cs | 319 ++++++++--------- ScreenCapture.NET/Model/RefImage.cs | 326 ++++++++++++++++++ 7 files changed, 538 insertions(+), 196 deletions(-) rename ScreenCapture.NET/Model/{ => Colors}/ColorFormat.cs (100%) create mode 100644 ScreenCapture.NET/Model/IImage.cs create mode 100644 ScreenCapture.NET/Model/RefImage.cs diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 8c0e0f9..d38e76c 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -23,20 +23,12 @@ public sealed class CaptureZone : ICaptureZone public int Id { get; } public Display Display { get; } - -#if NET7_0_OR_GREATER - public ColorFormat ColorFormat + + public ColorFormat ColorFormat { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => TColor.ColorFormat; } -#else - public ColorFormat ColorFormat - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => IColor.GetColorFormat(); - } -#endif /// /// Gets the x-location of the region on the screen. @@ -93,12 +85,18 @@ public sealed class CaptureZone : ICaptureZone get => MemoryMarshal.Cast(RawBuffer); } - public Image Image + public RefImage Image { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Pixels, 0, 0, Width, Height, Width); } + IImage ICaptureZone.Image + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Image(InternalBuffer, 0, 0, Width, Height, Width); + } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// @@ -154,6 +152,14 @@ public sealed class CaptureZone : ICaptureZone #region Methods + public RefImage GetRefImage() + where T : struct, IColor + { + if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested Color-Format does not match the data.", nameof(T)); + + return new RefImage(MemoryMarshal.Cast(RawBuffer), 0, 0, Width, Height, Width); + } + public IDisposable Lock() { Monitor.Enter(_lock); @@ -216,6 +222,7 @@ public sealed class CaptureZone : ICaptureZone #region Constructors public UnlockDisposable(object @lock) => this._lock = @lock; + ~UnlockDisposable() => Dispose(); #endregion @@ -227,6 +234,8 @@ public sealed class CaptureZone : ICaptureZone Monitor.Exit(_lock); _disposed = true; + + GC.SuppressFinalize(this); } #endregion diff --git a/ScreenCapture.NET/Model/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs similarity index 100% rename from ScreenCapture.NET/Model/ColorFormat.cs rename to ScreenCapture.NET/Model/Colors/ColorFormat.cs diff --git a/ScreenCapture.NET/Model/Colors/IColor.cs b/ScreenCapture.NET/Model/Colors/IColor.cs index 43386ff..f3ea888 100644 --- a/ScreenCapture.NET/Model/Colors/IColor.cs +++ b/ScreenCapture.NET/Model/Colors/IColor.cs @@ -1,4 +1,6 @@ -namespace ScreenCapture.NET; +using System; + +namespace ScreenCapture.NET; public interface IColor { @@ -7,16 +9,5 @@ public interface IColor 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 + public static virtual ColorFormat ColorFormat => throw new NotSupportedException(); } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 150f382..70cc58c 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -8,6 +8,7 @@ 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. @@ -40,6 +41,8 @@ public interface ICaptureZone ReadOnlySpan RawBuffer { get; } + IImage Image { get; } + /// /// Gets or sets if the should be automatically updated on every captured frame. /// @@ -58,4 +61,7 @@ public interface ICaptureZone /// Only necessary if is set to false. /// void RequestUpdate(); + + RefImage GetRefImage() + where TColor : struct, IColor; } diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs new file mode 100644 index 0000000..bbe18e7 --- /dev/null +++ b/ScreenCapture.NET/Model/IImage.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace ScreenCapture.NET; + +public interface IImage : IEnumerable +{ + int Width { get; } + int Height { get; } + + IColor this[int x, int y] { get; } + IImage this[int x, int y, int width, int height] { get; } + + IImageRows Rows { get; } + IImageColumns Columns { get; } + + public interface IImageRows : IEnumerable + { + IImageRow this[int column] { get; } + } + + public interface IImageColumns : IEnumerable + { + IImageColumn this[int column] { get; } + } + + public interface IImageRow : IEnumerable + { + IColor this[int x] { get; } + } + + public interface IImageColumn : IEnumerable + { + IColor this[int y] { get; } + } +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index f0d7d8f..87a3760 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -1,15 +1,17 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace ScreenCapture.NET; -public readonly ref struct Image +public sealed class Image : IImage where TColor : struct, IColor { #region Properties & Fields - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; @@ -22,46 +24,47 @@ public readonly ref struct Image #region Indexer - public TColor this[int x, int y] + public IColor this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (x > Width) || (y > Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); - return _pixels[((_y + y) * _stride) + (_x + x)]; + return MemoryMarshal.Cast(_buffer)[((_y + y) * _stride) + (_x + x)]; } } - public Image this[int x, int y, int width, int height] + public IImage this[int x, int y, int width, int height] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); - return new Image(_pixels, _x + x, _y + y, width, height, _stride); + return new Image(_buffer, _x + x, _y + y, width, height, _stride); } } - public ImageRows Rows + public IImage.IImageRows Rows { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(_pixels, _x, _y, Width, Height, _stride); + get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); } - public ImageColumns Columns + + public IImage.IImageColumns Columns { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(_pixels, _x, _y, Width, Height, _stride); + get => new ImageColumns(_buffer, _x, _y, Width, Height, _stride); } #endregion #region Constructors - internal Image(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal Image(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this.Width = width; @@ -73,78 +76,24 @@ public readonly ref struct Image #region Methods - public void CopyTo(in Span dest) + public IEnumerator GetEnumerator() { - if (dest == null) throw new ArgumentNullException(nameof(dest)); - if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest)); - - ImageRows rows = Rows; - Span target = dest; - foreach (ReadOnlyRefEnumerable row in rows) - { - row.CopyTo(target); - target = target[Width..]; - } + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + yield return this[x, y]; } - public TColor[] ToArray() - { - TColor[] array = new TColor[Width * Height]; - CopyTo(array); - return array; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageEnumerator GetEnumerator() => new(_pixels); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion - public ref struct ImageEnumerator + #region Indexer-Classes + + private sealed class ImageRows : IImage.IImageRows { #region Properties & Fields - private readonly ReadOnlySpan _pixels; - private int _position; - - /// - public TColor Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _pixels[_position]; - } - - #endregion - - #region Constructors - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageEnumerator(ReadOnlySpan pixels) - { - this._pixels = pixels; - - _position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _pixels.Length; - - #endregion - } - - #region Indexer-Structs - - public readonly ref struct ImageRows - { - #region Properties & Fields - - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; private readonly int _width; @@ -155,17 +104,13 @@ public readonly ref struct Image #region Indexer - public readonly ReadOnlyRefEnumerable this[int row] + public IImage.IImageRow this[int row] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); - ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); - ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); - - return new ReadOnlyRefEnumerable(rr, _width, 1); + return new ImageRow(_buffer, (((row + _y) * _stride) + _x), _width); } } @@ -173,9 +118,9 @@ public readonly ref struct Image #region Constructors - public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal ImageRows(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this._width = width; @@ -187,56 +132,71 @@ public readonly ref struct Image #region Methods - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageRowsEnumerator GetEnumerator() => new(this); + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion - - public ref struct ImageRowsEnumerator - { - #region Properties & Fields - - private readonly ImageRows _rows; - private int _position; - - /// - public ReadOnlyRefEnumerable Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _rows[_position]; - } - - #endregion - - #region Constructors - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageRowsEnumerator(ImageRows rows) - { - this._rows = rows; - - _position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _rows._height; - - #endregion - } } - public readonly ref struct ImageColumns + private sealed class ImageRow : IImage.IImageRow { #region Properties & Fields - private readonly ReadOnlySpan _pixels; + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + + #endregion + + #region Indexer + + public IColor this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[x]; + } + } + + #endregion + + #region Constructors + + internal ImageRow(byte[] buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int x = 0; x < _length; x++) + yield return this[x]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + private sealed class ImageColumns : IImage.IImageColumns + { + #region Properties & Fields + + private readonly byte[] _buffer; private readonly int _x; private readonly int _y; private readonly int _width; @@ -247,17 +207,13 @@ public readonly ref struct Image #region Indexer - public ReadOnlyRefEnumerable this[int column] + public IImage.IImageColumn this[int column] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); - ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); - ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); - - return new ReadOnlyRefEnumerable(rc, _height, _stride); + return new ImageColumn(_buffer, (_y * _stride) + _x + column, _height, _stride); } } @@ -265,9 +221,9 @@ public readonly ref struct Image #region Constructors - public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + internal ImageColumns(byte[] buffer, int x, int y, int width, int height, int stride) { - this._pixels = pixels; + this._buffer = buffer; this._x = x; this._y = y; this._width = width; @@ -279,47 +235,66 @@ public readonly ref struct Image #region Methods - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageColumnsEnumerator GetEnumerator() => new(this); + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + private sealed class ImageColumn : IImage.IImageColumn + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; #endregion - public ref struct ImageColumnsEnumerator + #region Indexer + + public IColor this[int y] { - #region Properties & Fields - - private readonly ImageColumns _columns; - private int _position; - - /// - public ReadOnlyRefEnumerable Current + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _columns[_position]; + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[y * _step]; } - - #endregion - - #region Constructors - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageColumnsEnumerator(ImageColumns columns) - { - this._columns = columns; - this._position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _columns._width; - - #endregion } + + #endregion + + #region Constructors + + internal ImageColumn(byte[] buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _length; y++) + yield return this[y]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion } #endregion diff --git a/ScreenCapture.NET/Model/RefImage.cs b/ScreenCapture.NET/Model/RefImage.cs new file mode 100644 index 0000000..2fe734d --- /dev/null +++ b/ScreenCapture.NET/Model/RefImage.cs @@ -0,0 +1,326 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +public readonly ref struct RefImage + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + + private readonly int _x; + private readonly int _y; + private readonly int _stride; + + public int Width { get; } + public int Height { get; } + + #endregion + + #region Indexer + + public TColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); + + return _pixels[((_y + y) * _stride) + (_x + x)]; + } + } + + public RefImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + + return new RefImage(_pixels, _x + x, _y + y, width, height, _stride); + } + } + + public ImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, _stride); + } + + #endregion + + #region Constructors + + internal RefImage(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + public void CopyTo(in Span dest) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest)); + + ImageRows rows = Rows; + Span target = dest; + foreach (ReadOnlyRefEnumerable row in rows) + { + row.CopyTo(target); + target = target[Width..]; + } + } + + public TColor[] ToArray() + { + TColor[] array = new TColor[Width * Height]; + CopyTo(array); + return array; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageEnumerator GetEnumerator() => new(_pixels); + + #endregion + + public ref struct ImageEnumerator + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private int _position; + + /// + public TColor Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pixels[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageEnumerator(ReadOnlySpan pixels) + { + this._pixels = pixels; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _pixels.Length; + + #endregion + } + + #region Indexer-Structs + + public readonly ref struct ImageRows + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public readonly ReadOnlyRefEnumerable this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); + + return new ReadOnlyRefEnumerable(rr, _width, 1); + } + } + + #endregion + + #region Constructors + + public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageRowsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowsEnumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageRowsEnumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } + } + + public readonly ref struct ImageColumns + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + #endregion + + #region Indexer + + public ReadOnlyRefEnumerable this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); + + return new ReadOnlyRefEnumerable(rc, _height, _stride); + } + } + + #endregion + + #region Constructors + + public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageColumnsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnsEnumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageColumnsEnumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } + } + + #endregion +} \ No newline at end of file From 18e562508e7cb2f83faa9352c8caaabcabf927b1 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 22:43:49 +0200 Subject: [PATCH 08/18] Removed Id from CaptureZone --- .../Generic/AbstractScreenCapture.cs | 3 +-- ScreenCapture.NET/Model/CaptureZone.cs | 23 ++----------------- ScreenCapture.NET/Model/ICaptureZone.cs | 5 ---- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs index 3eb8c0d..cffd4b4 100644 --- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs +++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs @@ -10,7 +10,6 @@ public abstract class AbstractScreenCapture : IScreenCapture #region Properties & Fields private bool _isDisposed; - private int _indexCounter = 0; protected HashSet> CaptureZones { get; } = new(); @@ -93,7 +92,7 @@ public abstract class AbstractScreenCapture : IScreenCapture int unscaledHeight = height; (width, height, downscaleLevel) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel); - CaptureZone captureZone = new(_indexCounter++, Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight); + CaptureZone captureZone = new(Display, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight); CaptureZones.Add(captureZone); return captureZone; diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index d38e76c..2a30767 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -17,13 +17,8 @@ public sealed class CaptureZone : ICaptureZone private readonly object _lock = new(); - /// - /// Gets the unique id of this . - /// - public int Id { get; } - public Display Display { get; } - + public ColorFormat ColorFormat { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -133,9 +128,8 @@ public sealed class CaptureZone : ICaptureZone /// The original width of the region. /// The original height of the region /// The buffer containing the image data. - internal CaptureZone(int id, Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) + internal CaptureZone(Display display, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight) { - this.Id = id; this.Display = display; this.X = x; this.Y = y; @@ -195,19 +189,6 @@ public sealed class CaptureZone : ICaptureZone InternalBuffer = new byte[newBufferSize]; } - /// - /// Determines whether this equals the given one. - /// - /// The to compare. - /// true if the specified object is equal to the current object; otherwise, false. - public bool Equals(CaptureZone other) => Id == other.Id; - - /// - public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other); - - /// - public override int GetHashCode() => Id; - #endregion private class UnlockDisposable : IDisposable diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 70cc58c..0379ee6 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -4,11 +4,6 @@ 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. From f55fa6a7014bc063a6a7a8b0b85926155b577c9f Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 22:44:06 +0200 Subject: [PATCH 09/18] Splitted the DX11 Screen Capture logic into a separate project --- .../DX11CompatibilityExtensions.cs | 0 .../DX11ScreenCapture.cs | 0 .../DX11ScreenCaptureService.cs | 15 +++- .../Helper/DPIAwareness.cs | 0 ScreenCapture.NET.DX11/Resources/icon.png | Bin 0 -> 705 bytes .../ScreenCapture.NET.DX11.csproj | 73 ++++++++++++++++++ .../ScreenCapture.NET.DX11.csproj.DotSettings | 2 + ScreenCapture.NET.sln | 12 ++- ScreenCapture.NET/ScreenCapture.NET.csproj | 8 +- 9 files changed, 100 insertions(+), 10 deletions(-) rename {ScreenCapture.NET/DirectX => ScreenCapture.NET.DX11}/DX11CompatibilityExtensions.cs (100%) rename {ScreenCapture.NET/DirectX => ScreenCapture.NET.DX11}/DX11ScreenCapture.cs (100%) rename {ScreenCapture.NET/DirectX => ScreenCapture.NET.DX11}/DX11ScreenCaptureService.cs (88%) rename {ScreenCapture.NET => ScreenCapture.NET.DX11}/Helper/DPIAwareness.cs (100%) create mode 100644 ScreenCapture.NET.DX11/Resources/icon.png create mode 100644 ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj create mode 100644 ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj.DotSettings diff --git a/ScreenCapture.NET/DirectX/DX11CompatibilityExtensions.cs b/ScreenCapture.NET.DX11/DX11CompatibilityExtensions.cs similarity index 100% rename from ScreenCapture.NET/DirectX/DX11CompatibilityExtensions.cs rename to ScreenCapture.NET.DX11/DX11CompatibilityExtensions.cs diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET.DX11/DX11ScreenCapture.cs similarity index 100% rename from ScreenCapture.NET/DirectX/DX11ScreenCapture.cs rename to ScreenCapture.NET.DX11/DX11ScreenCapture.cs diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs b/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs similarity index 88% rename from ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs rename to ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs index 28d0fce..40363a8 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCaptureService.cs +++ b/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs @@ -12,9 +12,10 @@ public class DX11ScreenCaptureService : IScreenCaptureService #region Properties & Fields private readonly IDXGIFactory1 _factory; - private readonly Dictionary _screenCaptures = new(); + private bool _isDisposed; + #endregion #region Constructors @@ -27,6 +28,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService DXGI.CreateDXGIFactory1(out _factory!).CheckError(); } + ~DX11ScreenCaptureService() => Dispose(); + #endregion #region Methods @@ -34,6 +37,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService /// public IEnumerable GetGraphicsCards() { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + int i = 0; while (_factory.EnumAdapters1(i, out IDXGIAdapter1 adapter).Success) { @@ -46,6 +51,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService /// public IEnumerable GetDisplays(GraphicsCard graphicsCard) { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + using IDXGIAdapter1? adapter = _factory.GetAdapter1(graphicsCard.Index); int i = 0; @@ -71,6 +78,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display); public DX11ScreenCapture GetScreenCapture(Display display) { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture)) _screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display)); return screenCapture; @@ -79,6 +88,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService /// public void Dispose() { + if (_isDisposed) return; + foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values) screenCapture.Dispose(); _screenCaptures.Clear(); @@ -86,6 +97,8 @@ public class DX11ScreenCaptureService : IScreenCaptureService _factory.Dispose(); GC.SuppressFinalize(this); + + _isDisposed = true; } #endregion diff --git a/ScreenCapture.NET/Helper/DPIAwareness.cs b/ScreenCapture.NET.DX11/Helper/DPIAwareness.cs similarity index 100% rename from ScreenCapture.NET/Helper/DPIAwareness.cs rename to ScreenCapture.NET.DX11/Helper/DPIAwareness.cs diff --git a/ScreenCapture.NET.DX11/Resources/icon.png b/ScreenCapture.NET.DX11/Resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46c50338bdcd4535ff63138ab7030dab6b11e747 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE(}r(cNo+^b@?U(MK}vQ zB8wRq_zr_Gjld?%{_ ztr9WvFf!(ud)_hqTTgv1lK=yg0|Q4xQ(Jdkg4JB9rJ^Rjo9|v{Of#O&87B4MV8Tqx z4J--_EDa10g$#`NIO`H_d}WsCKYsQ3gY4<$_kLD0Pp^0{GYzUeVYwf(_)H(Qv`p5; z7c+MIGuOJDO*?*FD1jft^xe4+S2zAkKQD-8--fqso^qvsB>H@v%YPLtyk9P!U(~eM zu*KE;fR(M!C!>@57OoXPuv_IkZ|2#}4d!b5->DVYpZxAC`z@F0_kPYj*=M(deB*G^ zH{q7=1KrsNX3ajJQaS6%@`vkov(%jXmobia3;O{% zghLi|+^bJ?xonb_*<4kxD&Wn)1Pnm9boPaI?Z*D<4-)s(cGjN#{!{m)Hs`m)h0mrh zS^xQ3_j$KXED=m)r~azw7k%Bn+TNq+^DOz-k(2!Yf2@8#xn|0{{e6@Eym}CD@95hT zCGlkuDUbB;@%jF~)BCL;O1EM2x1H1Pv)uD83NZN7{B5T-w*!L+--1u#(!YX^mkIcB z + + net7.0-windows;net6.0-windows + latest + enable + true + + Darth Affe + Wyrez + en-US + en-US + ScreenCapture.NET.DX11 + ScreenCapture.NET.DX11 + ScreenCapture.NET.DX11 + ScreenCapture.NET.DX11 + ScreenCapture.NET + Vortice based Screen-Capturing + Vortice based Screen-Capturing using Desktop Duplication + Copyright © Darth Affe 2023 + Copyright © Darth Affe 2023 + icon.png + https://github.com/DarthAffe/ScreenCapture.NET + LGPL-2.1-only + Github + https://github.com/DarthAffe/ScreenCapture.NET + True + + + The downscale-level is now automatically limited if it would scale the image below a size of 1x1 px. + This is change that can potentially break existing behavior but should only affect cases that are expected to crash with earlier versions. + + + 1.3.2 + 1.3.2 + 1.3.2 + + ..\bin\ + true + True + True + snupkg + + + + $(DefineConstants);TRACE;DEBUG + true + full + false + + + + portable + true + $(NoWarn);CS1591;CS1572;CS1573 + $(DefineConstants);RELEASE + + + + + True + + + + + + + + + + + + + diff --git a/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj.DotSettings b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj.DotSettings new file mode 100644 index 0000000..bdcca3e --- /dev/null +++ b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index 3888c2c..5691b86 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33829.357 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET", "ScreenCapture.NET\ScreenCapture.NET.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET", "ScreenCapture.NET\ScreenCapture.NET.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "..\..\..\..\DarthAffe\Source\Repos\ScreenCapture.NET\ScreenCapture.NET.DX11\ScreenCapture.NET.DX11.csproj", "{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.Build.0 = Release|Any CPU + {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj index 49dfad2..63094cd 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -14,8 +14,8 @@ ScreenCapture.NET ScreenCapture.NET ScreenCapture.NET - Vortice based Screen-Capturing - Vortice based Screen-Capturing using Desktop Duplication + Core functionality for Screen-Capturing + Base package for ScreenCapture.NET projects Copyright © Darth Affe 2023 Copyright © Darth Affe 2023 icon.png @@ -62,8 +62,4 @@ - - - - From adacf41d075ebd35e44569062d64a02ba687e8aa Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 5 Sep 2023 23:24:00 +0200 Subject: [PATCH 10/18] Fixed messed up reference paths --- ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj | 2 +- ScreenCapture.NET.sln | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj index 4e46d07..d148edf 100644 --- a/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj +++ b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj @@ -67,7 +67,7 @@ - + diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index 5691b86..8f891db 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.6.33829.357 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET", "ScreenCapture.NET\ScreenCapture.NET.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "..\..\..\..\DarthAffe\Source\Repos\ScreenCapture.NET\ScreenCapture.NET.DX11\ScreenCapture.NET.DX11.csproj", "{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "ScreenCapture.NET.DX11\ScreenCapture.NET.DX11.csproj", "{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From ec57728521d201375f2cb424b0d137341d764fde Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 6 Sep 2023 21:49:13 +0200 Subject: [PATCH 11/18] Added Tests for Images; Small improvements --- ScreenCapture.NET.sln | 11 ++ ScreenCapture.NET.sln.DotSettings | 1 + ScreenCapture.NET/Model/Colors/ColorARGB.cs | 47 +++++ ScreenCapture.NET/Model/Colors/ColorBGR.cs | 45 +++++ ScreenCapture.NET/Model/Colors/ColorFormat.cs | 3 + ScreenCapture.NET/Model/Colors/ColorRGB.cs | 45 +++++ ScreenCapture.NET/Model/IImage.cs | 4 + ScreenCapture.NET/Model/Image.cs | 10 +- ScreenCapture.NET/Model/RefImage.cs | 6 +- Tests/ScreenCapture.NET.Tests/ImageTest.cs | 165 ++++++++++++++++++ Tests/ScreenCapture.NET.Tests/RefImageTest.cs | 165 ++++++++++++++++++ .../ScreenCapture.NET.Tests.csproj | 22 +++ .../TestScreenCapture.cs | 37 ++++ 13 files changed, 559 insertions(+), 2 deletions(-) create mode 100644 ScreenCapture.NET/Model/Colors/ColorARGB.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorBGR.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorRGB.cs create mode 100644 Tests/ScreenCapture.NET.Tests/ImageTest.cs create mode 100644 Tests/ScreenCapture.NET.Tests/RefImageTest.cs create mode 100644 Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj create mode 100644 Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index 8f891db..abd9a1d 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET", "Screen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "ScreenCapture.NET.DX11\ScreenCapture.NET.DX11.csproj", "{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CF7A1475-3A44-4870-A80F-5988DA25418B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.Tests", "Tests\ScreenCapture.NET.Tests\ScreenCapture.NET.Tests.csproj", "{AA1829BB-EFA7-4BB8-8041-76374659A42B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,10 +25,17 @@ Global {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.Build.0 = Release|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AA1829BB-EFA7-4BB8-8041-76374659A42B} = {CF7A1475-3A44-4870-A80F-5988DA25418B} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B5111031-6E65-4331-9E6E-A07165289860} EndGlobalSection diff --git a/ScreenCapture.NET.sln.DotSettings b/ScreenCapture.NET.sln.DotSettings index 4097b57..4061c9f 100644 --- a/ScreenCapture.NET.sln.DotSettings +++ b/ScreenCapture.NET.sln.DotSettings @@ -1,4 +1,5 @@  + BGR BGRA DPI DX \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorARGB.cs b/ScreenCapture.NET/Model/Colors/ColorARGB.cs new file mode 100644 index 0000000..c00980f --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorARGB.cs @@ -0,0 +1,47 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorARGB : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.ARGB; + + private readonly byte _a; + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => _a; + public byte R => _r; + public byte G => _g; + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorARGB(byte a, byte r, byte g, byte b) + { + this._a = a; + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorBGR.cs b/ScreenCapture.NET/Model/Colors/ColorBGR.cs new file mode 100644 index 0000000..829d3b3 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorBGR.cs @@ -0,0 +1,45 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorBGR : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.BGR; + + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => byte.MaxValue; + public byte B => _b; + public byte G => _g; + public byte R => _r; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorBGR(byte b, byte g, byte r) + { + this._b = b; + this._g = g; + this._r = r; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs index c376580..eafc1e6 100644 --- a/ScreenCapture.NET/Model/Colors/ColorFormat.cs +++ b/ScreenCapture.NET/Model/Colors/ColorFormat.cs @@ -5,6 +5,9 @@ public readonly struct ColorFormat #region Instances public static readonly ColorFormat BGRA = new(1, 4); + public static readonly ColorFormat ARGB = new(2, 4); + public static readonly ColorFormat RGB = new(3, 3); + public static readonly ColorFormat BGR = new(4, 3); #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorRGB.cs b/ScreenCapture.NET/Model/Colors/ColorRGB.cs new file mode 100644 index 0000000..ec4acc6 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorRGB.cs @@ -0,0 +1,45 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorRGB : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.RGB; + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => byte.MaxValue; + public byte R => _r; + public byte G => _g; + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorRGB(byte r, byte g, byte b) + { + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs index bbe18e7..3037521 100644 --- a/ScreenCapture.NET/Model/IImage.cs +++ b/ScreenCapture.NET/Model/IImage.cs @@ -15,21 +15,25 @@ public interface IImage : IEnumerable public interface IImageRows : IEnumerable { + int Count { get; } IImageRow this[int column] { get; } } public interface IImageColumns : IEnumerable { + int Count { get; } IImageColumn this[int column] { get; } } public interface IImageRow : IEnumerable { + int Length { get; } IColor this[int x] { get; } } public interface IImageColumn : IEnumerable { + int Length { get; } IColor this[int y] { get; } } } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index 87a3760..295287a 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -40,7 +40,7 @@ public sealed class Image : IImage [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); return new Image(_buffer, _x + x, _y + y, width, height, _stride); } @@ -100,6 +100,8 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + public int Count => _height; + #endregion #region Indexer @@ -151,6 +153,8 @@ public sealed class Image : IImage private readonly int _start; private readonly int _length; + public int Length => _length; + #endregion #region Indexer @@ -203,6 +207,8 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + public int Count => _width; + #endregion #region Indexer @@ -255,6 +261,8 @@ public sealed class Image : IImage private readonly int _length; private readonly int _step; + public int Length => _length; + #endregion #region Indexer diff --git a/ScreenCapture.NET/Model/RefImage.cs b/ScreenCapture.NET/Model/RefImage.cs index 2fe734d..e15b0a1 100644 --- a/ScreenCapture.NET/Model/RefImage.cs +++ b/ScreenCapture.NET/Model/RefImage.cs @@ -38,7 +38,7 @@ public readonly ref struct RefImage [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); return new RefImage(_pixels, _x + x, _y + y, width, height, _stride); } @@ -151,6 +151,8 @@ public readonly ref struct RefImage private readonly int _height; private readonly int _stride; + public int Count => _height; + #endregion #region Indexer @@ -243,6 +245,8 @@ public readonly ref struct RefImage private readonly int _height; private readonly int _stride; + public int Count => _width; + #endregion #region Indexer diff --git a/Tests/ScreenCapture.NET.Tests/ImageTest.cs b/Tests/ScreenCapture.NET.Tests/ImageTest.cs new file mode 100644 index 0000000..5e5694b --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/ImageTest.cs @@ -0,0 +1,165 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ScreenCapture.NET.Tests; + +[TestClass] +public class ImageTest +{ + #region Properties & Fields + + private static IScreenCapture? _screenCapture; + private static ICaptureZone? _captureZone; + + #endregion + + #region Methods + + [ClassInitialize] + public static void ClassInit(TestContext _) + { + _screenCapture = new TestScreenCapture(); + _captureZone = _screenCapture.RegisterCaptureZone(0, 0, _screenCapture.Display.Width, _screenCapture.Display.Height); + _screenCapture.CaptureScreen(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + _screenCapture?.Dispose(); + _screenCapture = null; + } + + [TestMethod] + public void TestImageFullScreen() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(_captureZone.Width, image.Width); + Assert.AreEqual(_captureZone.Height, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + IImage image = _captureZone!.Image; + image = image[0, 0, image.Width, image.Height]; + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + IImage image = _captureZone!.Image; + + int counter = 0; + foreach (IColor color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + IImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + + Assert.AreEqual(720, image.Width); + Assert.AreEqual(13, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + IImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + image = image[15, 2, 47, 8]; + + Assert.AreEqual(47, image.Width); + Assert.AreEqual(8, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + IImage.IImageRow row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + IImage image = _captureZone!.Image; + + int y = 0; + foreach (IImage.IImageRow row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + IImage.IImageColumn column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + IImage image = _captureZone!.Image; + + int x = 0; + foreach (IImage.IImageColumn column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + #endregion +} \ No newline at end of file diff --git a/Tests/ScreenCapture.NET.Tests/RefImageTest.cs b/Tests/ScreenCapture.NET.Tests/RefImageTest.cs new file mode 100644 index 0000000..dae38bd --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/RefImageTest.cs @@ -0,0 +1,165 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ScreenCapture.NET.Tests; + +[TestClass] +public class RefImageTest +{ + #region Properties & Fields + + private static TestScreenCapture? _screenCapture; + private static CaptureZone? _captureZone; + + #endregion + + #region Methods + + [ClassInitialize] + public static void ClassInit(TestContext _) + { + _screenCapture = new TestScreenCapture(); + _captureZone = _screenCapture.RegisterCaptureZone(0, 0, _screenCapture.Display.Width, _screenCapture.Display.Height); + _screenCapture.CaptureScreen(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + _screenCapture?.Dispose(); + _screenCapture = null; + } + + [TestMethod] + public void TestImageFullScreen() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(_captureZone.Width, image.Width); + Assert.AreEqual(_captureZone.Height, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + RefImage image = _captureZone!.Image; + image = image[0, 0, image.Width, image.Height]; + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + RefImage image = _captureZone!.Image; + + int counter = 0; + foreach (ColorARGB color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + RefImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + + Assert.AreEqual(720, image.Width); + Assert.AreEqual(13, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + RefImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + image = image[15, 2, 47, 8]; + + Assert.AreEqual(47, image.Width); + Assert.AreEqual(8, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + ReadOnlyRefEnumerable row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + RefImage image = _captureZone!.Image; + + int y = 0; + foreach (ReadOnlyRefEnumerable row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + ReadOnlyRefEnumerable column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + RefImage image = _captureZone!.Image; + + int x = 0; + foreach (ReadOnlyRefEnumerable column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + #endregion +} \ No newline at end of file diff --git a/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj new file mode 100644 index 0000000..fbf4484 --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj @@ -0,0 +1,22 @@ + + + + net7.0 + enable + + false + true + + + + + + + + + + + + + + diff --git a/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs b/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs new file mode 100644 index 0000000..e1de0e5 --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET.Tests; + +internal class TestScreenCapture : AbstractScreenCapture +{ + #region Constructors + + public TestScreenCapture(Rotation rotation = Rotation.None) + : base(new Display(0, "Test", 1920, 1080, rotation, new GraphicsCard(0, "Test", 0, 0))) + { } + + #endregion + + #region Methods + + protected override bool PerformScreenCapture() => true; + + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) + { + Span pixels = MemoryMarshal.Cast(buffer); + + for (int y = 0; y < captureZone.Height; y++) + for (int x = 0; x < captureZone.Width; x++) + pixels[(y * captureZone.Width) + x] = GetColorFromLocation(x, y); + } + + public static ColorARGB GetColorFromLocation(int x, int y) + { + byte[] xBytes = BitConverter.GetBytes((short)x); + byte[] yBytes = BitConverter.GetBytes((short)y); + return new ColorARGB(xBytes[0], xBytes[1], yBytes[0], yBytes[1]); + } + + #endregion +} \ No newline at end of file From 6919cf7b60581f4ec50c901e9b1c22daeaffda42 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Thu, 7 Sep 2023 23:59:38 +0200 Subject: [PATCH 12/18] Updated vortice; Added try/catch around factory-Dispose --- ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs | 2 +- ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs b/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs index 40363a8..4193647 100644 --- a/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs +++ b/ScreenCapture.NET.DX11/DX11ScreenCaptureService.cs @@ -94,7 +94,7 @@ public class DX11ScreenCaptureService : IScreenCaptureService screenCapture.Dispose(); _screenCaptures.Clear(); - _factory.Dispose(); + try { _factory.Dispose(); } catch { /**/ } GC.SuppressFinalize(this); diff --git a/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj index d148edf..b13b988 100644 --- a/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj +++ b/ScreenCapture.NET.DX11/ScreenCapture.NET.DX11.csproj @@ -63,7 +63,7 @@ - + From 2667f84c429ffcd9c071149fff7e8ca35207ce08 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Thu, 7 Sep 2023 23:59:52 +0200 Subject: [PATCH 13/18] Added DX9 capture --- ScreenCapture.NET.DX9/DX9ScreenCapture.cs | 218 ++++++++++++++++++ .../DX9ScreenCaptureService.cs | 97 ++++++++ .../Downscale/AverageByteSampler.cs | 149 ++++++++++++ .../Downscale/SamplerInfo.cs | 63 +++++ ScreenCapture.NET.DX9/Resources/icon.png | Bin 0 -> 705 bytes .../ScreenCapture.NET.DX9.csproj | 75 ++++++ ScreenCapture.NET.sln | 8 +- .../ScreenCapture.NET.Tests.csproj | 11 +- 8 files changed, 616 insertions(+), 5 deletions(-) create mode 100644 ScreenCapture.NET.DX9/DX9ScreenCapture.cs create mode 100644 ScreenCapture.NET.DX9/DX9ScreenCaptureService.cs create mode 100644 ScreenCapture.NET.DX9/Downscale/AverageByteSampler.cs create mode 100644 ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs create mode 100644 ScreenCapture.NET.DX9/Resources/icon.png create mode 100644 ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj diff --git a/ScreenCapture.NET.DX9/DX9ScreenCapture.cs b/ScreenCapture.NET.DX9/DX9ScreenCapture.cs new file mode 100644 index 0000000..bf057b6 --- /dev/null +++ b/ScreenCapture.NET.DX9/DX9ScreenCapture.cs @@ -0,0 +1,218 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using ScreenCapture.NET.Downscale; +using SharpGen.Runtime; +using Vortice.Direct3D9; + +namespace ScreenCapture.NET; + +/// +/// Represents a ScreenCapture using DirectX 11 desktop duplicaton. +/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api +/// +// ReSharper disable once InconsistentNaming +public sealed class DX9ScreenCapture : AbstractScreenCapture +{ + #region Properties & Fields + + private readonly object _captureLock = new(); + + private readonly IDirect3D9 _direct3D9; + private IDirect3DDevice9? _device; + private IDirect3DSurface9? _surface; + private byte[]? _buffer; + private int _stride; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The to duplicate. + public DX9ScreenCapture(IDirect3D9 direct3D9, Display display) + : base(display) + { + this._direct3D9 = direct3D9; + + Restart(); + } + + #endregion + + #region Methods + + protected override bool PerformScreenCapture() + { + bool result = false; + lock (_captureLock) + { + if ((_device == null) || (_surface == null) || (_buffer == null)) + { + Restart(); + return false; + } + + try + { + _device.GetFrontBufferData(0, _surface); + + LockedRectangle dr = _surface.LockRect(LockFlags.NoSystemLock | LockFlags.ReadOnly); + + nint ptr = dr.DataPointer; + for (int y = 0; y < Display.Height; y++) + { + Marshal.Copy(ptr, _buffer, y * _stride, _stride); + ptr += dr.Pitch; + } + + _surface.UnlockRect(); + + result = true; + } + catch (SharpGenException dxException) + { + if (dxException.ResultCode == Result.AccessDenied) + { + try + { + Restart(); + } + catch { Thread.Sleep(100); } + } + } + } + + return result; + } + + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) + { + if (_buffer == null) return; + + using IDisposable @lock = captureZone.Lock(); + { + if (captureZone.DownscaleLevel == 0) + CopyZone(captureZone, buffer); + else + DownscaleZone(captureZone, buffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyZone(CaptureZone captureZone, in Span buffer) + { + ReadOnlySpan source = MemoryMarshal.Cast(_buffer); + Span target = MemoryMarshal.Cast(buffer); + + int offsetX = captureZone.X; + int offsetY = captureZone.Y; + int width = captureZone.Width; + int height = captureZone.Height; + + for (int y = 0; y < height; y++) + { + int sourceOffset = ((y + offsetY) * Display.Width) + offsetX; + int targetOffset = y * width; + + source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DownscaleZone(CaptureZone captureZone, in Span buffer) + { + ReadOnlySpan source = _buffer; + Span target = buffer; + + int blockSize = captureZone.DownscaleLevel switch + { + 1 => 2, + 2 => 4, + 3 => 8, + 4 => 16, + 5 => 32, + 6 => 64, + 7 => 128, + 8 => 256, + _ => (int)Math.Pow(2, captureZone.DownscaleLevel), + }; + + int offsetX = captureZone.X; + int offsetY = captureZone.Y; + int width = captureZone.Width; + int height = captureZone.Height; + int stride = captureZone.Stride; + int bpp = captureZone.ColorFormat.BytesPerPixel; + int unscaledWith = captureZone.UnscaledWidth; + + Span scaleBuffer = stackalloc byte[bpp]; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + AverageByteSampler.Sample(new SamplerInfo((x + offsetX) * blockSize, (y + offsetY) * blockSize, blockSize, blockSize, unscaledWith, bpp, source), scaleBuffer); + + int targetOffset = (y * stride) + (x * bpp); + + // DarthAffe 07.09.2023: Unroll as optimization since we know it's always 4 bpp - not ideal but it does quite a lot + target[targetOffset] = scaleBuffer[0]; + target[targetOffset + 1] = scaleBuffer[1]; + target[targetOffset + 2] = scaleBuffer[2]; + target[targetOffset + 3] = scaleBuffer[3]; + } + } + + /// + public override void Restart() + { + base.Restart(); + + lock (_captureLock) + { + DisposeDX(); + + try + { + PresentParameters presentParameters = new() + { + BackBufferWidth = Display.Width, + BackBufferHeight = Display.Height, + Windowed = true, + SwapEffect = SwapEffect.Discard + }; + _device = _direct3D9.CreateDevice(Display.Index, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, presentParameters); + _surface = _device.CreateOffscreenPlainSurface(Display.Width, Display.Height, Format.A8R8G8B8, Pool.Scratch); + _stride = Display.Width * ColorBGRA.ColorFormat.BytesPerPixel; + _buffer = new byte[Display.Height * _stride]; + } + catch + { + DisposeDX(); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DisposeDX(); + } + + private void DisposeDX() + { + try + { + try { _surface?.Dispose(); } catch { /**/} + try { _device?.Dispose(); } catch { /**/} + _buffer = null; + _device = null; + _surface = null; + } + catch { /**/ } + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.DX9/DX9ScreenCaptureService.cs b/ScreenCapture.NET.DX9/DX9ScreenCaptureService.cs new file mode 100644 index 0000000..8a725da --- /dev/null +++ b/ScreenCapture.NET.DX9/DX9ScreenCaptureService.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using Vortice.Direct3D9; + +namespace ScreenCapture.NET; + +/// +/// Represents a using the . +/// +public class DX9ScreenCaptureService : IScreenCaptureService +{ + #region Properties & Fields + + private readonly IDirect3D9 _direct3D9; + private readonly Dictionary _screenCaptures = new(); + + private bool _isDisposed; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public DX9ScreenCaptureService() + { + _direct3D9 = D3D9.Direct3DCreate9(); + } + + ~DX9ScreenCaptureService() => Dispose(); + + #endregion + + #region Methods + + /// + public IEnumerable GetGraphicsCards() + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + Dictionary graphicsCards = new(); + for (int i = 0; i < _direct3D9.AdapterCount; i++) + { + AdapterIdentifier adapterIdentifier = _direct3D9.GetAdapterIdentifier(i); + if (!graphicsCards.ContainsKey(adapterIdentifier.DeviceId)) + graphicsCards.Add(adapterIdentifier.DeviceId, new GraphicsCard(i, adapterIdentifier.Description, adapterIdentifier.VendorId, adapterIdentifier.DeviceId)); + } + + return graphicsCards.Values; + } + + /// + public IEnumerable GetDisplays(GraphicsCard graphicsCard) + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + for (int i = 0; i < _direct3D9.AdapterCount; i++) + { + AdapterIdentifier adapterIdentifier = _direct3D9.GetAdapterIdentifier(i); + if (adapterIdentifier.DeviceId == graphicsCard.DeviceId) + { + DisplayMode displayMode = _direct3D9.GetAdapterDisplayMode(i); + yield return new Display(i, adapterIdentifier.DeviceName, displayMode.Width, displayMode.Height, Rotation.None, graphicsCard); + } + } + } + + /// + IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display); + public DX9ScreenCapture GetScreenCapture(Display display) + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + if (!_screenCaptures.TryGetValue(display, out DX9ScreenCapture? screenCapture)) + _screenCaptures.Add(display, screenCapture = new DX9ScreenCapture(_direct3D9, display)); + return screenCapture; + } + + /// + public void Dispose() + { + if (_isDisposed) return; + + foreach (DX9ScreenCapture screenCapture in _screenCaptures.Values) + screenCapture.Dispose(); + _screenCaptures.Clear(); + + try { _direct3D9.Dispose(); } catch { /**/ } + + GC.SuppressFinalize(this); + + _isDisposed = true; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.DX9/Downscale/AverageByteSampler.cs b/ScreenCapture.NET.DX9/Downscale/AverageByteSampler.cs new file mode 100644 index 0000000..e8e7423 --- /dev/null +++ b/ScreenCapture.NET.DX9/Downscale/AverageByteSampler.cs @@ -0,0 +1,149 @@ +// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ScreenCapture.NET.Downscale; + +/// +/// Represents a sampled that averages multiple byte-data entries. +/// +internal static class AverageByteSampler +{ + #region Constants + + private static readonly int INT_VECTOR_LENGTH = Vector.Count; + + #endregion + + #region Methods + + public static unsafe void Sample(in SamplerInfo info, in Span pixelData) + { + int count = info.Width * info.Height; + if (count == 0) return; + + int dataLength = pixelData.Length; + Span sums = stackalloc uint[dataLength]; + + int elementsPerVector = Vector.Count / dataLength; + int valuesPerVector = elementsPerVector * dataLength; + if (Vector.IsHardwareAccelerated && (info.Height > 1) && (info.Width >= valuesPerVector) && (dataLength <= Vector.Count)) + { + int chunks = info.Width / elementsPerVector; + + Vector sum1 = Vector.Zero; + Vector sum2 = Vector.Zero; + Vector sum3 = Vector.Zero; + Vector sum4 = Vector.Zero; + + for (int y = 0; y < info.Height; y++) + { + ReadOnlySpan data = info[y]; + + fixed (byte* colorPtr = data) + { + byte* current = colorPtr; + for (int i = 0; i < chunks; i++) + { + Vector bytes = *(Vector*)current; + Vector.Widen(bytes, out Vector short1, out Vector short2); + Vector.Widen(short1, out Vector int1, out Vector int2); + Vector.Widen(short2, out Vector int3, out Vector int4); + + sum1 = Vector.Add(sum1, int1); + sum2 = Vector.Add(sum2, int2); + sum3 = Vector.Add(sum3, int3); + sum4 = Vector.Add(sum4, int4); + + current += valuesPerVector; + } + } + + int missingElements = data.Length - (chunks * valuesPerVector); + int offset = chunks * valuesPerVector; + for (int i = 0; i < missingElements; i += dataLength) + for (int j = 0; j < sums.Length; j++) + sums[j] += data[offset + i + j]; + } + + int value = 0; + int sumIndex = 0; + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum1[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum2[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum3[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum4[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + } + else + { + for (int y = 0; y < info.Height; y++) + { + ReadOnlySpan data = info[y]; + for (int i = 0; i < data.Length; i += dataLength) + for (int j = 0; j < sums.Length; j++) + sums[j] += data[i + j]; + } + } + + float divisor = count * byte.MaxValue; + for (int i = 0; i < pixelData.Length; i++) + pixelData[i] = (sums[i] / divisor).GetByteValueFromPercentage(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetByteValueFromPercentage(this float percentage) + { + if (float.IsNaN(percentage)) return 0; + + percentage = percentage.Clamp(0, 1.0f); + return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Clamp(this float value, float min, float max) + { + // ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10% + if (value < min) return min; + if (value > max) return max; + return value; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs b/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs new file mode 100644 index 0000000..97081e3 --- /dev/null +++ b/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs @@ -0,0 +1,63 @@ +// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs + +using System; + +namespace ScreenCapture.NET.Downscale; + +/// +/// Represents the information used to sample data. +/// +/// The type of the data to sample. +internal readonly ref struct SamplerInfo +{ + #region Properties & Fields + + private readonly ReadOnlySpan _data; + private readonly int _x; + private readonly int _y; + private readonly int _stride; + private readonly int _dataPerPixel; + private readonly int _dataWidth; + + /// + /// Gets the width of the region the data comes from. + /// + public readonly int Width; + + /// + /// Gets the height of region the data comes from. + /// + public readonly int Height; + + /// + /// Gets the data for the requested row. + /// + /// The row to get the data for. + /// A readonly span containing the data of the row. + public ReadOnlySpan this[int row] => _data.Slice((((_y + row) * _stride) + _x) * _dataPerPixel, _dataWidth); + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The width of the region the data comes from. + /// The height of region the data comes from. + /// The data to sample. + public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan data) + { + this._x = x; + this._y = y; + this._data = data; + this._stride = stride; + this._dataPerPixel = dataPerPixel; + this.Width = width; + this.Height = height; + + _dataWidth = width * dataPerPixel; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.DX9/Resources/icon.png b/ScreenCapture.NET.DX9/Resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46c50338bdcd4535ff63138ab7030dab6b11e747 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE(}r(cNo+^b@?U(MK}vQ zB8wRq_zr_Gjld?%{_ ztr9WvFf!(ud)_hqTTgv1lK=yg0|Q4xQ(Jdkg4JB9rJ^Rjo9|v{Of#O&87B4MV8Tqx z4J--_EDa10g$#`NIO`H_d}WsCKYsQ3gY4<$_kLD0Pp^0{GYzUeVYwf(_)H(Qv`p5; z7c+MIGuOJDO*?*FD1jft^xe4+S2zAkKQD-8--fqso^qvsB>H@v%YPLtyk9P!U(~eM zu*KE;fR(M!C!>@57OoXPuv_IkZ|2#}4d!b5->DVYpZxAC`z@F0_kPYj*=M(deB*G^ zH{q7=1KrsNX3ajJQaS6%@`vkov(%jXmobia3;O{% zghLi|+^bJ?xonb_*<4kxD&Wn)1Pnm9boPaI?Z*D<4-)s(cGjN#{!{m)Hs`m)h0mrh zS^xQ3_j$KXED=m)r~azw7k%Bn+TNq+^DOz-k(2!Yf2@8#xn|0{{e6@Eym}CD@95hT zCGlkuDUbB;@%jF~)BCL;O1EM2x1H1Pv)uD83NZN7{B5T-w*!L+--1u#(!YX^mkIcB z + + net7.0-windows;net6.0-windows + latest + enable + true + + Darth Affe + Wyrez + en-US + en-US + ScreenCapture.NET.DX9 + ScreenCapture.NET.DX9 + ScreenCapture.NET.DX9 + ScreenCapture.NET.DX9 + ScreenCapture.NET + Vortice based Screen-Capturing + Vortice based Screen-Capturing using Desktop Duplication + Copyright © Darth Affe 2023 + Copyright © Darth Affe 2023 + icon.png + https://github.com/DarthAffe/ScreenCapture.NET + LGPL-2.1-only + Github + https://github.com/DarthAffe/ScreenCapture.NET + True + + + + + 2.0.0 + 2.0.0 + 2.0.0 + + ..\bin\ + true + True + True + snupkg + + + + $(DefineConstants);TRACE;DEBUG + true + full + false + + + + portable + true + $(NoWarn);CS1591;CS1572;CS1573 + $(DefineConstants);RELEASE + + + + + True + + + + + + + + + + + + + + + + + diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index abd9a1d..e927c38 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "S EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CF7A1475-3A44-4870-A80F-5988DA25418B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.Tests", "Tests\ScreenCapture.NET.Tests\ScreenCapture.NET.Tests.csproj", "{AA1829BB-EFA7-4BB8-8041-76374659A42B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.Tests", "Tests\ScreenCapture.NET.Tests\ScreenCapture.NET.Tests.csproj", "{AA1829BB-EFA7-4BB8-8041-76374659A42B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "ScreenCapture.NET.DX9\ScreenCapture.NET.DX9.csproj", "{27EB5B17-2F83-43BA-A21F-06D93948B8BF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -29,6 +31,10 @@ Global {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.Build.0 = Release|Any CPU + {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj index fbf4484..b4b9251 100644 --- a/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj +++ b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj @@ -9,10 +9,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 94471f08b49b657837f07abdb689298d02f15af2 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 00:37:40 +0200 Subject: [PATCH 14/18] Added capture-service for X11 (linux); Added some more color formats --- .../ScreenCapture.NET.DX9.csproj | 4 - .../Downscale/AverageByteSampler.cs | 149 +++++++++ .../Downscale/SamplerInfo.cs | 63 ++++ ScreenCapture.NET.X11/Resources/icon.png | Bin 0 -> 705 bytes .../ScreenCapture.NET.X11.csproj | 68 ++++ ScreenCapture.NET.X11/X11.cs | 133 ++++++++ ScreenCapture.NET.X11/X11ScreenCapture.cs | 300 ++++++++++++++++++ .../X11ScreenCaptureService.cs | 102 ++++++ ScreenCapture.NET.sln | 6 + .../Generic/AbstractScreenCapture.cs | 15 +- ScreenCapture.NET/Model/Colors/ColorABGR.cs | 47 +++ ScreenCapture.NET/Model/Colors/ColorFormat.cs | 8 +- ScreenCapture.NET/Model/Colors/ColorRGBA.cs | 47 +++ 13 files changed, 928 insertions(+), 14 deletions(-) create mode 100644 ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs create mode 100644 ScreenCapture.NET.X11/Downscale/SamplerInfo.cs create mode 100644 ScreenCapture.NET.X11/Resources/icon.png create mode 100644 ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj create mode 100644 ScreenCapture.NET.X11/X11.cs create mode 100644 ScreenCapture.NET.X11/X11ScreenCapture.cs create mode 100644 ScreenCapture.NET.X11/X11ScreenCaptureService.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorABGR.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorRGBA.cs diff --git a/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj b/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj index 3db631c..caceecc 100644 --- a/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj +++ b/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj @@ -68,8 +68,4 @@ - - - - diff --git a/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs b/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs new file mode 100644 index 0000000..e8e7423 --- /dev/null +++ b/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs @@ -0,0 +1,149 @@ +// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ScreenCapture.NET.Downscale; + +/// +/// Represents a sampled that averages multiple byte-data entries. +/// +internal static class AverageByteSampler +{ + #region Constants + + private static readonly int INT_VECTOR_LENGTH = Vector.Count; + + #endregion + + #region Methods + + public static unsafe void Sample(in SamplerInfo info, in Span pixelData) + { + int count = info.Width * info.Height; + if (count == 0) return; + + int dataLength = pixelData.Length; + Span sums = stackalloc uint[dataLength]; + + int elementsPerVector = Vector.Count / dataLength; + int valuesPerVector = elementsPerVector * dataLength; + if (Vector.IsHardwareAccelerated && (info.Height > 1) && (info.Width >= valuesPerVector) && (dataLength <= Vector.Count)) + { + int chunks = info.Width / elementsPerVector; + + Vector sum1 = Vector.Zero; + Vector sum2 = Vector.Zero; + Vector sum3 = Vector.Zero; + Vector sum4 = Vector.Zero; + + for (int y = 0; y < info.Height; y++) + { + ReadOnlySpan data = info[y]; + + fixed (byte* colorPtr = data) + { + byte* current = colorPtr; + for (int i = 0; i < chunks; i++) + { + Vector bytes = *(Vector*)current; + Vector.Widen(bytes, out Vector short1, out Vector short2); + Vector.Widen(short1, out Vector int1, out Vector int2); + Vector.Widen(short2, out Vector int3, out Vector int4); + + sum1 = Vector.Add(sum1, int1); + sum2 = Vector.Add(sum2, int2); + sum3 = Vector.Add(sum3, int3); + sum4 = Vector.Add(sum4, int4); + + current += valuesPerVector; + } + } + + int missingElements = data.Length - (chunks * valuesPerVector); + int offset = chunks * valuesPerVector; + for (int i = 0; i < missingElements; i += dataLength) + for (int j = 0; j < sums.Length; j++) + sums[j] += data[offset + i + j]; + } + + int value = 0; + int sumIndex = 0; + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum1[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum2[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum3[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + + for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++) + { + sums[sumIndex] += sum4[j]; + ++sumIndex; + ++value; + + if (sumIndex >= dataLength) + sumIndex = 0; + } + } + else + { + for (int y = 0; y < info.Height; y++) + { + ReadOnlySpan data = info[y]; + for (int i = 0; i < data.Length; i += dataLength) + for (int j = 0; j < sums.Length; j++) + sums[j] += data[i + j]; + } + } + + float divisor = count * byte.MaxValue; + for (int i = 0; i < pixelData.Length; i++) + pixelData[i] = (sums[i] / divisor).GetByteValueFromPercentage(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetByteValueFromPercentage(this float percentage) + { + if (float.IsNaN(percentage)) return 0; + + percentage = percentage.Clamp(0, 1.0f); + return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Clamp(this float value, float min, float max) + { + // ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10% + if (value < min) return min; + if (value > max) return max; + return value; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs new file mode 100644 index 0000000..97081e3 --- /dev/null +++ b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs @@ -0,0 +1,63 @@ +// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs + +using System; + +namespace ScreenCapture.NET.Downscale; + +/// +/// Represents the information used to sample data. +/// +/// The type of the data to sample. +internal readonly ref struct SamplerInfo +{ + #region Properties & Fields + + private readonly ReadOnlySpan _data; + private readonly int _x; + private readonly int _y; + private readonly int _stride; + private readonly int _dataPerPixel; + private readonly int _dataWidth; + + /// + /// Gets the width of the region the data comes from. + /// + public readonly int Width; + + /// + /// Gets the height of region the data comes from. + /// + public readonly int Height; + + /// + /// Gets the data for the requested row. + /// + /// The row to get the data for. + /// A readonly span containing the data of the row. + public ReadOnlySpan this[int row] => _data.Slice((((_y + row) * _stride) + _x) * _dataPerPixel, _dataWidth); + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The width of the region the data comes from. + /// The height of region the data comes from. + /// The data to sample. + public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan data) + { + this._x = x; + this._y = y; + this._data = data; + this._stride = stride; + this._dataPerPixel = dataPerPixel; + this.Width = width; + this.Height = height; + + _dataWidth = width * dataPerPixel; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.X11/Resources/icon.png b/ScreenCapture.NET.X11/Resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46c50338bdcd4535ff63138ab7030dab6b11e747 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE(}r(cNo+^b@?U(MK}vQ zB8wRq_zr_Gjld?%{_ ztr9WvFf!(ud)_hqTTgv1lK=yg0|Q4xQ(Jdkg4JB9rJ^Rjo9|v{Of#O&87B4MV8Tqx z4J--_EDa10g$#`NIO`H_d}WsCKYsQ3gY4<$_kLD0Pp^0{GYzUeVYwf(_)H(Qv`p5; z7c+MIGuOJDO*?*FD1jft^xe4+S2zAkKQD-8--fqso^qvsB>H@v%YPLtyk9P!U(~eM zu*KE;fR(M!C!>@57OoXPuv_IkZ|2#}4d!b5->DVYpZxAC`z@F0_kPYj*=M(deB*G^ zH{q7=1KrsNX3ajJQaS6%@`vkov(%jXmobia3;O{% zghLi|+^bJ?xonb_*<4kxD&Wn)1Pnm9boPaI?Z*D<4-)s(cGjN#{!{m)Hs`m)h0mrh zS^xQ3_j$KXED=m)r~azw7k%Bn+TNq+^DOz-k(2!Yf2@8#xn|0{{e6@Eym}CD@95hT zCGlkuDUbB;@%jF~)BCL;O1EM2x1H1Pv)uD83NZN7{B5T-w*!L+--1u#(!YX^mkIcB z + + net7.0;net6.0 + linux-x64 + latest + enable + true + + Darth Affe + Wyrez + en-US + en-US + ScreenCapture.NET.X11 + ScreenCapture.NET.X11 + ScreenCapture.NET.X11 + ScreenCapture.NET.X11 + ScreenCapture.NET + Vortice based Screen-Capturing + Vortice based Screen-Capturing using Desktop Duplication + Copyright © Darth Affe 2023 + Copyright © Darth Affe 2023 + icon.png + https://github.com/DarthAffe/ScreenCapture.NET + LGPL-2.1-only + Github + https://github.com/DarthAffe/ScreenCapture.NET + True + + + + + 2.0.0 + 2.0.0 + 2.0.0 + + ..\bin\ + true + True + True + snupkg + + + + $(DefineConstants);TRACE;DEBUG + true + full + false + + + + portable + true + $(NoWarn);CS1591;CS1572;CS1573 + $(DefineConstants);RELEASE + + + + + True + + + + + + + + + diff --git a/ScreenCapture.NET.X11/X11.cs b/ScreenCapture.NET.X11/X11.cs new file mode 100644 index 0000000..85b8ec3 --- /dev/null +++ b/ScreenCapture.NET.X11/X11.cs @@ -0,0 +1,133 @@ +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +#if NET7_0_OR_GREATER +internal static partial class X11 +{ + internal const nint DISPLAY_NAME = 0; + + internal const long ALL_PLANES = -1; + internal const int ZPIXMAP = 2; + + [LibraryImport("libX11.so.6")] + internal static partial nint XOpenDisplay(nint displayName); + + [LibraryImport("libX11.so.6")] + internal static partial int XScreenCount(nint display); + + [LibraryImport("libX11.so.6")] + internal static partial nint XScreenOfDisplay(nint display, int screeenNumber); + + [LibraryImport("libX11.so.6")] + internal static partial int XWidthOfScreen(nint screen); + + [LibraryImport("libX11.so.6")] + internal static partial int XHeightOfScreen(nint screen); + + [LibraryImport("libX11.so.6")] + internal static partial nint XRootWindowOfScreen(nint screen); + + [LibraryImport("libX11.so.6")] + internal static partial nint XGetImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format); + + [LibraryImport("libX11.so.6")] + internal static partial nint XGetSubImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format, nint image, int destX, int dextY); + + [LibraryImport("libX11.so.6")] + internal static partial void XDestroyImage(nint image); + + [LibraryImport("libX11.so.6")] + internal static partial nint XDisplayString(nint display); + + [LibraryImport("libX11.so.6")] + internal static partial void XCloseDisplay(nint display); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct XImage + { + // ReSharper disable MemberCanBePrivate.Global + public int width; + public int height; + public int xoffset; + public int format; + public byte* data; + public int byte_order; + public int bitmap_unit; + public int bitmap_bit_order; + public int bitmap_pad; + public int depth; + public int bytes_per_line; + public int bits_per_pixel; + public uint red_mask; + public uint green_mask; + public uint blue_mask; + public nint obdata; + // ReSharper restore MemberCanBePrivate.Global + } +} +#else +internal static class X11 +{ + internal const nint DISPLAY_NAME = 0; + + internal const long ALL_PLANES = -1; + internal const int ZPIXMAP = 2; + + [DllImport("libX11.so.6")] + internal static extern nint XOpenDisplay(nint displayName); + + [DllImport("libX11.so.6")] + internal static extern int XScreenCount(nint display); + + [DllImport("libX11.so.6")] + internal static extern nint XScreenOfDisplay(nint display, int screeenNumber); + + [DllImport("libX11.so.6")] + internal static extern int XWidthOfScreen(nint screen); + + [DllImport("libX11.so.6")] + internal static extern int XHeightOfScreen(nint screen); + + [DllImport("libX11.so.6")] + internal static extern nint XRootWindowOfScreen(nint screen); + + [DllImport("libX11.so.6")] + internal static extern nint XGetImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format); + + [DllImport("libX11.so.6")] + internal static extern nint XGetSubImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format, nint image, int destX, int dextY); + + [DllImport("libX11.so.6")] + internal static extern void XDestroyImage(nint image); + + [DllImport("libX11.so.6")] + internal static extern nint XDisplayString(nint display); + + [DllImport("libX11.so.6")] + internal static extern void XCloseDisplay(nint display); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct XImage + { + // ReSharper disable MemberCanBePrivate.Global + public int width; + public int height; + public int xoffset; + public int format; + public byte* data; + public int byte_order; + public int bitmap_unit; + public int bitmap_bit_order; + public int bitmap_pad; + public int depth; + public int bytes_per_line; + public int bits_per_pixel; + public uint red_mask; + public uint green_mask; + public uint blue_mask; + public nint obdata; + // ReSharper restore MemberCanBePrivate.Global + } +} +#endif \ No newline at end of file diff --git a/ScreenCapture.NET.X11/X11ScreenCapture.cs b/ScreenCapture.NET.X11/X11ScreenCapture.cs new file mode 100644 index 0000000..562d9e1 --- /dev/null +++ b/ScreenCapture.NET.X11/X11ScreenCapture.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using ScreenCapture.NET.Downscale; + +namespace ScreenCapture.NET; + +/// +/// Represents a ScreenCapture using DirectX 11 desktop duplicaton. +/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api +/// +// ReSharper disable once InconsistentNaming +public sealed class X11ScreenCapture : AbstractScreenCapture +{ + #region Properties & Fields + + private readonly object _captureLock = new(); + + private nint _display; + + private readonly Dictionary, ZoneTextures> _textures = new(); + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The to duplicate. + public X11ScreenCapture(Display display) + : base(display) + { + Restart(); + } + + #endregion + + #region Methods + + protected override bool PerformScreenCapture() + { + lock (_captureLock) + { + if (_display == 0) + { + Restart(); + return false; + } + + lock (CaptureZones) + lock (_textures) + foreach (CaptureZone captureZone in CaptureZones) + { + if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) break; + textures.Update(); + } + + return true; + } + } + + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) + { + lock (_textures) + { + using IDisposable @lock = captureZone.Lock(); + { + if (captureZone.DownscaleLevel == 0) + CopyZone(captureZone, buffer); + else + DownscaleZone(captureZone, buffer); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyZone(CaptureZone captureZone, in Span buffer) + { + if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return; + + ReadOnlySpan source = MemoryMarshal.Cast(textures.Data); + Span target = MemoryMarshal.Cast(buffer); + + int width = captureZone.Width; + int height = captureZone.Height; + int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel; + + for (int y = 0; y < height; y++) + { + int sourceOffset = y * sourceStride; + int targetOffset = y * width; + source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DownscaleZone(CaptureZone captureZone, in Span buffer) + { + if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return; + + ReadOnlySpan source = textures.Data; + Span target = buffer; + + int blockSize = captureZone.DownscaleLevel switch + { + 1 => 2, + 2 => 4, + 3 => 8, + 4 => 16, + 5 => 32, + 6 => 64, + 7 => 128, + 8 => 256, + _ => (int)Math.Pow(2, captureZone.DownscaleLevel), + }; + + int width = captureZone.Width; + int height = captureZone.Height; + int stride = captureZone.Stride; + int bpp = captureZone.ColorFormat.BytesPerPixel; + int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel; + + Span scaleBuffer = stackalloc byte[bpp]; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + AverageByteSampler.Sample(new SamplerInfo(x * blockSize, y * blockSize, blockSize, blockSize, sourceStride, bpp, source), scaleBuffer); + + int targetOffset = (y * stride) + (x * bpp); + + // DarthAffe 09.09.2023: Unroll as optimization since we know it's always 4 bpp - not ideal but it does quite a lot + target[targetOffset] = scaleBuffer[0]; + target[targetOffset + 1] = scaleBuffer[1]; + target[targetOffset + 2] = scaleBuffer[2]; + target[targetOffset + 3] = scaleBuffer[3]; + } + } + + /// + public override CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0) + { + CaptureZone captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel); + + lock (_textures) + InitializeCaptureZone(captureZone); + + return captureZone; + } + + /// + public override bool UnregisterCaptureZone(CaptureZone captureZone) + { + if (!base.UnregisterCaptureZone(captureZone)) return false; + + lock (_textures) + { + if (_textures.TryGetValue(captureZone, out ZoneTextures? textures)) + { + textures.Dispose(); + _textures.Remove(captureZone); + + return true; + } + + return false; + } + } + + /// + public override void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null) + { + base.UpdateCaptureZone(captureZone, x, y, width, height, downscaleLevel); + + if ((width != null) || (height != null)) + { + lock (_textures) + { + if (_textures.TryGetValue(captureZone, out ZoneTextures? textures)) + { + textures.Dispose(); + InitializeCaptureZone(captureZone); + } + } + } + } + + private void InitializeCaptureZone(in CaptureZone captureZone) + => _textures[captureZone] = new ZoneTextures(_display, captureZone.Display.Index, captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight); + + /// + public override void Restart() + { + base.Restart(); + + lock (_captureLock) + { + DisposeDisplay(); + try + { + _display = X11.XOpenDisplay(X11.DISPLAY_NAME); + } + catch + { + DisposeDisplay(); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + lock (_captureLock) + { + try + { + foreach ((CaptureZone _, ZoneTextures textures) in _textures) + textures.Dispose(); + + DisposeDisplay(); + } + catch { /**/ } + } + } + + private void DisposeDisplay() + { + if (_display == 0) return; + + try { X11.XCloseDisplay(_display); } catch { /**/ } + } + + #endregion + + private sealed class ZoneTextures : IDisposable + { + #region Properties & Fields + + private readonly int _screenNumber; + private readonly int _x; + private readonly int _y; + private readonly uint _width; + private readonly uint _height; + private readonly int _size; + + private nint _display; + private nint _drawable; + public nint ImageHandle { get; private set; } + public X11.XImage Image { get; private set; } + public unsafe ReadOnlySpan Data => new(Image.data, _size); + + #endregion + + #region Constructors + + public ZoneTextures(nint display, int screenNumber, int x, int y, int width, int height) + { + this._screenNumber = screenNumber; + this._x = x; + this._y = y; + this._width = (uint)width; + this._height = (uint)height; + + _size = width * height * ColorBGRA.ColorFormat.BytesPerPixel; + Initialize(display); + } + + #endregion + + #region Methods + + public void Initialize(nint display) + { + Dispose(); + + _display = display; + + nint screen = X11.XScreenOfDisplay(_display, _screenNumber); + _drawable = X11.XRootWindowOfScreen(screen); + ImageHandle = X11.XGetImage(display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP); + Image = Marshal.PtrToStructure(ImageHandle); + + if (Image.bits_per_pixel != (ColorBGRA.ColorFormat.BytesPerPixel * 8)) throw new NotSupportedException("The X-Server is configured to a not supported pixel-format. Needs to be 32 bit per pixel BGR."); + } + + public void Update() => X11.XGetSubImage(_display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP, ImageHandle, 0, 0); + + public void Dispose() + { + if (ImageHandle != 0) + try { X11.XDestroyImage(ImageHandle); } catch { /**/ } + + Image = default; + } + + #endregion + } +} \ No newline at end of file diff --git a/ScreenCapture.NET.X11/X11ScreenCaptureService.cs b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs new file mode 100644 index 0000000..c54f5d3 --- /dev/null +++ b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +/// +/// Represents a using the . +/// +public class X11ScreenCaptureService : IScreenCaptureService +{ + #region Properties & Fields + + private readonly Dictionary _screenCaptures = new(); + + private bool _isDisposed; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public X11ScreenCaptureService() + { } + + ~X11ScreenCaptureService() => Dispose(); + + #endregion + + #region Methods + + /// + public IEnumerable GetGraphicsCards() + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + nint display = X11.XOpenDisplay(X11.DISPLAY_NAME); + try + { + string name = Marshal.PtrToStringAnsi(X11.XDisplayString(display)) ?? string.Empty; + yield return new GraphicsCard(0, name, 0, 0); + } + finally + { + X11.XCloseDisplay(display); + } + } + + /// + public IEnumerable GetDisplays(GraphicsCard graphicsCard) + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + nint display = X11.XOpenDisplay(X11.DISPLAY_NAME); + try + { + int screenCount = X11.XScreenCount(display); + for (int screenNumber = 0; screenNumber < screenCount; screenNumber++) + { + nint screen = X11.XScreenOfDisplay(display, screenNumber); + int screenWidth = X11.XWidthOfScreen(screen); + int screenHeight = X11.XHeightOfScreen(screen); + + // DarthAffe 10.09.2023: Emulate DX-Displaynames for no real reason ¯\(°_o)/¯ + yield return new Display(screenNumber, @$"\\.\DISPLAY{screenNumber + 1}", screenWidth, screenHeight, Rotation.None, graphicsCard); + } + } + finally + { + X11.XCloseDisplay(display); + } + } + + /// + IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display); + public X11ScreenCapture GetScreenCapture(Display display) + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + if (!_screenCaptures.TryGetValue(display, out X11ScreenCapture? screenCapture)) + _screenCaptures.Add(display, screenCapture = new X11ScreenCapture(display)); + return screenCapture; + } + + /// + public void Dispose() + { + if (_isDisposed) return; + + foreach (X11ScreenCapture screenCapture in _screenCaptures.Values) + screenCapture.Dispose(); + _screenCaptures.Clear(); + + GC.SuppressFinalize(this); + + _isDisposed = true; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index e927c38..b91b878 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.Tests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "ScreenCapture.NET.DX9\ScreenCapture.NET.DX9.csproj", "{27EB5B17-2F83-43BA-A21F-06D93948B8BF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.ActiveCfg = Release|Any CPU {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.Build.0 = Release|Any CPU + {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs index cffd4b4..f7551f1 100644 --- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs +++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs @@ -51,15 +51,16 @@ public abstract class AbstractScreenCapture : IScreenCapture result = false; } - foreach (CaptureZone captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested)) - { - try + lock (CaptureZones) + foreach (CaptureZone captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested)) { - PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer); - captureZone.SetUpdated(); + try + { + PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer); + captureZone.SetUpdated(); + } + catch { /* */ } } - catch { /* */ } - } OnUpdated(result); diff --git a/ScreenCapture.NET/Model/Colors/ColorABGR.cs b/ScreenCapture.NET/Model/Colors/ColorABGR.cs new file mode 100644 index 0000000..511a80b --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorABGR.cs @@ -0,0 +1,47 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorABGR : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.ABGR; + + private readonly byte _a; + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => _a; + public byte B => _b; + public byte G => _g; + public byte R => _r; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorABGR(byte a, byte b, byte g, byte r) + { + this._a = a; + this._b = b; + this._g = g; + this._r = r; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs index eafc1e6..ff3b03a 100644 --- a/ScreenCapture.NET/Model/Colors/ColorFormat.cs +++ b/ScreenCapture.NET/Model/Colors/ColorFormat.cs @@ -5,9 +5,11 @@ public readonly struct ColorFormat #region Instances public static readonly ColorFormat BGRA = new(1, 4); - public static readonly ColorFormat ARGB = new(2, 4); - public static readonly ColorFormat RGB = new(3, 3); - public static readonly ColorFormat BGR = new(4, 3); + public static readonly ColorFormat ABGR = new(2, 4); + public static readonly ColorFormat RGBA = new(3, 4); + public static readonly ColorFormat ARGB = new(4, 4); + public static readonly ColorFormat BGR = new(5, 3); + public static readonly ColorFormat RGB = new(6, 3); #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorRGBA.cs b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs new file mode 100644 index 0000000..0b01e87 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs @@ -0,0 +1,47 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorRGBA : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.RGBA; + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + private readonly byte _a; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte R => _r; + public byte G => _g; + public byte B => _b; + public byte A => _a; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorRGBA(byte r, byte g, byte b, byte a) + { + this._r = r; + this._g = g; + this._b = b; + this._a = a; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file From 07a175e552f8681b3be78c12fdabea880f56d296 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 00:40:33 +0200 Subject: [PATCH 15/18] Fixed comment --- ScreenCapture.NET.X11/X11ScreenCapture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ScreenCapture.NET.X11/X11ScreenCapture.cs b/ScreenCapture.NET.X11/X11ScreenCapture.cs index 562d9e1..0531622 100644 --- a/ScreenCapture.NET.X11/X11ScreenCapture.cs +++ b/ScreenCapture.NET.X11/X11ScreenCapture.cs @@ -7,7 +7,7 @@ using ScreenCapture.NET.Downscale; namespace ScreenCapture.NET; /// -/// Represents a ScreenCapture using DirectX 11 desktop duplicaton. +/// Represents a ScreenCapture using libX11. /// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api /// // ReSharper disable once InconsistentNaming From a1a43ddcfc620038084ac7d1c164baa612784f35 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 20:47:46 +0200 Subject: [PATCH 16/18] Changed X11 capture to only use a single fullscreen texture --- ScreenCapture.NET.X11/X11ScreenCapture.cs | 190 +++++----------------- 1 file changed, 37 insertions(+), 153 deletions(-) diff --git a/ScreenCapture.NET.X11/X11ScreenCapture.cs b/ScreenCapture.NET.X11/X11ScreenCapture.cs index 0531622..cf13cef 100644 --- a/ScreenCapture.NET.X11/X11ScreenCapture.cs +++ b/ScreenCapture.NET.X11/X11ScreenCapture.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ScreenCapture.NET.Downscale; @@ -18,8 +17,11 @@ public sealed class X11ScreenCapture : AbstractScreenCapture private readonly object _captureLock = new(); private nint _display; - - private readonly Dictionary, ZoneTextures> _textures = new(); + private nint _drawable; + private nint _imageHandle; + private X11.XImage _image; + private int _size; + private unsafe ReadOnlySpan Data => new(_image.data, _size); #endregion @@ -43,19 +45,13 @@ public sealed class X11ScreenCapture : AbstractScreenCapture { lock (_captureLock) { - if (_display == 0) + if ((_display == 0) || (_imageHandle == 0)) { Restart(); return false; } - lock (CaptureZones) - lock (_textures) - foreach (CaptureZone captureZone in CaptureZones) - { - if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) break; - textures.Update(); - } + X11.XGetSubImage(_display, _drawable, 0, 0, (uint)Display.Width, (uint)Display.Height, X11.ALL_PLANES, X11.ZPIXMAP, _imageHandle, 0, 0); return true; } @@ -63,33 +59,30 @@ public sealed class X11ScreenCapture : AbstractScreenCapture protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { - lock (_textures) + using IDisposable @lock = captureZone.Lock(); { - using IDisposable @lock = captureZone.Lock(); - { - if (captureZone.DownscaleLevel == 0) - CopyZone(captureZone, buffer); - else - DownscaleZone(captureZone, buffer); - } + if (captureZone.DownscaleLevel == 0) + CopyZone(captureZone, buffer); + else + DownscaleZone(captureZone, buffer); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CopyZone(CaptureZone captureZone, in Span buffer) { - if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return; - - ReadOnlySpan source = MemoryMarshal.Cast(textures.Data); + ReadOnlySpan source = MemoryMarshal.Cast(Data); Span target = MemoryMarshal.Cast(buffer); + int offsetX = captureZone.X; + int offsetY = captureZone.Y; int width = captureZone.Width; int height = captureZone.Height; - int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel; + int sourceStride = _image.bytes_per_line / captureZone.ColorFormat.BytesPerPixel; for (int y = 0; y < height; y++) { - int sourceOffset = y * sourceStride; + int sourceOffset = ((y + offsetY) * sourceStride) + offsetX; int targetOffset = y * width; source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width)); } @@ -98,9 +91,7 @@ public sealed class X11ScreenCapture : AbstractScreenCapture [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DownscaleZone(CaptureZone captureZone, in Span buffer) { - if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return; - - ReadOnlySpan source = textures.Data; + ReadOnlySpan source = Data; Span target = buffer; int blockSize = captureZone.DownscaleLevel switch @@ -116,17 +107,19 @@ public sealed class X11ScreenCapture : AbstractScreenCapture _ => (int)Math.Pow(2, captureZone.DownscaleLevel), }; + int offsetX = captureZone.X; + int offsetY = captureZone.Y; int width = captureZone.Width; int height = captureZone.Height; int stride = captureZone.Stride; int bpp = captureZone.ColorFormat.BytesPerPixel; - int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel; + int sourceStride = _image.bytes_per_line / bpp; Span scaleBuffer = stackalloc byte[bpp]; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { - AverageByteSampler.Sample(new SamplerInfo(x * blockSize, y * blockSize, blockSize, blockSize, sourceStride, bpp, source), scaleBuffer); + AverageByteSampler.Sample(new SamplerInfo((x + offsetX) * blockSize, (y + offsetY) * blockSize, blockSize, blockSize, sourceStride, bpp, source), scaleBuffer); int targetOffset = (y * stride) + (x * bpp); @@ -138,57 +131,6 @@ public sealed class X11ScreenCapture : AbstractScreenCapture } } - /// - public override CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0) - { - CaptureZone captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel); - - lock (_textures) - InitializeCaptureZone(captureZone); - - return captureZone; - } - - /// - public override bool UnregisterCaptureZone(CaptureZone captureZone) - { - if (!base.UnregisterCaptureZone(captureZone)) return false; - - lock (_textures) - { - if (_textures.TryGetValue(captureZone, out ZoneTextures? textures)) - { - textures.Dispose(); - _textures.Remove(captureZone); - - return true; - } - - return false; - } - } - - /// - public override void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null) - { - base.UpdateCaptureZone(captureZone, x, y, width, height, downscaleLevel); - - if ((width != null) || (height != null)) - { - lock (_textures) - { - if (_textures.TryGetValue(captureZone, out ZoneTextures? textures)) - { - textures.Dispose(); - InitializeCaptureZone(captureZone); - } - } - } - } - - private void InitializeCaptureZone(in CaptureZone captureZone) - => _textures[captureZone] = new ZoneTextures(_display, captureZone.Display.Index, captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight); - /// public override void Restart() { @@ -200,6 +142,14 @@ public sealed class X11ScreenCapture : AbstractScreenCapture try { _display = X11.XOpenDisplay(X11.DISPLAY_NAME); + + nint screen = X11.XScreenOfDisplay(_display, Display.Index); + _drawable = X11.XRootWindowOfScreen(screen); + _imageHandle = X11.XGetImage(_display, _drawable, 0, 0, (uint)Display.Width, (uint)Display.Height, X11.ALL_PLANES, X11.ZPIXMAP); + _image = Marshal.PtrToStructure(_imageHandle); + _size = _image.bytes_per_line * _image.height; + + if (_image.bits_per_pixel != (ColorBGRA.ColorFormat.BytesPerPixel * 8)) throw new NotSupportedException("The X-Server is configured to a not supported pixel-format. Needs to be 32 bit per pixel BGR."); } catch { @@ -214,87 +164,21 @@ public sealed class X11ScreenCapture : AbstractScreenCapture lock (_captureLock) { - try - { - foreach ((CaptureZone _, ZoneTextures textures) in _textures) - textures.Dispose(); - - DisposeDisplay(); - } + try { DisposeDisplay(); } catch { /**/ } } } private void DisposeDisplay() { - if (_display == 0) return; + if (_imageHandle != 0) + try { X11.XDestroyImage(_imageHandle); } catch { /**/ } - try { X11.XCloseDisplay(_display); } catch { /**/ } + _image = default; + + if (_display != 0) + try { X11.XCloseDisplay(_display); } catch { /**/ } } #endregion - - private sealed class ZoneTextures : IDisposable - { - #region Properties & Fields - - private readonly int _screenNumber; - private readonly int _x; - private readonly int _y; - private readonly uint _width; - private readonly uint _height; - private readonly int _size; - - private nint _display; - private nint _drawable; - public nint ImageHandle { get; private set; } - public X11.XImage Image { get; private set; } - public unsafe ReadOnlySpan Data => new(Image.data, _size); - - #endregion - - #region Constructors - - public ZoneTextures(nint display, int screenNumber, int x, int y, int width, int height) - { - this._screenNumber = screenNumber; - this._x = x; - this._y = y; - this._width = (uint)width; - this._height = (uint)height; - - _size = width * height * ColorBGRA.ColorFormat.BytesPerPixel; - Initialize(display); - } - - #endregion - - #region Methods - - public void Initialize(nint display) - { - Dispose(); - - _display = display; - - nint screen = X11.XScreenOfDisplay(_display, _screenNumber); - _drawable = X11.XRootWindowOfScreen(screen); - ImageHandle = X11.XGetImage(display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP); - Image = Marshal.PtrToStructure(ImageHandle); - - if (Image.bits_per_pixel != (ColorBGRA.ColorFormat.BytesPerPixel * 8)) throw new NotSupportedException("The X-Server is configured to a not supported pixel-format. Needs to be 32 bit per pixel BGR."); - } - - public void Update() => X11.XGetSubImage(_display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP, ImageHandle, 0, 0); - - public void Dispose() - { - if (ImageHandle != 0) - try { X11.XDestroyImage(ImageHandle); } catch { /**/ } - - Image = default; - } - - #endregion - } } \ No newline at end of file From bcb92eb422aec6cfe828f7bf5da83cd682106650 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 22:20:06 +0200 Subject: [PATCH 17/18] Added some missing doc-comments --- ScreenCapture.NET.DX11/DX11ScreenCapture.cs | 10 ++- ScreenCapture.NET.DX9/DX9ScreenCapture.cs | 14 +++- .../Downscale/SamplerInfo.cs | 4 + .../Downscale/SamplerInfo.cs | 4 + ScreenCapture.NET.X11/X11ScreenCapture.cs | 7 +- .../X11ScreenCaptureService.cs | 2 + .../Generic/AbstractScreenCapture.cs | 51 ++++++++++++ ScreenCapture.NET/Model/CaptureZone.cs | 59 ++++++-------- ScreenCapture.NET/Model/Colors/ColorABGR.cs | 21 ++++- ScreenCapture.NET/Model/Colors/ColorARGB.cs | 19 +++++ ScreenCapture.NET/Model/Colors/ColorBGR.cs | 18 +++++ ScreenCapture.NET/Model/Colors/ColorBGRA.cs | 19 +++++ ScreenCapture.NET/Model/Colors/ColorFormat.cs | 10 +++ ScreenCapture.NET/Model/Colors/ColorRGB.cs | 18 +++++ ScreenCapture.NET/Model/Colors/ColorRGBA.cs | 19 +++++ ScreenCapture.NET/Model/Colors/IColor.cs | 21 +++++ ScreenCapture.NET/Model/ICaptureZone.cs | 45 ++++++++++- ScreenCapture.NET/Model/IImage.cs | 80 +++++++++++++++++++ ScreenCapture.NET/Model/Image.cs | 30 +++++++ 19 files changed, 406 insertions(+), 45 deletions(-) diff --git a/ScreenCapture.NET.DX11/DX11ScreenCapture.cs b/ScreenCapture.NET.DX11/DX11ScreenCapture.cs index d64d8ee..618711d 100644 --- a/ScreenCapture.NET.DX11/DX11ScreenCapture.cs +++ b/ScreenCapture.NET.DX11/DX11ScreenCapture.cs @@ -68,7 +68,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture /// The used to create underlying objects. /// The to duplicate. /// Indicates if the DuplicateOutput1 interface should be used instead of the older DuplicateOutput. Currently there's no real use in setting this to true. - public DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false) + internal DX11ScreenCapture(IDXGIFactory1 factory, Display display, bool useNewDuplicationAdapter = false) : base(display) { this._factory = factory; @@ -81,6 +81,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture #region Methods + /// protected override bool PerformScreenCapture() { bool result = false; @@ -133,6 +134,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture return result; } + /// protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { if (_context == null) return; @@ -299,6 +301,7 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } } + /// protected override void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel) { if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized."); @@ -429,10 +432,13 @@ public sealed class DX11ScreenCapture : AbstractScreenCapture } } + /// protected override void Dispose(bool disposing) { base.Dispose(disposing); - DisposeDX(); + + lock (_captureLock) + DisposeDX(); } private void DisposeDX() diff --git a/ScreenCapture.NET.DX9/DX9ScreenCapture.cs b/ScreenCapture.NET.DX9/DX9ScreenCapture.cs index bf057b6..cb7aec6 100644 --- a/ScreenCapture.NET.DX9/DX9ScreenCapture.cs +++ b/ScreenCapture.NET.DX9/DX9ScreenCapture.cs @@ -9,8 +9,8 @@ using Vortice.Direct3D9; namespace ScreenCapture.NET; /// -/// Represents a ScreenCapture using DirectX 11 desktop duplicaton. -/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api +/// Represents a ScreenCapture using DirectX 9. +/// https://learn.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-getfrontbufferdata /// // ReSharper disable once InconsistentNaming public sealed class DX9ScreenCapture : AbstractScreenCapture @@ -32,8 +32,9 @@ public sealed class DX9ScreenCapture : AbstractScreenCapture /// /// Initializes a new instance of the class. /// + /// The D3D9 instance used. /// The to duplicate. - public DX9ScreenCapture(IDirect3D9 direct3D9, Display display) + internal DX9ScreenCapture(IDirect3D9 direct3D9, Display display) : base(display) { this._direct3D9 = direct3D9; @@ -45,6 +46,7 @@ public sealed class DX9ScreenCapture : AbstractScreenCapture #region Methods + /// protected override bool PerformScreenCapture() { bool result = false; @@ -89,6 +91,7 @@ public sealed class DX9ScreenCapture : AbstractScreenCapture return result; } + /// protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { if (_buffer == null) return; @@ -195,10 +198,13 @@ public sealed class DX9ScreenCapture : AbstractScreenCapture } } + /// protected override void Dispose(bool disposing) { base.Dispose(disposing); - DisposeDX(); + + lock (_captureLock) + DisposeDX(); } private void DisposeDX() diff --git a/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs b/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs index 97081e3..1862de2 100644 --- a/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs +++ b/ScreenCapture.NET.DX9/Downscale/SamplerInfo.cs @@ -43,8 +43,12 @@ internal readonly ref struct SamplerInfo /// /// Initializes a new instance of the class. /// + /// The X-location of the region the data comes from. + /// The Y-location of the region the data comes from. /// The width of the region the data comes from. /// The height of region the data comes from. + /// The number of pixels in a row of data. + /// The number of {T} representing a single pixel. /// The data to sample. public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan data) { diff --git a/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs index 97081e3..1862de2 100644 --- a/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs +++ b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs @@ -43,8 +43,12 @@ internal readonly ref struct SamplerInfo /// /// Initializes a new instance of the class. /// + /// The X-location of the region the data comes from. + /// The Y-location of the region the data comes from. /// The width of the region the data comes from. /// The height of region the data comes from. + /// The number of pixels in a row of data. + /// The number of {T} representing a single pixel. /// The data to sample. public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan data) { diff --git a/ScreenCapture.NET.X11/X11ScreenCapture.cs b/ScreenCapture.NET.X11/X11ScreenCapture.cs index cf13cef..c35339e 100644 --- a/ScreenCapture.NET.X11/X11ScreenCapture.cs +++ b/ScreenCapture.NET.X11/X11ScreenCapture.cs @@ -7,7 +7,7 @@ namespace ScreenCapture.NET; /// /// Represents a ScreenCapture using libX11. -/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api +/// https://x.org/releases/current/doc/libX11/libX11/libX11.html#XGetImage /// // ReSharper disable once InconsistentNaming public sealed class X11ScreenCapture : AbstractScreenCapture @@ -31,7 +31,7 @@ public sealed class X11ScreenCapture : AbstractScreenCapture /// Initializes a new instance of the class. /// /// The to duplicate. - public X11ScreenCapture(Display display) + internal X11ScreenCapture(Display display) : base(display) { Restart(); @@ -41,6 +41,7 @@ public sealed class X11ScreenCapture : AbstractScreenCapture #region Methods + /// protected override bool PerformScreenCapture() { lock (_captureLock) @@ -57,6 +58,7 @@ public sealed class X11ScreenCapture : AbstractScreenCapture } } + /// protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { using IDisposable @lock = captureZone.Lock(); @@ -158,6 +160,7 @@ public sealed class X11ScreenCapture : AbstractScreenCapture } } + /// protected override void Dispose(bool disposing) { base.Dispose(disposing); diff --git a/ScreenCapture.NET.X11/X11ScreenCaptureService.cs b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs index c54f5d3..2e39c27 100644 --- a/ScreenCapture.NET.X11/X11ScreenCaptureService.cs +++ b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs @@ -75,6 +75,8 @@ public class X11ScreenCaptureService : IScreenCaptureService /// IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display); + + /// public X11ScreenCapture GetScreenCapture(Display display) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs index f7551f1..297d7e6 100644 --- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs +++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs @@ -4,6 +4,7 @@ using System.Linq; namespace ScreenCapture.NET; +/// public abstract class AbstractScreenCapture : IScreenCapture where TColor : struct, IColor { @@ -11,20 +12,29 @@ public abstract class AbstractScreenCapture : IScreenCapture private bool _isDisposed; + /// + /// Gets a list of registered on this ScreenCature. + /// protected HashSet> CaptureZones { get; } = new(); + /// public Display Display { get; } #endregion #region Events + /// public event EventHandler? Updated; #endregion #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The to duplicate. protected AbstractScreenCapture(Display display) { this.Display = display; @@ -36,6 +46,7 @@ public abstract class AbstractScreenCapture : IScreenCapture #region Methods + /// public virtual bool CaptureScreen() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); @@ -67,10 +78,23 @@ public abstract class AbstractScreenCapture : IScreenCapture return result; } + /// + /// Performs the actual screen capture. + /// + /// true if the screen was captured sucessfully; otherwise, false. protected abstract bool PerformScreenCapture(); + /// + /// Performs an update of the given capture zone. + /// + /// The capture zone to update. + /// The buffer containing the current pixel-data of the capture zone. protected abstract void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer); + /// + /// Raises the -event. + /// + /// A bool indicating whether the update was successful or not. protected virtual void OnUpdated(bool result) { try @@ -80,7 +104,10 @@ public abstract class AbstractScreenCapture : IScreenCapture 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); @@ -100,6 +127,15 @@ public abstract class AbstractScreenCapture : IScreenCapture } } + /// + /// Validates the given values of a capture zone. + /// + /// The X-location of the zone. + /// The Y-location of the zone. + /// The width of the zone. + /// The height of the zone. + /// The downscale-level of the zone. + /// Throws if some of the provided data is not valid. protected virtual void ValidateCaptureZoneAndThrow(int x, int y, int width, int height, int downscaleLevel) { if (x < 0) throw new ArgumentException("x < 0"); @@ -110,6 +146,13 @@ public abstract class AbstractScreenCapture : IScreenCapture if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height"); } + /// + /// Calculates the actual size when downscaling is used. + /// + /// The original width. + /// The original height. + /// The level of downscaling to be used. + /// A tuple containing the scaled width, the scaled height and the downscale-level used. (This can be smaller then the one provided if the image is not big enough to scale down that often.) protected virtual (int width, int height, int downscaleLevel) CalculateScaledSize(int width, int height, int downscaleLevel) { if (downscaleLevel > 0) @@ -131,7 +174,10 @@ public abstract class AbstractScreenCapture : IScreenCapture 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); @@ -139,8 +185,11 @@ public abstract class AbstractScreenCapture : IScreenCapture 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); @@ -169,6 +218,7 @@ public abstract class AbstractScreenCapture : IScreenCapture } } + /// public virtual void Restart() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); @@ -190,6 +240,7 @@ public abstract class AbstractScreenCapture : IScreenCapture _isDisposed = true; } + /// protected virtual void Dispose(bool disposing) { } #endregion diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index 2a30767..fca089c 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -17,98 +17,89 @@ public sealed class CaptureZone : ICaptureZone private readonly object _lock = new(); + /// public Display Display { get; } + /// public ColorFormat ColorFormat { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => TColor.ColorFormat; } - /// - /// Gets the x-location of the region on the screen. - /// + /// public int X { get; internal set; } - /// - /// Gets the y-location of the region on the screen. - /// + /// public int Y { get; internal set; } - /// - /// Gets the width of the captured region. - /// + /// public int Width { get; private set; } - /// - /// Gets the height of the captured region. - /// + /// public int Height { get; private set; } + /// public int Stride { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Width * ColorFormat.BytesPerPixel; } - /// - /// Gets the level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. - /// + /// public int DownscaleLevel { get; private set; } - /// - /// Gets the original width of the region (this equals if is 0). - /// + /// public int UnscaledWidth { get; private set; } - /// - /// Gets the original height of the region (this equals if is 0). - /// + /// public int UnscaledHeight { get; private set; } internal byte[] InternalBuffer { get; set; } + /// public ReadOnlySpan RawBuffer { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => InternalBuffer; } + /// + /// Gets the pixel-data of this zone. + /// public ReadOnlySpan Pixels { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => MemoryMarshal.Cast(RawBuffer); } + /// + /// Gets a . Basically the same as but with better performance. + /// public RefImage Image { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Pixels, 0, 0, Width, Height, Width); } + /// IImage ICaptureZone.Image { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Image(InternalBuffer, 0, 0, Width, Height, Width); } - /// - /// Gets or sets if the should be automatically updated on every captured frame. - /// + /// public bool AutoUpdate { get; set; } = true; - /// - /// Gets if an update for the is requested on the next captured frame. - /// + /// public bool IsUpdateRequested { get; private set; } #endregion #region Events - /// - /// Occurs when the is updated. - /// + /// public event EventHandler? Updated; #endregion @@ -146,6 +137,7 @@ public sealed class CaptureZone : ICaptureZone #region Methods + /// public RefImage GetRefImage() where T : struct, IColor { @@ -154,16 +146,14 @@ public sealed class CaptureZone : ICaptureZone return new RefImage(MemoryMarshal.Cast(RawBuffer), 0, 0, Width, Height, Width); } + /// public IDisposable Lock() { Monitor.Enter(_lock); return new UnlockDisposable(_lock); } - /// - /// Requests to update this when the next frame is captured. - /// Only necessary if is set to false. - /// + /// public void RequestUpdate() => IsUpdateRequested = true; /// @@ -209,6 +199,7 @@ public sealed class CaptureZone : ICaptureZone #region Methods + /// public void Dispose() { if (_disposed) throw new ObjectDisposedException("The lock is already released"); diff --git a/ScreenCapture.NET/Model/Colors/ColorABGR.cs b/ScreenCapture.NET/Model/Colors/ColorABGR.cs index 511a80b..00cfa36 100644 --- a/ScreenCapture.NET/Model/Colors/ColorABGR.cs +++ b/ScreenCapture.NET/Model/Colors/ColorABGR.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 32 bit ABGR-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorABGR : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.ABGR; private readonly byte _a; @@ -19,16 +23,30 @@ public readonly struct ColorABGR : IColor private readonly byte _r; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte A => _a; + + /// public byte B => _b; + + /// public byte G => _g; + + /// public byte R => _r; // ReSharper restore ConvertToAutoPropertyWhenPossible #endregion #region Constructors - + + /// + /// Initializes a new instance of the class. + /// + /// The alpha-component of the color. + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. public ColorABGR(byte a, byte b, byte g, byte r) { this._a = a; @@ -41,6 +59,7 @@ public readonly struct ColorABGR : IColor #region Methods + /// public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorARGB.cs b/ScreenCapture.NET/Model/Colors/ColorARGB.cs index c00980f..2758be5 100644 --- a/ScreenCapture.NET/Model/Colors/ColorARGB.cs +++ b/ScreenCapture.NET/Model/Colors/ColorARGB.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 32 bit ARGB-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorARGB : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.ARGB; private readonly byte _a; @@ -19,9 +23,16 @@ public readonly struct ColorARGB : IColor private readonly byte _b; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte A => _a; + + /// public byte R => _r; + + /// public byte G => _g; + + /// public byte B => _b; // ReSharper restore ConvertToAutoPropertyWhenPossible @@ -29,6 +40,13 @@ public readonly struct ColorARGB : IColor #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The alpha-component of the color. + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. public ColorARGB(byte a, byte r, byte g, byte b) { this._a = a; @@ -41,6 +59,7 @@ public readonly struct ColorARGB : IColor #region Methods + /// public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorBGR.cs b/ScreenCapture.NET/Model/Colors/ColorBGR.cs index 829d3b3..e6f1a12 100644 --- a/ScreenCapture.NET/Model/Colors/ColorBGR.cs +++ b/ScreenCapture.NET/Model/Colors/ColorBGR.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 24 bit BGR-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorBGR : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.BGR; private readonly byte _b; @@ -18,9 +22,16 @@ public readonly struct ColorBGR : IColor private readonly byte _r; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte A => byte.MaxValue; + + /// public byte B => _b; + + /// public byte G => _g; + + /// public byte R => _r; // ReSharper restore ConvertToAutoPropertyWhenPossible @@ -28,6 +39,12 @@ public readonly struct ColorBGR : IColor #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. public ColorBGR(byte b, byte g, byte r) { this._b = b; @@ -39,6 +56,7 @@ public readonly struct ColorBGR : IColor #region Methods + /// public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs index 4029709..1db7ea3 100644 --- a/ScreenCapture.NET/Model/Colors/ColorBGRA.cs +++ b/ScreenCapture.NET/Model/Colors/ColorBGRA.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 32 bit BGRA-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorBGRA : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.BGRA; private readonly byte _b; @@ -19,9 +23,16 @@ public readonly struct ColorBGRA : IColor private readonly byte _a; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte B => _b; + + /// public byte G => _g; + + /// public byte R => _r; + + /// public byte A => _a; // ReSharper restore ConvertToAutoPropertyWhenPossible @@ -29,6 +40,13 @@ public readonly struct ColorBGRA : IColor #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. + /// The alpha-component of the color. public ColorBGRA(byte b, byte g, byte r, byte a) { this._b = b; @@ -41,6 +59,7 @@ public readonly struct ColorBGRA : IColor #region Methods + /// public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs index ff3b03a..a9f08e6 100644 --- a/ScreenCapture.NET/Model/Colors/ColorFormat.cs +++ b/ScreenCapture.NET/Model/Colors/ColorFormat.cs @@ -1,5 +1,8 @@ namespace ScreenCapture.NET; +/// +/// Represents a color format. +/// public readonly struct ColorFormat { #region Instances @@ -15,7 +18,14 @@ public readonly struct ColorFormat #region Properties & Fields + /// + /// Gets the Id of the color-format. + /// public readonly int Id; + + /// + /// Gets the Bytes per pixel for this color-format. + /// public readonly int BytesPerPixel; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorRGB.cs b/ScreenCapture.NET/Model/Colors/ColorRGB.cs index ec4acc6..36dd932 100644 --- a/ScreenCapture.NET/Model/Colors/ColorRGB.cs +++ b/ScreenCapture.NET/Model/Colors/ColorRGB.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 24 bit RGB-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorRGB : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.RGB; private readonly byte _r; @@ -18,9 +22,16 @@ public readonly struct ColorRGB : IColor private readonly byte _b; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte A => byte.MaxValue; + + /// public byte R => _r; + + /// public byte G => _g; + + /// public byte B => _b; // ReSharper restore ConvertToAutoPropertyWhenPossible @@ -28,6 +39,12 @@ public readonly struct ColorRGB : IColor #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. public ColorRGB(byte r, byte g, byte b) { this._r = r; @@ -39,6 +56,7 @@ public readonly struct ColorRGB : IColor #region Methods + /// public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorRGBA.cs b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs index 0b01e87..d51a7a1 100644 --- a/ScreenCapture.NET/Model/Colors/ColorRGBA.cs +++ b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs @@ -5,12 +5,16 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// +/// Represents a color in 32 bit RGBA-format. +/// [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] [StructLayout(LayoutKind.Sequential)] public readonly struct ColorRGBA : IColor { #region Properties & Fields + /// public static ColorFormat ColorFormat => ColorFormat.RGBA; private readonly byte _r; @@ -19,9 +23,16 @@ public readonly struct ColorRGBA : IColor private readonly byte _a; // ReSharper disable ConvertToAutoPropertyWhenPossible + /// public byte R => _r; + + /// public byte G => _g; + + /// public byte B => _b; + + /// public byte A => _a; // ReSharper restore ConvertToAutoPropertyWhenPossible @@ -29,6 +40,13 @@ public readonly struct ColorRGBA : IColor #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. + /// The alpha-component of the color. public ColorRGBA(byte r, byte g, byte b, byte a) { this._r = r; @@ -41,6 +59,7 @@ public readonly struct ColorRGBA : IColor #region Methods + /// public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; #endregion diff --git a/ScreenCapture.NET/Model/Colors/IColor.cs b/ScreenCapture.NET/Model/Colors/IColor.cs index f3ea888..826c8db 100644 --- a/ScreenCapture.NET/Model/Colors/IColor.cs +++ b/ScreenCapture.NET/Model/Colors/IColor.cs @@ -2,12 +2,33 @@ namespace ScreenCapture.NET; +/// +/// Represents a generic color made of 4 bytes (alpha, red, green and blue) +/// public interface IColor { + /// + /// Gets the red-component of this color. + /// byte R { get; } + + /// + /// Gets the green-component of this color. + /// byte G { get; } + + /// + /// Gets the blue-component of this color. + /// byte B { get; } + + /// + /// Gets the alpha-component of this color. + /// byte A { get; } + /// + /// Gets the color-format of this color. + /// public static virtual ColorFormat ColorFormat => throw new NotSupportedException(); } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/ICaptureZone.cs b/ScreenCapture.NET/Model/ICaptureZone.cs index 0379ee6..915da3a 100644 --- a/ScreenCapture.NET/Model/ICaptureZone.cs +++ b/ScreenCapture.NET/Model/ICaptureZone.cs @@ -2,61 +2,102 @@ namespace ScreenCapture.NET; +/// +/// Represents a zone on the screen to be captured. +/// public interface ICaptureZone { + /// + /// Gets the display this zone is on. + /// Display Display { get; } + + /// + /// Gets the color-format used in the buffer of this zone. + /// + ColorFormat ColorFormat { 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 stride of the buffer. + /// + int Stride { 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; } + /// + /// Gets the raw buffer of this zone. + /// ReadOnlySpan RawBuffer { get; } + /// + /// Gets the represented by the buffer of this zone. + /// IImage 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; + /// + /// Locks the image for use. Unlock by disposing the returned disposable. + /// + /// The disposable used to unlock the image. + IDisposable Lock(); + /// /// Requests to update this when the next frame is captured. /// Only necessary if is set to false. /// void RequestUpdate(); - RefImage GetRefImage() - where TColor : struct, IColor; + /// + /// Gets a . Basically the same as but with better performance if the color-layout is known. + /// + /// The color used by the buffer of this zone. + /// The representing this zone. + RefImage GetRefImage() where TColor : struct, IColor; } diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs index 3037521..a406a1f 100644 --- a/ScreenCapture.NET/Model/IImage.cs +++ b/ScreenCapture.NET/Model/IImage.cs @@ -2,38 +2,118 @@ namespace ScreenCapture.NET; +/// +/// Represents a image. +/// public interface IImage : IEnumerable { + /// + /// Gets the width of this image. + /// int Width { get; } + + /// + /// Gets the height of this image. + /// int Height { get; } + /// + /// Gets the color at the specified location. + /// + /// The X-location to read. + /// The Y-location to read. + /// The color at the specified location. IColor this[int x, int y] { get; } + + /// + /// Gets an image representing the specified location. + /// + /// The X-location of the image. + /// The Y-location of the image. + /// The width of the sub-image. + /// + /// IImage this[int x, int y, int width, int height] { get; } + /// + /// Gets a list of all rows of this image. + /// IImageRows Rows { get; } + + /// + /// Gets a list of all columns of this image. + /// IImageColumns Columns { get; } + /// + /// Represents a list of rows of an image. + /// public interface IImageRows : IEnumerable { + /// + /// Gets the amount of rows in this list. + /// int Count { get; } + + /// + /// Gets a specific . + /// + /// The ´row to get. + /// The requested . IImageRow this[int column] { get; } } + /// + /// Represents a list of columns of an image. + /// public interface IImageColumns : IEnumerable { + /// + /// Gets the amount of columns in this list. + /// int Count { get; } + + /// + /// Gets a specific . + /// + /// The column to get. + /// The requested . IImageColumn this[int column] { get; } } + /// + /// Represents a single row of an image. + /// public interface IImageRow : IEnumerable { + /// + /// Gets the length of the row. + /// int Length { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. IColor this[int x] { get; } } + /// + /// Represents a single column of an image. + /// public interface IImageColumn : IEnumerable { + /// + /// Gets the length of the column. + /// int Length { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. IColor this[int y] { get; } } } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index 295287a..bb3c1d2 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; namespace ScreenCapture.NET; +/// public sealed class Image : IImage where TColor : struct, IColor { @@ -17,13 +18,17 @@ public sealed class Image : IImage private readonly int _y; private readonly int _stride; + /// public int Width { get; } + + /// public int Height { get; } #endregion #region Indexer + /// public IColor this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -35,6 +40,7 @@ public sealed class Image : IImage } } + /// public IImage this[int x, int y, int width, int height] { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -46,12 +52,14 @@ public sealed class Image : IImage } } + /// public IImage.IImageRows Rows { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); } + /// public IImage.IImageColumns Columns { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -76,6 +84,7 @@ public sealed class Image : IImage #region Methods + /// public IEnumerator GetEnumerator() { for (int y = 0; y < Height; y++) @@ -83,12 +92,14 @@ public sealed class Image : IImage yield return this[x, y]; } + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion #region Indexer-Classes + /// private sealed class ImageRows : IImage.IImageRows { #region Properties & Fields @@ -100,12 +111,14 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + /// public int Count => _height; #endregion #region Indexer + /// public IImage.IImageRow this[int row] { get @@ -134,17 +147,20 @@ public sealed class Image : IImage #region Methods + /// public IEnumerator GetEnumerator() { for (int y = 0; y < _height; y++) yield return this[y]; } + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion } + /// private sealed class ImageRow : IImage.IImageRow { #region Properties & Fields @@ -153,12 +169,14 @@ public sealed class Image : IImage private readonly int _start; private readonly int _length; + /// public int Length => _length; #endregion #region Indexer + /// public IColor this[int x] { get @@ -185,17 +203,20 @@ public sealed class Image : IImage #region Methods + /// public IEnumerator GetEnumerator() { for (int x = 0; x < _length; x++) yield return this[x]; } + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion } + /// private sealed class ImageColumns : IImage.IImageColumns { #region Properties & Fields @@ -207,12 +228,14 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + /// public int Count => _width; #endregion #region Indexer + /// public IImage.IImageColumn this[int column] { get @@ -241,17 +264,20 @@ public sealed class Image : IImage #region Methods + /// public IEnumerator GetEnumerator() { for (int y = 0; y < _height; y++) yield return this[y]; } + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion } + /// private sealed class ImageColumn : IImage.IImageColumn { #region Properties & Fields @@ -261,12 +287,14 @@ public sealed class Image : IImage private readonly int _length; private readonly int _step; + /// public int Length => _length; #endregion #region Indexer + /// public IColor this[int y] { get @@ -294,12 +322,14 @@ public sealed class Image : IImage #region Methods + /// public IEnumerator GetEnumerator() { for (int y = 0; y < _length; y++) yield return this[y]; } + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion From 57462e9717aed95d58254805fa1ed0b4afe3afbd Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 22:44:24 +0200 Subject: [PATCH 18/18] Readded black-bar removal --- .../Extensions/BlackBarDetection.cs | 188 ++++++++++++++++++ ScreenCapture.NET/Model/BlackBarDetection.cs | 141 ------------- .../ScreenCapture.NET.csproj.DotSettings | 1 + 3 files changed, 189 insertions(+), 141 deletions(-) create mode 100644 ScreenCapture.NET/Extensions/BlackBarDetection.cs delete mode 100644 ScreenCapture.NET/Model/BlackBarDetection.cs diff --git a/ScreenCapture.NET/Extensions/BlackBarDetection.cs b/ScreenCapture.NET/Extensions/BlackBarDetection.cs new file mode 100644 index 0000000..4042fee --- /dev/null +++ b/ScreenCapture.NET/Extensions/BlackBarDetection.cs @@ -0,0 +1,188 @@ +namespace ScreenCapture.NET; + +/// +/// Helper-class for black-bar removal. +/// +public static class BlackBarDetection +{ + #region IImage + + /// + /// Create an image with black bars removed + /// + /// The image the bars are removed from. + /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) + /// A bool indicating if black bars should be removed at the top of the image. + /// A bool indicating if black bars should be removed at the bottom of the image. + /// A bool indicating if black bars should be removed on the left side of the image. + /// A bool indicating if black bars should be removed on the right side of the image. + /// The image with black bars removed. + public static IImage RemoveBlackBars(this IImage image, int threshold = 0, bool removeTop = true, bool removeBottom = true, bool removeLeft = true, bool removeRight = true) + { + int top = removeTop ? CalculateTop(image, threshold) : 0; + int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height; + int left = removeLeft ? CalculateLeft(image, threshold) : 0; + int right = removeRight ? CalculateRight(image, threshold) : image.Width; + + return image[left, top, right - left, bottom - top]; + } + + private static int CalculateTop(IImage image, int threshold) + { + IImage.IImageRows rows = image.Rows; + for (int y = 0; y < rows.Count; y++) + { + IImage.IImageRow row = rows[y]; + foreach (IColor color in row) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return y; + } + } + + return 0; + } + + private static int CalculateBottom(IImage image, int threshold) + { + IImage.IImageRows rows = image.Rows; + for (int y = rows.Count - 1; y >= 0; y--) + { + IImage.IImageRow row = rows[y]; + foreach (IColor color in row) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return y; + } + } + + return rows.Count; + } + + private static int CalculateLeft(IImage image, int threshold) + { + IImage.IImageColumns columns = image.Columns; + for (int x = 0; x < columns.Count; x++) + { + IImage.IImageColumn column = columns[x]; + foreach (IColor color in column) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return x; + } + } + + return 0; + } + + private static int CalculateRight(IImage image, int threshold) + { + IImage.IImageColumns columns = image.Columns; + for (int x = columns.Count - 1; x >= 0; x--) + { + IImage.IImageColumn column = columns[x]; + foreach (IColor color in column) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return x; + } + } + + return columns.Count; + } + + #endregion + + #region RefImage + + /// + /// Create an image with black bars removed + /// + /// The image the bars are removed from. + /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) + /// A bool indicating if black bars should be removed at the top of the image. + /// A bool indicating if black bars should be removed at the bottom of the image. + /// A bool indicating if black bars should be removed on the left side of the image. + /// A bool indicating if black bars should be removed on the right side of the image. + /// The image with black bars removed. + public static RefImage RemoveBlackBars(this RefImage image, int threshold = 0, bool removeTop = true, bool removeBottom = true, bool removeLeft = true, bool removeRight = true) + where TColor : struct, IColor + { + int top = removeTop ? CalculateTop(image, threshold) : 0; + int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height; + int left = removeLeft ? CalculateLeft(image, threshold) : 0; + int right = removeRight ? CalculateRight(image, threshold) : image.Width; + + return image[left, top, right - left, bottom - top]; + } + + private static int CalculateTop(this RefImage image, int threshold) + where TColor : struct, IColor + { + RefImage.ImageRows rows = image.Rows; + for (int y = 0; y < rows.Count; y++) + { + ReadOnlyRefEnumerable row = rows[y]; + foreach (TColor color in row) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return y; + } + } + + return 0; + } + + private static int CalculateBottom(this RefImage image, int threshold) + where TColor : struct, IColor + { + RefImage.ImageRows rows = image.Rows; + for (int y = rows.Count - 1; y >= 0; y--) + { + ReadOnlyRefEnumerable row = rows[y]; + foreach (TColor color in row) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return y; + } + } + + return rows.Count; + } + + private static int CalculateLeft(this RefImage image, int threshold) + where TColor : struct, IColor + { + RefImage.ImageColumns columns = image.Columns; + for (int x = 0; x < columns.Count; x++) + { + ReadOnlyRefEnumerable column = columns[x]; + foreach (TColor color in column) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return x; + } + } + + return 0; + } + + private static int CalculateRight(this RefImage image, int threshold) + where TColor : struct, IColor + { + RefImage.ImageColumns columns = image.Columns; + for (int x = columns.Count - 1; x >= 0; x--) + { + ReadOnlyRefEnumerable column = columns[x]; + foreach (TColor color in column) + { + if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold)) + return x; + } + } + + return columns.Count; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/BlackBarDetection.cs b/ScreenCapture.NET/Model/BlackBarDetection.cs deleted file mode 100644 index 1e93712..0000000 --- a/ScreenCapture.NET/Model/BlackBarDetection.cs +++ /dev/null @@ -1,141 +0,0 @@ -// ReSharper disable MemberCanBePrivate.Global - -using System; - -namespace ScreenCapture.NET; - -/// -/// Represents the configuration for the detection and removal of black bars around the screen image. -/// -public sealed class BlackBarDetection -{ - #region Properties & Fields - - private readonly ICaptureZone _captureZone; - - private int? _top; - /// - /// Gets the size of the detected black bar at the top of the image. - /// - public int Top => _top ??= CalculateTop(); - - private int? _bottom; - /// - /// Gets the size of the detected black bar at the bottom of the image. - /// - public int Bottom => _bottom ??= CalculateBottom(); - - private int? _left; - /// - /// Gets the size of the detected black bar at the left of the image. - /// - public int Left => _left ??= CalculateLeft(); - - private int? _right; - /// - /// Gets the size of the detected black bar at the right of the image. - /// - public int Right => _right ??= CalculateRight(); - - private int _theshold = 0; - /// - /// Gets or sets the threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) (default 0) - /// - public int Threshold - { - get => _theshold; - set - { - _theshold = value; - InvalidateCache(); - } - } - - #endregion - - #region Constructors - - internal BlackBarDetection(ICaptureZone captureZone) - { - this._captureZone = captureZone; - } - - #endregion - - #region Methods - - /// - /// Invalidates the cached values and recalculates , , and . - /// - public void InvalidateCache() - { - _top = null; - _bottom = null; - _left = null; - _right = null; - } - - private int CalculateTop() - { - //int threshold = Threshold; - //int stride = _captureZone.Stride; - //for (int row = 0; row < _captureZone.Height; row++) - //{ - // Span data = new(_captureZone.Buffer, row * stride, stride); - // for (int i = 0; i < data.Length; i += 4) - // if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) - // return row; - //} - - return 0; - } - - private int CalculateBottom() - { - //int threshold = Threshold; - //int stride = _captureZone.Stride; - //for (int row = _captureZone.Height - 1; row >= 0; row--) - //{ - // Span data = new(_captureZone.Buffer, row * stride, stride); - // for (int i = 0; i < data.Length; i += 4) - // if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) - // return (_captureZone.Height - 1) - row; - //} - - return 0; - } - - private int CalculateLeft() - { - //int threshold = Threshold; - //int stride = _captureZone.Stride; - //byte[] buffer = _captureZone.Buffer; - //for (int column = 0; column < _captureZone.Width; column++) - // for (int row = 0; row < _captureZone.Height; row++) - // { - // int offset = (stride * row) + (column * 4); - // if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) - // return column; - // } - - return 0; - } - - private int CalculateRight() - { - //int threshold = Threshold; - //int stride = _captureZone.Stride; - //byte[] buffer = _captureZone.Buffer; - //for (int column = _captureZone.Width - 1; column >= 0; column--) - // for (int row = 0; row < _captureZone.Height; row++) - // { - // int offset = (stride * row) + (column * 4); - // if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) - // return (_captureZone.Width - 1) - column; - // } - - return 0; - } - - #endregion -} \ No newline at end of file diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings index 3504048..560de9f 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings @@ -2,6 +2,7 @@ True True True + True True True True