From f05af5fdba461cf27bd73223eb4bc13f254a2d48 Mon Sep 17 00:00:00 2001 From: DarthAffe Date: Tue, 10 Jun 2025 23:07:38 +0200 Subject: [PATCH] Preparations for a GDI-based capture --- ScreenCapture.NET.GDI/GDIScreenCapture.cs | 175 ++++++++++++++++++ .../GDIScreenCaptureService.cs | 78 ++++++++ ScreenCapture.NET.GDI/Resources/icon.png | Bin 0 -> 705 bytes .../ScreenCapture.NET.GDI.csproj | 77 ++++++++ ScreenCapture.NET.sln | 6 + 5 files changed, 336 insertions(+) create mode 100644 ScreenCapture.NET.GDI/GDIScreenCapture.cs create mode 100644 ScreenCapture.NET.GDI/GDIScreenCaptureService.cs create mode 100644 ScreenCapture.NET.GDI/Resources/icon.png create mode 100644 ScreenCapture.NET.GDI/ScreenCapture.NET.GDI.csproj diff --git a/ScreenCapture.NET.GDI/GDIScreenCapture.cs b/ScreenCapture.NET.GDI/GDIScreenCapture.cs new file mode 100644 index 0000000..0cf8e78 --- /dev/null +++ b/ScreenCapture.NET.GDI/GDIScreenCapture.cs @@ -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; + +/// +/// Represents a ScreenCapture using GDI +/// +// ReSharper disable once InconsistentNaming +public sealed partial class GDIScreenCapture : AbstractScreenCapture +{ + #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 + + /// + /// Initializes a new instance of the class. + /// + /// The to duplicate. + internal GDIScreenCapture(Display display) + : base(display) + { + Restart(); + } + + #endregion + + #region Methods + + /// + 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; + } + + /// + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, Span 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 captureZone, Span buffer) + { + if (_bitmap == null) return; + + BitmapData data = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, _bitmap.PixelFormat); + ReadOnlySpan bitmapBuffer = new(data.Scan0.ToPointer(), data.Stride * data.Height); + + RefImage.Wrap(bitmapBuffer, Display.Width, Display.Height, data.Stride)[captureZone.X, captureZone.Y, captureZone.Width, captureZone.Height] + .CopyTo(MemoryMarshal.Cast(buffer)); + + _bitmap.UnlockBits(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void DownscaleZone(CaptureZone captureZone, Span buffer) + { + BitmapData data = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, _bitmap.PixelFormat); + ReadOnlySpan bitmapBuffer = new(data.Scan0.ToPointer(), data.Stride * data.Height); + + RefImage source = RefImage.Wrap(bitmapBuffer, Display.Width, Display.Height, data.Stride)[captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight]; + Span target = MemoryMarshal.Cast(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); + } + + /// + public override void Restart() + { + base.Restart(); + + lock (_captureLock) + { + _bitmap?.Dispose(); + _bitmap = new Bitmap(Display.Width, Display.Height, PixelFormat.Format32bppArgb); + } + } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + lock (_captureLock) + DisposeDX(); + } + + private void DisposeDX() + { + try + { + _bitmap?.Dispose(); + _bitmap = null; + } + catch { /**/ } + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.GDI/GDIScreenCaptureService.cs b/ScreenCapture.NET.GDI/GDIScreenCaptureService.cs new file mode 100644 index 0000000..8a04978 --- /dev/null +++ b/ScreenCapture.NET.GDI/GDIScreenCaptureService.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace ScreenCapture.NET; + +/// +/// Represents a using the . +/// +public class GDIScreenCaptureService : IScreenCaptureService +{ + #region Properties & Fields + + private readonly Dictionary _screenCaptures = new(); + + private bool _isDisposed; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public GDIScreenCaptureService() + { + } + + ~GDIScreenCaptureService() => Dispose(); + + #endregion + + #region Methods + + /// + public IEnumerable GetGraphicsCards() + { + if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); + + return [new GraphicsCard(0, "Default", 0, 0)]; + } + + /// + public IEnumerable 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)]; + } + + /// + 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; + } + + /// + public void Dispose() + { + if (_isDisposed) return; + + foreach (GDIScreenCapture screenCapture in _screenCaptures.Values) + screenCapture.Dispose(); + _screenCaptures.Clear(); + + GC.SuppressFinalize(this); + + _isDisposed = true; + } + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET.GDI/Resources/icon.png b/ScreenCapture.NET.GDI/Resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46c50338bdcd4535ff63138ab7030dab6b11e747 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE(}r(cNo+^b@?U(MK}vQ zB8wRq_zr_Gjld?%{_ ztr9WvFf!(ud)_hqTTgv1lK=yg0|Q4xQ(Jdkg4JB9rJ^Rjo9|v{Of#O&87B4MV8Tqx z4J--_EDa10g$#`NIO`H_d}WsCKYsQ3gY4<$_kLD0Pp^0{GYzUeVYwf(_)H(Qv`p5; z7c+MIGuOJDO*?*FD1jft^xe4+S2zAkKQD-8--fqso^qvsB>H@v%YPLtyk9P!U(~eM zu*KE;fR(M!C!>@57OoXPuv_IkZ|2#}4d!b5->DVYpZxAC`z@F0_kPYj*=M(deB*G^ zH{q7=1KrsNX3ajJQaS6%@`vkov(%jXmobia3;O{% zghLi|+^bJ?xonb_*<4kxD&Wn)1Pnm9boPaI?Z*D<4-)s(cGjN#{!{m)Hs`m)h0mrh zS^xQ3_j$KXED=m)r~azw7k%Bn+TNq+^DOz-k(2!Yf2@8#xn|0{{e6@Eym}CD@95hT zCGlkuDUbB;@%jF~)BCL;O1EM2x1H1Pv)uD83NZN7{B5T-w*!L+--1u#(!YX^mkIcB z + + net8.0-windows;net9.0-windows + win-x64 + latest + enable + true + true + + Darth Affe + Wyrez + en-US + en-US + ScreenCapture.NET.GDI + ScreenCapture.NET.GDI + ScreenCapture.NET.GDI + ScreenCapture.NET.GDI + ScreenCapture.NET + GDI based Screen-Capturing + GDI based Screen-Capturing + Copyright © Darth Affe 2025 + Copyright © Darth Affe 2025 + icon.png + https://github.com/DarthAffe/ScreenCapture.NET + LGPL-2.1-only + Github + https://github.com/DarthAffe/ScreenCapture.NET + True + + + + + 3.0.0 + 3.0.0 + 3.0.0 + + ..\bin\ + true + True + True + snupkg + + + + $(DefineConstants);TRACE;DEBUG + true + full + false + + + + portable + true + $(NoWarn);CS1591;CS1572;CS1573 + $(DefineConstants);RELEASE + + + + + + + + + True + + + + + + + + + + + + + diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index b91b878..6065a9c 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "Sc EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.GDI", "ScreenCapture.NET.GDI\ScreenCapture.NET.GDI.csproj", "{53FEEA34-B9F0-4A02-9408-BCC3AD0EC1E3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.Release|Any CPU.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE