mirror of
https://github.com/DarthAffe/ScreenCapture.NET.git
synced 2025-12-12 13:28:35 +00:00
Extracted ScreenCapture-library from ambilight project
This commit is contained in:
parent
8e58c0bc2b
commit
c1dd816015
25
ScreenCapture.sln
Normal file
25
ScreenCapture.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31025.194
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture", "ScreenCapture\ScreenCapture.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B5111031-6E65-4331-9E6E-A07165289860}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
3
ScreenCapture.sln.DotSettings
Normal file
3
ScreenCapture.sln.DotSettings
Normal file
@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DPI/@EntryIndexedValue">DPI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DX/@EntryIndexedValue">DX</s:String></wpf:ResourceDictionary>
|
||||
37
ScreenCapture/DPIAwareness.cs
Normal file
37
ScreenCapture/DPIAwareness.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public static class DPIAwareness
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag);
|
||||
|
||||
[DllImport("SHCore.dll", SetLastError = true)]
|
||||
internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool SetProcessDPIAware();
|
||||
|
||||
internal enum PROCESS_DPI_AWARENESS
|
||||
{
|
||||
Process_DPI_Unaware = 0,
|
||||
Process_System_DPI_Aware = 1,
|
||||
Process_Per_Monitor_DPI_Aware = 2
|
||||
}
|
||||
|
||||
internal enum DPI_AWARENESS_CONTEXT
|
||||
{
|
||||
DPI_AWARENESS_CONTEXT_UNAWARE = 16,
|
||||
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
|
||||
}
|
||||
|
||||
public static void Initalize()
|
||||
{
|
||||
SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
}
|
||||
}
|
||||
}
|
||||
337
ScreenCapture/DX11ScreenCapture.cs
Normal file
337
ScreenCapture/DX11ScreenCapture.cs
Normal file
@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpGen.Runtime;
|
||||
using Vortice.Direct3D;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.DXGI;
|
||||
using Vortice.Mathematics;
|
||||
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||
using ResultCode = Vortice.DXGI.ResultCode;
|
||||
using Usage = Vortice.Direct3D11.Usage;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class DX11ScreenCapture : IScreenCapture
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private static readonly FeatureLevel[] FEATURE_LEVELS =
|
||||
{
|
||||
FeatureLevel.Level_11_1,
|
||||
FeatureLevel.Level_11_0,
|
||||
FeatureLevel.Level_10_1,
|
||||
FeatureLevel.Level_10_0
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly object _captureLock = new();
|
||||
|
||||
private int _indexCounter = 0;
|
||||
|
||||
public Display Display { get; }
|
||||
|
||||
public int Timeout { get; set; } = 1000;
|
||||
|
||||
private readonly IDXGIFactory1 _factory;
|
||||
|
||||
private IDXGIOutput? _output;
|
||||
private IDXGIOutputDuplication? _duplicatedOutput;
|
||||
private ID3D11Device? _device;
|
||||
private ID3D11DeviceContext? _context;
|
||||
private ID3D11Texture2D? _captureTexture;
|
||||
|
||||
private readonly Dictionary<CaptureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView)> _captureZones = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public DX11ScreenCapture(IDXGIFactory1 factory, Display display)
|
||||
{
|
||||
this._factory = factory;
|
||||
this.Display = display;
|
||||
|
||||
Restart();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public bool CaptureScreen()
|
||||
{
|
||||
bool result = false;
|
||||
lock (_captureLock)
|
||||
{
|
||||
if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null))
|
||||
{
|
||||
Restart();
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IDXGIResource? screenResource = null;
|
||||
try
|
||||
{
|
||||
_duplicatedOutput.AcquireNextFrame(Timeout, out OutduplFrameInfo duplicateFrameInformation, out screenResource);
|
||||
if ((screenResource == null) || (duplicateFrameInformation.LastPresentTime == 0)) return false;
|
||||
|
||||
using ID3D11Texture2D screenTexture = screenResource.QueryInterface<ID3D11Texture2D>();
|
||||
_context.CopySubresourceRegion(_captureTexture, 0, 0, 0, 0, screenTexture, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
screenResource?.Dispose();
|
||||
_duplicatedOutput?.ReleaseFrame();
|
||||
}
|
||||
catch { /**/ }
|
||||
}
|
||||
|
||||
result = true;
|
||||
}
|
||||
catch (SharpGenException dxException)
|
||||
{
|
||||
if ((dxException.ResultCode == ResultCode.AccessLost)
|
||||
|| (dxException.ResultCode == ResultCode.AccessDenied)
|
||||
|| (dxException.ResultCode == ResultCode.InvalidCall))
|
||||
{
|
||||
try
|
||||
{
|
||||
Restart();
|
||||
}
|
||||
catch { Thread.Sleep(100); }
|
||||
}
|
||||
}
|
||||
catch { /**/ }
|
||||
|
||||
try
|
||||
{
|
||||
UpdateZones();
|
||||
}
|
||||
catch { /**/ }
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateZones()
|
||||
{
|
||||
if (_context == null) return;
|
||||
|
||||
lock (_captureZones)
|
||||
{
|
||||
foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested))
|
||||
{
|
||||
if (scalingTexture != null)
|
||||
{
|
||||
_context.CopySubresourceRegion(scalingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
||||
new Box(captureZone.X, captureZone.Y, 0,
|
||||
captureZone.X + captureZone.UnscaledWidth,
|
||||
captureZone.Y + captureZone.UnscaledHeight, 1));
|
||||
_context.GenerateMips(scalingTextureView);
|
||||
_context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, scalingTexture, captureZone.DownscaleLevel);
|
||||
}
|
||||
else
|
||||
_context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, _captureTexture, 0,
|
||||
new Box(captureZone.X, captureZone.Y, 0,
|
||||
captureZone.X + captureZone.UnscaledWidth,
|
||||
captureZone.Y + captureZone.UnscaledHeight, 1));
|
||||
|
||||
MappedSubresource mapSource = _context.Map(stagingTexture, 0, MapMode.Read, MapFlags.None);
|
||||
IntPtr sourcePtr = mapSource.DataPointer;
|
||||
lock (captureZone.Buffer)
|
||||
Marshal.Copy(sourcePtr, captureZone.Buffer, 0, captureZone.Buffer.Length);
|
||||
|
||||
_context.Unmap(stagingTexture, 0);
|
||||
captureZone.SetUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
|
||||
{
|
||||
if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized.");
|
||||
|
||||
if (x < 0) throw new ArgumentException("x < 0");
|
||||
if (y < 0) throw new ArgumentException("y < 0");
|
||||
if (width <= 0) throw new ArgumentException("with <= 0");
|
||||
if (height <= 0) throw new ArgumentException("height <= 0");
|
||||
if ((x + width) > Display.Width) throw new ArgumentException("x + width > Display width");
|
||||
if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height");
|
||||
|
||||
int textureWidth = (int)Math.Ceiling(width / 32.0) * 32;
|
||||
int textureHeight = (int)Math.Ceiling(height / 32.0) * 32;
|
||||
|
||||
int unscaledWidth = width;
|
||||
int unscaledHeight = height;
|
||||
if (downscaleLevel > 0)
|
||||
for (int i = 0; i < downscaleLevel; i++)
|
||||
{
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
|
||||
if (width < 1) width = 1;
|
||||
if (height < 1) height = 1;
|
||||
|
||||
int bufferWidth = (int)Math.Ceiling(width / 32.0) * 32;
|
||||
int bufferHeight = (int)Math.Ceiling(height / 32.0) * 32;
|
||||
|
||||
byte[] buffer = new byte[bufferWidth * bufferHeight * 4];
|
||||
|
||||
CaptureZone captureZone = new(_indexCounter++, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, textureWidth, textureHeight, bufferWidth, bufferHeight, buffer);
|
||||
lock (_captureZones)
|
||||
InitializeCaptureZone(captureZone);
|
||||
|
||||
return captureZone;
|
||||
}
|
||||
|
||||
public bool UnregisterCaptureZone(CaptureZone captureZone)
|
||||
{
|
||||
lock (_captureZones)
|
||||
{
|
||||
if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data))
|
||||
{
|
||||
_captureZones.Remove(captureZone);
|
||||
data.stagingTexture.Dispose();
|
||||
data.scalingTexture?.Dispose();
|
||||
data._scalingTextureView?.Dispose();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCaptureZone(in CaptureZone captureZone)
|
||||
{
|
||||
Texture2DDescription stagingTextureDesc = new()
|
||||
{
|
||||
CpuAccessFlags = CpuAccessFlags.Read,
|
||||
BindFlags = BindFlags.None,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
Width = captureZone.BufferWidth,
|
||||
Height = captureZone.BufferHeight,
|
||||
OptionFlags = ResourceOptionFlags.None,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
SampleDescription = { Count = 1, Quality = 0 },
|
||||
Usage = Usage.Staging
|
||||
};
|
||||
ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc);
|
||||
|
||||
ID3D11Texture2D? scalingTexture = null;
|
||||
ID3D11ShaderResourceView? scalingTextureView = null;
|
||||
if (captureZone.DownscaleLevel > 0)
|
||||
{
|
||||
Texture2DDescription scalingTextureDesc = new()
|
||||
{
|
||||
CpuAccessFlags = CpuAccessFlags.None,
|
||||
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
Width = captureZone.CaptureWidth,
|
||||
Height = captureZone.CaptureHeight,
|
||||
OptionFlags = ResourceOptionFlags.GenerateMips,
|
||||
MipLevels = captureZone.DownscaleLevel + 1,
|
||||
ArraySize = 1,
|
||||
SampleDescription = { Count = 1, Quality = 0 },
|
||||
Usage = Usage.Default
|
||||
};
|
||||
scalingTexture = _device!.CreateTexture2D(scalingTextureDesc);
|
||||
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
|
||||
}
|
||||
|
||||
_captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView);
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
lock (_captureLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
||||
Dispose();
|
||||
|
||||
using IDXGIAdapter1 adapter = _factory.GetAdapter1(Display.GraphicsCard.Index);
|
||||
|
||||
D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, FEATURE_LEVELS, out _device).CheckError();
|
||||
_context = _device.ImmediateContext;
|
||||
|
||||
_output = adapter.GetOutput(Display.Index);
|
||||
using IDXGIOutput5 output1 = _output.QueryInterface<IDXGIOutput5>();
|
||||
|
||||
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 = Usage.Default
|
||||
};
|
||||
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
|
||||
|
||||
lock (_captureZones)
|
||||
{
|
||||
foreach (CaptureZone captureZone in captureZones)
|
||||
InitializeCaptureZone(captureZone);
|
||||
}
|
||||
|
||||
_duplicatedOutput = output1.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
|
||||
}
|
||||
catch { Dispose(false); }
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
private void Dispose(bool removeCaptureZones)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_captureLock)
|
||||
{
|
||||
try { _duplicatedOutput?.Dispose(); } catch { /**/ }
|
||||
_duplicatedOutput = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (removeCaptureZones)
|
||||
{
|
||||
List<CaptureZone> captureZones = _captureZones.Keys.ToList();
|
||||
foreach (CaptureZone captureZone in captureZones)
|
||||
UnregisterCaptureZone(captureZone);
|
||||
}
|
||||
}
|
||||
catch { /**/ }
|
||||
|
||||
try { _output?.Dispose(); } catch { /**/ }
|
||||
try { _context?.Dispose(); } catch { /**/ }
|
||||
try { _device?.Dispose(); } catch { /**/ }
|
||||
try { _captureTexture?.Dispose(); } catch { /**/ }
|
||||
_context = null;
|
||||
_captureTexture = null;
|
||||
}
|
||||
}
|
||||
catch { /**/ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
71
ScreenCapture/DX11ScreenCaptureService.cs
Normal file
71
ScreenCapture/DX11ScreenCaptureService.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using Vortice.DXGI;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public class DX11ScreenCaptureService : IScreenCaptureService
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly IDXGIFactory1 _factory;
|
||||
|
||||
private readonly Dictionary<Display, DX11ScreenCapture> _screenCaptures = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public DX11ScreenCaptureService()
|
||||
{
|
||||
DXGI.CreateDXGIFactory1(out _factory).CheckError();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IEnumerable<GraphicsCard> GetGraphicsCards()
|
||||
{
|
||||
int i = 0;
|
||||
while (_factory.EnumAdapters1(i, out IDXGIAdapter1 adapter).Success)
|
||||
{
|
||||
yield return new GraphicsCard(i, adapter.Description1.Description, adapter.Description1.VendorId, adapter.Description1.DeviceId);
|
||||
adapter.Dispose();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard)
|
||||
{
|
||||
using IDXGIAdapter1? adapter = _factory.GetAdapter1(graphicsCard.Index);
|
||||
|
||||
int i = 0;
|
||||
while (adapter.EnumOutputs(i, out IDXGIOutput output).Success)
|
||||
{
|
||||
int width = output.Description.DesktopCoordinates.Right - output.Description.DesktopCoordinates.Left;
|
||||
int height = output.Description.DesktopCoordinates.Bottom - output.Description.DesktopCoordinates.Top;
|
||||
yield return new Display(i, output.Description.DeviceName, width, height, graphicsCard);
|
||||
output.Dispose();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public IScreenCapture GetScreenCapture(Display display)
|
||||
{
|
||||
if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture))
|
||||
_screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display));
|
||||
return screenCapture;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values)
|
||||
screenCapture.Dispose();
|
||||
_screenCaptures.Clear();
|
||||
|
||||
_factory.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
14
ScreenCapture/IScreenCapture.cs
Normal file
14
ScreenCapture/IScreenCapture.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public interface IScreenCapture : IDisposable
|
||||
{
|
||||
Display Display { get; }
|
||||
|
||||
bool CaptureScreen();
|
||||
CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0);
|
||||
bool UnregisterCaptureZone(CaptureZone captureZone);
|
||||
void Restart();
|
||||
}
|
||||
}
|
||||
12
ScreenCapture/IScreenCaptureService.cs
Normal file
12
ScreenCapture/IScreenCaptureService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public interface IScreenCaptureService : IDisposable
|
||||
{
|
||||
IEnumerable<GraphicsCard> GetGraphicsCards();
|
||||
IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard);
|
||||
IScreenCapture GetScreenCapture(Display display);
|
||||
}
|
||||
}
|
||||
119
ScreenCapture/Model/BlackBarDetection.cs
Normal file
119
ScreenCapture/Model/BlackBarDetection.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System;
|
||||
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public sealed class BlackBarDetection
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly CaptureZone _captureZone;
|
||||
|
||||
private int? _top;
|
||||
public int Top => _top ??= CalculateTop();
|
||||
|
||||
private int? _bottom;
|
||||
public int Bottom => _bottom ??= CalculateBottom();
|
||||
|
||||
private int? _left;
|
||||
public int Left => _left ??= CalculateLeft();
|
||||
|
||||
private int? _right;
|
||||
public int Right => _right ??= CalculateRight();
|
||||
|
||||
private int _theshold = 0;
|
||||
public int Threshold
|
||||
{
|
||||
get => _theshold;
|
||||
set
|
||||
{
|
||||
_theshold = value;
|
||||
InvalidateCache();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public BlackBarDetection(CaptureZone captureZone)
|
||||
{
|
||||
this._captureZone = captureZone;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void InvalidateCache()
|
||||
{
|
||||
_top = null;
|
||||
_bottom = null;
|
||||
_left = null;
|
||||
_right = null;
|
||||
}
|
||||
|
||||
private int CalculateTop()
|
||||
{
|
||||
int threshold = Threshold;
|
||||
int stride = _captureZone.BufferWidth * 4;
|
||||
int bytesPerRow = _captureZone.Width * 4;
|
||||
for (int row = 0; row < _captureZone.Height; row++)
|
||||
{
|
||||
Span<byte> data = new(_captureZone.Buffer, row * stride, bytesPerRow);
|
||||
for (int i = 0; i < data.Length; i += 4)
|
||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||
return row;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CalculateBottom()
|
||||
{
|
||||
int threshold = Threshold;
|
||||
int stride = _captureZone.BufferWidth * 4;
|
||||
int bytesPerRow = _captureZone.Width * 4;
|
||||
for (int row = _captureZone.Height; row >= 0; row--)
|
||||
{
|
||||
Span<byte> data = new(_captureZone.Buffer, row * stride, bytesPerRow);
|
||||
for (int i = 0; i < data.Length; i += 4)
|
||||
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
|
||||
return _captureZone.Height - row;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CalculateLeft()
|
||||
{
|
||||
int threshold = Threshold;
|
||||
int stride = _captureZone.BufferWidth * 4;
|
||||
byte[] buffer = _captureZone.Buffer;
|
||||
for (int column = 0; column < _captureZone.Width; column++)
|
||||
for (int row = 0; row < _captureZone.Height; row++)
|
||||
{
|
||||
int offset = (stride * row) + (column * 4);
|
||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) return column;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CalculateRight()
|
||||
{
|
||||
int threshold = Threshold;
|
||||
int stride = _captureZone.BufferWidth * 4;
|
||||
byte[] buffer = _captureZone.Buffer;
|
||||
for (int column = _captureZone.Width; column >= 0; column--)
|
||||
for (int row = 0; row < _captureZone.Height; row++)
|
||||
{
|
||||
int offset = (stride * row) + (column * 4);
|
||||
if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) return _captureZone.Width - column;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
72
ScreenCapture/Model/CaptureZone.cs
Normal file
72
ScreenCapture/Model/CaptureZone.cs
Normal file
@ -0,0 +1,72 @@
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public sealed class CaptureZone
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public int DownscaleLevel { get; }
|
||||
|
||||
public int UnscaledWidth { get; }
|
||||
public int UnscaledHeight { get; }
|
||||
|
||||
public int CaptureWidth { get; }
|
||||
public int CaptureHeight { get; }
|
||||
|
||||
public int BufferWidth { get; }
|
||||
public int BufferHeight { get; }
|
||||
public byte[] Buffer { get; }
|
||||
|
||||
public BlackBarDetection BlackBars { get; }
|
||||
|
||||
public bool AutoUpdate { get; set; } = true;
|
||||
public bool IsUpdateRequested { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public CaptureZone(int id, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, int captureWidth, int captureHeight, int bufferWidth, int bufferHeight, byte[] buffer)
|
||||
{
|
||||
this.Id = id;
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
this.UnscaledWidth = unscaledWidth;
|
||||
this.UnscaledHeight = unscaledHeight;
|
||||
this.DownscaleLevel = downscaleLevel;
|
||||
this.CaptureWidth = captureWidth;
|
||||
this.CaptureHeight = captureHeight;
|
||||
this.BufferWidth = bufferWidth;
|
||||
this.BufferHeight = bufferHeight;
|
||||
this.Buffer = buffer;
|
||||
|
||||
BlackBars = new BlackBarDetection(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void RequestUpdate() => IsUpdateRequested = true;
|
||||
|
||||
public void SetUpdated()
|
||||
{
|
||||
IsUpdateRequested = false;
|
||||
BlackBars.InvalidateCache();
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Id;
|
||||
public bool Equals(CaptureZone other) => Id == other.Id;
|
||||
public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
38
ScreenCapture/Model/Display.cs
Normal file
38
ScreenCapture/Model/Display.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public readonly struct Display
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public int Index { get; }
|
||||
public string DeviceName { get; }
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public GraphicsCard GraphicsCard { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Display(int index, string deviceName, int width, int height, GraphicsCard graphicsCard)
|
||||
{
|
||||
this.Index = index;
|
||||
this.DeviceName = deviceName;
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
this.GraphicsCard = graphicsCard;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public bool Equals(Display other) => Index == other.Index;
|
||||
public override bool Equals(object? obj) => obj is Display other && Equals(other);
|
||||
public override int GetHashCode() => Index;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
34
ScreenCapture/Model/GraphicsCard.cs
Normal file
34
ScreenCapture/Model/GraphicsCard.cs
Normal file
@ -0,0 +1,34 @@
|
||||
namespace ScreenCapture
|
||||
{
|
||||
public readonly struct GraphicsCard
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public int Index { get; }
|
||||
public string Name { get; }
|
||||
public int VendorId { get; }
|
||||
public int DeviceId { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public GraphicsCard(int index, string name, int vendorId, int deviceId)
|
||||
{
|
||||
this.Index = index;
|
||||
this.Name = name;
|
||||
this.VendorId = vendorId;
|
||||
this.DeviceId = deviceId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public bool Equals(GraphicsCard other) => Index == other.Index;
|
||||
public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other);
|
||||
public override int GetHashCode() => Index;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
27
ScreenCapture/ScreenCapture.csproj
Normal file
27
ScreenCapture/ScreenCapture.csproj
Normal file
@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</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>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
|
||||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Vortice.Direct3D11" Version="1.9.41" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
2
ScreenCapture/ScreenCapture.csproj.DotSettings
Normal file
2
ScreenCapture/ScreenCapture.csproj.DotSettings
Normal file
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
Loading…
x
Reference in New Issue
Block a user