Last active
May 16, 2023 07:46
-
-
Save abodalevsky/f97ecd3e720d7f286cf52edb697e69b3 to your computer and use it in GitHub Desktop.
Uses DWM to create projection of Window to another window. Clip projection from desktop to memory stream.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Creates projection of window (specified by HWND) to another window | |
// Takses snapshot of desktop and clip area where projection is resides | |
using System; | |
using System.Diagnostics; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.Runtime.InteropServices; | |
using System.Threading.Tasks; | |
using Draw = System.Drawing; | |
namespace Utilities | |
{ | |
/// <summary> | |
/// Helpers for take snapshots of app. | |
/// </summary> | |
internal class DwmSnapshotHelper | |
{ | |
#region Native | |
#pragma warning disable SA1310 // Field names should not contain underscore | |
private static readonly int DWM_TNP_VISIBLE = 0x8; | |
private static readonly int DWM_TNP_OPACITY = 0x4; | |
private static readonly int DWM_TNP_RECTDESTINATION = 0x1; | |
#pragma warning restore SA1310 // Field names should not contain underscore | |
[DllImport("dwmapi.dll")] | |
private static extern int DwmRegisterThumbnail(IntPtr dest, IntPtr src, out IntPtr thumb); | |
[DllImport("dwmapi.dll")] | |
private static extern int DwmUnregisterThumbnail(IntPtr thumb); | |
[DllImport("dwmapi.dll")] | |
private static extern int DwmQueryThumbnailSourceSize(IntPtr thumb, out POINT size); | |
[DllImport("dwmapi.dll")] | |
private static extern int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props); | |
[StructLayout(LayoutKind.Sequential)] | |
#pragma warning disable SA1600 // Elements should be documented | |
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter | |
#pragma warning disable SA1600 // Elements should be documented | |
internal struct DWM_THUMBNAIL_PROPERTIES | |
{ | |
public int dwFlags; | |
public RECT rcDestination; | |
public RECT rcSource; | |
public byte opacity; | |
public bool fVisible; | |
public bool fSourceClientAreaOnly; | |
} | |
#pragma warning restore SA1600 // Elements should be documented | |
#pragma warning restore SA1600 // Elements should be documented | |
#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter | |
[DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)] | |
private static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop); | |
#pragma warning disable SA1514 // Element documentation header should be preceded by blank line | |
private enum TernaryRasterOperations : uint | |
{ | |
/// <summary>dest = source</summary> | |
SRCCOPY = 0x00CC0020, | |
/// <summary>dest = source OR dest</summary> | |
SRCPAINT = 0x00EE0086, | |
/// <summary>dest = source AND dest</summary> | |
SRCAND = 0x008800C6, | |
/// <summary>dest = source XOR dest</summary> | |
SRCINVERT = 0x00660046, | |
/// <summary>dest = source AND (NOT dest)</summary> | |
SRCERASE = 0x00440328, | |
/// <summary>dest = (NOT source)</summary> | |
NOTSRCCOPY = 0x00330008, | |
/// <summary>dest = (NOT src) AND (NOT dest)</summary> | |
NOTSRCERASE = 0x001100A6, | |
/// <summary>dest = (source AND pattern)</summary> | |
MERGECOPY = 0x00C000CA, | |
/// <summary>dest = (NOT source) OR dest</summary> | |
MERGEPAINT = 0x00BB0226, | |
/// <summary>dest = pattern</summary> | |
PATCOPY = 0x00F00021, | |
/// <summary>dest = DPSnoo</summary> | |
PATPAINT = 0x00FB0A09, | |
/// <summary>dest = pattern XOR dest</summary> | |
PATINVERT = 0x005A0049, | |
/// <summary>dest = (NOT dest)</summary> | |
DSTINVERT = 0x00550009, | |
/// <summary>dest = BLACK</summary> | |
BLACKNESS = 0x00000042, | |
/// <summary>dest = WHITE</summary> | |
WHITENESS = 0x00FF0062, | |
/// <summary> | |
/// Capture window as seen on screen. This includes layered windows | |
/// such as WPF windows with AllowsTransparency="true" | |
/// </summary> | |
CAPTUREBLT = 0x40000000 | |
} | |
#pragma warning restore SA1514 // Element documentation header should be preceded by blank line | |
[DllImport("user32.dll")] | |
private static extern IntPtr GetDesktopWindow(); | |
[DllImport("user32.dll")] | |
private static extern IntPtr GetWindowDC(IntPtr hWnd); | |
[DllImport("user32.dll")] | |
private static extern bool GetClientRect(IntPtr hwnd, ref RECT rectangle); | |
#endregion | |
private static readonly IntPtr ThisHnadle = Process.GetCurrentProcess().MainWindowHandle; | |
private static IntPtr ThumbnailHandle = IntPtr.Zero; | |
internal static Bitmap TakeSnapshot(IntPtr handle, Draw.Size frame) | |
{ | |
if (ThisHnadle == IntPtr.Zero) | |
{ | |
throw new InvalidOperationException("Cannot obtain HWND of this application"); | |
} | |
if (ThumbnailHandle != IntPtr.Zero) | |
{ | |
Destroy(); | |
} | |
if (handle == IntPtr.Zero) | |
{ | |
throw new ArgumentNullException(nameof(handle)); | |
} | |
var absolutePoint = GetAbsolutePosition(); | |
Show(handle); | |
var imageRect = UpdateThumb(frame); | |
var bmp = TakeImageSnap(imageRect, absolutePoint); | |
Destroy(); | |
return bmp; | |
} | |
private static void Show(IntPtr handle) | |
{ | |
var result = DwmRegisterThumbnail(ThisHnadle, handle, out ThumbnailHandle); | |
if (result != 0) | |
{ | |
throw new InvalidOperationException($"DWM registration returned: HRESULT {result}"); | |
} | |
if (ThumbnailHandle == IntPtr.Zero) | |
{ | |
throw new InvalidOperationException("DwmRegisterThumbnail fails with unknown reason."); | |
} | |
} | |
private static void Destroy() | |
{ | |
DwmUnregisterThumbnail(ThumbnailHandle); | |
ThumbnailHandle = IntPtr.Zero; | |
} | |
private static RECT UpdateThumb(Draw.Size frame) | |
{ | |
DwmQueryThumbnailSourceSize(ThumbnailHandle, out var size); | |
var rect = CalculateThumbRect(size, frame); | |
var props = new DWM_THUMBNAIL_PROPERTIES | |
{ | |
fVisible = true, | |
dwFlags = DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY, | |
opacity = 255, | |
rcDestination = rect | |
}; | |
DwmUpdateThumbnailProperties(ThumbnailHandle, ref props); | |
return rect; | |
} | |
private static RECT CalculateThumbRect(POINT appSize, Draw.Size snapshotSize) | |
{ | |
var appRatio = (double)appSize.x / appSize.y; | |
if (SnapshotHelper.ShouldKeepWidth(new Draw.Size { Width = appSize.x, Height = appSize.y })) | |
{ | |
var zoom = (double)appSize.x / snapshotSize.Width; | |
var width = (int)(appSize.x / zoom); | |
var height = (int)(width / appRatio); | |
return new RECT(0, 0, width, height); | |
} | |
else | |
{ | |
var zoom = (double)appSize.y / snapshotSize.Height; | |
var height = (int)(appSize.y / zoom); | |
var width = (int)(height * appRatio); | |
return new RECT(0, 0, width, height); | |
} | |
} | |
private static Bitmap TakeImageSnap(RECT imageRect, POINT absolutePoint) | |
{ | |
var bmp = new Bitmap(imageRect.Width, imageRect.Height, PixelFormat.Format32bppArgb); | |
using (var gfxBmp = Graphics.FromImage(bmp)) | |
{ | |
var hdcBitmap = gfxBmp.GetHdc(); | |
var hdcSource = GetWindowDC(GetDesktopWindow()); | |
BitBlt(hdcBitmap, 0, 0, imageRect.Width, imageRect.Height, hdcSource, absolutePoint.x, absolutePoint.y, TernaryRasterOperations.SRCCOPY); | |
gfxBmp.ReleaseHdc(hdcBitmap); | |
} | |
return bmp; | |
} | |
private static POINT GetAbsolutePosition() | |
{ | |
var clientRect = new RECT(); | |
GetClientRect(ThisHnadle, ref clientRect); | |
var windowRect = new RECT(); | |
WindowNativeMethods.GetWindowRect(ThisHnadle, ref windowRect); | |
var borderSize = (windowRect.Width - clientRect.Width) / 2; | |
var captionSize = windowRect.Height - clientRect.Height - borderSize; | |
var absoluteX = windowRect.X + borderSize; | |
var absoluteY = windowRect.Y + captionSize; | |
return new POINT(absoluteX, absoluteY); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment