Skip to content

Instantly share code, notes, and snippets.

@azyobuzin
Last active August 10, 2017 02:36
Show Gist options
  • Save azyobuzin/2eb3379397e9db7336687723429084e7 to your computer and use it in GitHub Desktop.
Save azyobuzin/2eb3379397e9db7336687723429084e7 to your computer and use it in GitHub Desktop.
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp2.WindowsApi
{
public abstract class DCHandle : SafeHandle
{
public DCHandle(bool ownsHandle)
: base(IntPtr.Zero, ownsHandle)
{ }
public DCHandle() : this(true) { }
public override bool IsInvalid => this.handle == IntPtr.Zero;
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace ConsoleApp2.WindowsApi
{
public enum BitmapCompression : uint
{
BI_RGB = 0,
BI_RLE8 = 1,
BI_RLE4 = 2,
BI_BITFIELDS = 3,
BI_JPEG = 4,
BI_PNG = 5
}
public enum DibColorTableIdentifier : uint
{
DIB_RGB_COLORS = 0,
DIB_PAL_COLORS = 1
}
public enum RasterOperationCode : uint
{
SRCCOPY = 0x00CC0020,
SRCPAINT = 0x00EE0086,
SRCAND = 0x008800C6,
SRCINVERT = 0x00660046,
SRCERASE = 0x00440328,
NOTSRCCOPY = 0x00330008,
NOTSRCERASE = 0x001100A6,
MERGECOPY = 0x00C000CA,
MERGEPAINT = 0x00BB0226,
PATCOPY = 0x00F00021,
PATPAINT = 0x00FB0A09,
PATINVERT = 0x005A0049,
DSTINVERT = 0x00550009,
BLACKNESS = 0x00000042,
WHITENESS = 0x00FF0062,
NOMIRRORBITMAP = 0x80000000,
CAPTUREBLT = 0x40000000
}
public class BitmapHandle : SafeHandle
{
public BitmapHandle() : base(IntPtr.Zero, true)
{ }
public override bool IsInvalid => this.handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
return Gdi32.DeleteObject(this.handle);
}
}
public class GdiDCHandle : DCHandle
{
protected override bool ReleaseHandle()
{
return Gdi32.DeleteDC(this.handle);
}
}
public struct BitmapInfoHeader
{
public int Width;
public int Height;
public ushort Planes;
public ushort BitCount;
public BitmapCompression Compression;
public uint SizeImage;
public int XPelsPerMeter;
public int YPelsPerMeter;
public uint ClrUsed;
public uint ClrImportant;
}
public struct RgbQuad
{
public byte Blue;
public byte Green;
public byte Red;
public byte Reserved;
}
public class BitmapInfo
{
public BitmapInfoHeader Header { get; set; }
public IReadOnlyList<RgbQuad> Colors { get; set; }
internal unsafe byte[] ToBytes()
{
var colorCount = this.Colors?.Count ?? 0;
var colorsBytes = (colorCount <= 0 ? 1 : colorCount) * sizeof(RgbQuad);
var bs = new byte[
sizeof(uint) // biSize
+ sizeof(BitmapInfoHeader) // bmiHeader
+ colorsBytes // bmiColors
];
fixed (byte* ptr = bs)
{
*(uint*)ptr = (uint)(sizeof(uint) + sizeof(BitmapInfoHeader)); // biSize
*(BitmapInfoHeader*)(ptr + sizeof(uint)) = this.Header; // other fields
var dstColors = (RgbQuad*)(ptr + sizeof(uint) + sizeof(BitmapInfoHeader));
if (colorCount >= 1)
{
if (this.Colors is RgbQuad[] arrColors)
{
fixed (RgbQuad* srcColors = arrColors)
{
Buffer.MemoryCopy(srcColors, dstColors, colorsBytes, arrColors.Length * sizeof(RgbQuad));
}
}
else
{
for (var i = 0; i < colorCount; i++)
{
dstColors[i] = this.Colors[i];
}
}
}
}
return bs;
}
internal unsafe void Return(byte[] bs)
{
fixed (byte* ptr = bs)
{
this.Header = *(BitmapInfoHeader*)(ptr + sizeof(uint));
}
}
}
public static class Gdi32
{
public const string DllName = "gdi32";
[DllImport(DllName, EntryPoint = "BitBlt", ExactSpelling = true, SetLastError = true)]
private static extern bool _BitBlt(DCHandle hdc, int x, int y, int cx, int cy, DCHandle hdcSrc, int x1, int y1, RasterOperationCode rop);
public static void BitBlt(DCHandle hdc, int x, int y, int cx, int cy, DCHandle hdcSrc, int x1, int y1, RasterOperationCode rop)
{
if (!_BitBlt(hdc, x, y, cx, cy, hdcSrc, x1, y1, rop))
throw new Win32Exception();
}
[DllImport(DllName, EntryPoint = "CreateCompatibleBitmap", ExactSpelling = true)]
private static extern BitmapHandle _CreateCompatibleBitmap(DCHandle hdc, int cx, int cy);
public static BitmapHandle CreateCompatibleBitmap(DCHandle hdc, int cx, int cy)
{
var result = _CreateCompatibleBitmap(hdc, cx, cy);
if (result.IsInvalid) throw new Exception();
return result;
}
[DllImport(DllName, EntryPoint = "CreateCompatibleDC", ExactSpelling = true)]
private static extern GdiDCHandle _CreateCompatibleDC(DCHandle hdc);
public static GdiDCHandle CreateCompatibleDC(DCHandle hdc)
{
var result = _CreateCompatibleDC(hdc);
if (result.IsInvalid) throw new Exception();
return result;
}
[DllImport(DllName, ExactSpelling = true)]
internal static extern bool DeleteDC(IntPtr hdc);
[DllImport(DllName, ExactSpelling = true)]
internal static extern bool DeleteObject(IntPtr ho);
[DllImport(DllName, EntryPoint = "GetDIBits", ExactSpelling = true)]
private static extern int _GetDIBits(DCHandle hdc, BitmapHandle hbm, uint start, uint cLines, [Out] byte[] lpvBits, [In, Out] byte[] lpbmi, DibColorTableIdentifier usage);
public static int GetDIBits(DCHandle hdc, BitmapHandle hbm, uint start, uint cLines, byte[] lpvBits, BitmapInfo lpbmi, DibColorTableIdentifier usage)
{
var bsBitmapInfo = lpbmi.ToBytes();
var result = _GetDIBits(hdc, hbm, start, cLines, lpvBits, bsBitmapInfo, usage);
lpbmi.Return(bsBitmapInfo);
if (result == 0) throw new Exception();
return result;
}
[DllImport(DllName, ExactSpelling = true)]
public static extern IntPtr SelectObject(DCHandle hdc, IntPtr h);
}
}
using System;
using System.Diagnostics;
using System.Threading;
using ConsoleApp2.WindowsApi;
using ImageSharp;
using ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var p = Process.Start(@"notepad.exe");
Console.WriteLine(p.Id);
Thread.Sleep(1500);
var hwnd = FindMainWindow(p.Id);
var rect = User32.GetClientRect(hwnd);
var size = new Size(rect.Right, rect.Bottom);
byte[] buf;
using (var windowDC = User32.GetDC(hwnd))
using (var hBitmap = Gdi32.CreateCompatibleBitmap(windowDC, size.Width, size.Height))
using (var bitmapDC = Gdi32.CreateCompatibleDC(windowDC))
{
if (Gdi32.SelectObject(bitmapDC, hBitmap.DangerousGetHandle()) == IntPtr.Zero)
throw new Exception();
Gdi32.BitBlt(bitmapDC, 0, 0, size.Width, size.Height, windowDC, 0, 0, RasterOperationCode.SRCCOPY);
var bmi = new BitmapInfo()
{
Header = new BitmapInfoHeader()
{
Width = size.Width,
Height = size.Height,
Planes = 1,
BitCount = 32,
Compression = BitmapCompression.BI_RGB,
SizeImage = 0,
XPelsPerMeter = 0,
YPelsPerMeter = 0,
ClrUsed = 0,
ClrImportant = 0
}
};
buf = new byte[size.Width * size.Height * 4];
Gdi32.GetDIBits(windowDC, hBitmap, 0, (uint)size.Height, buf, bmi, DibColorTableIdentifier.DIB_RGB_COLORS);
}
using (var image = new Image<Argb32>(size.Width, size.Height))
{
var bytesPerLine = size.Width * 4;
var bufSpan = new ReadOnlySpan<byte>(buf);
var pxSpan = image.Pixels.AsBytes();
for (var line = 0; line < size.Height; line++)
{
bufSpan.Slice(buf.Length - (line + 1) * bytesPerLine, bytesPerLine)
.CopyTo(pxSpan.Slice(line * bytesPerLine, bytesPerLine));
}
image.Save("output.png");
}
Console.WriteLine("completed");
}
public static IntPtr FindMainWindow(int processId)
{
var result = IntPtr.Zero;
User32.EnumWindows(
(hWnd, lParam) =>
{
User32.GetWindowThreadProcessId(hWnd, out var pid);
if (pid == processId
&& User32.GetWindow(hWnd, GetWindowCmd.GW_OWNER) == IntPtr.Zero
&& User32.IsWindowVisible(hWnd))
{
result = hWnd;
return false;
}
return true;
},
IntPtr.Zero
);
return result;
}
}
}
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace ConsoleApp2.WindowsApi
{
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
public enum GetWindowCmd : uint
{
GW_HWNDFIRST,
GW_HWNDLAST,
GW_HWNDNEXT,
GW_HWNDPREV,
GW_OWNER,
GW_CHILD,
GW_ENABLEDPOPUP
}
public class BorrowedDCHandle : DCHandle
{
public IntPtr Hwnd { get; }
public BorrowedDCHandle(IntPtr hwnd, IntPtr hdc)
{
this.Hwnd = hwnd;
this.SetHandle(hdc);
}
protected override bool ReleaseHandle()
{
return User32.ReleaseDC(this.Hwnd, this.handle) == 1;
}
}
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public static class User32
{
public const string DllName = "user32";
[DllImport(DllName, EntryPoint = "EnumWindows", ExactSpelling = true, SetLastError = true)]
private static extern bool _EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public static bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam)
{
var result = _EnumWindows(lpEnumFunc, lParam);
if (!result)
{
var error = Marshal.GetLastWin32Error();
if (error != 0) throw new Win32Exception();
}
return result;
}
[DllImport(DllName, EntryPoint = "GetClientRect", ExactSpelling = true, SetLastError = true)]
private static extern bool _GetClientRect(IntPtr hWnd, out Rect lpRect);
public static Rect GetClientRect(IntPtr hWnd)
{
return _GetClientRect(hWnd, out var rect)
? rect
: throw new Win32Exception();
}
[DllImport(DllName, EntryPoint = "GetDC", ExactSpelling = true)]
private static extern IntPtr _GetDC(IntPtr hWnd);
public static BorrowedDCHandle GetDC(IntPtr hWnd)
{
var result = _GetDC(hWnd);
if (result == IntPtr.Zero) throw new Exception();
return new BorrowedDCHandle(hWnd, result);
}
[DllImport(DllName, EntryPoint = "GetWindow", ExactSpelling = true, SetLastError = true)]
private static extern IntPtr _GetWindow(IntPtr hWnd, GetWindowCmd uCmd);
public static IntPtr GetWindow(IntPtr hWnd, GetWindowCmd uCmd, bool throwIfNull = false)
{
var result = _GetWindow(hWnd, uCmd);
if (result == IntPtr.Zero && throwIfNull)
throw new Win32Exception();
return result;
}
[DllImport(DllName, ExactSpelling = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport(DllName, ExactSpelling = true)]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport(DllName, ExactSpelling = true)]
internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment