Base pour l'utilisation du gdi (api windows)

Description

J'ai eu assez de mal à savoir commment utiliser le GDI (la solution n'est pas donnée toute faite dans msdn) et donc je voulais faire partager ma très modeste experience.
Comme ça quelqu'un au bord de la crise de nerf pourra toujours copier-coller ce code sans se farcir toutes les docs pour déterminer comment faire marcher ce sacré bazard (API + GDI).

Le GDI, de mon point de vue, est assez compliqué, mais permet de faire tout ce qu'on veux faire pour des application simples, et ne necessite pas d'ajouter des bibliothèques graphiques comme la SDL, OpenGL (je veux parler de GLUT et WGL), GTK, Qt... qui encapsulent carrement l'API Windows. Ainsi le GDI s'integre parfaitement dans une programme basé sur l'API Windows (normal puisqu'il fait partie de l'API Windows), et ne la limite pas dans l'utilisation des controls (menus, boutons, dialog boxes...).

Cette source est juste un départ qui permet d'utiliser un pseudo "double buffering", une technique qui permet de ne pas afficher l'écran pendant la création du dessin. Même dans des application simple, on est rapidement obliger de faire comme ça, sinon c'est vraiment trop moche. Je dis "pseudo" car même si au final ça donne le même effet, on ne procède pas pareil que si on veut faire du vrai "double buffering" : normalement il faut avoir deux buffers video, un qui est affiché ("front buffer") et un dans lequel on compose l'image ("back buffer"), et les permuter quand on veux redessiner l'écran (le "front buffer" devient "back buffer" et vis-versa). Il suffit de permuter deux adresse mémoires, ce qui est extremement rapide.
Avec le GDI, on a pas acces à la mémoire video (ou alors je veux absolument savoir comment), donc il faut ruser. On crée un "memory device context", c'est à dire un espace mémoire qui fait office de "back buffer", et quand on veut réafficher il faut le copier dans la mémoire vidéo affichable. Mais au final, me direz-vous, c'est la même chose : une copie. Mais non, car là il faut copier octet par octet toute la mémoire, et pas seulement changer deux pointeurs. On le fait avce BitBlt(), une fonction du GDI.

Avec cette source, on a également la possiblité d'acceder directement à la mémoire du bitmap et donc de créer ses propres fonctions graphiques sans passer par le très très long SetPixel() ou même SetPixelV(). Pour cela, on utilise CreateDIBSection() au lieu de CreateCompatibleBitmap() lors de la création du bitmap. Encore faut-il que le device (écran) sur lequel on affiche supporte le 24bpp, mais normalement il y a pas de problème...

En résumé, dans ce code de base il y a les deux techniques élementaires qui permettent d'utiliser facilement le GDI.

Evidement, on a pas toutes les performances qu'on peut espérer avec des librairies graphiques comme OpenGL ou DirectX, mais pour autre chose qu'une application graphique, c'est pour moi la meilleur solution.

Source / Exemple :


#include <windows.h>
#include <time.h>
#include <stdlib.h>

#define LARGEUR 600
#define HAUTEUR 400

HBITMAP hBmp;
BITMAP Bmp;
HDC hMDC;					// Memory Device Context

HINSTANCE hInstance;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Note : ne pas oublier de supprimer l'objet avec DeleteObject() quand on n'en a plus besoin.
long BitmapCreation (unsigned long taille_x, unsigned long taille_y, HBITMAP *hBitmap, BITMAP *Bitmap);

