Blog / Home
About
Media Gallery

Welcome
to
Thronic.com

ժʝ_

GDI Double Buffer


A custom Win32 window can be made transparent by using the dwExStyle's WS_EX_LAYERED and WS_EX_TRANSPARENT as well as the dwStyle WS_POPUPWINDOW so I'll have a certain frame.
hWnd = CreateWindowEx(
	WS_EX_LAYERED | WS_EX_TRANSPARENT,
	"MainClass",
	"Window Title",
	WS_POPUPWINDOW,
	CW_USEDEFAULT, /* int X position. */
	CW_USEDEFAULT, /* int Y position. */
	240, /* int Width. */
	120, /* int Height. */
	NULL,
	NULL,
	hInstance,
	NULL
);

//
// Sets up our window with the transparency it needs. 
// Styles via CreateWindowEx() takes care of the rest.
// WS_POPUPWINDOW gives us a thin frame.
// WS_EX_LAYERED is needed by the function below.
// WS_EX_TRANSPARENT lets us interact with the game below.
//
SetLayeredWindowAttributes(hWnd, RGB(255, 255, 255), NULL, LWA_COLORKEY);


Never write to another GDI device (your potential overlay target). This will cause flicker and mess with game performance and makes absolutely no sense to do on purpose. Create a simple double buffered algorithm on your own win32 program acting as a layer over the game. GDI is perfectly suitable for representing X,Y data and I include this as it's suitable for entire 2D game developments, not just overlays (e.g. external aimbots). Double buffer sample code:

// Global variables, reuse instead of recreate.
HBRUSH BgBrush;
HFONT font;
HDC hdc, BufferDC;
HBITMAP BufferBitmap;
COLORREF TrueWhite = RGB(255,255,255);

// Our "white whatever" color since true white will be transparent.
COLORREF White = RGB(224,224,224); 

// Example window size.
int cx = 1980;
int cy = 1080;

//
//	Create a double buffer for the drawing. Draw on back-HDC and flip 
//	it later to the front-HDC. Resulting in a single update per frame. 
//
BufferDC = CreateCompatibleDC(hdc);
BufferBitmap = CreateCompatibleBitmap(hdc, cx, cy);
SelectObject(BufferDC, BufferBitmap);

//
//	Overwrite current BG to avoid artifacts from last frame. 
//	Achieve transparency by using same color as used with: 
//	SetLayeredWindowAttributes (in WinMain).
//
BgBrush = CreateSolidBrush(TrueWhite);
FillRect(BufferDC, &GameClientRect, BgBrush);
DeleteObject(BgBrush);

//
// Do your drawing here... 3D/2D Radar/ESP, debug info, etc.
//

//
//	Write the back buffer to the front and clean up.
//
BitBlt(hdc, 0, 0, cx, cy, BufferDC, 0, 0, SRCCOPY);
DeleteObject(BufferBitmap);
DeleteDC(BufferDC);

Then you just move the window over whatever you want and keep it there with a HWND_TOPMOST in the main loop.


Image background example, holding the GDI temporarily
void UpdateProjection()
{
	try {
	//
	//	Hent kontekstkoordinater rett fra spillvindu.
	//	HUSK: GetClientRect() = Vinduoppløsning. 
	//	      GetWindowRect() = Vinduplassering.
	//	
	//	Derfor bruker vi client til å oppdatere bakgrunn med.
	//
	GetClientRect(*ProjectorHwnd, &ClientRect);
	int cx = ClientRect.right;
	int cy = ClientRect.bottom;

	//
	//	Opprett mellomlager for tegning.
	//	Vi lar front-HDC være i fred mens vi tegner på et mellomlager
	//	og flipper over dette når vi er ferdig - kun én oppdatering. 
	//
	if ((hdc = GetDC(*ProjectorHwnd)) == NULL) {
		GetError(L"GetDC failed.");
		exit(EXIT_FAILURE);
	}

	if ((BufferDC = CreateCompatibleDC(hdc)) == NULL) {
		GetError(L"CreateCompatibleDC failed.");
		exit(EXIT_FAILURE);
	}

	/*
	if ((BufferBitmap = CreateCompatibleBitmap(hdc, cx, cy)) == NULL) {
		GetError(L"CreateCompatibleBitmap failed.");
		exit(EXIT_FAILURE);
	}
	*/
	
	if ((BufferBitmap = (HBITMAP)LoadImage(
		GetModuleHandle(NULL), 
		MAKEINTRESOURCE(BG_IMAGE),
		IMAGE_BITMAP,
		0,
		0,
		LR_DEFAULTCOLOR
	)) == NULL) {
		GetError(L"BufferBitmap failed.");
		exit(EXIT_FAILURE);
	}

	if (SelectObject(BufferDC, BufferBitmap) == NULL) {
		GetError(L"SelectObject failed.");
		exit(EXIT_FAILURE);
	}

	//
	//	FOR OVERLEGG (Ref. CSS Prosjektor/ESP).
	//	Overskriv gjeldende bakgrunn for å unngå artifakter fra 
	//	forrige bilde. Vi oppnår gj.siktighet med samme farge 
	//	vi brukte som parameter i SetLayeredWindowAttributes (WinMain).
	//
	//BgBrush = CreateSolidBrush(TrueWhite);
	//FillRect(BufferDC, &ClientRect, BgBrush);
	//DeleteObject(BgBrush);

	// Draw stuff...

	//
	//	Skriv mellomlager til prosjektoren og rydd opp.
	//
	BitBlt(hdc, 0, 0, cx, cy, BufferDC, 0, 0, SRCCOPY);
	DeleteObject(BufferBitmap);
	DeleteDC(BufferDC);
	ReleaseDC(*ProjectorHwnd, hdc);

	} catch(...) { 
		GetError(L"UI error."); 
		exit(EXIT_FAILURE);
	}
}


Transparent background example, holding the GDI globally
It's not advised to hold contexts indefinitely as they are meant to be shared between applications

hdc = GetDC(hWnd);

void UpdateProjection()
{
	//
	//	Hent kontekstkoordinater rett fra spillvindu.
	//	HUSK: GetClientRect() = Vinduoppløsning. 
	//	      GetWindowRect() = Vinduplassering.
	//	
	//	Derfor bruker vi client til å oppdatere bakgrunn med.
	//
	GetClientRect(hProc.__HWNDCss, &GameClientRect);
	int cx = GameClientRect.right;
	int cy = GameClientRect.bottom;

	//
	//	Opprett mellomlager for tegning.
	//	Vi lar front-HDC være i fred mens vi tegner på et mellomlager
	//	og flipper over dette når vi er ferdig - kun én oppdatering. 
	//
	BufferDC = CreateCompatibleDC(hdc);
	BufferBitmap = CreateCompatibleBitmap(hdc, cx, cy);
	SelectObject(BufferDC, BufferBitmap);

	//
	//	Overskriv gjeldende bakgrunn for å unngå artifakter fra 
	//	forrige bilde. Vi oppnår gj.siktighet med samme farge 
	//	vi brukte som parameter i SetLayeredWindowAttributes (WinMain).
	//
	BgBrush = CreateSolidBrush(TrueWhite);
	FillRect(BufferDC, &GameClientRect, BgBrush);
	DeleteObject(BgBrush);
	
	// Draw stuff

	//
	//	Skriv mellomlager til prosjektoren og rydd opp.
	//
	BitBlt(hdc, 0, 0, cx, cy, BufferDC, 0, 0, SRCCOPY);
	DeleteObject(BufferBitmap);
	DeleteDC(BufferDC);
}



Original Post: Jan 27th, '22 19:03 CET.
Updated: Nov 27th, '22 18:08 CET.

C/C++ Windows
π