diff --git a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs index 7afc9fb..ffffb9c 100644 --- a/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs +++ b/ScreenCapture.NET/DirectX/DX11ScreenCapture.cs @@ -185,11 +185,11 @@ public sealed class DX11ScreenCapture : IScreenCapture MappedSubresource mapSource = _context.Map(stagingTexture, 0, MapMode.Read, MapFlags.None); IntPtr sourcePtr = mapSource.DataPointer; - lock (captureZone.Buffer) + lock (captureZone.PixelBuffer) { for (int y = 0; y < captureZone.Height; y++) { - Marshal.Copy(sourcePtr, captureZone.Buffer, y * captureZone.Stride, captureZone.Stride); + Marshal.Copy(sourcePtr, captureZone.PixelBuffer.Raw, y * captureZone.Stride, captureZone.Stride); sourcePtr += mapSource.RowPitch; } } @@ -209,8 +209,7 @@ public sealed class DX11ScreenCapture : IScreenCapture int unscaledHeight = height; (width, height) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel); - byte[] buffer = new byte[width * height * BPP]; - + PixelBuffer buffer = new PixelBuffer(width, height); CaptureZone captureZone = new(_indexCounter++, x, y, width, height, BPP, downscaleLevel, unscaledWidth, unscaledHeight, buffer); lock (_captureZones) InitializeCaptureZone(captureZone); @@ -268,7 +267,7 @@ public sealed class DX11ScreenCapture : IScreenCapture captureZone.Width = newWidth; captureZone.Height = newHeight; captureZone.DownscaleLevel = newDownscaleLevel; - captureZone.Buffer = new byte[newWidth * newHeight * BPP]; + captureZone.PixelBuffer = new PixelBuffer(newWidth, newHeight); InitializeCaptureZone(captureZone); } @@ -305,18 +304,18 @@ public sealed class DX11ScreenCapture : IScreenCapture private void InitializeCaptureZone(in CaptureZone captureZone) { Texture2DDescription stagingTextureDesc = new() - { - CpuAccessFlags = CpuAccessFlags.Read, - BindFlags = BindFlags.None, - Format = Format.B8G8R8A8_UNorm, - Width = captureZone.Width, - Height = captureZone.Height, - OptionFlags = ResourceOptionFlags.None, - MipLevels = 1, - ArraySize = 1, - SampleDescription = { Count = 1, Quality = 0 }, - Usage = ResourceUsage.Staging - }; + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = captureZone.Width, + Height = captureZone.Height, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc); ID3D11Texture2D? scalingTexture = null; @@ -324,18 +323,18 @@ public sealed class DX11ScreenCapture : IScreenCapture if (captureZone.DownscaleLevel > 0) { Texture2DDescription scalingTextureDesc = new() - { - CpuAccessFlags = CpuAccessFlags.None, - BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - Format = Format.B8G8R8A8_UNorm, - Width = captureZone.UnscaledWidth, - Height = captureZone.UnscaledHeight, - OptionFlags = ResourceOptionFlags.GenerateMips, - MipLevels = captureZone.DownscaleLevel + 1, - ArraySize = 1, - SampleDescription = { Count = 1, Quality = 0 }, - Usage = ResourceUsage.Default - }; + { + CpuAccessFlags = CpuAccessFlags.None, + BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, + Format = Format.B8G8R8A8_UNorm, + Width = captureZone.UnscaledWidth, + Height = captureZone.UnscaledHeight, + OptionFlags = ResourceOptionFlags.GenerateMips, + MipLevels = captureZone.DownscaleLevel + 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Default + }; scalingTexture = _device!.CreateTexture2D(scalingTextureDesc); scalingTextureView = _device.CreateShaderResourceView(scalingTexture); } @@ -362,18 +361,18 @@ public sealed class DX11ScreenCapture : IScreenCapture using IDXGIOutput5 output = _output.QueryInterface(); Texture2DDescription captureTextureDesc = new() - { - CpuAccessFlags = CpuAccessFlags.None, - BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - Format = Format.B8G8R8A8_UNorm, - Width = Display.Width, - Height = Display.Height, - OptionFlags = ResourceOptionFlags.None, - MipLevels = 1, - ArraySize = 1, - SampleDescription = { Count = 1, Quality = 0 }, - Usage = ResourceUsage.Default - }; + { + CpuAccessFlags = CpuAccessFlags.None, + BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, + Format = Format.B8G8R8A8_UNorm, + Width = Display.Width, + Height = Display.Height, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Default + }; _captureTexture = _device.CreateTexture2D(captureTextureDesc); lock (_captureZones) @@ -383,7 +382,7 @@ public sealed class DX11ScreenCapture : IScreenCapture } if (_useNewDuplicationAdapter) - _duplicatedOutput = output.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth + _duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth else _duplicatedOutput = output.DuplicateOutput(_device); } diff --git a/ScreenCapture.NET/Helper/MathHelper.cs b/ScreenCapture.NET/Helper/MathHelper.cs new file mode 100644 index 0000000..1c78e76 --- /dev/null +++ b/ScreenCapture.NET/Helper/MathHelper.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace ScreenCapture.NET; + +public static class MathHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public 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 + } + + /// + /// Converts a normalized float value in the range [0..1] to a byte [0..255]. + /// + /// The normalized float value to convert. + /// The byte value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public 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); + } + + /// + /// Converts a byte value [0..255] to a normalized float value in the range [0..1]. + /// + /// The byte value to convert. + /// The normalized float value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetPercentageFromByteValue(this byte value) + => value == 255 ? 1.0f : (value / 256.0f); +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/BlackBarDetection.cs b/ScreenCapture.NET/Model/BlackBarDetection.cs index 254a57c..eb0b554 100644 --- a/ScreenCapture.NET/Model/BlackBarDetection.cs +++ b/ScreenCapture.NET/Model/BlackBarDetection.cs @@ -81,7 +81,7 @@ public sealed class BlackBarDetection int stride = _captureZone.Stride; for (int row = 0; row < _captureZone.Height; row++) { - Span data = new(_captureZone.Buffer, row * stride, stride); + Span data = new(_captureZone.PixelBuffer.Raw, row * stride, stride); for (int i = 0; i < data.Length; i += 4) if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) return row; @@ -96,7 +96,7 @@ public sealed class BlackBarDetection int stride = _captureZone.Stride; for (int row = _captureZone.Height - 1; row >= 0; row--) { - Span data = new(_captureZone.Buffer, row * stride, stride); + Span data = new(_captureZone.PixelBuffer.Raw, row * stride, stride); for (int i = 0; i < data.Length; i += 4) if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) return (_captureZone.Height - 1) - row; @@ -109,7 +109,7 @@ public sealed class BlackBarDetection { int threshold = Threshold; int stride = _captureZone.Stride; - byte[] buffer = _captureZone.Buffer; + byte[] buffer = _captureZone.PixelBuffer.Raw; for (int column = 0; column < _captureZone.Width; column++) for (int row = 0; row < _captureZone.Height; row++) { @@ -125,7 +125,7 @@ public sealed class BlackBarDetection { int threshold = Threshold; int stride = _captureZone.Stride; - byte[] buffer = _captureZone.Buffer; + byte[] buffer = _captureZone.PixelBuffer.Raw; for (int column = _captureZone.Width - 1; column >= 0; column--) for (int row = 0; row < _captureZone.Height; row++) { diff --git a/ScreenCapture.NET/Model/CaptureZone.cs b/ScreenCapture.NET/Model/CaptureZone.cs index e33d59e..cdfda8b 100644 --- a/ScreenCapture.NET/Model/CaptureZone.cs +++ b/ScreenCapture.NET/Model/CaptureZone.cs @@ -19,37 +19,58 @@ public sealed class CaptureZone /// /// Gets the x-location of the region on the screen. /// - public int X { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int X { get; set; } /// /// Gets the y-location of the region on the screen. /// - public int Y { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int Y { get; set; } /// /// Gets the width of the captured region. /// - public int Width { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int Width { get; set; } /// /// Gets the height of the captured region. /// - public int Height { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int Height { get; set; } /// /// Gets the level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel. /// - public int DownscaleLevel { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int DownscaleLevel { get; set; } /// /// Gets the original width of the region (this equals if is 0). /// - public int UnscaledWidth { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int UnscaledWidth { get; set; } /// /// Gets the original height of the region (this equals if is 0). /// - public int UnscaledHeight { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public int UnscaledHeight { get; set; } /// /// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]). @@ -64,7 +85,10 @@ public sealed class CaptureZone /// /// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32. /// - public byte[] Buffer { get; internal set; } + /// + /// Should only be set inside of ScreenCaptures! + /// + public PixelBuffer PixelBuffer { get; set; } /// /// Gets the config for black-bar detection. @@ -107,7 +131,7 @@ public sealed class CaptureZone /// The original width of the region. /// The original height of the region /// The buffer containing the image data. - internal CaptureZone(int id, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, byte[] buffer) + public CaptureZone(int id, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, PixelBuffer buffer) { this.Id = id; this.X = x; @@ -118,7 +142,7 @@ public sealed class CaptureZone this.UnscaledWidth = unscaledWidth; this.UnscaledHeight = unscaledHeight; this.DownscaleLevel = downscaleLevel; - this.Buffer = buffer; + this.PixelBuffer = buffer; BlackBars = new BlackBarDetection(this); } diff --git a/ScreenCapture.NET/Model/Colors/ColorBGRA8.cs b/ScreenCapture.NET/Model/Colors/ColorBGRA8.cs new file mode 100644 index 0000000..2374e29 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorBGRA8.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorBGRA8 : IColor +{ + #region Properties & Fields + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + private readonly byte _a; + + public byte A => _a; + public byte R => _r; + public byte G => _g; + public byte B => _b; + + public float sA => _a.GetPercentageFromByteValue(); + public float sR => _r.GetPercentageFromByteValue(); + public float sG => _g.GetPercentageFromByteValue(); + public float sB => _b.GetPercentageFromByteValue(); + + #endregion + + #region Constructors + + public ColorBGRA8() + { } + + public ColorBGRA8(byte r, byte g, byte b, byte a) + { + this._r = r; + this._g = g; + this._b = b; + this._a = a; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/IColor.cs b/ScreenCapture.NET/Model/Colors/IColor.cs new file mode 100644 index 0000000..f942926 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/IColor.cs @@ -0,0 +1,14 @@ +namespace ScreenCapture.NET; + +public interface IColor +{ + byte A { get; } + byte R { get; } + byte G { get; } + byte B { get; } + + float sA { get; } + float sR { get; } + float sG { get; } + float sB { get; } +} diff --git a/ScreenCapture.NET/Model/PixelBuffer.cs b/ScreenCapture.NET/Model/PixelBuffer.cs new file mode 100644 index 0000000..eeb7d32 --- /dev/null +++ b/ScreenCapture.NET/Model/PixelBuffer.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using CommunityToolkit.HighPerformance; + +namespace ScreenCapture.NET; + +public abstract class PixelBuffer +{ + #region Properties & Fields + + public byte[] Raw { get; } + + public abstract ReadOnlySpan2D Pixels { get; } + + #endregion + + #region Constructors + + protected PixelBuffer(byte[] buffer) + { + this.Raw = buffer; + } + + #endregion +} + +public sealed class PixelBuffer : PixelBuffer + where TColor : unmanaged, IColor +{ + #region Properties & Fields + + private readonly int _width; + private readonly int _height; + + public override unsafe ReadOnlySpan2D Pixels + { + get + { + TColor @ref = MemoryMarshal.AsRef(Raw); + return new ReadOnlySpan2D(Unsafe.AsPointer(ref @ref), _height, _width, 0); + } + } + + #endregion + + #region Constructors + + public PixelBuffer(int width, int height) + : base(new byte[width * height * Marshal.SizeOf()]) + { + this._width = width; + this._height = height; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj index b3f3072..df0a38b 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj @@ -1,6 +1,6 @@  - net6.0;net5.0 + net7.0;net6.0 latest enable true @@ -31,9 +31,9 @@ - Fixed ambiguous equals in Display - 1.2.0 - 1.2.0 - 1.2.0 + 2.0.0 + 2.0.0 + 2.0.0 ..\bin\ true @@ -64,6 +64,7 @@ + diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings index 5cd2a56..681424d 100644 --- a/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings +++ b/ScreenCapture.NET/ScreenCapture.NET.csproj.DotSettings @@ -2,4 +2,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file