int WINAPI WinMain(HINSTANCE hThisHinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hwnd;
	MSG msg;
	WNDCLASS wc;
	RECT Client;

	UNREFERENCED_PARAMETER (hPrevInstance);
	UNREFERENCED_PARAMETER (lpCmdLine);

	hInstance = hThisHinstance;

	wc.style = 0;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
	wc.lpszMenuName =  NULL;
	wc.lpszClassName = "Classe principale";

	if(!RegisterClass(&wc)) return FALSE;

	Client.left = 0;
	Client.top = 0;
	Client.right = LARGEUR;
	Client.bottom = HAUTEUR;
	AdjustWindowRectEx (&Client, WS_OVERLAPPEDWINDOW & (~(WS_THICKFRAME | WS_MAXIMIZEBOX)), FALSE, 0);

	if ((hwnd = CreateWindowEx (
			0,
			"Classe principale",
			"Test de l'utilisation de memory DC pour le \"double buffering\".",
			WS_OVERLAPPEDWINDOW & (~(WS_THICKFRAME | WS_MAXIMIZEBOX)),
			CW_USEDEFAULT, CW_USEDEFAULT,
			Client.right - Client.left, Client.bottom - Client.top,
			NULL, 
			NULL, 
			hInstance, 
			NULL)
		) == NULL
	){
		return FALSE;
	}

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;			// Display Device Context
	PAINTSTRUCT ps;
	HBRUSH hBrush, hOldBrush;

	switch (uMsg)
	{
	case WM_CREATE:
		// Determine le handle du DC
		if ((hDC = GetDC (hwnd)) == NULL) {
			return -1;}

		// Crée un memory DC
		if ((hMDC = CreateCompatibleDC (hDC)) == NULL) {
			return -1;}

		// Crée un bitmap
		if (BitmapCreation (LARGEUR, HAUTEUR, &hBmp, &Bmp) == EXIT_FAILURE) {
			return -1;}

		// Selectionne ce bitmap dans hMDC
		if (SelectObject (hMDC, hBmp) == NULL) {
			return -1;}

		ReleaseDC (hwnd, hDC);

		// Initialise l'affichage
		Ellipse (hMDC, 0, 0, LARGEUR, HAUTEUR);

		// Initialise le générateur de nombres aléatoires
		srand ((unsigned int)time (NULL));

		return 0;

	case WM_DESTROY:
		DeleteDC (hMDC);
		DeleteObject (hBmp);
		PostQuitMessage(0);
		return 0;

	case WM_KEYDOWN:
		// Nouvel affichage dans hMDC
		hBrush = CreateSolidBrush (RGB (rand()%256, rand()%256, rand ()%256));
		hOldBrush = SelectObject (hMDC, hBrush);
		Ellipse (hMDC, 0, 0, LARGEUR, HAUTEUR);
		SelectObject (hMDC, hOldBrush);
		DeleteObject (hBrush);

		// Raffraichit l'écran
		RedrawWindow (hwnd, NULL, NULL, RDW_INVALIDATE);
		return 0;

	case WM_PAINT:
		// Copie hMDC dans hDC
		hDC = BeginPaint (hwnd, &ps);
		BitBlt (hDC, 0, 0, LARGEUR, HAUTEUR, hMDC, 0, 0, SRCCOPY);
		EndPaint (hwnd, &ps);
		return 0;

	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
}

long BitmapCreation (unsigned long taille_x, unsigned long taille_y, HBITMAP *hBitmap, BITMAP *Bitmap)
{
	BITMAPINFO BitmapInfo;
	void *tmp;

	// Crée le bitmap
	BitmapInfo.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
	BitmapInfo.bmiHeader.biWidth = taille_x;
	BitmapInfo.bmiHeader.biHeight = taille_y;
	BitmapInfo.bmiHeader.biPlanes = 1;
	BitmapInfo.bmiHeader.biBitCount = 32;
	BitmapInfo.bmiHeader.biCompression = BI_RGB;
	BitmapInfo.bmiHeader.biSizeImage = taille_x * taille_y * 4;
	BitmapInfo.bmiHeader.biXPelsPerMeter = 0;
	BitmapInfo.bmiHeader.biYPelsPerMeter = 0;
	BitmapInfo.bmiHeader.biClrUsed = 0;
	BitmapInfo.bmiHeader.biClrImportant = 0;
	if ((*hBitmap = CreateDIBSection (NULL, &BitmapInfo, DIB_RGB_COLORS, &tmp, NULL, 0)) == NULL) {
		return EXIT_FAILURE;
	}

	// Obtient la structure BITMAP
	if (GetObject (*hBitmap, sizeof (BITMAP), Bitmap) != sizeof (BITMAP)){
		DeleteObject (*hBitmap);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

Conclusion :


Vous aurez sans doute remarqué, si vous avez testé cette source, qu'on peut constater un effet assez désagréable : de temps en temps on peut apercevoir un trait horizontal lors du réaffichage de l'écran.
Selon moi c'est un problème de syncronisation verticale, comme dans les jeux vidéos : BitBlt() n'a pas fini de copier hMDC dans hDC quand l'écran affiche physiquement l'image. (J'ai un écran LCD réglé à 60Hz.)
Dites moi si on peut régler ça.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.