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 0000000..46c5033 Binary files /dev/null and b/ScreenCapture.NET.GDI/Resources/icon.png differ diff --git a/ScreenCapture.NET.GDI/ScreenCapture.NET.GDI.csproj b/ScreenCapture.NET.GDI/ScreenCapture.NET.GDI.csproj new file mode 100644 index 0000000..e03b131 --- /dev/null +++ b/ScreenCapture.NET.GDI/ScreenCapture.NET.GDI.csproj @@ -0,0 +1,77 @@ + + + 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