Added capture-service for X11 (linux); Added some more color formats

This commit is contained in:
Darth Affe 2023-09-10 00:37:40 +02:00
parent 2667f84c42
commit 94471f08b4
13 changed files with 928 additions and 14 deletions

View File

@ -68,8 +68,4 @@
<ProjectReference Include="..\ScreenCapture.NET\ScreenCapture.NET.csproj" /> <ProjectReference Include="..\ScreenCapture.NET\ScreenCapture.NET.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Downscale\" />
</ItemGroup>
</Project> </Project>

View File

@ -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;
/// <summary>
/// Represents a sampled that averages multiple byte-data entries.
/// </summary>
internal static class AverageByteSampler
{
#region Constants
private static readonly int INT_VECTOR_LENGTH = Vector<uint>.Count;
#endregion
#region Methods
public static unsafe void Sample(in SamplerInfo<byte> info, in Span<byte> pixelData)
{
int count = info.Width * info.Height;
if (count == 0) return;
int dataLength = pixelData.Length;
Span<uint> sums = stackalloc uint[dataLength];
int elementsPerVector = Vector<byte>.Count / dataLength;
int valuesPerVector = elementsPerVector * dataLength;
if (Vector.IsHardwareAccelerated && (info.Height > 1) && (info.Width >= valuesPerVector) && (dataLength <= Vector<byte>.Count))
{
int chunks = info.Width / elementsPerVector;
Vector<uint> sum1 = Vector<uint>.Zero;
Vector<uint> sum2 = Vector<uint>.Zero;
Vector<uint> sum3 = Vector<uint>.Zero;
Vector<uint> sum4 = Vector<uint>.Zero;
for (int y = 0; y < info.Height; y++)
{
ReadOnlySpan<byte> data = info[y];
fixed (byte* colorPtr = data)
{
byte* current = colorPtr;
for (int i = 0; i < chunks; i++)
{
Vector<byte> bytes = *(Vector<byte>*)current;
Vector.Widen(bytes, out Vector<ushort> short1, out Vector<ushort> short2);
Vector.Widen(short1, out Vector<uint> int1, out Vector<uint> int2);
Vector.Widen(short2, out Vector<uint> int3, out Vector<uint> 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<byte> 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
}

View File

@ -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;
/// <summary>
/// Represents the information used to sample data.
/// </summary>
/// <typeparam name="T">The type of the data to sample.</typeparam>
internal readonly ref struct SamplerInfo<T>
{
#region Properties & Fields
private readonly ReadOnlySpan<T> _data;
private readonly int _x;
private readonly int _y;
private readonly int _stride;
private readonly int _dataPerPixel;
private readonly int _dataWidth;
/// <summary>
/// Gets the width of the region the data comes from.
/// </summary>
public readonly int Width;
/// <summary>
/// Gets the height of region the data comes from.
/// </summary>
public readonly int Height;
/// <summary>
/// Gets the data for the requested row.
/// </summary>
/// <param name="row">The row to get the data for.</param>
/// <returns>A readonly span containing the data of the row.</returns>
public ReadOnlySpan<T> this[int row] => _data.Slice((((_y + row) * _stride) + _x) * _dataPerPixel, _dataWidth);
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SamplerInfo{T}" /> class.
/// </summary>
/// <param name="width">The width of the region the data comes from.</param>
/// <param name="height">The height of region the data comes from.</param>
/// <param name="data">The data to sample.</param>
public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan<T> 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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

View File

@ -0,0 +1,68 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Authors>Darth Affe</Authors>
<Company>Wyrez</Company>
<Language>en-US</Language>
<NeutralLanguage>en-US</NeutralLanguage>
<Title>ScreenCapture.NET.X11</Title>
<AssemblyName>ScreenCapture.NET.X11</AssemblyName>
<AssemblyTitle>ScreenCapture.NET.X11</AssemblyTitle>
<PackageId>ScreenCapture.NET.X11</PackageId>
<RootNamespace>ScreenCapture.NET</RootNamespace>
<Description>Vortice based Screen-Capturing</Description>
<Summary>Vortice based Screen-Capturing using Desktop Duplication</Summary>
<Copyright>Copyright © Darth Affe 2023</Copyright>
<PackageCopyright>Copyright © Darth Affe 2023</PackageCopyright>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/DarthAffe/ScreenCapture.NET</PackageProjectUrl>
<PackageLicenseExpression>LGPL-2.1-only</PackageLicenseExpression>
<RepositoryType>Github</RepositoryType>
<RepositoryUrl>https://github.com/DarthAffe/ScreenCapture.NET</RepositoryUrl>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageReleaseNotes>
</PackageReleaseNotes>
<Version>2.0.0</Version>
<AssemblyVersion>2.0.0</AssemblyVersion>
<FileVersion>2.0.0</FileVersion>
<OutputPath>..\bin\</OutputPath>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IncludeSource>True</IncludeSource>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="Resources\icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCapture.NET\ScreenCapture.NET.csproj" />
</ItemGroup>
</Project>

View File

@ -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

View File

@ -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;
/// <summary>
/// Represents a ScreenCapture using DirectX 11 desktop duplicaton.
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
/// </summary>
// ReSharper disable once InconsistentNaming
public sealed class X11ScreenCapture : AbstractScreenCapture<ColorBGRA>
{
#region Properties & Fields
private readonly object _captureLock = new();
private nint _display;
private readonly Dictionary<CaptureZone<ColorBGRA>, ZoneTextures> _textures = new();
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="X11ScreenCapture"/> class.
/// </summary>
/// <param name="display">The <see cref="Display"/> to duplicate.</param>
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<ColorBGRA> captureZone in CaptureZones)
{
if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) break;
textures.Update();
}
return true;
}
}
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone, in Span<byte> 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<ColorBGRA> captureZone, in Span<byte> buffer)
{
if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
ReadOnlySpan<ColorBGRA> source = MemoryMarshal.Cast<byte, ColorBGRA>(textures.Data);
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(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<ColorBGRA> captureZone, in Span<byte> buffer)
{
if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
ReadOnlySpan<byte> source = textures.Data;
Span<byte> 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<byte> scaleBuffer = stackalloc byte[bpp];
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
AverageByteSampler.Sample(new SamplerInfo<byte>(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];
}
}
/// <inheritdoc />
public override CaptureZone<ColorBGRA> RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
{
CaptureZone<ColorBGRA> captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel);
lock (_textures)
InitializeCaptureZone(captureZone);
return captureZone;
}
/// <inheritdoc />
public override bool UnregisterCaptureZone(CaptureZone<ColorBGRA> 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;
}
}
/// <inheritdoc />
public override void UpdateCaptureZone(CaptureZone<ColorBGRA> 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<ColorBGRA> captureZone)
=> _textures[captureZone] = new ZoneTextures(_display, captureZone.Display.Index, captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight);
/// <inheritdoc />
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<ColorBGRA> _, 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<byte> 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<X11.XImage>(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
}
}

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace ScreenCapture.NET;
/// <summary>
/// Represents a <see cref="IScreenCaptureService"/> using the <see cref="X11ScreenCapture"/>.
/// </summary>
public class X11ScreenCaptureService : IScreenCaptureService
{
#region Properties & Fields
private readonly Dictionary<Display, X11ScreenCapture> _screenCaptures = new();
private bool _isDisposed;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="X11ScreenCaptureService"/> class.
/// </summary>
public X11ScreenCaptureService()
{ }
~X11ScreenCaptureService() => Dispose();
#endregion
#region Methods
/// <inheritdoc />
public IEnumerable<GraphicsCard> 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);
}
}
/// <inheritdoc />
public IEnumerable<Display> 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);
}
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
foreach (X11ScreenCapture screenCapture in _screenCaptures.Values)
screenCapture.Dispose();
_screenCaptures.Clear();
GC.SuppressFinalize(this);
_isDisposed = true;
}
#endregion
}

View File

@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.Tests", "
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "ScreenCapture.NET.DX9\ScreenCapture.NET.DX9.csproj", "{27EB5B17-2F83-43BA-A21F-06D93948B8BF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "ScreenCapture.NET.DX9\ScreenCapture.NET.DX9.csproj", "{27EB5B17-2F83-43BA-A21F-06D93948B8BF}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -51,6 +51,7 @@ public abstract class AbstractScreenCapture<TColor> : IScreenCapture
result = false; result = false;
} }
lock (CaptureZones)
foreach (CaptureZone<TColor> captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested)) foreach (CaptureZone<TColor> captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested))
{ {
try try

View File

@ -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
}

View File

@ -5,9 +5,11 @@ public readonly struct ColorFormat
#region Instances #region Instances
public static readonly ColorFormat BGRA = new(1, 4); public static readonly ColorFormat BGRA = new(1, 4);
public static readonly ColorFormat ARGB = new(2, 4); public static readonly ColorFormat ABGR = new(2, 4);
public static readonly ColorFormat RGB = new(3, 3); public static readonly ColorFormat RGBA = new(3, 4);
public static readonly ColorFormat BGR = new(4, 3); 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 #endregion

View File

@ -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
}