Base pour l'utilisation du gdi (api windows)

Soyez le premier à donner votre avis sur cette source.

Vue 9 616 fois - Téléchargée 302 fois

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

Ajouter un commentaire Commentaires
Messages postés
77
Date d'inscription
mardi 27 juin 2006
Statut
Membre
Dernière intervention
12 août 2010

Aucune idée, j'ai jamais touché à ces [mode troll on] conneries [mode troll off]. Renseigne-toi pour savoir comment gérer le graphisme avec cette technologie. Je doute que le GDI soit approprié. Bon courage!
Messages postés
29
Date d'inscription
lundi 30 juin 2003
Statut
Membre
Dernière intervention
25 juillet 2014
2
et si l'on veut incoporer dans ce projet une winform crée avec vsc++2008 ? comment on fait s'il vous plait ?
merci d'avance
Messages postés
77
Date d'inscription
mardi 27 juin 2006
Statut
Membre
Dernière intervention
12 août 2010

J'ai regardé ta source. Même si je n'ai jamais fait de MFC, je crois avoir compris l'important : un bitmap est créé au début de l'affichage, il sert à la préparation de l'image, puis il est blité sur le DC de l'application, et enfin il est libéré. Si c'est bien ça, la seule différence avec ma méthode, c'est que le bitmap n'existe que pendant l'affichage. Après, ça dépend de ce qu'on veut faire : si on veut pouvoir modifier l'image à tout moment, on aura intérêt à utiliser ma méthode, si on veux économiser des ressources ce sera plutot la tienne. Mais la mienne à tout de même un avantage certain : on est pas obligé de tout recalculer à chaque affichage, il suffit juste de changer ce que l'on veut. Prenons l'exemple d'un jeu d'échec. Si un joueur bouge une pièce, il suffit de redessiner la case de départ et celle d'arrivée sur le bitmap.
Messages postés
77
Date d'inscription
mardi 27 juin 2006
Statut
Membre
Dernière intervention
12 août 2010

C'est un double buffering dans le sens qu'on n'affiche pas directement ce qu'on prépare. On prépare (ça peut mettre du temps, j'ai des exemples où on met 1mn à préparer l'image), et on affiche tout d'un coup. C'est exactement le principe des jeux vidéos : l'image en préparation est stockée dans un espace mémoire distinct de la mémoire vidéo affichée.
Pour cette source, ça ne se voit pas (j'ai réduit au minimum), mais si on devais réafficher le fond à chaque appui de touche puis afficher l'ellipse, on voit un scintillement si on n'utilise pas cette méthode.
Messages postés
94
Date d'inscription
jeudi 23 novembre 2000
Statut
Membre
Dernière intervention
1 juin 2013

OKI,Comme je l'ai dis dans mon précédent Post j'ai VC2005
donc je ne peux pas utilisé ton projet (.dsw pour VC2005)

Par contre ton Explication m'a bien aidé :
- AVANT j'avais ouvert le .C puis j'ai Cliqué sur Executé
(Je pense qu'il a choisit par défaut une Execution en mode Console

- MAINTENANT j'ai Crée un Projet Win(Graph) et ça marche!!
Merci! Tu m'as mis sur la Voie !
(Mon bug peux aidé certains)

Pour le Programe :
Visuellement, je ne vois pas où est le Double-Buffering !
En regardant rapidement le code, Je pense qu'il s'agirait plutôt d'un 'Single'-Buffering ???

Je vous invite sur :
http://www.cppfrance.com/codes/DOUBLE-BUFFERING-MFC_48812.aspx
C'est en MFC. Pour voir rapidement le Double-Buffering :
Clic Gauche + Glisser la Souris
Presser la Touche 'F5' pour Activé en Direct le Double-Buffering
N'Hésiter pas à laisser un Post sur ce que vous en Pensez
++
Afficher les 34 commentaires

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.