Skip to content

Instantly share code, notes, and snippets.

@abodalevsky
Last active May 16, 2023 07:46
Show Gist options
  • Save abodalevsky/f97ecd3e720d7f286cf52edb697e69b3 to your computer and use it in GitHub Desktop.
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.
// 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