mirror of
https://github.com/DarthAffe/ScreenCapture.NET.git
synced 2025-12-12 13:28:35 +00:00
Added capture-service for X11 (linux); Added some more color formats
This commit is contained in:
parent
2667f84c42
commit
94471f08b4
@ -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>
|
||||||
|
|||||||
149
ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs
Normal file
149
ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs
Normal 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
|
||||||
|
}
|
||||||
63
ScreenCapture.NET.X11/Downscale/SamplerInfo.cs
Normal file
63
ScreenCapture.NET.X11/Downscale/SamplerInfo.cs
Normal 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
|
||||||
|
}
|
||||||
BIN
ScreenCapture.NET.X11/Resources/icon.png
Normal file
BIN
ScreenCapture.NET.X11/Resources/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 705 B |
68
ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj
Normal file
68
ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj
Normal 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>
|
||||||
133
ScreenCapture.NET.X11/X11.cs
Normal file
133
ScreenCapture.NET.X11/X11.cs
Normal 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
|
||||||
300
ScreenCapture.NET.X11/X11ScreenCapture.cs
Normal file
300
ScreenCapture.NET.X11/X11ScreenCapture.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
102
ScreenCapture.NET.X11/X11ScreenCaptureService.cs
Normal file
102
ScreenCapture.NET.X11/X11ScreenCaptureService.cs
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
47
ScreenCapture.NET/Model/Colors/ColorABGR.cs
Normal file
47
ScreenCapture.NET/Model/Colors/ColorABGR.cs
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
47
ScreenCapture.NET/Model/Colors/ColorRGBA.cs
Normal file
47
ScreenCapture.NET/Model/Colors/ColorRGBA.cs
Normal 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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user