Preparations for a GDI-based capture

This commit is contained in:
DarthAffe 2025-06-10 23:07:38 +02:00
parent d0388f8c75
commit f05af5fdba
5 changed files with 336 additions and 0 deletions

View File

@ -0,0 +1,175 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using HPPH;
using HPPH.System.Drawing;
namespace ScreenCapture.NET;
/// <summary>
/// Represents a ScreenCapture using GDI
/// </summary>
// ReSharper disable once InconsistentNaming
public sealed partial class GDIScreenCapture : AbstractScreenCapture<ColorBGRA>
{
#region DLL-Import
private const int SRCCOPY = 0x00CC0020;
[LibraryImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool BitBlt(nint hdcDest, int xDest, int yDest, int w, int h, nint hdcSrc, int xSrc, int ySrc, int rop);
[LibraryImport("user32.dll")]
private static partial nint GetDC(nint hwnd);
[LibraryImport("user32.dll")]
private static partial int ReleaseDC(nint hwnd, nint hdc);
#endregion
#region Properties & Fields
private readonly Lock _captureLock = new();
private Bitmap? _bitmap;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="GDIScreenCapture"/> class.
/// </summary>
/// <param name="display">The <see cref="Display"/> to duplicate.</param>
internal GDIScreenCapture(Display display)
: base(display)
{
Restart();
}
#endregion
#region Methods
/// <inheritdoc />
protected override unsafe bool PerformScreenCapture()
{
lock (_captureLock)
{
if (_bitmap == null) return false;
using Graphics gDest = Graphics.FromImage(_bitmap);
nint dc = 0;
nint dest = 0;
try
{
//dc = GetDC(nint.Zero);
//dest = gDest.GetHdc();
Stopwatch sw = Stopwatch.StartNew();
gDest.CopyFromScreen(0, 0, 0, 0, new Size(Display.Width, 2));
//BitBlt(dest, 0, 0, Display.Width, Display.Height, dc, 0, 0, SRCCOPY);
Console.WriteLine(sw.Elapsed.TotalMilliseconds);
}
catch
{
return false;
}
finally
{
gDest.ReleaseHdc(dest);
if (dc != nint.Zero)
ReleaseDC(nint.Zero, dc);
}
}
return true;
}
/// <inheritdoc />
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone, Span<byte> buffer)
{
if (_bitmap == null) return;
using IDisposable @lock = captureZone.Lock();
if (captureZone.DownscaleLevel == 0)
CopyZone(captureZone, buffer);
else
DownscaleZone(captureZone, buffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void CopyZone(CaptureZone<ColorBGRA> captureZone, Span<byte> buffer)
{
if (_bitmap == null) return;
BitmapData data = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, _bitmap.PixelFormat);
ReadOnlySpan<byte> bitmapBuffer = new(data.Scan0.ToPointer(), data.Stride * data.Height);
RefImage<ColorBGRA>.Wrap(bitmapBuffer, Display.Width, Display.Height, data.Stride)[captureZone.X, captureZone.Y, captureZone.Width, captureZone.Height]
.CopyTo(MemoryMarshal.Cast<byte, ColorBGRA>(buffer));
_bitmap.UnlockBits(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void DownscaleZone(CaptureZone<ColorBGRA> captureZone, Span<byte> buffer)
{
BitmapData data = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, _bitmap.PixelFormat);
ReadOnlySpan<byte> bitmapBuffer = new(data.Scan0.ToPointer(), data.Stride * data.Height);
RefImage<ColorBGRA> source = RefImage<ColorBGRA>.Wrap(bitmapBuffer, Display.Width, Display.Height, data.Stride)[captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight];
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(buffer);
int blockSize = 1 << captureZone.DownscaleLevel;
int width = captureZone.Width;
int height = captureZone.Height;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
target[(y * width) + x] = source[x * blockSize, y * blockSize, blockSize, blockSize].Average();
_bitmap.UnlockBits(data);
}
/// <inheritdoc />
public override void Restart()
{
base.Restart();
lock (_captureLock)
{
_bitmap?.Dispose();
_bitmap = new Bitmap(Display.Width, Display.Height, PixelFormat.Format32bppArgb);
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
lock (_captureLock)
DisposeDX();
}
private void DisposeDX()
{
try
{
_bitmap?.Dispose();
_bitmap = null;
}
catch { /**/ }
}
#endregion
}

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ScreenCapture.NET;
/// <summary>
/// Represents a <see cref="IScreenCaptureService"/> using the <see cref="GDIScreenCapture"/>.
/// </summary>
public class GDIScreenCaptureService : IScreenCaptureService
{
#region Properties & Fields
private readonly Dictionary<Display, GDIScreenCapture> _screenCaptures = new();
private bool _isDisposed;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="GDIScreenCaptureService"/> class.
/// </summary>
public GDIScreenCaptureService()
{
}
~GDIScreenCaptureService() => Dispose();
#endregion
#region Methods
/// <inheritdoc />
public IEnumerable<GraphicsCard> GetGraphicsCards()
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
return [new GraphicsCard(0, "Default", 0, 0)];
}
/// <inheritdoc />
public IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
Screen screen = Screen.PrimaryScreen!;
return [new Display(0, screen.DeviceName, screen.Bounds.Width, screen.Bounds.Height, Rotation.None, graphicsCard)];
}
/// <inheritdoc />
IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
public GDIScreenCapture GetScreenCapture(Display display)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
if (!_screenCaptures.TryGetValue(display, out GDIScreenCapture? screenCapture))
_screenCaptures.Add(display, screenCapture = new GDIScreenCapture(display));
return screenCapture;
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
foreach (GDIScreenCapture screenCapture in _screenCaptures.Values)
screenCapture.Dispose();
_screenCaptures.Clear();
GC.SuppressFinalize(this);
_isDisposed = true;
}
#endregion
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

View File

@ -0,0 +1,77 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net9.0-windows</TargetFrameworks>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWindowsForms>true</UseWindowsForms>
<Authors>Darth Affe</Authors>
<Company>Wyrez</Company>
<Language>en-US</Language>
<NeutralLanguage>en-US</NeutralLanguage>
<Title>ScreenCapture.NET.GDI</Title>
<AssemblyName>ScreenCapture.NET.GDI</AssemblyName>
<AssemblyTitle>ScreenCapture.NET.GDI</AssemblyTitle>
<PackageId>ScreenCapture.NET.GDI</PackageId>
<RootNamespace>ScreenCapture.NET</RootNamespace>
<Description>GDI based Screen-Capturing</Description>
<Summary>GDI based Screen-Capturing</Summary>
<Copyright>Copyright © Darth Affe 2025</Copyright>
<PackageCopyright>Copyright © Darth Affe 2025</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>3.0.0</Version>
<AssemblyVersion>3.0.0</AssemblyVersion>
<FileVersion>3.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 Condition="'$(TargetFramework)' == 'net8.0-windows'">
<Using Include="ScreenCapture.NET.Compatibility.Net8" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="HPPH.System.Drawing" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCapture.NET\ScreenCapture.NET.csproj" />
</ItemGroup>
</Project>

View File

@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "Sc
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.GDI", "ScreenCapture.NET.GDI\ScreenCapture.NET.GDI.csproj", "{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -41,6 +43,10 @@ Global
{F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.Build.0 = Release|Any CPU {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.Build.0 = Release|Any CPU
{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE