From 94471f08b49b657837f07abdb689298d02f15af2 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 10 Sep 2023 00:37:40 +0200 Subject: [PATCH] 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